8186464: ZipFile cannot read some InfoZip ZIP64 zip files
authorsherman
Wed, 20 Sep 2017 16:41:54 -0700
changeset 47223 723486922bfe
parent 47222 c8ac05bbe477
child 47224 e81f9ee4df7f
8186464: ZipFile cannot read some InfoZip ZIP64 zip files Reviewed-by: martin
src/java.base/share/classes/java/util/zip/ZipFile.java
src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileSystem.java
test/jdk/java/util/zip/ZipFile/ReadZip.java
--- a/src/java.base/share/classes/java/util/zip/ZipFile.java	Wed Sep 20 02:16:19 2017 +0000
+++ b/src/java.base/share/classes/java/util/zip/ZipFile.java	Wed Sep 20 16:41:54 2017 -0700
@@ -1122,30 +1122,36 @@
                                 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;
                     }
                 }
--- a/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileSystem.java	Wed Sep 20 02:16:19 2017 +0000
+++ b/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileSystem.java	Wed Sep 20 16:41:54 2017 -0700
@@ -79,6 +79,7 @@
     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 @@
                               (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 @@
                     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 @@
 
     // 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 @@
             }
             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 @@
         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 @@
                 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
 
--- a/test/jdk/java/util/zip/ZipFile/ReadZip.java	Wed Sep 20 02:16:19 2017 +0000
+++ b/test/jdk/java/util/zip/ZipFile/ReadZip.java	Wed Sep 20 16:41:54 2017 -0700
@@ -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 @@
             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 @@
                                       + 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);
+        }
     }
 }