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