1 /* 2 * Copyright (c) 1997, 2011, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package java.util.jar; 27 28 import java.io.*; 29 import java.lang.ref.SoftReference; 30 import java.net.URL; 31 import java.util.*; 32 import java.util.zip.*; 33 import java.security.CodeSigner; 34 import java.security.cert.Certificate; 35 import java.security.AccessController; 36 import java.security.CodeSource; 37 import sun.misc.IOUtils; 38 import sun.security.action.GetPropertyAction; 39 import sun.security.util.ManifestEntryVerifier; 40 import sun.misc.SharedSecrets; 41 42 /** 43 * The <code>JarFile</code> class is used to read the contents of a jar file 44 * from any file that can be opened with <code>java.io.RandomAccessFile</code>. 45 * It extends the class <code>java.util.zip.ZipFile</code> with support 46 * for reading an optional <code>Manifest</code> entry. The 47 * <code>Manifest</code> can be used to specify meta-information about the 48 * jar file and its entries. 49 * 50 * <p> Unless otherwise noted, passing a <tt>null</tt> argument to a constructor 51 * or method in this class will cause a {@link NullPointerException} to be 52 * thrown. 53 * 54 * @author David Connelly 55 * @see Manifest 56 * @see java.util.zip.ZipFile 57 * @see java.util.jar.JarEntry 58 * @since 1.2 59 */ 60 public 61 class JarFile extends ZipFile { 62 private SoftReference<Manifest> manRef; 63 private JarEntry manEntry; 64 private JarVerifier jv; 65 private boolean jvInitialized; 66 private boolean verify; 67 private boolean computedHasClassPathAttribute; 68 private boolean hasClassPathAttribute; 69 70 // Set up JavaUtilJarAccess in SharedSecrets 71 static { 72 SharedSecrets.setJavaUtilJarAccess(new JavaUtilJarAccessImpl()); 73 } 74 75 /** 76 * The JAR manifest file name. 77 */ 78 public static final String MANIFEST_NAME = "META-INF/MANIFEST.MF"; 79 80 /** 81 * Creates a new <code>JarFile</code> to read from the specified 82 * file <code>name</code>. The <code>JarFile</code> will be verified if 83 * it is signed. 84 * @param name the name of the jar file to be opened for reading 85 * @throws IOException if an I/O error has occurred 86 * @throws SecurityException if access to the file is denied 87 * by the SecurityManager 88 */ 89 public JarFile(String name) throws IOException { 90 this(new File(name), true, ZipFile.OPEN_READ); 91 } 92 93 /** 94 * Creates a new <code>JarFile</code> to read from the specified 95 * file <code>name</code>. 96 * @param name the name of the jar file to be opened for reading 97 * @param verify whether or not to verify the jar file if 98 * it is signed. 99 * @throws IOException if an I/O error has occurred 100 * @throws SecurityException if access to the file is denied 101 * by the SecurityManager 102 */ 103 public JarFile(String name, boolean verify) throws IOException { 104 this(new File(name), verify, ZipFile.OPEN_READ); 105 } 106 107 /** 108 * Creates a new <code>JarFile</code> to read from the specified 109 * <code>File</code> object. The <code>JarFile</code> will be verified if 110 * it is signed. 111 * @param file the jar file to be opened for reading 112 * @throws IOException if an I/O error has occurred 113 * @throws SecurityException if access to the file is denied 114 * by the SecurityManager 115 */ 116 public JarFile(File file) throws IOException { 117 this(file, true, ZipFile.OPEN_READ); 118 } 119 120 121 /** 122 * Creates a new <code>JarFile</code> to read from the specified 123 * <code>File</code> object. 124 * @param file the jar file to be opened for reading 125 * @param verify whether or not to verify the jar file if 126 * it is signed. 127 * @throws IOException if an I/O error has occurred 128 * @throws SecurityException if access to the file is denied 129 * by the SecurityManager. 130 */ 131 public JarFile(File file, boolean verify) throws IOException { 132 this(file, verify, ZipFile.OPEN_READ); 133 } 134 135 136 /** 137 * Creates a new <code>JarFile</code> to read from the specified 138 * <code>File</code> object in the specified mode. The mode argument 139 * must be either <tt>OPEN_READ</tt> or <tt>OPEN_READ | OPEN_DELETE</tt>. 140 * 141 * @param file the jar file to be opened for reading 142 * @param verify whether or not to verify the jar file if 143 * it is signed. 144 * @param mode the mode in which the file is to be opened 145 * @throws IOException if an I/O error has occurred 146 * @throws IllegalArgumentException 147 * if the <tt>mode</tt> argument is invalid 148 * @throws SecurityException if access to the file is denied 149 * by the SecurityManager 150 * @since 1.3 151 */ 152 public JarFile(File file, boolean verify, int mode) throws IOException { 153 super(file, mode); 154 this.verify = verify; 155 } 156 157 /** 158 * Returns the jar file manifest, or <code>null</code> if none. 159 * 160 * @return the jar file manifest, or <code>null</code> if none 161 * 162 * @throws IllegalStateException 163 * may be thrown if the jar file has been closed 164 */ 165 public Manifest getManifest() throws IOException { 166 return getManifestFromReference(); 167 } 168 169 private Manifest getManifestFromReference() throws IOException { 170 Manifest man = manRef != null ? manRef.get() : null; 171 172 if (man == null) { 173 174 JarEntry manEntry = getManEntry(); 175 176 // If found then load the manifest 177 if (manEntry != null) { 178 if (verify) { 179 byte[] b = getBytes(manEntry); 180 man = new Manifest(new ByteArrayInputStream(b)); 181 if (!jvInitialized) { 182 jv = new JarVerifier(b); 183 } 184 } else { 185 man = new Manifest(super.getInputStream(manEntry)); 186 } 187 manRef = new SoftReference(man); 188 } 189 } 190 return man; 191 } 192 193 private native String[] getMetaInfEntryNames(); 194 195 /** 196 * Returns the <code>JarEntry</code> for the given entry name or 197 * <code>null</code> if not found. 198 * 199 * @param name the jar file entry name 200 * @return the <code>JarEntry</code> for the given entry name or 201 * <code>null</code> if not found. 202 * 203 * @throws IllegalStateException 204 * may be thrown if the jar file has been closed 205 * 206 * @see java.util.jar.JarEntry 207 */ 208 public JarEntry getJarEntry(String name) { 209 return (JarEntry)getEntry(name); 210 } 211 212 /** 213 * Returns the <code>ZipEntry</code> for the given entry name or 214 * <code>null</code> if not found. 215 * 216 * @param name the jar file entry name 217 * @return the <code>ZipEntry</code> for the given entry name or 218 * <code>null</code> if not found 219 * 220 * @throws IllegalStateException 221 * may be thrown if the jar file has been closed 222 * 223 * @see java.util.zip.ZipEntry 224 */ 225 public ZipEntry getEntry(String name) { 226 ZipEntry ze = super.getEntry(name); 227 if (ze != null) { 228 return new JarFileEntry(ze); 229 } 230 return null; 231 } 232 233 /** 234 * Returns an enumeration of the zip file entries. 235 */ 236 public Enumeration<JarEntry> entries() { 237 final Enumeration enum_ = super.entries(); 238 return new Enumeration<JarEntry>() { 239 public boolean hasMoreElements() { 240 return enum_.hasMoreElements(); 241 } 242 public JarFileEntry nextElement() { 243 ZipEntry ze = (ZipEntry)enum_.nextElement(); 244 return new JarFileEntry(ze); 245 } 246 }; 247 } 248 249 private class JarFileEntry extends JarEntry { 250 JarFileEntry(ZipEntry ze) { 251 super(ze); 252 } 253 public Attributes getAttributes() throws IOException { 254 Manifest man = JarFile.this.getManifest(); 255 if (man != null) { 256 return man.getAttributes(getName()); 257 } else { 258 return null; 259 } 260 } 261 public Certificate[] getCertificates() { 262 try { 263 maybeInstantiateVerifier(); 264 } catch (IOException e) { 265 throw new RuntimeException(e); 266 } 267 if (certs == null && jv != null) { 268 certs = jv.getCerts(JarFile.this, this); 269 } 270 return certs == null ? null : certs.clone(); 271 } 272 public CodeSigner[] getCodeSigners() { 273 try { 274 maybeInstantiateVerifier(); 275 } catch (IOException e) { 276 throw new RuntimeException(e); 277 } 278 if (signers == null && jv != null) { 279 signers = jv.getCodeSigners(JarFile.this, this); 280 } 281 return signers == null ? null : signers.clone(); 282 } 283 } 284 285 /* 286 * Ensures that the JarVerifier has been created if one is 287 * necessary (i.e., the jar appears to be signed.) This is done as 288 * a quick check to avoid processing of the manifest for unsigned 289 * jars. 290 */ 291 private void maybeInstantiateVerifier() throws IOException { 292 if (jv != null) { 293 return; 294 } 295 296 if (verify) { 297 String[] names = getMetaInfEntryNames(); 298 if (names != null) { 299 for (int i = 0; i < names.length; i++) { 300 String name = names[i].toUpperCase(Locale.ENGLISH); 301 if (name.endsWith(".DSA") || 302 name.endsWith(".RSA") || 303 name.endsWith(".EC") || 304 name.endsWith(".SF")) { 305 // Assume since we found a signature-related file 306 // that the jar is signed and that we therefore 307 // need a JarVerifier and Manifest 308 getManifest(); 309 return; 310 } 311 } 312 } 313 // No signature-related files; don't instantiate a 314 // verifier 315 verify = false; 316 } 317 } 318 319 320 /* 321 * Initializes the verifier object by reading all the manifest 322 * entries and passing them to the verifier. 323 */ 324 private void initializeVerifier() { 325 ManifestEntryVerifier mev = null; 326 327 // Verify "META-INF/" entries... 328 try { 329 String[] names = getMetaInfEntryNames(); 330 if (names != null) { 331 for (int i = 0; i < names.length; i++) { 332 JarEntry e = getJarEntry(names[i]); 333 if (e == null) { 334 throw new JarException("corrupted jar file"); 335 } 336 if (!e.isDirectory()) { 337 if (mev == null) { 338 mev = new ManifestEntryVerifier 339 (getManifestFromReference()); 340 } 341 byte[] b = getBytes(e); 342 if (b != null && b.length > 0) { 343 jv.beginEntry(e, mev); 344 jv.update(b.length, b, 0, b.length, mev); 345 jv.update(-1, null, 0, 0, mev); 346 } 347 } 348 } 349 } 350 } catch (IOException ex) { 351 // if we had an error parsing any blocks, just 352 // treat the jar file as being unsigned 353 jv = null; 354 verify = false; 355 if (JarVerifier.debug != null) { 356 JarVerifier.debug.println("jarfile parsing error!"); 357 ex.printStackTrace(); 358 } 359 } 360 361 // if after initializing the verifier we have nothing 362 // signed, we null it out. 363 364 if (jv != null) { 365 366 jv.doneWithMeta(); 367 if (JarVerifier.debug != null) { 368 JarVerifier.debug.println("done with meta!"); 369 } 370 371 if (jv.nothingToVerify()) { 372 if (JarVerifier.debug != null) { 373 JarVerifier.debug.println("nothing to verify!"); 374 } 375 jv = null; 376 verify = false; 377 } 378 } 379 } 380 381 /* 382 * Reads all the bytes for a given entry. Used to process the 383 * META-INF files. 384 */ 385 private byte[] getBytes(ZipEntry ze) throws IOException { 386 try (InputStream is = super.getInputStream(ze)) { 387 return IOUtils.readFully(is, (int)ze.getSize(), true); 388 } 389 } 390 391 /** 392 * Returns an input stream for reading the contents of the specified 393 * zip file entry. 394 * @param ze the zip file entry 395 * @return an input stream for reading the contents of the specified 396 * zip file entry 397 * @throws ZipException if a zip file format error has occurred 398 * @throws IOException if an I/O error has occurred 399 * @throws SecurityException if any of the jar file entries 400 * are incorrectly signed. 401 * @throws IllegalStateException 402 * may be thrown if the jar file has been closed 403 */ 404 public synchronized InputStream getInputStream(ZipEntry ze) 405 throws IOException 406 { 407 maybeInstantiateVerifier(); 408 if (jv == null) { 409 return super.getInputStream(ze); 410 } 411 if (!jvInitialized) { 412 initializeVerifier(); 413 jvInitialized = true; 414 // could be set to null after a call to 415 // initializeVerifier if we have nothing to 416 // verify 417 if (jv == null) 418 return super.getInputStream(ze); 419 } 420 421 // wrap a verifier stream around the real stream 422 return new JarVerifier.VerifierStream( 423 getManifestFromReference(), 424 ze instanceof JarFileEntry ? 425 (JarEntry) ze : getJarEntry(ze.getName()), 426 super.getInputStream(ze), 427 jv); 428 } 429 430 // Statics for hand-coded Boyer-Moore search in hasClassPathAttribute() 431 // The bad character shift for "class-path" 432 private static int[] lastOcc; 433 // The good suffix shift for "class-path" 434 private static int[] optoSft; 435 // Initialize the shift arrays to search for "class-path" 436 private static char[] src = {'c','l','a','s','s','-','p','a','t','h'}; 437 static { 438 lastOcc = new int[128]; 439 optoSft = new int[10]; 440 lastOcc[(int)'c']=1; 441 lastOcc[(int)'l']=2; 442 lastOcc[(int)'s']=5; 443 lastOcc[(int)'-']=6; 444 lastOcc[(int)'p']=7; 445 lastOcc[(int)'a']=8; 446 lastOcc[(int)'t']=9; 447 lastOcc[(int)'h']=10; 448 for (int i=0; i<9; i++) 449 optoSft[i]=10; 450 optoSft[9]=1; 451 } 452 453 private JarEntry getManEntry() { 454 if (manEntry == null) { 455 // First look up manifest entry using standard name 456 manEntry = getJarEntry(MANIFEST_NAME); 457 if (manEntry == null) { 458 // If not found, then iterate through all the "META-INF/" 459 // entries to find a match. 460 String[] names = getMetaInfEntryNames(); 461 if (names != null) { 462 for (int i = 0; i < names.length; i++) { 463 if (MANIFEST_NAME.equals( 464 names[i].toUpperCase(Locale.ENGLISH))) { 465 manEntry = getJarEntry(names[i]); 466 break; 467 } 468 } 469 } 470 } 471 } 472 return manEntry; 473 } 474 475 // Returns true iff this jar file has a manifest with a class path 476 // attribute. Returns false if there is no manifest or the manifest 477 // does not contain a "Class-Path" attribute. Currently exported to 478 // core libraries via sun.misc.SharedSecrets. 479 boolean hasClassPathAttribute() throws IOException { 480 if (computedHasClassPathAttribute) { 481 return hasClassPathAttribute; 482 } 483 484 hasClassPathAttribute = false; 485 if (!isKnownToNotHaveClassPathAttribute()) { 486 JarEntry manEntry = getManEntry(); 487 if (manEntry != null) { 488 byte[] b = getBytes(manEntry); 489 int last = b.length - src.length; 490 int i = 0; 491 next: 492 while (i<=last) { 493 for (int j=9; j>=0; j--) { 494 char c = (char) b[i+j]; 495 c = (((c-'A')|('Z'-c)) >= 0) ? (char)(c + 32) : c; 496 if (c != src[j]) { 497 i += Math.max(j + 1 - lastOcc[c&0x7F], optoSft[j]); 498 continue next; 499 } 500 } 501 hasClassPathAttribute = true; 502 break; 503 } 504 } 505 } 506 computedHasClassPathAttribute = true; 507 return hasClassPathAttribute; 508 } 509 510 private static String javaHome; 511 private static String[] jarNames; 512 private boolean isKnownToNotHaveClassPathAttribute() { 513 // Optimize away even scanning of manifest for jar files we 514 // deliver which don't have a class-path attribute. If one of 515 // these jars is changed to include such an attribute this code 516 // must be changed. 517 if (javaHome == null) { 518 javaHome = AccessController.doPrivileged( 519 new GetPropertyAction("java.home")); 520 } 521 if (jarNames == null) { 522 String[] names = new String[10]; 523 String fileSep = File.separator; 524 int i = 0; 525 names[i++] = fileSep + "rt.jar"; 526 names[i++] = fileSep + "sunrsasign.jar"; 527 names[i++] = fileSep + "jsse.jar"; 528 names[i++] = fileSep + "jce.jar"; 529 names[i++] = fileSep + "charsets.jar"; 530 names[i++] = fileSep + "dnsns.jar"; 531 names[i++] = fileSep + "ldapsec.jar"; 532 names[i++] = fileSep + "localedata.jar"; 533 names[i++] = fileSep + "sunjce_provider.jar"; 534 names[i++] = fileSep + "sunpkcs11.jar"; 535 jarNames = names; 536 } 537 538 String name = getName(); 539 String localJavaHome = javaHome; 540 if (name.startsWith(localJavaHome)) { 541 String[] names = jarNames; 542 for (int i = 0; i < names.length; i++) { 543 if (name.endsWith(names[i])) { 544 return true; 545 } 546 } 547 } 548 return false; 549 } 550 551 private synchronized void ensureInitialization() { 552 try { 553 maybeInstantiateVerifier(); 554 } catch (IOException e) { 555 throw new RuntimeException(e); 556 } 557 if (jv != null && !jvInitialized) { 558 initializeVerifier(); 559 jvInitialized = true; 560 } 561 } 562 563 JarEntry newEntry(ZipEntry ze) { 564 return new JarFileEntry(ze); 565 } 566 567 Enumeration<String> entryNames(CodeSource[] cs) { 568 ensureInitialization(); 569 if (jv != null) { 570 return jv.entryNames(this, cs); 571 } 572 573 /* 574 * JAR file has no signed content. Is there a non-signing 575 * code source? 576 */ 577 boolean includeUnsigned = false; 578 for (int i = 0; i < cs.length; i++) { 579 if (cs[i].getCodeSigners() == null) { 580 includeUnsigned = true; 581 break; 582 } 583 } 584 if (includeUnsigned) { 585 return unsignedEntryNames(); 586 } else { 587 return new Enumeration<String>() { 588 589 public boolean hasMoreElements() { 590 return false; 591 } 592 593 public String nextElement() { 594 throw new NoSuchElementException(); 595 } 596 }; 597 } 598 } 599 600 /** 601 * Returns an enumeration of the zip file entries 602 * excluding internal JAR mechanism entries and including 603 * signed entries missing from the ZIP directory. 604 */ 605 Enumeration<JarEntry> entries2() { 606 ensureInitialization(); 607 if (jv != null) { 608 return jv.entries2(this, super.entries()); 609 } 610 611 // screen out entries which are never signed 612 final Enumeration enum_ = super.entries(); 613 return new Enumeration<JarEntry>() { 614 615 ZipEntry entry; 616 617 public boolean hasMoreElements() { 618 if (entry != null) { 619 return true; 620 } 621 while (enum_.hasMoreElements()) { 622 ZipEntry ze = (ZipEntry) enum_.nextElement(); 623 if (JarVerifier.isSigningRelated(ze.getName())) { 624 continue; 625 } 626 entry = ze; 627 return true; 628 } 629 return false; 630 } 631 632 public JarFileEntry nextElement() { 633 if (hasMoreElements()) { 634 ZipEntry ze = entry; 635 entry = null; 636 return new JarFileEntry(ze); 637 } 638 throw new NoSuchElementException(); 639 } 640 }; 641 } 642 643 CodeSource[] getCodeSources(URL url) { 644 ensureInitialization(); 645 if (jv != null) { 646 return jv.getCodeSources(this, url); 647 } 648 649 /* 650 * JAR file has no signed content. Is there a non-signing 651 * code source? 652 */ 653 Enumeration unsigned = unsignedEntryNames(); 654 if (unsigned.hasMoreElements()) { 655 return new CodeSource[]{JarVerifier.getUnsignedCS(url)}; 656 } else { 657 return null; 658 } 659 } 660 661 private Enumeration<String> unsignedEntryNames() { 662 final Enumeration entries = entries(); 663 return new Enumeration<String>() { 664 665 String name; 666 667 /* 668 * Grab entries from ZIP directory but screen out 669 * metadata. 670 */ 671 public boolean hasMoreElements() { 672 if (name != null) { 673 return true; 674 } 675 while (entries.hasMoreElements()) { 676 String value; 677 ZipEntry e = (ZipEntry) entries.nextElement(); 678 value = e.getName(); 679 if (e.isDirectory() || JarVerifier.isSigningRelated(value)) { 680 continue; 681 } 682 name = value; 683 return true; 684 } 685 return false; 686 } 687 688 public String nextElement() { 689 if (hasMoreElements()) { 690 String value = name; 691 name = null; 692 return value; 693 } 694 throw new NoSuchElementException(); 695 } 696 }; 697 } 698 699 CodeSource getCodeSource(URL url, String name) { 700 ensureInitialization(); 701 if (jv != null) { 702 if (jv.eagerValidation) { 703 CodeSource cs = null; 704 JarEntry je = getJarEntry(name); 705 if (je != null) { 706 cs = jv.getCodeSource(url, this, je); 707 } else { 708 cs = jv.getCodeSource(url, name); 709 } 710 return cs; 711 } else { 712 return jv.getCodeSource(url, name); 713 } 714 } 715 716 return JarVerifier.getUnsignedCS(url); 717 } 718 719 void setEagerValidation(boolean eager) { 720 try { 721 maybeInstantiateVerifier(); 722 } catch (IOException e) { 723 throw new RuntimeException(e); 724 } 725 if (jv != null) { 726 jv.setEagerValidation(eager); 727 } 728 } 729 730 List getManifestDigests() { 731 ensureInitialization(); 732 if (jv != null) { 733 return jv.getManifestDigests(); 734 } 735 return new ArrayList(); 736 } 737 }