Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8195141

ZipFileSystem fails to read directory structures generated by ZipOutputStream if backslash as directory separator is used

XMLWordPrintable

    • x86_64
    • windows

      FULL PRODUCT VERSION :
      D:\temp\jdkbug>java -version
      java version "10-ea" 2018-03-20
      Java(TM) SE Runtime Environment 18.3 (build 10-ea+37)
      Java HotSpot(TM) 64-Bit Server VM 18.3 (build 10-ea+37, mixed mode)

      compiled with javac 1.8.0_152


      ADDITIONAL OS VERSION INFORMATION :
      Microsoft Windows [Version 6.2.9200]

      A DESCRIPTION OF THE PROBLEM :
      Recently, I wanted to replace / delete a file below a directory inside of some zip archive which was created by ZipOutputStream. I was unable to do so because ZipFileSystem always failed to find the directory -- deleting the file resulted in an exception and replacing it introduced a second copy of the same file name into the zip (essentially a corruption).

      I reduced the problem to a minimal (non) working example and realized that ZipOutputStream accepts both \ and / as directory separators, but ZipFileSystem can only read the result if ZipOutputStream was instructed to use / as directory separator.

      Note that the result of ZipOutputStream looks like a normal archive in the following aspects:
      1. ZipInputStream can read it
      2. Windows can read it
      3. 7z can read it

      That leads me to the conclusion that ZipFileSystem should be more stable with respect to directory delimiters.

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      Execute the attached code sample.

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      The file xdl/content.dat should be deleted from the zip.
      ACTUAL -
      I get
      D:\temp\jdkbug>java -cp . Main
      /xdl\content.dat (below /)
      /file1.dat (below /)
      Exception in thread "main" java.nio.file.NoSuchFileException: /xdl/content.dat
              at jdk.zipfs/jdk.nio.zipfs.ZipFileSystem.deleteFile(Unknown Source)
              at jdk.zipfs/jdk.nio.zipfs.ZipPath.delete(Unknown Source)
              at jdk.zipfs/jdk.nio.zipfs.ZipFileSystemProvider.delete(Unknown Source)
              at java.base/java.nio.file.Files.delete(Unknown Source)
              at Main.main(Main.java:48)


      REPRODUCIBILITY :
      This bug can be reproduced always.

      ---------- BEGIN SOURCE ----------
      import java.io.File;
      import java.io.FileOutputStream;
      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.util.HashMap;
      import java.util.Map;
      import java.util.zip.Deflater;
      import java.util.zip.ZipEntry;
      import java.util.zip.ZipOutputStream;

      public class Main
      {
        public static void main(String[] args) throws Exception
        {
          final File archive = new File("d:\\temp\\jdkbug\\input.zip");
          ZipOutputStream os = new ZipOutputStream(new FileOutputStream(archive));
          os.setLevel(Deflater.BEST_SPEED);
          os.putNextEntry(new ZipEntry("file1.dat"));
          os.write("some content\n".getBytes("UTF-8"));
          os.closeEntry();
          os.putNextEntry(new ZipEntry("xdl\\content.dat"));
          os.write("content content content content content content content content content content content content content content content content content content content content content "
              .getBytes("UTF-8"));
          os.closeEntry();
          os.close();

          Map<String, Object> env = new HashMap<>();
          try (FileSystem fs = FileSystems.newFileSystem(new URI("jar:" + archive.toURI()), env))
          {
            try (java.nio.file.DirectoryStream<Path> directoryStream = Files.newDirectoryStream(fs.getPath("/")))
            {
              for (Path p : directoryStream)
              {
                System.out.println(p + " (below " + p.getParent() + ")");
              }
            }
            System.out.flush();
          }
          try (FileSystem fs = FileSystems.newFileSystem(new URI("jar:" + archive.toURI()), env))
          {
            Path fileInsideZipPath;
            // fileInsideZipPath = fs.getPath("xdl\\com.recommind.jobcontext.dat");
            fileInsideZipPath = fs.getPath("/xdl/content.dat");
            //Files.createDirectories(fileInsideZipPath.getParent());
            Files.delete(fileInsideZipPath);
            //Files.copy(new File("d:\\temp\\P").toPath(), fileInsideZipPath, StandardCopyOption.REPLACE_EXISTING);
          }
          System.exit(0);
        }
      }

      ---------- END SOURCE ----------

      CUSTOMER SUBMITTED WORKAROUND :
      use
      os.putNextEntry(new ZipEntry("xdl/content.dat"));
      when generating the zip instead of
      os.putNextEntry(new ZipEntry("xdl\\content.dat"));

      I found no workaround which would let ZipFileSystem process the original zip as expected.

            sherman Xueming Shen
            webbuggrp Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            4 Start watching this issue

              Created:
              Updated:
              Resolved: