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