| // Copyright 2015 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.zip; |
| |
| import com.google.devtools.build.zip.ZipFileEntry.Feature; |
| |
| import java.nio.charset.Charset; |
| import java.util.Collection; |
| import java.util.LinkedHashMap; |
| import java.util.Map; |
| import java.util.zip.ZipException; |
| |
| import javax.annotation.Nullable; |
| |
| /** |
| * A representation of a ZIP file. Contains the file comment, encoding, and entries. Also contains |
| * internal information about the structure and location of ZIP file parts. |
| */ |
| class ZipFileData { |
| private final Charset charset; |
| private String comment; |
| |
| private long centralDirectorySize; |
| private long centralDirectoryOffset; |
| private long expectedEntries; |
| private long numEntries; |
| private final Map<String, ZipFileEntry> entries; |
| |
| private boolean maybeZip64; |
| private boolean isZip64; |
| private long zip64EndOfCentralDirectoryOffset; |
| |
| /** |
| * Creates a new ZIP file with the specified charset encoding. |
| */ |
| public ZipFileData(Charset charset) { |
| if (charset == null) { |
| throw new NullPointerException(); |
| } |
| this.charset = charset; |
| comment = ""; |
| entries = new LinkedHashMap<>(); |
| } |
| |
| /** |
| * Returns the encoding of the file. |
| */ |
| public Charset getCharset() { |
| return charset; |
| } |
| |
| /** |
| * Returns the file comment. |
| */ |
| public String getComment() { |
| return comment; |
| } |
| |
| /** |
| * Sets the file comment from the raw byte data in the file. Converts the bytes to a string using |
| * the file's charset encoding. |
| * |
| * @throws ZipException if the comment is longer than allowed by the ZIP format |
| */ |
| public void setComment(byte[] comment) throws ZipException { |
| if (comment == null) { |
| throw new NullPointerException(); |
| } |
| if (comment.length > 0xffff) { |
| throw new ZipException(String.format("File comment too long. Is %d; max %d.", |
| comment.length, 0xffff)); |
| } |
| this.comment = fromBytes(comment); |
| } |
| |
| /** |
| * Sets the file comment. |
| * |
| * @throws ZipException if the comment will be longer than allowed by the ZIP format when encoded |
| * using the file's charset encoding |
| */ |
| public void setComment(String comment) throws ZipException { |
| setComment(getBytes(comment)); |
| } |
| |
| /** |
| * Returns the size of the central directory in bytes. |
| */ |
| public long getCentralDirectorySize() { |
| return centralDirectorySize; |
| } |
| |
| /** |
| * Sets the size of the central directory in bytes. If the size is larger than 0xffffffff, the |
| * file is set to Zip64 mode. |
| * |
| * <p>See <a href="http://www.pkware.com/documents/casestudies/APPNOTE.TXT">ZIP Format</a> |
| * section 4.4.23 |
| */ |
| public void setCentralDirectorySize(long centralDirectorySize) { |
| this.centralDirectorySize = centralDirectorySize; |
| if (centralDirectorySize > 0xffffffffL) { |
| setZip64(true); |
| } |
| } |
| |
| /** |
| * Returns the file offset of the start of the central directory. |
| */ |
| public long getCentralDirectoryOffset() { |
| return centralDirectoryOffset; |
| } |
| |
| /** |
| * Sets the file offset of the start of the central directory. If the offset is larger than |
| * 0xffffffff, the file is set to Zip64 mode. |
| * |
| * <p>See <a href="http://www.pkware.com/documents/casestudies/APPNOTE.TXT">ZIP Format</a> |
| * section 4.4.24 |
| */ |
| public void setCentralDirectoryOffset(long offset) { |
| this.centralDirectoryOffset = offset; |
| if (centralDirectoryOffset > 0xffffffffL) { |
| setZip64(true); |
| } |
| } |
| |
| /** |
| * Returns the number of entries expected to be in the ZIP file. This value is determined from the |
| * end of central directory record. |
| */ |
| public long getExpectedEntries() { |
| return expectedEntries; |
| } |
| |
| /** |
| * Sets the number of entries expected to be in the ZIP file. This value should be set by reading |
| * the end of central directory record. |
| * |
| * <p>See <a href="http://www.pkware.com/documents/casestudies/APPNOTE.TXT">ZIP Format</a> |
| * section 4.4.22 |
| */ |
| public void setExpectedEntries(long count) { |
| this.expectedEntries = count; |
| if (expectedEntries > 0xffff) { |
| setZip64(true); |
| } |
| } |
| |
| /** |
| * Returns the number of entries actually in the ZIP file. This value is derived from the number |
| * of times {@link #addEntry(ZipFileEntry)} was called. |
| * |
| * <p><em>NOTE:</em> This value should be used rather than getting the size from the |
| * {@link Collection} returned from {@link #getEntries()}, because the value may be too large to |
| * be properly represented by an int. |
| */ |
| public long getNumEntries() { |
| return numEntries; |
| } |
| |
| /** |
| * Sets the number of entries actually in the ZIP file. If the value is larger than 0xffff, the |
| * file is set to Zip64 mode. |
| */ |
| private void setNumEntries(long numEntries) { |
| this.numEntries = numEntries; |
| if (numEntries > 0xffff) { |
| setZip64(true); |
| } |
| } |
| |
| /** |
| * Returns a collection of all entries in the ZIP file. |
| */ |
| public Collection<ZipFileEntry> getEntries() { |
| return entries.values(); |
| } |
| |
| /** |
| * Returns the entry with the given name, or null if it does not exist. |
| */ |
| public ZipFileEntry getEntry(@Nullable String name) { |
| return entries.get(name); |
| } |
| |
| /** |
| * Adds an entry to the ZIP file. If this causes the actual number of entries to exceed |
| * 0xffffffff, or if the file requires Zip64 features, the file is set to Zip64 mode. |
| */ |
| public void addEntry(ZipFileEntry entry) { |
| entries.put(entry.getName(), entry); |
| setNumEntries(numEntries + 1); |
| if (entry.getFeatureSet().contains(Feature.ZIP64_SIZE) |
| || entry.getFeatureSet().contains(Feature.ZIP64_CSIZE) |
| || entry.getFeatureSet().contains(Feature.ZIP64_OFFSET)) { |
| setZip64(true); |
| } |
| } |
| |
| /** |
| * Returns if the file may be in Zip64 mode. This is true if any of the values in the end of |
| * central directory record are -1. |
| * |
| * <p>See <a href="http://www.pkware.com/documents/casestudies/APPNOTE.TXT">ZIP Format</a> |
| * section 4.4.19 - 4.4.24 |
| */ |
| public boolean isMaybeZip64() { |
| return maybeZip64; |
| } |
| |
| /** |
| * Set if the file may be in Zip64 mode. This is true if any of the values in the end of |
| * central directory record are -1. |
| * |
| * <p>See <a href="http://www.pkware.com/documents/casestudies/APPNOTE.TXT">ZIP Format</a> |
| * section 4.4.19 - 4.4.24 |
| */ |
| public void setMaybeZip64(boolean maybeZip64) { |
| this.maybeZip64 = maybeZip64; |
| } |
| |
| /** |
| * Returns if the file is in Zip64 mode. This is true if any of a number of fields exceed the |
| * maximum value. |
| * |
| * <p>See <a href="http://www.pkware.com/documents/casestudies/APPNOTE.TXT">ZIP Format</a> for |
| * details |
| */ |
| public boolean isZip64() { |
| return isZip64; |
| } |
| |
| /** |
| * Set if the file is in Zip64 mode. This is true if any of a number of fields exceed the maximum |
| * value. |
| * |
| * <p>See <a href="http://www.pkware.com/documents/casestudies/APPNOTE.TXT">ZIP Format</a> for |
| * details |
| */ |
| public void setZip64(boolean isZip64) { |
| this.isZip64 = isZip64; |
| setMaybeZip64(true); |
| } |
| |
| /** |
| * Returns the file offset of the Zip64 end of central directory record. The record is only |
| * present if {@link #isZip64()} returns true. |
| */ |
| public long getZip64EndOfCentralDirectoryOffset() { |
| return zip64EndOfCentralDirectoryOffset; |
| } |
| |
| /** |
| * Sets the file offset of the Zip64 end of central directory record and sets the file to Zip64 |
| * mode. |
| */ |
| public void setZip64EndOfCentralDirectoryOffset(long offset) { |
| this.zip64EndOfCentralDirectoryOffset = offset; |
| setZip64(true); |
| } |
| |
| /** |
| * Returns the byte representation of the specified string using the file's charset encoding. |
| */ |
| public byte[] getBytes(String string) { |
| return string.getBytes(charset); |
| } |
| |
| /** |
| * Returns the string represented by the specified byte array using the file's charset encoding. |
| */ |
| public String fromBytes(byte[] bytes) { |
| return new String(bytes, charset); |
| } |
| } |