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

To add suuport of Info-ZIP extended timestamp in extra data fields

Reviewed-by: martin, alanb
This commit is contained in:
Xueming Shen 2013-05-29 19:50:47 -07:00
parent d57a6a4cb3
commit 9e918d6fbc
14 changed files with 551 additions and 134 deletions

View File

@ -68,6 +68,14 @@ interface ZipConstants {
static final int EXTSIZ = 8; // compressed size static final int EXTSIZ = 8; // compressed size
static final int EXTLEN = 12; // uncompressed size 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 * Central directory (CEN) header field offsets
*/ */

View File

@ -25,8 +25,6 @@
package java.util.zip; package java.util.zip;
import java.util.Date;
/** /**
* This class is used to represent a ZIP file entry. * This class is used to represent a ZIP file entry.
* *
@ -35,7 +33,7 @@ import java.util.Date;
public public
class ZipEntry implements ZipConstants, Cloneable { class ZipEntry implements ZipConstants, Cloneable {
String name; // entry name 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 crc = -1; // crc-32 of entry data
long size = -1; // uncompressed size of entry data long size = -1; // uncompressed size of entry data
long csize = -1; // compressed size of entry data long csize = -1; // compressed size of entry data
@ -79,7 +77,7 @@ class ZipEntry implements ZipConstants, Cloneable {
*/ */
public ZipEntry(ZipEntry e) { public ZipEntry(ZipEntry e) {
name = e.name; name = e.name;
time = e.time; mtime = e.mtime;
crc = e.crc; crc = e.crc;
size = e.size; size = e.size;
csize = e.csize; csize = e.csize;
@ -89,7 +87,7 @@ class ZipEntry implements ZipConstants, Cloneable {
comment = e.comment; comment = e.comment;
} }
/* /**
* Creates a new un-initialized zip entry * Creates a new un-initialized zip entry
*/ */
ZipEntry() {} ZipEntry() {}
@ -103,22 +101,26 @@ class ZipEntry implements ZipConstants, Cloneable {
} }
/** /**
* Sets the modification time of the entry. * Sets the last modification time of the entry.
* @param time the entry modification time in number of milliseconds *
* since the epoch * @param time the last modification time of the entry in milliseconds since the epoch
* @see #getTime() * @see #getTime()
*/ */
public void setTime(long time) { public void setTime(long time) {
this.time = javaToDosTime(time); this.mtime = time;
} }
/** /**
* Returns the modification time of the entry, or -1 if not specified. * Returns the last modification time of the entry.
* @return the modification time of the entry, or -1 if not specified * <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) * @see #setTime(long)
*/ */
public long getTime() { public long getTime() {
return time != -1 ? dosToJavaTime(time) : -1; return mtime;
} }
/** /**
@ -277,35 +279,6 @@ class ZipEntry implements ZipConstants, Cloneable {
return getName(); 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. * Returns the hash code value for this entry.
*/ */

View File

@ -46,6 +46,7 @@ import java.util.stream.Stream;
import java.util.stream.StreamSupport; import java.util.stream.StreamSupport;
import static java.util.zip.ZipConstants64.*; import static java.util.zip.ZipConstants64.*;
import static java.util.zip.ZipUtils.*;
/** /**
* This class is used to read entries from a zip file. * This class is used to read entries from a zip file.
@ -564,12 +565,44 @@ class ZipFile implements ZipConstants, Closeable {
e.name = zc.toString(bname, bname.length); e.name = zc.toString(bname, bname.length);
} }
} }
e.time = getEntryTime(jzentry);
e.crc = getEntryCrc(jzentry); e.crc = getEntryCrc(jzentry);
e.size = getEntrySize(jzentry); e.size = getEntrySize(jzentry);
e. csize = getEntryCSize(jzentry); e. csize = getEntryCSize(jzentry);
e.method = getEntryMethod(jzentry); e.method = getEntryMethod(jzentry);
e.extra = getEntryBytes(jzentry, JZENTRY_EXTRA); 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); byte[] bcomm = getEntryBytes(jzentry, JZENTRY_COMMENT);
if (bcomm == null) { if (bcomm == null) {
e.comment = null; e.comment = null;

View File

@ -32,6 +32,7 @@ import java.io.PushbackInputStream;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import static java.util.zip.ZipConstants64.*; import static java.util.zip.ZipConstants64.*;
import static java.util.zip.ZipUtils.*;
/** /**
* This class implements an input stream filter for reading files in the * This class implements an input stream filter for reading files in the
@ -302,7 +303,7 @@ class ZipInputStream extends InflaterInputStream implements ZipConstants {
throw new ZipException("encrypted ZIP entry not supported"); throw new ZipException("encrypted ZIP entry not supported");
} }
e.method = get16(tmpbuf, LOCHOW); e.method = get16(tmpbuf, LOCHOW);
e.time = get32(tmpbuf, LOCTIM); e.mtime = dosToJavaTime(get32(tmpbuf, LOCTIM));
if ((flag & 8) == 8) { if ((flag & 8) == 8) {
/* "Data Descriptor" present */ /* "Data Descriptor" present */
if (e.method != DEFLATED) { if (e.method != DEFLATED) {
@ -316,32 +317,51 @@ class ZipInputStream extends InflaterInputStream implements ZipConstants {
} }
len = get16(tmpbuf, LOCEXT); len = get16(tmpbuf, LOCEXT);
if (len > 0) { if (len > 0) {
byte[] bb = new byte[len]; byte[] extra = new byte[len];
readFully(bb, 0, len); readFully(extra, 0, len);
e.setExtra(bb); e.setExtra(extra);
// extra fields are in "HeaderID(2)DataSize(2)Data... format // extra fields are in "HeaderID(2)DataSize(2)Data... format
if (e.csize == ZIP64_MAGICVAL || e.size == ZIP64_MAGICVAL) { int off = 0;
int off = 0; while (off + 4 < len) {
while (off + 4 < len) { int pos = off;
int sz = get16(bb, off + 2); int tag = get16(extra, pos);
if (get16(bb, off) == ZIP64_EXTID) { int sz = get16(extra, pos + 2);
off += 4; pos += 4;
// LOC extra zip64 entry MUST include BOTH original and if (pos + sz > len) // invalid data
// compressed file size fields break;
if (sz < 16 || (off + sz) > len ) { switch (tag) {
// Invalid zip64 extra fields, simply skip. Even it's case EXTID_ZIP64 :
// rare, it's possible the entry size happens to be // LOC extra zip64 entry MUST include BOTH original and
// the magic value and it "accidnetly" has some bytes // compressed file size fields.
// in extra match the id. //
return e; // If invalid zip64 extra fields, simply skip. Even it's
} // rare, it's possible the entry size happens to be
e.size = get64(bb, off); // the magic value and it "accidently" has some bytes
e.csize = get64(bb, off + 8); // in extra match the id.
break; if (sz >= 16 && (pos + sz) <= len ) {
e.size = get64(extra, pos);
e.csize = get64(extra, pos + 8);
} }
off += (sz + 4); 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);
} }
} }
return e; return e;
} }
@ -430,27 +450,4 @@ class ZipInputStream extends InflaterInputStream implements ZipConstants {
} }
} }
/*
* 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);
}
} }

View File

@ -32,6 +32,7 @@ import java.nio.charset.StandardCharsets;
import java.util.Vector; import java.util.Vector;
import java.util.HashSet; import java.util.HashSet;
import static java.util.zip.ZipConstants64.*; import static java.util.zip.ZipConstants64.*;
import static java.util.zip.ZipUtils.*;
/** /**
* This class implements an output stream filter for writing files in the * This class implements an output stream filter for writing files in the
@ -190,7 +191,7 @@ class ZipOutputStream extends DeflaterOutputStream implements ZipConstants {
if (current != null) { if (current != null) {
closeEntry(); // close previous entry closeEntry(); // close previous entry
} }
if (e.time == -1) { if (e.mtime == -1) {
e.setTime(System.currentTimeMillis()); e.setTime(System.currentTimeMillis());
} }
if (e.method == -1) { if (e.method == -1) {
@ -382,16 +383,25 @@ class ZipOutputStream extends DeflaterOutputStream implements ZipConstants {
private void writeLOC(XEntry xentry) throws IOException { private void writeLOC(XEntry xentry) throws IOException {
ZipEntry e = xentry.entry; ZipEntry e = xentry.entry;
int flag = e.flag; int flag = e.flag;
int elen = (e.extra != null) ? e.extra.length : 0;
boolean hasZip64 = false; 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);
}
writeInt(LOCSIG); // LOC header signature writeInt(LOCSIG); // LOC header signature
if ((flag & 8) == 8) { if ((flag & 8) == 8) {
writeShort(version(e)); // version needed to extract writeShort(version(e)); // version needed to extract
writeShort(flag); // general purpose bit flag writeShort(flag); // general purpose bit flag
writeShort(e.method); // compression method 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 // store size, uncompressed size, and crc-32 in data descriptor
// immediately following compressed entry data // immediately following compressed entry data
@ -407,7 +417,7 @@ class ZipOutputStream extends DeflaterOutputStream implements ZipConstants {
} }
writeShort(flag); // general purpose bit flag writeShort(flag); // general purpose bit flag
writeShort(e.method); // compression method writeShort(e.method); // compression method
writeInt(e.time); // last modification time writeInt(javaToDosTime(e.mtime)); // last modification time
writeInt(e.crc); // crc-32 writeInt(e.crc); // crc-32
if (hasZip64) { if (hasZip64) {
writeInt(ZIP64_MAGICVAL); writeInt(ZIP64_MAGICVAL);
@ -420,6 +430,8 @@ class ZipOutputStream extends DeflaterOutputStream implements ZipConstants {
} }
byte[] nameBytes = zc.getBytes(e.name); byte[] nameBytes = zc.getBytes(e.name);
writeShort(nameBytes.length); writeShort(nameBytes.length);
if (!foundEXTT)
elen += 9; // use Info-ZIP's ext time in extra
writeShort(elen); writeShort(elen);
writeBytes(nameBytes, 0, nameBytes.length); writeBytes(nameBytes, 0, nameBytes.length);
if (hasZip64) { if (hasZip64) {
@ -428,6 +440,12 @@ class ZipOutputStream extends DeflaterOutputStream implements ZipConstants {
writeLong(e.size); writeLong(e.size);
writeLong(e.csize); 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) { if (e.extra != null) {
writeBytes(e.extra, 0, e.extra.length); writeBytes(e.extra, 0, e.extra.length);
} }
@ -457,25 +475,25 @@ class ZipOutputStream extends DeflaterOutputStream implements ZipConstants {
ZipEntry e = xentry.entry; ZipEntry e = xentry.entry;
int flag = e.flag; int flag = e.flag;
int version = version(e); int version = version(e);
long csize = e.csize; long csize = e.csize;
long size = e.size; long size = e.size;
long offset = xentry.offset; long offset = xentry.offset;
int e64len = 0; int elenZIP64 = 0;
boolean hasZip64 = false; boolean hasZip64 = false;
if (e.csize >= ZIP64_MAGICVAL) { if (e.csize >= ZIP64_MAGICVAL) {
csize = ZIP64_MAGICVAL; csize = ZIP64_MAGICVAL;
e64len += 8; // csize(8) elenZIP64 += 8; // csize(8)
hasZip64 = true; hasZip64 = true;
} }
if (e.size >= ZIP64_MAGICVAL) { if (e.size >= ZIP64_MAGICVAL) {
size = ZIP64_MAGICVAL; // size(8) size = ZIP64_MAGICVAL; // size(8)
e64len += 8; elenZIP64 += 8;
hasZip64 = true; hasZip64 = true;
} }
if (xentry.offset >= ZIP64_MAGICVAL) { if (xentry.offset >= ZIP64_MAGICVAL) {
offset = ZIP64_MAGICVAL; offset = ZIP64_MAGICVAL;
e64len += 8; // offset(8) elenZIP64 += 8; // offset(8)
hasZip64 = true; hasZip64 = true;
} }
writeInt(CENSIG); // CEN header signature writeInt(CENSIG); // CEN header signature
@ -488,18 +506,32 @@ class ZipOutputStream extends DeflaterOutputStream implements ZipConstants {
} }
writeShort(flag); // general purpose bit flag writeShort(flag); // general purpose bit flag
writeShort(e.method); // compression method writeShort(e.method); // compression method
writeInt(e.time); // last modification time writeInt(javaToDosTime(e.mtime)); // last modification time
writeInt(e.crc); // crc-32 writeInt(e.crc); // crc-32
writeInt(csize); // compressed size writeInt(csize); // compressed size
writeInt(size); // uncompressed size writeInt(size); // uncompressed size
byte[] nameBytes = zc.getBytes(e.name); byte[] nameBytes = zc.getBytes(e.name);
writeShort(nameBytes.length); 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) { if (hasZip64) {
// + headid(2) + datasize(2) // + headid(2) + datasize(2)
writeShort(e64len + 4 + (e.extra != null ? e.extra.length : 0)); elen += (elenZIP64 + 4);
} else {
writeShort(e.extra != null ? e.extra.length : 0);
} }
if (!foundEXTT)
elen += 9; // Info-ZIP's Extended Timestamp
writeShort(elen);
byte[] commentBytes; byte[] commentBytes;
if (e.comment != null) { if (e.comment != null) {
commentBytes = zc.getBytes(e.comment); commentBytes = zc.getBytes(e.comment);
@ -515,7 +547,7 @@ class ZipOutputStream extends DeflaterOutputStream implements ZipConstants {
writeBytes(nameBytes, 0, nameBytes.length); writeBytes(nameBytes, 0, nameBytes.length);
if (hasZip64) { if (hasZip64) {
writeShort(ZIP64_EXTID);// Zip64 extra writeShort(ZIP64_EXTID);// Zip64 extra
writeShort(e64len); writeShort(elenZIP64);
if (size == ZIP64_MAGICVAL) if (size == ZIP64_MAGICVAL)
writeLong(e.size); writeLong(e.size);
if (csize == ZIP64_MAGICVAL) if (csize == ZIP64_MAGICVAL)
@ -523,6 +555,12 @@ class ZipOutputStream extends DeflaterOutputStream implements ZipConstants {
if (offset == ZIP64_MAGICVAL) if (offset == ZIP64_MAGICVAL)
writeLong(xentry.offset); writeLong(xentry.offset);
} }
if (!foundEXTT) {
writeShort(EXTID_EXTT);
writeShort(5);
writeByte(0x1); // flags byte
writeInt(javaToUnixTime(e.mtime));
}
if (e.extra != null) { if (e.extra != null) {
writeBytes(e.extra, 0, e.extra.length); writeBytes(e.extra, 0, e.extra.length);
} }
@ -588,6 +626,15 @@ class ZipOutputStream extends DeflaterOutputStream implements ZipConstants {
} }
} }
/*
* 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. * Writes a 16-bit short to the output stream in little-endian byte order.
*/ */

