All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.schlichtherle.truezip.zip.ZipEntry Maven / Gradle / Ivy

Go to download

The file system driver family for ZIP and related archive file types. Add the JAR artifact of this module to the run time class path to make its file system drivers available for service location in the client API modules.

There is a newer version: 7.7.10
Show newest version
/*
 * Copyright (C) 2005-2013 Schlichtherle IT Services.
 * All rights reserved. Use is subject to license terms.
 */
package de.schlichtherle.truezip.zip;

import static de.schlichtherle.truezip.zip.Constants.EMPTY;
import static de.schlichtherle.truezip.zip.Constants.FORCE_ZIP64_EXT;
import static de.schlichtherle.truezip.zip.ExtraField.WINZIP_AES_ID;
import static de.schlichtherle.truezip.zip.ExtraField.ZIP64_HEADER_ID;
import static de.schlichtherle.truezip.zip.LittleEndian.readLong;
import static de.schlichtherle.truezip.zip.LittleEndian.writeLong;
import java.util.Formatter;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.NotThreadSafe;

/**
 * Drop-in replacement for {@link java.util.zip.ZipEntry java.util.zip.ZipEntry}.
 * For every numeric property of this class, the default value is
 * {@code UNKNOWN} in order to indicate an unknown state and it's
 * permitted to set this value explicitly in order to reset the property.
 * 

* Note that a {@code ZipEntry} object can be used with only one * {@link ZipFile} or {@link ZipOutputStream} instance. * Reusing the same {@code ZipEntry} object with a second object of these * classes is an error and may result in unpredictable behaviour. *

* In general, this class is not thread-safe. * However, it is safe to call only the getters of this class from multiple * threads concurrently. * * @author Christian Schlichtherle */ // TODO: Consider implementing de.schlichtherle.truezip.fs.entry.Entry. @NotThreadSafe public class ZipEntry implements Cloneable { // Bit masks for initialized fields. private static final int PLATFORM = 1, METHOD = 1 << 1, CRC = 1 << 2, DTIME = 1 << 6, EATTR = 1 << 7; /** The unknown value for numeric properties. */ public static final byte UNKNOWN = -1; /** Windows platform. */ public static final short PLATFORM_FAT = 0; /** Unix platform. */ public static final short PLATFORM_UNIX = 3; /** * Method for Stored (uncompressed) entries. * * @see #setMethod(int) */ public static final int STORED = 0; /** * Method for Deflated compressed entries. * * @see #setMethod(int) */ public static final int DEFLATED = 8; /** * Method for BZIP2 compressed entries. * * @see #setMethod(int) * @since TrueZIP 7.3 */ public static final int BZIP2 = 12; /** * Pseudo compression method for WinZip AES encrypted entries. * * @since TrueZIP 7.3 */ static final int WINZIP_AES = 99; /** General Purpose Bit Flag mask for encrypted data. */ static final int GPBF_ENCRYPTED = 1; static final int GPBF_DATA_DESCRIPTOR = 1 << 3; static final int GPBF_UTF8 = 1 << 11; /** * Smallest supported DOS date/time value in a ZIP file, * which is January 1st, 1980 AD 00:00:00 local time. */ public static final long MIN_DOS_TIME = DateTimeConverter.MIN_DOS_TIME; /** * Largest supported DOS date/time value in a ZIP file, * which is December 31st, 2107 AD 23:59:58 local time. */ public static final long MAX_DOS_TIME = DateTimeConverter.MAX_DOS_TIME; private byte init; // bit flags for init state private String name; private byte platform; // 1 byte unsigned int (UByte) private short general; // 2 bytes unsigned int (UShort) private short method; // 2 bytes unsigned int (UShort) private int dtime; // 4 bytes unsigned int (UInt) private int crc; // 4 bytes unsigned int (UInt) private long csize = UNKNOWN; // 63 bits unsigned integer (ULong) private long size = UNKNOWN; // 63 bits unsigned integer (Ulong) private int eattr; // 4 bytes unsigned int (Uint) /** Relative Offset Of Local File Header. */ private long offset = UNKNOWN; // 63 bits unsigned integer (ULong) /** * The map of extra fields. * Maps from Header ID [Integer] to Extra Field [ExtraField]. * Should be {@code null} or may be empty if no extra fields are used. */ private @CheckForNull ExtraFields fields; /** Comment field. */ private @CheckForNull String comment; /** Constructs a new ZIP entry with the given name. */ public ZipEntry(final String name) { UShort.check(name.length()); this.name = name; } /** * Constructs a new ZIP entry with the given name and all other properties * copied from the given template. */ @SuppressWarnings("AccessingNonPublicFieldOfAnotherObject") protected ZipEntry(final String name, final ZipEntry template) { UShort.check(name.length()); this.init = template.init; this.name = name; this.platform = template.platform; this.general = template.general; this.method = template.method; this.dtime = template.dtime; this.crc = template.crc; this.csize = template.csize; this.size = template.size; this.eattr = template.eattr; this.offset = template.offset; final ExtraFields templateFields = template.fields; this.fields = templateFields == null ? null : templateFields.clone(); this.comment = template.comment; } @Override @SuppressWarnings("AccessingNonPublicFieldOfAnotherObject") public ZipEntry clone() { final ZipEntry entry; try { entry = (ZipEntry) super.clone(); } catch (CloneNotSupportedException ex) { throw new AssertionError(ex); } final ExtraFields fields = this.fields; entry.fields = fields == null ? null : fields.clone(); return entry; } private boolean isInit(final int mask) { return 0 != (init & mask); } private void setInit(final int mask, final boolean init) { if (init) this.init |= mask; else this.init &= ~mask; } /** Returns the ZIP entry name. */ public final String getName() { return name; } /** * Returns true if and only if this ZIP entry represents a directory entry * (i.e. end with {@code '/'}). */ public final boolean isDirectory() { return name.endsWith("/"); } public final short getPlatform() { return isInit(PLATFORM) ? (short) (platform & UByte.MAX_VALUE) : UNKNOWN; } public final void setPlatform(final short platform) { final boolean known = UNKNOWN != platform; if (known) { UByte.check(platform, name, "Platform out of range"); this.platform = (byte) platform; } else { this.platform = 0; } setInit(PLATFORM, known); } final short getRawPlatform() { return (short) (platform & UByte.MAX_VALUE); } final void setRawPlatform(final int platform) { assert UByte.check(platform); this.platform = (byte) platform; setInit(PLATFORM, true); } final int getRawVersionNeededToExtract() { final int method = getRawMethod(); return BZIP2 == method ? 46 : isZip64ExtensionsRequired() ? 45 : DEFLATED == method || isDirectory() ? 20 : 10; } /** Returns the General Purpose Bit Flags. */ final int getGeneralPurposeBitFlags() { return general & UShort.MAX_VALUE; } /** Sets the General Purpose Bit Flags. */ final void setGeneralPurposeBitFlags(final int general) { assert UShort.check(general); this.general = (short) general; } /** Returns the indexed General Purpose Bit Flag. */ final boolean getGeneralPurposeBitFlag(final int mask) { return 0 != (general & mask); } /** Sets the indexed General Purpose Bit Flag. */ final void setGeneralPurposeBitFlag(final int mask, final boolean bit) { if (bit) general |= mask; else general &= ~mask; } /** * Returns {@code true} if and only if this ZIP entry is encrypted. * Note that only WinZip AES encryption is currently supported. * * @return {@code true} if and only if this ZIP entry is encrypted. * @since TrueZIP 7.3 */ public final boolean isEncrypted() { return getGeneralPurposeBitFlag(GPBF_ENCRYPTED); } /** * Sets the encryption flag for this ZIP entry. * If you set this to {@code true}, you will also need to provide * {@link ZipOutputStream#setCryptoParameters(ZipCryptoParameters) crypto parameters}. *

