8188869: jdk9/10 reject zip/jar files where seconds value of timestamp is out of supported range 0 - 59
authorredestad
Thu, 12 Oct 2017 16:00:29 +0200
changeset 47331 39d1de71faca
parent 47330 eb010905ccb7
child 47332 b87d7b5d5ded
8188869: jdk9/10 reject zip/jar files where seconds value of timestamp is out of supported range 0 - 59 Reviewed-by: sherman, alanb
src/java.base/share/classes/java/util/zip/ZipUtils.java
src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipUtils.java
test/jdk/java/util/zip/ZipFile/ZeroDate.java
test/jdk/jdk/nio/zipfs/ZeroDate.java
--- a/src/java.base/share/classes/java/util/zip/ZipUtils.java	Thu Oct 12 13:35:41 2017 +0200
+++ b/src/java.base/share/classes/java/util/zip/ZipUtils.java	Thu Oct 12 16:00:29 2017 +0200
@@ -28,9 +28,11 @@
 import java.nio.file.attribute.FileTime;
 import java.security.AccessController;
 import java.security.PrivilegedAction;
+import java.time.DateTimeException;
 import java.time.Instant;
 import java.time.LocalDateTime;
 import java.time.ZoneId;
+import java.util.Date;
 import java.util.concurrent.TimeUnit;
 
 import static java.util.zip.ZipConstants.ENDHDR;
