From 02b9452ed39eccdfe3210e65b17d4759333c0f15 Mon Sep 17 00:00:00 2001 From: Xueming Shen <sherman@openjdk.org> Date: Wed, 20 Sep 2017 16:41:54 -0700 Subject: [PATCH] 8186464: ZipFile cannot read some InfoZip ZIP64 zip files Reviewed-by: martin --- .../share/classes/java/util/zip/ZipFile.java | 54 ++++++++------- .../classes/jdk/nio/zipfs/ZipFileSystem.java | 66 +++++++++++-------- test/jdk/java/util/zip/ZipFile/ReadZip.java | 61 ++++++++++++++++- 3 files changed, 126 insertions(+), 55 deletions(-) diff --git a/src/java.base/share/classes/java/util/zip/ZipFile.java b/src/java.base/share/classes/java/util/zip/ZipFile.java index 74264ed21a4..ae7c704f6ce 100644 --- a/src/java.base/share/classes/java/util/zip/ZipFile.java +++ b/src/java.base/share/classes/java/util/zip/ZipFile.java @@ -1122,30 +1122,36 @@ class ZipFile implements ZipConstants, Closeable { zerror("zip comment read failed"); } } - if (end.cenlen == ZIP64_MAGICVAL || - end.cenoff == ZIP64_MAGICVAL || - end.centot == ZIP64_MAGICCOUNT) - { - // need to find the zip64 end; - try { - byte[] loc64 = new byte[ZIP64_LOCHDR]; - if (readFullyAt(loc64, 0, loc64.length, end.endpos - ZIP64_LOCHDR) - != loc64.length || GETSIG(loc64) != ZIP64_LOCSIG) { - return end; - } - long end64pos = ZIP64_LOCOFF(loc64); - byte[] end64buf = new byte[ZIP64_ENDHDR]; - if (readFullyAt(end64buf, 0, end64buf.length, end64pos) - != end64buf.length || GETSIG(end64buf) != ZIP64_ENDSIG) { - return end; - } - // end64 found, re-calcualte everything. - end.cenlen = ZIP64_ENDSIZ(end64buf); - end.cenoff = ZIP64_ENDOFF(end64buf); - end.centot = (int)ZIP64_ENDTOT(end64buf); // assume total < 2g - end.endpos = end64pos; - } catch (IOException x) {} // no zip64 loc/end - } + // must check for a zip64 end record; it is always permitted to be present + try { + byte[] loc64 = new byte[ZIP64_LOCHDR]; + if (end.endpos < ZIP64_LOCHDR || + readFullyAt(loc64, 0, loc64.length, end.endpos - ZIP64_LOCHDR) + != loc64.length || GETSIG(loc64) != ZIP64_LOCSIG) { + return end; + } + long end64pos = ZIP64_LOCOFF(loc64); + byte[] end64buf = new byte[ZIP64_ENDHDR]; + if (readFullyAt(end64buf, 0, end64buf.length, end64pos) + != end64buf.length || GETSIG(end64buf) != ZIP64_ENDSIG) { + return end; + } + // end64 candidate found, + long cenlen64 = ZIP64_ENDSIZ(end64buf); + long cenoff64 = ZIP64_ENDOFF(end64buf); + long centot64 = ZIP64_ENDTOT(end64buf); + // double-check + if (cenlen64 != end.cenlen && end.cenlen != ZIP64_MAGICVAL || + cenoff64 != end.cenoff && end.cenoff != ZIP64_MAGICVAL || + centot64 != end.centot && end.centot != ZIP64_MAGICCOUNT) { + return end; + } + // to use the end64 values + end.cenlen = cenlen64; + end.cenoff = cenoff64; + end.centot = (int)centot64; // assume total < 2g + end.endpos = end64pos; + } catch (IOException x) {} // no zip64 loc/end return end; } } diff --git a/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileSystem.java b/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileSystem.java index 2b9891b8e15..8f88d98bc1f 100644 --- a/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileSystem.java +++ b/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileSystem.java @@ -79,6 +79,7 @@ class ZipFileSystem extends FileSystem { private static final boolean isWindows = AccessController.doPrivileged( (PrivilegedAction<Boolean>) () -> System.getProperty("os.name") .startsWith("Windows")); + private final boolean forceEnd64; ZipFileSystem(ZipFileSystemProvider provider, Path zfpath, @@ -91,12 +92,13 @@ class ZipFileSystem extends FileSystem { (String)env.get("encoding") : "UTF-8"; this.noExtt = "false".equals(env.get("zipinfo-time")); this.useTempFile = TRUE.equals(env.get("useTempFile")); + this.forceEnd64 = "true".equals(env.get("forceZIP64End")); this.provider = provider; this.zfpath = zfpath; if (Files.notExists(zfpath)) { if (createNew) { try (OutputStream os = Files.newOutputStream(zfpath, CREATE_NEW, WRITE)) { - new END().write(os, 0); + new END().write(os, 0, forceEnd64); } } else { throw new FileSystemNotFoundException(zfpath.toString()); @@ -1000,28 +1002,36 @@ class ZipFileSystem extends FileSystem { end.cenoff = ENDOFF(buf); end.comlen = ENDCOM(buf); end.endpos = pos + i; - if (end.cenlen == ZIP64_MINVAL || - end.cenoff == ZIP64_MINVAL || - end.centot == ZIP64_MINVAL32) - { - // need to find the zip64 end; - byte[] loc64 = new byte[ZIP64_LOCHDR]; - if (readFullyAt(loc64, 0, loc64.length, end.endpos - ZIP64_LOCHDR) - != loc64.length) { - return end; - } - long end64pos = ZIP64_LOCOFF(loc64); - byte[] end64buf = new byte[ZIP64_ENDHDR]; - if (readFullyAt(end64buf, 0, end64buf.length, end64pos) - != end64buf.length) { - return end; - } - // end64 found, re-calcualte everything. - end.cenlen = ZIP64_ENDSIZ(end64buf); - end.cenoff = ZIP64_ENDOFF(end64buf); - end.centot = (int)ZIP64_ENDTOT(end64buf); // assume total < 2g - end.endpos = end64pos; + // try if there is zip64 end; + byte[] loc64 = new byte[ZIP64_LOCHDR]; + if (end.endpos < ZIP64_LOCHDR || + readFullyAt(loc64, 0, loc64.length, end.endpos - ZIP64_LOCHDR) + != loc64.length || + !locator64SigAt(loc64, 0)) { + return end; } + long end64pos = ZIP64_LOCOFF(loc64); + byte[] end64buf = new byte[ZIP64_ENDHDR]; + if (readFullyAt(end64buf, 0, end64buf.length, end64pos) + != end64buf.length || + !end64SigAt(end64buf, 0)) { + return end; + } + // end64 found, + long cenlen64 = ZIP64_ENDSIZ(end64buf); + long cenoff64 = ZIP64_ENDOFF(end64buf); + long centot64 = ZIP64_ENDTOT(end64buf); + // double-check + if (cenlen64 != end.cenlen && end.cenlen != ZIP64_MINVAL || + cenoff64 != end.cenoff && end.cenoff != ZIP64_MINVAL || + centot64 != end.centot && end.centot != ZIP64_MINVAL32) { + return end; + } + // to use the end64 values + end.cenlen = cenlen64; + end.cenoff = cenoff64; + end.centot = (int)centot64; // assume total < 2g + end.endpos = end64pos; return end; } } @@ -1192,7 +1202,7 @@ class ZipFileSystem extends FileSystem { // sync the zip file system, if there is any udpate private void sync() throws IOException { - //System.out.printf("->sync(%s) starting....!%n", toString()); + // System.out.printf("->sync(%s) starting....!%n", toString()); // check ex-closer if (!exChClosers.isEmpty()) { for (ExChannelCloser ecc : exChClosers) { @@ -1282,7 +1292,7 @@ class ZipFileSystem extends FileSystem { } end.centot = elist.size(); end.cenlen = written - end.cenoff; - end.write(os, written); + end.write(os, written, forceEnd64); } if (!streams.isEmpty()) { // @@ -1712,8 +1722,8 @@ class ZipFileSystem extends FileSystem { long endpos; // int disktot; - void write(OutputStream os, long offset) throws IOException { - boolean hasZip64 = false; + void write(OutputStream os, long offset, boolean forceEnd64) throws IOException { + boolean hasZip64 = forceEnd64; // false; long xlen = cenlen; long xoff = cenoff; if (xlen >= ZIP64_MINVAL) { @@ -1738,8 +1748,8 @@ class ZipFileSystem extends FileSystem { writeShort(os, 45); // version needed to extract writeInt(os, 0); // number of this disk writeInt(os, 0); // central directory start disk - writeLong(os, centot); // number of directory entires on disk - writeLong(os, centot); // number of directory entires + writeLong(os, centot); // number of directory entries on disk + writeLong(os, centot); // number of directory entries writeLong(os, cenlen); // length of central directory writeLong(os, cenoff); // offset of central directory diff --git a/test/jdk/java/util/zip/ZipFile/ReadZip.java b/test/jdk/java/util/zip/ZipFile/ReadZip.java index 465f1ce3731..e158232448c 100644 --- a/test/jdk/java/util/zip/ZipFile/ReadZip.java +++ b/test/jdk/java/util/zip/ZipFile/ReadZip.java @@ -22,19 +22,27 @@ */ /* @test - @bug 4241361 4842702 4985614 6646605 5032358 6923692 6233323 8144977 + @bug 4241361 4842702 4985614 6646605 5032358 6923692 6233323 8144977 8186464 @summary Make sure we can read a zip file. @key randomness */ import java.io.*; +import java.net.URI; import java.nio.file.Files; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.NoSuchFileException; import java.nio.file.StandardCopyOption; import java.nio.file.StandardOpenOption; +import java.util.List; +import java.util.Map; import java.util.zip.*; +import static java.nio.charset.StandardCharsets.US_ASCII; + public class ReadZip { private static void unreached (Object o) throws Exception @@ -137,8 +145,6 @@ public class ReadZip { newZip.delete(); } - - // Throw a FNF exception when read a non-existing zip file try { unreached (new ZipFile( new File(System.getProperty("test.src", "."), @@ -146,5 +152,54 @@ public class ReadZip { + String.valueOf(new java.util.Random().nextInt()) + ".zip"))); } catch (NoSuchFileException nsfe) {} + + // read a zip file with ZIP64 end + Path path = Paths.get(System.getProperty("test.dir", ""), "end64.zip"); + try { + URI uri = URI.create("jar:" + path.toUri()); + Map<String, Object> env = Map.of("create", "true", "forceZIP64End", "true"); + try (FileSystem fs = FileSystems.newFileSystem(uri, env)) { + Files.write(fs.getPath("hello"), "hello".getBytes()); + } + try (ZipFile zf = new ZipFile(path.toFile())) { + if (!"hello".equals(new String(zf.getInputStream(new ZipEntry("hello")) + .readAllBytes(), + US_ASCII))) + throw new RuntimeException("zipfile: read entry failed"); + } catch (IOException x) { + throw new RuntimeException("zipfile: zip64 end failed"); + } + try (FileSystem fs = FileSystems.newFileSystem(uri, Map.of())) { + if (!"hello".equals(new String(Files.readAllBytes(fs.getPath("hello"))))) + throw new RuntimeException("zipfs: read entry failed"); + } catch (IOException x) { + throw new RuntimeException("zipfile: zip64 end failed"); + } + } finally { + Files.deleteIfExists(path); + } + + // read a zip file created via "echo hello | zip dst.zip -", which uses + // ZIP64 end record + if (Files.notExists(Paths.get("/usr/bin/zip"))) + return; + try { + Process zip = new ProcessBuilder("zip", path.toString().toString(), "-").start(); + OutputStream os = zip.getOutputStream(); + os.write("hello".getBytes(US_ASCII)); + os.close(); + zip.waitFor(); + if (zip.exitValue() == 0 && Files.exists(path)) { + try (ZipFile zf = new ZipFile(path.toFile())) { + if (!"hello".equals(new String(zf.getInputStream(new ZipEntry("-")) + .readAllBytes()))) + throw new RuntimeException("zipfile: read entry failed"); + } catch (IOException x) { + throw new RuntimeException("zipfile: zip64 end failed"); + } + } + } finally { + Files.deleteIfExists(path); + } } }