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 27 package java.util.jar; 28 29 import java.io.*; 30 import java.lang.ref.SoftReference; 31 import java.net.URL; 32 import java.util.*; 33 import java.util.zip.*; 34 import java.security.CodeSigner; 35 import java.security.cert.Certificate; 36 import java.security.AccessController; 37 import java.security.CodeSource; 38 import sun.misc.IOUtils; 39 import sun.security.action.GetPropertyAction; 40 import sun.security.util.ManifestEntryVerifier; 41 /* ----- BEGIN android ----- 42 import 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 */ 63 public 64 class 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 synchronized 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 private native String[] getMetaInfEntryNames(); 202 203 /** 204 * Returns the <code>JarEntry</code> for the given entry name or 205 * <code>null</code> if not found. 206 * 207 * @param name the jar file entry name 208 * @return the <code>JarEntry</code> for the given entry name or 209 * <code>null</code> if not found. 210 * 211 * @throws IllegalStateException 212 * may be thrown if the jar file has been closed 213 * 214 * @see java.util.jar.JarEntry 215 */ 216 public JarEntry getJarEntry(String name) { 217 return (JarEntry)getEntry(name); 218 } 219 220 /** 221 * Returns the <code>ZipEntry</code> for the given entry name or 222 * <code>null</code> if not found. 223 * 224 * @param name the jar file entry name 225 * @return the <code>ZipEntry</code> for the given entry name or 226 * <code>null</code> if not found 227 * 228 * @throws IllegalStateException 229 * may be thrown if the jar file has been closed 230 * 231 * @see java.util.zip.ZipEntry 232 */ 233 public ZipEntry getEntry(String name) { 234 ZipEntry ze = super.getEntry(name); 235 if (ze != null) { 236 return new JarFileEntry(ze); 237 } 238 return null; 239 } 240 241 /** 242 * Returns an enumeration of the zip file entries. 243 */ 244 public Enumeration<JarEntry> entries() { 245 final Enumeration enum_ = super.entries(); 246 return new Enumeration<JarEntry>() { 247 public boolean hasMoreElements() { 248 return enum_.hasMoreElements(); 249 } 250 public JarFileEntry nextElement() { 251 ZipEntry ze = (ZipEntry)enum_.nextElement(); 252 return new JarFileEntry(ze); 253 } 254 }; 255 } 256 257 private class JarFileEntry extends JarEntry { 258 JarFileEntry(ZipEntry ze) { 259 super(ze); 260 } 261 public Attributes getAttributes() throws IOException { 262 Manifest man = JarFile.this.getManifest(); 263 if (man != null) { 264 return man.getAttributes(getName()); 265 } else { 266 return null; 267 } 268 } 269 public Certificate[] getCertificates() { 270 try { 271 maybeInstantiateVerifier(); 272 } catch (IOException e) { 273 throw new RuntimeException(e); 274 } 275 if (certs == null && jv != null) { 276 certs = jv.getCerts(JarFile.this, this); 277 } 278 return certs == null ? null : certs.clone(); 279 } 280 public CodeSigner[] getCodeSigners() { 281 try { 282 maybeInstantiateVerifier(); 283 } catch (IOException e) { 284 throw new RuntimeException(e); 285 } 286 if (signers == null && jv != null) { 287 signers = jv.getCodeSigners(JarFile.this, this); 288 } 289 return signers == null ? null : signers.clone(); 290 } 291 } 292 293 /* 294 * Ensures that the JarVerifier has been created if one is 295 * necessary (i.e., the jar appears to be signed.) This is done as 296 * a quick check to avoid processing of the manifest for unsigned 297 * jars. 298 */ 299 private void maybeInstantiateVerifier() throws IOException { 300 if (jv != null) { 301 return; 302 } 303 304 if (verify) { 305 String[] names = getMetaInfEntryNames(); 306 if (names != null) { 307 for (int i = 0; i < names.length; i++) { 308 String name = names[i].toUpperCase(Locale.ENGLISH); 309 if (name.endsWith(".DSA") || 310 name.endsWith(".RSA") || 311 name.endsWith(".EC") || 312 name.endsWith(".SF")) { 313 // Assume since we found a signature-related file 314 // that the jar is signed and that we therefore 315 // need a JarVerifier and Manifest 316 getManifest(); 317 return; 318 } 319 } 320 } 321 // No signature-related files; don't instantiate a 322 // verifier 323 verify = false; 324 } 325 } 326 327 328 /* 329 * Initializes the verifier object by reading all the manifest 330 * entries and passing them to the verifier. 331 */ 332 private void initializeVerifier() { 333 ManifestEntryVerifier mev = null; 334 335 // Verify "META-INF/" entries... 336 try { 337 String[] names = getMetaInfEntryNames(); 338 if (names != null) { 339 for (int i = 0; i < names.length; i++) { 340 JarEntry e = getJarEntry(names[i]); 341 if (e == null) { 342 throw new JarException("corrupted jar file"); 343 } 344 if (!e.isDirectory()) { 345 if (mev == null) { 346 mev = new ManifestEntryVerifier 347 (getManifestFromReference()); 348 } 349 byte[] b = getBytes(e); 350 if (b != null && b.length > 0) { 351 jv.beginEntry(e, mev); 352 jv.update(b.length, b, 0, b.length, mev); 353 jv.update(-1, null, 0, 0, mev); 354 } 355 } 356 } 357 } 358 } catch (IOException ex) { 359 // if we had an error parsing any blocks, just 360 // treat the jar file as being unsigned 361 jv = null; 362 verify = false; 363 if (JarVerifier.debug != null) { 364 JarVerifier.debug.println("jarfile parsing error!"); 365 ex.printStackTrace(); 366 } 367 } 368 369 // if after initializing the verifier we have nothing 370 // signed, we null it out. 371 372 if (jv != null) { 373 374 jv.doneWithMeta(); 375 if (JarVerifier.debug != null) { 376 JarVerifier.debug.println("done with meta!"); 377 } 378 379 if (jv.nothingToVerify()) { 380 if (JarVerifier.debug != null) { 381 JarVerifier.debug.println("nothing to verify!"); 382 } 383 jv = null; 384 verify = false; 385 } 386 } 387 } 388 389 /* 390 * Reads all the bytes for a given entry. Used to process the 391 * META-INF files. 392 */ 393 private byte[] getBytes(ZipEntry ze) throws IOException { 394 try (InputStream is = super.getInputStream(ze)) { 395 return IOUtils.readFully(is, (int)ze.getSize(), true); 396 } 397 } 398 399 /** 400 * Returns an input stream for reading the contents of the specified 401 * zip file entry. 402 * @param ze the zip file entry 403 * @return an input stream for reading the contents of the specified 404 * zip file entry 405 * @throws ZipException if a zip file format error has occurred 406 * @throws IOException if an I/O error has occurred 407 * @throws SecurityException if any of the jar file entries 408 * are incorrectly signed. 409 * @throws IllegalStateException 410 * may be thrown if the jar file has been closed 411 */ 412 public synchronized InputStream getInputStream(ZipEntry ze) 413 throws IOException 414 { 415 maybeInstantiateVerifier(); 416 if (jv == null) { 417 return super.getInputStream(ze); 418 } 419 if (!jvInitialized) { 420 initializeVerifier(); 421 jvInitialized = true; 422 // could be set to null after a call to 423 // initializeVerifier if we have nothing to 424 // verify 425 if (jv == null) 426 return super.getInputStream(ze); 427 } 428 429 // wrap a verifier stream around the real stream 430 return new JarVerifier.VerifierStream( 431 getManifestFromReference(), 432 ze instanceof JarFileEntry ? 433 (JarEntry) ze : getJarEntry(ze.getName()), 434 super.getInputStream(ze), 435 jv); 436 } 437 438 // Statics for hand-coded Boyer-Moore search in hasClassPathAttribute() 439 // The bad character shift for "class-path" 440 private static int[] lastOcc; 441 // The good suffix shift for "class-path" 442 private static int[] optoSft; 443 // Initialize the shift arrays to search for "class-path" 444 private static char[] src = {'c','l','a','s','s','-','p','a','t','h'}; 445 static { 446 lastOcc = new int[128]; 447 optoSft = new int[10]; 448 lastOcc[(int)'c']=1; 449 lastOcc[(int)'l']=2; 450 lastOcc[(int)'s']=5; 451 lastOcc[(int)'-']=6; 452 lastOcc[(int)'p']=7; 453 lastOcc[(int)'a']=8; 454 lastOcc[(int)'t']=9; 455 lastOcc[(int)'h']=10; 456 for (int i=0; i<9; i++) 457 optoSft[i]=10; 458 optoSft[9]=1; 459 } 460 461 private synchronized JarEntry getManEntry() { 462 if (manEntry == null) { 463 // First look up manifest entry using standard name 464 manEntry = getJarEntry(MANIFEST_NAME); 465 if (manEntry == null) { 466 // If not found, then iterate through all the "META-INF/" 467 // entries to find a match. 468 String[] names = getMetaInfEntryNames(); 469 if (names != null) { 470 for (int i = 0; i < names.length; i++) { 471 if (MANIFEST_NAME.equals( 472 names[i].toUpperCase(Locale.ENGLISH))) { 473 manEntry = getJarEntry(names[i]); 474 break; 475 } 476 } 477 } 478 } 479 } 480 return manEntry; 481 } 482 483 // Returns true iff this jar file has a manifest with a class path 484 // attribute. Returns false if there is no manifest or the manifest 485 // does not contain a "Class-Path" attribute. Currently exported to 486 // core libraries via sun.misc.SharedSecrets. 487 /** 488 * @hide 489 */ 490 public boolean hasClassPathAttribute() throws IOException { 491 if (computedHasClassPathAttribute) { 492 return hasClassPathAttribute; 493 } 494 495 hasClassPathAttribute = false; 496 if (!isKnownToNotHaveClassPathAttribute()) { 497 JarEntry manEntry = getManEntry(); 498 if (manEntry != null) { 499 byte[] b = getBytes(manEntry); 500 int last = b.length - src.length; 501 int i = 0; 502 next: 503 while (i<=last) { 504 for (int j=9; j>=0; j--) { 505 char c = (char) b[i+j]; 506 c = (((c-'A')|('Z'-c)) >= 0) ? (char)(c + 32) : c; 507 if (c != src[j]) { 508 i += Math.max(j + 1 - lastOcc[c&0x7F], optoSft[j]); 509 continue next; 510 } 511 } 512 hasClassPathAttribute = true; 513 break; 514 } 515 } 516 } 517 computedHasClassPathAttribute = true; 518 return hasClassPathAttribute; 519 } 520 521 private static String javaHome; 522 private static String[] jarNames; 523 private boolean isKnownToNotHaveClassPathAttribute() { 524 // Optimize away even scanning of manifest for jar files we 525 // deliver which don't have a class-path attribute. If one of 526 // these jars is changed to include such an attribute this code 527 // must be changed. 528 if (javaHome == null) { 529 javaHome = AccessController.doPrivileged( 530 new GetPropertyAction("java.home")); 531 } 532 if (jarNames == null) { 533 String[] names = new String[10]; 534 String fileSep = File.separator; 535 int i = 0; 536 names[i++] = fileSep + "rt.jar"; 537 names[i++] = fileSep + "sunrsasign.jar"; 538 names[i++] = fileSep + "jsse.jar"; 539 names[i++] = fileSep + "jce.jar"; 540 names[i++] = fileSep + "charsets.jar"; 541 names[i++] = fileSep + "dnsns.jar"; 542 names[i++] = fileSep + "ldapsec.jar"; 543 names[i++] = fileSep + "localedata.jar"; 544 names[i++] = fileSep + "sunjce_provider.jar"; 545 names[i++] = fileSep + "sunpkcs11.jar"; 546 jarNames = names; 547 } 548 549 String name = getName(); 550 String localJavaHome = javaHome; 551 if (name.startsWith(localJavaHome)) { 552 String[] names = jarNames; 553 for (int i = 0; i < names.length; i++) { 554 if (name.endsWith(names[i])) { 555 return true; 556 } 557 } 558 } 559 return false; 560 } 561 562 private synchronized void ensureInitialization() { 563 try { 564 maybeInstantiateVerifier(); 565 } catch (IOException e) { 566 throw new RuntimeException(e); 567 } 568 if (jv != null && !jvInitialized) { 569 initializeVerifier(); 570 jvInitialized = true; 571 } 572 } 573 574 JarEntry newEntry(ZipEntry ze) { 575 return new JarFileEntry(ze); 576 } 577 578 private Enumeration<String> unsignedEntryNames() { 579 final Enumeration entries = entries(); 580 return new Enumeration<String>() { 581 582 String name; 583 584 /* 585 * Grab entries from ZIP directory but screen out 586 * metadata. 587 */ 588 public boolean hasMoreElements() { 589 if (name != null) { 590 return true; 591 } 592 while (entries.hasMoreElements()) { 593 String value; 594 ZipEntry e = (ZipEntry) entries.nextElement(); 595 value = e.getName(); 596 if (e.isDirectory() || JarVerifier.isSigningRelated(value)) { 597 continue; 598 } 599 name = value; 600 return true; 601 } 602 return false; 603 } 604 605 public String nextElement() { 606 if (hasMoreElements()) { 607 String value = name; 608 name = null; 609 return value; 610 } 611 throw new NoSuchElementException(); 612 } 613 }; 614 } 615 } 616