com.google.devtools.build.xcode.zip.ZipFiles.java Source code

Java tutorial

Introduction

Here is the source code for com.google.devtools.build.xcode.zip.ZipFiles.java

Source

// Copyright 2014 The Bazel Authors. All rights reserved.
//
// 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.google.devtools.build.xcode.zip;

import com.google.common.collect.ImmutableMap;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.SeekableByteChannel;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Map;

/**
 * Utility code for reading information from zip files.
 */
public final class ZipFiles {
    /** Read a little-endian integer comprised of {@code count} bytes from the input channel. */
    private static int readBytes(int count, ReadableByteChannel input) throws IOException {
        ByteBuffer buffer = ByteBuffer.allocate(count);
        if (input.read(buffer) != count) {
            throw new IOException("could not read expected number of bytes: " + count);
        }
        int result = 0;
        for (int i = count - 1; i >= 0; i--) {
            result <<= 8;
            result |= buffer.get(i) & 0xff;
        }
        return result;
    }

    /**
     * Returns the external file attributes of each entry as a mapping from the entry name to the
     * 32-bit value. As long as the attributes are generated by a Unix host, this includes the POSIX
     * file permissions in the upper two bytes. Entries not generated by a Unix host are not included
     * in the result.
     */
    public static Map<String, Integer> unixExternalFileAttributes(Path zipFile) throws IOException {
        // Field descriptions in comments were taken from this document:
        // http://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT
        ImmutableMap.Builder<String, Integer> attributes = new ImmutableMap.Builder<>();
        try (SeekableByteChannel input = Files.newByteChannel(zipFile)) {
            // The data we care about is toward the end of the file, after the compressed data for each
            // file. We begin by looking for the start of the end of central directory record, which is
            // marked by the signature 0x06054b50
            //
            // This contains the centralDirectoryStartOffset value, which tells us where to seek to find
            // the first central directory entry. Each such entry is marked by the signature 0x02014b50
            // and appear in sequence, one entry for each file in the .zip.
            //
            // The central directory entry contains many values, including the file name, the external
            // file attributes, and the version made by value. If the version made by indicates a Unix
            // host (0x03??), we include the external file attributes in the returned map.

            long offset = input.size() - 4;
            while (offset >= 0) {
                input.position(offset);
                int signature = readBytes(4, input);
                if (signature == 0x06054b50) {
                    break;
                } else if (signature == 0x06064b50) {
                    throw new IOException("Zip64 format not supported: " + zipFile);
                }
                offset--;
            }
            if (offset < 0) {
                throw new IOException();
            }

            // Read end of central directory structure
            input.position(input.position() + 2 // number of this disk
                    + 2 // number of the disk with the start of the central directory
            );
            int entryCount = readBytes(2, input);
            input.position(input.position() + 2 // total number of entries in the central directory
            );
            input.position(input.position() + 4 // size of the central directory
            );
            int centralDirectoryStartOffset = readBytes(4, input);
            if (0xffffffff == centralDirectoryStartOffset) {
                throw new IOException("Zip64 format not supported.");
            }

            input.position(centralDirectoryStartOffset);
            int entriesFound = 0;

            // Read each central directory entry
            while ((entriesFound < entryCount) && (readBytes(4, input) == 0x02014b50)) {
                int versionMadeBy = readBytes(2, input);
                input.position(input.position() + 2 // version needed to extract
                        + 2 // general purpose bit flag
                        + 2 // compression method
                        + 2 // last mod file time
                        + 2 // last mod file date
                        + 4 // crc-32
                        + 4 // compressed size
                        + 4 // uncompressed size
                );
                int filenameLength = readBytes(2, input);
                int extraFieldLength = readBytes(2, input);
                int fileCommentLength = readBytes(2, input);
                input.position(input.position() + 2 // disk number start
                        + 2 // internal file attributes
                );
                int externalFileAttributes = readBytes(4, input);
                input.position(input.position() + 4 // relative offset of local header
                );
                ByteBuffer filenameBuffer = ByteBuffer.allocate(filenameLength);
                if (filenameLength != input.read(filenameBuffer)) {
                    throw new IOException(String.format(
                            "Could not read file name (length %d) in central directory record", filenameLength));
                }
                input.position(input.position() + extraFieldLength + fileCommentLength);
                entriesFound++;
                if ((versionMadeBy >> 8) == 3) {
                    // Zip made by a Unix host - the external file attributes are POSIX permissions.
                    String filename = new String(filenameBuffer.array(), StandardCharsets.UTF_8);
                    attributes.put(filename, externalFileAttributes);
                }
            }
            if (entriesFound != entryCount) {
                System.err.printf(
                        "WARNING: Expected %d entries in central directory record in '%s', but found %d\n",
                        entryCount, zipFile, entriesFound);
            }
        }
        return attributes.build();
    }

    private ZipFiles() {
    }
}