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