4759491: method ZipEntry.setTime(long) works incorrectly
6303183: Support NTFS and Unix-style timestamps for entries in Zip files
7012856: (zipfs) Newly created entry in zip file system should set all file times non-null values.
7012868: (zipfs) file times of entry in zipfs should always be the same regardless of TimeZone.
Summary: to add suuport of Info-ZIP extended timestamp in extra data fields
Reviewed-by: martin, alanb
--- a/jdk/src/share/classes/java/util/zip/ZipConstants.java Wed May 29 14:57:51 2013 +0100
+++ b/jdk/src/share/classes/java/util/zip/ZipConstants.java Wed May 29 19:50:47 2013 -0700
@@ -69,6 +69,14 @@
static final int EXTLEN = 12; // uncompressed size
/*
+ * 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
+
+ /*
* Central directory (CEN) header field offsets
*/
static final int CENVEM = 4; // version made by
--- a/jdk/src/share/classes/java/util/zip/ZipEntry.java Wed May 29 14:57:51 2013 +0100
+++ b/jdk/src/share/classes/java/util/zip/ZipEntry.java Wed May 29 19:50:47 2013 -0700
@@ -25,8 +25,6 @@
package java.util.zip;
-import java.util.Date;
-
/**
* This class is used to represent a ZIP file entry.
*
@@ -35,7 +33,7 @@
public
class ZipEntry implements ZipConstants, Cloneable {
String name; // entry name
- long time = -1; // modification time (in DOS time)
+ long mtime = -1; // last modification time
long crc = -1; // crc-32 of entry data
long size = -1; // uncompressed size of entry data
long csize = -1; // compressed size of entry data
@@ -79,7 +77,7 @@
*/
public ZipEntry(ZipEntry e) {
name = e.name;
- time = e.time;
+ mtime = e.mtime;
crc = e.crc;
size = e.size;
csize = e.csize;
@@ -89,7 +87,7 @@
comment = e.comment;
}
- /*
+ /**
* Creates a new un-initialized zip entry
*/
ZipEntry() {}
@@ -103,22 +101,26 @@
}
/**
- * Sets the modification time of the entry.
- * @param time the entry modification time in number of milliseconds
- * since the epoch
+ * Sets the last modification time of the entry.
+ *
+ * @param time the last modification time of the entry in milliseconds since the epoch
* @see #getTime()
*/
public void setTime(long time) {
- this.time = javaToDosTime(time);
+ this.mtime = time;
}
/**
- * Returns the modification time of the entry, or -1 if not specified.
- * @return the modification time of the entry, or -1 if not specified
+ * Returns the last modification time of the entry.
+ * <p> 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
* @see #setTime(long)
*/
public long getTime() {
- return time != -1 ? dosToJavaTime(time) : -1;
+ return mtime;
}
/**
@@ -277,35 +279,6 @@
return getName();
}
- /*
- * Converts DOS time to Java time (number of milliseconds since epoch).
- */
- private static long dosToJavaTime(long dtime) {
- @SuppressWarnings("deprecation") // Use of date constructor.
- Date d = new Date((int)(((dtime >> 25) & 0x7f) + 80),
- (int)(((dtime >> 21) & 0x0f) - 1),
- (int)((dtime >> 16) & 0x1f),
- (int)((dtime >> 11) & 0x1f),
- (int)((dtime >> 5) & 0x3f),
- (int)((dtime << 1) & 0x3e));
- return d.getTime();
- }
-
- /*
- * Converts Java time to DOS time.
- */
- @SuppressWarnings("deprecation") // Use of date methods
- private static long javaToDosTime(long time) {
- Date d = new Date(time);
- int year = d.getYear() + 1900;
- if (year < 1980) {
- return (1 << 21) | (1 << 16);
- }
- return (year - 1980) << 25 | (d.getMonth() + 1) << 21 |
- d.getDate() << 16 | d.getHours() << 11 | d.getMinutes() << 5 |
- d.getSeconds() >> 1;
- }
-
/**
* Returns the hash code value for this entry.
*/
--- a/jdk/src/share/classes/java/util/zip/ZipFile.java Wed May 29 14:57:51 2013 +0100
+++ b/jdk/src/share/classes/java/util/zip/ZipFile.java Wed May 29 19:50:47 2013 -0700
@@ -46,6 +46,7 @@
import java.util.stream.StreamSupport;
import static java.util.zip.ZipConstants64.*;
+import static java.util.zip.ZipUtils.*;
/**
* This class is used to read entries from a zip file.
@@ -564,12 +565,44 @@
e.name = zc.toString(bname, bname.length);
}
}
- e.time = getEntryTime(jzentry);
e.crc = getEntryCrc(jzentry);
e.size = getEntrySize(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));
+ }
byte[] bcomm = getEntryBytes(jzentry, JZENTRY_COMMENT);
if (bcomm == null) {
e.comment = null;
--- a/jdk/src/share/classes/java/util/zip/ZipInputStream.java Wed May 29 14:57:51 2013 +0100
+++ b/jdk/src/share/classes/java/util/zip/ZipInputStream.java Wed May 29 19:50:47 2013 -0700
@@ -32,6 +32,7 @@
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import static java.util.zip.ZipConstants64.*;
+import static java.util.zip.ZipUtils.*;
/**
* This class implements an input stream filter for reading files in the
@@ -302,7 +303,7 @@
throw new ZipException("encrypted ZIP entry not supported");
}
e.method = get16(tmpbuf, LOCHOW);
- e.time = get32(tmpbuf, LOCTIM);
+ e.mtime = dosToJavaTime(get32(tmpbuf, LOCTIM));
if ((flag & 8) == 8) {
/* "Data Descriptor" present */
if (e.method != DEFLATED) {
@@ -316,32 +317,51 @@
}
len = get16(tmpbuf, LOCEXT);
if (len > 0) {
- byte[] bb = new byte[len];
- readFully(bb, 0, len);
- e.setExtra(bb);
+ byte[] extra = new byte[len];
+ readFully(extra, 0, len);
+ e.setExtra(extra);
// extra fields are in "HeaderID(2)DataSize(2)Data... format
- if (e.csize == ZIP64_MAGICVAL || e.size == ZIP64_MAGICVAL) {
- int off = 0;
- while (off + 4 < len) {
- int sz = get16(bb, off + 2);
- if (get16(bb, off) == ZIP64_EXTID) {
- off += 4;
- // LOC extra zip64 entry MUST include BOTH original and
- // compressed file size fields
- if (sz < 16 || (off + sz) > len ) {
- // Invalid zip64 extra fields, simply skip. Even it's
- // rare, it's possible the entry size happens to be
- // the magic value and it "accidnetly" has some bytes
- // in extra match the id.
- return e;
- }
- e.size = get64(bb, off);
- e.csize = get64(bb, off + 8);
+ 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;
}
- off += (sz + 4);
+ break;
+ default: // unknown tag
}
+ off += (sz + 4);
}
+
}
return e;
}
@@ -430,27 +450,4 @@
}
}
- /*
- * Fetches unsigned 16-bit value from byte array at specified offset.
- * The bytes are assumed to be in Intel (little-endian) byte order.
- */
- private static final int get16(byte b[], int off) {
- return Byte.toUnsignedInt(b[off]) | (Byte.toUnsignedInt(b[off+1]) << 8);
- }
-
- /*
- * Fetches unsigned 32-bit value from byte array at specified offset.
- * The bytes are assumed to be in Intel (little-endian) byte order.
- */
- private static final long get32(byte b[], int off) {
- return (get16(b, off) | ((long)get16(b, off+2) << 16)) & 0xffffffffL;
- }
-
- /*
- * Fetches signed 64-bit value from byte array at specified offset.
- * The bytes are assumed to be in Intel (little-endian) byte order.
- */
- private static final long get64(byte b[], int off) {
- return get32(b, off) | (get32(b, off+4) << 32);
- }
}
--- a/jdk/src/share/classes/java/util/zip/ZipOutputStream.java Wed May 29 14:57:51 2013 +0100
+++ b/jdk/src/share/classes/java/util/zip/ZipOutputStream.java Wed May 29 19:50:47 2013 -0700
@@ -32,6 +32,7 @@
import java.util.Vector;
import java.util.HashSet;
import static java.util.zip.ZipConstants64.*;
+import static java.util.zip.ZipUtils.*;
/**
* This class implements an output stream filter for writing files in the
@@ -190,7 +191,7 @@
if (current != null) {
closeEntry(); // close previous entry
}
- if (e.time == -1) {
+ if (e.mtime == -1) {
e.setTime(System.currentTimeMillis());
}
if (e.method == -1) {
@@ -382,16 +383,25 @@
private void writeLOC(XEntry xentry) throws IOException {
ZipEntry e = xentry.entry;
int flag = e.flag;
+ boolean hasZip64 = false;
int elen = (e.extra != null) ? e.extra.length : 0;
- boolean hasZip64 = false;
-
+ 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);
+ }
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(e.time); // last modification time
+ writeInt(javaToDosTime(e.mtime)); // last modification time
// store size, uncompressed size, and crc-32 in data descriptor
// immediately following compressed entry data
@@ -407,7 +417,7 @@
}
writeShort(flag); // general purpose bit flag
writeShort(e.method); // compression method
- writeInt(e.time); // last modification time
+ writeInt(javaToDosTime(e.mtime)); // last modification time
writeInt(e.crc); // crc-32
if (hasZip64) {
writeInt(ZIP64_MAGICVAL);
@@ -420,6 +430,8 @@
}
byte[] nameBytes = zc.getBytes(e.name);
writeShort(nameBytes.length);
+ if (!foundEXTT)
+ elen += 9; // use Info-ZIP's ext time in extra
writeShort(elen);
writeBytes(nameBytes, 0, nameBytes.length);
if (hasZip64) {
@@ -428,6 +440,12 @@
writeLong(e.size);
writeLong(e.csize);
}
+ if (!foundEXTT) {
+ 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);
}
@@ -457,25 +475,25 @@
ZipEntry e = xentry.entry;
int flag = e.flag;
int version = version(e);
-
long csize = e.csize;
long size = e.size;
long offset = xentry.offset;
- int e64len = 0;
+ int elenZIP64 = 0;
boolean hasZip64 = false;
+
if (e.csize >= ZIP64_MAGICVAL) {
csize = ZIP64_MAGICVAL;
- e64len += 8; // csize(8)
+ elenZIP64 += 8; // csize(8)
hasZip64 = true;
}
if (e.size >= ZIP64_MAGICVAL) {
size = ZIP64_MAGICVAL; // size(8)
- e64len += 8;
+ elenZIP64 += 8;
hasZip64 = true;
}
if (xentry.offset >= ZIP64_MAGICVAL) {
offset = ZIP64_MAGICVAL;
- e64len += 8; // offset(8)
+ elenZIP64 += 8; // offset(8)
hasZip64 = true;
}
writeInt(CENSIG); // CEN header signature
@@ -488,18 +506,32 @@
}
writeShort(flag); // general purpose bit flag
writeShort(e.method); // compression method
- writeInt(e.time); // last modification time
+ writeInt(javaToDosTime(e.mtime)); // 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);
+ }
if (hasZip64) {
// + headid(2) + datasize(2)
- writeShort(e64len + 4 + (e.extra != null ? e.extra.length : 0));
- } else {
- writeShort(e.extra != null ? e.extra.length : 0);
+ elen += (elenZIP64 + 4);
}
+ if (!foundEXTT)
+ elen += 9; // Info-ZIP's Extended Timestamp
+ writeShort(elen);
byte[] commentBytes;
if (e.comment != null) {
commentBytes = zc.getBytes(e.comment);
@@ -515,7 +547,7 @@
writeBytes(nameBytes, 0, nameBytes.length);
if (hasZip64) {
writeShort(ZIP64_EXTID);// Zip64 extra
- writeShort(e64len);
+ writeShort(elenZIP64);
if (size == ZIP64_MAGICVAL)
writeLong(e.size);
if (csize == ZIP64_MAGICVAL)
@@ -523,6 +555,12 @@
if (offset == ZIP64_MAGICVAL)
writeLong(xentry.offset);
}
+ if (!foundEXTT) {
+ writeShort(EXTID_EXTT);
+ writeShort(5);
+ writeByte(0x1); // flags byte
+ writeInt(javaToUnixTime(e.mtime));
+ }
if (e.extra != null) {
writeBytes(e.extra, 0, e.extra.length);
}
@@ -589,6 +627,15 @@
}
/*
+ * Writes a 8-bit byte to the output stream.
+ */
+ private void writeByte(int v) throws IOException {
+ OutputStream out = this.out;
+ out.write(v & 0xff);
+ written += 1;
+ }
+
+ /*
* Writes a 16-bit short to the output stream in little-endian byte order.
*/
private void writeShort(int v) throws IOException {
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/java/util/zip/ZipUtils.java Wed May 29 19:50:47 2013 -0700
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 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
+ * 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 java.util.Date;
+import java.util.concurrent.TimeUnit;
+
+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.
+ */
+ public static final long winToJavaTime(long wtime) {
+ return TimeUnit.MILLISECONDS.convert(
+ wtime / 10 + WINDOWS_EPOCH_IN_MICROSECONDS, TimeUnit.MICROSECONDS);
+ }
+
+ /**
+ * Converts Java time to Windows time.
+ */
+ public static final long javaToWinTime(long time) {
+ return (TimeUnit.MICROSECONDS.convert(time, TimeUnit.MILLISECONDS)
+ - WINDOWS_EPOCH_IN_MICROSECONDS) * 10;
+ }
+
+ /**
+ * Converts "standard Unix time"(in seconds, UTC/GMT) to Java time
+ */
+ public static final long unixToJavaTime(long utime) {
+ return TimeUnit.MILLISECONDS.convert(utime, TimeUnit.SECONDS);
+ }
+
+ /**
+ * Converts Java time to "standard Unix time".
+ */
+ public static final long javaToUnixTime(long time) {
+ return TimeUnit.SECONDS.convert(time, TimeUnit.MILLISECONDS);
+ }
+
+ /**
+ * Converts DOS time to Java time (number of milliseconds since epoch).
+ */
+ public static long dosToJavaTime(long dtime) {
+ @SuppressWarnings("deprecation") // Use of date constructor.
+ Date d = new Date((int)(((dtime >> 25) & 0x7f) + 80),
+ (int)(((dtime >> 21) & 0x0f) - 1),
+ (int)((dtime >> 16) & 0x1f),
+ (int)((dtime >> 11) & 0x1f),
+ (int)((dtime >> 5) & 0x3f),
+ (int)((dtime << 1) & 0x3e));
+ return d.getTime();
+ }
+
+ /**
+ * Converts Java time to DOS time.
+ */
+ @SuppressWarnings("deprecation") // Use of date methods
+ public static long javaToDosTime(long time) {
+ Date d = new Date(time);
+ int year = d.getYear() + 1900;
+ if (year < 1980) {
+ return (1 << 21) | (1 << 16);
+ }
+ return (year - 1980) << 25 | (d.getMonth() + 1) << 21 |
+ d.getDate() << 16 | d.getHours() << 11 | d.getMinutes() << 5 |
+ 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.
+ */
+ public static final int get16(byte b[], int off) {
+ return Byte.toUnsignedInt(b[off]) | (Byte.toUnsignedInt(b[off+1]) << 8);
+ }
+
+ /**
+ * Fetches unsigned 32-bit value from byte array at specified offset.
+ * The bytes are assumed to be in Intel (little-endian) byte order.
+ */
+ public static final long get32(byte b[], int off) {
+ return (get16(b, off) | ((long)get16(b, off+2) << 16)) & 0xffffffffL;
+ }
+
+ /**
+ * Fetches signed 64-bit value from byte array at specified offset.
+ * The bytes are assumed to be in Intel (little-endian) byte order.
+ */
+ public static final long get64(byte b[], int off) {
+ return get32(b, off) | (get32(b, off+4) << 32);
+ }
+
+}
--- a/jdk/src/share/demo/nio/zipfs/src/com/sun/nio/zipfs/ZipFileSystem.java Wed May 29 14:57:51 2013 +0100
+++ b/jdk/src/share/demo/nio/zipfs/src/com/sun/nio/zipfs/ZipFileSystem.java Wed May 29 19:50:47 2013 -0700
@@ -1818,7 +1818,7 @@
Entry(byte[] name) {
name(name);
- this.mtime = System.currentTimeMillis();
+ this.mtime = this.ctime = this.atime = System.currentTimeMillis();
this.crc = 0;
this.size = 0;
this.csize = 0;
@@ -1912,17 +1912,18 @@
{
int written = CENHDR;
int version0 = version();
-
long csize0 = csize;
long size0 = size;
long locoff0 = locoff;
int elen64 = 0; // extra for ZIP64
int elenNTFS = 0; // extra for NTFS (a/c/mtime)
int elenEXTT = 0; // extra for Extended Timestamp
+ boolean foundExtraTime = false; // if time stamp NTFS, EXTT present
// confirm size/length
int nlen = (name != null) ? name.length : 0;
int elen = (extra != null) ? extra.length : 0;
+ int eoff = 0;
int clen = (comment != null) ? comment.length : 0;
if (csize >= ZIP64_MINVAL) {
csize0 = ZIP64_MINVAL;
@@ -1936,14 +1937,24 @@
locoff0 = ZIP64_MINVAL;
elen64 += 8; // offset(8)
}
- if (elen64 != 0)
+ if (elen64 != 0) {
elen64 += 4; // header and data sz 4 bytes
+ }
- if (atime != -1) {
- if (isWindows) // use NTFS
+ while (eoff + 4 < elen) {
+ int tag = SH(extra, eoff);
+ int sz = SH(extra, eoff + 2);
+ if (tag == EXTID_EXTT || tag == EXTID_NTFS) {
+ foundExtraTime = true;
+ }
+ eoff += (4 + sz);
+ }
+ if (!foundExtraTime) {
+ if (isWindows) { // use NTFS
elenNTFS = 36; // total 36 bytes
- else // Extended Timestamp otherwise
+ } else { // Extended Timestamp otherwise
elenEXTT = 9; // only mtime in cen
+ }
}
writeInt(os, CENSIG); // CEN header signature
if (elen64 != 0) {
@@ -2092,11 +2103,13 @@
{
writeInt(os, LOCSIG); // LOC header signature
int version = version();
-
int nlen = (name != null) ? name.length : 0;
int elen = (extra != null) ? extra.length : 0;
+ boolean foundExtraTime = false; // if extra timestamp present
+ int eoff = 0;
int elen64 = 0;
int elenEXTT = 0;
+ int elenNTFS = 0;
if ((flag & FLAG_DATADESCR) != 0) {
writeShort(os, version()); // version needed to extract
writeShort(os, flag); // general purpose bit flag
@@ -2128,14 +2141,27 @@
writeInt(os, size); // uncompressed size
}
}
- if (atime != -1 && !isWindows) { // on unix use "ext time"
- if (ctime == -1)
- elenEXTT = 13;
- else
- elenEXTT = 17;
+ while (eoff + 4 < elen) {
+ int tag = SH(extra, eoff);
+ int sz = SH(extra, eoff + 2);
+ if (tag == EXTID_EXTT || tag == EXTID_NTFS) {
+ foundExtraTime = true;
+ }
+ eoff += (4 + sz);
+ }
+ if (!foundExtraTime) {
+ if (isWindows) {
+ elenNTFS = 36; // NTFS, total 36 bytes
+ } else { // on unix use "ext time"
+ elenEXTT = 9;
+ if (atime != -1)
+ elenEXTT += 4;
+ if (ctime != -1)
+ elenEXTT += 4;
+ }
}
writeShort(os, name.length);
- writeShort(os, elen + elen64 + elenEXTT);
+ writeShort(os, elen + elen64 + elenNTFS + elenEXTT);
writeBytes(os, name);
if (elen64 != 0) {
writeShort(os, EXTID_ZIP64);
@@ -2143,15 +2169,28 @@
writeLong(os, size);
writeLong(os, csize);
}
+ if (elenNTFS != 0) {
+ writeShort(os, EXTID_NTFS);
+ writeShort(os, elenNTFS - 4);
+ writeInt(os, 0); // reserved
+ writeShort(os, 0x0001); // NTFS attr tag
+ writeShort(os, 24);
+ writeLong(os, javaToWinTime(mtime));
+ writeLong(os, javaToWinTime(atime));
+ writeLong(os, javaToWinTime(ctime));
+ }
if (elenEXTT != 0) {
writeShort(os, EXTID_EXTT);
writeShort(os, elenEXTT - 4);// size for the folowing data block
- if (ctime == -1)
- os.write(0x3); // mtime and atime
- else
- os.write(0x7); // mtime, atime and ctime
+ int fbyte = 0x1;
+ if (atime != -1) // mtime and atime
+ fbyte |= 0x2;
+ if (ctime != -1) // mtime, atime and ctime
+ fbyte |= 0x4;
+ os.write(fbyte); // flags byte
writeInt(os, javaToUnixTime(mtime));
- writeInt(os, javaToUnixTime(atime));
+ if (atime != -1)
+ writeInt(os, javaToUnixTime(atime));
if (ctime != -1)
writeInt(os, javaToUnixTime(ctime));
}
--- a/jdk/src/share/demo/nio/zipfs/src/com/sun/nio/zipfs/ZipInfo.java Wed May 29 14:57:51 2013 +0100
+++ b/jdk/src/share/demo/nio/zipfs/src/com/sun/nio/zipfs/ZipInfo.java Wed May 29 19:50:47 2013 -0700
@@ -214,7 +214,7 @@
winToJavaTime(LL(extra, off + 24)));
break;
case EXTID_EXTT:
- print(" ->Inof-ZIP Extended Timestamp: flag=%x%n",extra[off]);
+ print(" ->Info-ZIP Extended Timestamp: flag=%x%n",extra[off]);
pos = off + 1 ;
while (pos + 4 <= off + sz) {
print(" *%tc%n",
@@ -223,6 +223,7 @@
}
break;
default:
+ print(" ->[tag=%x, size=%d]%n", tag, sz);
}
off += sz;
}
--- a/jdk/test/demo/zipfs/ZipFSTester.java Wed May 29 14:57:51 2013 +0100
+++ b/jdk/test/demo/zipfs/ZipFSTester.java Wed May 29 19:50:47 2013 -0700
@@ -29,6 +29,7 @@
import java.nio.file.attribute.*;
import java.net.*;
import java.util.*;
+import java.util.concurrent.TimeUnit;
import java.util.zip.*;
import static java.nio.file.StandardOpenOption.*;
@@ -48,6 +49,7 @@
test0(fs);
test1(fs);
test2(fs); // more tests
+ testTime(Paths.get(args[0]));
}
}
@@ -337,6 +339,46 @@
Files.delete(fs3Path);
}
+ // test file stamp
+ static void testTime(Path src) throws Exception {
+ // create a new filesystem, copy this file into it
+ Map<String, Object> env = new HashMap<String, Object>();
+ env.put("create", "true");
+ Path fsPath = getTempPath();
+ FileSystem fs = newZipFileSystem(fsPath, env);
+
+ System.out.println("test copy with timestamps...");
+ // copyin
+ Path dst = getPathWithParents(fs, "me");
+ Files.copy(src, dst, COPY_ATTRIBUTES);
+ checkEqual(src, dst);
+
+ BasicFileAttributes attrs = Files
+ .getFileAttributeView(src, BasicFileAttributeView.class)
+ .readAttributes();
+ System.out.println("mtime: " + attrs.lastModifiedTime());
+ System.out.println("ctime: " + attrs.creationTime());
+ System.out.println("atime: " + attrs.lastAccessTime());
+ System.out.println(" ==============>");
+ BasicFileAttributes dstAttrs = Files
+ .getFileAttributeView(dst, BasicFileAttributeView.class)
+ .readAttributes();
+ System.out.println("mtime: " + dstAttrs.lastModifiedTime());
+ System.out.println("ctime: " + dstAttrs.creationTime());
+ System.out.println("atime: " + dstAttrs.lastAccessTime());
+
+ // 1-second granularity
+ if (attrs.lastModifiedTime().to(TimeUnit.SECONDS) !=
+ dstAttrs.lastModifiedTime().to(TimeUnit.SECONDS) ||
+ attrs.lastAccessTime().to(TimeUnit.SECONDS) !=
+ dstAttrs.lastAccessTime().to(TimeUnit.SECONDS) ||
+ attrs.creationTime().to(TimeUnit.SECONDS) !=
+ dstAttrs.creationTime().to(TimeUnit.SECONDS)) {
+ throw new RuntimeException("Timestamp Copy Failed!");
+ }
+ Files.delete(fsPath);
+ }
+
private static FileSystem newZipFileSystem(Path path, Map<String, ?> env)
throws Exception
{
--- a/jdk/test/demo/zipfs/basic.sh Wed May 29 14:57:51 2013 +0100
+++ b/jdk/test/demo/zipfs/basic.sh Wed May 29 19:50:47 2013 -0700
@@ -22,7 +22,7 @@
#
# @test
# @bug 6990846 7009092 7009085 7015391 7014948 7005986 7017840 7007596
-# 7157656 8002390
+# 7157656 8002390 7012868 7012856
# @summary Test ZipFileSystem demo
# @build Basic PathOps ZipFSTester
# @run shell basic.sh
--- a/jdk/test/java/util/jar/TestExtra.java Wed May 29 14:57:51 2013 +0100
+++ b/jdk/test/java/util/jar/TestExtra.java Wed May 29 19:50:47 2013 -0700
@@ -23,7 +23,7 @@
/**
* @test
- * @bug 6480504
+ * @bug 6480504 6303183
* @summary Test that client-provided data in the extra field is written and
* read correctly, taking into account the JAR_MAGIC written into the extra
* field of the first entry of JAR files.
@@ -117,8 +117,7 @@
ZipInputStream zis = getInputStream();
ze = zis.getNextEntry();
- byte[] e = ze.getExtra();
- check(e.length == 8, "expected extra length is 8, got " + e.length);
+ checkExtra(data, ze.getExtra());
checkEntry(ze, 0, 0);
}
@@ -140,10 +139,43 @@
ZipInputStream zis = getInputStream();
ze = zis.getNextEntry();
byte[] e = ze.getExtra();
- check(e.length == 8, "expected extra length is 8, got " + e.length);
+ checkExtra(data, ze.getExtra());
checkEntry(ze, 0, 0);
}
+ // check if all "expected" extra fields equal to their
+ // corresponding fields in "extra". The "extra" might have
+ // timestamp fields added by ZOS.
+ static void checkExtra(byte[] expected, byte[] extra) {
+ if (expected == null)
+ return;
+ int off = 0;
+ int len = expected.length;
+ while (off + 4 < len) {
+ int tag = get16(expected, off);
+ int sz = get16(expected, off + 2);
+ int off0 = 0;
+ int len0 = extra.length;
+ boolean matched = false;
+ while (off0 + 4 < len0) {
+ int tag0 = get16(extra, off0);
+ int sz0 = get16(extra, off0 + 2);
+ if (tag == tag0 && sz == sz0) {
+ matched = true;
+ for (int i = 0; i < sz; i++) {
+ if (expected[off + i] != extra[off0 +i])
+ matched = false;
+ }
+ break;
+ }
+ off0 += (4 + sz0);
+ }
+ if (!matched) {
+ fail("Expected extra data [tag=" + tag + "sz=" + sz + "] check failed");
+ }
+ off += (4 + sz);
+ }
+ }
/** Check that the entry's extra data is correct. */
void checkEntry(ZipEntry ze, int count, int dataLength) {
--- a/jdk/test/java/util/zip/StoredCRC.java Wed May 29 14:57:51 2013 +0100
+++ b/jdk/test/java/util/zip/StoredCRC.java Wed May 29 19:50:47 2013 -0700
@@ -77,9 +77,9 @@
unexpected(t);
}
- // Test that data corruption is detected. Offset 39 was
+ // Test that data corruption is detected. "offset" was
// determined to be in the entry's uncompressed data.
- data[39] ^= 1;
+ data[getDataOffset(data) + 4] ^= 1;
zis = new ZipInputStream(
new ByteArrayInputStream(data));
@@ -97,6 +97,15 @@
}
}
+ public static final int getDataOffset(byte b[]) {
+ final int LOCHDR = 30; // LOC header size
+ final int LOCEXT = 28; // extra field length
+ final int LOCNAM = 26; // filename length
+ int lenExt = Byte.toUnsignedInt(b[LOCEXT]) | (Byte.toUnsignedInt(b[LOCEXT + 1]) << 8);
+ int lenNam = Byte.toUnsignedInt(b[LOCNAM]) | (Byte.toUnsignedInt(b[LOCNAM + 1]) << 8);
+ return LOCHDR + lenExt + lenNam;
+ }
+
//--------------------- Infrastructure ---------------------------
static volatile int passed = 0, failed = 0;
static boolean pass() {passed++; return true;}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/util/zip/TestExtraTime.java Wed May 29 19:50:47 2013 -0700
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 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
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+/**
+ * @test
+ * @bug 4759491 6303183 7012868
+ * @summary Test ZOS and ZIS timestamp in extra field correctly
+ */
+
+import java.io.*;
+import java.util.TimeZone;
+import java.util.concurrent.TimeUnit;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+import java.util.zip.ZipOutputStream;
+
+
+public class TestExtraTime {
+
+ public static void main(String[] args) throws Throwable{
+
+ 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"));
+ }
+ }
+
+ private static void test(long mtime, TimeZone tz) throws Throwable {
+ TimeZone tz0 = TimeZone.getDefault();
+ if (tz != null) {
+ TimeZone.setDefault(tz);
+ }
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ ZipOutputStream zos = new ZipOutputStream(baos);
+ ZipEntry ze = new ZipEntry("TestExtreTime.java");
+
+ ze.setTime(mtime);
+ zos.putNextEntry(ze);
+ zos.write(new byte[] { 1,2 ,3, 4});
+ zos.close();
+ if (tz != null) {
+ TimeZone.setDefault(tz0);
+ }
+ ZipInputStream zis = new ZipInputStream(
+ new ByteArrayInputStream(baos.toByteArray()));
+ ze = zis.getNextEntry();
+ zis.close();
+
+ 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!");
+
+ }
+}
--- a/jdk/test/java/util/zip/ZipFile/Assortment.java Wed May 29 14:57:51 2013 +0100
+++ b/jdk/test/java/util/zip/ZipFile/Assortment.java Wed May 29 19:50:47 2013 -0700
@@ -22,7 +22,7 @@
*/
/* @test
- * @bug 4770745 6234507
+ * @bug 4770745 6234507 6303183
* @summary test a variety of zip file entries
* @author Martin Buchholz
*/
@@ -54,6 +54,44 @@
check(condition, "Something's wrong");
}
+ static final int get16(byte b[], int off) {
+ return Byte.toUnsignedInt(b[off]) | (Byte.toUnsignedInt(b[off+1]) << 8);
+ }
+
+ // check if all "expected" extra fields equal to their
+ // corresponding fields in "extra". The "extra" might have
+ // timestamp fields added by ZOS.
+ static boolean equalsExtraData(byte[] expected, byte[] extra) {
+ if (expected == null)
+ return true;
+ int off = 0;
+ int len = expected.length;
+ while (off + 4 < len) {
+ int tag = get16(expected, off);
+ int sz = get16(expected, off + 2);
+ int off0 = 0;
+ int len0 = extra.length;
+ boolean matched = false;
+ while (off0 + 4 < len0) {
+ int tag0 = get16(extra, off0);
+ int sz0 = get16(extra, off0 + 2);
+ if (tag == tag0 && sz == sz0) {
+ matched = true;
+ for (int i = 0; i < sz; i++) {
+ if (expected[off + i] != extra[off0 +i])
+ matched = false;
+ }
+ break;
+ }
+ off0 += (4 + sz0);
+ }
+ if (!matched)
+ return false;
+ off += (4 + sz);
+ }
+ return true;
+ }
+
private static class Entry {
private String name;
private int method;
@@ -109,7 +147,7 @@
check((((comment == null) || comment.equals(""))
&& (e.getComment() == null))
|| comment.equals(e.getComment()));
- check(Arrays.equals(extra, e.getExtra()));
+ check(equalsExtraData(extra, e.getExtra()));
check(Arrays.equals(data, getData(f, e)));
check(e.getSize() == data.length);
check((method == ZipEntry.DEFLATED) ||
@@ -129,8 +167,7 @@
byte[] extra = (this.extra != null && this.extra.length == 0) ?
null : this.extra;
- check(Arrays.equals(extra, e.getExtra()));
-
+ check(equalsExtraData(extra, e.getExtra()));
check(name.equals(e.getName()));
check(method == e.getMethod());
check(e.getSize() == -1 || e.getSize() == data.length);