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.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 jdk.internal.misc.SharedSecrets; 40 import sun.security.action.GetPropertyAction; 41 import sun.security.util.ManifestEntryVerifier; 42 import sun.security.util.SignatureFileVerifier; 43 44 /** 45 * The {@code JarFile} class is used to read the contents of a jar file 46 * from any file that can be opened with {@code java.io.RandomAccessFile}. 47 * It extends the class {@code java.util.zip.ZipFile} with support 48 * for reading an optional {@code Manifest} entry, and support for 49 * processing multi-release jar files. The {@code Manifest} can be used 50 * to specify meta-information about the jar file and its entries. 51 * 52 * <p>A multi-release jar file is a jar file that contains 53 * a manifest with a main attribute named "Multi-Release", 54 * a set of "base" entries, some of which are public classes with public 55 * or protected methods that comprise the public interface of the jar file, 56 * and a set of "versioned" entries contained in subdirectories of the 57 * "META-INF/versions" directory. The versioned entries are partitioned by the 58 * major version of the Java platform. A versioned entry, with a version 59 * {@code n}, {@code 8 < n}, in the "META-INF/versions/{n}" directory overrides 60 * the base entry as well as any entry with a version number {@code i} where 61 * {@code 8 < i < n}. 62 * 63 * <p>By default, a {@code JarFile} for a multi-release jar file is configured 64 * to process the multi-release jar file as if it were a plain (unversioned) jar 65 * file, and as such an entry name is associated with at most one base entry. 66 * The {@code JarFile} may be configured to process a multi-release jar file by 67 * creating the {@code JarFile} with the 68 * {@link JarFile#JarFile(File, boolean, int, Release)} constructor. The 69 * {@code Release} object sets a maximum version used when searching for 70 * versioned entries. When so configured, an entry name 71 * can correspond with at most one base entry and zero or more versioned 72 * entries. A search is required to associate the entry name with the latest 73 * versioned entry whose version is less than or equal to the maximum version 74 * (see {@link #getEntry(String)}). 75 * 76 * <p>Class loaders that utilize {@code JarFile} to load classes from the 77 * contents of {@code JarFile} entries should construct the {@code JarFile} 78 * by invoking the {@link JarFile#JarFile(File, boolean, int, Release)} 79 * constructor with the value {@code Release.RUNTIME} assigned to the last 80 * argument. This assures that classes compatible with the major 81 * version of the running JVM are loaded from multi-release jar files. 82 * 83 * <p>If the verify flag is on when opening a signed jar file, the content of 84 * the file is verified against its signature embedded inside the file. Please 85 * note that the verification process does not include validating the signer's 86 * certificate. A caller should inspect the return value of 87 * {@link JarEntry#getCodeSigners()} to further determine if the signature 88 * can be trusted. 89 * 90 * <p> Unless otherwise noted, passing a {@code null} argument to a constructor 91 * or method in this class will cause a {@link NullPointerException} to be 92 * thrown. 93 * 94 * @implNote 95 * <div class="block"> 96 * If the API can not be used to configure a {@code JarFile} (e.g. to override 97 * the configuration of a compiled application or library), two {@code System} 98 * properties are available. 99 * <ul> 100 * <li> 101 * {@code jdk.util.jar.version} can be assigned a value that is the 102 * {@code String} representation of a non-negative integer 103 * {@code <= Version.current().major()}. The value is used to set the effective 104 * runtime version to something other than the default value obtained by 105 * evaluating {@code Version.current().major()}. The effective runtime version 106 * is the version that the {@link JarFile#JarFile(File, boolean, int, Release)} 107 * constructor uses when the value of the last argument is 108 * {@code Release.RUNTIME}. 109 * </li> 110 * <li> 111 * {@code jdk.util.jar.enableMultiRelease} can be assigned one of the three 112 * {@code String} values <em>true</em>, <em>false</em>, or <em>force</em>. The 113 * value <em>true</em>, the default value, enables multi-release jar file 114 * processing. The value <em>false</em> disables multi-release jar processing, 115 * ignoring the "Multi-Release" manifest attribute, and the versioned 116 * directories in a multi-release jar file if they exist. Furthermore, 117 * the method {@link JarFile#isMultiRelease()} returns <em>false</em>. The value 118 * <em>force</em> causes the {@code JarFile} to be initialized to runtime 119 * versioning after construction. It effectively does the same as this code: 120 * {@code (new JarFile(File, boolean, int, Release.RUNTIME)}. 121 * </li> 122 * </ul> 123 * </div> 124 * 125 * @author David Connelly 126 * @see Manifest 127 * @see java.util.zip.ZipFile 128 * @see java.util.jar.JarEntry 129 * @since 1.2 130 */ 131 public 132 class JarFile extends ZipFile { 133 private final static int BASE_VERSION; 134 private final static int RUNTIME_VERSION; 135 private final static boolean MULTI_RELEASE_ENABLED; 136 private final static boolean MULTI_RELEASE_FORCED; 137 private SoftReference<Manifest> manRef; 138 private JarEntry manEntry; 139 private JarVerifier jv; 140 private boolean jvInitialized; 141 private boolean verify; 142 private final int version; 143 private boolean notVersioned; 144 private final boolean runtimeVersioned; 145 private boolean isMultiRelease; // is jar multi-release? 146 147 // indicates if Class-Path attribute present 148 private boolean hasClassPathAttribute; 149 // true if manifest checked for special attributes 150 private volatile boolean hasCheckedSpecialAttributes; 151 152 static { 153 // Set up JavaUtilJarAccess in SharedSecrets 154 SharedSecrets.setJavaUtilJarAccess(new JavaUtilJarAccessImpl()); 155 156 BASE_VERSION = 8; // one less than lowest version for versioned entries 157 int runtimeVersion = jdk.Version.current().major(); 158 String jarVersion = AccessController.doPrivileged( 159 new GetPropertyAction("jdk.util.jar.version")); 160 if (jarVersion != null) { 161 int jarVer = Integer.parseInt(jarVersion); 162 runtimeVersion = (jarVer > runtimeVersion) 163 ? runtimeVersion : Math.max(jarVer, 0); 164 } 165 RUNTIME_VERSION = runtimeVersion; 166 String enableMultiRelease = AccessController.doPrivileged( 167 new GetPropertyAction("jdk.util.jar.enableMultiRelease", "true")); 168 switch (enableMultiRelease) { 169 case "true": 170 default: 171 MULTI_RELEASE_ENABLED = true; 172 MULTI_RELEASE_FORCED = false; 173 break; 174 case "false": 175 MULTI_RELEASE_ENABLED = false; 176 MULTI_RELEASE_FORCED = false; 177 break; 178 case "force": 179 MULTI_RELEASE_ENABLED = true; 180 MULTI_RELEASE_FORCED = true; 181 break; 182 } 183 } 184 185 /** 186 * A set of constants that represent the entries in either the base directory 187 * or one of the versioned directories in a multi-release jar file. It's 188 * possible for a multi-release jar file to contain versioned directories 189 * that are not represented by the constants of the {@code Release} enum. 190 * In those cases, the entries will not be located by this {@code JarFile} 191 * through the aliasing mechanism, but they can be directly accessed by 192 * specifying the full path name of the entry. 193 * 194 * @since 9 195 */ 196 public enum Release { 197 /** 198 * Represents unversioned entries, or entries in "regular", as opposed 199 * to multi-release jar files. 200 */ 201 BASE(BASE_VERSION), 202 203 /** 204 * Represents entries found in the META-INF/versions/9 directory of a 205 * multi-release jar file. 206 */ 207 VERSION_9(9), 208 209 // fill in the "blanks" for future releases 210 211 /** 212 * Represents entries found in the META-INF/versions/{n} directory of a 213 * multi-release jar file, where {@code n} is the effective runtime 214 * version of the jar file. 215 * 216 * @implNote 217 * <div class="block"> 218 * The effective runtime version is determined 219 * by evaluating {@code Version.current().major()} or by using the value 220 * of the {@code jdk.util.jar.version} System property if it exists. 221 * </div> 222 */ 223 RUNTIME(RUNTIME_VERSION); 224 225 Release(int version) { 226 this.version = version; 227 } 228 229 private static Release valueOf(int version) { 230 return version <= BASE.value() ? BASE : valueOf("VERSION_" + version); 231 } 232 233 private final int version; 234 235 private int value() { 236 return this.version; 237 } 238 } 239 240 private static final String META_INF = "META-INF/"; 241 242 private static final String META_INF_VERSIONS = META_INF + "versions/"; 243 244 /** 245 * The JAR manifest file name. 246 */ 247 public static final String MANIFEST_NAME = META_INF + "MANIFEST.MF"; 248 249 /** 250 * Creates a new {@code JarFile} to read from the specified 251 * file {@code name}. The {@code JarFile} will be verified if 252 * it is signed. 253 * @param name the name of the jar file to be opened for reading 254 * @throws IOException if an I/O error has occurred 255 * @throws SecurityException if access to the file is denied 256 * by the SecurityManager 257 */ 258 public JarFile(String name) throws IOException { 259 this(new File(name), true, ZipFile.OPEN_READ); 260 } 261 262 /** 263 * Creates a new {@code JarFile} to read from the specified 264 * file {@code name}. 265 * @param name the name of the jar file to be opened for reading 266 * @param verify whether or not to verify the jar file if 267 * it is signed. 268 * @throws IOException if an I/O error has occurred 269 * @throws SecurityException if access to the file is denied 270 * by the SecurityManager 271 */ 272 public JarFile(String name, boolean verify) throws IOException { 273 this(new File(name), verify, ZipFile.OPEN_READ); 274 } 275 276 /** 277 * Creates a new {@code JarFile} to read from the specified 278 * {@code File} object. The {@code JarFile} will be verified if 279 * it is signed. 280 * @param file the jar file to be opened for reading 281 * @throws IOException if an I/O error has occurred 282 * @throws SecurityException if access to the file is denied 283 * by the SecurityManager 284 */ 285 public JarFile(File file) throws IOException { 286 this(file, true, ZipFile.OPEN_READ); 287 } 288 289 /** 290 * Creates a new {@code JarFile} to read from the specified 291 * {@code File} object. 292 * @param file the jar file to be opened for reading 293 * @param verify whether or not to verify the jar file if 294 * it is signed. 295 * @throws IOException if an I/O error has occurred 296 * @throws SecurityException if access to the file is denied 297 * by the SecurityManager. 298 */ 299 public JarFile(File file, boolean verify) throws IOException { 300 this(file, verify, ZipFile.OPEN_READ); 301 } 302 303 /** 304 * Creates a new {@code JarFile} to read from the specified 305 * {@code File} object in the specified mode. The mode argument 306 * must be either {@code OPEN_READ} or {@code OPEN_READ | OPEN_DELETE}. 307 * 308 * @param file the jar file to be opened for reading 309 * @param verify whether or not to verify the jar file if 310 * it is signed. 311 * @param mode the mode in which the file is to be opened 312 * @throws IOException if an I/O error has occurred 313 * @throws IllegalArgumentException 314 * if the {@code mode} argument is invalid 315 * @throws SecurityException if access to the file is denied 316 * by the SecurityManager 317 * @since 1.3 318 */ 319 public JarFile(File file, boolean verify, int mode) throws IOException { 320 this(file, verify, mode, Release.BASE); 321 this.notVersioned = true; 322 } 323 324 /** 325 * Creates a new {@code JarFile} to read from the specified 326 * {@code File} object in the specified mode. The mode argument 327 * must be either {@code OPEN_READ} or {@code OPEN_READ | OPEN_DELETE}. 328 * The version argument configures the {@code JarFile} for processing 329 * multi-release jar files. 330 * 331 * @param file the jar file to be opened for reading 332 * @param verify whether or not to verify the jar file if 333 * it is signed. 334 * @param mode the mode in which the file is to be opened 335 * @param version specifies the release version for a multi-release jar file 336 * @throws IOException if an I/O error has occurred 337 * @throws IllegalArgumentException 338 * if the {@code mode} argument is invalid 339 * @throws SecurityException if access to the file is denied 340 * by the SecurityManager 341 * @throws NullPointerException if {@code version} is {@code null} 342 * @since 9 343 */ 344 public JarFile(File file, boolean verify, int mode, Release version) throws IOException { 345 super(file, mode); 346 Objects.requireNonNull(version); 347 this.verify = verify; 348 // version applies to multi-release jar files, ignored for regular jar files 349 if (MULTI_RELEASE_FORCED) { 350 this.version = RUNTIME_VERSION; 351 version = Release.RUNTIME; 352 } else { 353 this.version = version.value(); 354 } 355 this.runtimeVersioned = version == Release.RUNTIME; 356 357 assert runtimeVersionExists(); 358 } 359 360 private boolean runtimeVersionExists() { 361 int version = jdk.Version.current().major(); 362 try { 363 Release.valueOf(version); 364 return true; 365 } catch (IllegalArgumentException x) { 366 System.err.println("No JarFile.Release object for release " + version); 367 return false; 368 } 369 } 370 371 /** 372 * Returns the maximum version used when searching for versioned entries. 373 * 374 * @return the maximum version, or {@code Release.BASE} if this jar file is 375 * processed as if it is an unversioned jar file or is not a 376 * multi-release jar file 377 * @since 9 378 */ 379 public final Release getVersion() { 380 if (isMultiRelease()) { 381 return runtimeVersioned ? Release.RUNTIME : Release.valueOf(version); 382 } else { 383 return Release.BASE; 384 } 385 } 386 387 /** 388 * Indicates whether or not this jar file is a multi-release jar file. 389 * 390 * @return true if this JarFile is a multi-release jar file 391 * @since 9 392 */ 393 public final boolean isMultiRelease() { 394 if (isMultiRelease) { 395 return true; 396 } 397 if (MULTI_RELEASE_ENABLED && version != BASE_VERSION) { 398 try { 399 checkForSpecialAttributes(); 400 } catch (IOException io) { 401 isMultiRelease = false; 402 } 403 } 404 return isMultiRelease; 405 } 406 407 /** 408 * Returns the jar file manifest, or {@code null} if none. 409 * 410 * @return the jar file manifest, or {@code null} if none 411 * 412 * @throws IllegalStateException 413 * may be thrown if the jar file has been closed 414 * @throws IOException if an I/O error has occurred 415 */ 416 public Manifest getManifest() throws IOException { 417 return getManifestFromReference(); 418 } 419 420 private Manifest getManifestFromReference() throws IOException { 421 Manifest man = manRef != null ? manRef.get() : null; 422 423 if (man == null) { 424 425 JarEntry manEntry = getManEntry(); 426 427 // If found then load the manifest 428 if (manEntry != null) { 429 if (verify) { 430 byte[] b = getBytes(manEntry); 431 man = new Manifest(new ByteArrayInputStream(b)); 432 if (!jvInitialized) { 433 jv = new JarVerifier(b); 434 } 435 } else { 436 man = new Manifest(super.getInputStream(manEntry)); 437 } 438 manRef = new SoftReference<>(man); 439 } 440 } 441 return man; 442 } 443 444 private String[] getMetaInfEntryNames() { 445 return jdk.internal.misc.SharedSecrets.getJavaUtilZipFileAccess() 446 .getMetaInfEntryNames((ZipFile)this); 447 } 448 449 /** 450 * Returns the {@code JarEntry} for the given base entry name or 451 * {@code null} if not found. 452 * 453 * <p>If this {@code JarFile} is a multi-release jar file and is configured 454 * to be processed as such, then a search is performed to find and return 455 * a {@code JarEntry} that is the latest versioned entry associated with the 456 * given entry name. The returned {@code JarEntry} is the versioned entry 457 * corresponding to the given base entry name prefixed with the string 458 * {@code "META-INF/versions/{n}/"}, for the largest value of {@code n} for 459 * which an entry exists. If such a versioned entry does not exist, then 460 * the {@code JarEntry} for the base entry is returned, otherwise 461 * {@code null} is returned if no entries are found. The initial value for 462 * the version {@code n} is the maximum version as returned by the method 463 * {@link JarFile#getVersion()}. 464 * 465 * @param name the jar file entry name 466 * @return the {@code JarEntry} for the given entry name, or 467 * the versioned entry name, or {@code null} if not found 468 * 469 * @throws IllegalStateException 470 * may be thrown if the jar file has been closed 471 * 472 * @see java.util.jar.JarEntry 473 * 474 * @implSpec 475 * <div class="block"> 476 * This implementation invokes {@link JarFile#getEntry(String)}. 477 * </div> 478 */ 479 public JarEntry getJarEntry(String name) { 480 return (JarEntry)getEntry(name); 481 } 482 483 /** 484 * Returns the {@code ZipEntry} for the given base entry name or 485 * {@code null} if not found. 486 * 487 * <p>If this {@code JarFile} is a multi-release jar file and is configured 488 * to be processed as such, then a search is performed to find and return 489 * a {@code ZipEntry} that is the latest versioned entry associated with the 490 * given entry name. The returned {@code ZipEntry} is the versioned entry 491 * corresponding to the given base entry name prefixed with the string 492 * {@code "META-INF/versions/{n}/"}, for the largest value of {@code n} for 493 * which an entry exists. If such a versioned entry does not exist, then 494 * the {@code ZipEntry} for the base entry is returned, otherwise 495 * {@code null} is returned if no entries are found. The initial value for 496 * the version {@code n} is the maximum version as returned by the method 497 * {@link JarFile#getVersion()}. 498 * 499 * @param name the jar file entry name 500 * @return the {@code ZipEntry} for the given entry name or 501 * the versioned entry name or {@code null} if not found 502 * 503 * @throws IllegalStateException 504 * may be thrown if the jar file has been closed 505 * 506 * @see java.util.zip.ZipEntry 507 * 508 * @implSpec 509 * <div class="block"> 510 * This implementation may return a versioned entry for the requested name 511 * even if there is not a corresponding base entry. This can occur 512 * if there is a private or package-private versioned entry that matches. 513 * If a subclass overrides this method, assure that the override method 514 * invokes {@code super.getEntry(name)} to obtain all versioned entries. 515 * </div> 516 */ 517 public ZipEntry getEntry(String name) { 518 ZipEntry ze = super.getEntry(name); 519 if (ze != null) { 520 return new JarFileEntry(ze); 521 } 522 // no matching base entry, but maybe there is a versioned entry, 523 // like a new private class 524 if (isMultiRelease()) { 525 ze = new ZipEntry(name); 526 ZipEntry vze = getVersionedEntry(ze); 527 if (ze != vze) { 528 return new JarFileEntry(name, vze); 529 } 530 } 531 return null; 532 } 533 534 private class JarEntryIterator implements Enumeration<JarEntry>, 535 Iterator<JarEntry> 536 { 537 final Enumeration<? extends ZipEntry> e = JarFile.super.entries(); 538 ZipEntry ze; 539 540 public boolean hasNext() { 541 if (notVersioned) { 542 return e.hasMoreElements(); 543 } 544 if (ze != null) { 545 return true; 546 } 547 return findNext(); 548 } 549 550 private boolean findNext() { 551 while (e.hasMoreElements()) { 552 ZipEntry ze2 = e.nextElement(); 553 if (!ze2.getName().startsWith(META_INF_VERSIONS)) { 554 ze = ze2; 555 return true; 556 } 557 } 558 return false; 559 } 560 561 public JarEntry next() { 562 ZipEntry ze2; 563 564 if (notVersioned) { 565 ze2 = e.nextElement(); 566 return new JarFileEntry(ze2.getName(), ze2); 567 } 568 if (ze != null || findNext()) { 569 ze2 = ze; 570 ze = null; 571 return new JarFileEntry(ze2); 572 } 573 throw new NoSuchElementException(); 574 } 575 576 public boolean hasMoreElements() { 577 return hasNext(); 578 } 579 580 public JarEntry nextElement() { 581 return next(); 582 } 583 584 public Iterator<JarEntry> asIterator() { 585 return this; 586 } 587 } 588 589 /** 590 * Returns an enumeration of the jar file entries. The set of entries 591 * returned depends on whether or not the jar file is a multi-release jar 592 * file, and on the constructor used to create the {@code JarFile}. If the 593 * jar file is not a multi-release jar file, all entries are returned, 594 * regardless of how the {@code JarFile} is created. If the constructor 595 * does not take a {@code Release} argument, all entries are returned. 596 * If the jar file is a multi-release jar file and the constructor takes a 597 * {@code Release} argument, then the set of entries returned is equivalent 598 * to the set of entries that would be returned if the set was built by 599 * invoking {@link JarFile#getEntry(String)} or 600 * {@link JarFile#getJarEntry(String)} with the name of each base entry in 601 * the jar file. A base entry is an entry whose path name does not start 602 * with "META-INF/versions/". 603 * 604 * @return an enumeration of the jar file entries 605 * @throws IllegalStateException 606 * may be thrown if the jar file has been closed 607 */ 608 public Enumeration<JarEntry> entries() { 609 return new JarEntryIterator(); 610 } 611 612 /** 613 * Returns an ordered {@code Stream} over all the jar file entries. 614 * Entries appear in the {@code Stream} in the order they appear in 615 * the central directory of the jar file. The set of entries 616 * returned depends on whether or not the jar file is a multi-release jar 617 * file, and on the constructor used to create the {@code JarFile}. If the 618 * jar file is not a multi-release jar file, all entries are returned, 619 * regardless of how the {@code JarFile} is created. If the constructor 620 * does not take a {@code Release} argument, all entries are returned. 621 * If the jar file is a multi-release jar file and the constructor takes a 622 * {@code Release} argument, then the set of entries returned is equivalent 623 * to the set of entries that would be returned if the set was built by 624 * invoking {@link JarFile#getEntry(String)} or 625 * {@link JarFile#getJarEntry(String)} with the name of each base entry in 626 * the jar file. A base entry is an entry whose path name does not start 627 * with "META-INF/versions/". 628 * @return an ordered {@code Stream} of entries in this jar file 629 * @throws IllegalStateException if the jar file has been closed 630 * @since 1.8 631 */ 632 public Stream<JarEntry> stream() { 633 return StreamSupport.stream(Spliterators.spliterator( 634 new JarEntryIterator(), size(), 635 Spliterator.ORDERED | Spliterator.DISTINCT | 636 Spliterator.IMMUTABLE | Spliterator.NONNULL), false); 637 } 638 639 private ZipEntry searchForVersionedEntry(final int version, String name) { 640 ZipEntry vze = null; 641 String sname = "/" + name; 642 int i = version; 643 while (i > BASE_VERSION) { 644 vze = super.getEntry(META_INF_VERSIONS + i + sname); 645 if (vze != null) break; 646 i--; 647 } 648 return vze; 649 } 650 651 private ZipEntry getVersionedEntry(ZipEntry ze) { 652 ZipEntry vze = null; 653 if (version > BASE_VERSION && !ze.isDirectory()) { 654 String name = ze.getName(); 655 if (!name.startsWith(META_INF)) { 656 vze = searchForVersionedEntry(version, name); 657 } 658 } 659 return vze == null ? ze : vze; 660 } 661 662 private class JarFileEntry extends JarEntry { 663 final private String name; 664 665 JarFileEntry(ZipEntry ze) { 666 super(isMultiRelease() ? getVersionedEntry(ze) : ze); 667 this.name = ze.getName(); 668 } 669 JarFileEntry(String name, ZipEntry vze) { 670 super(vze); 671 this.name = name; 672 } 673 public Attributes getAttributes() throws IOException { 674 Manifest man = JarFile.this.getManifest(); 675 if (man != null) { 676 return man.getAttributes(super.getName()); 677 } else { 678 return null; 679 } 680 } 681 public Certificate[] getCertificates() { 682 try { 683 maybeInstantiateVerifier(); 684 } catch (IOException e) { 685 throw new RuntimeException(e); 686 } 687 if (certs == null && jv != null) { 688 certs = jv.getCerts(JarFile.this, reifiedEntry()); 689 } 690 return certs == null ? null : certs.clone(); 691 } 692 public CodeSigner[] getCodeSigners() { 693 try { 694 maybeInstantiateVerifier(); 695 } catch (IOException e) { 696 throw new RuntimeException(e); 697 } 698 if (signers == null && jv != null) { 699 signers = jv.getCodeSigners(JarFile.this, reifiedEntry()); 700 } 701 return signers == null ? null : signers.clone(); 702 } 703 JarFileEntry reifiedEntry() { 704 if (isMultiRelease()) { 705 String entryName = super.getName(); 706 return entryName.equals(this.name) ? this : new JarFileEntry(entryName, this); 707 } 708 return this; 709 } 710 711 @Override 712 public String getName() { 713 return name; 714 } 715 } 716 717 /* 718 * Ensures that the JarVerifier has been created if one is 719 * necessary (i.e., the jar appears to be signed.) This is done as 720 * a quick check to avoid processing of the manifest for unsigned 721 * jars. 722 */ 723 private void maybeInstantiateVerifier() throws IOException { 724 if (jv != null) { 725 return; 726 } 727 728 if (verify) { 729 String[] names = getMetaInfEntryNames(); 730 if (names != null) { 731 for (String nameLower : names) { 732 String name = nameLower.toUpperCase(Locale.ENGLISH); 733 if (name.endsWith(".DSA") || 734 name.endsWith(".RSA") || 735 name.endsWith(".EC") || 736 name.endsWith(".SF")) { 737 // Assume since we found a signature-related file 738 // that the jar is signed and that we therefore 739 // need a JarVerifier and Manifest 740 getManifest(); 741 return; 742 } 743 } 744 } 745 // No signature-related files; don't instantiate a 746 // verifier 747 verify = false; 748 } 749 } 750 751 752 /* 753 * Initializes the verifier object by reading all the manifest 754 * entries and passing them to the verifier. 755 */ 756 private void initializeVerifier() { 757 ManifestEntryVerifier mev = null; 758 759 // Verify "META-INF/" entries... 760 try { 761 String[] names = getMetaInfEntryNames(); 762 if (names != null) { 763 for (String name : names) { 764 String uname = name.toUpperCase(Locale.ENGLISH); 765 if (MANIFEST_NAME.equals(uname) 766 || SignatureFileVerifier.isBlockOrSF(uname)) { 767 JarEntry e = getJarEntry(name); 768 if (e == null) { 769 throw new JarException("corrupted jar file"); 770 } 771 if (mev == null) { 772 mev = new ManifestEntryVerifier 773 (getManifestFromReference()); 774 } 775 byte[] b = getBytes(e); 776 if (b != null && b.length > 0) { 777 jv.beginEntry(e, mev); 778 jv.update(b.length, b, 0, b.length, mev); 779 jv.update(-1, null, 0, 0, mev); 780 } 781 } 782 } 783 } 784 } catch (IOException ex) { 785 // if we had an error parsing any blocks, just 786 // treat the jar file as being unsigned 787 jv = null; 788 verify = false; 789 if (JarVerifier.debug != null) { 790 JarVerifier.debug.println("jarfile parsing error!"); 791 ex.printStackTrace(); 792 } 793 } 794 795 // if after initializing the verifier we have nothing 796 // signed, we null it out. 797 798 if (jv != null) { 799 800 jv.doneWithMeta(); 801 if (JarVerifier.debug != null) { 802 JarVerifier.debug.println("done with meta!"); 803 } 804 805 if (jv.nothingToVerify()) { 806 if (JarVerifier.debug != null) { 807 JarVerifier.debug.println("nothing to verify!"); 808 } 809 jv = null; 810 verify = false; 811 } 812 } 813 } 814 815 /* 816 * Reads all the bytes for a given entry. Used to process the 817 * META-INF files. 818 */ 819 private byte[] getBytes(ZipEntry ze) throws IOException { 820 try (InputStream is = super.getInputStream(ze)) { 821 int len = (int)ze.getSize(); 822 int bytesRead; 823 byte[] b; 824 // trust specified entry sizes when reasonably small 825 if (len != -1 && len <= 65535) { 826 b = new byte[len]; 827 bytesRead = is.readNBytes(b, 0, len); 828 } else { 829 b = is.readAllBytes(); 830 bytesRead = b.length; 831 } 832 if (len != -1 && len != bytesRead) { 833 throw new EOFException("Expected:" + len + ", read:" + bytesRead); 834 } 835 return b; 836 } 837 } 838 839 /** 840 * Returns an input stream for reading the contents of the specified 841 * zip file entry. 842 * @param ze the zip file entry 843 * @return an input stream for reading the contents of the specified 844 * zip file entry 845 * @throws ZipException if a zip file format error has occurred 846 * @throws IOException if an I/O error has occurred 847 * @throws SecurityException if any of the jar file entries 848 * are incorrectly signed. 849 * @throws IllegalStateException 850 * may be thrown if the jar file has been closed 851 */ 852 public synchronized InputStream getInputStream(ZipEntry ze) 853 throws IOException 854 { 855 maybeInstantiateVerifier(); 856 if (jv == null) { 857 return super.getInputStream(ze); 858 } 859 if (!jvInitialized) { 860 initializeVerifier(); 861 jvInitialized = true; 862 // could be set to null after a call to 863 // initializeVerifier if we have nothing to 864 // verify 865 if (jv == null) 866 return super.getInputStream(ze); 867 } 868 869 // wrap a verifier stream around the real stream 870 return new JarVerifier.VerifierStream( 871 getManifestFromReference(), 872 verifiableEntry(ze), 873 super.getInputStream(ze), 874 jv); 875 } 876 877 private JarEntry verifiableEntry(ZipEntry ze) { 878 if (ze instanceof JarFileEntry) { 879 // assure the name and entry match for verification 880 return ((JarFileEntry)ze).reifiedEntry(); 881 } 882 ze = getJarEntry(ze.getName()); 883 if (ze instanceof JarFileEntry) { 884 return ((JarFileEntry)ze).reifiedEntry(); 885 } 886 return (JarEntry)ze; 887 } 888 889 // Statics for hand-coded Boyer-Moore search 890 private static final byte[] CLASSPATH_CHARS = 891 {'c','l','a','s','s','-','p','a','t','h', ':', ' '}; 892 893 // The bad character shift for "class-path:" 894 private static final byte[] CLASSPATH_LASTOCC; 895 896 private static final byte[] MULTIRELEASE_CHARS = 897 {'m','u','l','t','i','-','r','e','l','e', 'a', 's', 'e', ':', ' '}; 898 899 // The bad character shift for "multi-release: " 900 private static final byte[] MULTIRELEASE_LASTOCC; 901 902 static { 903 CLASSPATH_LASTOCC = new byte[128]; 904 CLASSPATH_LASTOCC[(int)'c'] = 1; 905 CLASSPATH_LASTOCC[(int)'l'] = 2; 906 CLASSPATH_LASTOCC[(int)'s'] = 5; 907 CLASSPATH_LASTOCC[(int)'-'] = 6; 908 CLASSPATH_LASTOCC[(int)'p'] = 7; 909 CLASSPATH_LASTOCC[(int)'a'] = 8; 910 CLASSPATH_LASTOCC[(int)'t'] = 9; 911 CLASSPATH_LASTOCC[(int)'h'] = 10; 912 CLASSPATH_LASTOCC[(int)':'] = 11; 913 CLASSPATH_LASTOCC[(int)' '] = 12; 914 915 MULTIRELEASE_LASTOCC = new byte[128]; 916 MULTIRELEASE_LASTOCC[(int)'m'] = 1; 917 MULTIRELEASE_LASTOCC[(int)'u'] = 2; 918 MULTIRELEASE_LASTOCC[(int)'t'] = 4; 919 MULTIRELEASE_LASTOCC[(int)'i'] = 5; 920 MULTIRELEASE_LASTOCC[(int)'-'] = 6; 921 MULTIRELEASE_LASTOCC[(int)'r'] = 7; 922 MULTIRELEASE_LASTOCC[(int)'l'] = 9; 923 MULTIRELEASE_LASTOCC[(int)'a'] = 11; 924 MULTIRELEASE_LASTOCC[(int)'s'] = 12; 925 MULTIRELEASE_LASTOCC[(int)'e'] = 13; 926 MULTIRELEASE_LASTOCC[(int)':'] = 14; 927 MULTIRELEASE_LASTOCC[(int)' '] = 15; 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} array is the precomputed bad character shifts. 966 * Since there are no repeated substrings in our search strings, 967 * the good character shifts can be replaced with a comparison. 968 */ 969 private boolean match(byte[] src, byte[] b, byte[] lastOcc) { 970 int len = src.length; 971 int last = b.length - len; 972 int i = 0; 973 next: 974 while (i <= last) { 975 for (int j = (len - 1); j >= 0; j--) { 976 byte c = b[i + j]; 977 if (c >= 'A' && c <= 'Z') { 978 c += 32; 979 } 980 if (c != src[j]) { 981 int goodShift = (j < len - 1) ? len : 1; 982 i += Math.max(j + 1 - lastOcc[c&0x7F], goodShift); 983 continue next; 984 } 985 } 986 return true; 987 } 988 return false; 989 } 990 991 /** 992 * On first invocation, check if the JAR file has the Class-Path 993 * and the Multi-Release attribute. A no-op on subsequent calls. 994 */ 995 private void checkForSpecialAttributes() throws IOException { 996 if (hasCheckedSpecialAttributes) { 997 return; 998 } 999 synchronized (this) { 1000 if (hasCheckedSpecialAttributes) { 1001 return; 1002 } 1003 JarEntry manEntry = getManEntry(); 1004 if (manEntry != null) { 1005 byte[] b = getBytes(manEntry); 1006 hasClassPathAttribute = match(CLASSPATH_CHARS, b, 1007 CLASSPATH_LASTOCC); 1008 // is this a multi-release jar file 1009 if (MULTI_RELEASE_ENABLED && version != BASE_VERSION) { 1010 isMultiRelease = match(MULTIRELEASE_CHARS, b, 1011 MULTIRELEASE_LASTOCC); 1012 } 1013 } 1014 hasCheckedSpecialAttributes = true; 1015 } 1016 } 1017 1018 private synchronized void ensureInitialization() { 1019 try { 1020 maybeInstantiateVerifier(); 1021 } catch (IOException e) { 1022 throw new RuntimeException(e); 1023 } 1024 if (jv != null && !jvInitialized) { 1025 initializeVerifier(); 1026 jvInitialized = true; 1027 } 1028 } 1029 1030 JarEntry newEntry(ZipEntry ze) { 1031 return new JarFileEntry(ze); 1032 } 1033 1034 Enumeration<String> entryNames(CodeSource[] cs) { 1035 ensureInitialization(); 1036 if (jv != null) { 1037 return jv.entryNames(this, cs); 1038 } 1039 1040 /* 1041 * JAR file has no signed content. Is there a non-signing 1042 * code source? 1043 */ 1044 boolean includeUnsigned = false; 1045 for (CodeSource c : cs) { 1046 if (c.getCodeSigners() == null) { 1047 includeUnsigned = true; 1048 break; 1049 } 1050 } 1051 if (includeUnsigned) { 1052 return unsignedEntryNames(); 1053 } else { 1054 return new Enumeration<>() { 1055 1056 public boolean hasMoreElements() { 1057 return false; 1058 } 1059 1060 public String nextElement() { 1061 throw new NoSuchElementException(); 1062 } 1063 }; 1064 } 1065 } 1066 1067 /** 1068 * Returns an enumeration of the zip file entries 1069 * excluding internal JAR mechanism entries and including 1070 * signed entries missing from the ZIP directory. 1071 */ 1072 Enumeration<JarEntry> entries2() { 1073 ensureInitialization(); 1074 if (jv != null) { 1075 return jv.entries2(this, super.entries()); 1076 } 1077 1078 // screen out entries which are never signed 1079 final Enumeration<? extends ZipEntry> enum_ = super.entries(); 1080 return new Enumeration<>() { 1081 1082 ZipEntry entry; 1083 1084 public boolean hasMoreElements() { 1085 if (entry != null) { 1086 return true; 1087 } 1088 while (enum_.hasMoreElements()) { 1089 ZipEntry ze = enum_.nextElement(); 1090 if (JarVerifier.isSigningRelated(ze.getName())) { 1091 continue; 1092 } 1093 entry = ze; 1094 return true; 1095 } 1096 return false; 1097 } 1098 1099 public JarFileEntry nextElement() { 1100 if (hasMoreElements()) { 1101 ZipEntry ze = entry; 1102 entry = null; 1103 return new JarFileEntry(ze); 1104 } 1105 throw new NoSuchElementException(); 1106 } 1107 }; 1108 } 1109 1110 CodeSource[] getCodeSources(URL url) { 1111 ensureInitialization(); 1112 if (jv != null) { 1113 return jv.getCodeSources(this, url); 1114 } 1115 1116 /* 1117 * JAR file has no signed content. Is there a non-signing 1118 * code source? 1119 */ 1120 Enumeration<String> unsigned = unsignedEntryNames(); 1121 if (unsigned.hasMoreElements()) { 1122 return new CodeSource[]{JarVerifier.getUnsignedCS(url)}; 1123 } else { 1124 return null; 1125 } 1126 } 1127 1128 private Enumeration<String> unsignedEntryNames() { 1129 final Enumeration<JarEntry> entries = entries(); 1130 return new Enumeration<>() { 1131 1132 String name; 1133 1134 /* 1135 * Grab entries from ZIP directory but screen out 1136 * metadata. 1137 */ 1138 public boolean hasMoreElements() { 1139 if (name != null) { 1140 return true; 1141 } 1142 while (entries.hasMoreElements()) { 1143 String value; 1144 ZipEntry e = entries.nextElement(); 1145 value = e.getName(); 1146 if (e.isDirectory() || JarVerifier.isSigningRelated(value)) { 1147 continue; 1148 } 1149 name = value; 1150 return true; 1151 } 1152 return false; 1153 } 1154 1155 public String nextElement() { 1156 if (hasMoreElements()) { 1157 String value = name; 1158 name = null; 1159 return value; 1160 } 1161 throw new NoSuchElementException(); 1162 } 1163 }; 1164 } 1165 1166 CodeSource getCodeSource(URL url, String name) { 1167 ensureInitialization(); 1168 if (jv != null) { 1169 if (jv.eagerValidation) { 1170 CodeSource cs = null; 1171 JarEntry je = getJarEntry(name); 1172 if (je != null) { 1173 cs = jv.getCodeSource(url, this, je); 1174 } else { 1175 cs = jv.getCodeSource(url, name); 1176 } 1177 return cs; 1178 } else { 1179 return jv.getCodeSource(url, name); 1180 } 1181 } 1182 1183 return JarVerifier.getUnsignedCS(url); 1184 } 1185 1186 void setEagerValidation(boolean eager) { 1187 try { 1188 maybeInstantiateVerifier(); 1189 } catch (IOException e) { 1190 throw new RuntimeException(e); 1191 } 1192 if (jv != null) { 1193 jv.setEagerValidation(eager); 1194 } 1195 } 1196 1197 List<Object> getManifestDigests() { 1198 ensureInitialization(); 1199 if (jv != null) { 1200 return jv.getManifestDigests(); 1201 } 1202 return new ArrayList<>(); 1203 } 1204 }