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.net.URI;
022import java.net.URISyntaxException;
023import java.net.URL;
024import java.nio.file.FileSystems;
025import java.nio.file.Files;
026import java.nio.file.Path;
027import java.nio.file.attribute.GroupPrincipal;
028import java.nio.file.attribute.PosixFileAttributeView;
029import java.nio.file.attribute.PosixFilePermission;
030import java.nio.file.attribute.UserPrincipal;
031import java.nio.file.attribute.UserPrincipalLookupService;
032import java.util.Objects;
033import java.util.Set;
034
035import org.apache.logging.log4j.Logger;
036import org.apache.logging.log4j.status.StatusLogger;
037
038/**
039 * File utilities.
040 */
041public final class FileUtils {
042
043    /** Constant for the file URL protocol. */
044    private static final String PROTOCOL_FILE = "file";
045
046    private static final String JBOSS_FILE = "vfsfile";
047
048    private static final Logger LOGGER = StatusLogger.getLogger();
049
050    private FileUtils() {
051    }
052
053    /**
054     * Tries to convert the specified URI to a file object. If this fails, <b>null</b> is returned.
055     *
056     * @param uri the URI
057     * @return the resulting file object
058     */
059    public static File fileFromUri(URI uri) {
060        if (uri == null) {
061            return null;
062        }
063        if (uri.isAbsolute()) {
064            if (JBOSS_FILE.equals(uri.getScheme())) {
065                try {
066                    // patch the scheme
067                    uri = new URI(PROTOCOL_FILE, uri.getSchemeSpecificPart(), uri.getFragment());
068                } catch (URISyntaxException use) {
069                    // should not happen, ignore
070                }
071            }
072            try {
073                if (PROTOCOL_FILE.equals(uri.getScheme())) {
074                    return new File(uri);
075                }
076            } catch (final Exception ex) {
077                LOGGER.warn("Invalid URI {}", uri);
078            }
079        } else {
080            File file = new File(uri.toString());
081            try {
082                if (file.exists()) {
083                    return file;
084                }
085                final String path = uri.getPath();
086                return new File(path);
087            } catch (final Exception ex) {
088                LOGGER.warn("Invalid URI {}", uri);
089            }
090        }
091        return null;
092    }
093
094    public static boolean isFile(final URL url) {
095        return url != null && (url.getProtocol().equals(PROTOCOL_FILE) || url.getProtocol().equals(JBOSS_FILE));
096    }
097
098    public static String getFileExtension(final File file) {
099        final String fileName = file.getName();
100        if (fileName.lastIndexOf(".") != -1 && fileName.lastIndexOf(".") != 0) {
101            return fileName.substring(fileName.lastIndexOf(".") + 1);
102        }
103        return null;
104    }
105
106    /**
107     * Asserts that the given directory exists and creates it if necessary.
108     * 
109     * @param dir the directory that shall exist
110     * @param createDirectoryIfNotExisting specifies if the directory shall be created if it does not exist.
111     * @throws java.io.IOException thrown if the directory could not be created.
112     */
113    public static void mkdir(final File dir, final boolean createDirectoryIfNotExisting) throws IOException {
114        // commons io FileUtils.forceMkdir would be useful here, we just want to omit this dependency
115        if (!dir.exists()) {
116            if (!createDirectoryIfNotExisting) {
117                throw new IOException("The directory " + dir.getAbsolutePath() + " does not exist.");
118            }
119            if (!dir.mkdirs()) {
120                throw new IOException("Could not create directory " + dir.getAbsolutePath());
121            }
122        }
123        if (!dir.isDirectory()) {
124            throw new IOException("File " + dir + " exists and is not a directory. Unable to create directory.");
125        }
126    }
127    
128    /**
129     * Creates the parent directories for the given File.
130     * 
131     * @param file
132     * @throws IOException
133     */
134    public static void makeParentDirs(final File file) throws IOException {
135        final File parent = Objects.requireNonNull(file, "file").getCanonicalFile().getParentFile();
136        if (parent != null) {
137            mkdir(parent, true);
138        }
139    }
140
141    /**
142     * Define file posix attribute view on a path/file.
143     *
144     * @param path Target path
145     * @param filePermissions Permissions to apply
146     * @param fileOwner File owner
147     * @param fileGroup File group
148     * @throws IOException If IO error during definition of file attribute view
149     */
150    public static void defineFilePosixAttributeView(final Path path,
151            final Set<PosixFilePermission> filePermissions,
152            final String fileOwner,
153            final String fileGroup) throws IOException {
154        final PosixFileAttributeView view = Files.getFileAttributeView(path, PosixFileAttributeView.class);
155        if (view != null) {
156            final UserPrincipalLookupService lookupService = FileSystems.getDefault()
157                    .getUserPrincipalLookupService();
158            if (fileOwner != null) {
159                final UserPrincipal userPrincipal = lookupService.lookupPrincipalByName(fileOwner);
160                if (userPrincipal != null) {
161                    // If not sudoers member, it will throw Operation not permitted
162                    // Only processes with an effective user ID equal to the user ID
163                    // of the file or with appropriate privileges may change the ownership of a file.
164                    // If _POSIX_CHOWN_RESTRICTED is in effect for path
165                    view.setOwner(userPrincipal);
166                }
167            }
168            if (fileGroup != null) {
169                final GroupPrincipal groupPrincipal = lookupService.lookupPrincipalByGroupName(fileGroup);
170                if (groupPrincipal != null) {
171                    // The current user id should be members of this group,
172                    // if not will raise Operation not permitted
173                    view.setGroup(groupPrincipal);
174                }
175            }
176            if (filePermissions != null) {
177                view.setPermissions(filePermissions);
178            }
179        }
180    }
181
182    /**
183     * Check if posix file attribute view is supported on the default FileSystem.
184     *
185     * @return true if posix file attribute view supported, false otherwise
186     */
187    public static boolean isFilePosixAttributeViewSupported() {
188        return FileSystems.getDefault().supportedFileAttributeViews().contains("posix");
189    }
190}