# HG changeset patch # User cushon # Date 1502756371 25200 # Node ID fcef6541208475154445389c4fc918b75c77676a # Parent d222699a226e928b76d6ca4358b6c0cdb19e62dc 8184940: JDK 9 rejects zip files where the modified day or month is 0 Reviewed-by: martin diff -r d222699a226e -r fcef65412084 jdk/src/java.base/share/classes/java/util/zip/ZipUtils.java --- a/jdk/src/java.base/share/classes/java/util/zip/ZipUtils.java Mon Aug 14 13:57:15 2017 -0700 +++ b/jdk/src/java.base/share/classes/java/util/zip/ZipUtils.java Mon Aug 14 17:19:31 2017 -0700 @@ -81,13 +81,24 @@ * Converts DOS time to Java time (number of milliseconds since epoch). */ public static long dosToJavaTime(long dtime) { - LocalDateTime ldt = LocalDateTime.of( - (int) (((dtime >> 25) & 0x7f) + 1980), - (int) ((dtime >> 21) & 0x0f), - (int) ((dtime >> 16) & 0x1f), - (int) ((dtime >> 11) & 0x1f), - (int) ((dtime >> 5) & 0x3f), - (int) ((dtime << 1) & 0x3e)); + 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 >> 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); + } + LocalDateTime ldt = LocalDateTime.of(year, month, day, hour, minute, second); return TimeUnit.MILLISECONDS.convert(ldt.toEpochSecond( ZoneId.systemDefault().getRules().getOffset(ldt)), TimeUnit.SECONDS); } diff -r d222699a226e -r fcef65412084 jdk/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipUtils.java --- a/jdk/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipUtils.java Mon Aug 14 13:57:15 2017 -0700 +++ b/jdk/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipUtils.java Mon Aug 14 17:19:31 2017 -0700 @@ -106,13 +106,24 @@ * Converts DOS time to Java time (number of milliseconds since epoch). */ public static long dosToJavaTime(long dtime) { - LocalDateTime ldt = LocalDateTime.of( - (int) (((dtime >> 25) & 0x7f) + 1980), - (int) ((dtime >> 21) & 0x0f), - (int) ((dtime >> 16) & 0x1f), - (int) ((dtime >> 11) & 0x1f), - (int) ((dtime >> 5) & 0x3f), - (int) ((dtime << 1) & 0x3e)); + 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 >> 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); + } + LocalDateTime ldt = LocalDateTime.of(year, month, day, hour, minute, second); return TimeUnit.MILLISECONDS.convert(ldt.toEpochSecond( ZoneId.systemDefault().getRules().getOffset(ldt)), TimeUnit.SECONDS); } diff -r d222699a226e -r fcef65412084 jdk/test/java/util/zip/ZipFile/ZeroDate.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/test/java/util/zip/ZipFile/ZeroDate.java Mon Aug 14 17:19:31 2017 -0700 @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2017, 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. + */ + +import static java.util.zip.ZipFile.CENOFF; +import static java.util.zip.ZipFile.CENTIM; +import static java.util.zip.ZipFile.ENDHDR; +import static java.util.zip.ZipFile.ENDOFF; +import static java.util.zip.ZipFile.LOCTIM; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.Instant; +import java.time.LocalDate; +import java.time.ZoneId; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import java.util.zip.ZipOutputStream; + +/* @test + * @bug 8184940 + * @summary JDK 9 rejects zip files where the modified day or month is 0 + * @author Liam Miller-Cushon + */ +public class ZeroDate { + + public static void main(String[] args) throws Exception { + // create a zip file, and read it in as a byte array + Path path = Files.createTempFile("bad", ".zip"); + try (OutputStream os = Files.newOutputStream(path); + ZipOutputStream zos = new ZipOutputStream(os)) { + ZipEntry e = new ZipEntry("x"); + zos.putNextEntry(e); + zos.write((int) 'x'); + } + int len = (int) Files.size(path); + byte[] data = new byte[len]; + try (InputStream is = Files.newInputStream(path)) { + is.read(data); + } + Files.delete(path); + + // year, month, day are zero + testDate(data.clone(), 0, LocalDate.of(1979, 11, 30)); + // only year is zero + testDate(data.clone(), 0 << 25 | 4 << 21 | 5 << 16, LocalDate.of(1980, 4, 5)); + } + + private static void testDate(byte[] data, int date, LocalDate expected) throws IOException { + // set the datetime + int endpos = data.length - ENDHDR; + int cenpos = u16(data, endpos + ENDOFF); + int locpos = u16(data, cenpos + CENOFF); + writeU32(data, cenpos + CENTIM, date); + writeU32(data, locpos + LOCTIM, date); + + // ensure that the archive is still readable, and the date is 1979-11-30 + Path path = Files.createTempFile("out", ".zip"); + try (OutputStream os = Files.newOutputStream(path)) { + os.write(data); + } + 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(); + if (!actualInstant.equals(expectedInstant)) { + throw new AssertionError( + String.format("actual: %s, expected: %s", actualInstant, expectedInstant)); + } + } finally { + Files.delete(path); + } + } + + static int u8(byte[] data, int offset) { + return data[offset] & 0xff; + } + + static int u16(byte[] data, int offset) { + return u8(data, offset) + (u8(data, offset + 1) << 8); + } + + private static void writeU32(byte[] data, int pos, int value) { + data[pos] = (byte) (value & 0xff); + data[pos + 1] = (byte) ((value >> 8) & 0xff); + data[pos + 2] = (byte) ((value >> 16) & 0xff); + data[pos + 3] = (byte) ((value >> 24) & 0xff); + } +} diff -r d222699a226e -r fcef65412084 jdk/test/jdk/nio/zipfs/ZeroDate.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/test/jdk/nio/zipfs/ZeroDate.java Mon Aug 14 17:19:31 2017 -0700 @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2017, 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. + */ + +import static java.util.zip.ZipFile.CENOFF; +import static java.util.zip.ZipFile.CENTIM; +import static java.util.zip.ZipFile.ENDHDR; +import static java.util.zip.ZipFile.ENDOFF; +import static java.util.zip.ZipFile.LOCTIM; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URI; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.attribute.BasicFileAttributes; +import java.time.Instant; +import java.time.LocalDate; +import java.time.ZoneId; +import java.util.Collections; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +/* @test + * @bug 8184940 + * @summary JDK 9 rejects zip files where the modified day or month is 0 + * @author Liam Miller-Cushon + */ +public class ZeroDate { + + public static void main(String[] args) throws Exception { + // create a zip file, and read it in as a byte array + Path path = Files.createTempFile("bad", ".zip"); + try (OutputStream os = Files.newOutputStream(path); + ZipOutputStream zos = new ZipOutputStream(os)) { + ZipEntry e = new ZipEntry("x"); + zos.putNextEntry(e); + zos.write((int) 'x'); + } + int len = (int) Files.size(path); + byte[] data = new byte[len]; + try (InputStream is = Files.newInputStream(path)) { + is.read(data); + } + Files.delete(path); + + // year, month, day are zero + testDate(data.clone(), 0, LocalDate.of(1979, 11, 30)); + // only year is zero + testDate(data.clone(), 0 << 25 | 4 << 21 | 5 << 16, LocalDate.of(1980, 4, 5)); + } + + private static void testDate(byte[] data, int date, LocalDate expected) throws IOException { + // set the datetime + int endpos = data.length - ENDHDR; + int cenpos = u16(data, endpos + ENDOFF); + int locpos = u16(data, cenpos + CENOFF); + writeU32(data, cenpos + CENTIM, date); + writeU32(data, locpos + LOCTIM, date); + + // ensure that the archive is still readable, and the date is 1979-11-30 + Path path = Files.createTempFile("out", ".zip"); + try (OutputStream os = Files.newOutputStream(path)) { + os.write(data); + } + URI uri = URI.create("jar:file://" + path.toAbsolutePath()); + try (FileSystem fs = FileSystems.newFileSystem(uri, Collections.emptyMap())) { + Path entry = fs.getPath("x"); + Instant actualInstant = + Files.readAttributes(entry, BasicFileAttributes.class) + .lastModifiedTime() + .toInstant(); + Instant expectedInstant = + expected.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant(); + if (!actualInstant.equals(expectedInstant)) { + throw new AssertionError( + String.format("actual: %s, expected: %s", actualInstant, expectedInstant)); + } + } finally { + Files.delete(path); + } + } + + static int u8(byte[] data, int offset) { + return data[offset] & 0xff; + } + + static int u16(byte[] data, int offset) { + return u8(data, offset) + (u8(data, offset + 1) << 8); + } + + private static void writeU32(byte[] data, int pos, int value) { + data[pos] = (byte) (value & 0xff); + data[pos + 1] = (byte) ((value >> 8) & 0xff); + data[pos + 2] = (byte) ((value >> 16) & 0xff); + data[pos + 3] = (byte) ((value >> 24) & 0xff); + } +}