# HG changeset patch # User redestad # Date 1425081971 -3600 # Sat Feb 28 01:06:11 2015 +0100 # Node ID 697365c035ee6a8795dbde8a4ec415b2b4f8847a # Parent effdf04cfcecd47f3155299d6140fd851ef7d873 8073497: Lazy conversion of ZipEntry time Reviewed-by: sherman, plevart diff --git a/src/java.base/share/classes/java/util/zip/ZipEntry.java b/src/java.base/share/classes/java/util/zip/ZipEntry.java --- a/src/java.base/share/classes/java/util/zip/ZipEntry.java +++ b/src/java.base/share/classes/java/util/zip/ZipEntry.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1995, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1995, 2015, 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 @@ -41,7 +41,9 @@ class ZipEntry implements ZipConstants, Cloneable { String name; // entry name - long time = -1; // last modification time + long xdostime = -1; // last modification time (in extended DOS time, + // where milliseconds lost in conversion might + // be encoded into the upper half) FileTime mtime; // last modification time, from extra field data FileTime atime; // last access time, from extra field data FileTime ctime; // creation time, from extra field data @@ -64,6 +66,28 @@ public static final int DEFLATED = 8; /** + * DOS time constant for representing timestamps before 1980. + */ + static final long DOSTIME_BEFORE_1980 = (1 << 21) | (1 << 16); + + /** + * Approximately 128 years, in milliseconds (ignoring leap years etc). + * + * This establish an approximate high-bound value for DOS times in + * milliseconds since epoch, used to enable an efficient but + * sufficient bounds check to avoid generating extended last modified + * time entries. + * + * Calculating the exact number is locale dependent, would require loading + * TimeZone data eagerly, and would make little practical sense. Since DOS + * times theoretically go to 2107 - with compatibility not guaranteed + * after 2099 - setting this to a time that is before but near 2099 + * should be sufficient. + */ + private static final long UPPER_DOSTIME_BOUND = + 128L * 365 * 24 * 60 * 60 * 1000; + + /** * Creates a new zip entry with the specified name. * * @param name @@ -93,7 +117,7 @@ public ZipEntry(ZipEntry e) { Objects.requireNonNull(e, "entry"); name = e.name; - time = e.time; + xdostime = e.xdostime; mtime = e.mtime; atime = e.atime; ctime = e.ctime; @@ -137,8 +161,14 @@ * @see #getLastModifiedTime() */ public void setTime(long time) { - this.time = time; - this.mtime = null; + this.xdostime = javaToExtendedDosTime(time); + // Avoid setting the mtime field if time is in the valid + // range for a DOS time + if (xdostime != DOSTIME_BEFORE_1980 && time <= UPPER_DOSTIME_BOUND) { + this.mtime = null; + } else { + this.mtime = FileTime.from(time, TimeUnit.MILLISECONDS); + } } /** @@ -158,7 +188,10 @@ * @see #setLastModifiedTime(FileTime) */ public long getTime() { - return time; + if (mtime != null) { + return mtime.toMillis(); + } + return (xdostime != -1) ? extendedDosToJavaTime(xdostime) : -1; } /** @@ -181,7 +214,7 @@ */ public ZipEntry setLastModifiedTime(FileTime time) { this.mtime = Objects.requireNonNull(time, "lastModifiedTime"); - this.time = time.to(TimeUnit.MILLISECONDS); + this.xdostime = javaToExtendedDosTime(time.to(TimeUnit.MILLISECONDS)); return this; } @@ -204,9 +237,9 @@ public FileTime getLastModifiedTime() { if (mtime != null) return mtime; - if (time == -1) + if (xdostime == -1) return null; - return FileTime.from(time, TimeUnit.MILLISECONDS); + return FileTime.from(getTime(), TimeUnit.MILLISECONDS); } /** diff --git a/src/java.base/share/classes/java/util/zip/ZipFile.java b/src/java.base/share/classes/java/util/zip/ZipFile.java --- a/src/java.base/share/classes/java/util/zip/ZipFile.java +++ b/src/java.base/share/classes/java/util/zip/ZipFile.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1995, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1995, 2015, 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 @@ -46,7 +46,6 @@ 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. @@ -567,7 +566,7 @@ e.name = zc.toString(bname, bname.length); } } - e.time = dosToJavaTime(getEntryTime(jzentry)); + e.xdostime = getEntryTime(jzentry); e.crc = getEntryCrc(jzentry); e.size = getEntrySize(jzentry); e.csize = getEntryCSize(jzentry); diff --git a/src/java.base/share/classes/java/util/zip/ZipInputStream.java b/src/java.base/share/classes/java/util/zip/ZipInputStream.java --- a/src/java.base/share/classes/java/util/zip/ZipInputStream.java +++ b/src/java.base/share/classes/java/util/zip/ZipInputStream.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2015, 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 @@ -303,7 +303,7 @@ throw new ZipException("encrypted ZIP entry not supported"); } e.method = get16(tmpbuf, LOCHOW); - e.time = dosToJavaTime(get32(tmpbuf, LOCTIM)); + e.xdostime = get32(tmpbuf, LOCTIM); if ((flag & 8) == 8) { /* "Data Descriptor" present */ if (e.method != DEFLATED) { diff --git a/src/java.base/share/classes/java/util/zip/ZipOutputStream.java b/src/java.base/share/classes/java/util/zip/ZipOutputStream.java --- a/src/java.base/share/classes/java/util/zip/ZipOutputStream.java +++ b/src/java.base/share/classes/java/util/zip/ZipOutputStream.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2015, 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 @@ -61,7 +61,6 @@ private static class XEntry { 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; @@ -192,7 +191,7 @@ if (current != null) { closeEntry(); // close previous entry } - if (e.time == -1) { + if (e.xdostime == -1) { // by default, do NOT use extended timestamps in extra // data, for now. e.setTime(System.currentTimeMillis()); @@ -389,18 +388,12 @@ boolean hasZip64 = false; 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(xentry.dostime); // last modification time + writeInt(e.xdostime); // last modification time // store size, uncompressed size, and crc-32 in data descriptor // immediately following compressed entry data writeInt(0); @@ -415,7 +408,7 @@ } writeShort(flag); // general purpose bit flag writeShort(e.method); // compression method - writeInt(xentry.dostime); // last modification time + writeInt(e.xdostime); // last modification time writeInt(e.crc); // crc-32 if (hasZip64) { writeInt(ZIP64_MAGICVAL); @@ -522,9 +515,7 @@ } writeShort(flag); // general purpose bit flag writeShort(e.method); // compression method - // use the copy in xentry, which has been converted - // from e.time in writeLOC() - writeInt(xentry.dostime); // last modification time + writeInt(e.xdostime); // last modification time writeInt(e.crc); // crc-32 writeInt(csize); // compressed size writeInt(size); // uncompressed size diff --git a/src/java.base/share/classes/java/util/zip/ZipUtils.java b/src/java.base/share/classes/java/util/zip/ZipUtils.java --- a/src/java.base/share/classes/java/util/zip/ZipUtils.java +++ b/src/java.base/share/classes/java/util/zip/ZipUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2015, 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 @@ -29,9 +29,6 @@ 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 @@ -69,7 +66,7 @@ /** * Converts DOS time to Java time (number of milliseconds since epoch). */ - public static long dosToJavaTime(long dtime) { + 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), @@ -81,14 +78,26 @@ } /** + * Converts extended DOS time to Java time, where up to 1999 milliseconds + * might be encoded into the upper half of the returned long. + * + * @param xdostime the extended DOS time value + * @return milliseconds since epoch + */ + public static long extendedDosToJavaTime(long xdostime) { + long time = dosToJavaTime(xdostime); + return time + (xdostime >> 32); + } + + /** * Converts Java time to DOS time. */ @SuppressWarnings("deprecation") // Use of date methods - public static long javaToDosTime(long time) { + 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 ZipEntry.DOSTIME_BEFORE_1980; } return (year - 1980) << 25 | (d.getMonth() + 1) << 21 | d.getDate() << 16 | d.getHours() << 11 | d.getMinutes() << 5 | @@ -96,6 +105,23 @@ } /** + * Converts Java time to DOS time, encoding any milliseconds lost + * in the conversion into the upper half of the returned long. + * + * @param time milliseconds since epoch + * @return DOS time with 2s remainder encoded into upper half + */ + public static long javaToExtendedDosTime(long time) { + if (time < 0) { + return ZipEntry.DOSTIME_BEFORE_1980; + } + long dostime = javaToDosTime(time); + return (dostime != ZipEntry.DOSTIME_BEFORE_1980) + ? dostime + ((time % 2000) << 32) + : ZipEntry.DOSTIME_BEFORE_1980; + } + + /** * Fetches unsigned 16-bit value from byte array at specified offset. * The bytes are assumed to be in Intel (little-endian) byte order. */ diff --git a/test/java/util/zip/TestExtraTime.java b/test/java/util/zip/TestExtraTime.java --- a/test/java/util/zip/TestExtraTime.java +++ b/test/java/util/zip/TestExtraTime.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2015, 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 @@ -71,6 +71,7 @@ } testNullHandling(); + testTimeConversions(); } static void test(FileTime mtime, FileTime atime, FileTime ctime, @@ -178,4 +179,33 @@ // pass } } + + // verify that setting and getting any time is possible as per the intent + // of 4759491 + static void testTimeConversions() { + // Sample across the entire range + long step = Long.MAX_VALUE / 100L; + testTimeConversions(Long.MIN_VALUE, Long.MAX_VALUE - step, step); + + // Samples through the near future + long currentTime = System.currentTimeMillis(); + testTimeConversions(currentTime, currentTime + 1_000_000, 10_000); + } + + static void testTimeConversions(long from, long to, long step) { + ZipEntry ze = new ZipEntry("TestExtraTime.java"); + for (long time = from; time <= to; time += step) { + ze.setTime(time); + FileTime lastModifiedTime = ze.getLastModifiedTime(); + if (lastModifiedTime.toMillis() != time) { + throw new RuntimeException("setTime should make getLastModifiedTime " + + "return the specified instant: " + time + + " got: " + lastModifiedTime.toMillis()); + } + if (ze.getTime() != time) { + throw new RuntimeException("getTime after setTime, expected: " + + time + " got: " + ze.getTime()); + } + } + } }