rev 13123 : 8132734: JDK 9 runtime changes to support multi-release jar files
Summary: JEP 238 Multi-Release JAR Files runtime support
Contributed-by: steve.drach@oracle.com
rev 12882 : 8139706: JarFile.getBytes could use InputStream.readNBytes
Reviewed-by: sherman, chegar, alanb
rev 12860 : 8138978: Examine usages of sun.misc.IOUtils
Reviewed-by: alanb, mullan, psandoz, rriggs, weijun
rev 12807 : 8137056: Move SharedSecrets and interface friends out of sun.misc
Reviewed-by: alanb, mchung, psandoz, rriggs
rev 12522 : 8133115: docs: replace <tt> tags (obsolete in html5) for java.util.logging, java.util.prefs, java.util.zip, java.util.jar
Reviewed-by: lancea
rev 12298 : 8081678: Add Stream returning methods to classes where there currently exist only Enumeration returning methods
Reviewed-by: lancea, alanb, chegar, dfuchs, mullan, smarks
rev 12022 : 8064736: Part of java.util.jar.JarFile spec looks confusing with references to Zip
Summary: update the api doc for entries()/stream() accordingly
Reviewed-by: alanb
rev 11804 : 8078467: Update core libraries to use diamond with anonymous classes
Reviewed-by: mchung, alanb
rev 11051 : 8049367: Modular Run-Time Images
Reviewed-by: chegar, dfuchs, ihse, joehw, mullan, psandoz, wetmore
Contributed-by: alan.bateman@oracle.com, alex.buckley@oracle.com, bradford.wetmore@oracle.com, chris.hegarty@oracle.com, erik.joelsson@oracle.com, james.laskey@oracle.com, jonathan.gibbons@oracle.com, karen.kinnear@oracle.com, magnus.ihse.bursie@oracle.com, mandy.chung@oracle.com, mark.reinhold@oracle.com, paul.sandoz@oracle.com, sundararajan.athijegannathan@oracle.com
rev 10469 : 8054834: Modular Source Code
Reviewed-by: alanb, chegar, ihse, mduigou
Contributed-by: alan.bateman@oracle.com, alex.buckley@oracle.com, chris.hegarty@oracle.com, erik.joelsson@oracle.com, jonathan.gibbons@oracle.com, karen.kinnear@oracle.com, magnus.ihse.bursie@oracle.com, mandy.chung@oracle.com, mark.reinhold@oracle.com, paul.sandoz@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.security.PrivilegedAction;
  32 import java.util.*;
  33 import java.util.stream.Stream;
  34 import java.util.stream.StreamSupport;
  35 import java.util.zip.*;
  36 import java.security.CodeSigner;
  37 import java.security.cert.Certificate;
  38 import java.security.AccessController;
  39 import java.security.CodeSource;
  40 import jdk.internal.misc.SharedSecrets;

  41 import sun.security.util.ManifestEntryVerifier;
  42 import sun.security.util.SignatureFileVerifier;
  43 
  44 import static java.util.jar.Attributes.Name.MULTI_RELEASE;
  45 
  46 /**
  47  * The {@code JarFile} class is used to read the contents of a jar file
  48  * from any file that can be opened with {@code java.io.RandomAccessFile}.
  49  * It extends the class {@code java.util.zip.ZipFile} with support
  50  * for reading an optional {@code Manifest} entry, and support for
  51  * processing multi-release jar files.  The {@code Manifest} can be used
  52  * to specify meta-information about the jar file and its entries.
  53  *
  54  * <p>A multi-release jar file is a jar file that contains a manifest with a
  55  * main attribute named "Multi-Release", and also contains a directory
  56  * "META-INF/versions" with subdirectories that contain versioned entries
  57  * segregated by the major version of Java platform releases, starting with
  58  * release 9.  A versioned entry, with a version {@code n}, {@code 8 < n}, in
  59  * the "META-INF/versions/{n}" directory overrides the unversioned root entry
  60  * 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 root entry.
  66  * The {@code JarFile} may be configured to process a
  67  * multi-release jar file by setting a maximum version used when searching for
  68  * versioned entries (see methods {@link JarFile#setVersioned(int)} and
  69  * {@link JarFile#setRuntimeVersioned()}).  When so configured, an entry name
  70  * can correspond with at most one root entry and zero or more versioned
  71  * entries. A search is required to associate the entry name with the latest
  72  * versioned entry whose version is less than or equal to the maximum version
  73  * (see {@link #getEntry(String)}).
  74  *
  75  * <p>Class loaders that utilize {@code JarFile} to load classes from the
  76  * contents of {@code JarFile} entries should, after construction of a
  77  * {@code JarFile}, call {@link JarFile#setRuntimeVersioned()} prior to any
  78  * subsequent operations.  This assures that classes compatible with the major
  79  * version of the running JVM are loaded from multi-release jar files.
  80  *
  81  * <p>If the verify flag is on when opening a signed jar file, the content of
  82  * the file is verified against its signature embedded inside the file. Please
  83  * note that the verification process does not include validating the signer's
  84  * certificate. A caller should inspect the return value of
  85  * {@link JarEntry#getCodeSigners()} to further determine if the signature
  86  * can be trusted.
  87  *
  88  * <p> Unless otherwise noted, passing a {@code null} argument to a constructor
  89  * or method in this class will cause a {@link NullPointerException} to be
  90  * thrown.
  91  *
  92  * @implNote
  93  * <div class="block">
  94  * If the API can not be used to configure a {@code JarFile} (e.g. to override
  95  * the configuration of a compiled application or library), two {@code System}
  96  * properties are available.
  97  * <ul>
  98  * <li>
  99  * {@code jdk.util.jar.version} can be assigned a value that is the
 100  * {@code String} representation of a non-negative integer
 101  * {@code <= Version.current().major()}.  The value is used to set the effective
 102  * runtime version to something other than the default value obtained by
 103  * evaluating {@code Version.current().major()}. The effective runtime version
 104  * is the version that the method {@link JarFile#setRuntimeVersioned()} uses.
 105  * This makes the following two method invocations equivalent:
 106  * {@code jf.setRuntimeVersioned()} and {@code jf.setVersioned(n)} where
 107  * {@code jf} is a reference to a {@code JarFile} and {@code n} is the
 108  * value assigned to the {@code jdk.util.jar.version} property.
 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> and the
 118  * method {@link JarFile#setVersioned(int)} does nothing.  The value
 119  * <em>force</em> causes the {@code JarFile} to be initialized to runtime
 120  * versioning after construction.  It effectively does the same as this code:
 121  * {@code (new JarFile(param)).setRuntimeVersioned()}.
 122  * </li>
 123  * </ul>
 124  * </div>
 125  *
 126  * @author  David Connelly
 127  * @see     Manifest
 128  * @see     java.util.zip.ZipFile
 129  * @see     java.util.jar.JarEntry
 130  * @since   1.2
 131  */
 132 public
 133 class JarFile extends ZipFile {
 134     private final static int BASE_VERSION;
 135     private final static int RUNTIME_VERSION;
 136     private final static boolean MULTI_RELEASE_ENABLED;
 137     private final static boolean MULTI_RELEASE_FORCED;
 138     private volatile int version;
 139     private SoftReference<Manifest> manRef;
 140     private JarEntry manEntry;
 141     private JarVerifier jv;
 142     private boolean jvInitialized;
 143     private boolean verify;
 144 
 145     // indicates if Class-Path attribute present (only valid if hasCheckedSpecialAttributes true)
 146     private boolean hasClassPathAttribute;
 147     // true if manifest checked for special attributes
 148     private volatile boolean hasCheckedSpecialAttributes;
 149     // configuration lock for multi-release jars, prevent version changes after reading an entry
 150     private boolean configured;
 151 
 152     /*
 153      * Temporary place holder until java.util.Version (JEP 223) is integrated.
 154      */
 155     private static class Version {
 156         private static Version instance;
 157 
 158         private Version() {
 159             super();
 160         }
 161 
 162         public static Version current() {
 163             if (instance == null) {
 164                 instance = new Version();
 165             }
 166             return instance;
 167         }
 168 
 169         public int major() {
 170             return sun.misc.Version.jdkMinorVersion();
 171         }
 172     }
 173 

 174     static {
 175         // Set up JavaUtilJarAccess in SharedSecrets
 176         SharedSecrets.setJavaUtilJarAccess(new JavaUtilJarAccessImpl());
 177 
 178         BASE_VERSION = 8;  // current JDK implementation version minus 1
 179         RUNTIME_VERSION = AccessController.doPrivileged(
 180                 new PrivilegedAction<Integer>() {
 181                     public Integer run() {
 182                         Integer v = Version.current().major();
 183                         Integer i = Integer.getInteger("jdk.util.jar.version", v);
 184                         i = i < 0 ? 0 : i;
 185                         return i > v ? v : i;
 186                     }
 187                 }
 188         );
 189         String multi_release = AccessController.doPrivileged(
 190                 new PrivilegedAction<String>() {
 191                     public String run() {
 192                         return System.getProperty("jdk.util.jar.enableMultiRelease", "true");
 193                     }
 194                 }
 195         );
 196         switch (multi_release) {
 197             case "true":
 198             default:
 199                 MULTI_RELEASE_ENABLED = true;
 200                 MULTI_RELEASE_FORCED = false;
 201                 break;
 202             case "false":
 203                 MULTI_RELEASE_ENABLED = false;
 204                 MULTI_RELEASE_FORCED = false;
 205                 break;
 206             case "force":
 207                 MULTI_RELEASE_ENABLED = true;
 208                 MULTI_RELEASE_FORCED = true;
 209                 break;
 210         }
 211     }
 212 
 213     private static final String META_INF = "META-INF/";
 214 
 215     private static final String META_INF_VERSIONS = META_INF + "versions/";
 216 
 217     /**
 218      * The JAR manifest file name.
 219      */
 220     public static final String MANIFEST_NAME = META_INF + "MANIFEST.MF";
 221 
 222     /**
 223      * Creates a new {@code JarFile} to read from the specified
 224      * file {@code name}. The {@code JarFile} will be verified if
 225      * it is signed.
 226      * @param name the name of the jar file to be opened for reading
 227      * @throws IOException if an I/O error has occurred
 228      * @throws SecurityException if access to the file is denied
 229      *         by the SecurityManager
 230      */
 231     public JarFile(String name) throws IOException {
 232         this(new File(name), true, ZipFile.OPEN_READ);
 233     }
 234 
 235     /**
 236      * Creates a new {@code JarFile} to read from the specified
 237      * file {@code name}.
 238      * @param name the name of the jar file to be opened for reading
 239      * @param verify whether or not to verify the jar file if
 240      * it is signed.
 241      * @throws IOException if an I/O error has occurred
 242      * @throws SecurityException if access to the file is denied
 243      *         by the SecurityManager
 244      */
 245     public JarFile(String name, boolean verify) throws IOException {
 246         this(new File(name), verify, ZipFile.OPEN_READ);
 247     }
 248 
 249     /**
 250      * Creates a new {@code JarFile} to read from the specified
 251      * {@code File} object. The {@code JarFile} will be verified if
 252      * it is signed.
 253      * @param file 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(File file) throws IOException {
 259         this(file, true, ZipFile.OPEN_READ);
 260     }
 261 
 262 
 263     /**
 264      * Creates a new {@code JarFile} to read from the specified
 265      * {@code File} object.
 266      * @param file the jar file to be opened for reading
 267      * @param verify whether or not to verify the jar file if
 268      * it is signed.
 269      * @throws IOException if an I/O error has occurred
 270      * @throws SecurityException if access to the file is denied
 271      *         by the SecurityManager.
 272      */
 273     public JarFile(File file, boolean verify) throws IOException {
 274         this(file, verify, ZipFile.OPEN_READ);
 275     }
 276 
 277 
 278     /**
 279      * Creates a new {@code JarFile} to read from the specified
 280      * {@code File} object in the specified mode.  The mode argument
 281      * must be either {@code OPEN_READ} or {@code OPEN_READ | OPEN_DELETE}.
 282      *
 283      * @param file the jar file to be opened for reading
 284      * @param verify whether or not to verify the jar file if
 285      * it is signed.
 286      * @param mode the mode in which the file is to be opened
 287      * @throws IOException if an I/O error has occurred
 288      * @throws IllegalArgumentException
 289      *         if the {@code mode} argument is invalid
 290      * @throws SecurityException if access to the file is denied
 291      *         by the SecurityManager
 292      * @since 1.3
 293      */
 294     public JarFile(File file, boolean verify, int mode) throws IOException {
 295         super(file, mode);
 296         this.verify = verify;
 297         this.version = MULTI_RELEASE_FORCED ? RUNTIME_VERSION : BASE_VERSION;
 298     }
 299 
 300     /**
 301      * If this {@code JarFile} is a multi-release jar file, sets the maximum
 302      * version used when searching for versioned entries to the specified
 303      * version.  If the specified version is less than 9, then this
 304      * {@code JarFile} is configured to be processed as if it is an unversioned
 305      * jar file.
 306      *
 307      * <p>This method may be called one or more times after construction and
 308      * before the first operation that obtains an entry, after which the
 309      * {@code JarFile} configuration is considered immutable, and subsequent
 310      * calls to this method will result in an {@code IllegalStateException}.
 311      *
 312      * <p>This method has no effect if this {@code JarFile} is not a
 313      * multi-release jar file.
 314      *
 315      * @param version the maximum version
 316      *
 317      * @return this {@code JarFile} configured so that the search for versioned
 318      *         entries starts with the specified version
 319      *
 320      * @throws IllegalStateException if called after an entry has been obtained
 321      *
 322      * @see #getVersioned()
 323      * @see #setRuntimeVersioned()
 324      * @since 9
 325      **/
 326     public JarFile setVersioned(int version) {
 327         if (isMultiRelease()) {
 328             synchronized (this) {  // see getVersionedEntry
 329                 if (configured)
 330                     throw new IllegalStateException("multi-release configuration locked");
 331                 this.version = (version > BASE_VERSION) ? version : BASE_VERSION;
 332             }
 333         }
 334         return this;
 335     }
 336 
 337     /**
 338      * If this {@code JarFile} is a multi-release jar file, sets the maximum
 339      * version used when searching for versioned entries to the major version
 340      * of the running JVM.
 341      *
 342      * <p>This method may be called one or more times after construction and
 343      * before the first operation that obtains an entry, after which the
 344      * {@code JarFile} configuration is considered immutable, and subsequent
 345      * calls to this method will result in an {@code IllegalStateException}.
 346      *
 347      * <p>This method has no effect if this {@code JarFile} is not a
 348      * multi-release jar file.
 349      *
 350      * @apiNote
 351      * This method behaves the same as
 352      * {@code JarFile.setVersioned(Version.current().major())}.
 353      *
 354      * @return this {@code JarFile} configured so that the search for versioned
 355      *         entries starts with the major version of the running JVM
 356      *
 357      * @throws IllegalStateException if called after an entry has been obtained
 358      *
 359      * @see #getVersioned()
 360      * @see #setVersioned(int)
 361      * @since 9
 362      */
 363     public JarFile setRuntimeVersioned() {
 364         return setVersioned(RUNTIME_VERSION);
 365     }
 366 
 367     /**
 368      * Returns the maximum version used when searching for versioned entries.
 369      *
 370      * @return the maximum version, or {@code 0} if this jar file is
 371      *         processed as if it is an unversioned jar file or is not a
 372      *         multi-release jar file
 373      *
 374      * @see #setVersioned(int)
 375      * @see #setRuntimeVersioned()
 376      * @since 9
 377      */
 378     public int getVersioned() {
 379         if (!isMultiRelease()) {
 380             version = BASE_VERSION;  // guard against inappropriate use of MULTI_RELEASE_FORCED
 381         }
 382         return version <= BASE_VERSION ? 0 : version;
 383     }
 384 
 385     /**
 386      * Indicates whether or not this jar file is a multi-release jar file.
 387      *
 388      * @return true if this JarFile is a multi-release jar file
 389      * @since 9
 390      */
 391     public boolean isMultiRelease() {
 392         // do not call this code in a constructor because some subclasses use
 393         // lazy loading of manifest so it won't be available at construction time
 394         if (MULTI_RELEASE_ENABLED) {
 395             // Doubled-checked locking pattern
 396             Boolean result = isMultiRelease;
 397             if (result == null) {
 398                 synchronized (this) {
 399                     result = isMultiRelease;
 400                     if (result == null) {
 401                         Manifest man = null;
 402                         try {
 403                             man = getManifest();
 404                         } catch (IOException e) {
 405                             //Ignored, manifest cannot be read
 406                         }
 407                         isMultiRelease = result = (man != null)
 408                                 && man.getMainAttributes().containsKey(MULTI_RELEASE)
 409                                 ? Boolean.TRUE : Boolean.FALSE;
 410                     }
 411                 }
 412             }
 413             return result == Boolean.TRUE;
 414         } else {
 415             return false;
 416         }
 417     }
 418     // the following field, isMultiRelease, should only be used in the method
 419     // isMultiRelease(), like a static local
 420     private volatile Boolean isMultiRelease;    // is jar multi-release?
 421 
 422 
 423 
 424     /**
 425      * Returns the jar file manifest, or {@code null} if none.
 426      *
 427      * @return the jar file manifest, or {@code null} if none
 428      *
 429      * @throws IllegalStateException
 430      *         may be thrown if the jar file has been closed
 431      * @throws IOException  if an I/O error has occurred
 432      */
 433     public Manifest getManifest() throws IOException {
 434         return getManifestFromReference();
 435     }
 436 
 437     private Manifest getManifestFromReference() throws IOException {
 438         Manifest man = manRef != null ? manRef.get() : null;
 439 
 440         if (man == null) {
 441 
 442             JarEntry manEntry = getManEntry();
 443 
 444             // If found then load the manifest
 445             if (manEntry != null) {
 446                 if (verify) {
 447                     byte[] b = getBytes(manEntry);
 448                     man = new Manifest(new ByteArrayInputStream(b));
 449                     if (!jvInitialized) {
 450                         jv = new JarVerifier(b);
 451                     }
 452                 } else {
 453                     man = new Manifest(super.getInputStream(manEntry));
 454                 }
 455                 manRef = new SoftReference<>(man);
 456             }
 457         }
 458         return man;
 459     }
 460 
 461     private native String[] getMetaInfEntryNames();
 462 
 463     /**
 464      * Returns the {@code JarEntry} for the given root entry name or
 465      * {@code null} if not found.
 466      *
 467      * <p>If this {@code JarFile} is a multi-release jar file and is configured
 468      * to be processed as such, then a search is performed to find and return
 469      * a {@code JarEntry} that is the latest versioned entry associated with the
 470      * given entry name.  The returned {@code JarEntry} is the versioned entry
 471      * corresponding to the given root entry name prefixed with the string
 472      * {@code "META-INF/versions/{n}/"}, for the largest value of {@code n} for
 473      * which an entry exists.  If such a versioned entry does not exist, then
 474      * the {@code JarEntry} for the root entry is returned, otherwise
 475      * {@code null} is returned if no entries are found.  The initial value for
 476      * the version {@code n} is the maximum version as returned by the method
 477      * {@link JarFile#getVersioned()}.
 478      *
 479      * @param name the jar file entry name
 480      * @return the {@code JarEntry} for the given entry name, or
 481      *         the versioned entry name, or {@code null} if not found
 482      *
 483      * @throws IllegalStateException
 484      *         may be thrown if the jar file has been closed
 485      *
 486      * @see java.util.jar.JarEntry
 487      */
 488     public JarEntry getJarEntry(String name) {
 489         return (JarEntry)getEntry(name);
 490     }
 491 
 492     /**
 493      * Returns the {@code ZipEntry} for the given root entry name or
 494      * {@code null} if not found.
 495      *
 496      * <p>If this {@code JarFile} is a multi-release jar file and is configured
 497      * to be processed as such, then a search is performed to find and return
 498      * a {@code ZipEntry} that is the latest versioned entry associated with the
 499      * given entry name.  The returned {@code ZipEntry} is the versioned entry
 500      * corresponding to the given root entry name prefixed with the string
 501      * {@code "META-INF/versions/{n}/"}, for the largest value of {@code n} for
 502      * which an entry exists.  If such a versioned entry does not exist, then
 503      * the {@code ZipEntry} for the root entry is returned, otherwise
 504      * {@code null} is returned if no entries are found.  The initial value for
 505      * the version {@code n} is the maximum version as returned by the method
 506      * {@link JarFile#getVersioned()}.
 507      *
 508      * @param name the jar file entry name
 509      * @return the {@code ZipEntry} for the given entry name or
 510      *         the versioned entry name or {@code null} if not found
 511      *
 512      * @throws IllegalStateException
 513      *         may be thrown if the jar file has been closed
 514      *
 515      * @see java.util.zip.ZipEntry
 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, like a new private class
 523         if (isMultiRelease()) {
 524             ze = new ZipEntry(name);
 525             ZipEntry vze = getVersionedEntry(ze);
 526             if (ze != vze) {
 527                 return new JarFileEntry(name, vze);
 528             }
 529         }
 530         return null;
 531     }
 532 
 533     private class JarEntryIterator implements Enumeration<JarEntry>,
 534             Iterator<JarEntry>
 535     {
 536         final Enumeration<? extends ZipEntry> e = JarFile.super.entries();
 537 
 538         public boolean hasNext() {
 539             return e.hasMoreElements();
 540         }
 541 
 542         public JarEntry next() {
 543             ZipEntry ze = e.nextElement();
 544             return new JarFileEntry(ze);
 545         }
 546 
 547         public boolean hasMoreElements() {
 548             return hasNext();
 549         }
 550 
 551         public JarEntry nextElement() {
 552             return next();
 553         }
 554 
 555         public Iterator<JarEntry> asIterator() {
 556             return this;
 557         }
 558     }
 559 
 560     /**
 561      * Returns an enumeration of the jar file entries.
 562      *
 563      * @return an enumeration of the jar file entries
 564      * @throws IllegalStateException
 565      *         may be thrown if the jar file has been closed
 566      */
 567     public Enumeration<JarEntry> entries() {
 568         return new JarEntryIterator();
 569     }
 570 
 571     /**
 572      * Returns an ordered {@code Stream} over the jar file entries.
 573      * Entries appear in the {@code Stream} in the order they appear in
 574      * the central directory of the jar file.
 575      *
 576      * @return an ordered {@code Stream} of entries in this jar file
 577      * @throws IllegalStateException if the jar file has been closed
 578      * @since 1.8
 579      */
 580     public Stream<JarEntry> stream() {
 581         return StreamSupport.stream(Spliterators.spliterator(
 582                 new JarEntryIterator(), size(),
 583                 Spliterator.ORDERED | Spliterator.DISTINCT |
 584                         Spliterator.IMMUTABLE | Spliterator.NONNULL), false);
 585     }
 586 
 587     private ZipEntry searchForVersionedEntry(int version, String name) {
 588         ZipEntry vze = null;
 589         String sname = "/" + name;
 590         int i = version;
 591         do {
 592             vze = JarFile.super.getEntry(META_INF_VERSIONS + i + sname);
 593             if (vze != null) break;
 594         } while (--i > BASE_VERSION);
 595         return vze;
 596     }
 597 
 598     /**
 599      * Returns the runtime versioned {@code JarEntry} for the given root entry
 600      * name or {@code null} if not found.
 601      *
 602      * <p>If this {@code JarFile} is a multi-release jar file, then a
 603      * search is performed to find and return a {@code JarEntry} that
 604      * is the runtime versioned entry associated with the
 605      * given entry name.  The runtime version is obtained by invoking
 606      * {@code Version.current().major()}, perhaps modified by the two
 607      * {@code System} properties described in the {@code JarFile} overview.
 608      * The returned {@code JarEntry} is the versioned entry corresponding to the
 609      * given root entry name prefixed with the string
 610      * {@code "META-INF/versions/{n}/"}, for the largest value of
 611      * {@code n, 8 < n <= runtime version}, for which an entry exists.  If such a
 612      * versioned entry does not exist, then the root {@code JarEntry} is returned
 613      * if it exists, else {@code null} is returned.
 614      *
 615      * @param name the jar file entry name
 616      * @return the {@code JarEntry} for the given versioned entry name, or
 617      *         {@code null} if not found
 618      *
 619      * @throws IllegalStateException
 620      *         may be thrown if the jar file has been closed
 621      *
 622      * @see java.util.jar.JarEntry
 623      * @since 9
 624      */
 625     public JarEntry getRuntimeVersionedEntry(String name) {
 626         ZipEntry vze = null;
 627         if (RUNTIME_VERSION > BASE_VERSION && isMultiRelease() && !name.startsWith(META_INF)) {
 628             vze = searchForVersionedEntry(RUNTIME_VERSION, name);
 629         }
 630         if (vze == null) {
 631             vze = JarFile.super.getEntry(name);
 632         }
 633         return vze == null ? null : new JarFileEntry(name, vze);
 634     }
 635 
 636     private ZipEntry getVersionedEntry(ZipEntry ze) {
 637         ZipEntry vze = null;
 638         if (!configured) {
 639             synchronized (this) {  // see setVersioned
 640                 configured = true;
 641             }
 642         }
 643         // we know that version will not change at this point, but don't read it twice
 644         int v = version;
 645         if (v > BASE_VERSION && !ze.isDirectory()) {
 646             String name = ze.getName();
 647             if (!name.startsWith(META_INF)) {
 648                 vze = searchForVersionedEntry(v, name);
 649             }
 650         }
 651         return vze == null ? ze : vze;
 652     }
 653 
 654     private class JarFileEntry extends JarEntry {
 655         final private String name;
 656 
 657         JarFileEntry(ZipEntry ze) {
 658             super(isMultiRelease() ? getVersionedEntry(ze) : ze);
 659             this.name = ze.getName();
 660         }
 661         JarFileEntry(String name, ZipEntry vze) {
 662             super(vze);
 663             this.name = name;
 664         }
 665         public Attributes getAttributes() throws IOException {
 666             Manifest man = JarFile.this.getManifest();
 667             if (man != null) {
 668                 return man.getAttributes(super.getName());
 669             } else {
 670                 return null;
 671             }
 672         }
 673         public Certificate[] getCertificates() {
 674             try {
 675                 maybeInstantiateVerifier();
 676             } catch (IOException e) {
 677                 throw new RuntimeException(e);
 678             }
 679             if (certs == null && jv != null) {
 680                 certs = jv.getCerts(JarFile.this, reifiedEntry());
 681             }
 682             return certs == null ? null : certs.clone();
 683         }
 684         public CodeSigner[] getCodeSigners() {
 685             try {
 686                 maybeInstantiateVerifier();
 687             } catch (IOException e) {
 688                 throw new RuntimeException(e);
 689             }
 690             if (signers == null && jv != null) {
 691                 signers = jv.getCodeSigners(JarFile.this, reifiedEntry());
 692             }
 693             return signers == null ? null : signers.clone();
 694         }
 695         JarFileEntry reifiedEntry() {
 696             if (isMultiRelease()) {
 697                 String entryName = super.getName();
 698                 return entryName.equals(this.name) ? this : new JarFileEntry(entryName, this);
 699             }
 700             return this;
 701         }
 702 
 703         @Override
 704         public String getName() {
 705             return name;
 706         }
 707     }
 708 
 709     /*
 710      * Ensures that the JarVerifier has been created if one is
 711      * necessary (i.e., the jar appears to be signed.) This is done as
 712      * a quick check to avoid processing of the manifest for unsigned
 713      * jars.
 714      */
 715     private void maybeInstantiateVerifier() throws IOException {
 716         if (jv != null) {
 717             return;
 718         }
 719 
 720         if (verify) {
 721             String[] names = getMetaInfEntryNames();
 722             if (names != null) {
 723                 for (String nameLower : names) {
 724                     String name = nameLower.toUpperCase(Locale.ENGLISH);
 725                     if (name.endsWith(".DSA") ||
 726                         name.endsWith(".RSA") ||
 727                         name.endsWith(".EC") ||
 728                         name.endsWith(".SF")) {
 729                         // Assume since we found a signature-related file
 730                         // that the jar is signed and that we therefore
 731                         // need a JarVerifier and Manifest
 732                         getManifest();
 733                         return;
 734                     }
 735                 }
 736             }
 737             // No signature-related files; don't instantiate a
 738             // verifier
 739             verify = false;
 740         }
 741     }
 742 
 743 
 744     /*
 745      * Initializes the verifier object by reading all the manifest
 746      * entries and passing them to the verifier.
 747      */
 748     private void initializeVerifier() {
 749         ManifestEntryVerifier mev = null;
 750 
 751         // Verify "META-INF/" entries...
 752         try {
 753             String[] names = getMetaInfEntryNames();
 754             if (names != null) {
 755                 for (String name : names) {
 756                     String uname = name.toUpperCase(Locale.ENGLISH);
 757                     if (MANIFEST_NAME.equals(uname)
 758                             || SignatureFileVerifier.isBlockOrSF(uname)) {
 759                         JarEntry e = getJarEntry(name);
 760                         if (e == null) {
 761                             throw new JarException("corrupted jar file");
 762                         }
 763                         if (mev == null) {
 764                             mev = new ManifestEntryVerifier
 765                                 (getManifestFromReference());
 766                         }
 767                         byte[] b = getBytes(e);
 768                         if (b != null && b.length > 0) {
 769                             jv.beginEntry(e, mev);
 770                             jv.update(b.length, b, 0, b.length, mev);
 771                             jv.update(-1, null, 0, 0, mev);
 772                         }
 773                     }
 774                 }
 775             }
 776         } catch (IOException ex) {
 777             // if we had an error parsing any blocks, just
 778             // treat the jar file as being unsigned
 779             jv = null;
 780             verify = false;
 781             if (JarVerifier.debug != null) {
 782                 JarVerifier.debug.println("jarfile parsing error!");
 783                 ex.printStackTrace();
 784             }
 785         }
 786 
 787         // if after initializing the verifier we have nothing
 788         // signed, we null it out.
 789 
 790         if (jv != null) {
 791 
 792             jv.doneWithMeta();
 793             if (JarVerifier.debug != null) {
 794                 JarVerifier.debug.println("done with meta!");
 795             }
 796 
 797             if (jv.nothingToVerify()) {
 798                 if (JarVerifier.debug != null) {
 799                     JarVerifier.debug.println("nothing to verify!");
 800                 }
 801                 jv = null;
 802                 verify = false;
 803             }
 804         }
 805     }
 806 
 807     /*
 808      * Reads all the bytes for a given entry. Used to process the
 809      * META-INF files.
 810      */
 811     private byte[] getBytes(ZipEntry ze) throws IOException {
 812         try (InputStream is = super.getInputStream(ze)) {
 813             int len = (int)ze.getSize();
 814             int bytesRead;
 815             byte[] b;
 816             // trust specified entry sizes when reasonably small
 817             if (len != -1 && len <= 65535) {
 818                 b = new byte[len];
 819                 bytesRead = is.readNBytes(b, 0, len);
 820             } else {
 821                 b = is.readAllBytes();
 822                 bytesRead = b.length;
 823             }
 824             if (len != -1 && len != bytesRead) {
 825                 throw new EOFException("Expected:" + len + ", read:" + bytesRead);
 826             }
 827             return b;
 828         }
 829     }
 830 
 831     /**
 832      * Returns an input stream for reading the contents of the specified
 833      * zip file entry.
 834      * @param ze the zip file entry
 835      * @return an input stream for reading the contents of the specified
 836      *         zip file entry
 837      * @throws ZipException if a zip file format error has occurred
 838      * @throws IOException if an I/O error has occurred
 839      * @throws SecurityException if any of the jar file entries
 840      *         are incorrectly signed.
 841      * @throws IllegalStateException
 842      *         may be thrown if the jar file has been closed
 843      */
 844     public synchronized InputStream getInputStream(ZipEntry ze)
 845         throws IOException
 846     {
 847         maybeInstantiateVerifier();
 848         if (jv == null) {
 849             return super.getInputStream(ze);
 850         }
 851         if (!jvInitialized) {
 852             initializeVerifier();
 853             jvInitialized = true;
 854             // could be set to null after a call to
 855             // initializeVerifier if we have nothing to
 856             // verify
 857             if (jv == null)
 858                 return super.getInputStream(ze);
 859         }
 860 
 861         // wrap a verifier stream around the real stream
 862         return new JarVerifier.VerifierStream(
 863             getManifestFromReference(),
 864             verifiableEntry(ze),

 865             super.getInputStream(ze),
 866             jv);
 867     }
 868 
 869     private JarEntry verifiableEntry(ZipEntry ze) {
 870         if (!(ze instanceof JarFileEntry)) {
 871             ze = getJarEntry(ze.getName());
 872         }
 873         // assure the name and entry match for verification
 874         return ze == null ? null : ((JarFileEntry)ze).reifiedEntry();
 875     }
 876 
 877     // Statics for hand-coded Boyer-Moore search
 878     private static final char[] CLASSPATH_CHARS = {'c','l','a','s','s','-','p','a','t','h'};
 879     // The bad character shift for "class-path"
 880     private static final int[] CLASSPATH_LASTOCC;
 881     // The good suffix shift for "class-path"
 882     private static final int[] CLASSPATH_OPTOSFT;
 883 
 884     static {
 885         CLASSPATH_LASTOCC = new int[128];
 886         CLASSPATH_OPTOSFT = new int[10];
 887         CLASSPATH_LASTOCC[(int)'c'] = 1;
 888         CLASSPATH_LASTOCC[(int)'l'] = 2;
 889         CLASSPATH_LASTOCC[(int)'s'] = 5;
 890         CLASSPATH_LASTOCC[(int)'-'] = 6;
 891         CLASSPATH_LASTOCC[(int)'p'] = 7;
 892         CLASSPATH_LASTOCC[(int)'a'] = 8;
 893         CLASSPATH_LASTOCC[(int)'t'] = 9;
 894         CLASSPATH_LASTOCC[(int)'h'] = 10;
 895         for (int i=0; i<9; i++)
 896             CLASSPATH_OPTOSFT[i] = 10;
 897         CLASSPATH_OPTOSFT[9]=1;
 898     }
 899 
 900     private JarEntry getManEntry() {
 901         if (manEntry == null) {
 902             // First look up manifest entry using standard name
 903             ZipEntry manEntry = super.getEntry(MANIFEST_NAME);
 904             if (manEntry == null) {
 905                 // If not found, then iterate through all the "META-INF/"
 906                 // entries to find a match.
 907                 String[] names = getMetaInfEntryNames();
 908                 if (names != null) {
 909                     for (String name : names) {
 910                         if (MANIFEST_NAME.equals(name.toUpperCase(Locale.ENGLISH))) {
 911                             manEntry = super.getEntry(name);
 912                             break;
 913                         }
 914                     }
 915                 }
 916             }
 917             this.manEntry = manEntry == null ? null : new JarFileEntry(manEntry.getName(), manEntry);
 918         }
 919         return manEntry;
 920     }
 921 
 922    /**
 923     * Returns {@code true} iff this JAR file has a manifest with the
 924     * Class-Path attribute
 925     */
 926     boolean hasClassPathAttribute() throws IOException {
 927         checkForSpecialAttributes();
 928         return hasClassPathAttribute;
 929     }
 930 
 931     /**
 932      * Returns true if the pattern {@code src} is found in {@code b}.
 933      * The {@code lastOcc} and {@code optoSft} arrays are the precomputed
 934      * bad character and good suffix shifts.
 935      */
 936     private boolean match(char[] src, byte[] b, int[] lastOcc, int[] optoSft) {
 937         int len = src.length;
 938         int last = b.length - len;
 939         int i = 0;
 940         next:
 941         while (i<=last) {
 942             for (int j=(len-1); j>=0; j--) {
 943                 char c = (char) b[i+j];
 944                 c = (((c-'A')|('Z'-c)) >= 0) ? (char)(c + 32) : c;
 945                 if (c != src[j]) {
 946                     i += Math.max(j + 1 - lastOcc[c&0x7F], optoSft[j]);
 947                     continue next;
 948                  }
 949             }
 950             return true;
 951         }
 952         return false;
 953     }
 954 
 955     /**
 956      * On first invocation, check if the JAR file has the Class-Path
 957      * attribute. A no-op on subsequent calls.
 958      */
 959     private void checkForSpecialAttributes() throws IOException {
 960         if (hasCheckedSpecialAttributes) return;
 961         JarEntry manEntry = getManEntry();
 962         if (manEntry != null) {
 963             byte[] b = getBytes(manEntry);
 964             if (match(CLASSPATH_CHARS, b, CLASSPATH_LASTOCC, CLASSPATH_OPTOSFT))
 965                 hasClassPathAttribute = true;
 966         }
 967         hasCheckedSpecialAttributes = true;
 968     }
 969 
 970     private synchronized void ensureInitialization() {
 971         try {
 972             maybeInstantiateVerifier();
 973         } catch (IOException e) {
 974             throw new RuntimeException(e);
 975         }
 976         if (jv != null && !jvInitialized) {
 977             initializeVerifier();
 978             jvInitialized = true;
 979         }
 980     }
 981 
 982     JarEntry newEntry(ZipEntry ze) {
 983         return new JarFileEntry(ze);
 984     }
 985 
 986     Enumeration<String> entryNames(CodeSource[] cs) {
 987         ensureInitialization();
 988         if (jv != null) {
 989             return jv.entryNames(this, cs);
 990         }
 991 
 992         /*
 993          * JAR file has no signed content. Is there a non-signing
 994          * code source?
 995          */
 996         boolean includeUnsigned = false;
 997         for (CodeSource c : cs) {
 998             if (c.getCodeSigners() == null) {
 999                 includeUnsigned = true;
1000                 break;
1001             }
1002         }
1003         if (includeUnsigned) {
1004             return unsignedEntryNames();
1005         } else {
1006             return new Enumeration<>() {
1007 
1008                 public boolean hasMoreElements() {
1009                     return false;
1010                 }
1011 
1012                 public String nextElement() {
1013                     throw new NoSuchElementException();
1014                 }
1015             };
1016         }
1017     }
1018 
1019     /**
1020      * Returns an enumeration of the zip file entries
1021      * excluding internal JAR mechanism entries and including
1022      * signed entries missing from the ZIP directory.
1023      */
1024     Enumeration<JarEntry> entries2() {
1025         ensureInitialization();
1026         if (jv != null) {
1027             return jv.entries2(this, super.entries());
1028         }
1029 
1030         // screen out entries which are never signed
1031         final Enumeration<? extends ZipEntry> enum_ = super.entries();
1032         return new Enumeration<>() {
1033 
1034             ZipEntry entry;
1035 
1036             public boolean hasMoreElements() {
1037                 if (entry != null) {
1038                     return true;
1039                 }
1040                 while (enum_.hasMoreElements()) {
1041                     ZipEntry ze = enum_.nextElement();
1042                     if (JarVerifier.isSigningRelated(ze.getName())) {
1043                         continue;
1044                     }
1045                     entry = ze;
1046                     return true;
1047                 }
1048                 return false;
1049             }
1050 
1051             public JarFileEntry nextElement() {
1052                 if (hasMoreElements()) {
1053                     ZipEntry ze = entry;
1054                     entry = null;
1055                     return new JarFileEntry(ze);
1056                 }
1057                 throw new NoSuchElementException();
1058             }
1059         };
1060     }
1061 
1062     CodeSource[] getCodeSources(URL url) {
1063         ensureInitialization();
1064         if (jv != null) {
1065             return jv.getCodeSources(this, url);
1066         }
1067 
1068         /*
1069          * JAR file has no signed content. Is there a non-signing
1070          * code source?
1071          */
1072         Enumeration<String> unsigned = unsignedEntryNames();
1073         if (unsigned.hasMoreElements()) {
1074             return new CodeSource[]{JarVerifier.getUnsignedCS(url)};
1075         } else {
1076             return null;
1077         }
1078     }
1079 
1080     private Enumeration<String> unsignedEntryNames() {
1081         final Enumeration<JarEntry> entries = entries();
1082         return new Enumeration<>() {
1083 
1084             String name;
1085 
1086             /*
1087              * Grab entries from ZIP directory but screen out
1088              * metadata.
1089              */
1090             public boolean hasMoreElements() {
1091                 if (name != null) {
1092                     return true;
1093                 }
1094                 while (entries.hasMoreElements()) {
1095                     String value;
1096                     ZipEntry e = entries.nextElement();
1097                     value = e.getName();
1098                     if (e.isDirectory() || JarVerifier.isSigningRelated(value)) {
1099                         continue;
1100                     }
1101                     name = value;
1102                     return true;
1103                 }
1104                 return false;
1105             }
1106 
1107             public String nextElement() {
1108                 if (hasMoreElements()) {
1109                     String value = name;
1110                     name = null;
1111                     return value;
1112                 }
1113                 throw new NoSuchElementException();
1114             }
1115         };
1116     }
1117 
1118     CodeSource getCodeSource(URL url, String name) {
1119         ensureInitialization();
1120         if (jv != null) {
1121             if (jv.eagerValidation) {
1122                 CodeSource cs = null;
1123                 JarEntry je = getJarEntry(name);
1124                 if (je != null) {
1125                     cs = jv.getCodeSource(url, this, je);
1126                 } else {
1127                     cs = jv.getCodeSource(url, name);
1128                 }
1129                 return cs;
1130             } else {
1131                 return jv.getCodeSource(url, name);
1132             }
1133         }
1134 
1135         return JarVerifier.getUnsignedCS(url);
1136     }
1137 
1138     void setEagerValidation(boolean eager) {
1139         try {
1140             maybeInstantiateVerifier();
1141         } catch (IOException e) {
1142             throw new RuntimeException(e);
1143         }
1144         if (jv != null) {
1145             jv.setEagerValidation(eager);
1146         }
1147     }
1148 
1149     List<Object> getManifestDigests() {
1150         ensureInitialization();
1151         if (jv != null) {
1152             return jv.getManifestDigests();
1153         }
1154         return new ArrayList<>();
1155     }
1156 }
--- EOF ---