// 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 + "]"; } }