rev 13960 : 8152733: Avoid creating Manifest when checking for Multi-Release attribute
Reviewed-by: psandoz, alanb, dchuyko
Contributed-by: claes.redestad@oracle.com, steve.drach@oracle.com

   1 /*
   2  * Copyright (c) 1997, 2015, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package java.util.jar;
  27 
  28 import java.io.*;
  29 import java.lang.ref.SoftReference;
  30 import java.net.URL;

  31 import java.util.*;
  32 import java.util.stream.Stream;
  33 import java.util.stream.StreamSupport;
  34 import java.util.zip.*;
  35 import java.security.CodeSigner;
  36 import java.security.cert.Certificate;
  37 import java.security.AccessController;
  38 import java.security.CodeSource;
  39 import jdk.internal.misc.SharedSecrets;
  40 import sun.security.action.GetPropertyAction;
  41 import sun.security.util.ManifestEntryVerifier;
  42 import sun.security.util.SignatureFileVerifier;
  43 


  44 /**
  45  * The {@code JarFile} class is used to read the contents of a jar file
  46  * from any file that can be opened with {@code java.io.RandomAccessFile}.
  47  * It extends the class {@code java.util.zip.ZipFile} with support
  48  * for reading an optional {@code Manifest} entry, and support for
  49  * processing multi-release jar files.  The {@code Manifest} can be used
  50  * to specify meta-information about the jar file and its entries.
  51  *
  52  * <p>A multi-release jar file is a jar file that contains
  53  * a manifest with a main attribute named "Multi-Release",
  54  * a set of "base" entries, some of which are public classes with public
  55  * or protected methods that comprise the public interface of the jar file,
  56  * and a set of "versioned" entries contained in subdirectories of the
  57  * "META-INF/versions" directory.  The versioned entries are partitioned by the
  58  * major version of the Java platform.  A versioned entry, with a version
  59  * {@code n}, {@code 8 < n}, in the "META-INF/versions/{n}" directory overrides
  60  * the base entry as well as any entry with a version number {@code i} where
  61  * {@code 8 < i < n}.
  62  *
  63  * <p>By default, a {@code JarFile} for a multi-release jar file is configured
  64  * to process the multi-release jar file as if it were a plain (unversioned) jar
  65  * file, and as such an entry name is associated with at most one base entry.
  66  * The {@code JarFile} may be configured to process a multi-release jar file by
  67  * creating the {@code JarFile} with the
  68  * {@link JarFile#JarFile(File, boolean, int, Release)} constructor.  The
  69  * {@code Release} object sets a maximum version used when searching for
  70  * versioned entries.  When so configured, an entry name
  71  * can correspond with at most one base entry and zero or more versioned
  72  * entries. A search is required to associate the entry name with the latest
  73  * versioned entry whose version is less than or equal to the maximum version
  74  * (see {@link #getEntry(String)}).
  75  *
  76  * <p>Class loaders that utilize {@code JarFile} to load classes from the
  77  * contents of {@code JarFile} entries should construct the {@code JarFile}
  78  * by invoking the {@link JarFile#JarFile(File, boolean, int, Release)}
  79  * constructor with the value {@code Release.RUNTIME} assigned to the last
  80  * argument.  This assures that classes compatible with the major
  81  * version of the running JVM are loaded from multi-release jar files.
  82  *
  83  * <p>If the verify flag is on when opening a signed jar file, the content of
  84  * the file is verified against its signature embedded inside the file. Please
  85  * note that the verification process does not include validating the signer's
  86  * certificate. A caller should inspect the return value of
  87  * {@link JarEntry#getCodeSigners()} to further determine if the signature
  88  * can be trusted.
  89  *
  90  * <p> Unless otherwise noted, passing a {@code null} argument to a constructor
  91  * or method in this class will cause a {@link NullPointerException} to be
  92  * thrown.
  93  *
  94  * @implNote
  95  * <div class="block">
  96  * If the API can not be used to configure a {@code JarFile} (e.g. to override
  97  * the configuration of a compiled application or library), two {@code System}
  98  * properties are available.
  99  * <ul>
 100  * <li>
 101  * {@code jdk.util.jar.version} can be assigned a value that is the
 102  * {@code String} representation of a non-negative integer
 103  * {@code <= Version.current().major()}.  The value is used to set the effective
 104  * runtime version to something other than the default value obtained by
 105  * evaluating {@code Version.current().major()}. The effective runtime version
 106  * is the version that the {@link JarFile#JarFile(File, boolean, int, Release)}
 107  * constructor uses when the value of the last argument is
 108  * {@code Release.RUNTIME}.
 109  * </li>
 110  * <li>
 111  * {@code jdk.util.jar.enableMultiRelease} can be assigned one of the three
 112  * {@code String} values <em>true</em>, <em>false</em>, or <em>force</em>.  The
 113  * value <em>true</em>, the default value, enables multi-release jar file
 114  * processing.  The value <em>false</em> disables multi-release jar processing,
 115  * ignoring the "Multi-Release" manifest attribute, and the versioned
 116  * directories in a multi-release jar file if they exist.  Furthermore,
 117  * the method {@link JarFile#isMultiRelease()} returns <em>false</em>. The value
 118  * <em>force</em> causes the {@code JarFile} to be initialized to runtime
 119  * versioning after construction.  It effectively does the same as this code:
 120  * {@code (new JarFile(File, boolean, int, Release.RUNTIME)}.
 121  * </li>
 122  * </ul>
 123  * </div>
 124  *
 125  * @author  David Connelly
 126  * @see     Manifest
 127  * @see     java.util.zip.ZipFile
 128  * @see     java.util.jar.JarEntry
 129  * @since   1.2
 130  */
 131 public
 132 class JarFile extends ZipFile {
 133     private final static int BASE_VERSION;
 134     private final static int RUNTIME_VERSION;
 135     private final static boolean MULTI_RELEASE_ENABLED;
 136     private final static boolean MULTI_RELEASE_FORCED;
 137     private SoftReference<Manifest> manRef;
 138     private JarEntry manEntry;
 139     private JarVerifier jv;
 140     private boolean jvInitialized;
 141     private boolean verify;
 142     private final int version;
 143     private boolean notVersioned;
 144     private final boolean runtimeVersioned;
 145     private boolean isMultiRelease;    // is jar multi-release?
 146 
 147     // indicates if Class-Path attribute present
 148     private boolean hasClassPathAttribute;
 149     // true if manifest checked for special attributes
 150     private volatile boolean hasCheckedSpecialAttributes;
 151 
 152     static {
 153         // Set up JavaUtilJarAccess in SharedSecrets
 154         SharedSecrets.setJavaUtilJarAccess(new JavaUtilJarAccessImpl());
 155 
 156         BASE_VERSION = 8;  // one less than lowest version for versioned entries
 157         int runtimeVersion = jdk.Version.current().major();
 158         String jarVersion = AccessController.doPrivileged(
 159                 new GetPropertyAction("jdk.util.jar.version"));
 160         if (jarVersion != null) {
 161             int jarVer = Integer.parseInt(jarVersion);
 162             runtimeVersion = (jarVer > runtimeVersion)
 163                     ? runtimeVersion : Math.max(jarVer, 0);
 164         }
 165         RUNTIME_VERSION = runtimeVersion;
 166         String enableMultiRelease = AccessController.doPrivileged(
 167                 new GetPropertyAction("jdk.util.jar.enableMultiRelease", "true"));
 168         switch (enableMultiRelease) {






 169             case "true":
 170             default:
 171                 MULTI_RELEASE_ENABLED = true;
 172                 MULTI_RELEASE_FORCED = false;
 173                 break;
 174             case "false":
 175                 MULTI_RELEASE_ENABLED = false;
 176                 MULTI_RELEASE_FORCED = false;
 177                 break;
 178             case "force":
 179                 MULTI_RELEASE_ENABLED = true;
 180                 MULTI_RELEASE_FORCED = true;
 181                 break;
 182         }
 183     }
 184 
 185     /**
 186      * A set of constants that represent the entries in either the base directory
 187      * or one of the versioned directories in a multi-release jar file.  It's
 188      * possible for a multi-release jar file to contain versioned directories
 189      * that are not represented by the constants of the {@code Release} enum.
 190      * In those cases, the entries will not be located by this {@code JarFile}
 191      * through the aliasing mechanism, but they can be directly accessed by
 192      * specifying the full path name of the entry.
 193      *
 194      * @since 9
 195      */
 196     public enum Release {
 197         /**
 198          * Represents unversioned entries, or entries in "regular", as opposed
 199          * to multi-release jar files.
 200          */
 201         BASE(BASE_VERSION),
 202 
 203         /**
 204          * Represents entries found in the META-INF/versions/9 directory of a
 205          * multi-release jar file.
 206          */
 207         VERSION_9(9),
 208 
 209         // fill in the "blanks" for future releases
 210 
 211         /**
 212          * Represents entries found in the META-INF/versions/{n} directory of a
 213          * multi-release jar file, where {@code n} is the effective runtime
 214          * version of the jar file.
 215          *
 216          * @implNote
 217          * <div class="block">
 218          * The effective runtime version is determined
 219          * by evaluating {@code Version.current().major()} or by using the value
 220          * of the {@code jdk.util.jar.version} System property if it exists.
 221          * </div>
 222          */
 223         RUNTIME(RUNTIME_VERSION);
 224 
 225         Release(int version) {
 226             this.version = version;
 227         }
 228 
 229         private static Release valueOf(int version) {
 230             return version <= BASE.value() ? BASE : valueOf("VERSION_" + version);
 231         }
 232 
 233         private final int version;
 234 
 235         private int value() {
 236             return this.version;
 237         }
 238     }
 239 
 240     private static final String META_INF = "META-INF/";
 241 
 242     private static final String META_INF_VERSIONS = META_INF + "versions/";
 243 
 244     /**
 245      * The JAR manifest file name.
 246      */
 247     public static final String MANIFEST_NAME = META_INF + "MANIFEST.MF";
 248 
 249     /**
 250      * Creates a new {@code JarFile} to read from the specified
 251      * file {@code name}. The {@code JarFile} will be verified if
 252      * it is signed.
 253      * @param name the name of the jar file to be opened for reading
 254      * @throws IOException if an I/O error has occurred
 255      * @throws SecurityException if access to the file is denied
 256      *         by the SecurityManager
 257      */
 258     public JarFile(String name) throws IOException {
 259         this(new File(name), true, ZipFile.OPEN_READ);
 260     }
 261 
 262     /**
 263      * Creates a new {@code JarFile} to read from the specified
 264      * file {@code name}.
 265      * @param name the name of the jar file to be opened for reading
 266      * @param verify whether or not to verify the jar file if
 267      * it is signed.
 268      * @throws IOException if an I/O error has occurred
 269      * @throws SecurityException if access to the file is denied
 270      *         by the SecurityManager
 271      */
 272     public JarFile(String name, boolean verify) throws IOException {
 273         this(new File(name), verify, ZipFile.OPEN_READ);
 274     }
 275 
 276     /**
 277      * Creates a new {@code JarFile} to read from the specified
 278      * {@code File} object. The {@code JarFile} will be verified if
 279      * it is signed.
 280      * @param file the jar file to be opened for reading
 281      * @throws IOException if an I/O error has occurred
 282      * @throws SecurityException if access to the file is denied
 283      *         by the SecurityManager
 284      */
 285     public JarFile(File file) throws IOException {
 286         this(file, true, ZipFile.OPEN_READ);
 287     }
 288 
 289     /**
 290      * Creates a new {@code JarFile} to read from the specified
 291      * {@code File} object.
 292      * @param file the jar file to be opened for reading
 293      * @param verify whether or not to verify the jar file if
 294      * it is signed.
 295      * @throws IOException if an I/O error has occurred
 296      * @throws SecurityException if access to the file is denied
 297      *         by the SecurityManager.
 298      */
 299     public JarFile(File file, boolean verify) throws IOException {
 300         this(file, verify, ZipFile.OPEN_READ);
 301     }
 302 
 303     /**
 304      * Creates a new {@code JarFile} to read from the specified
 305      * {@code File} object in the specified mode.  The mode argument
 306      * must be either {@code OPEN_READ} or {@code OPEN_READ | OPEN_DELETE}.
 307      *
 308      * @param file the jar file to be opened for reading
 309      * @param verify whether or not to verify the jar file if
 310      * it is signed.
 311      * @param mode the mode in which the file is to be opened
 312      * @throws IOException if an I/O error has occurred
 313      * @throws IllegalArgumentException
 314      *         if the {@code mode} argument is invalid
 315      * @throws SecurityException if access to the file is denied
 316      *         by the SecurityManager
 317      * @since 1.3
 318      */
 319     public JarFile(File file, boolean verify, int mode) throws IOException {
 320         this(file, verify, mode, Release.BASE);
 321         this.notVersioned = true;
 322     }
 323 
 324     /**
 325      * Creates a new {@code JarFile} to read from the specified
 326      * {@code File} object in the specified mode.  The mode argument
 327      * must be either {@code OPEN_READ} or {@code OPEN_READ | OPEN_DELETE}.
 328      * The version argument configures the {@code JarFile} for processing
 329      * multi-release jar files.
 330      *
 331      * @param file the jar file to be opened for reading
 332      * @param verify whether or not to verify the jar file if
 333      * it is signed.
 334      * @param mode the mode in which the file is to be opened
 335      * @param version specifies the release version for a multi-release jar file
 336      * @throws IOException if an I/O error has occurred
 337      * @throws IllegalArgumentException
 338      *         if the {@code mode} argument is invalid
 339      * @throws SecurityException if access to the file is denied
 340      *         by the SecurityManager
 341      * @throws NullPointerException if {@code version} is {@code null}
 342      * @since 9
 343      */
 344     public JarFile(File file, boolean verify, int mode, Release version) throws IOException {
 345         super(file, mode);
 346         Objects.requireNonNull(version);
 347         this.verify = verify;
 348         // version applies to multi-release jar files, ignored for regular jar files
 349         if (MULTI_RELEASE_FORCED) {
 350             this.version = RUNTIME_VERSION;
 351             version = Release.RUNTIME;
 352         } else {
 353             this.version = version.value();
 354         }
 355         this.runtimeVersioned = version == Release.RUNTIME;
 356 
 357         assert runtimeVersionExists();
 358     }
 359 
 360     private boolean runtimeVersionExists() {
 361         int version = jdk.Version.current().major();
 362         try {
 363             Release.valueOf(version);
 364             return true;
 365         } catch (IllegalArgumentException x) {
 366             System.err.println("No JarFile.Release object for release " + version);
 367             return false;
 368         }
 369     }
 370 
 371     /**
 372      * Returns the maximum version used when searching for versioned entries.
 373      *
 374      * @return the maximum version, or {@code Release.BASE} if this jar file is
 375      *         processed as if it is an unversioned jar file or is not a
 376      *         multi-release jar file
 377      * @since 9
 378      */
 379     public final Release getVersion() {
 380         if (isMultiRelease()) {
 381             return runtimeVersioned ? Release.RUNTIME : Release.valueOf(version);
 382         } else {
 383             return Release.BASE;
 384         }
 385     }
 386 
 387     /**
 388      * Indicates whether or not this jar file is a multi-release jar file.
 389      *
 390      * @return true if this JarFile is a multi-release jar file
 391      * @since 9
 392      */
 393     public final boolean isMultiRelease() {
 394         if (isMultiRelease) {
 395             return true;

















 396         }
 397         if (MULTI_RELEASE_ENABLED && version != BASE_VERSION) {
 398             try {
 399                 checkForSpecialAttributes();
 400             } catch (IOException io) {
 401                 isMultiRelease = false;
 402             }



 403         }
 404         return isMultiRelease;
 405     }



 406 
 407     /**
 408      * Returns the jar file manifest, or {@code null} if none.
 409      *
 410      * @return the jar file manifest, or {@code null} if none
 411      *
 412      * @throws IllegalStateException
 413      *         may be thrown if the jar file has been closed
 414      * @throws IOException  if an I/O error has occurred
 415      */
 416     public Manifest getManifest() throws IOException {
 417         return getManifestFromReference();
 418     }
 419 
 420     private Manifest getManifestFromReference() throws IOException {
 421         Manifest man = manRef != null ? manRef.get() : null;
 422 
 423         if (man == null) {
 424 
 425             JarEntry manEntry = getManEntry();
 426 
 427             // If found then load the manifest
 428             if (manEntry != null) {
 429                 if (verify) {
 430                     byte[] b = getBytes(manEntry);
 431                     man = new Manifest(new ByteArrayInputStream(b));
 432                     if (!jvInitialized) {
 433                         jv = new JarVerifier(b);
 434                     }
 435                 } else {
 436                     man = new Manifest(super.getInputStream(manEntry));
 437                 }
 438                 manRef = new SoftReference<>(man);
 439             }
 440         }
 441         return man;
 442     }
 443 
 444     private String[] getMetaInfEntryNames() {
 445         return jdk.internal.misc.SharedSecrets.getJavaUtilZipFileAccess()
 446                                               .getMetaInfEntryNames((ZipFile)this);
 447     }
 448 
 449     /**
 450      * Returns the {@code JarEntry} for the given base entry name or
 451      * {@code null} if not found.
 452      *
 453      * <p>If this {@code JarFile} is a multi-release jar file and is configured
 454      * to be processed as such, then a search is performed to find and return
 455      * a {@code JarEntry} that is the latest versioned entry associated with the
 456      * given entry name.  The returned {@code JarEntry} is the versioned entry
 457      * corresponding to the given base entry name prefixed with the string
 458      * {@code "META-INF/versions/{n}/"}, for the largest value of {@code n} for
 459      * which an entry exists.  If such a versioned entry does not exist, then
 460      * the {@code JarEntry} for the base entry is returned, otherwise
 461      * {@code null} is returned if no entries are found.  The initial value for
 462      * the version {@code n} is the maximum version as returned by the method
 463      * {@link JarFile#getVersion()}.
 464      *
 465      * @param name the jar file entry name
 466      * @return the {@code JarEntry} for the given entry name, or
 467      *         the versioned entry name, or {@code null} if not found
 468      *
 469      * @throws IllegalStateException
 470      *         may be thrown if the jar file has been closed
 471      *
 472      * @see java.util.jar.JarEntry
 473      *
 474      * @implSpec
 475      * <div class="block">
 476      * This implementation invokes {@link JarFile#getEntry(String)}.
 477      * </div>
 478      */
 479     public JarEntry getJarEntry(String name) {
 480         return (JarEntry)getEntry(name);
 481     }
 482 
 483     /**
 484      * Returns the {@code ZipEntry} for the given base entry name or
 485      * {@code null} if not found.
 486      *
 487      * <p>If this {@code JarFile} is a multi-release jar file and is configured
 488      * to be processed as such, then a search is performed to find and return
 489      * a {@code ZipEntry} that is the latest versioned entry associated with the
 490      * given entry name.  The returned {@code ZipEntry} is the versioned entry
 491      * corresponding to the given base entry name prefixed with the string
 492      * {@code "META-INF/versions/{n}/"}, for the largest value of {@code n} for
 493      * which an entry exists.  If such a versioned entry does not exist, then
 494      * the {@code ZipEntry} for the base entry is returned, otherwise
 495      * {@code null} is returned if no entries are found.  The initial value for
 496      * the version {@code n} is the maximum version as returned by the method
 497      * {@link JarFile#getVersion()}.
 498      *
 499      * @param name the jar file entry name
 500      * @return the {@code ZipEntry} for the given entry name or
 501      *         the versioned entry name or {@code null} if not found
 502      *
 503      * @throws IllegalStateException
 504      *         may be thrown if the jar file has been closed
 505      *
 506      * @see java.util.zip.ZipEntry
 507      *
 508      * @implSpec
 509      * <div class="block">
 510      * This implementation may return a versioned entry for the requested name
 511      * even if there is not a corresponding base entry.  This can occur
 512      * if there is a private or package-private versioned entry that matches.
 513      * If a subclass overrides this method, assure that the override method
 514      * invokes {@code super.getEntry(name)} to obtain all versioned entries.
 515      * </div>
 516      */
 517     public ZipEntry getEntry(String name) {
 518         ZipEntry ze = super.getEntry(name);
 519         if (ze != null) {
 520             return new JarFileEntry(ze);
 521         }
 522         // no matching base entry, but maybe there is a versioned entry,
 523         // like a new private class
 524         if (isMultiRelease()) {
 525             ze = new ZipEntry(name);
 526             ZipEntry vze = getVersionedEntry(ze);
 527             if (ze != vze) {
 528                 return new JarFileEntry(name, vze);
 529             }
 530         }
 531         return null;
 532     }
 533 
 534     private class JarEntryIterator implements Enumeration<JarEntry>,
 535             Iterator<JarEntry>
 536     {
 537         final Enumeration<? extends ZipEntry> e = JarFile.super.entries();
 538         ZipEntry ze;
 539 
 540         public boolean hasNext() {
 541             if (notVersioned) {
 542                 return e.hasMoreElements();
 543             }
 544             if (ze != null) {
 545                 return true;
 546             }
 547             return findNext();
 548         }
 549 
 550         private boolean findNext() {
 551             while (e.hasMoreElements()) {
 552                 ZipEntry ze2 = e.nextElement();
 553                 if (!ze2.getName().startsWith(META_INF_VERSIONS)) {
 554                     ze = ze2;
 555                     return true;
 556                 }
 557             }
 558             return false;
 559         }
 560 
 561         public JarEntry next() {
 562             ZipEntry ze2;
 563 
 564             if (notVersioned) {
 565                 ze2 = e.nextElement();
 566                 return new JarFileEntry(ze2.getName(), ze2);
 567             }
 568             if (ze != null || findNext()) {
 569                 ze2 = ze;
 570                 ze = null;
 571                 return new JarFileEntry(ze2);
 572             }
 573             throw new NoSuchElementException();
 574         }
 575 
 576         public boolean hasMoreElements() {
 577             return hasNext();
 578         }
 579 
 580         public JarEntry nextElement() {
 581             return next();
 582         }
 583 
 584         public Iterator<JarEntry> asIterator() {
 585             return this;
 586         }
 587     }
 588 
 589     /**
 590      * Returns an enumeration of the jar file entries.  The set of entries
 591      * returned depends on whether or not the jar file is a multi-release jar
 592      * file, and on the constructor used to create the {@code JarFile}.  If the
 593      * jar file is not a multi-release jar file, all entries are returned,
 594      * regardless of how the {@code JarFile} is created.  If the constructor
 595      * does not take a {@code Release} argument, all entries are returned.
 596      * If the jar file is a multi-release jar file and the constructor takes a
 597      * {@code Release} argument, then the set of entries returned is equivalent
 598      * to the set of entries that would be returned if the set was built by
 599      * invoking {@link JarFile#getEntry(String)} or
 600      * {@link JarFile#getJarEntry(String)} with the name of each base entry in
 601      * the jar file.  A base entry is an entry whose path name does not start
 602      * with "META-INF/versions/".
 603      *
 604      * @return an enumeration of the jar file entries
 605      * @throws IllegalStateException
 606      *         may be thrown if the jar file has been closed
 607      */
 608     public Enumeration<JarEntry> entries() {
 609         return new JarEntryIterator();
 610     }
 611 
 612     /**
 613      * Returns an ordered {@code Stream} over all the jar file entries.
 614      * Entries appear in the {@code Stream} in the order they appear in
 615      * the central directory of the jar file.  The set of entries
 616      * returned depends on whether or not the jar file is a multi-release jar
 617      * file, and on the constructor used to create the {@code JarFile}.  If the
 618      * jar file is not a multi-release jar file, all entries are returned,
 619      * regardless of how the {@code JarFile} is created.  If the constructor
 620      * does not take a {@code Release} argument, all entries are returned.
 621      * If the jar file is a multi-release jar file and the constructor takes a
 622      * {@code Release} argument, then the set of entries returned is equivalent
 623      * to the set of entries that would be returned if the set was built by
 624      * invoking {@link JarFile#getEntry(String)} or
 625      * {@link JarFile#getJarEntry(String)} with the name of each base entry in
 626      * the jar file.  A base entry is an entry whose path name does not start
 627      * with "META-INF/versions/".
 628      * @return an ordered {@code Stream} of entries in this jar file
 629      * @throws IllegalStateException if the jar file has been closed
 630      * @since 1.8
 631      */
 632     public Stream<JarEntry> stream() {
 633         return StreamSupport.stream(Spliterators.spliterator(
 634                 new JarEntryIterator(), size(),
 635                 Spliterator.ORDERED | Spliterator.DISTINCT |
 636                         Spliterator.IMMUTABLE | Spliterator.NONNULL), false);
 637     }
 638 
 639     private ZipEntry searchForVersionedEntry(final int version, String name) {
 640         ZipEntry vze = null;
 641         String sname = "/" + name;
 642         int i = version;
 643         while (i > BASE_VERSION) {
 644             vze = super.getEntry(META_INF_VERSIONS + i + sname);
 645             if (vze != null) break;
 646             i--;
 647         }
 648         return vze;
 649     }
 650 
 651     private ZipEntry getVersionedEntry(ZipEntry ze) {
 652         ZipEntry vze = null;
 653         if (version > BASE_VERSION && !ze.isDirectory()) {
 654             String name = ze.getName();
 655             if (!name.startsWith(META_INF)) {
 656                 vze = searchForVersionedEntry(version, name);
 657             }
 658         }
 659         return vze == null ? ze : vze;
 660     }
 661 
 662     private class JarFileEntry extends JarEntry {
 663         final private String name;
 664 
 665         JarFileEntry(ZipEntry ze) {
 666             super(isMultiRelease() ? getVersionedEntry(ze) : ze);
 667             this.name = ze.getName();
 668         }
 669         JarFileEntry(String name, ZipEntry vze) {
 670             super(vze);
 671             this.name = name;
 672         }
 673         public Attributes getAttributes() throws IOException {
 674             Manifest man = JarFile.this.getManifest();
 675             if (man != null) {
 676                 return man.getAttributes(super.getName());
 677             } else {
 678                 return null;
 679             }
 680         }
 681         public Certificate[] getCertificates() {
 682             try {
 683                 maybeInstantiateVerifier();
 684             } catch (IOException e) {
 685                 throw new RuntimeException(e);
 686             }
 687             if (certs == null && jv != null) {
 688                 certs = jv.getCerts(JarFile.this, reifiedEntry());
 689             }
 690             return certs == null ? null : certs.clone();
 691         }
 692         public CodeSigner[] getCodeSigners() {
 693             try {
 694                 maybeInstantiateVerifier();
 695             } catch (IOException e) {
 696                 throw new RuntimeException(e);
 697             }
 698             if (signers == null && jv != null) {
 699                 signers = jv.getCodeSigners(JarFile.this, reifiedEntry());
 700             }
 701             return signers == null ? null : signers.clone();
 702         }
 703         JarFileEntry reifiedEntry() {
 704             if (isMultiRelease()) {
 705                 String entryName = super.getName();
 706                 return entryName.equals(this.name) ? this : new JarFileEntry(entryName, this);
 707             }
 708             return this;
 709         }
 710 
 711         @Override
 712         public String getName() {
 713             return name;
 714         }
 715     }
 716 
 717     /*
 718      * Ensures that the JarVerifier has been created if one is
 719      * necessary (i.e., the jar appears to be signed.) This is done as
 720      * a quick check to avoid processing of the manifest for unsigned
 721      * jars.
 722      */
 723     private void maybeInstantiateVerifier() throws IOException {
 724         if (jv != null) {
 725             return;
 726         }
 727 
 728         if (verify) {
 729             String[] names = getMetaInfEntryNames();
 730             if (names != null) {
 731                 for (String nameLower : names) {
 732                     String name = nameLower.toUpperCase(Locale.ENGLISH);
 733                     if (name.endsWith(".DSA") ||
 734                         name.endsWith(".RSA") ||
 735                         name.endsWith(".EC") ||
 736                         name.endsWith(".SF")) {
 737                         // Assume since we found a signature-related file
 738                         // that the jar is signed and that we therefore
 739                         // need a JarVerifier and Manifest
 740                         getManifest();
 741                         return;
 742                     }
 743                 }
 744             }
 745             // No signature-related files; don't instantiate a
 746             // verifier
 747             verify = false;
 748         }
 749     }
 750 
 751 
 752     /*
 753      * Initializes the verifier object by reading all the manifest
 754      * entries and passing them to the verifier.
 755      */
 756     private void initializeVerifier() {
 757         ManifestEntryVerifier mev = null;
 758 
 759         // Verify "META-INF/" entries...
 760         try {
 761             String[] names = getMetaInfEntryNames();
 762             if (names != null) {
 763                 for (String name : names) {
 764                     String uname = name.toUpperCase(Locale.ENGLISH);
 765                     if (MANIFEST_NAME.equals(uname)
 766                             || SignatureFileVerifier.isBlockOrSF(uname)) {
 767                         JarEntry e = getJarEntry(name);
 768                         if (e == null) {
 769                             throw new JarException("corrupted jar file");
 770                         }
 771                         if (mev == null) {
 772                             mev = new ManifestEntryVerifier
 773                                 (getManifestFromReference());
 774                         }
 775                         byte[] b = getBytes(e);
 776                         if (b != null && b.length > 0) {
 777                             jv.beginEntry(e, mev);
 778                             jv.update(b.length, b, 0, b.length, mev);
 779                             jv.update(-1, null, 0, 0, mev);
 780                         }
 781                     }
 782                 }
 783             }
 784         } catch (IOException ex) {
 785             // if we had an error parsing any blocks, just
 786             // treat the jar file as being unsigned
 787             jv = null;
 788             verify = false;
 789             if (JarVerifier.debug != null) {
 790                 JarVerifier.debug.println("jarfile parsing error!");
 791                 ex.printStackTrace();
 792             }
 793         }
 794 
 795         // if after initializing the verifier we have nothing
 796         // signed, we null it out.
 797 
 798         if (jv != null) {
 799 
 800             jv.doneWithMeta();
 801             if (JarVerifier.debug != null) {
 802                 JarVerifier.debug.println("done with meta!");
 803             }
 804 
 805             if (jv.nothingToVerify()) {
 806                 if (JarVerifier.debug != null) {
 807                     JarVerifier.debug.println("nothing to verify!");
 808                 }
 809                 jv = null;
 810                 verify = false;
 811             }
 812         }
 813     }
 814 
 815     /*
 816      * Reads all the bytes for a given entry. Used to process the
 817      * META-INF files.
 818      */
 819     private byte[] getBytes(ZipEntry ze) throws IOException {
 820         try (InputStream is = super.getInputStream(ze)) {
 821             int len = (int)ze.getSize();
 822             int bytesRead;
 823             byte[] b;
 824             // trust specified entry sizes when reasonably small
 825             if (len != -1 && len <= 65535) {
 826                 b = new byte[len];
 827                 bytesRead = is.readNBytes(b, 0, len);
 828             } else {
 829                 b = is.readAllBytes();
 830                 bytesRead = b.length;
 831             }
 832             if (len != -1 && len != bytesRead) {
 833                 throw new EOFException("Expected:" + len + ", read:" + bytesRead);
 834             }
 835             return b;
 836         }
 837     }
 838 
 839     /**
 840      * Returns an input stream for reading the contents of the specified
 841      * zip file entry.
 842      * @param ze the zip file entry
 843      * @return an input stream for reading the contents of the specified
 844      *         zip file entry
 845      * @throws ZipException if a zip file format error has occurred
 846      * @throws IOException if an I/O error has occurred
 847      * @throws SecurityException if any of the jar file entries
 848      *         are incorrectly signed.
 849      * @throws IllegalStateException
 850      *         may be thrown if the jar file has been closed
 851      */
 852     public synchronized InputStream getInputStream(ZipEntry ze)
 853         throws IOException
 854     {
 855         maybeInstantiateVerifier();
 856         if (jv == null) {
 857             return super.getInputStream(ze);
 858         }
 859         if (!jvInitialized) {
 860             initializeVerifier();
 861             jvInitialized = true;
 862             // could be set to null after a call to
 863             // initializeVerifier if we have nothing to
 864             // verify
 865             if (jv == null)
 866                 return super.getInputStream(ze);
 867         }
 868 
 869         // wrap a verifier stream around the real stream
 870         return new JarVerifier.VerifierStream(
 871             getManifestFromReference(),
 872             verifiableEntry(ze),
 873             super.getInputStream(ze),
 874             jv);
 875     }
 876 
 877     private JarEntry verifiableEntry(ZipEntry ze) {
 878         if (ze instanceof JarFileEntry) {
 879             // assure the name and entry match for verification
 880             return ((JarFileEntry)ze).reifiedEntry();
 881         }
 882         ze = getJarEntry(ze.getName());
 883         if (ze instanceof JarFileEntry) {
 884             return ((JarFileEntry)ze).reifiedEntry();
 885         }
 886         return (JarEntry)ze;
 887     }
 888 
 889     // Statics for hand-coded Boyer-Moore search
 890     private static final byte[] CLASSPATH_CHARS =
 891             {'C','L','A','S','S','-','P','A','T','H', ':', ' '};
 892 
 893     // The bad character shift for "class-path:"
 894     private static final byte[] CLASSPATH_LASTOCC;
 895 
 896     private static final byte[] MULTIRELEASE_CHARS =
 897             {'M','U','L','T','I','-','R','E','L','E', 'A', 'S', 'E', ':', ' '};
 898 
 899     // The bad character shift for "multi-release: "
 900     private static final byte[] MULTIRELEASE_LASTOCC;
 901 
 902     static {
 903         CLASSPATH_LASTOCC = new byte[64];
 904         CLASSPATH_LASTOCC[(int)'C' - 32] = 1;
 905         CLASSPATH_LASTOCC[(int)'L' - 32] = 2;
 906         CLASSPATH_LASTOCC[(int)'S' - 32] = 5;
 907         CLASSPATH_LASTOCC[(int)'-' - 32] = 6;
 908         CLASSPATH_LASTOCC[(int)'P' - 32] = 7;
 909         CLASSPATH_LASTOCC[(int)'A' - 32] = 8;
 910         CLASSPATH_LASTOCC[(int)'T' - 32] = 9;
 911         CLASSPATH_LASTOCC[(int)'H' - 32] = 10;
 912         CLASSPATH_LASTOCC[(int)':' - 32] = 11;
 913         CLASSPATH_LASTOCC[(int)' ' - 32] = 12;
 914 
 915         MULTIRELEASE_LASTOCC = new byte[64];
 916         MULTIRELEASE_LASTOCC[(int)'M' - 32] = 1;
 917         MULTIRELEASE_LASTOCC[(int)'U' - 32] = 2;
 918         MULTIRELEASE_LASTOCC[(int)'T' - 32] = 4;
 919         MULTIRELEASE_LASTOCC[(int)'I' - 32] = 5;
 920         MULTIRELEASE_LASTOCC[(int)'-' - 32] = 6;
 921         MULTIRELEASE_LASTOCC[(int)'R' - 32] = 7;
 922         MULTIRELEASE_LASTOCC[(int)'L' - 32] = 9;
 923         MULTIRELEASE_LASTOCC[(int)'A' - 32] = 11;
 924         MULTIRELEASE_LASTOCC[(int)'S' - 32] = 12;
 925         MULTIRELEASE_LASTOCC[(int)'E' - 32] = 13;
 926         MULTIRELEASE_LASTOCC[(int)':' - 32] = 14;
 927         MULTIRELEASE_LASTOCC[(int)' ' - 32] = 15;
 928     }
 929 
 930     private JarEntry getManEntry() {
 931         if (manEntry == null) {
 932             // First look up manifest entry using standard name
 933             ZipEntry manEntry = super.getEntry(MANIFEST_NAME);
 934             if (manEntry == null) {
 935                 // If not found, then iterate through all the "META-INF/"
 936                 // entries to find a match.
 937                 String[] names = getMetaInfEntryNames();
 938                 if (names != null) {
 939                     for (String name : names) {
 940                         if (MANIFEST_NAME.equals(name.toUpperCase(Locale.ENGLISH))) {
 941                             manEntry = super.getEntry(name);
 942                             break;
 943                         }
 944                     }
 945                 }
 946             }
 947             this.manEntry = (manEntry == null)
 948                     ? null
 949                     : new JarFileEntry(manEntry.getName(), manEntry);
 950         }
 951         return manEntry;
 952     }
 953 
 954    /**
 955     * Returns {@code true} iff this JAR file has a manifest with the
 956     * Class-Path attribute
 957     */
 958     boolean hasClassPathAttribute() throws IOException {
 959         checkForSpecialAttributes();
 960         return hasClassPathAttribute;
 961     }
 962 
 963     /**
 964      * Returns true if the pattern {@code src} is found in {@code b}.
 965      * The {@code lastOcc} array is the precomputed bad character shifts.
 966      * Since there are no repeated substring in our search strings,
 967      * the good suffix shifts can be replaced with a comparison.
 968      */
 969     private boolean match(byte[] src, byte[] b, byte[] lastOcc) {
 970         int len = src.length;
 971         int last = b.length - len;
 972         int i = 0;
 973         next:
 974         while (i <= last) {
 975             for (int j = (len - 1); j >= 0; j--) {
 976                 byte c = b[i + j];
 977                 if (c >= ' ' && c <= 'z') {
 978                     if (c >= 'a') c -= 32; // Canonicalize
 979 
 980                     if (c != src[j]) {
 981                         // no match
 982                         int goodShift = (j < len - 1) ? len : 1;
 983                         int badShift = lastOcc[c - 32];
 984                         i += Math.max(j + 1 - badShift, goodShift);
 985                         continue next;
 986                     }
 987                 } else {
 988                     // no match, character not valid for name
 989                     i += len;
 990                     continue next;
 991                 }
 992             }
 993             return true;
 994         }
 995         return false;
 996     }
 997 
 998     /**
 999      * On first invocation, check if the JAR file has the Class-Path
1000      * and the Multi-Release attribute. A no-op on subsequent calls.
1001      */
1002     private void checkForSpecialAttributes() throws IOException {
1003         if (hasCheckedSpecialAttributes) {
1004             return;
1005         }
1006         synchronized (this) {
1007             if (hasCheckedSpecialAttributes) {
1008                 return;
1009             }
1010             JarEntry manEntry = getManEntry();
1011             if (manEntry != null) {
1012                 byte[] b = getBytes(manEntry);
1013                 hasClassPathAttribute = match(CLASSPATH_CHARS, b,
1014                         CLASSPATH_LASTOCC);
1015                 // is this a multi-release jar file
1016                 if (MULTI_RELEASE_ENABLED && version != BASE_VERSION) {
1017                     isMultiRelease = match(MULTIRELEASE_CHARS, b,
1018                             MULTIRELEASE_LASTOCC);
1019                 }
1020             }
1021             hasCheckedSpecialAttributes = true;
1022         }
1023     }
1024 
1025     private synchronized void ensureInitialization() {
1026         try {
1027             maybeInstantiateVerifier();
1028         } catch (IOException e) {
1029             throw new RuntimeException(e);
1030         }
1031         if (jv != null && !jvInitialized) {
1032             initializeVerifier();
1033             jvInitialized = true;
1034         }
1035     }
1036 
1037     JarEntry newEntry(ZipEntry ze) {
1038         return new JarFileEntry(ze);
1039     }
1040 
1041     Enumeration<String> entryNames(CodeSource[] cs) {
1042         ensureInitialization();
1043         if (jv != null) {
1044             return jv.entryNames(this, cs);
1045         }
1046 
1047         /*
1048          * JAR file has no signed content. Is there a non-signing
1049          * code source?
1050          */
1051         boolean includeUnsigned = false;
1052         for (CodeSource c : cs) {
1053             if (c.getCodeSigners() == null) {
1054                 includeUnsigned = true;
1055                 break;
1056             }
1057         }
1058         if (includeUnsigned) {
1059             return unsignedEntryNames();
1060         } else {
1061             return new Enumeration<>() {
1062 
1063                 public boolean hasMoreElements() {
1064                     return false;
1065                 }
1066 
1067                 public String nextElement() {
1068                     throw new NoSuchElementException();
1069                 }
1070             };
1071         }
1072     }
1073 
1074     /**
1075      * Returns an enumeration of the zip file entries
1076      * excluding internal JAR mechanism entries and including
1077      * signed entries missing from the ZIP directory.
1078      */
1079     Enumeration<JarEntry> entries2() {
1080         ensureInitialization();
1081         if (jv != null) {
1082             return jv.entries2(this, super.entries());
1083         }
1084 
1085         // screen out entries which are never signed
1086         final Enumeration<? extends ZipEntry> enum_ = super.entries();
1087         return new Enumeration<>() {
1088 
1089             ZipEntry entry;
1090 
1091             public boolean hasMoreElements() {
1092                 if (entry != null) {
1093                     return true;
1094                 }
1095                 while (enum_.hasMoreElements()) {
1096                     ZipEntry ze = enum_.nextElement();
1097                     if (JarVerifier.isSigningRelated(ze.getName())) {
1098                         continue;
1099                     }
1100                     entry = ze;
1101                     return true;
1102                 }
1103                 return false;
1104             }
1105 
1106             public JarFileEntry nextElement() {
1107                 if (hasMoreElements()) {
1108                     ZipEntry ze = entry;
1109                     entry = null;
1110                     return new JarFileEntry(ze);
1111                 }
1112                 throw new NoSuchElementException();
1113             }
1114         };
1115     }
1116 
1117     CodeSource[] getCodeSources(URL url) {
1118         ensureInitialization();
1119         if (jv != null) {
1120             return jv.getCodeSources(this, url);
1121         }
1122 
1123         /*
1124          * JAR file has no signed content. Is there a non-signing
1125          * code source?
1126          */
1127         Enumeration<String> unsigned = unsignedEntryNames();
1128         if (unsigned.hasMoreElements()) {
1129             return new CodeSource[]{JarVerifier.getUnsignedCS(url)};
1130         } else {
1131             return null;
1132         }
1133     }
1134 
1135     private Enumeration<String> unsignedEntryNames() {
1136         final Enumeration<JarEntry> entries = entries();
1137         return new Enumeration<>() {
1138 
1139             String name;
1140 
1141             /*
1142              * Grab entries from ZIP directory but screen out
1143              * metadata.
1144              */
1145             public boolean hasMoreElements() {
1146                 if (name != null) {
1147                     return true;
1148                 }
1149                 while (entries.hasMoreElements()) {
1150                     String value;
1151                     ZipEntry e = entries.nextElement();
1152                     value = e.getName();
1153                     if (e.isDirectory() || JarVerifier.isSigningRelated(value)) {
1154                         continue;
1155                     }
1156                     name = value;
1157                     return true;
1158                 }
1159                 return false;
1160             }
1161 
1162             public String nextElement() {
1163                 if (hasMoreElements()) {
1164                     String value = name;
1165                     name = null;
1166                     return value;
1167                 }
1168                 throw new NoSuchElementException();
1169             }
1170         };
1171     }
1172 
1173     CodeSource getCodeSource(URL url, String name) {
1174         ensureInitialization();
1175         if (jv != null) {
1176             if (jv.eagerValidation) {
1177                 CodeSource cs = null;
1178                 JarEntry je = getJarEntry(name);
1179                 if (je != null) {
1180                     cs = jv.getCodeSource(url, this, je);
1181                 } else {
1182                     cs = jv.getCodeSource(url, name);
1183                 }
1184                 return cs;
1185             } else {
1186                 return jv.getCodeSource(url, name);
1187             }
1188         }
1189 
1190         return JarVerifier.getUnsignedCS(url);
1191     }
1192 
1193     void setEagerValidation(boolean eager) {
1194         try {
1195             maybeInstantiateVerifier();
1196         } catch (IOException e) {
1197             throw new RuntimeException(e);
1198         }
1199         if (jv != null) {
1200             jv.setEagerValidation(eager);
1201         }
1202     }
1203 
1204     List<Object> getManifestDigests() {
1205         ensureInitialization();
1206         if (jv != null) {
1207             return jv.getManifestDigests();
1208         }
1209         return new ArrayList<>();
1210     }
1211 }
--- EOF ---