001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache license, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the license for the specific language governing permissions and 015 * limitations under the license. 016 */ 017package org.apache.logging.log4j.core.util; 018 019import java.io.File; 020import java.io.IOException; 021import java.io.UnsupportedEncodingException; 022import java.net.MalformedURLException; 023import java.net.URI; 024import java.net.URL; 025import java.net.URLDecoder; 026import java.nio.charset.StandardCharsets; 027import java.nio.file.FileSystems; 028import java.nio.file.Files; 029import java.nio.file.Path; 030import java.nio.file.attribute.GroupPrincipal; 031import java.nio.file.attribute.PosixFileAttributeView; 032import java.nio.file.attribute.PosixFilePermission; 033import java.nio.file.attribute.UserPrincipal; 034import java.nio.file.attribute.UserPrincipalLookupService; 035import java.util.Objects; 036import java.util.Set; 037 038import org.apache.logging.log4j.Logger; 039import org.apache.logging.log4j.status.StatusLogger; 040 041/** 042 * File utilities. 043 */ 044public final class FileUtils { 045 046 /** Constant for the file URL protocol. */ 047 private static final String PROTOCOL_FILE = "file"; 048 049 private static final String JBOSS_FILE = "vfsfile"; 050 051 private static final Logger LOGGER = StatusLogger.getLogger(); 052 053 private FileUtils() { 054 } 055 056 /** 057 * Tries to convert the specified URI to a file object. If this fails, <b>null</b> is returned. 058 * 059 * @param uri the URI 060 * @return the resulting file object 061 */ 062 public static File fileFromUri(URI uri) { 063 // There MUST be a better way to do this. TODO Search other ASL projects... 064 if (uri == null || (uri.getScheme() != null 065 && (!PROTOCOL_FILE.equals(uri.getScheme()) && !JBOSS_FILE.equals(uri.getScheme())))) { 066 return null; 067 } 068 if (uri.getScheme() == null) { 069 File file = new File(uri.toString()); 070 if (file.exists()) { 071 return file; 072 } 073 try { 074 final String path = uri.getPath(); 075 file = new File(path); 076 if (file.exists()) { 077 return file; 078 } 079 uri = new File(path).toURI(); 080 } catch (final Exception ex) { 081 LOGGER.warn("Invalid URI {}", uri); 082 return null; 083 } 084 } 085 final String charsetName = StandardCharsets.UTF_8.name(); 086 try { 087 String fileName = uri.toURL().getFile(); 088 if (new File(fileName).exists()) { // LOG4J2-466 089 return new File(fileName); // allow files with '+' char in name 090 } 091 fileName = URLDecoder.decode(fileName, charsetName); 092 return new File(fileName); 093 } catch (final MalformedURLException ex) { 094 LOGGER.warn("Invalid URL {}", uri, ex); 095 } catch (final UnsupportedEncodingException uee) { 096 LOGGER.warn("Invalid encoding: {}", charsetName, uee); 097 } 098 return null; 099 } 100 101 public static boolean isFile(final URL url) { 102 return url != null && (url.getProtocol().equals(PROTOCOL_FILE) || url.getProtocol().equals(JBOSS_FILE)); 103 } 104 105 public static String getFileExtension(final File file) { 106 final String fileName = file.getName(); 107 if (fileName.lastIndexOf(".") != -1 && fileName.lastIndexOf(".") != 0) { 108 return fileName.substring(fileName.lastIndexOf(".") + 1); 109 } 110 return null; 111 } 112 113 /** 114 * Asserts that the given directory exists and creates it if necessary. 115 * 116 * @param dir the directory that shall exist 117 * @param createDirectoryIfNotExisting specifies if the directory shall be created if it does not exist. 118 * @throws java.io.IOException thrown if the directory could not be created. 119 */ 120 public static void mkdir(final File dir, final boolean createDirectoryIfNotExisting) throws IOException { 121 // commons io FileUtils.forceMkdir would be useful here, we just want to omit this dependency 122 if (!dir.exists()) { 123 if (!createDirectoryIfNotExisting) { 124 throw new IOException("The directory " + dir.getAbsolutePath() + " does not exist."); 125 } 126 if (!dir.mkdirs()) { 127 throw new IOException("Could not create directory " + dir.getAbsolutePath()); 128 } 129 } 130 if (!dir.isDirectory()) { 131 throw new IOException("File " + dir + " exists and is not a directory. Unable to create directory."); 132 } 133 } 134 135 /** 136 * Creates the parent directories for the given File. 137 * 138 * @param file 139 * @throws IOException 140 */ 141 public static void makeParentDirs(final File file) throws IOException { 142 final File parent = Objects.requireNonNull(file, "file").getCanonicalFile().getParentFile(); 143 if (parent != null) { 144 mkdir(parent, true); 145 } 146 } 147 148 /** 149 * Define file posix attribute view on a path/file. 150 * 151 * @param path Target path 152 * @param filePermissions Permissions to apply 153 * @param fileOwner File owner 154 * @param fileGroup File group 155 * @throws IOException If IO error during definition of file attribute view 156 */ 157 public static void defineFilePosixAttributeView(final Path path, 158 final Set<PosixFilePermission> filePermissions, 159 final String fileOwner, 160 final String fileGroup) throws IOException { 161 final PosixFileAttributeView view = Files.getFileAttributeView(path, PosixFileAttributeView.class); 162 if (view != null) { 163 final UserPrincipalLookupService lookupService = FileSystems.getDefault() 164 .getUserPrincipalLookupService(); 165 if (fileOwner != null) { 166 final UserPrincipal userPrincipal = lookupService.lookupPrincipalByName(fileOwner); 167 if (userPrincipal != null) { 168 // If not sudoers member, it will throw Operation not permitted 169 // Only processes with an effective user ID equal to the user ID 170 // of the file or with appropriate privileges may change the ownership of a file. 171 // If _POSIX_CHOWN_RESTRICTED is in effect for path 172 view.setOwner(userPrincipal); 173 } 174 } 175 if (fileGroup != null) { 176 final GroupPrincipal groupPrincipal = lookupService.lookupPrincipalByGroupName(fileGroup); 177 if (groupPrincipal != null) { 178 // The current user id should be members of this group, 179 // if not will raise Operation not permitted 180 view.setGroup(groupPrincipal); 181 } 182 } 183 if (filePermissions != null) { 184 view.setPermissions(filePermissions); 185 } 186 } 187 } 188 189 /** 190 * Check if posix file attribute view is supported on the default FileSystem. 191 * 192 * @return true if posix file attribute view supported, false otherwise 193 */ 194 public static boolean isFilePosixAttributeViewSupported() { 195 return FileSystems.getDefault().supportedFileAttributeViews().contains("posix"); 196 } 197}