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