src/java.base/share/classes/java/util/jar/JarFile.java

Print this page

        

@@ -24,10 +24,11 @@
  */
 
 package java.util.jar;
 
 import jdk.internal.misc.SharedSecrets;
+import jdk.internal.misc.JavaUtilZipFileAccess;
 import sun.security.action.GetPropertyAction;
 import sun.security.util.ManifestEntryVerifier;
 import sun.security.util.SignatureFileVerifier;
 
 import java.io.ByteArrayInputStream;

@@ -43,14 +44,16 @@
 import java.util.ArrayList;
 import java.util.Enumeration;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Locale;
+import java.util.Map;
 import java.util.NoSuchElementException;
 import java.util.Objects;
 import java.util.Spliterator;
 import java.util.Spliterators;
+import java.util.stream.Collector;
 import java.util.stream.Stream;
 import java.util.stream.StreamSupport;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipException;
 import java.util.zip.ZipFile;

@@ -161,13 +164,17 @@
     // indicates if Class-Path attribute present
     private boolean hasClassPathAttribute;
     // true if manifest checked for special attributes
     private volatile boolean hasCheckedSpecialAttributes;
 
+    private static final JavaUtilZipFileAccess JUZFA;
+
     static {
         // Set up JavaUtilJarAccess in SharedSecrets
         SharedSecrets.setJavaUtilJarAccess(new JavaUtilJarAccessImpl());
+        // Get JavaUtilZipFileAccess from SharedSecrets
+        JUZFA = jdk.internal.misc.SharedSecrets.getJavaUtilZipFileAccess();
         // multi-release jar file versions >= 9
         BASE_VERSION = Runtime.Version.parse(Integer.toString(8));
         BASE_VERSION_MAJOR = BASE_VERSION.major();
         String jarVersion = GetPropertyAction.privilegedGetProperty("jdk.util.jar.version");
         int runtimeVersion = Runtime.version().major();

@@ -422,12 +429,11 @@
         }
         return man;
     }
 
     private String[] getMetaInfEntryNames() {
-        return jdk.internal.misc.SharedSecrets.getJavaUtilZipFileAccess()
-                                              .getMetaInfEntryNames((ZipFile)this);
+        return JUZFA.getMetaInfEntryNames((ZipFile)this);
     }
 
     /**
      * Returns the {@code JarEntry} for the given base entry name or
      * {@code null} if not found.

@@ -495,62 +501,26 @@
      * If a subclass overrides this method, assure that the override method
      * invokes {@code super.getEntry(name)} to obtain all versioned entries.
      * </div>
      */
     public ZipEntry getEntry(String name) {
-        ZipEntry ze = super.getEntry(name);
-        if (ze != null) {
-            return new JarFileEntry(ze);
-        }
-        // no matching base entry, but maybe there is a versioned entry,
-        // like a new private class
+        JarFileEntry je = getEntry0(name);
         if (isMultiRelease()) {
-            ze = new ZipEntry(name);
-            ZipEntry vze = getVersionedEntry(ze);
-            if (ze != vze) {
-                return new JarFileEntry(name, vze);
-            }
-        }
-        return null;
-    }
-
-    private class JarEntryIterator implements Enumeration<JarEntry>,
-            Iterator<JarEntry>
-    {
-        final Enumeration<? extends ZipEntry> e = JarFile.super.entries();
-
-        public boolean hasNext() {
-            return e.hasMoreElements();
-        }
-
-        public JarEntry next() {
-            ZipEntry ze = e.nextElement();
-            return new JarFileEntry(ze.getName(), ze);
-        }
-
-        public boolean hasMoreElements() {
-            return hasNext();
-        }
-
-        public JarEntry nextElement() {
-            return next();
-        }
-
-        public Iterator<JarEntry> asIterator() {
-            return this;
+            return getVersionedEntry(name, je);
         }
+        return je;
     }
 
     /**
      * Returns an enumeration of the jar file entries.
      *
      * @return an enumeration of the jar file entries
      * @throws IllegalStateException
      *         may be thrown if the jar file has been closed
      */
     public Enumeration<JarEntry> entries() {
-        return new JarEntryIterator();
+        return JUZFA.entries(this, JarFileEntry::new);
     }
 
     /**
      * Returns an ordered {@code Stream} over the jar file entries.
      * Entries appear in the {@code Stream} in the order they appear in

@@ -559,80 +529,114 @@
      * @return an ordered {@code Stream} of entries in this jar file
      * @throws IllegalStateException if the jar file has been closed
      * @since 1.8
      */
     public Stream<JarEntry> stream() {
-        return StreamSupport.stream(Spliterators.spliterator(
-                new JarEntryIterator(), size(),
-                Spliterator.ORDERED | Spliterator.DISTINCT |
-                        Spliterator.IMMUTABLE | Spliterator.NONNULL), false);
-    }
-
-    private ZipEntry searchForVersionedEntry(final int version, String name) {
-        ZipEntry vze = null;
-        String sname = "/" + name;
-        int i = version;
-        while (i > BASE_VERSION_MAJOR) {
-            vze = super.getEntry(META_INF_VERSIONS + i + sname);
-            if (vze != null) break;
-            i--;
+        return JUZFA.stream(this, JarFileEntry::new);
+    }
+
+    /**
+     * Returns a {@code Stream} of the versioned jar file entries.
+     *
+     * <p>If this {@code JarFile} is a multi-release jar file and is configured to
+     * be processed as such, then an entry in the stream is the latest versioned entry
+     * associated with the corresponding base entry name. The maximum version of the
+     * latest versioned entry is the version returned by {@link #getVersion()}.
+     * The returned stream may include an entry that only exists as a versioned entry.
+     *
+     * If the jar file is not a multi-release jar file or the {@code JarFile} is not
+     * configured for processing a multi-release jar file, this method returns the
+     * same stream that {@link #stream()} returns.
+     *
+     * @return stream of versioned entries
+     * @since 10
+     */
+    public Stream<JarEntry> versionedStream() {
+
+        if (isMultiRelease()) {
+            return JUZFA.entryNameStream(this).map(this::getBasename)
+                                              .filter(Objects::nonNull)
+                                              .distinct()
+                                              .map(this::getJarEntry);
         }
-        return vze;
+        return stream();
     }
 
-    private ZipEntry getVersionedEntry(ZipEntry ze) {
-        ZipEntry vze = null;
+    /*
+     * Invokes {@ZipFile}'s getEntry to Return a {@code JarFileEntry} for the
+     * given entry name or {@code null} if not found.
+     */
+    private JarFileEntry getEntry0(String name) {
+        return (JarFileEntry)JUZFA.getEntry(this, name, JarFileEntry::new);
+    }
+
+    private String getBasename(String name) {
+        if (name.startsWith(META_INF_VERSIONS)) {
+            int off = META_INF_VERSIONS.length();
+            int index = name.indexOf('/', off);
+            try {
+                // filter out dir META-INF/versions/ and META-INF/versions/*/
+                // and any entry with version > 'version'
+                if (index == -1 || index == (name.length() - 1) ||
+                    Integer.parseInt(name, off, index, 10) > versionMajor) {
+                    return null;
+                }
+            } catch (NumberFormatException x) {
+                return null; // remove malformed entries silently
+            }
+            // map to its base name
+            return name.substring(index + 1);
+        }
+        return name;
+    }
+
+    private JarEntry getVersionedEntry(String name, JarEntry je) {
         if (BASE_VERSION_MAJOR < versionMajor) {
-            String name = ze.getName();
             if (!name.startsWith(META_INF)) {
-                vze = searchForVersionedEntry(versionMajor, name);
+                // search for versioned entry
+                int v = versionMajor;
+                while (v > BASE_VERSION_MAJOR) {
+                    JarFileEntry vje = getEntry0(META_INF_VERSIONS + v + "/" + name);
+                    if (vje != null) {
+                        return vje.withBasename(name);
+                    }
+                    v--;
             }
         }
-        return vze == null ? ze : vze;
+        }
+        return je;
     }
 
-    /**
-     * Returns the real name of a {@code JarEntry}.  If this {@code JarFile} is
-     * a multi-release jar file and is configured to be processed as such, the
-     * name returned by this method is the path name of the versioned entry
-     * that the {@code JarEntry} represents, rather than the path name of the
-     * base entry that {@link JarEntry#getName()} returns.  If the
-     * {@code JarEntry} does not represent a versioned entry, or the
-     * jar file is not a multi-release jar file or {@code JarFile} is not
-     * configured for processing a multi-release jar file, this method returns
-     * the same name that {@link JarEntry#getName()} returns.
-     *
-     * @param entry the JarEntry
-     * @return the real name of the JarEntry
-     * @since 9
-     */
+    // placeholder for now
     String getRealName(JarEntry entry) {
-        if (entry instanceof JarFileEntry) {
-            return ((JarFileEntry)entry).realName();
-        }
-        return entry.getName();
+        return entry.getRealName();
     }
 
     private class JarFileEntry extends JarEntry {
-        final private String name;
+        private String basename;
 
-        JarFileEntry(ZipEntry ze) {
-            super(isMultiRelease() ? getVersionedEntry(ze) : ze);
-            this.name = ze.getName();
+        JarFileEntry(String name) {
+            super(name);
+            this.basename = name;
         }
+
         JarFileEntry(String name, ZipEntry vze) {
             super(vze);
-            this.name = name;
+            this.basename = name;
         }
+
+        @Override
         public Attributes getAttributes() throws IOException {
             Manifest man = JarFile.this.getManifest();
             if (man != null) {
                 return man.getAttributes(super.getName());
             } else {
                 return null;
             }
         }
+
+        @Override
         public Certificate[] getCertificates() {
             try {
                 maybeInstantiateVerifier();
             } catch (IOException e) {
                 throw new RuntimeException(e);

@@ -640,10 +644,12 @@
             if (certs == null && jv != null) {
                 certs = jv.getCerts(JarFile.this, realEntry());
             }
             return certs == null ? null : certs.clone();
         }
+
+        @Override
         public CodeSigner[] getCodeSigners() {
             try {
                 maybeInstantiateVerifier();
             } catch (IOException e) {
                 throw new RuntimeException(e);

@@ -651,24 +657,34 @@
             if (signers == null && jv != null) {
                 signers = jv.getCodeSigners(JarFile.this, realEntry());
             }
             return signers == null ? null : signers.clone();
         }
+
+        @Override
+        public String getRealName() {
+            return super.getName();
+        }
+
+        @Override
+        public String getName() {
+            return basename;
+        }
+
         JarFileEntry realEntry() {
             if (isMultiRelease() && versionMajor != BASE_VERSION_MAJOR) {
                 String entryName = super.getName();
-                return entryName.equals(this.name) ? this : new JarFileEntry(entryName, this);
+                return entryName == basename || entryName.equals(basename) ?
+                        this : new JarFileEntry(entryName, this);
             }
             return this;
         }
-        String realName() {
-            return super.getName();
-        }
 
-        @Override
-        public String getName() {
-            return name;
+        // changes the basename, returns "this"
+        JarFileEntry withBasename(String name) {
+            basename = name;
+            return this;
         }
     }
 
     /*
      * Ensures that the JarVerifier has been created if one is

@@ -702,11 +718,10 @@
             // verifier
             verify = false;
         }
     }
 
-
     /*
      * Initializes the verifier object by reading all the manifest
      * entries and passing them to the verifier.
      */
     private void initializeVerifier() {

@@ -902,27 +917,25 @@
     }
 
     private JarEntry getManEntry() {
         if (manEntry == null) {
             // First look up manifest entry using standard name
-            ZipEntry manEntry = super.getEntry(MANIFEST_NAME);
+            JarEntry manEntry = getEntry0(MANIFEST_NAME);
             if (manEntry == null) {
                 // If not found, then iterate through all the "META-INF/"
                 // entries to find a match.
                 String[] names = getMetaInfEntryNames();
                 if (names != null) {
                     for (String name : names) {
                         if (MANIFEST_NAME.equals(name.toUpperCase(Locale.ENGLISH))) {
-                            manEntry = super.getEntry(name);
+                            manEntry = getEntry0(name);
                             break;
                         }
                     }
                 }
             }
-            this.manEntry = (manEntry == null)
-                    ? null
-                    : new JarFileEntry(manEntry.getName(), manEntry);
+            this.manEntry = manEntry;
         }
         return manEntry;
     }
 
    /**

@@ -1030,12 +1043,36 @@
             initializeVerifier();
             jvInitialized = true;
         }
     }
 
-    JarEntry newEntry(ZipEntry ze) {
-        return new JarFileEntry(ze);
+    /*
+     * Returns a versioned {@code JarFileEntry} for the given entry,
+     * if there is one. Otherwise returns the original entry. This
+     * is invoked by the {@code entries2} for verifier.
+     */
+    JarEntry newEntry(JarEntry je) {
+        if (isMultiRelease()) {
+            return getVersionedEntry(je.getName(), je);
+        }
+        return je;
+    }
+
+    /*
+     * Returns a versioned {@code JarFileEntry} for the given entry
+     * name, if there is one. Otherwise returns a {@code JarFileEntry}
+     * with the given name. It is invoked from JarVerifier's entries2
+     * for {@code singers}.
+     */
+    JarEntry newEntry(String name) {
+        if (isMultiRelease()) {
+            JarEntry vje = getVersionedEntry(name, (JarEntry)null);
+            if (vje != null) {
+                return vje;
+            }
+        }
+        return new JarFileEntry(name);
     }
 
     Enumeration<String> entryNames(CodeSource[] cs) {
         ensureInitialization();
         if (jv != null) {

@@ -1075,39 +1112,41 @@
      * signed entries missing from the ZIP directory.
      */
     Enumeration<JarEntry> entries2() {
         ensureInitialization();
         if (jv != null) {
-            return jv.entries2(this, super.entries());
+            return jv.entries2(this, JUZFA.entries(JarFile.this,
+                                                   JarFileEntry::new));
         }
 
         // screen out entries which are never signed
-        final Enumeration<? extends ZipEntry> enum_ = super.entries();
+        final var unfilteredEntries = JUZFA.entries(JarFile.this, JarFileEntry::new);
+
         return new Enumeration<>() {
 
-            ZipEntry entry;
+            JarEntry entry;
 
             public boolean hasMoreElements() {
                 if (entry != null) {
                     return true;
                 }
-                while (enum_.hasMoreElements()) {
-                    ZipEntry ze = enum_.nextElement();
-                    if (JarVerifier.isSigningRelated(ze.getName())) {
+                while (unfilteredEntries.hasMoreElements()) {
+                    JarEntry je = unfilteredEntries.nextElement();
+                    if (JarVerifier.isSigningRelated(je.getName())) {
                         continue;
                     }
-                    entry = ze;
+                    entry = je;
                     return true;
                 }
                 return false;
             }
 
-            public JarFileEntry nextElement() {
+            public JarEntry nextElement() {
                 if (hasMoreElements()) {
-                    ZipEntry ze = entry;
+                    JarEntry je = entry;
                     entry = null;
-                    return new JarFileEntry(ze);
+                    return newEntry(je);
                 }
                 throw new NoSuchElementException();
             }
         };
     }