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