1 /*
   2  * Copyright (c) 1997, 2011, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package java.util.jar;
  27 
  28 import java.io.*;
  29 import java.lang.ref.SoftReference;
  30 import java.net.URL;
  31 import java.util.*;
  32 import java.util.stream.Stream;
  33 import java.util.stream.StreamSupport;
  34 import java.util.zip.*;
  35 import java.security.CodeSigner;
  36 import java.security.cert.Certificate;
  37 import java.security.AccessController;
  38 import java.security.CodeSource;
  39 import sun.misc.IOUtils;
  40 import sun.security.action.GetPropertyAction;
  41 import sun.security.util.ManifestEntryVerifier;
  42 import sun.misc.SharedSecrets;
  43 
  44 /**
  45  * The <code>JarFile</code> class is used to read the contents of a jar file
  46  * from any file that can be opened with <code>java.io.RandomAccessFile</code>.
  47  * It extends the class <code>java.util.zip.ZipFile</code> with support
  48  * for reading an optional <code>Manifest</code> entry. The
  49  * <code>Manifest</code> can be used to specify meta-information about the
  50  * jar file and its entries.
  51  *
  52  * <p> Unless otherwise noted, passing a <tt>null</tt> argument to a constructor
  53  * or method in this class will cause a {@link NullPointerException} to be
  54  * thrown.
  55  *
  56  * @author  David Connelly
  57  * @see     Manifest
  58  * @see     java.util.zip.ZipFile
  59  * @see     java.util.jar.JarEntry
  60  * @since   1.2
  61  */
  62 public
  63 class JarFile extends ZipFile {
  64     private SoftReference<Manifest> manRef;
  65     private JarEntry manEntry;
  66     private JarVerifier jv;
  67     private boolean jvInitialized;
  68     private boolean verify;
  69 
  70     // indicates if Class-Path attribute present (only valid if hasCheckedSpecialAttributes true)
  71     private boolean hasClassPathAttribute;
  72     // indicates if Profile attribute present (only valid if hasCheckedSpecialAttributes true)
  73     private boolean hasProfileAttribute;
  74     // true if manifest checked for special attributes
  75     private volatile boolean hasCheckedSpecialAttributes;
  76 
  77     // Set up JavaUtilJarAccess in SharedSecrets
  78     static {
  79         SharedSecrets.setJavaUtilJarAccess(new JavaUtilJarAccessImpl());
  80     }
  81 
  82     /**
  83      * The JAR manifest file name.
  84      */
  85     public static final String MANIFEST_NAME = "META-INF/MANIFEST.MF";
  86 
  87     /**
  88      * Creates a new <code>JarFile</code> to read from the specified
  89      * file <code>name</code>. The <code>JarFile</code> will be verified if
  90      * it is signed.
  91      * @param name the name of the jar file to be opened for reading
  92      * @throws IOException if an I/O error has occurred
  93      * @throws SecurityException if access to the file is denied
  94      *         by the SecurityManager
  95      */
  96     public JarFile(String name) throws IOException {
  97         this(new File(name), true, ZipFile.OPEN_READ);
  98     }
  99 
 100     /**
 101      * Creates a new <code>JarFile</code> to read from the specified
 102      * file <code>name</code>.
 103      * @param name the name of the jar file to be opened for reading
 104      * @param verify whether or not to verify the jar file if
 105      * it is signed.
 106      * @throws IOException if an I/O error has occurred
 107      * @throws SecurityException if access to the file is denied
 108      *         by the SecurityManager
 109      */
 110     public JarFile(String name, boolean verify) throws IOException {
 111         this(new File(name), verify, ZipFile.OPEN_READ);
 112     }
 113 
 114     /**
 115      * Creates a new <code>JarFile</code> to read from the specified
 116      * <code>File</code> object. The <code>JarFile</code> will be verified if
 117      * it is signed.
 118      * @param file the jar file to be opened for reading
 119      * @throws IOException if an I/O error has occurred
 120      * @throws SecurityException if access to the file is denied
 121      *         by the SecurityManager
 122      */
 123     public JarFile(File file) throws IOException {
 124         this(file, true, ZipFile.OPEN_READ);
 125     }
 126 
 127 
 128     /**
 129      * Creates a new <code>JarFile</code> to read from the specified
 130      * <code>File</code> object.
 131      * @param file the jar file to be opened for reading
 132      * @param verify whether or not to verify the jar file if
 133      * it is signed.
 134      * @throws IOException if an I/O error has occurred
 135      * @throws SecurityException if access to the file is denied
 136      *         by the SecurityManager.
 137      */
 138     public JarFile(File file, boolean verify) throws IOException {
 139         this(file, verify, ZipFile.OPEN_READ);
 140     }
 141 
 142 
 143     /**
 144      * Creates a new <code>JarFile</code> to read from the specified
 145      * <code>File</code> object in the specified mode.  The mode argument
 146      * must be either <tt>OPEN_READ</tt> or <tt>OPEN_READ | OPEN_DELETE</tt>.
 147      *
 148      * @param file the jar file to be opened for reading
 149      * @param verify whether or not to verify the jar file if
 150      * it is signed.
 151      * @param mode the mode in which the file is to be opened
 152      * @throws IOException if an I/O error has occurred
 153      * @throws IllegalArgumentException
 154      *         if the <tt>mode</tt> argument is invalid
 155      * @throws SecurityException if access to the file is denied
 156      *         by the SecurityManager
 157      * @since 1.3
 158      */
 159     public JarFile(File file, boolean verify, int mode) throws IOException {
 160         super(file, mode);
 161         this.verify = verify;
 162     }
 163 
 164     /**
 165      * Returns the jar file manifest, or <code>null</code> if none.
 166      *
 167      * @return the jar file manifest, or <code>null</code> if none
 168      *
 169      * @throws IllegalStateException
 170      *         may be thrown if the jar file has been closed
 171      */
 172     public Manifest getManifest() throws IOException {
 173         return getManifestFromReference();
 174     }
 175 
 176     private Manifest getManifestFromReference() throws IOException {
 177         Manifest man = manRef != null ? manRef.get() : null;
 178 
 179         if (man == null) {
 180 
 181             JarEntry manEntry = getManEntry();
 182 
 183             // If found then load the manifest
 184             if (manEntry != null) {
 185                 if (verify) {
 186                     byte[] b = getBytes(manEntry);
 187                     man = new Manifest(new ByteArrayInputStream(b));
 188                     if (!jvInitialized) {
 189                         jv = new JarVerifier(b);
 190                     }
 191                 } else {
 192                     man = new Manifest(super.getInputStream(manEntry));
 193                 }
 194                 manRef = new SoftReference<>(man);
 195             }
 196         }
 197         return man;
 198     }
 199 
 200     private native String[] getMetaInfEntryNames();
 201 
 202     /**
 203      * Returns the <code>JarEntry</code> for the given entry name or
 204      * <code>null</code> if not found.
 205      *
 206      * @param name the jar file entry name
 207      * @return the <code>JarEntry</code> for the given entry name or
 208      *         <code>null</code> if not found.
 209      *
 210      * @throws IllegalStateException
 211      *         may be thrown if the jar file has been closed
 212      *
 213      * @see java.util.jar.JarEntry
 214      */
 215     public JarEntry getJarEntry(String name) {
 216         return (JarEntry)getEntry(name);
 217     }
 218 
 219     /**
 220      * Returns the <code>ZipEntry</code> for the given entry name or
 221      * <code>null</code> if not found.
 222      *
 223      * @param name the jar file entry name
 224      * @return the <code>ZipEntry</code> for the given entry name or
 225      *         <code>null</code> if not found
 226      *
 227      * @throws IllegalStateException
 228      *         may be thrown if the jar file has been closed
 229      *
 230      * @see java.util.zip.ZipEntry
 231      */
 232     public ZipEntry getEntry(String name) {
 233         ZipEntry ze = super.getEntry(name);
 234         if (ze != null) {
 235             return new JarFileEntry(ze);
 236         }
 237         return null;
 238     }
 239 
 240     protected class JarEntryIterator implements Enumeration<JarEntry>,
 241             Iterator<JarEntry>
 242     {
 243         final Iterator<ZipEntry> e = new ZipFile.ZipEntryIterator();
 244 
 245         public boolean hasMoreElements() {
 246             return e.hasNext();
 247         }
 248 
 249         public JarEntry nextElement() {
 250             return next();
 251         }
 252 
 253         public boolean hasNext() {
 254             return e.hasNext();
 255         }
 256 
 257         public JarEntry next() {
 258             ZipEntry ze = e.next();
 259             return new JarFileEntry(ze);
 260         }
 261     }
 262 
 263     /**
 264      * Returns an enumeration of the zip file entries.
 265      */
 266     public Enumeration<JarEntry> entries() {
 267         return new JarEntryIterator();
 268     }
 269 
 270     @Override
 271     public Stream<JarEntry> stream() {
 272         return StreamSupport.stream(Spliterators.spliterator(
 273                 new JarEntryIterator(), size(),
 274                 Spliterator.DISTINCT | Spliterator.IMMUTABLE | Spliterator.NONNULL));
 275     }
 276 
 277     private class JarFileEntry extends JarEntry {
 278         JarFileEntry(ZipEntry ze) {
 279             super(ze);
 280         }
 281         public Attributes getAttributes() throws IOException {
 282             Manifest man = JarFile.this.getManifest();
 283             if (man != null) {
 284                 return man.getAttributes(getName());
 285             } else {
 286                 return null;
 287             }
 288         }
 289         public Certificate[] getCertificates() {
 290             try {
 291                 maybeInstantiateVerifier();
 292             } catch (IOException e) {
 293                 throw new RuntimeException(e);
 294             }
 295             if (certs == null && jv != null) {
 296                 certs = jv.getCerts(JarFile.this, this);
 297             }
 298             return certs == null ? null : certs.clone();
 299         }
 300         public CodeSigner[] getCodeSigners() {
 301             try {
 302                 maybeInstantiateVerifier();
 303             } catch (IOException e) {
 304                 throw new RuntimeException(e);
 305             }
 306             if (signers == null && jv != null) {
 307                 signers = jv.getCodeSigners(JarFile.this, this);
 308             }
 309             return signers == null ? null : signers.clone();
 310         }
 311     }
 312 
 313     /*
 314      * Ensures that the JarVerifier has been created if one is
 315      * necessary (i.e., the jar appears to be signed.) This is done as
 316      * a quick check to avoid processing of the manifest for unsigned
 317      * jars.
 318      */
 319     private void maybeInstantiateVerifier() throws IOException {
 320         if (jv != null) {
 321             return;
 322         }
 323 
 324         if (verify) {
 325             String[] names = getMetaInfEntryNames();
 326             if (names != null) {
 327                 for (int i = 0; i < names.length; i++) {
 328                     String name = names[i].toUpperCase(Locale.ENGLISH);
 329                     if (name.endsWith(".DSA") ||
 330                         name.endsWith(".RSA") ||
 331                         name.endsWith(".EC") ||
 332                         name.endsWith(".SF")) {
 333                         // Assume since we found a signature-related file
 334                         // that the jar is signed and that we therefore
 335                         // need a JarVerifier and Manifest
 336                         getManifest();
 337                         return;
 338                     }
 339                 }
 340             }
 341             // No signature-related files; don't instantiate a
 342             // verifier
 343             verify = false;
 344         }
 345     }
 346 
 347 
 348     /*
 349      * Initializes the verifier object by reading all the manifest
 350      * entries and passing them to the verifier.
 351      */
 352     private void initializeVerifier() {
 353         ManifestEntryVerifier mev = null;
 354 
 355         // Verify "META-INF/" entries...
 356         try {
 357             String[] names = getMetaInfEntryNames();
 358             if (names != null) {
 359                 for (int i = 0; i < names.length; i++) {
 360                     JarEntry e = getJarEntry(names[i]);
 361                     if (e == null) {
 362                         throw new JarException("corrupted jar file");
 363                     }
 364                     if (!e.isDirectory()) {
 365                         if (mev == null) {
 366                             mev = new ManifestEntryVerifier
 367                                 (getManifestFromReference());
 368                         }
 369                         byte[] b = getBytes(e);
 370                         if (b != null && b.length > 0) {
 371                             jv.beginEntry(e, mev);
 372                             jv.update(b.length, b, 0, b.length, mev);
 373                             jv.update(-1, null, 0, 0, mev);
 374                         }
 375                     }
 376                 }
 377             }
 378         } catch (IOException ex) {
 379             // if we had an error parsing any blocks, just
 380             // treat the jar file as being unsigned
 381             jv = null;
 382             verify = false;
 383             if (JarVerifier.debug != null) {
 384                 JarVerifier.debug.println("jarfile parsing error!");
 385                 ex.printStackTrace();
 386             }
 387         }
 388 
 389         // if after initializing the verifier we have nothing
 390         // signed, we null it out.
 391 
 392         if (jv != null) {
 393 
 394             jv.doneWithMeta();
 395             if (JarVerifier.debug != null) {
 396                 JarVerifier.debug.println("done with meta!");
 397             }
 398 
 399             if (jv.nothingToVerify()) {
 400                 if (JarVerifier.debug != null) {
 401                     JarVerifier.debug.println("nothing to verify!");
 402                 }
 403                 jv = null;
 404                 verify = false;
 405             }
 406         }
 407     }
 408 
 409     /*
 410      * Reads all the bytes for a given entry. Used to process the
 411      * META-INF files.
 412      */
 413     private byte[] getBytes(ZipEntry ze) throws IOException {
 414         try (InputStream is = super.getInputStream(ze)) {
 415             return IOUtils.readFully(is, (int)ze.getSize(), true);
 416         }
 417     }
 418 
 419     /**
 420      * Returns an input stream for reading the contents of the specified
 421      * zip file entry.
 422      * @param ze the zip file entry
 423      * @return an input stream for reading the contents of the specified
 424      *         zip file entry
 425      * @throws ZipException if a zip file format error has occurred
 426      * @throws IOException if an I/O error has occurred
 427      * @throws SecurityException if any of the jar file entries
 428      *         are incorrectly signed.
 429      * @throws IllegalStateException
 430      *         may be thrown if the jar file has been closed
 431      */
 432     public synchronized InputStream getInputStream(ZipEntry ze)
 433         throws IOException
 434     {
 435         maybeInstantiateVerifier();
 436         if (jv == null) {
 437             return super.getInputStream(ze);
 438         }
 439         if (!jvInitialized) {
 440             initializeVerifier();
 441             jvInitialized = true;
 442             // could be set to null after a call to
 443             // initializeVerifier if we have nothing to
 444             // verify
 445             if (jv == null)
 446                 return super.getInputStream(ze);
 447         }
 448 
 449         // wrap a verifier stream around the real stream
 450         return new JarVerifier.VerifierStream(
 451             getManifestFromReference(),
 452             ze instanceof JarFileEntry ?
 453             (JarEntry) ze : getJarEntry(ze.getName()),
 454             super.getInputStream(ze),
 455             jv);
 456     }
 457 
 458     // Statics for hand-coded Boyer-Moore search
 459     private static final char[] CLASSPATH_CHARS = {'c','l','a','s','s','-','p','a','t','h'};
 460     private static final char[] PROFILE_CHARS = { 'p', 'r', 'o', 'f', 'i', 'l', 'e' };
 461     // The bad character shift for "class-path"
 462     private static final int[] CLASSPATH_LASTOCC;
 463     // The good suffix shift for "class-path"
 464     private static final int[] CLASSPATH_OPTOSFT;
 465     // The bad character shift for "profile"
 466     private static final int[] PROFILE_LASTOCC;
 467     // The good suffix shift for "profile"
 468     private static final int[] PROFILE_OPTOSFT;
 469 
 470     static {
 471         CLASSPATH_LASTOCC = new int[128];
 472         CLASSPATH_OPTOSFT = new int[10];
 473         CLASSPATH_LASTOCC[(int)'c'] = 1;
 474         CLASSPATH_LASTOCC[(int)'l'] = 2;
 475         CLASSPATH_LASTOCC[(int)'s'] = 5;
 476         CLASSPATH_LASTOCC[(int)'-'] = 6;
 477         CLASSPATH_LASTOCC[(int)'p'] = 7;
 478         CLASSPATH_LASTOCC[(int)'a'] = 8;
 479         CLASSPATH_LASTOCC[(int)'t'] = 9;
 480         CLASSPATH_LASTOCC[(int)'h'] = 10;
 481         for (int i=0; i<9; i++)
 482             CLASSPATH_OPTOSFT[i] = 10;
 483         CLASSPATH_OPTOSFT[9]=1;
 484 
 485         PROFILE_LASTOCC = new int[128];
 486         PROFILE_OPTOSFT = new int[7];
 487         PROFILE_LASTOCC[(int)'p'] = 1;
 488         PROFILE_LASTOCC[(int)'r'] = 2;
 489         PROFILE_LASTOCC[(int)'o'] = 3;
 490         PROFILE_LASTOCC[(int)'f'] = 4;
 491         PROFILE_LASTOCC[(int)'i'] = 5;
 492         PROFILE_LASTOCC[(int)'l'] = 6;
 493         PROFILE_LASTOCC[(int)'e'] = 7;
 494         for (int i=0; i<6; i++)
 495             PROFILE_OPTOSFT[i] = 7;
 496         PROFILE_OPTOSFT[6] = 1;
 497     }
 498 
 499     private JarEntry getManEntry() {
 500         if (manEntry == null) {
 501             // First look up manifest entry using standard name
 502             manEntry = getJarEntry(MANIFEST_NAME);
 503             if (manEntry == null) {
 504                 // If not found, then iterate through all the "META-INF/"
 505                 // entries to find a match.
 506                 String[] names = getMetaInfEntryNames();
 507                 if (names != null) {
 508                     for (int i = 0; i < names.length; i++) {
 509                         if (MANIFEST_NAME.equals(
 510                                                  names[i].toUpperCase(Locale.ENGLISH))) {
 511                             manEntry = getJarEntry(names[i]);
 512                             break;
 513                         }
 514                     }
 515                 }
 516             }
 517         }
 518         return manEntry;
 519     }
 520 
 521    /**
 522     * Returns {@code true} iff this JAR file has a manifest with the
 523     * Class-Path attribute
 524     */
 525     boolean hasClassPathAttribute() throws IOException {
 526         checkForSpecialAttributes();
 527         return hasClassPathAttribute;
 528     }
 529 
 530     /**
 531      * Returns {@code true} iff this JAR file has a manifest with the
 532      * Profile attribute
 533      */
 534     boolean hasProfileAttribute() throws IOException {
 535         checkForSpecialAttributes();
 536         return hasProfileAttribute;
 537     }
 538 
 539     /**
 540      * Returns true if the pattern {@code src} is found in {@code b}.
 541      * The {@code lastOcc} and {@code optoSft} arrays are the precomputed
 542      * bad character and good suffix shifts.
 543      */
 544     private boolean match(char[] src, byte[] b, int[] lastOcc, int[] optoSft) {
 545         int len = src.length;
 546         int last = b.length - len;
 547         int i = 0;
 548         next:
 549         while (i<=last) {
 550             for (int j=(len-1); j>=0; j--) {
 551                 char c = (char) b[i+j];
 552                 c = (((c-'A')|('Z'-c)) >= 0) ? (char)(c + 32) : c;
 553                 if (c != src[j]) {
 554                     i += Math.max(j + 1 - lastOcc[c&0x7F], optoSft[j]);
 555                     continue next;
 556                  }
 557             }
 558             return true;
 559         }
 560         return false;
 561     }
 562 
 563     /**
 564      * On first invocation, check if the JAR file has the Class-Path
 565      * and/or Profile attributes. A no-op on subsequent calls.
 566      */
 567     private void checkForSpecialAttributes() throws IOException {
 568         if (hasCheckedSpecialAttributes) return;
 569         if (!isKnownNotToHaveSpecialAttributes()) {
 570             JarEntry manEntry = getManEntry();
 571             if (manEntry != null) {
 572                 byte[] b = getBytes(manEntry);
 573                 if (match(CLASSPATH_CHARS, b, CLASSPATH_LASTOCC, CLASSPATH_OPTOSFT))
 574                     hasClassPathAttribute = true;
 575                 if (match(PROFILE_CHARS, b, PROFILE_LASTOCC, PROFILE_OPTOSFT))
 576                     hasProfileAttribute = true;
 577             }
 578         }
 579         hasCheckedSpecialAttributes = true;
 580     }
 581 
 582     private static String javaHome;
 583     private static volatile String[] jarNames;
 584     private boolean isKnownNotToHaveSpecialAttributes() {
 585         // Optimize away even scanning of manifest for jar files we
 586         // deliver which don't have a class-path attribute. If one of
 587         // these jars is changed to include such an attribute this code
 588         // must be changed.
 589         if (javaHome == null) {
 590             javaHome = AccessController.doPrivileged(
 591                 new GetPropertyAction("java.home"));
 592         }
 593         if (jarNames == null) {
 594             String[] names = new String[11];
 595             String fileSep = File.separator;
 596             int i = 0;
 597             names[i++] = fileSep + "rt.jar";
 598             names[i++] = fileSep + "jsse.jar";
 599             names[i++] = fileSep + "jce.jar";
 600             names[i++] = fileSep + "charsets.jar";
 601             names[i++] = fileSep + "dnsns.jar";
 602             names[i++] = fileSep + "zipfs.jar";
 603             names[i++] = fileSep + "localedata.jar";
 604             names[i++] = fileSep = "cldrdata.jar";
 605             names[i++] = fileSep + "sunjce_provider.jar";
 606             names[i++] = fileSep + "sunpkcs11.jar";
 607             names[i++] = fileSep + "sunec.jar";
 608             jarNames = names;
 609         }
 610 
 611         String name = getName();
 612         String localJavaHome = javaHome;
 613         if (name.startsWith(localJavaHome)) {
 614             String[] names = jarNames;
 615             for (int i = 0; i < names.length; i++) {
 616                 if (name.endsWith(names[i])) {
 617                     return true;
 618                 }
 619             }
 620         }
 621         return false;
 622     }
 623 
 624     private synchronized void ensureInitialization() {
 625         try {
 626             maybeInstantiateVerifier();
 627         } catch (IOException e) {
 628             throw new RuntimeException(e);
 629         }
 630         if (jv != null && !jvInitialized) {
 631             initializeVerifier();
 632             jvInitialized = true;
 633         }
 634     }
 635 
 636     JarEntry newEntry(ZipEntry ze) {
 637         return new JarFileEntry(ze);
 638     }
 639 
 640     Enumeration<String> entryNames(CodeSource[] cs) {
 641         ensureInitialization();
 642         if (jv != null) {
 643             return jv.entryNames(this, cs);
 644         }
 645 
 646         /*
 647          * JAR file has no signed content. Is there a non-signing
 648          * code source?
 649          */
 650         boolean includeUnsigned = false;
 651         for (int i = 0; i < cs.length; i++) {
 652             if (cs[i].getCodeSigners() == null) {
 653                 includeUnsigned = true;
 654                 break;
 655             }
 656         }
 657         if (includeUnsigned) {
 658             return unsignedEntryNames();
 659         } else {
 660             return new Enumeration<String>() {
 661 
 662                 public boolean hasMoreElements() {
 663                     return false;
 664                 }
 665 
 666                 public String nextElement() {
 667                     throw new NoSuchElementException();
 668                 }
 669             };
 670         }
 671     }
 672 
 673     /**
 674      * Returns an enumeration of the zip file entries
 675      * excluding internal JAR mechanism entries and including
 676      * signed entries missing from the ZIP directory.
 677      */
 678     Enumeration<JarEntry> entries2() {
 679         ensureInitialization();
 680         if (jv != null) {
 681             return jv.entries2(this, super.entries());
 682         }
 683 
 684         // screen out entries which are never signed
 685         final Enumeration<? extends ZipEntry> enum_ = super.entries();
 686         return new Enumeration<JarEntry>() {
 687 
 688             ZipEntry entry;
 689 
 690             public boolean hasMoreElements() {
 691                 if (entry != null) {
 692                     return true;
 693                 }
 694                 while (enum_.hasMoreElements()) {
 695                     ZipEntry ze = enum_.nextElement();
 696                     if (JarVerifier.isSigningRelated(ze.getName())) {
 697                         continue;
 698                     }
 699                     entry = ze;
 700                     return true;
 701                 }
 702                 return false;
 703             }
 704 
 705             public JarFileEntry nextElement() {
 706                 if (hasMoreElements()) {
 707                     ZipEntry ze = entry;
 708                     entry = null;
 709                     return new JarFileEntry(ze);
 710                 }
 711                 throw new NoSuchElementException();
 712             }
 713         };
 714     }
 715 
 716     CodeSource[] getCodeSources(URL url) {
 717         ensureInitialization();
 718         if (jv != null) {
 719             return jv.getCodeSources(this, url);
 720         }
 721 
 722         /*
 723          * JAR file has no signed content. Is there a non-signing
 724          * code source?
 725          */
 726         Enumeration<String> unsigned = unsignedEntryNames();
 727         if (unsigned.hasMoreElements()) {
 728             return new CodeSource[]{JarVerifier.getUnsignedCS(url)};
 729         } else {
 730             return null;
 731         }
 732     }
 733 
 734     private Enumeration<String> unsignedEntryNames() {
 735         final Enumeration<JarEntry> entries = entries();
 736         return new Enumeration<String>() {
 737 
 738             String name;
 739 
 740             /*
 741              * Grab entries from ZIP directory but screen out
 742              * metadata.
 743              */
 744             public boolean hasMoreElements() {
 745                 if (name != null) {
 746                     return true;
 747                 }
 748                 while (entries.hasMoreElements()) {
 749                     String value;
 750                     ZipEntry e = entries.nextElement();
 751                     value = e.getName();
 752                     if (e.isDirectory() || JarVerifier.isSigningRelated(value)) {
 753                         continue;
 754                     }
 755                     name = value;
 756                     return true;
 757                 }
 758                 return false;
 759             }
 760 
 761             public String nextElement() {
 762                 if (hasMoreElements()) {
 763                     String value = name;
 764                     name = null;
 765                     return value;
 766                 }
 767                 throw new NoSuchElementException();
 768             }
 769         };
 770     }
 771 
 772     CodeSource getCodeSource(URL url, String name) {
 773         ensureInitialization();
 774         if (jv != null) {
 775             if (jv.eagerValidation) {
 776                 CodeSource cs = null;
 777                 JarEntry je = getJarEntry(name);
 778                 if (je != null) {
 779                     cs = jv.getCodeSource(url, this, je);
 780                 } else {
 781                     cs = jv.getCodeSource(url, name);
 782                 }
 783                 return cs;
 784             } else {
 785                 return jv.getCodeSource(url, name);
 786             }
 787         }
 788 
 789         return JarVerifier.getUnsignedCS(url);
 790     }
 791 
 792     void setEagerValidation(boolean eager) {
 793         try {
 794             maybeInstantiateVerifier();
 795         } catch (IOException e) {
 796             throw new RuntimeException(e);
 797         }
 798         if (jv != null) {
 799             jv.setEagerValidation(eager);
 800         }
 801     }
 802 
 803     List<Object> getManifestDigests() {
 804         ensureInitialization();
 805         if (jv != null) {
 806             return jv.getManifestDigests();
 807         }
 808         return new ArrayList<Object>();
 809     }
 810 }