--- old/src/share/classes/java/util/zip/ZipEntry.java 2013-08-02 13:14:07.000000000 -0700 +++ new/src/share/classes/java/util/zip/ZipEntry.java 2013-08-02 13:14:07.000000000 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 1995, 2011, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1995, 2013, 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 @@ -25,6 +25,11 @@ 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; + /** * This class is used to represent a ZIP file entry. * @@ -32,8 +37,12 @@ */ public class ZipEntry implements ZipConstants, Cloneable { + String name; // entry name - long mtime = -1; // last modification time + long time = -1; // last modification time + 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 @@ -55,15 +64,15 @@ /** * Creates a new zip entry with the specified name. * - * @param name the entry name - * @exception NullPointerException if the entry name is null - * @exception IllegalArgumentException if the entry name is longer than - * 0xFFFF bytes + * @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) { - if (name == null) { - throw new NullPointerException(); - } + Objects.requireNonNull(name, "name"); if (name.length() > 0xFFFF) { throw new IllegalArgumentException("entry name too long"); } @@ -73,11 +82,19 @@ /** * Creates a new zip entry with fields taken from the specified * zip entry. - * @param e a zip Entry object + * + * @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; + time = e.time; mtime = e.mtime; + atime = e.atime; + ctime = e.ctime; crc = e.crc; size = e.size; csize = e.csize; @@ -103,33 +120,178 @@ /** * Sets the last modification time of the entry. * - * @param time the last modification time of the entry in milliseconds since the epoch + *

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.mtime = time; + this.time = time; + this.mtime = null; } /** * Returns the last modification time of the entry. - *

The last modificatin time may come from zip entry's extensible - * data field {@code NTFS} or {@code Info-ZIP Extended Timestamp}, if - * the entry is read from {@link ZipInputStream} or {@link ZipFile}. * - * @return the last modification time of the entry, or -1 if not specified + *

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() { - return mtime; + return time; + } + + /** + * Sets the last modification time of the entry. + * + *

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) { + Objects.requireNonNull(name, "time"); + this.mtime = time; + this.time = time.to(TimeUnit.MILLISECONDS); + return this; + } + + /** + * Returns the last modification time of the entry. + * + *

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 (time == -1) + return null; + return FileTime.from(time, TimeUnit.MILLISECONDS); + } + + /** + * Sets the last access time of the entry. + * + *

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) { + Objects.requireNonNull(name, "time"); + this.atime = time; + return this; + } + + /** + * Returns the last access time of the entry. + * + *

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(long) + * @since 1.8 + */ + public FileTime getLastAccessTime() { + return atime; + } + + /** + * Sets the creation time of the entry. + * + *

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) { + Objects.requireNonNull(name, "time"); + this.ctime = time; + return this; + } + + /** + * Returns the creation time of the entry. + * + *

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 - * @exception IllegalArgumentException if the specified size is less - * than 0, is greater than 0xFFFFFFFF when - * ZIP64 format is not supported, - * or is less than 0 when ZIP64 is supported + * + * @throws IllegalArgumentException if the specified size is less + * than 0, is greater than 0xFFFFFFFF when + * ZIP64 format is not supported, + * or is less than 0 when ZIP64 is supported * @see #getSize() */ public void setSize(long size) { @@ -140,7 +302,8 @@ } /** - * Returns the uncompressed size of the entry data, or -1 if not known. + * Returns the uncompressed size of the entry data. + * * @return the uncompressed size of the entry data, or -1 if not known * @see #setSize(long) */ @@ -149,9 +312,11 @@ } /** - * 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 + * Returns the size of the compressed entry data. + * + *

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) */ @@ -161,7 +326,9 @@ /** * Sets the size of the compressed entry data. + * * @param csize the compressed size to set to + * * @see #getCompressedSize() */ public void setCompressedSize(long csize) { @@ -170,9 +337,11 @@ /** * Sets the CRC-32 checksum of the uncompressed entry data. + * * @param crc the CRC-32 value - * @exception IllegalArgumentException if the specified CRC-32 value is - * less than 0 or greater than 0xFFFFFFFF + * + * @throws IllegalArgumentException if the specified CRC-32 value is + * less than 0 or greater than 0xFFFFFFFF * @see #getCrc() */ public void setCrc(long crc) { @@ -183,10 +352,11 @@ } /** - * Returns the CRC-32 checksum of the uncompressed entry data, or -1 if - * not known. + * 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() { @@ -195,9 +365,11 @@ /** * Sets the compression method for the entry. + * * @param method the compression method, either STORED or DEFLATED - * @exception IllegalArgumentException if the specified compression - * method is invalid + * + * @throws IllegalArgumentException if the specified compression + * method is invalid * @see #getMethod() */ public void setMethod(int method) { @@ -208,7 +380,8 @@ } /** - * Returns the compression method of the entry, or -1 if not specified. + * Returns the compression method of the entry. + * * @return the compression method of the entry, or -1 if not specified * @see #setMethod(int) */ @@ -218,21 +391,104 @@ /** * Sets the optional extra field data for the entry. - * @param extra the extra field data bytes - * @exception IllegalArgumentException if the length of the specified - * extra field data is greater than 0xFFFF bytes + * + *

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 + * Info-ZIP + * Application Note 970311. + * + * @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) { - if (extra != null && extra.length > 0xFFFF) { - throw new IllegalArgumentException("invalid extra field length"); + 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: + 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, or null if none. + * 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() { @@ -255,8 +511,10 @@ } /** - * Returns the comment string for the entry, or null if none. + * Returns the comment string for the entry. + * * @return the comment string for the entry, or null if none + * * @see #setComment(String) */ public String getComment() {