* Note that only {@link WinZipAesParameters WinZip AES encryption} is * currently supported. * * @param encrypted whether or not this ZIP entry should get encrypted. * @since TrueZIP 7.3 */ public final void setEncrypted(boolean encrypted) { setGeneralPurposeBitFlag(GPBF_ENCRYPTED, encrypted); } /** * Sets the encryption property to {@code false} and removes any other * encryption artifacts, e.g. a WinZip AES extra field. * * @since TrueZIP 7.4 * @see #TRUEZIP-176 */ public final void clearEncryption() { setEncrypted(false); final WinZipAesEntryExtraField field = (WinZipAesEntryExtraField) removeExtraField(WINZIP_AES_ID); if (WINZIP_AES == getRawMethod()) setRawMethod(null == field ? UNKNOWN : field.getMethod()); } /** * Returns the compression method for this entry. * * @see #setMethod(int) * @see ZipOutputStream#getMethod() */ public final int getMethod() { return isInit(METHOD) ? method & UShort.MAX_VALUE : UNKNOWN; } /** * Sets the compression method for this entry. * * @see #getMethod() * @see ZipOutputStream#setMethod(int) * @throws IllegalArgumentException If {@code method} is not * {@link #STORED}, {@link #DEFLATED}, {@link #BZIP2} or * {@link #UNKNOWN}. */ public final void setMethod(final int method) { switch (method) { case WINZIP_AES: // This is only present to support manual copying of properties. // It should never be set by applications. case STORED: case DEFLATED: case BZIP2: this.method = (short) method; setInit(METHOD, true); break; case UNKNOWN: this.method = 0; setInit(METHOD, false); break; default: throw new IllegalArgumentException( name + " (unsupported compression method " + method + ")"); } } final int getRawMethod() { return method & UShort.MAX_VALUE; } final void setRawMethod(final int method) { assert UShort.check(method); this.method = (short) method; setInit(METHOD, true); } public final long getTime() { if (!isInit(DTIME)) return UNKNOWN; return getDateTimeConverter().toJavaTime(dtime & UInt.MAX_VALUE); } public final void setTime(final long jtime) { final boolean known = UNKNOWN != jtime; if (known) { this.dtime = (int) getDateTimeConverter().toDosTime(jtime); } else { this.dtime = 0; } setInit(DTIME, known); } final long getRawTime() { return dtime & UInt.MAX_VALUE; } final void setRawTime(final long dtime) { assert UInt.check(dtime); this.dtime = (int) dtime; setInit(DTIME, true); } /** * Returns a {@link DateTimeConverter} for the conversion of Java time * to DOS date/time fields and vice versa. *

* The implementation in the class {@link ZipEntry} returns * {@link DateTimeConverter#JAR}. * * @return {@link DateTimeConverter#JAR} */ protected DateTimeConverter getDateTimeConverter() { return DateTimeConverter.JAR; } public final long getCrc() { return isInit(CRC) ? crc & UInt.MAX_VALUE : UNKNOWN; } public final void setCrc(final long crc) { final boolean known = UNKNOWN != crc; if (known) { UInt.check(crc, name, "CRC-32 out of range"); this.crc = (int) crc; } else { this.crc = 0; } setInit(CRC, known); } final long getRawCrc() { return crc & UInt.MAX_VALUE; } final void setRawCrc(final long crc) { assert UInt.check(crc); this.crc = (int) crc; setInit(CRC, true); } /** * Returns the compressed size of this entry. * * @see #setCompressedSize */ public final long getCompressedSize() { return csize; } /** * Sets the compressed size of this entry. * * @param csize The Compressed Size. * @throws IllegalArgumentException If {@code csize} is not in the * range from {@code 0} to {@link ULong#MAX_VALUE} * ({@value de.schlichtherle.truezip.zip.ULong#MAX_VALUE}). * @see #getCompressedSize */ public final void setCompressedSize(final long csize) { if (UNKNOWN != csize) ULong.check(csize, name, "Compressed Size out of range"); this.csize = csize; } final long getRawCompressedSize() { final long csize = this.csize; if (UNKNOWN == csize) return 0; return FORCE_ZIP64_EXT || UInt.MAX_VALUE <= csize ? UInt.MAX_VALUE : csize; } final void setRawCompressedSize(final long csize) { assert ULong.check(csize); this.csize = csize; } /** * Returns the uncompressed size of this entry. * * @see #setCompressedSize */ public final long getSize() { return size; } /** * Sets the uncompressed size of this entry. * * @param size The (Uncompressed) Size. * @throws IllegalArgumentException If {@code size} is not in the * range from {@code 0} to {@link ULong#MAX_VALUE} * ({@value de.schlichtherle.truezip.zip.ULong#MAX_VALUE}). * @see #getCompressedSize */ public final void setSize(final long size) { if (UNKNOWN != size) ULong.check(size, name, "Uncompressed Size out of range"); this.size = size; } final long getRawSize() { final long size = this.size; if (UNKNOWN == size) return 0; return FORCE_ZIP64_EXT || UInt.MAX_VALUE <= size ? UInt.MAX_VALUE : size; } final void setRawSize(final long size) { assert ULong.check(size); this.size = size; } /** * Returns the external file attributes. * * @since TrueZIP 7.3 * @return The external file attributes. */ public final long getExternalAttributes() { return isInit(EATTR) ? eattr & UInt.MAX_VALUE : UNKNOWN; } /** * Sets the external file attributes. * * @param eattr the external file attributes. * @since TrueZIP 7.3 */ public final void setExternalAttributes(final long eattr) { final boolean known = UNKNOWN != eattr; if (known) { UInt.check(eattr, name, "external file attributes out of range"); this.eattr = (int) eattr; } else { this.eattr = 0; } setInit(EATTR, known); } final long getRawExternalAttributes() { if (!isInit(EATTR)) return isDirectory() ? 0x10 : 0; return eattr & UInt.MAX_VALUE; } final void setRawExternalAttributes(final long eattr) { assert UInt.check(eattr); this.eattr = (int) eattr; setInit(EATTR, true); } final long getOffset() { return offset; } final long getRawOffset() { final long offset = this.offset; if (UNKNOWN == offset) return 0; return FORCE_ZIP64_EXT || UInt.MAX_VALUE <= offset ? UInt.MAX_VALUE : offset; } final void setRawOffset(final long offset) { assert ULong.check(offset); this.offset = offset; } final @Nullable ExtraField getExtraField(int headerId) { final ExtraFields fields = this.fields; return fields == null ? null : fields.get(headerId); } final @Nullable ExtraField addExtraField(final ExtraField field) { assert null != field; ExtraFields fields = this.fields; if (null == fields) this.fields = fields = new ExtraFields(); return fields.add(field); } final @Nullable ExtraField removeExtraField(final int headerId) { final ExtraFields fields = this.fields; return null != fields ? fields.remove(headerId) : null; } /** * Returns a protective copy of the serialized extra fields. * Note that unlike its template {@link java.util.zip.ZipEntry#getExtra()}, * this method never returns {@code null}. * * @return A new byte array holding the serialized extra fields. * {@code null} is never returned. */ public final byte[] getExtra() { return getExtraFields(false); } /** * Sets the serialized extra fields by making a protective copy. * Note that this method parses the serialized extra fields according to * the ZIP File Format Specification and limits its size to 64 KB. * Therefore, this property cannot not be used to hold arbitrary * (application) data. * Consider storing such data in a separate entry instead. * * @param buf The byte array holding the serialized extra fields. * @throws IllegalArgumentException if the serialized extra fields exceed * 64 KB or do not conform to the ZIP File Format Specification. */ public final void setExtra(final @CheckForNull byte[] buf) throws IllegalArgumentException { if (null != buf) UShort.check(buf.length, "extra fields too large", null); if (null == buf || buf.length <= 0) this.fields = null; else setExtraFields(buf, false); } /** * Returns a protective copy of the serialized extra fields. * * @return A new byte array holding the serialized extra fields. * {@code null} is never returned. * @see #getRawExtraFields() */ final byte[] getRawExtraFields() { return getExtraFields(true); } /** * Sets extra fields and parses ZIP64 extra field. * This method must not get called before the uncompressed size, * compressed size and offset have been initialized! * * @throws IllegalArgumentException if the serialized extra fields do not * conform to the ZIP File Format Specification. */ final void setRawExtraFields(final byte[] buf) throws IllegalArgumentException { setExtraFields(buf, true); } private byte[] getExtraFields(final boolean zip64) { ExtraFields fields = this.fields; if (zip64) { final ExtraField field = composeZip64ExtraField(); if (null != field) { fields = null != fields ? fields.clone() : new ExtraFields(); fields.add(field); } } else { assert null == fields || null == fields.get(ZIP64_HEADER_ID); } return null == fields ? EMPTY : fields.getExtra(); } /** * @throws IllegalArgumentException if the serialized extra fields do not * conform to the ZIP File Format Specification. */ private void setExtraFields(final byte[] buf, final boolean zip64) throws IllegalArgumentException { assert UShort.check(buf.length); if (0 < buf.length) { final ExtraFields fields = new ExtraFields(); try { fields.readFrom(buf, 0, buf.length); if (zip64) parseZip64ExtraField(fields); } catch (final IndexOutOfBoundsException ex) { throw new IllegalArgumentException(ex); } fields.remove(ZIP64_HEADER_ID); this.fields = 0 < fields.size() ? fields : null; } else { this.fields = null; } } /** * Composes a ZIP64 Extended Information Extra Field from the properties * of this entry. * If no ZIP64 Extended Information Extra Field is required it is removed * from the collection of extra fields. */ private @CheckForNull ExtraField composeZip64ExtraField() { final byte[] data = new byte[3 * 8]; // maximum size int off = 0; // Write out Uncompressed Size. final long size = getSize(); if (FORCE_ZIP64_EXT && UNKNOWN != size || UInt.MAX_VALUE <= size) { writeLong(size, data, off); off += 8; } // Write out Compressed Size. final long csize = getCompressedSize(); if (FORCE_ZIP64_EXT && UNKNOWN != csize || UInt.MAX_VALUE <= csize) { writeLong(csize, data, off); off += 8; } // Write out Relative Header Offset. final long offset = getOffset(); if (FORCE_ZIP64_EXT && UNKNOWN != offset || UInt.MAX_VALUE <= offset) { writeLong(offset, data, off); off += 8; } // Create ZIP64 Extended Information Extra Field from serialized data. final ExtraField field; if (off > 0) { field = new DefaultExtraField(ZIP64_HEADER_ID); field.readFrom(data, 0, off); } else { field = null; } return field; } /** * Parses the properties of this entry from the ZIP64 Extended Information * Extra Field, if present. * The ZIP64 Extended Information Extra Field is not removed. */ private void parseZip64ExtraField(final ExtraFields fields) throws IndexOutOfBoundsException { final ExtraField ef = fields.get(ZIP64_HEADER_ID); if (null == ef) return; final byte[] data = ef.getDataBlock(); int off = 0; // Read in Uncompressed Size. final long size = getRawSize(); if (UInt.MAX_VALUE <= size) { assert UInt.MAX_VALUE == size; setRawSize(readLong(data, off)); off += 8; } // Read in Compressed Size. final long csize = getRawCompressedSize(); if (UInt.MAX_VALUE <= csize) { assert UInt.MAX_VALUE == csize; setRawCompressedSize(readLong(data, off)); off += 8; } // Read in Relative Header Offset. final long offset = getRawOffset(); if (UInt.MAX_VALUE <= offset) { assert UInt.MAX_VALUE == offset; setRawOffset(readLong(data, off)); //off += 8; } } public final @CheckForNull String getComment() { return comment; } /** * Sets the entry comment. * Note that this method limits the comment size to 64 KB. * Therefore, this property should not be used to hold arbitrary * (application) data. * Consider storing such data in a separate entry instead. * * @param comment The entry comment. * @throws RuntimeException if the entry comment exceeds 64 KB. */ public final void setComment(final @CheckForNull String comment) { if (null != comment) UShort.check(comment.length(), name, "Comment too long"); this.comment = comment; } final String getRawComment() { final String comment = this.comment; return null != comment ? comment : ""; } final void setRawComment(final String comment) { assert UShort.check(comment.length()); this.comment = comment; } final boolean isDataDescriptorRequired() { return UNKNOWN == (getCrc() | getCompressedSize() | getSize()); } final boolean isZip64ExtensionsRequired() { // Offset MUST be considered in decision about ZIP64 format - see // description of Data Descriptor in ZIP File Format Specification! if (FORCE_ZIP64_EXT) return true /*UNKNOWN != getCompressedSize() || UNKNOWN != getSize() || UNKNOWN != getOffset()*/; else return UInt.MAX_VALUE <= getCompressedSize() || UInt.MAX_VALUE <= getSize() || UInt.MAX_VALUE <= getOffset(); } /** * Returns a string representation of this object for debugging and logging * purposes. */ @Override public String toString() { final StringBuilder s = new StringBuilder(256); final Formatter f = new Formatter(s) .format("%s[name=%s", getClass().getName(), getName()); long value; if (UNKNOWN != (value = getGeneralPurposeBitFlags())) f.format(", gpbf=0x%04X", value); if (UNKNOWN != (value = getMethod())) f.format(", method=%d", value); if (UNKNOWN != (value = getTime())) f.format(", time=%tc", value); if (UNKNOWN != (value = getCrc())) f.format(", crc=0x%08X", value); if (UNKNOWN != (value = getCompressedSize())) f.format(", compressedSize=%d", value); if (UNKNOWN != (value = getSize())) f.format(", size=%d", value); if (UNKNOWN != (value = getExternalAttributes())) f.format(", ea=0x%08X", value); { final String comment = getComment(); if (null != comment) f.format(", comment=\"%s\"", comment); } return s.append("]").toString(); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy