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