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 InputStream is = super.getInputStream(ze); 387 byte[] b = IOUtils.readFully(is, (int)ze.getSize(), true); 388 is.close(); 389 return b; 390 } 391 392 /** 393 * Returns an input stream for reading the contents of the specified 394 * zip file entry. 395 * @param ze the zip file entry 396 * @return an input stream for reading the contents of the specified 397 * zip file entry 398 * @throws ZipException if a zip file format error has occurred 399 * @throws IOException if an I/O error has occurred 400 * @throws SecurityException if any of the jar file entries 401 * are incorrectly signed. 402 * @throws IllegalStateException 403 * may be thrown if the jar file has been closed 404 */ 405 public synchronized InputStream getInputStream(ZipEntry ze) 406 throws IOException 407 { 408 maybeInstantiateVerifier(); 409 if (jv == null) { 410 return super.getInputStream(ze); 411 } 412 if (!jvInitialized) { 413 initializeVerifier(); 414 jvInitialized = true; 415 // could be set to null after a call to 416 // initializeVerifier if we have nothing to 417 // verify 418 if (jv == null) 419 return super.getInputStream(ze); 420 } 421 422 // wrap a verifier stream around the real stream 423 return new JarVerifier.VerifierStream( 424 getManifestFromReference(), 425 ze instanceof JarFileEntry ? 426 (JarEntry) ze : getJarEntry(ze.getName()), 427 super.getInputStream(ze), 428 jv); 429 } 430 431 // Statics for hand-coded Boyer-Moore search in hasClassPathAttribute() 432 // The bad character shift for "class-path" 433 private static int[] lastOcc; 434 // The good suffix shift for "class-path" 435 private static int[] optoSft; 436 // Initialize the shift arrays to search for "class-path" 437 private static char[] src = {'c','l','a','s','s','-','p','a','t','h'}; 438 static { 439 lastOcc = new int[128]; 440 optoSft = new int[10]; 441 lastOcc[(int)'c']=1; 442 lastOcc[(int)'l']=2; 443 lastOcc[(int)'s']=5; 444 lastOcc[(int)'-']=6; 445 lastOcc[(int)'p']=7; 446 lastOcc[(int)'a']=8; 447 lastOcc[(int)'t']=9; 448 lastOcc[(int)'h']=10; 449 for (int i=0; i<9; i++) 450 optoSft[i]=10; 451 optoSft[9]=1; 452 } 453 454 private JarEntry getManEntry() { 455 if (manEntry == null) { 456 // First look up manifest entry using standard name 457 manEntry = getJarEntry(MANIFEST_NAME); 458 if (manEntry == null) { 459 // If not found, then iterate through all the "META-INF/" 460 // entries to find a match. 461 String[] names = getMetaInfEntryNames(); 462 if (names != null) { 463 for (int i = 0; i < names.length; i++) { 464 if (MANIFEST_NAME.equals( 465 names[i].toUpperCase(Locale.ENGLISH))) { 466 manEntry = getJarEntry(names[i]); 467 break; 468 } 469 } 470 } 471 } 472 } 473 return manEntry; 474 } 475 476 // Returns true iff this jar file has a manifest with a class path 477 // attribute. Returns false if there is no manifest or the manifest 478 // does not contain a "Class-Path" attribute. Currently exported to 479 // core libraries via sun.misc.SharedSecrets. 480 boolean hasClassPathAttribute() throws IOException { 481 if (computedHasClassPathAttribute) { 482 return hasClassPathAttribute; 483 } 484 485 hasClassPathAttribute = false; 486 if (!isKnownToNotHaveClassPathAttribute()) { 487 JarEntry manEntry = getManEntry(); 488 if (manEntry != null) { 489 byte[] b = getBytes(manEntry); 490 int last = b.length - src.length; 491 int i = 0; 492 next: 493 while (i<=last) { 494 for (int j=9; j>=0; j--) { 495 char c = (char) b[i+j]; 496 c = (((c-'A')|('Z'-c)) >= 0) ? (char)(c + 32) : c; 497 if (c != src[j]) { 498 i += Math.max(j + 1 - lastOcc[c&0x7F], optoSft[j]); 499 continue next; 500 } 501 } 502 hasClassPathAttribute = true; 503 break; 504 } 505 } 506 } 507 computedHasClassPathAttribute = true; 508 return hasClassPathAttribute; 509 } 510 511 private static String javaHome; 512 private static String[] jarNames; 513 private boolean isKnownToNotHaveClassPathAttribute() { 514 // Optimize away even scanning of manifest for jar files we 515 // deliver which don't have a class-path attribute. If one of 516 // these jars is changed to include such an attribute this code 517 // must be changed. 518 if (javaHome == null) { 519 javaHome = AccessController.doPrivileged( 520 new GetPropertyAction("java.home")); 521 } 522 if (jarNames == null) { 523 String[] names = new String[10]; 524 String fileSep = File.separator; 525 int i = 0; 526 names[i++] = fileSep + "rt.jar"; 527 names[i++] = fileSep + "sunrsasign.jar"; 528 names[i++] = fileSep + "jsse.jar"; 529 names[i++] = fileSep + "jce.jar"; 530 names[i++] = fileSep + "charsets.jar"; 531 names[i++] = fileSep + "dnsns.jar"; 532 names[i++] = fileSep + "ldapsec.jar"; 533 names[i++] = fileSep + "localedata.jar"; 534 names[i++] = fileSep + "sunjce_provider.jar"; 535 names[i++] = fileSep + "sunpkcs11.jar"; 536 jarNames = names; 537 } 538 539 String name = getName(); 540 String localJavaHome = javaHome; 541 if (name.startsWith(localJavaHome)) { 542 String[] names = jarNames; 543 for (int i = 0; i < names.length; i++) { 544 if (name.endsWith(names[i])) { 545 return true; 546 } 547 } 548 } 549 return false; 550 } 551 552 private synchronized void ensureInitialization() { 553 try { 554 maybeInstantiateVerifier(); 555 } catch (IOException e) { 556 throw new RuntimeException(e); 557 } 558 if (jv != null && !jvInitialized) { 559 initializeVerifier(); 560 jvInitialized = true; 561 } 562 } 563 564 JarEntry newEntry(ZipEntry ze) { 565 return new JarFileEntry(ze); 566 } 567 568 Enumeration<String> entryNames(CodeSource[] cs) { 569 ensureInitialization(); 570 if (jv != null) { 571 return jv.entryNames(this, cs); 572 } 573 574 /* 575 * JAR file has no signed content. Is there a non-signing 576 * code source? 577 */ 578 boolean includeUnsigned = false; 579 for (int i = 0; i < cs.length; i++) { 580 if (cs[i].getCodeSigners() == null) { 581 includeUnsigned = true; 582 break; 583 } 584 } 585 if (includeUnsigned) { 586 return unsignedEntryNames(); 587 } else { 588 return new Enumeration<String>() { 589 590 public boolean hasMoreElements() { 591 return false; 592 } 593 594 public String nextElement() { 595 throw new NoSuchElementException(); 596 } 597 }; 598 } 599 } 600 601 /** 602 * Returns an enumeration of the zip file entries 603 * excluding internal JAR mechanism entries and including 604 * signed entries missing from the ZIP directory. 605 */ 606 Enumeration<JarEntry> entries2() { 607 ensureInitialization(); 608 if (jv != null) { 609 return jv.entries2(this, super.entries()); 610 } 611 612 // screen out entries which are never signed 613 final Enumeration enum_ = super.entries(); 614 return new Enumeration<JarEntry>() { 615 616 ZipEntry entry; 617 618 public boolean hasMoreElements() { 619 if (entry != null) { 620 return true; 621 } 622 while (enum_.hasMoreElements()) { 623 ZipEntry ze = (ZipEntry) enum_.nextElement(); 624 if (JarVerifier.isSigningRelated(ze.getName())) { 625 continue; 626 } 627 entry = ze; 628 return true; 629 } 630 return false; 631 } 632 633 public JarFileEntry nextElement() { 634 if (hasMoreElements()) { 635 ZipEntry ze = entry; 636 entry = null; 637 return new JarFileEntry(ze); 638 } 639 throw new NoSuchElementException(); 640 } 641 }; 642 } 643 644 CodeSource[] getCodeSources(URL url) { 645 ensureInitialization(); 646 if (jv != null) { 647 return jv.getCodeSources(this, url); 648 } 649 650 /* 651 * JAR file has no signed content. Is there a non-signing 652 * code source? 653 */ 654 Enumeration unsigned = unsignedEntryNames(); 655 if (unsigned.hasMoreElements()) { 656 return new CodeSource[]{JarVerifier.getUnsignedCS(url)}; 657 } else { 658 return null; 659 } 660 } 661 662 private Enumeration<String> unsignedEntryNames() { 663 final Enumeration entries = entries(); 664 return new Enumeration<String>() { 665 666 String name; 667 668 /* 669 * Grab entries from ZIP directory but screen out 670 * metadata. 671 */ 672 public boolean hasMoreElements() { 673 if (name != null) { 674 return true; 675 } 676 while (entries.hasMoreElements()) { 677 String value; 678 ZipEntry e = (ZipEntry) entries.nextElement(); 679 value = e.getName(); 680 if (e.isDirectory() || JarVerifier.isSigningRelated(value)) { 681 continue; 682 } 683 name = value; 684 return true; 685 } 686 return false; 687 } 688 689 public String nextElement() { 690 if (hasMoreElements()) { 691 String value = name; 692 name = null; 693 return value; 694 } 695 throw new NoSuchElementException(); 696 } 697 }; 698 } 699 700 CodeSource getCodeSource(URL url, String name) { 701 ensureInitialization(); 702 if (jv != null) { 703 if (jv.eagerValidation) { 704 CodeSource cs = null; 705 JarEntry je = getJarEntry(name); 706 if (je != null) { 707 cs = jv.getCodeSource(url, this, je); 708 } else { 709 cs = jv.getCodeSource(url, name); 710 } 711 return cs; 712 } else { 713 return jv.getCodeSource(url, name); 714 } 715 } 716 717 return JarVerifier.getUnsignedCS(url); 718 } 719 720 void setEagerValidation(boolean eager) { 721 try { 722 maybeInstantiateVerifier(); 723 } catch (IOException e) { 724 throw new RuntimeException(e); 725 } 726 if (jv != null) { 727 jv.setEagerValidation(eager); 728 } 729 } 730 731 List getManifestDigests() { 732 ensureInitialization(); 733 if (jv != null) { 734 return jv.getManifestDigests(); 735 } 736 return new ArrayList(); 737 } 738 }