blob: 309f4ff48a7febdcb73b8e9ca88c4d8ae31df8e6 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
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
26package java.util.jar;
27
28import java.io.*;
29import java.lang.ref.SoftReference;
30import java.util.*;
31import java.util.zip.*;
32import java.security.CodeSigner;
33import java.security.cert.Certificate;
34import java.security.AccessController;
35import sun.security.action.GetPropertyAction;
36import sun.security.util.ManifestEntryVerifier;
37import 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 */
57public
58class 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}