View File

@ -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);
}
}

View File

@ -1818,7 +1818,7 @@ public class ZipFileSystem extends FileSystem {
Entry(byte[] name) { Entry(byte[] name) {
name(name); name(name);
this.mtime = System.currentTimeMillis(); this.mtime = this.ctime = this.atime = System.currentTimeMillis();
this.crc = 0; this.crc = 0;
this.size = 0; this.size = 0;
this.csize = 0; this.csize = 0;
@ -1912,17 +1912,18 @@ public class ZipFileSystem extends FileSystem {
{ {
int written = CENHDR; int written = CENHDR;
int version0 = version(); int version0 = version();
long csize0 = csize; long csize0 = csize;
long size0 = size; long size0 = size;
long locoff0 = locoff; long locoff0 = locoff;
int elen64 = 0; // extra for ZIP64 int elen64 = 0; // extra for ZIP64
int elenNTFS = 0; // extra for NTFS (a/c/mtime) int elenNTFS = 0; // extra for NTFS (a/c/mtime)
int elenEXTT = 0; // extra for Extended Timestamp int elenEXTT = 0; // extra for Extended Timestamp
boolean foundExtraTime = false; // if time stamp NTFS, EXTT present
// confirm size/length // confirm size/length
int nlen = (name != null) ? name.length : 0; int nlen = (name != null) ? name.length : 0;
int elen = (extra != null) ? extra.length : 0; int elen = (extra != null) ? extra.length : 0;
int eoff = 0;
int clen = (comment != null) ? comment.length : 0; int clen = (comment != null) ? comment.length : 0;
if (csize >= ZIP64_MINVAL) { if (csize >= ZIP64_MINVAL) {
csize0 = ZIP64_MINVAL; csize0 = ZIP64_MINVAL;
@ -1936,14 +1937,24 @@ public class ZipFileSystem extends FileSystem {
locoff0 = ZIP64_MINVAL; locoff0 = ZIP64_MINVAL;
elen64 += 8; // offset(8) elen64 += 8; // offset(8)
} }
if (elen64 != 0) if (elen64 != 0) {
elen64 += 4; // header and data sz 4 bytes elen64 += 4; // header and data sz 4 bytes
}
if (atime != -1) { while (eoff + 4 < elen) {
if (isWindows) // use NTFS 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 elenNTFS = 36; // total 36 bytes
else // Extended Timestamp otherwise } else { // Extended Timestamp otherwise
elenEXTT = 9; // only mtime in cen elenEXTT = 9; // only mtime in cen
}
} }
writeInt(os, CENSIG); // CEN header signature writeInt(os, CENSIG); // CEN header signature
if (elen64 != 0) { if (elen64 != 0) {
@ -2092,11 +2103,13 @@ public class ZipFileSystem extends FileSystem {
{ {
writeInt(os, LOCSIG); // LOC header signature writeInt(os, LOCSIG); // LOC header signature
int version = version(); int version = version();
int nlen = (name != null) ? name.length : 0; int nlen = (name != null) ? name.length : 0;
int elen = (extra != null) ? extra.length : 0; int elen = (extra != null) ? extra.length : 0;
boolean foundExtraTime = false; // if extra timestamp present
int eoff = 0;
int elen64 = 0; int elen64 = 0;
int elenEXTT = 0; int elenEXTT = 0;
int elenNTFS = 0;
if ((flag & FLAG_DATADESCR) != 0) { if ((flag & FLAG_DATADESCR) != 0) {
writeShort(os, version()); // version needed to extract writeShort(os, version()); // version needed to extract
writeShort(os, flag); // general purpose bit flag writeShort(os, flag); // general purpose bit flag
@ -2128,14 +2141,27 @@ public class ZipFileSystem extends FileSystem {
writeInt(os, size); // uncompressed size writeInt(os, size); // uncompressed size
} }
} }
if (atime != -1 && !isWindows) { // on unix use "ext time" while (eoff + 4 < elen) {
if (ctime == -1) int tag = SH(extra, eoff);
elenEXTT = 13; int sz = SH(extra, eoff + 2);
else if (tag == EXTID_EXTT || tag == EXTID_NTFS) {
elenEXTT = 17; 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, name.length);
writeShort(os, elen + elen64 + elenEXTT); writeShort(os, elen + elen64 + elenNTFS + elenEXTT);
writeBytes(os, name); writeBytes(os, name);
if (elen64 != 0) { if (elen64 != 0) {
writeShort(os, EXTID_ZIP64); writeShort(os, EXTID_ZIP64);
@ -2143,15 +2169,28 @@ public class ZipFileSystem extends FileSystem {
writeLong(os, size); writeLong(os, size);
writeLong(os, csize); 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) { if (elenEXTT != 0) {
writeShort(os, EXTID_EXTT); writeShort(os, EXTID_EXTT);
writeShort(os, elenEXTT - 4);// size for the folowing data block writeShort(os, elenEXTT - 4);// size for the folowing data block
if (ctime == -1) int fbyte = 0x1;
os.write(0x3); // mtime and atime if (atime != -1) // mtime and atime
else fbyte |= 0x2;
os.write(0x7); // mtime, atime and ctime if (ctime != -1) // mtime, atime and ctime
fbyte |= 0x4;
os.write(fbyte); // flags byte
writeInt(os, javaToUnixTime(mtime)); writeInt(os, javaToUnixTime(mtime));
writeInt(os, javaToUnixTime(atime)); if (atime != -1)
writeInt(os, javaToUnixTime(atime));
if (ctime != -1) if (ctime != -1)
writeInt(os, javaToUnixTime(ctime)); writeInt(os, javaToUnixTime(ctime));
} }

View File

@ -214,7 +214,7 @@ public class ZipInfo {
winToJavaTime(LL(extra, off + 24))); winToJavaTime(LL(extra, off + 24)));
break; break;
case EXTID_EXTT: 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 ; pos = off + 1 ;
while (pos + 4 <= off + sz) { while (pos + 4 <= off + sz) {
print(" *%tc%n", print(" *%tc%n",
@ -223,6 +223,7 @@ public class ZipInfo {
} }
break; break;
default: default:
print(" ->[tag=%x, size=%d]%n", tag, sz);
} }
off += sz; off += sz;
} }

View File

@ -29,6 +29,7 @@ import java.nio.file.spi.*;
import java.nio.file.attribute.*; import java.nio.file.attribute.*;
import java.net.*; import java.net.*;
import java.util.*; import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.zip.*; import java.util.zip.*;
import static java.nio.file.StandardOpenOption.*; import static java.nio.file.StandardOpenOption.*;
@ -48,6 +49,7 @@ public class ZipFSTester {
test0(fs); test0(fs);
test1(fs); test1(fs);
test2(fs); // more tests test2(fs); // more tests
testTime(Paths.get(args[0]));
} }
} }
@ -337,6 +339,46 @@ public class ZipFSTester {
Files.delete(fs3Path); 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) private static FileSystem newZipFileSystem(Path path, Map<String, ?> env)
throws Exception throws Exception
{ {

View File

@ -22,7 +22,7 @@
# #
# @test # @test
# @bug 6990846 7009092 7009085 7015391 7014948 7005986 7017840 7007596 # @bug 6990846 7009092 7009085 7015391 7014948 7005986 7017840 7007596
# 7157656 8002390 # 7157656 8002390 7012868 7012856
# @summary Test ZipFileSystem demo # @summary Test ZipFileSystem demo
# @build Basic PathOps ZipFSTester # @build Basic PathOps ZipFSTester
# @run shell basic.sh # @run shell basic.sh

View File

@ -23,7 +23,7 @@
/** /**
* @test * @test
* @bug 6480504 * @bug 6480504 6303183
* @summary Test that client-provided data in the extra field is written and * @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 * read correctly, taking into account the JAR_MAGIC written into the extra
* field of the first entry of JAR files. * field of the first entry of JAR files.
@ -117,8 +117,7 @@ public class TestExtra {
ZipInputStream zis = getInputStream(); ZipInputStream zis = getInputStream();
ze = zis.getNextEntry(); ze = zis.getNextEntry();
byte[] e = ze.getExtra(); checkExtra(data, ze.getExtra());
check(e.length == 8, "expected extra length is 8, got " + e.length);
checkEntry(ze, 0, 0); checkEntry(ze, 0, 0);
} }
@ -140,10 +139,43 @@ public class TestExtra {
ZipInputStream zis = getInputStream(); ZipInputStream zis = getInputStream();
ze = zis.getNextEntry(); ze = zis.getNextEntry();
byte[] e = ze.getExtra(); byte[] e = ze.getExtra();
check(e.length == 8, "expected extra length is 8, got " + e.length); checkExtra(data, ze.getExtra());
checkEntry(ze, 0, 0); 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. */ /** Check that the entry's extra data is correct. */
void checkEntry(ZipEntry ze, int count, int dataLength) { void checkEntry(ZipEntry ze, int count, int dataLength) {

View File

@ -77,9 +77,9 @@ public class StoredCRC {
unexpected(t); 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. // determined to be in the entry's uncompressed data.
data[39] ^= 1; data[getDataOffset(data) + 4] ^= 1;
zis = new ZipInputStream( zis = new ZipInputStream(
new ByteArrayInputStream(data)); new ByteArrayInputStream(data));
@ -97,6 +97,15 @@ public class StoredCRC {
} }
} }
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 --------------------------- //--------------------- Infrastructure ---------------------------
static volatile int passed = 0, failed = 0; static volatile int passed = 0, failed = 0;
static boolean pass() {passed++; return true;} static boolean pass() {passed++; return true;}

View File

@ -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!");
}
}

View File

@ -22,7 +22,7 @@
*/ */
/* @test /* @test
* @bug 4770745 6234507 * @bug 4770745 6234507 6303183
* @summary test a variety of zip file entries * @summary test a variety of zip file entries
* @author Martin Buchholz * @author Martin Buchholz
*/ */
@ -54,6 +54,44 @@ public class Assortment {
check(condition, "Something's wrong"); 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 static class Entry {
private String name; private String name;
private int method; private int method;
@ -109,7 +147,7 @@ public class Assortment {
check((((comment == null) || comment.equals("")) check((((comment == null) || comment.equals(""))
&& (e.getComment() == null)) && (e.getComment() == null))
|| comment.equals(e.getComment())); || comment.equals(e.getComment()));
check(Arrays.equals(extra, e.getExtra())); check(equalsExtraData(extra, e.getExtra()));
check(Arrays.equals(data, getData(f, e))); check(Arrays.equals(data, getData(f, e)));
check(e.getSize() == data.length); check(e.getSize() == data.length);
check((method == ZipEntry.DEFLATED) || check((method == ZipEntry.DEFLATED) ||
@ -129,8 +167,7 @@ public class Assortment {
byte[] extra = (this.extra != null && this.extra.length == 0) ? byte[] extra = (this.extra != null && this.extra.length == 0) ?
null : this.extra; null : this.extra;
check(Arrays.equals(extra, e.getExtra())); check(equalsExtraData(extra, e.getExtra()));
check(name.equals(e.getName())); check(name.equals(e.getName()));
check(method == e.getMethod()); check(method == e.getMethod());
check(e.getSize() == -1 || e.getSize() == data.length); check(e.getSize() == -1 || e.getSize() == data.length);