JarFile.java revision 45908408e773e57f09677ab5a84fd3733e156f95
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.util.*; 32import java.util.zip.*; 33import java.security.CodeSigner; 34import java.security.cert.Certificate; 35import java.security.AccessController; 36import sun.misc.IOUtils; 37import sun.security.action.GetPropertyAction; 38import sun.security.util.ManifestEntryVerifier; 39 40/** 41 * The <code>JarFile</code> class is used to read the contents of a jar file 42 * from any file that can be opened with <code>java.io.RandomAccessFile</code>. 43 * It extends the class <code>java.util.zip.ZipFile</code> with support 44 * for reading an optional <code>Manifest</code> entry. The 45 * <code>Manifest</code> can be used to specify meta-information about the 46 * jar file and its entries. 47 * 48 * <p> Unless otherwise noted, passing a <tt>null</tt> argument to a constructor 49 * or method in this class will cause a {@link NullPointerException} to be 50 * thrown. 51 * 52 * @author David Connelly 53 * @see Manifest 54 * @see java.util.zip.ZipFile 55 * @see java.util.jar.JarEntry 56 * @since 1.2 57 */ 58public 59class JarFile extends ZipFile { 60 static final String META_DIR = "META-INF/"; 61 private Manifest manifest; 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 /** 70 * The JAR manifest file name. 71 */ 72 public static final String MANIFEST_NAME = "META-INF/MANIFEST.MF"; 73 74 /** 75 * Creates a new <code>JarFile</code> to read from the specified 76 * file <code>name</code>. The <code>JarFile</code> will be verified if 77 * it is signed. 78 * @param name the name of the jar file to be opened for reading 79 * @throws IOException if an I/O error has occurred 80 * @throws SecurityException if access to the file is denied 81 * by the SecurityManager 82 */ 83 public JarFile(String name) throws IOException { 84 this(new File(name), true, ZipFile.OPEN_READ); 85 } 86 87 /** 88 * Creates a new <code>JarFile</code> to read from the specified 89 * file <code>name</code>. 90 * @param name the name of the jar file to be opened for reading 91 * @param verify whether or not to verify the jar file if 92 * it is signed. 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, boolean verify) throws IOException { 98 this(new File(name), verify, ZipFile.OPEN_READ); 99 } 100 101 /** 102 * Creates a new <code>JarFile</code> to read from the specified 103 * <code>File</code> object. The <code>JarFile</code> will be verified if 104 * it is signed. 105 * @param file the jar file to be opened for reading 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(File file) throws IOException { 111 this(file, true, ZipFile.OPEN_READ); 112 } 113 114 115 /** 116 * Creates a new <code>JarFile</code> to read from the specified 117 * <code>File</code> object. 118 * @param file the jar file to be opened for reading 119 * @param verify whether or not to verify the jar file if 120 * it is signed. 121 * @throws IOException if an I/O error has occurred 122 * @throws SecurityException if access to the file is denied 123 * by the SecurityManager. 124 */ 125 public JarFile(File file, boolean verify) throws IOException { 126 this(file, verify, ZipFile.OPEN_READ); 127 } 128 129 130 /** 131 * Creates a new <code>JarFile</code> to read from the specified 132 * <code>File</code> object in the specified mode. The mode argument 133 * must be either <tt>OPEN_READ</tt> or <tt>OPEN_READ | OPEN_DELETE</tt>. 134 * 135 * @param file the jar file to be opened for reading 136 * @param verify whether or not to verify the jar file if 137 * it is signed. 138 * @param mode the mode in which the file is to be opened 139 * @throws IOException if an I/O error has occurred 140 * @throws IllegalArgumentException 141 * if the <tt>mode</tt> argument is invalid 142 * @throws SecurityException if access to the file is denied 143 * by the SecurityManager 144 * @since 1.3 145 */ 146 public JarFile(File file, boolean verify, int mode) throws IOException { 147 super(file, mode); 148 this.verify = verify; 149 } 150 151 /** 152 * Returns the jar file manifest, or <code>null</code> if none. 153 * 154 * @return the jar file manifest, or <code>null</code> if none 155 * 156 * @throws IllegalStateException 157 * may be thrown if the jar file has been closed 158 */ 159 public Manifest getManifest() throws IOException { 160 return getManifestFromReference(); 161 } 162 163 private synchronized Manifest getManifestFromReference() throws IOException { 164 if (manifest == null) { 165 166 JarEntry manEntry = getManEntry(); 167 168 // If found then load the manifest 169 if (manEntry != null) { 170 if (verify) { 171 byte[] b = getBytes(manEntry); 172 manifest = new Manifest(new ByteArrayInputStream(b)); 173 if (!jvInitialized) { 174 jv = new JarVerifier(b); 175 } 176 } else { 177 manifest = new Manifest(super.getInputStream(manEntry)); 178 } 179 } 180 } 181 return manifest; 182 } 183 184 private String[] getMetaInfEntryNames() { 185 List<String> list = new ArrayList<String>(8); 186 187 Enumeration<? extends ZipEntry> allEntries = entries(); 188 while (allEntries.hasMoreElements()) { 189 ZipEntry ze = allEntries.nextElement(); 190 if (ze.getName().startsWith(META_DIR) 191 && ze.getName().length() > META_DIR.length()) { 192 list.add(ze.getName()); 193 } 194 } 195 return list.toArray(new String[list.size()]); 196 } 197 198 /** 199 * Returns the <code>JarEntry</code> for the given entry name or 200 * <code>null</code> if not found. 201 * 202 * @param name the jar file entry name 203 * @return the <code>JarEntry</code> for the given entry name or 204 * <code>null</code> if not found. 205 * 206 * @throws IllegalStateException 207 * may be thrown if the jar file has been closed 208 * 209 * @see java.util.jar.JarEntry 210 */ 211 public JarEntry getJarEntry(String name) { 212 return (JarEntry)getEntry(name); 213 } 214 215 /** 216 * Returns the <code>ZipEntry</code> for the given entry name or 217 * <code>null</code> if not found. 218 * 219 * @param name the jar file entry name 220 * @return the <code>ZipEntry</code> for the given entry name or 221 * <code>null</code> if not found 222 * 223 * @throws IllegalStateException 224 * may be thrown if the jar file has been closed 225 * 226 * @see java.util.zip.ZipEntry 227 */ 228 public ZipEntry getEntry(String name) { 229 ZipEntry ze = super.getEntry(name); 230 if (ze != null) { 231 return new JarFileEntry(ze); 232 } 233 return null; 234 } 235 236 /** 237 * Returns an enumeration of the zip file entries. 238 */ 239 public Enumeration<JarEntry> entries() { 240 final Enumeration enum_ = super.entries(); 241 return new Enumeration<JarEntry>() { 242 public boolean hasMoreElements() { 243 return enum_.hasMoreElements(); 244 } 245 public JarFileEntry nextElement() { 246 ZipEntry ze = (ZipEntry)enum_.nextElement(); 247 return new JarFileEntry(ze); 248 } 249 }; 250 } 251 252 private class JarFileEntry extends JarEntry { 253 JarFileEntry(ZipEntry ze) { 254 super(ze); 255 } 256 public Attributes getAttributes() throws IOException { 257 Manifest man = JarFile.this.getManifest(); 258 if (man != null) { 259 return man.getAttributes(getName()); 260 } else { 261 return null; 262 } 263 } 264 public Certificate[] getCertificates() { 265 try { 266 maybeInstantiateVerifier(); 267 } catch (IOException e) { 268 throw new RuntimeException(e); 269 } 270 if (certs == null && jv != null) { 271 certs = jv.getCerts(JarFile.this, this); 272 } 273 return certs == null ? null : certs.clone(); 274 } 275 public CodeSigner[] getCodeSigners() { 276 try { 277 maybeInstantiateVerifier(); 278 } catch (IOException e) { 279 throw new RuntimeException(e); 280 } 281 if (signers == null && jv != null) { 282 signers = jv.getCodeSigners(JarFile.this, this); 283 } 284 return signers == null ? null : signers.clone(); 285 } 286 } 287 288 /* 289 * Ensures that the JarVerifier has been created if one is 290 * necessary (i.e., the jar appears to be signed.) This is done as 291 * a quick check to avoid processing of the manifest for unsigned 292 * jars. 293 */ 294 private void maybeInstantiateVerifier() throws IOException { 295 if (jv != null) { 296 return; 297 } 298 299 if (verify) { 300 String[] names = getMetaInfEntryNames(); 301 if (names != null) { 302 for (int i = 0; i < names.length; i++) { 303 String name = names[i].toUpperCase(Locale.ENGLISH); 304 if (name.endsWith(".DSA") || 305 name.endsWith(".RSA") || 306 name.endsWith(".EC") || 307 name.endsWith(".SF")) { 308 // Assume since we found a signature-related file 309 // that the jar is signed and that we therefore 310 // need a JarVerifier and Manifest 311 getManifest(); 312 return; 313 } 314 } 315 } 316 // No signature-related files; don't instantiate a 317 // verifier 318 verify = false; 319 } 320 } 321 322 323 /* 324 * Initializes the verifier object by reading all the manifest 325 * entries and passing them to the verifier. 326 */ 327 private void initializeVerifier() { 328 ManifestEntryVerifier mev = null; 329 330 // Verify "META-INF/" entries... 331 try { 332 String[] names = getMetaInfEntryNames(); 333 if (names != null) { 334 for (int i = 0; i < names.length; i++) { 335 JarEntry e = getJarEntry(names[i]); 336 if (e == null) { 337 throw new JarException("corrupted jar file"); 338 } 339 if (!e.isDirectory()) { 340 if (mev == null) { 341 mev = new ManifestEntryVerifier 342 (getManifestFromReference()); 343 } 344 byte[] b = getBytes(e); 345 if (b != null && b.length > 0) { 346 jv.beginEntry(e, mev); 347 jv.update(b.length, b, 0, b.length, mev); 348 jv.update(-1, null, 0, 0, mev); 349 } 350 } 351 } 352 } 353 } catch (IOException ex) { 354 // if we had an error parsing any blocks, just 355 // treat the jar file as being unsigned 356 jv = null; 357 verify = false; 358 if (JarVerifier.debug != null) { 359 JarVerifier.debug.println("jarfile parsing error!"); 360 ex.printStackTrace(); 361 } 362 } 363 364 // if after initializing the verifier we have nothing 365 // signed, we null it out. 366 367 if (jv != null) { 368 369 jv.doneWithMeta(); 370 if (JarVerifier.debug != null) { 371 JarVerifier.debug.println("done with meta!"); 372 } 373 374 if (jv.nothingToVerify()) { 375 if (JarVerifier.debug != null) { 376 JarVerifier.debug.println("nothing to verify!"); 377 } 378 jv = null; 379 verify = false; 380 } 381 } 382 } 383 384 /* 385 * Reads all the bytes for a given entry. Used to process the 386 * META-INF files. 387 */ 388 private byte[] getBytes(ZipEntry ze) throws IOException { 389 try (InputStream is = super.getInputStream(ze)) { 390 return IOUtils.readFully(is, (int)ze.getSize(), true); 391 } 392 } 393 394 /** 395 * Returns an input stream for reading the contents of the specified 396 * zip file entry. 397 * @param ze the zip file entry 398 * @return an input stream for reading the contents of the specified 399 * zip file entry 400 * @throws ZipException if a zip file format error has occurred 401 * @throws IOException if an I/O error has occurred 402 * @throws SecurityException if any of the jar file entries 403 * are incorrectly signed. 404 * @throws IllegalStateException 405 * may be thrown if the jar file has been closed 406 */ 407 public synchronized InputStream getInputStream(ZipEntry ze) 408 throws IOException 409 { 410 maybeInstantiateVerifier(); 411 if (jv == null) { 412 return super.getInputStream(ze); 413 } 414 if (!jvInitialized) { 415 initializeVerifier(); 416 jvInitialized = true; 417 // could be set to null after a call to 418 // initializeVerifier if we have nothing to 419 // verify 420 if (jv == null) 421 return super.getInputStream(ze); 422 } 423 424 // wrap a verifier stream around the real stream 425 return new JarVerifier.VerifierStream( 426 getManifestFromReference(), 427 ze instanceof JarFileEntry ? 428 (JarEntry) ze : getJarEntry(ze.getName()), 429 super.getInputStream(ze), 430 jv); 431 } 432 433 // Statics for hand-coded Boyer-Moore search in hasClassPathAttribute() 434 // The bad character shift for "class-path" 435 private static int[] lastOcc; 436 // The good suffix shift for "class-path" 437 private static int[] optoSft; 438 // Initialize the shift arrays to search for "class-path" 439 private static char[] src = {'c','l','a','s','s','-','p','a','t','h'}; 440 static { 441 lastOcc = new int[128]; 442 optoSft = new int[10]; 443 lastOcc[(int)'c']=1; 444 lastOcc[(int)'l']=2; 445 lastOcc[(int)'s']=5; 446 lastOcc[(int)'-']=6; 447 lastOcc[(int)'p']=7; 448 lastOcc[(int)'a']=8; 449 lastOcc[(int)'t']=9; 450 lastOcc[(int)'h']=10; 451 for (int i=0; i<9; i++) 452 optoSft[i]=10; 453 optoSft[9]=1; 454 } 455 456 private synchronized JarEntry getManEntry() { 457 if (manEntry == null) { 458 // First look up manifest entry using standard name 459 manEntry = getJarEntry(MANIFEST_NAME); 460 if (manEntry == null) { 461 // If not found, then iterate through all the "META-INF/" 462 // entries to find a match. 463 String[] names = getMetaInfEntryNames(); 464 if (names != null) { 465 for (int i = 0; i < names.length; i++) { 466 if (MANIFEST_NAME.equals( 467 names[i].toUpperCase(Locale.ENGLISH))) { 468 manEntry = getJarEntry(names[i]); 469 break; 470 } 471 } 472 } 473 } 474 } 475 return manEntry; 476 } 477 478 // Returns true iff this jar file has a manifest with a class path 479 // attribute. Returns false if there is no manifest or the manifest 480 // does not contain a "Class-Path" attribute. Currently exported to 481 // core libraries via sun.misc.SharedSecrets. 482 /** 483 * @hide 484 */ 485 public boolean hasClassPathAttribute() throws IOException { 486 if (computedHasClassPathAttribute) { 487 return hasClassPathAttribute; 488 } 489 490 hasClassPathAttribute = false; 491 if (!isKnownToNotHaveClassPathAttribute()) { 492 JarEntry manEntry = getManEntry(); 493 if (manEntry != null) { 494 byte[] b = getBytes(manEntry); 495 int last = b.length - src.length; 496 int i = 0; 497 next: 498 while (i<=last) { 499 for (int j=9; j>=0; j--) { 500 char c = (char) b[i+j]; 501 c = (((c-'A')|('Z'-c)) >= 0) ? (char)(c + 32) : c; 502 if (c != src[j]) { 503 i += Math.max(j + 1 - lastOcc[c&0x7F], optoSft[j]); 504 continue next; 505 } 506 } 507 hasClassPathAttribute = true; 508 break; 509 } 510 } 511 } 512 computedHasClassPathAttribute = true; 513 return hasClassPathAttribute; 514 } 515 516 private static String javaHome; 517 private static String[] jarNames; 518 private boolean isKnownToNotHaveClassPathAttribute() { 519 // Optimize away even scanning of manifest for jar files we 520 // deliver which don't have a class-path attribute. If one of 521 // these jars is changed to include such an attribute this code 522 // must be changed. 523 if (javaHome == null) { 524 javaHome = AccessController.doPrivileged( 525 new GetPropertyAction("java.home")); 526 } 527 if (jarNames == null) { 528 String[] names = new String[10]; 529 String fileSep = File.separator; 530 int i = 0; 531 names[i++] = fileSep + "rt.jar"; 532 names[i++] = fileSep + "sunrsasign.jar"; 533 names[i++] = fileSep + "jsse.jar"; 534 names[i++] = fileSep + "jce.jar"; 535 names[i++] = fileSep + "charsets.jar"; 536 names[i++] = fileSep + "dnsns.jar"; 537 names[i++] = fileSep + "ldapsec.jar"; 538 names[i++] = fileSep + "localedata.jar"; 539 names[i++] = fileSep + "sunjce_provider.jar"; 540 names[i++] = fileSep + "sunpkcs11.jar"; 541 jarNames = names; 542 } 543 544 String name = getName(); 545 String localJavaHome = javaHome; 546 if (name.startsWith(localJavaHome)) { 547 String[] names = jarNames; 548 for (int i = 0; i < names.length; i++) { 549 if (name.endsWith(names[i])) { 550 return true; 551 } 552 } 553 } 554 return false; 555 } 556 557 JarEntry newEntry(ZipEntry ze) { 558 return new JarFileEntry(ze); 559 } 560} 561