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