/*
* 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);
}
}
}