/* |
|
* Copyright (c) 1995, 2017, Oracle and/or its affiliates. All rights reserved. |
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
|
* |
|
* This code is free software; you can redistribute it and/or modify it |
|
* under the terms of the GNU General Public License version 2 only, as |
|
* published by the Free Software Foundation. Oracle designates this |
|
* particular file as subject to the "Classpath" exception as provided |
|
* by Oracle in the LICENSE file that accompanied this code. |
|
* |
|
* This code is distributed in the hope that it will be useful, but WITHOUT |
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
|
* version 2 for more details (a copy is included in the LICENSE file that |
|
* accompanied this code). |
|
* |
|
* You should have received a copy of the GNU General Public License version |
|
* 2 along with this work; if not, write to the Free Software Foundation, |
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
|
* |
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
|
* or visit www.oracle.com if you need additional information or have any |
|
* questions. |
|
*/ |
|
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 java.time.LocalDateTime; |
|
import java.time.ZonedDateTime; |
|
import java.time.ZoneId; |
|
import static java.util.zip.ZipConstants64.*; |
|
/** |
|
* This class is used to represent a ZIP file entry. |
|
* |
|
* @author David Connelly |
|
* @since 1.1 |
|
*/ |
|
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 in local date-time. |
|
* |
|
* <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}. |
|
* If the date-time set is out of the range of the standard {@code |
|
* MS-DOS date and time format}, the time will also be stored into |
|
* zip file entry's extended timestamp fields in {@code optional |
|
* extra data} in UTC time. The {@link java.time.ZoneId#systemDefault() |
|
* system default TimeZone} is used to convert the local date-time |
|
* to UTC time. |
|
* |
|
* <p> {@code LocalDateTime} uses a precision of nanoseconds, whereas |
|
* this class uses a precision of milliseconds. The conversion will |
|
* truncate any excess precision information as though the amount in |
|
* nanoseconds was subject to integer division by one million. |
|
* |
|
* @param time |
|
* The last modification time of the entry in local date-time |
|
* |
|
* @see #getTimeLocal() |
|
* @since 9 |
|
*/ |
|
public void setTimeLocal(LocalDateTime time) { |
|
int year = time.getYear() - 1980; |
|
if (year < 0) { |
|
this.xdostime = DOSTIME_BEFORE_1980; |
|
} else { |
|
this.xdostime = ((year << 25 | |
|
time.getMonthValue() << 21 | |
|
time.getDayOfMonth() << 16 | |
|
time.getHour() << 11 | |
|
time.getMinute() << 5 | |
|
time.getSecond() >> 1) & 0xffffffffL) |
|
+ ((long)(((time.getSecond() & 0x1) * 1000) + |
|
time.getNano() / 1000_000) << 32); |
|
} |
|
if (xdostime != DOSTIME_BEFORE_1980 && year <= 0x7f) { |
|
this.mtime = null; |
|
} else { |
|
this.mtime = FileTime.from( |
|
ZonedDateTime.of(time, ZoneId.systemDefault()).toInstant()); |
|
} |
|
} |
|
/** |
|
* Returns the last modification time of the entry in local date-time. |
|
* |
|
* <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 entry's standard MS-DOS formatted {@code date and time fields}. |
|
* |
|
* <p> The {@link java.time.ZoneId#systemDefault() system default TimeZone} |
|
* is used to convert the UTC time to local date-time. |
|
* |
|
* @return The last modification time of the entry in local date-time |
|
* |
|
* @see #setTimeLocal(LocalDateTime) |
|
* @since 9 |
|
*/ |
|
public LocalDateTime getTimeLocal() { |
|
if (mtime != null) { |
|
return LocalDateTime.ofInstant(mtime.toInstant(), ZoneId.systemDefault()); |
|
} |
|
int ms = (int)(xdostime >> 32); |
|
return LocalDateTime.of((int)(((xdostime >> 25) & 0x7f) + 1980), |
|
(int)((xdostime >> 21) & 0x0f), |
|
(int)((xdostime >> 16) & 0x1f), |
|
(int)((xdostime >> 11) & 0x1f), |
|
(int)((xdostime >> 5) & 0x3f), |
|
(int)((xdostime << 1) & 0x3e) + ms / 1000, |
|
(ms % 1000) * 1000_000); |
|
} |
|
/** |
|
* 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 |
|
* |
|
* @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; |
|
long wtime = get64(extra, pos + 4); |
|
if (wtime != WINDOWS_TIME_NOT_AVAILABLE) { |
|
mtime = winTimeToFileTime(wtime); |
|
} |
|
wtime = get64(extra, pos + 12); |
|
if (wtime != WINDOWS_TIME_NOT_AVAILABLE) { |
|
atime = winTimeToFileTime(wtime); |
|
} |
|
wtime = get64(extra, pos + 20); |
|
if (wtime != WINDOWS_TIME_NOT_AVAILABLE) { |
|
ctime = winTimeToFileTime(wtime); |
|
} |
|
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(get32S(extra, off + sz0)); |
|
sz0 += 4; |
|
} |
|
if ((flag & 0x2) != 0 && (sz0 + 4) <= sz) { |
|
atime = unixTimeToFileTime(get32S(extra, off + sz0)); |
|
sz0 += 4; |
|
} |
|
if ((flag & 0x4) != 0 && (sz0 + 4) <= sz) { |
|
ctime = unixTimeToFileTime(get32S(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); |
|
} |
|
} |
|
} |