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