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}