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