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

JarEntry.setCreation/LastAccessTime without setLastModifiedTime causes Invalid CEN header

    Details

    • Subcomponent:
    • Introduced In Build:
      b129
    • Introduced In Version:
      9
    • Resolved In Build:
      b02
    • CPU:
      generic
    • OS:
      generic
    • Verification:
      Verified

      Backports

        Description

        ADDITIONAL SYSTEM INFORMATION :
        Macbook Pro 15-inch 2016
        Mac OS X High Sierra 10.13.5
        OpenJDK jdk-10.0.1.jdk & jdk-11.jdk

        A DESCRIPTION OF THE PROBLEM :
        I have observed that when building a Jar using JarOutputStream, when adding a JarEntry if I call JarEntry.setLastModifiedTime prior to calling setTime, the Jar is built incorrectly and reading it using java.util.jar.JarFile will throw an exception.

        java.util.zip.ZipException: invalid CEN header (bad header size)

        Expected Results: Regardless of order, or calls to these methods should produce a valid Jar file or exception while writing the Jar file to indicate the error.




        REGRESSION : Last worked in version 8u171

        STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
        1. Open a JarOutputStream
        2. Create a single JarEntry
        3. setCreationTime
        4. setLastAccessTime
        5. setLastModifiedTime
        6. setTime
        7. JarOutputStream.putNextEntry

        EXPECTED VERSUS ACTUAL BEHAVIOR :
        EXPECTED -
        Expect the Jar to be built correctly or for the JarOutputStream to fail if it is unable to be built correctly.

        This previously worked on Oracle JRE jdk1.8.0_171 and earlier.
        ACTUAL -
        The JarOutputStream does not fail, but the jar is not built correctly, and then reading the jar using java.util.jar.JarFile will throw java.util.zip.ZipException: invalid CEN header (bad header size).

        I can recreate these results on Open JDK 10.0.1 and 11.

        ---------- BEGIN SOURCE ----------
        import java.io.File;
        import java.io.IOException;
        import java.nio.file.Files;
        import java.nio.file.Path;
        import java.nio.file.Paths;
        import java.nio.file.attribute.FileTime;
        import java.util.function.Consumer;
        import java.util.jar.JarEntry;
        import java.util.jar.JarFile;
        import java.util.jar.JarOutputStream;
        import java.util.jar.Manifest;
        import java.util.zip.ZipException;

        /**
         * @author Daniel DeGroff
         */
        public class JarOutputStreamTest {

          public static void main(String[] args) throws IOException {

            // Create a test file to include in the jar.
            Files.deleteIfExists(Paths.get("./foo"));
            Path file = Files.createFile(Paths.get("./foo"));

            FileTime creationTime = (FileTime) Files.getAttribute(file, "creationTime");
            FileTime lastAccessTime = (FileTime) Files.getAttribute(file, "lastAccessTime");
            FileTime lastModifiedTime = Files.getLastModifiedTime(file);

            File jarFile = new File("testcase");
            jarFile.deleteOnExit();

            // 1. Passes, order is Ok.
            runTestCase(jarFile, file, entry -> {
              entry.setCreationTime(creationTime);
              entry.setLastAccessTime(lastAccessTime);

              // Calling setTime prior to setLastModifiedTime is Ok.
              entry.setTime(lastModifiedTime.toMillis());
              entry.setLastModifiedTime(lastModifiedTime);
            });

            // 2. Passes, omit the call to setTime
            runTestCase(jarFile, file, entry -> {
              entry.setCreationTime(creationTime);
              entry.setLastAccessTime(lastAccessTime);

              // Omitting the call to setTime is Ok.
              entry.setLastModifiedTime(lastModifiedTime);
            });

            // 3. Passes, omit setCreationTime and setLastAccessTime then order does not matter
            runTestCase(jarFile, file, entry -> {
              // Calling these two in either order is ok when we don't call setCreationTime and setLastAccessTime
              entry.setTime(lastModifiedTime.toMillis());
              entry.setLastModifiedTime(lastModifiedTime);
            });

            // 4. Passes, omit setCreationTime and setLastAccessTime then order does not matter
            runTestCase(jarFile, file, entry -> {
              // Calling these two in either order is ok when we don't call setCreationTime and setLastAccessTime
              entry.setLastModifiedTime(lastModifiedTime);
              entry.setTime(lastModifiedTime.toMillis());
            });

            // 5. Fails
            runTestCase(jarFile, file, entry -> {
              entry.setCreationTime(creationTime);
              entry.setLastAccessTime(lastAccessTime);

              // Calling setLastModifiedTime prior to setTime when also calling setCreationTime and setLastAccessTime fails.
              entry.setLastModifiedTime(lastModifiedTime);
              entry.setTime(lastModifiedTime.toMillis());
            });
          }


          private static void runTestCase(File jarFile, Path file, Consumer<JarEntry> consumer) throws IOException {
            try (JarOutputStream jos = new JarOutputStream(Files.newOutputStream(jarFile.toPath()), new Manifest())) {
              JarEntry entry = new JarEntry(file.toString());
              consumer.accept(entry);
              entry.setSize((Long) Files.getAttribute(file, "size"));
              jos.putNextEntry(entry);
              jos.flush();
              jos.closeEntry();
            }

            try {
              new JarFile(jarFile);
              System.out.println("Success!");
            } catch (ZipException e) {
              // Throws java.util.zip.ZipException: invalid CEN header (bad header size)
              System.out.println("Fail. " + e.getClass().getCanonicalName() + ": " + e.getMessage());
            }
          }
        }

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

        CUSTOMER SUBMITTED WORKAROUND :
        Workaround 1:

        Call JarEntry.setTime before JarEntry.setLastModifiedTime

        Workaround 2:

        Leave setLastModifiedTime before setTime but omit setCreationTime and setLastAccessTime

        Workaround 3:

        Do not call setTime, instead only call setCreationTime, setLastAccessTime, and setLastModifiedTime

         

        FREQUENCY : always


          Attachments

            Issue Links

              Activity

                People

                • Assignee:
                  sherman Xueming Shen
                  Reporter:
                  webbuggrp Webbug Group
                • Votes:
                  0 Vote for this issue
                  Watchers:
                  7 Start watching this issue

                  Dates

                  • Created:
                    Updated:
                    Resolved: