diff --git a/src/share/classes/java/util/zip/ZipConstants.java b/src/share/classes/java/util/zip/ZipConstants.java index 79cefbd46e8196d64fd7561f37e78a9a1215bbdb..c91c20ebe1abbd86ba8915112254d4283e245d05 100644 --- a/src/share/classes/java/util/zip/ZipConstants.java +++ b/src/share/classes/java/util/zip/ZipConstants.java @@ -71,10 +71,17 @@ interface ZipConstants { /* * Extra field header ID */ - static final int EXTID_ZIP64 = 0x0001; // Zip64 - static final int EXTID_NTFS = 0x000a; // NTFS - static final int EXTID_UNIX = 0x000d; // UNIX - static final int EXTID_EXTT = 0x5455; // Info-ZIP Extended Timestamp + static final int EXTID_ZIP64 = 0x0001; // Zip64 + static final int EXTID_NTFS = 0x000a; // NTFS + static final int EXTID_UNIX = 0x000d; // UNIX + static final int EXTID_EXTT = 0x5455; // Info-ZIP Extended Timestamp + + /* + * EXTT timestamp flags + */ + static final int EXTT_FLAG_LMT = 0x1; // LastModifiedTime + static final int EXTT_FLAG_LAT = 0x2; // LastAccessTime + static final int EXTT_FLAT_CT = 0x4; // CreationTime /* * Central directory (CEN) header field offsets diff --git a/src/share/classes/java/util/zip/ZipEntry.java b/src/share/classes/java/util/zip/ZipEntry.java index 60d440ebe4608d0d2337b1256de7e6c454c3887b..32a2681a84dd0386ed2b953b35f7bbad99790e3b 100644 --- a/src/share/classes/java/util/zip/ZipEntry.java +++ b/src/share/classes/java/util/zip/ZipEntry.java @@ -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 @@ package java.util.zip; */ 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 @@ class ZipEntry implements ZipConstants, Cloneable { /** * 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 @@ class ZipEntry implements ZipConstants, Cloneable { /** * 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 @@ class ZipEntry implements ZipConstants, Cloneable { /** * 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 @@ class ZipEntry implements ZipConstants, Cloneable { } /** - * 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 @@ class ZipEntry implements ZipConstants, Cloneable { } /** - * 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 @@ class ZipEntry implements ZipConstants, Cloneable { /** * 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 @@ class ZipEntry implements ZipConstants, Cloneable { /** * 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 @@ class ZipEntry implements ZipConstants, Cloneable { } /** - * 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 @@ class ZipEntry implements ZipConstants, Cloneable { /** * 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 @@ class ZipEntry implements ZipConstants, Cloneable { } /** - * 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 @@ class ZipEntry implements ZipConstants, Cloneable { /** * 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 @@ class ZipEntry implements ZipConstants, Cloneable { } /** - * 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() { diff --git a/src/share/classes/java/util/zip/ZipFile.java b/src/share/classes/java/util/zip/ZipFile.java index 6a023f81355d046d049ad5cdbc9e6190653ffe66..12aa0dcfaba1c23a0bb31e37b4c15f9a9c697fff 100644 --- a/src/share/classes/java/util/zip/ZipFile.java +++ b/src/share/classes/java/util/zip/ZipFile.java @@ -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 @@ -567,44 +567,12 @@ class ZipFile implements ZipConstants, Closeable { e.name = zc.toString(bname, bname.length); } } + e.time = dosToJavaTime(getEntryTime(jzentry)); e.crc = getEntryCrc(jzentry); e.size = getEntrySize(jzentry); - e. csize = getEntryCSize(jzentry); + e.csize = getEntryCSize(jzentry); e.method = getEntryMethod(jzentry); - e.extra = getEntryBytes(jzentry, JZENTRY_EXTRA); - if (e.extra != null) { - byte[] extra = e.extra; - int len = e.extra.length; - int off = 0; - while (off + 4 < len) { - int pos = off; - int tag = get16(extra, pos); - int sz = get16(extra, pos + 2); - pos += 4; - if (pos + sz > len) // invalid data - break; - switch (tag) { - case EXTID_NTFS: - pos += 4; // reserved 4 bytes - if (get16(extra, pos) != 0x0001 || get16(extra, pos + 2) != 24) - break; - e.mtime = winToJavaTime(get64(extra, pos + 4)); - break; - case EXTID_EXTT: - int flag = Byte.toUnsignedInt(extra[pos++]); - if ((flag & 0x1) != 0) { - e.mtime = unixToJavaTime(get32(extra, pos)); - pos += 4; - } - break; - default: // unknown tag - } - off += (sz + 4); - } - } - if (e.mtime == -1) { - e.mtime = dosToJavaTime(getEntryTime(jzentry)); - } + e.setExtra0(getEntryBytes(jzentry, JZENTRY_EXTRA), false); byte[] bcomm = getEntryBytes(jzentry, JZENTRY_COMMENT); if (bcomm == null) { e.comment = null; diff --git a/src/share/classes/java/util/zip/ZipInputStream.java b/src/share/classes/java/util/zip/ZipInputStream.java index fee9f5108d3f8251f7422f3d66944fde77dfd99a..77a598df8185c34d54574404bb0f39530dcddb24 100644 --- a/src/share/classes/java/util/zip/ZipInputStream.java +++ b/src/share/classes/java/util/zip/ZipInputStream.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2012, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 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 @@ -288,9 +288,9 @@ class ZipInputStream extends InflaterInputStream implements ZipConstants { int len = get16(tmpbuf, LOCNAM); int blen = b.length; if (len > blen) { - do + do { blen = blen * 2; - while (len > blen); + } while (len > blen); b = new byte[blen]; } readFully(b, 0, len); @@ -303,7 +303,7 @@ class ZipInputStream extends InflaterInputStream implements ZipConstants { throw new ZipException("encrypted ZIP entry not supported"); } e.method = get16(tmpbuf, LOCHOW); - e.mtime = dosToJavaTime(get32(tmpbuf, LOCTIM)); + e.time = dosToJavaTime(get32(tmpbuf, LOCTIM)); if ((flag & 8) == 8) { /* "Data Descriptor" present */ if (e.method != DEFLATED) { @@ -319,49 +319,7 @@ class ZipInputStream extends InflaterInputStream implements ZipConstants { if (len > 0) { byte[] extra = new byte[len]; readFully(extra, 0, len); - e.setExtra(extra); - // extra fields are in "HeaderID(2)DataSize(2)Data... format - int off = 0; - while (off + 4 < len) { - int pos = off; - int tag = get16(extra, pos); - int sz = get16(extra, pos + 2); - pos += 4; - if (pos + sz > len) // invalid data - break; - switch (tag) { - case EXTID_ZIP64 : - // 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 && (pos + sz) <= len ) { - e.size = get64(extra, pos); - e.csize = get64(extra, pos + 8); - } - break; - case EXTID_NTFS: - pos += 4; // reserved 4 bytes - if (get16(extra, pos) != 0x0001 || get16(extra, pos + 2) != 24) - break; - // override the loc field, NTFS time has 'microsecond' granularity - e.mtime = winToJavaTime(get64(extra, pos + 4)); - break; - case EXTID_EXTT: - int flag = Byte.toUnsignedInt(extra[pos++]); - if ((flag & 0x1) != 0) { - e.mtime = unixToJavaTime(get32(extra, pos)); - pos += 4; - } - break; - default: // unknown tag - } - off += (sz + 4); - } - + e.setExtra0(extra, true); } return e; } diff --git a/src/share/classes/java/util/zip/ZipOutputStream.java b/src/share/classes/java/util/zip/ZipOutputStream.java index 7a2cf852d304e2d7d8000b60681b524476dddc8d..4072fbed52dce26d275f45dde1d9a73dda9e4d8b 100644 --- a/src/share/classes/java/util/zip/ZipOutputStream.java +++ b/src/share/classes/java/util/zip/ZipOutputStream.java @@ -59,8 +59,9 @@ class ZipOutputStream extends DeflaterOutputStream implements ZipConstants { "jdk.util.zip.inhibitZip64", "false"))); private static class XEntry { - public final ZipEntry entry; - public final long offset; + final ZipEntry entry; + final long offset; + long dostime; // last modification time in msdos format public XEntry(ZipEntry entry, long offset) { this.entry = entry; this.offset = offset; @@ -191,7 +192,9 @@ class ZipOutputStream extends DeflaterOutputStream implements ZipConstants { if (current != null) { closeEntry(); // close previous entry } - if (e.mtime == -1) { + if (e.time == -1) { + // by default, do NOT use extended timestamps in extra + // data, for now. e.setTime(System.currentTimeMillis()); } if (e.method == -1) { @@ -384,25 +387,20 @@ class ZipOutputStream extends DeflaterOutputStream implements ZipConstants { ZipEntry e = xentry.entry; int flag = e.flag; boolean hasZip64 = false; - int elen = (e.extra != null) ? e.extra.length : 0; - int eoff = 0; - boolean foundEXTT = false; // if EXTT already present - // do nothing. - while (eoff + 4 < elen) { - int tag = get16(e.extra, eoff); - int sz = get16(e.extra, eoff + 2); - if (tag == EXTID_EXTT) { - foundEXTT = true; - } - eoff += (4 + sz); - } + int elen = getExtraLen(e.extra); + + // keep a copy of dostime for writeCEN(), otherwise the tz + // sensitive local time entries in loc and cen might be + // different if the default tz get changed during writeLOC() + // and writeCEN() + xentry.dostime = javaToDosTime(e.time); + writeInt(LOCSIG); // LOC header signature if ((flag & 8) == 8) { writeShort(version(e)); // version needed to extract writeShort(flag); // general purpose bit flag writeShort(e.method); // compression method - writeInt(javaToDosTime(e.mtime)); // last modification time - + writeInt(xentry.dostime); // last modification time // store size, uncompressed size, and crc-32 in data descriptor // immediately following compressed entry data writeInt(0); @@ -417,7 +415,7 @@ class ZipOutputStream extends DeflaterOutputStream implements ZipConstants { } writeShort(flag); // general purpose bit flag writeShort(e.method); // compression method - writeInt(javaToDosTime(e.mtime)); // last modification time + writeInt(xentry.dostime); // last modification time writeInt(e.crc); // crc-32 if (hasZip64) { writeInt(ZIP64_MAGICVAL); @@ -430,8 +428,23 @@ class ZipOutputStream extends DeflaterOutputStream implements ZipConstants { } byte[] nameBytes = zc.getBytes(e.name); writeShort(nameBytes.length); - if (!foundEXTT) - elen += 9; // use Info-ZIP's ext time in extra + + int elenEXTT = 0; // info-zip extended timestamp + int flagEXTT = 0; + if (e.mtime != null) { + elenEXTT += 4; + flagEXTT |= EXTT_FLAG_LMT; + } + if (e.atime != null) { + elenEXTT += 4; + flagEXTT |= EXTT_FLAG_LAT; + } + if (e.ctime != null) { + elenEXTT += 4; + flagEXTT |= EXTT_FLAT_CT; + } + if (flagEXTT != 0) + elen += (elenEXTT + 5); // headid(2) + size(2) + flag(1) + data writeShort(elen); writeBytes(nameBytes, 0, nameBytes.length); if (hasZip64) { @@ -440,15 +453,18 @@ class ZipOutputStream extends DeflaterOutputStream implements ZipConstants { writeLong(e.size); writeLong(e.csize); } - if (!foundEXTT) { + if (flagEXTT != 0) { writeShort(EXTID_EXTT); - writeShort(5); // size for the folowing data block - writeByte(0x1); // flags byte, mtime only - writeInt(javaToUnixTime(e.mtime)); - } - if (e.extra != null) { - writeBytes(e.extra, 0, e.extra.length); - } + writeShort(elenEXTT + 1); // flag + data + writeByte(flagEXTT); + if (e.mtime != null) + writeInt(fileTimeToUnixTime(e.mtime)); + if (e.atime != null) + writeInt(fileTimeToUnixTime(e.atime)); + if (e.ctime != null) + writeInt(fileTimeToUnixTime(e.ctime)); + } + writeExtra(e.extra); locoff = written; } @@ -506,31 +522,35 @@ class ZipOutputStream extends DeflaterOutputStream implements ZipConstants { } writeShort(flag); // general purpose bit flag writeShort(e.method); // compression method - writeInt(javaToDosTime(e.mtime)); // last modification time + // use the copy in xentry, which has been converted + // from e.time in writeLOC() + writeInt(xentry.dostime); // last modification time writeInt(e.crc); // crc-32 writeInt(csize); // compressed size writeInt(size); // uncompressed size byte[] nameBytes = zc.getBytes(e.name); writeShort(nameBytes.length); - int elen = (e.extra != null) ? e.extra.length : 0; - int eoff = 0; - boolean foundEXTT = false; // if EXTT already present - // do nothing. - while (eoff + 4 < elen) { - int tag = get16(e.extra, eoff); - int sz = get16(e.extra, eoff + 2); - if (tag == EXTID_EXTT) { - foundEXTT = true; - } - eoff += (4 + sz); - } + int elen = getExtraLen(e.extra); if (hasZip64) { - // + headid(2) + datasize(2) - elen += (elenZIP64 + 4); + elen += (elenZIP64 + 4);// + headid(2) + datasize(2) + } + // cen info-zip extended timestamp only outputs mtime + // but set the flag for a/ctime, if present in loc + int flagEXTT = 0; + if (e.mtime != null) { + elen += 4; // + mtime(4) + flagEXTT |= EXTT_FLAG_LMT; + } + if (e.atime != null) { + flagEXTT |= EXTT_FLAG_LAT; + } + if (e.ctime != null) { + flagEXTT |= EXTT_FLAT_CT; + } + if (flagEXTT != 0) { + elen += 5; // headid + sz + flag } - if (!foundEXTT) - elen += 9; // Info-ZIP's Extended Timestamp writeShort(elen); byte[] commentBytes; if (e.comment != null) { @@ -545,6 +565,8 @@ class ZipOutputStream extends DeflaterOutputStream implements ZipConstants { writeInt(0); // external file attributes (unused) writeInt(offset); // relative offset of local header writeBytes(nameBytes, 0, nameBytes.length); + + // take care of EXTID_ZIP64 and EXTID_EXTT if (hasZip64) { writeShort(ZIP64_EXTID);// Zip64 extra writeShort(elenZIP64); @@ -555,15 +577,18 @@ class ZipOutputStream extends DeflaterOutputStream implements ZipConstants { if (offset == ZIP64_MAGICVAL) writeLong(xentry.offset); } - if (!foundEXTT) { + if (flagEXTT != 0) { writeShort(EXTID_EXTT); - writeShort(5); - writeByte(0x1); // flags byte - writeInt(javaToUnixTime(e.mtime)); - } - if (e.extra != null) { - writeBytes(e.extra, 0, e.extra.length); + if (e.mtime != null) { + writeShort(5); // flag + mtime + writeByte(flagEXTT); + writeInt(fileTimeToUnixTime(e.mtime)); + } else { + writeShort(1); // flag only + writeByte(flagEXTT); + } } + writeExtra(e.extra); if (commentBytes != null) { writeBytes(commentBytes, 0, Math.min(commentBytes.length, 0xffff)); } @@ -626,6 +651,47 @@ class ZipOutputStream extends DeflaterOutputStream implements ZipConstants { } } + /* + * Returns the length of extra data without EXTT and ZIP64. + */ + private int getExtraLen(byte[] extra) { + if (extra == null) + return 0; + int skipped = 0; + int len = extra.length; + int off = 0; + while (off + 4 <= len) { + int tag = get16(extra, off); + int sz = get16(extra, off + 2); + if (tag == EXTID_EXTT || tag == EXTID_ZIP64) { + skipped += (sz + 4); + } + off += (sz + 4); + } + return len - skipped; + } + + /* + * Writes extra data without EXTT and ZIP64. + * + * Extra timestamp and ZIP64 data is handled/output separately + * in writeLOC and writeCEN. + */ + private void writeExtra(byte[] extra) throws IOException { + if (extra != null) { + int len = extra.length; + int off = 0; + while (off + 4 <= len) { + int tag = get16(extra, off); + int sz = get16(extra, off + 2); + if (tag != EXTID_EXTT && tag != EXTID_ZIP64) { + writeBytes(extra, off, sz + 4); + } + off += (sz + 4); + } + } + } + /* * Writes a 8-bit byte to the output stream. */ diff --git a/src/share/classes/java/util/zip/ZipUtils.java b/src/share/classes/java/util/zip/ZipUtils.java index 2b2dd9a6e4beff4c7d6fb8f826e392f3664c832d..5a5e9a060860d1ddda9d0883b0ab5a0844052b6c 100644 --- a/src/share/classes/java/util/zip/ZipUtils.java +++ b/src/share/classes/java/util/zip/ZipUtils.java @@ -25,42 +25,45 @@ package java.util.zip; +import java.nio.file.attribute.FileTime; import java.util.Date; import java.util.concurrent.TimeUnit; +import static java.util.zip.ZipConstants.*; +import static java.util.zip.ZipConstants64.*; + class ZipUtils { // used to adjust values between Windows and java epoch private static final long WINDOWS_EPOCH_IN_MICROSECONDS = -11644473600000000L; /** - * Converts Windows time (in microseconds, UTC/GMT) time to Java time. + * Converts Windows time (in microseconds, UTC/GMT) time to FileTime. */ - public static final long winToJavaTime(long wtime) { - return TimeUnit.MILLISECONDS.convert( - wtime / 10 + WINDOWS_EPOCH_IN_MICROSECONDS, TimeUnit.MICROSECONDS); + public static final FileTime winTimeToFileTime(long wtime) { + return FileTime.from(wtime / 10 + WINDOWS_EPOCH_IN_MICROSECONDS, + TimeUnit.MICROSECONDS); } /** - * Converts Java time to Windows time. + * Converts FileTime to Windows time. */ - public static final long javaToWinTime(long time) { - return (TimeUnit.MICROSECONDS.convert(time, TimeUnit.MILLISECONDS) - - WINDOWS_EPOCH_IN_MICROSECONDS) * 10; + public static final long fileTimeToWinTime(FileTime ftime) { + return (ftime.to(TimeUnit.MICROSECONDS) - WINDOWS_EPOCH_IN_MICROSECONDS) * 10; } /** - * Converts "standard Unix time"(in seconds, UTC/GMT) to Java time + * Converts "standard Unix time"(in seconds, UTC/GMT) to FileTime */ - public static final long unixToJavaTime(long utime) { - return TimeUnit.MILLISECONDS.convert(utime, TimeUnit.SECONDS); + public static final FileTime unixTimeToFileTime(long utime) { + return FileTime.from(utime, TimeUnit.SECONDS); } /** - * Converts Java time to "standard Unix time". + * Converts FileTime to "standard Unix time". */ - public static final long javaToUnixTime(long time) { - return TimeUnit.SECONDS.convert(time, TimeUnit.MILLISECONDS); + public static final long fileTimeToUnixTime(FileTime ftime) { + return ftime.to(TimeUnit.SECONDS); } /** @@ -92,7 +95,6 @@ class ZipUtils { d.getSeconds() >> 1; } - /** * Fetches unsigned 16-bit value from byte array at specified offset. * The bytes are assumed to be in Intel (little-endian) byte order. @@ -116,5 +118,4 @@ class ZipUtils { public static final long get64(byte b[], int off) { return get32(b, off) | (get32(b, off+4) << 32); } - } diff --git a/test/ProblemList.txt b/test/ProblemList.txt index 7dcb991f4864d23e7986e3ac56f5ae5cbb0f2ece..9a2a4bf3d59ce0059290613cde799ff4047d4ddb 100644 --- a/test/ProblemList.txt +++ b/test/ProblemList.txt @@ -335,8 +335,6 @@ sun/jvmstat/monitor/MonitoredVm/CR6672135.java generic-all # Tests take too long, on sparcs see 7143279 tools/pack200/CommandLineTests.java solaris-all, macosx-all tools/pack200/Pack200Test.java solaris-all, macosx-all -# 8015666 -tools/pack200/TimeStamp.java generic-all # 8007410 tools/launcher/FXLauncherTest.java linux-all diff --git a/test/java/util/zip/TestExtraTime.java b/test/java/util/zip/TestExtraTime.java index 6af11b12055b1a243574f7bbd2781d270ec32374..9923ea693e0698ff28e8918b90f37b635e97bd11 100644 --- a/test/java/util/zip/TestExtraTime.java +++ b/test/java/util/zip/TestExtraTime.java @@ -23,14 +23,19 @@ /** * @test - * @bug 4759491 6303183 7012868 + * @bug 4759491 6303183 7012868 8015666 * @summary Test ZOS and ZIS timestamp in extra field correctly */ import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.attribute.FileTime; import java.util.TimeZone; import java.util.concurrent.TimeUnit; import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; import java.util.zip.ZipInputStream; import java.util.zip.ZipOutputStream; @@ -41,14 +46,32 @@ public class TestExtraTime { File src = new File(System.getProperty("test.src", "."), "TestExtraTime.java"); if (src.exists()) { - long mtime = src.lastModified(); - test(mtime, null); - test(10, null); // ms-dos 1980 epoch problem - test(mtime, TimeZone.getTimeZone("Asia/Shanghai")); + long time = src.lastModified(); + FileTime mtime = FileTime.from(time, TimeUnit.MILLISECONDS); + FileTime atime = FileTime.from(time + 300000, TimeUnit.MILLISECONDS); + FileTime ctime = FileTime.from(time - 300000, TimeUnit.MILLISECONDS); + TimeZone tz = TimeZone.getTimeZone("Asia/Shanghai"); + + test(mtime, null, null, null); + // ms-dos 1980 epoch problem + test(FileTime.from(10, TimeUnit.MILLISECONDS), null, null, null); + // non-default tz + test(mtime, null, null, tz); + + test(mtime, atime, null, null); + test(mtime, null, ctime, null); + test(mtime, atime, ctime, null); + + test(mtime, atime, null, tz); + test(mtime, null, ctime, tz); + test(mtime, atime, ctime, tz); } } - private static void test(long mtime, TimeZone tz) throws Throwable { + static void test(FileTime mtime, FileTime atime, FileTime ctime, + TimeZone tz) throws Throwable { + System.out.printf("--------------------%nTesting: [%s]/[%s]/[%s]%n", + mtime, atime, ctime); TimeZone tz0 = TimeZone.getDefault(); if (tz != null) { TimeZone.setDefault(tz); @@ -57,23 +80,55 @@ public class TestExtraTime { ZipOutputStream zos = new ZipOutputStream(baos); ZipEntry ze = new ZipEntry("TestExtreTime.java"); - ze.setTime(mtime); + ze.setLastModifiedTime(mtime); + if (atime != null) + ze.setLastAccessTime(atime); + if (ctime != null) + ze.setCreationTime(ctime); zos.putNextEntry(ze); zos.write(new byte[] { 1,2 ,3, 4}); zos.close(); if (tz != null) { TimeZone.setDefault(tz0); } + // ZipInputStream ZipInputStream zis = new ZipInputStream( new ByteArrayInputStream(baos.toByteArray())); ze = zis.getNextEntry(); zis.close(); + check(mtime, atime, ctime, ze); - System.out.printf("%tc => %tc%n", mtime, ze.getTime()); - - if (TimeUnit.MILLISECONDS.toSeconds(mtime) != - TimeUnit.MILLISECONDS.toSeconds(ze.getTime())) - throw new RuntimeException("Timestamp storing failed!"); + // ZipFile + Path zpath = Paths.get(System.getProperty("test.dir", "."), + "TestExtraTimp.zip"); + Files.copy(new ByteArrayInputStream(baos.toByteArray()), zpath); + ZipFile zf = new ZipFile(zpath.toFile()); + ze = zf.getEntry("TestExtreTime.java"); + // ZipFile read entry from cen, which does not have a/ctime, + // for now. + check(mtime, null, null, ze); + zf.close(); + Files.delete(zpath); + } + static void check(FileTime mtime, FileTime atime, FileTime ctime, + ZipEntry ze) { + /* + System.out.printf(" mtime [%tc]: [%tc]/[%tc]%n", + mtime.to(TimeUnit.MILLISECONDS), + ze.getTime(), + ze.getLastModifiedTime().to(TimeUnit.MILLISECONDS)); + */ + if (mtime.to(TimeUnit.SECONDS) != + ze.getLastModifiedTime().to(TimeUnit.SECONDS)) + throw new RuntimeException("Timestamp: storing mtime failed!"); + if (atime != null && + atime.to(TimeUnit.SECONDS) != + ze.getLastAccessTime().to(TimeUnit.SECONDS)) + throw new RuntimeException("Timestamp: storing atime failed!"); + if (ctime != null && + ctime.to(TimeUnit.SECONDS) != + ze.getCreationTime().to(TimeUnit.SECONDS)) + throw new RuntimeException("Timestamp: storing ctime failed!"); } } diff --git a/test/tools/pack200/TimeStamp.java b/test/tools/pack200/TimeStamp.java index acce4624aab82c026d4a30f64975b1a9dc210ca6..60e69b726b28e679c80b4d090fbc12c39e3e8bb5 100644 --- a/test/tools/pack200/TimeStamp.java +++ b/test/tools/pack200/TimeStamp.java @@ -88,6 +88,7 @@ public class TimeStamp { unpackNative(packFile, pstFile); verifyJar(goldenFile, pstFile); pstFile.delete(); + Utils.cleanup(); } static void unpackNative(File packFile, File outFile) { @@ -149,7 +150,6 @@ public class TimeStamp { Utils.close(jf1); Utils.close(jf2); } - Utils.cleanup(); if (errors > 0) { throw new RuntimeException("FAIL:" + errors + " error(s) encounted"); }