@@ -78,31 +80,39 @@
     }
 
     /**
+     /*
      * Converts DOS time to Java time (number of milliseconds since epoch).
      */
     public static long dosToJavaTime(long dtime) {
-        int year;
-        int month;
-        int day;
+        int year = (int) (((dtime >> 25) & 0x7f) + 1980);
+        int month = (int) ((dtime >> 21) & 0x0f);
+        int day = (int) ((dtime >> 16) & 0x1f);
         int hour = (int) ((dtime >> 11) & 0x1f);
         int minute = (int) ((dtime >> 5) & 0x3f);
         int second = (int) ((dtime << 1) & 0x3e);
-        if ((dtime >> 16) == 0) {
-            // Interpret the 0 DOS date as 1979-11-30 for compatibility with
-            // other implementations.
-            year = 1979;
-            month = 11;
-            day = 30;
-        } else {
-            year = (int) (((dtime >> 25) & 0x7f) + 1980);
-            month = (int) ((dtime >> 21) & 0x0f);
-            day = (int) ((dtime >> 16) & 0x1f);
+
+        if (month > 0 && month < 13 && day > 0 && hour < 24 && minute < 60 && second < 60) {
+            try {
+                LocalDateTime ldt = LocalDateTime.of(year, month, day, hour, minute, second);
+                return TimeUnit.MILLISECONDS.convert(ldt.toEpochSecond(
+                        ZoneId.systemDefault().getRules().getOffset(ldt)), TimeUnit.SECONDS);
+            } catch (DateTimeException dte) {
+                // ignore
+            }
         }
-        LocalDateTime ldt = LocalDateTime.of(year, month, day, hour, minute, second);
-        return TimeUnit.MILLISECONDS.convert(ldt.toEpochSecond(
-                ZoneId.systemDefault().getRules().getOffset(ldt)), TimeUnit.SECONDS);
+        return overflowDosToJavaTime(year, month, day, hour, minute, second);
     }
 
+    /*
+     * Deal with corner cases where an arguably mal-formed DOS time is used
+     */
+    @SuppressWarnings("deprecation") // Use of Date constructor
+    private static long overflowDosToJavaTime(int year, int month, int day,
+                                              int hour, int minute, int second) {
+        return new Date(year - 1900, month - 1, day, hour, minute, second).getTime();
+    }
+
+
     /**
      * Converts extended DOS time to Java time, where up to 1999 milliseconds
      * might be encoded into the upper half of the returned long.
--- a/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipUtils.java	Thu Oct 12 13:35:41 2017 +0200
+++ b/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipUtils.java	Thu Oct 12 16:00:29 2017 +0200
@@ -27,10 +27,12 @@
 
 import java.io.IOException;
 import java.io.OutputStream;
+import java.time.DateTimeException;
 import java.time.Instant;
 import java.time.LocalDateTime;
 import java.time.ZoneId;
 import java.util.Arrays;
+import java.util.Date;
 import java.util.regex.PatternSyntaxException;
 import java.util.concurrent.TimeUnit;
 
@@ -106,26 +108,32 @@
      * Converts DOS time to Java time (number of milliseconds since epoch).
      */
     public static long dosToJavaTime(long dtime) {
-        int year;
-        int month;
-        int day;
+        int year = (int) (((dtime >> 25) & 0x7f) + 1980);
+        int month = (int) ((dtime >> 21) & 0x0f);
+        int day = (int) ((dtime >> 16) & 0x1f);
         int hour = (int) ((dtime >> 11) & 0x1f);
         int minute = (int) ((dtime >> 5) & 0x3f);
         int second = (int) ((dtime << 1) & 0x3e);
-        if ((dtime >> 16) == 0) {
-            // Interpret the 0 DOS date as 1979-11-30 for compatibility with
-            // other implementations.
-            year = 1979;
-            month = 11;
-            day = 30;
-        } else {
-            year = (int) (((dtime >> 25) & 0x7f) + 1980);
-            month = (int) ((dtime >> 21) & 0x0f);
-            day = (int) ((dtime >> 16) & 0x1f);
+
+        if (month > 0 && month < 13 && day > 0 && hour < 24 && minute < 60 && second < 60) {
+            try {
+                LocalDateTime ldt = LocalDateTime.of(year, month, day, hour, minute, second);
+                return TimeUnit.MILLISECONDS.convert(ldt.toEpochSecond(
+                        ZoneId.systemDefault().getRules().getOffset(ldt)), TimeUnit.SECONDS);
+            } catch (DateTimeException dte) {
+                // ignore
+            }
         }
-        LocalDateTime ldt = LocalDateTime.of(year, month, day, hour, minute, second);
-        return TimeUnit.MILLISECONDS.convert(ldt.toEpochSecond(
-                ZoneId.systemDefault().getRules().getOffset(ldt)), TimeUnit.SECONDS);
+        return overflowDosToJavaTime(year, month, day, hour, minute, second);
+    }
+
+    /*
+     * Deal with corner cases where an arguably mal-formed DOS time is used
+     */
+    @SuppressWarnings("deprecation") // Use of Date constructor
+    private static long overflowDosToJavaTime(int year, int month, int day,
+                                              int hour, int minute, int second) {
+        return new Date(year - 1900, month - 1, day, hour, minute, second).getTime();
     }
 
     /*
--- a/test/jdk/java/util/zip/ZipFile/ZeroDate.java	Thu Oct 12 13:35:41 2017 +0200
+++ b/test/jdk/java/util/zip/ZipFile/ZeroDate.java	Thu Oct 12 16:00:29 2017 +0200
@@ -34,14 +34,16 @@
 import java.nio.file.Path;
 import java.time.Instant;
 import java.time.LocalDate;
+import java.time.LocalDateTime;
 import java.time.ZoneId;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipFile;
 import java.util.zip.ZipOutputStream;
 
 /* @test
- * @bug 8184940
+ * @bug 8184940 8188869
  * @summary JDK 9 rejects zip files where the modified day or month is 0
+ *          or otherwise represent an invalid date, such as 1980-02-30 24:60:60
  * @author Liam Miller-Cushon
  */
 public class ZeroDate {
@@ -63,12 +65,19 @@
         Files.delete(path);
 
         // year, month, day are zero
-        testDate(data.clone(), 0, LocalDate.of(1979, 11, 30));
+        testDate(data.clone(), 0, LocalDate.of(1979, 11, 30).atStartOfDay());
         // only year is zero
-        testDate(data.clone(), 0 << 25 | 4 << 21 | 5 << 16, LocalDate.of(1980, 4, 5));
+        testDate(data.clone(), 0 << 25 | 4 << 21 | 5 << 16, LocalDate.of(1980, 4, 5).atStartOfDay());
+        // month is greater than 12
+        testDate(data.clone(), 0 << 25 | 13 << 21 | 1 << 16, LocalDate.of(1981, 1, 1).atStartOfDay());
+        // 30th of February
+        testDate(data.clone(), 0 << 25 | 2 << 21 | 30 << 16, LocalDate.of(1980, 3, 1).atStartOfDay());
+        // 30th of February, 24:60:60
+        testDate(data.clone(), 0 << 25 | 2 << 21 | 30 << 16 | 24 << 11 | 60 << 5 | 60 >> 1,
+                LocalDateTime.of(1980, 3, 2, 1, 1, 0));
     }
 
-    private static void testDate(byte[] data, int date, LocalDate expected) throws IOException {
+    private static void testDate(byte[] data, int date, LocalDateTime expected) throws IOException {
         // set the datetime
         int endpos = data.length - ENDHDR;
         int cenpos = u16(data, endpos + ENDOFF);
@@ -84,8 +93,7 @@
         try (ZipFile zf = new ZipFile(path.toFile())) {
             ZipEntry ze = zf.entries().nextElement();
             Instant actualInstant = ze.getLastModifiedTime().toInstant();
-            Instant expectedInstant =
-                    expected.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant();
+            Instant expectedInstant = expected.atZone(ZoneId.systemDefault()).toInstant();
             if (!actualInstant.equals(expectedInstant)) {
                 throw new AssertionError(
                         String.format("actual: %s, expected: %s", actualInstant, expectedInstant));
--- a/test/jdk/jdk/nio/zipfs/ZeroDate.java	Thu Oct 12 13:35:41 2017 +0200
+++ b/test/jdk/jdk/nio/zipfs/ZeroDate.java	Thu Oct 12 16:00:29 2017 +0200
@@ -38,14 +38,16 @@
 import java.nio.file.attribute.BasicFileAttributes;
 import java.time.Instant;
 import java.time.LocalDate;
+import java.time.LocalDateTime;
 import java.time.ZoneId;
 import java.util.Collections;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipOutputStream;
 
 /* @test
- * @bug 8184940 8186227
+ * @bug 8184940 8186227 8188869
  * @summary JDK 9 rejects zip files where the modified day or month is 0
+ *          or otherwise represent an invalid date, such as 1980-02-30 24:60:60
  * @author Liam Miller-Cushon
  */
 public class ZeroDate {
@@ -67,12 +69,19 @@
         Files.delete(path);
 
         // year, month, day are zero
-        testDate(data.clone(), 0, LocalDate.of(1979, 11, 30));
+        testDate(data.clone(), 0, LocalDate.of(1979, 11, 30).atStartOfDay());
         // only year is zero
-        testDate(data.clone(), 0 << 25 | 4 << 21 | 5 << 16, LocalDate.of(1980, 4, 5));
+        testDate(data.clone(), 0 << 25 | 4 << 21 | 5 << 16, LocalDate.of(1980, 4, 5).atStartOfDay());
+        // month is greater than 12
+        testDate(data.clone(), 0 << 25 | 13 << 21 | 1 << 16, LocalDate.of(1981, 1, 1).atStartOfDay());
+        // 30th of February
+        testDate(data.clone(), 0 << 25 | 2 << 21 | 30 << 16, LocalDate.of(1980, 3, 1).atStartOfDay());
+        // 30th of February, 24:60:60
+        testDate(data.clone(), 0 << 25 | 2 << 21 | 30 << 16 | 24 << 11 | 60 << 5 | 60 >> 1,
+                LocalDateTime.of(1980, 3, 2, 1, 1, 0));
     }
 
-    private static void testDate(byte[] data, int date, LocalDate expected) throws IOException {
+    private static void testDate(byte[] data, int date, LocalDateTime expected) throws IOException {
         // set the datetime
         int endpos = data.length - ENDHDR;
         int cenpos = u16(data, endpos + ENDOFF);
@@ -93,7 +102,7 @@
                             .lastModifiedTime()
                             .toInstant();
             Instant expectedInstant =
-                    expected.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant();
+                    expected.atZone(ZoneId.systemDefault()).toInstant();
             if (!actualInstant.equals(expectedInstant)) {
                 throw new AssertionError(
                         String.format("actual: %s, expected: %s", actualInstant, expectedInstant));