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

 147 
 148     // indicates if Class-Path attribute present (only valid if hasCheckedSpecialAttributes true)
 149     private boolean hasClassPathAttribute;
 150     // true if manifest checked for special attributes
 151     private volatile boolean hasCheckedSpecialAttributes;
 152 
 153     static {
 154         // Set up JavaUtilJarAccess in SharedSecrets
 155         SharedSecrets.setJavaUtilJarAccess(new JavaUtilJarAccessImpl());
 156 
 157         BASE_VERSION = 8;  // one less than lowest version for versioned entries
 158         RUNTIME_VERSION = AccessController.doPrivileged(
 159                 new PrivilegedAction<Integer>() {
 160                     public Integer run() {
 161                         Integer v = jdk.Version.current().major();
 162                         Integer i = Integer.getInteger("jdk.util.jar.version", v);
 163                         i = i < 0 ? 0 : i;
 164                         return i > v ? v : i;
 165                     }
 166                 }
 167         );
 168         String multi_release = AccessController.doPrivileged(
 169                 new PrivilegedAction<String>() {
 170                     public String run() {
 171                         return System.getProperty("jdk.util.jar.enableMultiRelease", "true");
 172                     }
 173                 }
 174         );
 175         switch (multi_release) {
 176             case "true":
 177             default:
 178                 MULTI_RELEASE_ENABLED = true;
 179                 MULTI_RELEASE_FORCED = false;
 180                 break;
 181             case "false":
 182                 MULTI_RELEASE_ENABLED = false;
 183                 MULTI_RELEASE_FORCED = false;
 184                 break;
 185             case "force":
 186                 MULTI_RELEASE_ENABLED = true;
 187                 MULTI_RELEASE_FORCED = true;
 188                 break;
 189         }
 190     }
 191 
 192     /**
 193      * A set of constants that represent the entries in either the base directory
 194      * or one of the versioned directories in a multi-release jar file.  It's
 195      * possible for a multi-release jar file to contain versioned directories
 196      * that are not represented by the constants of the {@code Release} enum.
 197      * In those cases, the entries will not be located by this {@code JarFile}
 198      * through the aliasing mechanism, but they can be directly accessed by
 199      * specifying the full path name of the entry.
 200      *
 201      * @since 9
 202      */
 203     public enum Release {
 204         /**
 205          * Represents unversioned entries, or entries in "regular", as opposed
 206          * to multi-release jar files.
 207          */
 208         BASE(BASE_VERSION),
 209 
 210         /**
 211          * Represents entries found in the META-INF/versions/9 directory of a
 212          * multi-release jar file.
 213          */
 214         VERSION_9(9),
 215 
 216         // fill in the "blanks" for future releases
 217 
 218         /**
 219          * Represents entries found in the META-INF/versions/{n} directory of a
 220          * multi-release jar file, where {@code n} is the effective runtime
 221          * version of the jar file.
 222          *
 223          * @implNote
 224          * <div class="block">
 225          * The effective runtime version is determined
 226          * by evaluating {@code Version.current().major()} or by using the value
 227          * of the {@code jdk.util.jar.version} System property if it exists.
 228          * </div>
 229          */
 230         RUNTIME(RUNTIME_VERSION);
 231 
 232         Release(int version) {
 233             this.version = version;
 234         }
 235 
 236         private static Release valueOf(int version) {
 237             return version <= BASE.value() ? BASE : valueOf("VERSION_" + version);
 238         }
 239 
 240         private final int version;
 241 
 242         private int value() {
 243             return this.version;
 244         }
 245     }
 246 
 247     private static final String META_INF = "META-INF/";
 248 
 249     private static final String META_INF_VERSIONS = META_INF + "versions/";
 250 
 251     /**
 252      * The JAR manifest file name.
 253      */
 254     public static final String MANIFEST_NAME = META_INF + "MANIFEST.MF";
 255 
 256     /**
 257      * Creates a new {@code JarFile} to read from the specified
 258      * file {@code name}. The {@code JarFile} will be verified if
 259      * it is signed.
 260      * @param name the name of the jar file to be opened for reading
 261      * @throws IOException if an I/O error has occurred
 262      * @throws SecurityException if access to the file is denied
 263      *         by the SecurityManager
 264      */
 265     public JarFile(String name) throws IOException {
 266         this(new File(name), true, ZipFile.OPEN_READ);
 267     }
 268 
 269     /**
 270      * Creates a new {@code JarFile} to read from the specified
 271      * file {@code name}.
 272      * @param name the name of the jar file to be opened for reading
 273      * @param verify whether or not to verify the jar file if
 274      * it is signed.
 275      * @throws IOException if an I/O error has occurred
 276      * @throws SecurityException if access to the file is denied
 277      *         by the SecurityManager
 278      */
 279     public JarFile(String name, boolean verify) throws IOException {
 280         this(new File(name), verify, ZipFile.OPEN_READ);
 281     }
 282 
 283     /**
 284      * Creates a new {@code JarFile} to read from the specified
 285      * {@code File} object. The {@code JarFile} will be verified if
 286      * it is signed.
 287      * @param file the jar file to be opened for reading
 288      * @throws IOException if an I/O error has occurred
 289      * @throws SecurityException if access to the file is denied
 290      *         by the SecurityManager
 291      */
 292     public JarFile(File file) throws IOException {
 293         this(file, true, ZipFile.OPEN_READ);
 294     }
 295 
 296     /**
 297      * Creates a new {@code JarFile} to read from the specified
 298      * {@code File} object.
 299      * @param file the jar file to be opened for reading
 300      * @param verify whether or not to verify the jar file if
 301      * it is signed.
 302      * @throws IOException if an I/O error has occurred
 303      * @throws SecurityException if access to the file is denied
 304      *         by the SecurityManager.
 305      */
 306     public JarFile(File file, boolean verify) throws IOException {
 307         this(file, verify, ZipFile.OPEN_READ);
 308     }
 309 
 310     /**
 311      * Creates a new {@code JarFile} to read from the specified
 312      * {@code File} object in the specified mode.  The mode argument
 313      * must be either {@code OPEN_READ} or {@code OPEN_READ | OPEN_DELETE}.
 314      *
 315      * @param file the jar file to be opened for reading
 316      * @param verify whether or not to verify the jar file if
 317      * it is signed.
 318      * @param mode the mode in which the file is to be opened
 319      * @throws IOException if an I/O error has occurred
 320      * @throws IllegalArgumentException
 321      *         if the {@code mode} argument is invalid
 322      * @throws SecurityException if access to the file is denied
 323      *         by the SecurityManager
 324      * @since 1.3
 325      */
 326     public JarFile(File file, boolean verify, int mode) throws IOException {
 327         this(file, verify, mode, Release.BASE);
 328         this.notVersioned = true;
 329     }
 330 
 331     /**
 332      * Creates a new {@code JarFile} to read from the specified
 333      * {@code File} object in the specified mode.  The mode argument
 334      * must be either {@code OPEN_READ} or {@code OPEN_READ | OPEN_DELETE}.
 335      * The version argument configures the {@code JarFile} for processing
 336      * multi-release jar files.
 337      *
 338      * @param file the jar file to be opened for reading
 339      * @param verify whether or not to verify the jar file if
 340      * it is signed.
 341      * @param mode the mode in which the file is to be opened
 342      * @param version specifies the release version for a multi-release jar file
 343      * @throws IOException if an I/O error has occurred
 344      * @throws IllegalArgumentException
 345      *         if the {@code mode} argument is invalid
 346      * @throws SecurityException if access to the file is denied
 347      *         by the SecurityManager
 348      * @throws NullPointerException if {@code version} is {@code null}
 349      * @since 9
 350      */
 351     public JarFile(File file, boolean verify, int mode, Release version) throws IOException {
 352         super(file, mode);
 353         Objects.requireNonNull(version);
 354         this.verify = verify;
 355         // version applies to multi-release jar files, ignored for regular jar files
 356         this.version = MULTI_RELEASE_FORCED ? RUNTIME_VERSION : version.value();





 357         this.runtimeVersioned = version == Release.RUNTIME;

 358         assert runtimeVersionExists();
 359     }
 360 
 361     private boolean runtimeVersionExists() {
 362         int version = jdk.Version.current().major();
 363         try {
 364             Release.valueOf(version);
 365             return true;
 366         } catch (IllegalArgumentException x) {
 367             System.err.println("No JarFile.Release object for release " + version);
 368             return false;
 369         }
 370     }
 371 
 372     /**
 373      * Returns the maximum version used when searching for versioned entries.
 374      *
 375      * @return the maximum version, or {@code Release.BASE} if this jar file is
 376      *         processed as if it is an unversioned jar file or is not a
 377      *         multi-release jar file
 378      * @since 9
 379      */
 380     public final Release getVersion() {
 381         if (isMultiRelease()) {
 382             return runtimeVersioned ? Release.RUNTIME : Release.valueOf(version);
 383         } else {
 384             return Release.BASE;
 385         }
 386     }
 387 
 388     /**
 389      * Indicates whether or not this jar file is a multi-release jar file.
 390      *
 391      * @return true if this JarFile is a multi-release jar file
 392      * @since 9
 393      */
 394     public final boolean isMultiRelease() {
 395         // do not call this code in a constructor because some subclasses use
 396         // lazy loading of manifest so it won't be available at construction time
 397         if (MULTI_RELEASE_ENABLED) {
 398             // Doubled-checked locking pattern
 399             Boolean result = isMultiRelease;
 400             if (result == null) {
 401                 synchronized (this) {
 402                     result = isMultiRelease;
 403                     if (result == null) {
 404                         Manifest man = null;
 405                         try {
 406                             man = getManifest();
 407                         } catch (IOException e) {
 408                             //Ignored, manifest cannot be read
 409                         }
 410                         isMultiRelease = result = (man != null)
 411                                 && man.getMainAttributes().containsKey(MULTI_RELEASE)
 412                                 ? Boolean.TRUE : Boolean.FALSE;
 413                     }
 414                 }





 415             }
 416             return result == Boolean.TRUE;
 417         } else {
 418             return false;
 419         }

 420     }
 421     // the following field, isMultiRelease, should only be used in the method
 422     // isMultiRelease(), like a static local
 423     private volatile Boolean isMultiRelease;    // is jar multi-release?
 424 
 425     /**
 426      * Returns the jar file manifest, or {@code null} if none.
 427      *
 428      * @return the jar file manifest, or {@code null} if none
 429      *
 430      * @throws IllegalStateException
 431      *         may be thrown if the jar file has been closed
 432      * @throws IOException  if an I/O error has occurred
 433      */
 434     public Manifest getManifest() throws IOException {
 435         return getManifestFromReference();
 436     }
 437 
 438     private Manifest getManifestFromReference() throws IOException {
 439         Manifest man = manRef != null ? manRef.get() : null;
 440 
 441         if (man == null) {
 442 
 443             JarEntry manEntry = getManEntry();
 444 
 445             // If found then load the manifest
 446             if (manEntry != null) {
 447                 if (verify) {
 448                     byte[] b = getBytes(manEntry);
 449                     man = new Manifest(new ByteArrayInputStream(b));
 450                     if (!jvInitialized) {
 451                         jv = new JarVerifier(b);
 452                     }
 453                 } else {
 454                     man = new Manifest(super.getInputStream(manEntry));
 455                 }
 456                 manRef = new SoftReference<>(man);
 457             }
 458         }
 459         return man;
 460     }
 461 
 462     private String[] getMetaInfEntryNames() {
 463         return jdk.internal.misc.SharedSecrets.getJavaUtilZipFileAccess()
 464                                               .getMetaInfEntryNames((ZipFile)this);
 465     }
 466 
 467     /**
 468      * Returns the {@code JarEntry} for the given base entry name or
 469      * {@code null} if not found.
 470      *
 471      * <p>If this {@code JarFile} is a multi-release jar file and is configured
 472      * to be processed as such, then a search is performed to find and return
 473      * a {@code JarEntry} that is the latest versioned entry associated with the
 474      * given entry name.  The returned {@code JarEntry} is the versioned entry
 475      * corresponding to the given base entry name prefixed with the string
 476      * {@code "META-INF/versions/{n}/"}, for the largest value of {@code n} for
 477      * which an entry exists.  If such a versioned entry does not exist, then
 478      * the {@code JarEntry} for the base entry is returned, otherwise
 479      * {@code null} is returned if no entries are found.  The initial value for
 480      * the version {@code n} is the maximum version as returned by the method
 481      * {@link JarFile#getVersion()}.
 482      *
 483      * @param name the jar file entry name
 484      * @return the {@code JarEntry} for the given entry name, or
 485      *         the versioned entry name, or {@code null} if not found
 486      *
 487      * @throws IllegalStateException
 488      *         may be thrown if the jar file has been closed
 489      *
 490      * @see java.util.jar.JarEntry
 491      *
 492      * @implSpec
 493      * <div class="block">
 494      * This implementation invokes {@link JarFile#getEntry(String)}.
 495      * </div>
 496      */
 497     public JarEntry getJarEntry(String name) {
 498         return (JarEntry)getEntry(name);
 499     }
 500 
 501     /**
 502      * Returns the {@code ZipEntry} for the given base entry name or
 503      * {@code null} if not found.
 504      *
 505      * <p>If this {@code JarFile} is a multi-release jar file and is configured
 506      * to be processed as such, then a search is performed to find and return
 507      * a {@code ZipEntry} that is the latest versioned entry associated with the
 508      * given entry name.  The returned {@code ZipEntry} is the versioned entry
 509      * corresponding to the given base entry name prefixed with the string
 510      * {@code "META-INF/versions/{n}/"}, for the largest value of {@code n} for
 511      * which an entry exists.  If such a versioned entry does not exist, then
 512      * the {@code ZipEntry} for the base entry is returned, otherwise
 513      * {@code null} is returned if no entries are found.  The initial value for
 514      * the version {@code n} is the maximum version as returned by the method
 515      * {@link JarFile#getVersion()}.
 516      *
 517      * @param name the jar file entry name
 518      * @return the {@code ZipEntry} for the given entry name or
 519      *         the versioned entry name or {@code null} if not found
 520      *
 521      * @throws IllegalStateException
 522      *         may be thrown if the jar file has been closed
 523      *
 524      * @see java.util.zip.ZipEntry
 525      *
 526      * @implSpec
 527      * <div class="block">
 528      * This implementation may return a versioned entry for the requested name
 529      * even if there is not a corresponding base entry.  This can occur
 530      * if there is a private or package-private versioned entry that matches.
 531      * If a subclass overrides this method, assure that the override method
 532      * invokes {@code super.getEntry(name)} to obtain all versioned entries.
 533      * </div>
 534      */
 535     public ZipEntry getEntry(String name) {
 536         ZipEntry ze = super.getEntry(name);
 537         if (ze != null) {
 538             return new JarFileEntry(ze);
 539         }
 540         // no matching base entry, but maybe there is a versioned entry,
 541         // like a new private class
 542         if (isMultiRelease()) {
 543             ze = new ZipEntry(name);
 544             ZipEntry vze = getVersionedEntry(ze);
 545             if (ze != vze) {
 546                 return new JarFileEntry(name, vze);
 547             }
 548         }
 549         return null;
 550     }
 551 
 552     private class JarEntryIterator implements Enumeration<JarEntry>,
 553             Iterator<JarEntry>
 554     {
 555         final Enumeration<? extends ZipEntry> e = JarFile.super.entries();
 556         ZipEntry ze;
 557 
 558         public boolean hasNext() {
 559             if (notVersioned) {
 560                 return e.hasMoreElements();
 561             }
 562             if (ze != null) {
 563                 return true;
 564             }
 565             return findNext();
 566         }
 567 
 568         private boolean findNext() {
 569             while (e.hasMoreElements()) {
 570                 ZipEntry ze2 = e.nextElement();
 571                 if (!ze2.getName().startsWith(META_INF_VERSIONS)) {
 572                     ze = ze2;
 573                     return true;
 574                 }
 575             }
 576             return false;
 577         }
 578 
 579         public JarEntry next() {
 580             ZipEntry ze2;
 581 
 582             if (notVersioned) {
 583                 ze2 = e.nextElement();
 584                 return new JarFileEntry(ze2.getName(), ze2);
 585             }
 586             if (ze != null || findNext()) {
 587                 ze2 = ze;
 588                 ze = null;
 589                 return new JarFileEntry(ze2);
 590             }
 591             throw new NoSuchElementException();
 592         }
 593 
 594         public boolean hasMoreElements() {
 595             return hasNext();
 596         }
 597 
 598         public JarEntry nextElement() {
 599             return next();
 600         }
 601 
 602         public Iterator<JarEntry> asIterator() {
 603             return this;
 604         }
 605     }
 606 
 607     /**
 608      * Returns an enumeration of the jar file entries.  The set of entries
 609      * returned depends on whether or not the jar file is a multi-release jar
 610      * file, and on the constructor used to create the {@code JarFile}.  If the
 611      * jar file is not a multi-release jar file, all entries are returned,
 612      * regardless of how the {@code JarFile} is created.  If the constructor
 613      * does not take a {@code Release} argument, all entries are returned.
 614      * If the jar file is a multi-release jar file and the constructor takes a
 615      * {@code Release} argument, then the set of entries returned is equivalent
 616      * to the set of entries that would be returned if the set was built by
 617      * invoking {@link JarFile#getEntry(String)} or
 618      * {@link JarFile#getJarEntry(String)} with the name of each base entry in
 619      * the jar file.  A base entry is an entry whose path name does not start
 620      * with "META-INF/versions/".
 621      *
 622      * @return an enumeration of the jar file entries
 623      * @throws IllegalStateException
 624      *         may be thrown if the jar file has been closed
 625      */
 626     public Enumeration<JarEntry> entries() {
 627         return new JarEntryIterator();
 628     }
 629 
 630     /**
 631      * Returns an ordered {@code Stream} over all the jar file entries.
 632      * Entries appear in the {@code Stream} in the order they appear in
 633      * the central directory of the jar file.  The set of entries
 634      * returned depends on whether or not the jar file is a multi-release jar
 635      * file, and on the constructor used to create the {@code JarFile}.  If the
 636      * jar file is not a multi-release jar file, all entries are returned,
 637      * regardless of how the {@code JarFile} is created.  If the constructor
 638      * does not take a {@code Release} argument, all entries are returned.
 639      * If the jar file is a multi-release jar file and the constructor takes a
 640      * {@code Release} argument, then the set of entries returned is equivalent
 641      * to the set of entries that would be returned if the set was built by
 642      * invoking {@link JarFile#getEntry(String)} or
 643      * {@link JarFile#getJarEntry(String)} with the name of each base entry in
 644      * the jar file.  A base entry is an entry whose path name does not start
 645      * with "META-INF/versions/".
 646      * @return an ordered {@code Stream} of entries in this jar file
 647      * @throws IllegalStateException if the jar file has been closed
 648      * @since 1.8
 649      */
 650     public Stream<JarEntry> stream() {
 651         return StreamSupport.stream(Spliterators.spliterator(
 652                 new JarEntryIterator(), size(),
 653                 Spliterator.ORDERED | Spliterator.DISTINCT |
 654                         Spliterator.IMMUTABLE | Spliterator.NONNULL), false);
 655     }
 656 
 657     private ZipEntry searchForVersionedEntry(final int version, String name) {
 658         ZipEntry vze = null;
 659         String sname = "/" + name;
 660         int i = version;
 661         while (i > BASE_VERSION) {
 662             vze = super.getEntry(META_INF_VERSIONS + i + sname);
 663             if (vze != null) break;
 664             i--;
 665         }
 666         return vze;
 667     }
 668 
 669     private ZipEntry getVersionedEntry(ZipEntry ze) {
 670         ZipEntry vze = null;
 671         if (version > BASE_VERSION && !ze.isDirectory()) {
 672             String name = ze.getName();
 673             if (!name.startsWith(META_INF)) {
 674                 vze = searchForVersionedEntry(version, name);
 675             }
 676         }
 677         return vze == null ? ze : vze;
 678     }
 679 
 680     private class JarFileEntry extends JarEntry {
 681         final private String name;
 682 
 683         JarFileEntry(ZipEntry ze) {
 684             super(isMultiRelease() ? getVersionedEntry(ze) : ze);
 685             this.name = ze.getName();
 686         }
 687         JarFileEntry(String name, ZipEntry vze) {
 688             super(vze);
 689             this.name = name;
 690         }
 691         public Attributes getAttributes() throws IOException {
 692             Manifest man = JarFile.this.getManifest();
 693             if (man != null) {
 694                 return man.getAttributes(super.getName());
 695             } else {
 696                 return null;
 697             }
 698         }
 699         public Certificate[] getCertificates() {
 700             try {
 701                 maybeInstantiateVerifier();
 702             } catch (IOException e) {
 703                 throw new RuntimeException(e);
 704             }
 705             if (certs == null && jv != null) {
 706                 certs = jv.getCerts(JarFile.this, reifiedEntry());
 707             }
 708             return certs == null ? null : certs.clone();
 709         }
 710         public CodeSigner[] getCodeSigners() {
 711             try {
 712                 maybeInstantiateVerifier();
 713             } catch (IOException e) {
 714                 throw new RuntimeException(e);
 715             }
 716             if (signers == null && jv != null) {
 717                 signers = jv.getCodeSigners(JarFile.this, reifiedEntry());
 718             }
 719             return signers == null ? null : signers.clone();
 720         }
 721         JarFileEntry reifiedEntry() {
 722             if (isMultiRelease()) {
 723                 String entryName = super.getName();
 724                 return entryName.equals(this.name) ? this : new JarFileEntry(entryName, this);
 725             }
 726             return this;
 727         }
 728 
 729         @Override
 730         public String getName() {
 731             return name;
 732         }
 733     }
 734 
 735     /*
 736      * Ensures that the JarVerifier has been created if one is
 737      * necessary (i.e., the jar appears to be signed.) This is done as
 738      * a quick check to avoid processing of the manifest for unsigned
 739      * jars.
 740      */
 741     private void maybeInstantiateVerifier() throws IOException {
 742         if (jv != null) {
 743             return;
 744         }
 745 
 746         if (verify) {
 747             String[] names = getMetaInfEntryNames();
 748             if (names != null) {
 749                 for (String nameLower : names) {
 750                     String name = nameLower.toUpperCase(Locale.ENGLISH);
 751                     if (name.endsWith(".DSA") ||
 752                         name.endsWith(".RSA") ||
 753                         name.endsWith(".EC") ||
 754                         name.endsWith(".SF")) {
 755                         // Assume since we found a signature-related file
 756                         // that the jar is signed and that we therefore
 757                         // need a JarVerifier and Manifest
 758                         getManifest();
 759                         return;
 760                     }
 761                 }
 762             }
 763             // No signature-related files; don't instantiate a
 764             // verifier
 765             verify = false;
 766         }
 767     }
 768 
 769 
 770     /*
 771      * Initializes the verifier object by reading all the manifest
 772      * entries and passing them to the verifier.
 773      */
 774     private void initializeVerifier() {
 775         ManifestEntryVerifier mev = null;
 776 
 777         // Verify "META-INF/" entries...
 778         try {
 779             String[] names = getMetaInfEntryNames();
 780             if (names != null) {
 781                 for (String name : names) {
 782                     String uname = name.toUpperCase(Locale.ENGLISH);
 783                     if (MANIFEST_NAME.equals(uname)
 784                             || SignatureFileVerifier.isBlockOrSF(uname)) {
 785                         JarEntry e = getJarEntry(name);
 786                         if (e == null) {
 787                             throw new JarException("corrupted jar file");
 788                         }
 789                         if (mev == null) {
 790                             mev = new ManifestEntryVerifier
 791                                 (getManifestFromReference());
 792                         }
 793                         byte[] b = getBytes(e);
 794                         if (b != null && b.length > 0) {
 795                             jv.beginEntry(e, mev);
 796                             jv.update(b.length, b, 0, b.length, mev);
 797                             jv.update(-1, null, 0, 0, mev);
 798                         }
 799                     }
 800                 }
 801             }
 802         } catch (IOException ex) {
 803             // if we had an error parsing any blocks, just
 804             // treat the jar file as being unsigned
 805             jv = null;
 806             verify = false;
 807             if (JarVerifier.debug != null) {
 808                 JarVerifier.debug.println("jarfile parsing error!");
 809                 ex.printStackTrace();
 810             }
 811         }
 812 
 813         // if after initializing the verifier we have nothing
 814         // signed, we null it out.
 815 
 816         if (jv != null) {
 817 
 818             jv.doneWithMeta();
 819             if (JarVerifier.debug != null) {
 820                 JarVerifier.debug.println("done with meta!");
 821             }
 822 
 823             if (jv.nothingToVerify()) {
 824                 if (JarVerifier.debug != null) {
 825                     JarVerifier.debug.println("nothing to verify!");
 826                 }
 827                 jv = null;
 828                 verify = false;
 829             }
 830         }
 831     }
 832 
 833     /*
 834      * Reads all the bytes for a given entry. Used to process the
 835      * META-INF files.
 836      */
 837     private byte[] getBytes(ZipEntry ze) throws IOException {
 838         try (InputStream is = super.getInputStream(ze)) {
 839             int len = (int)ze.getSize();
 840             int bytesRead;
 841             byte[] b;
 842             // trust specified entry sizes when reasonably small
 843             if (len != -1 && len <= 65535) {
 844                 b = new byte[len];
 845                 bytesRead = is.readNBytes(b, 0, len);
 846             } else {
 847                 b = is.readAllBytes();
 848                 bytesRead = b.length;
 849             }
 850             if (len != -1 && len != bytesRead) {
 851                 throw new EOFException("Expected:" + len + ", read:" + bytesRead);
 852             }
 853             return b;
 854         }
 855     }
 856 
 857     /**
 858      * Returns an input stream for reading the contents of the specified
 859      * zip file entry.
 860      * @param ze the zip file entry
 861      * @return an input stream for reading the contents of the specified
 862      *         zip file entry
 863      * @throws ZipException if a zip file format error has occurred
 864      * @throws IOException if an I/O error has occurred
 865      * @throws SecurityException if any of the jar file entries
 866      *         are incorrectly signed.
 867      * @throws IllegalStateException
 868      *         may be thrown if the jar file has been closed
 869      */
 870     public synchronized InputStream getInputStream(ZipEntry ze)
 871         throws IOException
 872     {
 873         maybeInstantiateVerifier();
 874         if (jv == null) {
 875             return super.getInputStream(ze);
 876         }
 877         if (!jvInitialized) {
 878             initializeVerifier();
 879             jvInitialized = true;
 880             // could be set to null after a call to
 881             // initializeVerifier if we have nothing to
 882             // verify
 883             if (jv == null)
 884                 return super.getInputStream(ze);
 885         }
 886 
 887         // wrap a verifier stream around the real stream
 888         return new JarVerifier.VerifierStream(
 889             getManifestFromReference(),
 890             verifiableEntry(ze),
 891             super.getInputStream(ze),
 892             jv);
 893     }
 894 
 895     private JarEntry verifiableEntry(ZipEntry ze) {
 896         if (ze instanceof JarFileEntry) {
 897             // assure the name and entry match for verification
 898             return ((JarFileEntry)ze).reifiedEntry();
 899         }
 900         ze = getJarEntry(ze.getName());
 901         if (ze instanceof JarFileEntry) {
 902             return ((JarFileEntry)ze).reifiedEntry();
 903         }
 904         return (JarEntry)ze;
 905     }
 906 
 907     // Statics for hand-coded Boyer-Moore search
 908     private static final char[] CLASSPATH_CHARS = {'c','l','a','s','s','-','p','a','t','h'};
 909     // The bad character shift for "class-path"
 910     private static final int[] CLASSPATH_LASTOCC;
 911     // The good suffix shift for "class-path"
 912     private static final int[] CLASSPATH_OPTOSFT;






 913 
 914     static {
 915         CLASSPATH_LASTOCC = new int[128];
 916         CLASSPATH_OPTOSFT = new int[10];
 917         CLASSPATH_LASTOCC[(int)'c'] = 1;
 918         CLASSPATH_LASTOCC[(int)'l'] = 2;
 919         CLASSPATH_LASTOCC[(int)'s'] = 5;
 920         CLASSPATH_LASTOCC[(int)'-'] = 6;
 921         CLASSPATH_LASTOCC[(int)'p'] = 7;
 922         CLASSPATH_LASTOCC[(int)'a'] = 8;
 923         CLASSPATH_LASTOCC[(int)'t'] = 9;
 924         CLASSPATH_LASTOCC[(int)'h'] = 10;
 925         for (int i=0; i<9; i++)
 926             CLASSPATH_OPTOSFT[i] = 10;
 927         CLASSPATH_OPTOSFT[9]=1;












 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} and {@code optoSft} arrays are the precomputed
 966      * bad character and good suffix shifts.

 967      */
 968     private boolean match(char[] src, byte[] b, int[] lastOcc, int[] optoSft) {
 969         int len = src.length;
 970         int last = b.length - len;
 971         int i = 0;
 972         next:
 973         while (i<=last) {
 974             for (int j=(len-1); j>=0; j--) {
 975                 char c = (char) b[i+j];
 976                 c = (((c-'A')|('Z'-c)) >= 0) ? (char)(c + 32) : c;


 977                 if (c != src[j]) {
 978                     i += Math.max(j + 1 - lastOcc[c&0x7F], optoSft[j]);








 979                     continue next;
 980                  }
 981             }
 982             return true;
 983         }
 984         return false;
 985     }
 986 
 987     /**
 988      * On first invocation, check if the JAR file has the Class-Path
 989      * attribute. A no-op on subsequent calls.
 990      */
 991     private void checkForSpecialAttributes() throws IOException {
 992         if (hasCheckedSpecialAttributes) return;






 993         JarEntry manEntry = getManEntry();
 994         if (manEntry != null) {
 995             byte[] b = getBytes(manEntry);
 996             if (match(CLASSPATH_CHARS, b, CLASSPATH_LASTOCC, CLASSPATH_OPTOSFT))
 997                 hasClassPathAttribute = true;





 998         }
 999         hasCheckedSpecialAttributes = true;

1000     }
1001 
1002     private synchronized void ensureInitialization() {
1003         try {
1004             maybeInstantiateVerifier();
1005         } catch (IOException e) {
1006             throw new RuntimeException(e);
1007         }
1008         if (jv != null && !jvInitialized) {
1009             initializeVerifier();
1010             jvInitialized = true;
1011         }
1012     }
1013 
1014     JarEntry newEntry(ZipEntry ze) {
1015         return new JarFileEntry(ze);
1016     }
1017 
1018     Enumeration<String> entryNames(CodeSource[] cs) {
1019         ensureInitialization();
1020         if (jv != null) {
1021             return jv.entryNames(this, cs);
1022         }
1023 
1024         /*
1025          * JAR file has no signed content. Is there a non-signing
1026          * code source?
1027          */
1028         boolean includeUnsigned = false;
1029         for (CodeSource c : cs) {
1030             if (c.getCodeSigners() == null) {
1031                 includeUnsigned = true;
1032                 break;
1033             }
1034         }
1035         if (includeUnsigned) {
1036             return unsignedEntryNames();
1037         } else {
1038             return new Enumeration<>() {
1039 
1040                 public boolean hasMoreElements() {
1041                     return false;
1042                 }
1043 
1044                 public String nextElement() {
1045                     throw new NoSuchElementException();
1046                 }
1047             };
1048         }
1049     }
1050 
1051     /**
1052      * Returns an enumeration of the zip file entries
1053      * excluding internal JAR mechanism entries and including
1054      * signed entries missing from the ZIP directory.
1055      */
1056     Enumeration<JarEntry> entries2() {
1057         ensureInitialization();
1058         if (jv != null) {
1059             return jv.entries2(this, super.entries());
1060         }
1061 
1062         // screen out entries which are never signed
1063         final Enumeration<? extends ZipEntry> enum_ = super.entries();
1064         return new Enumeration<>() {
1065 
1066             ZipEntry entry;
1067 
1068             public boolean hasMoreElements() {
1069                 if (entry != null) {
1070                     return true;
1071                 }
1072                 while (enum_.hasMoreElements()) {
1073                     ZipEntry ze = enum_.nextElement();
1074                     if (JarVerifier.isSigningRelated(ze.getName())) {
1075                         continue;
1076                     }
1077                     entry = ze;
1078                     return true;
1079                 }
1080                 return false;
1081             }
1082 
1083             public JarFileEntry nextElement() {
1084                 if (hasMoreElements()) {
1085                     ZipEntry ze = entry;
1086                     entry = null;
1087                     return new JarFileEntry(ze);
1088                 }
1089                 throw new NoSuchElementException();
1090             }
1091         };
1092     }
1093 
1094     CodeSource[] getCodeSources(URL url) {
1095         ensureInitialization();
1096         if (jv != null) {
1097             return jv.getCodeSources(this, url);
1098         }
1099 
1100         /*
1101          * JAR file has no signed content. Is there a non-signing
1102          * code source?
1103          */
1104         Enumeration<String> unsigned = unsignedEntryNames();
1105         if (unsigned.hasMoreElements()) {
1106             return new CodeSource[]{JarVerifier.getUnsignedCS(url)};
1107         } else {
1108             return null;
1109         }
1110     }
1111 
1112     private Enumeration<String> unsignedEntryNames() {
1113         final Enumeration<JarEntry> entries = entries();
1114         return new Enumeration<>() {
1115 
1116             String name;
1117 
1118             /*
1119              * Grab entries from ZIP directory but screen out
1120              * metadata.
1121              */
1122             public boolean hasMoreElements() {
1123                 if (name != null) {
1124                     return true;
1125                 }
1126                 while (entries.hasMoreElements()) {
1127                     String value;
1128                     ZipEntry e = entries.nextElement();
1129                     value = e.getName();
1130                     if (e.isDirectory() || JarVerifier.isSigningRelated(value)) {
1131                         continue;
1132                     }
1133                     name = value;
1134                     return true;
1135                 }
1136                 return false;
1137             }
1138 
1139             public String nextElement() {
1140                 if (hasMoreElements()) {
1141                     String value = name;
1142                     name = null;
1143                     return value;
1144                 }
1145                 throw new NoSuchElementException();
1146             }
1147         };
1148     }
1149 
1150     CodeSource getCodeSource(URL url, String name) {
1151         ensureInitialization();
1152         if (jv != null) {
1153             if (jv.eagerValidation) {
1154                 CodeSource cs = null;
1155                 JarEntry je = getJarEntry(name);
1156                 if (je != null) {
1157                     cs = jv.getCodeSource(url, this, je);
1158                 } else {
1159                     cs = jv.getCodeSource(url, name);
1160                 }
1161                 return cs;
1162             } else {
1163                 return jv.getCodeSource(url, name);
1164             }
1165         }
1166 
1167         return JarVerifier.getUnsignedCS(url);
1168     }
1169 
1170     void setEagerValidation(boolean eager) {
1171         try {
1172             maybeInstantiateVerifier();
1173         } catch (IOException e) {
1174             throw new RuntimeException(e);
1175         }
1176         if (jv != null) {
1177             jv.setEagerValidation(eager);
1178         }
1179     }
1180 
1181     List<Object> getManifestDigests() {
1182         ensureInitialization();
1183         if (jv != null) {
1184             return jv.getManifestDigests();
1185         }
1186         return new ArrayList<>();
1187     }
1188 }
--- EOF ---