-
Bug
-
Resolution: Fixed
-
P3
-
9
-
b20
-
Verified
Issue | Fix Version | Assignee | Priority | Status | Resolution | Resolved In Build |
---|---|---|---|---|---|---|
JDK-8196526 | 9u-open | Liam Miller-Cushon | P3 | Resolved | Fixed | master |
Dates in zip files are encoded in MS-DOS format [1][2] where the year is relative to 1980 and the day and month both start at 1. In JDK 8 and earlier the zip implementations in java.util and zipfs supported reading dates where the year, day, and month were 0, interpreting them as 1979-11-30. Starting in JDK 9 zip archives containing 0 as the modified day or month are rejected.
The rejected dates are arguably invalid, but they are used by existing zip implementations: for example the go language's archive/zip package uses 0 as the date unless a date is set explicitly, and some build tools [3] zero out the date to make their output deterministic.
To ensure JDK 9 is able to read all existing and previously supported zip files it should continue to handle 0 dates in the same manner as JDK 8.
[1] https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT (see 4.4.6 date and time fields)
[2] https://docs.microsoft.com/en-us/cpp/c-runtime-library/32-bit-windows-time-date-formats
[3] https://github.com/google/protobuf/blob/50aa4febaffa6a6f168ec3f9afc6fbdd40e4d31f/src/google/protobuf/compiler/zip_writer.cc#L157
Proposed fix:
To support these dates the implementation of ZipUtils.dosToJavaTime (in both java.util and zipfs) could be changed to:
===
public static long dosToJavaTime(long dtime) {
int year;
int month;
int day;
int hour = (int) ((dtime >> 11) & 0x1f);
int minute = (int) ((dtime >> 5) & 0x3f);
int second = (int) ((dtime << 1) & 0x3e);
if ((dtime >> 25) == 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);
}
LocalDateTime ldt = LocalDateTime.of(year, month, day, hour, minute, second);
return TimeUnit.MILLISECONDS.convert(ldt.toEpochSecond(
ZoneId.systemDefault().getRules().getOffset(ldt)), TimeUnit.SECONDS);
}
===
Steps to reproduce:
1) Create a zip archive where the modified date is '0'. Note that both unzip and the JDK 8 jar tool accept the archive. (unzip reports the date as 1980-0-0, jar reports 1979-11-30)
$ go run zipwriter.go
$ unzip -l go.zip
Archive: go.zip
Length Date Time Name
--------- ---------- ----- ----
5 1980-00-00 00:00 hello
--------- -------
5 1 file
$ jar tvf go.zip
5 Fri Nov 30 00:00:00 PST 1979 hello
2) Dump the zip entry's modified date using the JDK 8 java.util.JarFile and zipfs:
$ java -fullversion
java full version "1.8.0_152-ea-b05"
$ java ZipfsJarTime go.zip/hello / creationTime : null
...
lastModifiedTime: Fri Nov 30 00:00:00 PST 1979
$ java JarTime go.zip
hello / 1979-11-30T08:00:00Z
3) Repeating (2) with JDK 9 fails:
$ java -fullversion
java full version "9+178"
$ java JarTime go.zip
Exception in thread "main" java.time.DateTimeException: Invalid value for MonthOfYear (valid values 1 - 12): 0
at java.base/java.time.temporal.ValueRange.checkValidValue(ValueRange.java:311)
at java.base/java.time.temporal.ChronoField.checkValidValue(ChronoField.java:714)
at java.base/java.time.LocalDate.of(LocalDate.java:269)
at java.base/java.time.LocalDateTime.of(LocalDateTime.java:336)
at java.base/java.util.zip.ZipUtils.dosToJavaTime(ZipUtils.java:82)
at java.base/java.util.zip.ZipUtils.extendedDosToJavaTime(ZipUtils.java:101)
at java.base/java.util.zip.ZipEntry.getTime(ZipEntry.java:198)
at java.base/java.util.zip.ZipEntry.getLastModifiedTime(ZipEntry.java:325)
at JarTime.main(JarTime.java:13)
$ java ZipfsJarTime go.zip
Exception in thread "main" java.time.DateTimeException: Invalid value for MonthOfYear (valid values 1 - 12): 0
at java.base/java.time.temporal.ValueRange.checkValidValue(ValueRange.java:311)
at java.base/java.time.temporal.ChronoField.checkValidValue(ChronoField.java:714)
at java.base/java.time.LocalDate.of(LocalDate.java:269)
at java.base/java.time.LocalDateTime.of(LocalDateTime.java:336)
at jdk.zipfs/jdk.nio.zipfs.ZipUtils.dosToJavaTime(ZipUtils.java:109)
at jdk.zipfs/jdk.nio.zipfs.ZipFileSystem$Entry.cen(ZipFileSystem.java:1950)
at jdk.zipfs/jdk.nio.zipfs.ZipFileSystem$Entry.readCEN(ZipFileSystem.java:1937)
at jdk.zipfs/jdk.nio.zipfs.ZipFileSystem.getEntry(ZipFileSystem.java:1324)
at jdk.zipfs/jdk.nio.zipfs.ZipFileSystem.getFileAttributes(ZipFileSystem.java:312)
at jdk.zipfs/jdk.nio.zipfs.ZipPath.getAttributes(ZipPath.java:721)
at jdk.zipfs/jdk.nio.zipfs.ZipFileSystemProvider.readAttributes(ZipFileSystemProvider.java:293)
at java.base/java.nio.file.Files.readAttributes(Files.java:1755)
at java.base/java.nio.file.FileTreeWalker.getAttributes(FileTreeWalker.java:219)
at java.base/java.nio.file.FileTreeWalker.visit(FileTreeWalker.java:276)
at java.base/java.nio.file.FileTreeWalker.next(FileTreeWalker.java:373)
at java.base/java.nio.file.Files.walkFileTree(Files.java:2749)
at java.base/java.nio.file.Files.walkFileTree(Files.java:2785)
at ZipfsJarTime.main(ZipfsJarTime.java:12)
The rejected dates are arguably invalid, but they are used by existing zip implementations: for example the go language's archive/zip package uses 0 as the date unless a date is set explicitly, and some build tools [3] zero out the date to make their output deterministic.
To ensure JDK 9 is able to read all existing and previously supported zip files it should continue to handle 0 dates in the same manner as JDK 8.
[1] https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT (see 4.4.6 date and time fields)
[2] https://docs.microsoft.com/en-us/cpp/c-runtime-library/32-bit-windows-time-date-formats
[3] https://github.com/google/protobuf/blob/50aa4febaffa6a6f168ec3f9afc6fbdd40e4d31f/src/google/protobuf/compiler/zip_writer.cc#L157
Proposed fix:
To support these dates the implementation of ZipUtils.dosToJavaTime (in both java.util and zipfs) could be changed to:
===
public static long dosToJavaTime(long dtime) {
int year;
int month;
int day;
int hour = (int) ((dtime >> 11) & 0x1f);
int minute = (int) ((dtime >> 5) & 0x3f);
int second = (int) ((dtime << 1) & 0x3e);
if ((dtime >> 25) == 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);
}
LocalDateTime ldt = LocalDateTime.of(year, month, day, hour, minute, second);
return TimeUnit.MILLISECONDS.convert(ldt.toEpochSecond(
ZoneId.systemDefault().getRules().getOffset(ldt)), TimeUnit.SECONDS);
}
===
Steps to reproduce:
1) Create a zip archive where the modified date is '0'. Note that both unzip and the JDK 8 jar tool accept the archive. (unzip reports the date as 1980-0-0, jar reports 1979-11-30)
$ go run zipwriter.go
$ unzip -l go.zip
Archive: go.zip
Length Date Time Name
--------- ---------- ----- ----
5 1980-00-00 00:00 hello
--------- -------
5 1 file
$ jar tvf go.zip
5 Fri Nov 30 00:00:00 PST 1979 hello
2) Dump the zip entry's modified date using the JDK 8 java.util.JarFile and zipfs:
$ java -fullversion
java full version "1.8.0_152-ea-b05"
$ java ZipfsJarTime go.zip/hello / creationTime : null
...
lastModifiedTime: Fri Nov 30 00:00:00 PST 1979
$ java JarTime go.zip
hello / 1979-11-30T08:00:00Z
3) Repeating (2) with JDK 9 fails:
$ java -fullversion
java full version "9+178"
$ java JarTime go.zip
Exception in thread "main" java.time.DateTimeException: Invalid value for MonthOfYear (valid values 1 - 12): 0
at java.base/java.time.temporal.ValueRange.checkValidValue(ValueRange.java:311)
at java.base/java.time.temporal.ChronoField.checkValidValue(ChronoField.java:714)
at java.base/java.time.LocalDate.of(LocalDate.java:269)
at java.base/java.time.LocalDateTime.of(LocalDateTime.java:336)
at java.base/java.util.zip.ZipUtils.dosToJavaTime(ZipUtils.java:82)
at java.base/java.util.zip.ZipUtils.extendedDosToJavaTime(ZipUtils.java:101)
at java.base/java.util.zip.ZipEntry.getTime(ZipEntry.java:198)
at java.base/java.util.zip.ZipEntry.getLastModifiedTime(ZipEntry.java:325)
at JarTime.main(JarTime.java:13)
$ java ZipfsJarTime go.zip
Exception in thread "main" java.time.DateTimeException: Invalid value for MonthOfYear (valid values 1 - 12): 0
at java.base/java.time.temporal.ValueRange.checkValidValue(ValueRange.java:311)
at java.base/java.time.temporal.ChronoField.checkValidValue(ChronoField.java:714)
at java.base/java.time.LocalDate.of(LocalDate.java:269)
at java.base/java.time.LocalDateTime.of(LocalDateTime.java:336)
at jdk.zipfs/jdk.nio.zipfs.ZipUtils.dosToJavaTime(ZipUtils.java:109)
at jdk.zipfs/jdk.nio.zipfs.ZipFileSystem$Entry.cen(ZipFileSystem.java:1950)
at jdk.zipfs/jdk.nio.zipfs.ZipFileSystem$Entry.readCEN(ZipFileSystem.java:1937)
at jdk.zipfs/jdk.nio.zipfs.ZipFileSystem.getEntry(ZipFileSystem.java:1324)
at jdk.zipfs/jdk.nio.zipfs.ZipFileSystem.getFileAttributes(ZipFileSystem.java:312)
at jdk.zipfs/jdk.nio.zipfs.ZipPath.getAttributes(ZipPath.java:721)
at jdk.zipfs/jdk.nio.zipfs.ZipFileSystemProvider.readAttributes(ZipFileSystemProvider.java:293)
at java.base/java.nio.file.Files.readAttributes(Files.java:1755)
at java.base/java.nio.file.FileTreeWalker.getAttributes(FileTreeWalker.java:219)
at java.base/java.nio.file.FileTreeWalker.visit(FileTreeWalker.java:276)
at java.base/java.nio.file.FileTreeWalker.next(FileTreeWalker.java:373)
at java.base/java.nio.file.Files.walkFileTree(Files.java:2749)
at java.base/java.nio.file.Files.walkFileTree(Files.java:2785)
at ZipfsJarTime.main(ZipfsJarTime.java:12)
- backported by
-
JDK-8196526 JDK 9 rejects zip files where the modified day or month is 0
-
- Resolved
-
- duplicates
-
JDK-8194730 JDK 9 cannot load jar files where the modified day or month is 0
-
- Closed
-
-
JDK-8195101 Jar update file fails due to DateTimeException
-
- Closed
-
- relates to
-
JDK-8186227 jdk/nio/zipfs/ZeroDate.java fails on Windows with "IllegalArgumentException: Illegal character in opaque part at index 13"
-
- Closed
-