diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/objc_tools/bundlemerge/java/com/google/devtools/build/xcode/bundlemerge/BundleMerging.java | 13 | ||||
-rw-r--r-- | src/tools/xcode-common/java/com/google/devtools/build/xcode/zip/ZipFiles.java | 153 |
2 files changed, 163 insertions, 3 deletions
diff --git a/src/objc_tools/bundlemerge/java/com/google/devtools/build/xcode/bundlemerge/BundleMerging.java b/src/objc_tools/bundlemerge/java/com/google/devtools/build/xcode/bundlemerge/BundleMerging.java index 20b8b72c7d..69c00d3f9c 100644 --- a/src/objc_tools/bundlemerge/java/com/google/devtools/build/xcode/bundlemerge/BundleMerging.java +++ b/src/objc_tools/bundlemerge/java/com/google/devtools/build/xcode/bundlemerge/BundleMerging.java @@ -30,6 +30,7 @@ import com.google.devtools.build.xcode.common.Platform; import com.google.devtools.build.xcode.common.TargetDeviceFamily; import com.google.devtools.build.xcode.plmerge.KeysToRemoveIfEmptyString; import com.google.devtools.build.xcode.plmerge.PlistMerging; +import com.google.devtools.build.xcode.zip.ZipFiles; import com.google.devtools.build.xcode.zip.ZipInputEntry; import java.io.IOException; @@ -37,6 +38,7 @@ import java.io.OutputStream; import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Map; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; @@ -184,15 +186,20 @@ public final class BundleMerging { */ private void addEntriesFromOtherZip(ZipCombiner combiner, Path sourceZip, String entryNamesPrefix) throws IOException { + Map<String, Integer> externalFileAttributes = ZipFiles.unixExternalFileAttributes(sourceZip); try (ZipInputStream zipIn = new ZipInputStream(Files.newInputStream(sourceZip))) { while (true) { ZipEntry zipInEntry = zipIn.getNextEntry(); if (zipInEntry == null) { break; } - // TODO(bazel-team): preserve the external file attribute field in the source zip entry. - combiner.addFile(entryNamesPrefix + zipInEntry.getName(), DOS_EPOCH, zipIn, - ZipInputEntry.DEFAULT_DIRECTORY_ENTRY_INFO); + Integer externalFileAttr = externalFileAttributes.get(zipInEntry.getName()); + if (externalFileAttr == null) { + externalFileAttr = ZipInputEntry.DEFAULT_EXTERNAL_FILE_ATTRIBUTE; + } + combiner.addFile( + entryNamesPrefix + zipInEntry.getName(), DOS_EPOCH, zipIn, + ZipInputEntry.DEFAULT_DIRECTORY_ENTRY_INFO.withExternalFileAttribute(externalFileAttr)); } } } diff --git a/src/tools/xcode-common/java/com/google/devtools/build/xcode/zip/ZipFiles.java b/src/tools/xcode-common/java/com/google/devtools/build/xcode/zip/ZipFiles.java new file mode 100644 index 0000000000..21d64699f7 --- /dev/null +++ b/src/tools/xcode-common/java/com/google/devtools/build/xcode/zip/ZipFiles.java @@ -0,0 +1,153 @@ +// Copyright 2014 Google Inc. 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() {} +} |