com.facebook.buck.zip.Unzip.java Source code

Java tutorial

Introduction

Here is the source code for com.facebook.buck.zip.Unzip.java

Source

/*
 * Copyright 2013-present Facebook, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may
 * not use this file except in compliance with the License. You may obtain
 * a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */

package com.facebook.buck.zip;

import com.facebook.buck.io.MoreFiles;
import com.facebook.buck.io.MorePosixFilePermissions;
import com.facebook.buck.io.ProjectFilesystem;
import com.facebook.buck.util.MoreCollectors;
import com.google.common.base.Charsets;
import com.google.common.collect.ImmutableList;
import com.google.common.io.ByteStreams;

import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipFile;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.PosixFilePermission;
import java.util.Enumeration;
import java.util.Set;

public class Unzip {

    /** Utility class: do not instantiate. */
    private Unzip() {
    }

    public enum ExistingFileMode {
        OVERWRITE, OVERWRITE_AND_CLEAN_DIRECTORIES,
    }

    /**
     * Unzips a file to a destination and returns the paths of the written files.
     */
    public static ImmutableList<Path> extractZipFile(Path zipFile, ProjectFilesystem filesystem, Path relativePath,
            ExistingFileMode existingFileMode) throws IOException {
        // if requested, clean before extracting
        if (existingFileMode == ExistingFileMode.OVERWRITE_AND_CLEAN_DIRECTORIES) {
            try (ZipFile zip = new ZipFile(zipFile.toFile())) {
                Enumeration<ZipArchiveEntry> entries = zip.getEntries();
                while (entries.hasMoreElements()) {
                    ZipArchiveEntry entry = entries.nextElement();
                    filesystem.deleteRecursivelyIfExists(relativePath.resolve(entry.getName()));
                }
            }
        }
        ImmutableList.Builder<Path> filesWritten = ImmutableList.builder();
        try (ZipFile zip = new ZipFile(zipFile.toFile())) {
            Enumeration<ZipArchiveEntry> entries = zip.getEntries();
            while (entries.hasMoreElements()) {
                ZipArchiveEntry entry = entries.nextElement();
                String fileName = entry.getName();
                Path target = relativePath.resolve(fileName);

                // TODO(bolinfest): Keep track of which directories have already been written to avoid
                // making unnecessary Files.createDirectories() calls. In practice, a single zip file will
                // have many entries in the same directory.

                if (entry.isDirectory()) {
                    // Create the directory and all its parent directories
                    filesystem.mkdirs(target);
                } else {
                    // Create parent folder
                    filesystem.createParentDirs(target);

                    filesWritten.add(target);
                    // Write file
                    try (InputStream is = zip.getInputStream(entry)) {
                        if (entry.isUnixSymlink()) {
                            filesystem.createSymLink(target,
                                    filesystem.getPath(new String(ByteStreams.toByteArray(is), Charsets.UTF_8)),
                                    /* force */ true);
                        } else {
                            try (OutputStream out = filesystem.newFileOutputStream(target)) {
                                ByteStreams.copy(is, out);
                            }
                        }
                    }

                    // restore mtime for the file
                    filesystem.resolve(target).toFile().setLastModified(entry.getTime());

                    // TODO(shs96c): Implement what the comment below says we should do.
                    //
                    // Sets the file permissions of the output file given the information in {@code entry}'s
                    // extra data field. According to the docs at
                    // http://www.opensource.apple.com/source/zip/zip-6/unzip/unzip/proginfo/extra.fld there
                    // are two extensions that might support file permissions: Acorn and ASi UNIX. We shall
                    // assume that inputs are not from an Acorn SparkFS. The relevant section from the docs:
                    //
                    // <pre>
                    //    The following is the layout of the ASi extra block for Unix.  The
                    //    local-header and central-header versions are identical.
                    //    (Last Revision 19960916)
                    //
                    //    Value         Size        Description
                    //    -----         ----        -----------
                    //   (Unix3) 0x756e        Short       tag for this extra block type ("nu")
                    //   TSize         Short       total data size for this block
                    //   CRC           Long        CRC-32 of the remaining data
                    //   Mode          Short       file permissions
                    //   SizDev        Long        symlink'd size OR major/minor dev num
                    //   UID           Short       user ID
                    //   GID           Short       group ID
                    //   (var.)        variable    symbolic link filename
                    //
                    //   Mode is the standard Unix st_mode field from struct stat, containing
                    //   user/group/other permissions, setuid/setgid and symlink info, etc.
                    // </pre>
                    //
                    // From the stat man page, we see that the following mask values are defined for the file
                    // permissions component of the st_mode field:
                    //
                    // <pre>
                    //   S_ISUID   0004000   set-user-ID bit
                    //   S_ISGID   0002000   set-group-ID bit (see below)
                    //   S_ISVTX   0001000   sticky bit (see below)
                    //
                    //   S_IRWXU     00700   mask for file owner permissions
                    //
                    //   S_IRUSR     00400   owner has read permission
                    //   S_IWUSR     00200   owner has write permission
                    //   S_IXUSR     00100   owner has execute permission
                    //
                    //   S_IRWXG     00070   mask for group permissions
                    //   S_IRGRP     00040   group has read permission
                    //   S_IWGRP     00020   group has write permission
                    //   S_IXGRP     00010   group has execute permission
                    //
                    //   S_IRWXO     00007   mask for permissions for others
                    //   (not in group)
                    //   S_IROTH     00004   others have read permission
                    //   S_IWOTH     00002   others have write permission
                    //   S_IXOTH     00001   others have execute permission
                    // </pre>
                    //
                    // For the sake of our own sanity, we're going to assume that no-one is using symlinks,
                    // but we'll check and throw if they are.
                    //
                    // Before we do anything, we should check the header ID. Pfft!
                    //
                    // Having jumped through all these hoops, it turns out that InfoZIP's "unzip" store the
                    // values in the external file attributes of a zip entry (found in the zip's central
                    // directory) assuming that the OS creating the zip was one of an enormous list that
                    // includes UNIX but not Windows, it first searches for the extra fields, and if not found
                    // falls through to a code path that supports MS-DOS and which stores the UNIX file
                    // attributes in the upper 16 bits of the external attributes field.
                    //
                    // We'll support neither approach fully, but we encode whether this file was executable
                    // via storing 0100 in the fields that are typically used by zip implementations to store
                    // POSIX permissions. If we find it was executable, use the platform independent java
                    // interface to make this unpacked file executable.

                    Set<PosixFilePermission> permissions = MorePosixFilePermissions
                            .fromMode(entry.getExternalAttributes() >> 16);
                    if (permissions.contains(PosixFilePermission.OWNER_EXECUTE)) {
                        MoreFiles.makeExecutable(filesystem.resolve(target));
                    }
                }
            }
        }
        return filesWritten.build();
    }

    public static ImmutableList<Path> extractZipFile(Path zipFile, ProjectFilesystem filesystem,
            ExistingFileMode existingFileMode) throws IOException {
        return extractZipFile(zipFile, filesystem, filesystem.getPath(""), existingFileMode);
    }

    public static ImmutableList<Path> extractZipFile(Path zipFile, final Path destination,
            ExistingFileMode existingFileMode) throws IOException {
        // Create output directory if it does not exist
        Files.createDirectories(destination);
        return extractZipFile(zipFile, new ProjectFilesystem(destination), destination.getFileSystem().getPath(""),
                existingFileMode).stream().map(input -> destination.resolve(input).toAbsolutePath())
                        .collect(MoreCollectors.toImmutableList());
    }

}