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