You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
595 lines
18 KiB
595 lines
18 KiB
/*
|
|
* Copyright (c) 1995, 2015, Oracle and/or its affiliates. All rights reserved.
|
|
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*/
|
|
|
|
package java.util.zip;
|
|
|
|
import static java.util.zip.ZipUtils.*;
|
|
import java.nio.file.attribute.FileTime;
|
|
import java.util.Objects;
|
|
import java.util.concurrent.TimeUnit;
|
|
|
|
import static java.util.zip.ZipConstants64.*;
|
|
|
|
/**
|
|
* This class is used to represent a ZIP file entry.
|
|
*
|
|
* @author David Connelly
|
|
*/
|
|
public
|
|
class ZipEntry implements ZipConstants, Cloneable {
|
|
|
|
String name; // entry name
|
|
long xdostime = -1; // last modification time (in extended DOS time,
|
|
// where milliseconds lost in conversion might
|
|
// be encoded into the upper half)
|
|
FileTime mtime; // last modification time, from extra field data
|
|
FileTime atime; // last access time, from extra field data
|
|
FileTime ctime; // creation time, from extra field data
|
|
long crc = -1; // crc-32 of entry data
|
|
long size = -1; // uncompressed size of entry data
|
|
long csize = -1; // compressed size of entry data
|
|
int method = -1; // compression method
|
|
int flag = 0; // general purpose flag
|
|
byte[] extra; // optional extra field data for entry
|
|
String comment; // optional comment string for entry
|
|
|
|
/**
|
|
* Compression method for uncompressed entries.
|
|
*/
|
|
public static final int STORED = 0;
|
|
|
|
/**
|
|
* Compression method for compressed (deflated) entries.
|
|
*/
|
|
public static final int DEFLATED = 8;
|
|
|
|
/**
|
|
* DOS time constant for representing timestamps before 1980.
|
|
*/
|
|
static final long DOSTIME_BEFORE_1980 = (1 << 21) | (1 << 16);
|
|
|
|
/**
|
|
* Approximately 128 years, in milliseconds (ignoring leap years etc).
|
|
*
|
|
* This establish an approximate high-bound value for DOS times in
|
|
* milliseconds since epoch, used to enable an efficient but
|
|
* sufficient bounds check to avoid generating extended last modified
|
|
* time entries.
|
|
*
|
|
* Calculating the exact number is locale dependent, would require loading
|
|
* TimeZone data eagerly, and would make little practical sense. Since DOS
|
|
* times theoretically go to 2107 - with compatibility not guaranteed
|
|
* after 2099 - setting this to a time that is before but near 2099
|
|
* should be sufficient.
|
|
*/
|
|
private static final long UPPER_DOSTIME_BOUND =
|
|
128L * 365 * 24 * 60 * 60 * 1000;
|
|
|
|
/**
|
|
* Creates a new zip entry with the specified name.
|
|
*
|
|
* @param name
|
|
* The entry name
|
|
*
|
|
* @throws NullPointerException if the entry name is null
|
|
* @throws IllegalArgumentException if the entry name is longer than
|
|
* 0xFFFF bytes
|
|
*/
|
|
public ZipEntry(String name) {
|
|
Objects.requireNonNull(name, "name");
|
|
if (name.length() > 0xFFFF) {
|
|
throw new IllegalArgumentException("entry name too long");
|
|
}
|
|
this.name = name;
|
|
}
|
|
|
|
/**
|
|
* Creates a new zip entry with fields taken from the specified
|
|
* zip entry.
|
|
*
|
|
* @param e
|
|
* A zip Entry object
|
|
*
|
|
* @throws NullPointerException if the entry object is null
|
|
*/
|
|
public ZipEntry(ZipEntry e) {
|
|
Objects.requireNonNull(e, "entry");
|
|
name = e.name;
|
|
xdostime = e.xdostime;
|
|
mtime = e.mtime;
|
|
atime = e.atime;
|
|
ctime = e.ctime;
|
|
crc = e.crc;
|
|
size = e.size;
|
|
csize = e.csize;
|
|
method = e.method;
|
|
flag = e.flag;
|
|
extra = e.extra;
|
|
comment = e.comment;
|
|
}
|
|
|
|
/**
|
|
* Creates a new un-initialized zip entry
|
|
*/
|
|
ZipEntry() {}
|
|
|
|
/**
|
|
* Returns the name of the entry.
|
|
* @return the name of the entry
|
|
*/
|
|
public String getName() {
|
|
return name;
|
|
}
|
|
|
|
/**
|
|
* Sets the last modification time of the entry.
|
|
*
|
|
* <p> If the entry is output to a ZIP file or ZIP file formatted
|
|
* output stream the last modification time set by this method will
|
|
* be stored into the {@code date and time fields} of the zip file
|
|
* entry and encoded in standard {@code MS-DOS date and time format}.
|
|
* The {@link java.util.TimeZone#getDefault() default TimeZone} is
|
|
* used to convert the epoch time to the MS-DOS data and time.
|
|
*
|
|
* @param time
|
|
* The last modification time of the entry in milliseconds
|
|
* since the epoch
|
|
*
|
|
* @see #getTime()
|
|
* @see #getLastModifiedTime()
|
|
*/
|
|
public void setTime(long time) {
|
|
this.xdostime = javaToExtendedDosTime(time);
|
|
// Avoid setting the mtime field if time is in the valid
|
|
// range for a DOS time
|
|
if (xdostime != DOSTIME_BEFORE_1980 && time <= UPPER_DOSTIME_BOUND) {
|
|
this.mtime = null;
|
|
} else {
|
|
this.mtime = FileTime.from(time, TimeUnit.MILLISECONDS);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the last modification time of the entry.
|
|
*
|
|
* <p> If the entry is read from a ZIP file or ZIP file formatted
|
|
* input stream, this is the last modification time from the {@code
|
|
* date and time fields} of the zip file entry. The
|
|
* {@link java.util.TimeZone#getDefault() default TimeZone} is used
|
|
* to convert the standard MS-DOS formatted date and time to the
|
|
* epoch time.
|
|
*
|
|
* @return The last modification time of the entry in milliseconds
|
|
* since the epoch, or -1 if not specified
|
|
*
|
|
* @see #setTime(long)
|
|
* @see #setLastModifiedTime(FileTime)
|
|
*/
|
|
public long getTime() {
|
|
if (mtime != null) {
|
|
return mtime.toMillis();
|
|
}
|
|
return (xdostime != -1) ? extendedDosToJavaTime(xdostime) : -1;
|
|
}
|
|
|
|
/**
|
|
* Sets the last modification time of the entry.
|
|
*
|
|
* <p> When output to a ZIP file or ZIP file formatted output stream
|
|
* the last modification time set by this method will be stored into
|
|
* zip file entry's {@code date and time fields} in {@code standard
|
|
* MS-DOS date and time format}), and the extended timestamp fields
|
|
* in {@code optional extra data} in UTC time.
|
|
*
|
|
* @param time
|
|
* The last modification time of the entry
|
|
* @return This zip entry
|
|
*
|
|
* @throws NullPointerException if the {@code time} is null
|
|
*
|
|
* @see #getLastModifiedTime()
|
|
* @since 1.8
|
|
*/
|
|
public ZipEntry setLastModifiedTime(FileTime time) {
|
|
this.mtime = Objects.requireNonNull(time, "lastModifiedTime");
|
|
this.xdostime = javaToExtendedDosTime(time.to(TimeUnit.MILLISECONDS));
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Returns the last modification time of the entry.
|
|
*
|
|
* <p> If the entry is read from a ZIP file or ZIP file formatted
|
|
* input stream, this is the last modification time from the zip
|
|
* file entry's {@code optional extra data} if the extended timestamp
|
|
* fields are present. Otherwise the last modification time is read
|
|
* from the entry's {@code date and time fields}, the {@link
|
|
* java.util.TimeZone#getDefault() default TimeZone} is used to convert
|
|
* the standard MS-DOS formatted date and time to the epoch time.
|
|
*
|
|
* @return The last modification time of the entry, null if not specified
|
|
*
|
|
* @see #setLastModifiedTime(FileTime)
|
|
* @since 1.8
|
|
*/
|
|
public FileTime getLastModifiedTime() {
|
|
if (mtime != null)
|
|
return mtime;
|
|
if (xdostime == -1)
|
|
return null;
|
|
return FileTime.from(getTime(), TimeUnit.MILLISECONDS);
|
|
}
|
|
|
|
/**
|
|
* Sets the last access time of the entry.
|
|
*
|
|
* <p> If set, the last access time will be stored into the extended
|
|
* timestamp fields of entry's {@code optional extra data}, when output
|
|
* to a ZIP file or ZIP file formatted stream.
|
|
*
|
|
* @param time
|
|
* The last access time of the entry
|
|
* @return This zip entry
|
|
*
|
|
* @throws NullPointerException if the {@code time} is null
|
|
*
|
|
* @see #getLastAccessTime()
|
|
* @since 1.8
|
|
*/
|
|
public ZipEntry setLastAccessTime(FileTime time) {
|
|
this.atime = Objects.requireNonNull(time, "lastAccessTime");
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Returns the last access time of the entry.
|
|
*
|
|
* <p> The last access time is from the extended timestamp fields
|
|
* of entry's {@code optional extra data} when read from a ZIP file
|
|
* or ZIP file formatted stream.
|
|
*
|
|
* @return The last access time of the entry, null if not specified
|
|
|
|
* @see #setLastAccessTime(FileTime)
|
|
* @since 1.8
|
|
*/
|
|
public FileTime getLastAccessTime() {
|
|
return atime;
|
|
}
|
|
|
|
/**
|
|
* Sets the creation time of the entry.
|
|
*
|
|
* <p> If set, the creation time will be stored into the extended
|
|
* timestamp fields of entry's {@code optional extra data}, when
|
|
* output to a ZIP file or ZIP file formatted stream.
|
|
*
|
|
* @param time
|
|
* The creation time of the entry
|
|
* @return This zip entry
|
|
*
|
|
* @throws NullPointerException if the {@code time} is null
|
|
*
|
|
* @see #getCreationTime()
|
|
* @since 1.8
|
|
*/
|
|
public ZipEntry setCreationTime(FileTime time) {
|
|
this.ctime = Objects.requireNonNull(time, "creationTime");
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Returns the creation time of the entry.
|
|
*
|
|
* <p> The creation time is from the extended timestamp fields of
|
|
* entry's {@code optional extra data} when read from a ZIP file
|
|
* or ZIP file formatted stream.
|
|
*
|
|
* @return the creation time of the entry, null if not specified
|
|
* @see #setCreationTime(FileTime)
|
|
* @since 1.8
|
|
*/
|
|
public FileTime getCreationTime() {
|
|
return ctime;
|
|
}
|
|
|
|
/**
|
|
* Sets the uncompressed size of the entry data.
|
|
*
|
|
* @param size the uncompressed size in bytes
|
|
*
|
|
* @throws IllegalArgumentException if the specified size is less
|
|
* than 0, is greater than 0xFFFFFFFF when
|
|
* <a href="package-summary.html#zip64">ZIP64 format</a> is not supported,
|
|
* or is less than 0 when ZIP64 is supported
|
|
* @see #getSize()
|
|
*/
|
|
public void setSize(long size) {
|
|
if (size < 0) {
|
|
throw new IllegalArgumentException("invalid entry size");
|
|
}
|
|
this.size = size;
|
|
}
|
|
|
|
/**
|
|
* Returns the uncompressed size of the entry data.
|
|
*
|
|
* @return the uncompressed size of the entry data, or -1 if not known
|
|
* @see #setSize(long)
|
|
*/
|
|
public long getSize() {
|
|
return size;
|
|
}
|
|
|
|
/**
|
|
* Returns the size of the compressed entry data.
|
|
*
|
|
* <p> In the case of a stored entry, the compressed size will be the same
|
|
* as the uncompressed size of the entry.
|
|
*
|
|
* @return the size of the compressed entry data, or -1 if not known
|
|
* @see #setCompressedSize(long)
|
|
*/
|
|
public long getCompressedSize() {
|
|
return csize;
|
|
}
|
|
|
|
/**
|
|
* Sets the size of the compressed entry data.
|
|
*
|
|
* @param csize the compressed size to set to
|
|
*
|
|
* @see #getCompressedSize()
|
|
*/
|
|
public void setCompressedSize(long csize) {
|
|
this.csize = csize;
|
|
}
|
|
|
|
/**
|
|
* Sets the CRC-32 checksum of the uncompressed entry data.
|
|
*
|
|
* @param crc the CRC-32 value
|
|
*
|
|
* @throws IllegalArgumentException if the specified CRC-32 value is
|
|
* less than 0 or greater than 0xFFFFFFFF
|
|
* @see #getCrc()
|
|
*/
|
|
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.
|
|
*
|
|
* @return the CRC-32 checksum of the uncompressed entry data, or -1 if
|
|
* not known
|
|
*
|
|
* @see #setCrc(long)
|
|
*/
|
|
public long getCrc() {
|
|
return crc;
|
|
}
|
|
|
|
/**
|
|
* Sets the compression method for the entry.
|
|
*
|
|
* @param method the compression method, either STORED or DEFLATED
|
|
*
|
|
* @throws IllegalArgumentException if the specified compression
|
|
* method is invalid
|
|
* @see #getMethod()
|
|
*/
|
|
public void setMethod(int method) {
|
|
if (method != STORED && method != DEFLATED) {
|
|
throw new IllegalArgumentException("invalid compression method");
|
|
}
|
|
this.method = method;
|
|
}
|
|
|
|
/**
|
|
* Returns the compression method of the entry.
|
|
*
|
|
* @return the compression method of the entry, or -1 if not specified
|
|
* @see #setMethod(int)
|
|
*/
|
|
public int getMethod() {
|
|
return method;
|
|
}
|
|
|
|
/**
|
|
* Sets the optional extra field data for the entry.
|
|
*
|
|
* <p> Invoking this method may change this entry's last modification
|
|
* time, last access time and creation time, if the {@code extra} field
|
|
* data includes the extensible timestamp fields, such as {@code NTFS tag
|
|
* 0x0001} or {@code Info-ZIP Extended Timestamp}, as specified in
|
|
* <a href="http://www.info-zip.org/doc/appnote-19970311-iz.zip">Info-ZIP
|
|
* Application Note 970311</a>.
|
|
*
|
|
* @param extra
|
|
* The extra field data bytes
|
|
*
|
|
* @throws IllegalArgumentException if the length of the specified
|
|
* extra field data is greater than 0xFFFF bytes
|
|
*
|
|
* @see #getExtra()
|
|
*/
|
|
public void setExtra(byte[] extra) {
|
|
setExtra0(extra, false);
|
|
}
|
|
|
|
/**
|
|
* Sets the optional extra field data for the entry.
|
|
*
|
|
* @param extra
|
|
* the extra field data bytes
|
|
* @param doZIP64
|
|
* if true, set size and csize from ZIP64 fields if present
|
|
*/
|
|
void setExtra0(byte[] extra, boolean doZIP64) {
|
|
if (extra != null) {
|
|
if (extra.length > 0xFFFF) {
|
|
throw new IllegalArgumentException("invalid extra field length");
|
|
}
|
|
// extra fields are in "HeaderID(2)DataSize(2)Data... format
|
|
int off = 0;
|
|
int len = extra.length;
|
|
while (off + 4 < len) {
|
|
int tag = get16(extra, off);
|
|
int sz = get16(extra, off + 2);
|
|
off += 4;
|
|
if (off + sz > len) // invalid data
|
|
break;
|
|
switch (tag) {
|
|
case EXTID_ZIP64:
|
|
if (doZIP64) {
|
|
// LOC extra zip64 entry MUST include BOTH original
|
|
// and compressed file size fields.
|
|
// If invalid zip64 extra fields, simply skip. Even
|
|
// it's rare, it's possible the entry size happens to
|
|
// be the magic value and it "accidently" has some
|
|
// bytes in extra match the id.
|
|
if (sz >= 16) {
|
|
size = get64(extra, off);
|
|
csize = get64(extra, off + 8);
|
|
}
|
|
}
|
|
break;
|
|
case EXTID_NTFS:
|
|
if (sz < 32) // reserved 4 bytes + tag 2 bytes + size 2 bytes
|
|
break; // m[a|c]time 24 bytes
|
|
int pos = off + 4; // reserved 4 bytes
|
|
if (get16(extra, pos) != 0x0001 || get16(extra, pos + 2) != 24)
|
|
break;
|
|
mtime = winTimeToFileTime(get64(extra, pos + 4));
|
|
atime = winTimeToFileTime(get64(extra, pos + 12));
|
|
ctime = winTimeToFileTime(get64(extra, pos + 20));
|
|
break;
|
|
case EXTID_EXTT:
|
|
int flag = Byte.toUnsignedInt(extra[off]);
|
|
int sz0 = 1;
|
|
// The CEN-header extra field contains the modification
|
|
// time only, or no timestamp at all. 'sz' is used to
|
|
// flag its presence or absence. But if mtime is present
|
|
// in LOC it must be present in CEN as well.
|
|
if ((flag & 0x1) != 0 && (sz0 + 4) <= sz) {
|
|
mtime = unixTimeToFileTime(get32(extra, off + sz0));
|
|
sz0 += 4;
|
|
}
|
|
if ((flag & 0x2) != 0 && (sz0 + 4) <= sz) {
|
|
atime = unixTimeToFileTime(get32(extra, off + sz0));
|
|
sz0 += 4;
|
|
}
|
|
if ((flag & 0x4) != 0 && (sz0 + 4) <= sz) {
|
|
ctime = unixTimeToFileTime(get32(extra, off + sz0));
|
|
sz0 += 4;
|
|
}
|
|
break;
|
|
default:
|
|
}
|
|
off += sz;
|
|
}
|
|
}
|
|
this.extra = extra;
|
|
}
|
|
|
|
/**
|
|
* Returns the extra field data for the entry.
|
|
*
|
|
* @return the extra field data for the entry, or null if none
|
|
*
|
|
* @see #setExtra(byte[])
|
|
*/
|
|
public byte[] getExtra() {
|
|
return extra;
|
|
}
|
|
|
|
/**
|
|
* Sets the optional comment string for the entry.
|
|
*
|
|
* <p>ZIP entry comments have maximum length of 0xffff. If the length of the
|
|
* specified comment string is greater than 0xFFFF bytes after encoding, only
|
|
* the first 0xFFFF bytes are output to the ZIP file entry.
|
|
*
|
|
* @param comment the comment string
|
|
*
|
|
* @see #getComment()
|
|
*/
|
|
public void setComment(String comment) {
|
|
this.comment = comment;
|
|
}
|
|
|
|
/**
|
|
* Returns the comment string for the entry.
|
|
*
|
|
* @return the comment string for the entry, or null if none
|
|
*
|
|
* @see #setComment(String)
|
|
*/
|
|
public String getComment() {
|
|
return comment;
|
|
}
|
|
|
|
/**
|
|
* Returns true if this is a directory entry. A directory entry is
|
|
* defined to be one whose name ends with a '/'.
|
|
* @return true if this is a directory entry
|
|
*/
|
|
public boolean isDirectory() {
|
|
return name.endsWith("/");
|
|
}
|
|
|
|
/**
|
|
* Returns a string representation of the ZIP entry.
|
|
*/
|
|
public String toString() {
|
|
return getName();
|
|
}
|
|
|
|
/**
|
|
* Returns the hash code value for this entry.
|
|
*/
|
|
public int hashCode() {
|
|
return name.hashCode();
|
|
}
|
|
|
|
/**
|
|
* Returns a copy of this entry.
|
|
*/
|
|
public Object clone() {
|
|
try {
|
|
ZipEntry e = (ZipEntry)super.clone();
|
|
e.extra = (extra == null) ? null : extra.clone();
|
|
return e;
|
|
} catch (CloneNotSupportedException e) {
|
|
// This should never happen, since we are Cloneable
|
|
throw new InternalError(e);
|
|
}
|
|
}
|
|
}
|