blob: ed981f4f0abea617bd0ee0a14a36a1931875c251 [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 java.util.EnumSet;
import javax.annotation.Nullable;
/**
* A full representation of a ZIP file entry.
*
* <p>See <a href="http://www.pkware.com/documents/casestudies/APPNOTE.TXT">ZIP Format</a> for
* a description of the entry fields. (Section 4.3.7 and 4.4)
*/
public final class ZipFileEntry {
/** Compression method for ZIP entries. */
public enum Compression {
STORED((short) 0, Feature.STORED),
DEFLATED((short) 8, Feature.DEFLATED);
public static Compression fromValue(int value) {
for (Compression c : Compression.values()) {
if (c.getValue() == value) {
return c;
}
}
return null;
}
private short value;
private Feature feature;
private Compression(short value, Feature feature) {
this.value = value;
this.feature = feature;
}
public short getValue() {
return value;
}
public short getMinVersion() {
return feature.getMinVersion();
}
Feature getFeature() {
return feature;
}
}
/** General purpose bit flag for ZIP entries. */
public enum Flag {
DATA_DESCRIPTOR(3);
private int bit;
private Flag(int bit) {
this.bit = bit;
}
public int getBit() {
return bit;
}
}
/** Zip file features that entries may use. */
enum Feature {
DEFAULT((short) 0x0a),
STORED((short) 0x0a),
DEFLATED((short) 0x14),
ZIP64_SIZE((short) 0x2d),
ZIP64_CSIZE((short) 0x2d),
ZIP64_OFFSET((short) 0x2d);
private short minVersion;
private Feature(short minVersion) {
this.minVersion = minVersion;
}
public short getMinVersion() {
return minVersion;
}
static short getMinRequiredVersion(EnumSet<Feature> featureSet) {
short minVersion = Feature.DEFAULT.getMinVersion();
for (Feature feature : featureSet) {
minVersion = (short) Math.max(minVersion, feature.getMinVersion());
}
return minVersion;
}
}
private String name;
private long time = -1;
private long crc = -1;
private long size = -1;
private long csize = -1;
private Compression method;
private short version = -1;
private short versionNeeded = -1;
private short flags;
private short internalAttributes;
private int externalAttributes;
private long localHeaderOffset = -1;
private ExtraDataList extra;
@Nullable private String comment;
private EnumSet<Feature> featureSet;
/**
* Creates a new ZIP entry with the specified name.
*
* @throws NullPointerException if the entry name is null
*/
public ZipFileEntry(String name) {
this.featureSet = EnumSet.of(Feature.DEFAULT);
setName(name);
setMethod(Compression.STORED);
setExtra(new ExtraDataList());
}
/**
* Creates a new ZIP entry with fields taken from the specified ZIP entry.
*/
public ZipFileEntry(ZipFileEntry e) {
this.name = e.getName();
this.time = e.getTime();
this.crc = e.getCrc();
this.size = e.getSize();
this.csize = e.getCompressedSize();
this.method = e.getMethod();
this.version = e.getVersion();
this.versionNeeded = e.getVersionNeeded();
this.flags = e.getFlags();
this.internalAttributes = e.getInternalAttributes();
this.externalAttributes = e.getExternalAttributes();
this.localHeaderOffset = e.getLocalHeaderOffset();
this.extra = new ExtraDataList(e.getExtra());
this.comment = e.getComment();
this.featureSet = EnumSet.copyOf(e.getFeatureSet());
}
/**
* Sets the name of the entry.
*/
public void setName(String name) {
if (name == null) {
throw new NullPointerException();
}
this.name = name;
}
/**
* Returns the name of the entry.
*/
public String getName() {
return name;
}
/**
* Sets the modification time of the entry.
*
* @param time the entry modification time in number of milliseconds since the epoch
*/
public void setTime(long time) {
this.time = time;
}
/**
* Returns the modification time of the entry, or -1 if not specified.
*/
public long getTime() {
return time;
}
/**
* Sets the CRC-32 checksum of the uncompressed entry data.
*
* @throws IllegalArgumentException if the specified CRC-32 value is less than 0 or greater than
* 0xFFFFFFFF
*/
public void setCrc(long crc) {
if (crc < 0 || crc > 0xffffffffL) {
throw new IllegalArgumentException("invalid entry crc-32");
}
this.crc = crc;
}
/**
* Returns the CRC-32 checksum of the uncompressed entry data, or -1 if not known.
*/
public long getCrc() {
return crc;
}
/**
* Sets the uncompressed size of the entry data in bytes.
*
* @throws IllegalArgumentException if the specified size is less than 0
*/
public void setSize(long size) {
if (size < 0) {
throw new IllegalArgumentException("invalid entry size");
}
if (size > 0xffffffffL) {
featureSet.add(Feature.ZIP64_SIZE);
} else {
featureSet.remove(Feature.ZIP64_SIZE);
}
this.size = size;
}
/**
* Returns the uncompressed size of the entry data, or -1 if not known.
*/
public long getSize() {
return size;
}
/**
* Sets the size of the compressed entry data in bytes.
*
* @throws IllegalArgumentException if the specified size is less than 0
*/
public void setCompressedSize(long csize) {
if (csize < 0) {
throw new IllegalArgumentException("invalid entry size");
}
if (csize > 0xffffffffL) {
featureSet.add(Feature.ZIP64_CSIZE);
} else {
featureSet.remove(Feature.ZIP64_CSIZE);
}
this.csize = csize;
}
/**
* Returns the size of the compressed entry data, or -1 if not known. In the case of a stored
* entry, the compressed size will be the same as the uncompressed size of the entry.
*/
public long getCompressedSize() {
return csize;
}
/**
* Sets the compression method for the entry.
*/
public void setMethod(Compression method) {
if (method == null) {
throw new NullPointerException();
}
if (this.method != null) {
featureSet.remove(this.method.getFeature());
}
this.method = method;
featureSet.add(this.method.getFeature());
}
/**
* Returns the compression method of the entry.
*/
public Compression getMethod() {
return method;
}
/**
* Sets the made by version for the entry.
*/
public void setVersion(short version) {
this.version = version;
}
/**
* Returns the made by version of the entry, accounting for assigned version and feature set.
*/
public short getVersion() {
return (short) Math.max(version, Feature.getMinRequiredVersion(featureSet));
}
/**
* Sets the version needed to extract the entry.
*/
public void setVersionNeeded(short versionNeeded) {
this.versionNeeded = versionNeeded;
}
/**
* Returns the version needed to extract the entry, accounting for assigned version and feature
* set.
*/
public short getVersionNeeded() {
return (short) Math.max(versionNeeded, Feature.getMinRequiredVersion(featureSet));
}
/**
* Sets the general purpose bit flags for the entry.
*/
public void setFlags(short flags) {
this.flags = flags;
}
/**
* Sets or clears the specified bit of the general purpose bit flags.
*
* @param flag the flag to set or clear
* @param set whether the flag is to be set or cleared
*/
public void setFlag(Flag flag, boolean set) {
short mask = 0x0000;
mask |= 1 << flag.getBit();
if (set) {
flags |= mask;
} else {
flags &= ~mask;
}
}
/**
* Returns the general purpose bit flags of the entry.
*
* <p>See <a href="http://www.pkware.com/documents/casestudies/APPNOTE.TXT">ZIP Format</a>
* section 4.4.4.
*/
public short getFlags() {
return flags;
}
/**
* Sets the internal file attributes of the entry.
*/
public void setInternalAttributes(short internalAttributes) {
this.internalAttributes = internalAttributes;
}
/**
* Returns the internal file attributes of the entry.
*/
public short getInternalAttributes() {
return internalAttributes;
}
/**
* Sets the external file attributes of the entry.
*/
public void setExternalAttributes(int externalAttributes) {
this.externalAttributes = externalAttributes;
}
/**
* Returns the external file attributes of the entry.
*/
public int getExternalAttributes() {
return externalAttributes;
}
/**
* Sets the file offset, in bytes, of the location of the local file header for the entry.
*
* <p>See <a href="http://www.pkware.com/documents/casestudies/APPNOTE.TXT">ZIP Format</a>
* section 4.4.16
*
* @throws IllegalArgumentException if the specified local header offset is less than 0
*/
void setLocalHeaderOffset(long localHeaderOffset) {
if (localHeaderOffset < 0) {
throw new IllegalArgumentException("invalid local header offset");
}
if (localHeaderOffset > 0xffffffffL) {
featureSet.add(Feature.ZIP64_OFFSET);
} else {
featureSet.remove(Feature.ZIP64_OFFSET);
}
this.localHeaderOffset = localHeaderOffset;
}
/**
* Returns the file offset of the local header of the entry.
*/
public long getLocalHeaderOffset() {
return localHeaderOffset;
}
/**
* Sets the optional extra field data for the entry.
*
* @throws IllegalArgumentException if the length of the specified extra field data is greater
* than 0xFFFF bytes
*/
public void setExtra(ExtraDataList extra) {
if (extra == null) {
throw new NullPointerException();
}
if (extra.getLength() > 0xffff) {
throw new IllegalArgumentException("invalid extra field length");
}
this.extra = extra;
}
/**
* Returns the extra field data for the entry.
*/
public ExtraDataList getExtra() {
return extra;
}
/**
* Sets the optional comment string for the entry.
*/
public void setComment(@Nullable String comment) {
this.comment = comment;
}
/**
* Returns the comment string for the entry, or null if none.
*/
public String getComment() {
return comment;
}
/**
* Returns the feature set that this entry uses.
*/
EnumSet<Feature> getFeatureSet() {
return featureSet;
}
@Override
public String toString() {
return "ZipFileEntry[" + name + "]";
}
}