blob: cbfdb372a2357e666f83ef194243f3a7045bc18f [file] [log] [blame]
// 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);
}
}