001 /*
002 * Copyright 1997-2006 Sun Microsystems, Inc. All Rights Reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation. Sun designates this
008 * particular file as subject to the "Classpath" exception as provided
009 * by Sun in the LICENSE file that accompanied this code.
010 *
011 * This code is distributed in the hope that it will be useful, but WITHOUT
012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014 * version 2 for more details (a copy is included in the LICENSE file that
015 * accompanied this code).
016 *
017 * You should have received a copy of the GNU General Public License version
018 * 2 along with this work; if not, write to the Free Software Foundation,
019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020 *
021 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022 * CA 95054 USA or visit www.sun.com if you need additional information or
023 * have any questions.
024 */
025
026 package java.util.jar;
027
028 import java.io.*;
029 import java.lang.ref.SoftReference;
030 import java.util.*;
031 import java.util.zip.*;
032 import java.security.CodeSigner;
033 import java.security.cert.Certificate;
034 import java.security.AccessController;
035 import sun.security.action.GetPropertyAction;
036 import sun.security.util.ManifestEntryVerifier;
037 import sun.misc.SharedSecrets;
038
039 /**
040 * The <code>JarFile</code> class is used to read the contents of a jar file
041 * from any file that can be opened with <code>java.io.RandomAccessFile</code>.
042 * It extends the class <code>java.util.zip.ZipFile</code> with support
043 * for reading an optional <code>Manifest</code> entry. The
044 * <code>Manifest</code> can be used to specify meta-information about the
045 * jar file and its entries.
046 *
047 * <p> Unless otherwise noted, passing a <tt>null</tt> argument to a constructor
048 * or method in this class will cause a {@link NullPointerException} to be
049 * thrown.
050 *
051 * @author David Connelly
052 * @version 1.73, 05/05/07
053 * @see Manifest
054 * @see java.util.zip.ZipFile
055 * @see java.util.jar.JarEntry
056 * @since 1.2
057 */
058 public class JarFile extends ZipFile {
059 private SoftReference<Manifest> manRef;
060 private JarEntry manEntry;
061 private JarVerifier jv;
062 private boolean jvInitialized;
063 private boolean verify;
064 private boolean computedHasClassPathAttribute;
065 private boolean hasClassPathAttribute;
066
067 // Set up JavaUtilJarAccess in SharedSecrets
068 static {
069 SharedSecrets.setJavaUtilJarAccess(new JavaUtilJarAccessImpl());
070 }
071
072 /**
073 * The JAR manifest file name.
074 */
075 public static final String MANIFEST_NAME = "META-INF/MANIFEST.MF";
076
077 /**
078 * Creates a new <code>JarFile</code> to read from the specified
079 * file <code>name</code>. The <code>JarFile</code> will be verified if
080 * it is signed.
081 * @param name the name of the jar file to be opened for reading
082 * @throws IOException if an I/O error has occurred
083 * @throws SecurityException if access to the file is denied
084 * by the SecurityManager
085 */
086 public JarFile(String name) throws IOException {
087 this (new File(name), true, ZipFile.OPEN_READ);
088 }
089
090 /**
091 * Creates a new <code>JarFile</code> to read from the specified
092 * file <code>name</code>.
093 * @param name the name of the jar file to be opened for reading
094 * @param verify whether or not to verify the jar file if
095 * it is signed.
096 * @throws IOException if an I/O error has occurred
097 * @throws SecurityException if access to the file is denied
098 * by the SecurityManager
099 */
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 * Creates a new <code>JarFile</code> to read from the specified
119 * <code>File</code> object.
120 * @param file the jar file to be opened for reading
121 * @param verify whether or not to verify the jar file if
122 * it is signed.
123 * @throws IOException if an I/O error has occurred
124 * @throws SecurityException if access to the file is denied
125 * by the SecurityManager.
126 */
127 public JarFile(File file, boolean verify) throws IOException {
128 this (file, verify, ZipFile.OPEN_READ);
129 }
130
131 /**
132 * Creates a new <code>JarFile</code> to read from the specified
133 * <code>File</code> object in the specified mode. The mode argument
134 * must be either <tt>OPEN_READ</tt> or <tt>OPEN_READ | OPEN_DELETE</tt>.
135 *
136 * @param file the jar file to be opened for reading
137 * @param verify whether or not to verify the jar file if
138 * it is signed.
139 * @param mode the mode in which the file is to be opened
140 * @throws IOException if an I/O error has occurred
141 * @throws IllegalArgumentException
142 * if the <tt>mode</tt> argument is invalid
143 * @throws SecurityException if access to the file is denied
144 * by the SecurityManager
145 * @since 1.3
146 */
147 public JarFile(File file, boolean verify, int mode)
148 throws IOException {
149 super (file, mode);
150 this .verify = verify;
151 }
152
153 /**
154 * Returns the jar file manifest, or <code>null</code> if none.
155 *
156 * @return the jar file manifest, or <code>null</code> if none
157 *
158 * @throws IllegalStateException
159 * may be thrown if the jar file has been closed
160 */
161 public Manifest getManifest() throws IOException {
162 return getManifestFromReference();
163 }
164
165 private Manifest getManifestFromReference() throws IOException {
166 Manifest man = manRef != null ? manRef.get() : null;
167
168 if (man == null) {
169
170 JarEntry manEntry = getManEntry();
171
172 // If found then load the manifest
173 if (manEntry != null) {
174 if (verify) {
175 byte[] b = getBytes(manEntry);
176 man = new Manifest(new ByteArrayInputStream(b));
177 if (!jvInitialized) {
178 jv = new JarVerifier(b);
179 }
180 } else {
181 man = new Manifest(super .getInputStream(manEntry));
182 }
183 manRef = new SoftReference(man);
184 }
185 }
186 return man;
187 }
188
189 private native String[] getMetaInfEntryNames();
190
191 /**
192 * Returns the <code>JarEntry</code> for the given entry name or
193 * <code>null</code> if not found.
194 *
195 * @param name the jar file entry name
196 * @return the <code>JarEntry</code> for the given entry name or
197 * <code>null</code> if not found.
198 *
199 * @throws IllegalStateException
200 * may be thrown if the jar file has been closed
201 *
202 * @see java.util.jar.JarEntry
203 */
204 public JarEntry getJarEntry(String name) {
205 return (JarEntry) getEntry(name);
206 }
207
208 /**
209 * Returns the <code>ZipEntry</code> for the given entry name or
210 * <code>null</code> if not found.
211 *
212 * @param name the jar file entry name
213 * @return the <code>ZipEntry</code> for the given entry name or
214 * <code>null</code> if not found
215 *
216 * @throws IllegalStateException
217 * may be thrown if the jar file has been closed
218 *
219 * @see java.util.zip.ZipEntry
220 */
221 public ZipEntry getEntry(String name) {
222 ZipEntry ze = super .getEntry(name);
223 if (ze != null) {
224 return new JarFileEntry(ze);
225 }
226 return null;
227 }
228
229 /**
230 * Returns an enumeration of the zip file entries.
231 */
232 public Enumeration<JarEntry> entries() {
233 final Enumeration enum_ = super .entries();
234 return new Enumeration<JarEntry>() {
235 public boolean hasMoreElements() {
236 return enum_.hasMoreElements();
237 }
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
251 public Attributes getAttributes() throws IOException {
252 Manifest man = JarFile.this .getManifest();
253 if (man != null) {
254 return man.getAttributes(getName());
255 } else {
256 return null;
257 }
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(getName());
268 }
269 return certs == null ? null : (Certificate[]) certs.clone();
270 }
271
272 public CodeSigner[] getCodeSigners() {
273 try {
274 maybeInstantiateVerifier();
275 } catch (IOException e) {
276 throw new RuntimeException(e);
277 }
278 if (signers == null && jv != null) {
279 signers = jv.getCodeSigners(getName());
280 }
281 return signers == null ? null : (CodeSigner[]) signers
282 .clone();
283 }
284 }
285
286 /*
287 * Ensures that the JarVerifier has been created if one is
288 * necessary (i.e., the jar appears to be signed.) This is done as
289 * a quick check to avoid processing of the manifest for unsigned
290 * jars.
291 */
292 private void maybeInstantiateVerifier() throws IOException {
293 if (jv != null) {
294 return;
295 }
296
297 if (verify) {
298 String[] names = getMetaInfEntryNames();
299 if (names != null) {
300 for (int i = 0; i < names.length; i++) {
301 String name = names[i].toUpperCase(Locale.ENGLISH);
302 if (name.endsWith(".DSA") || name.endsWith(".RSA")
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 * Initializes the verifier object by reading all the manifest
320 * entries and passing them to the verifier.
321 */
322 private void initializeVerifier() {
323 ManifestEntryVerifier mev = null;
324
325 // Verify "META-INF/" entries...
326 try {
327 String[] names = getMetaInfEntryNames();
328 if (names != null) {
329 for (int i = 0; i < names.length; i++) {
330 JarEntry e = getJarEntry(names[i]);
331 if (!e.isDirectory()) {
332 if (mev == null) {
333 mev = new ManifestEntryVerifier(
334 getManifestFromReference());
335 }
336 byte[] b = getBytes(e);
337 if (b != null && b.length > 0) {
338 jv.beginEntry(e, mev);
339 jv.update(b.length, b, 0, b.length, mev);
340 jv.update(-1, null, 0, 0, mev);
341 }
342 }
343 }
344 }
345 } catch (IOException ex) {
346 // if we had an error parsing any blocks, just
347 // treat the jar file as being unsigned
348 jv = null;
349 verify = false;
350 }
351
352 // if after initializing the verifier we have nothing
353 // signed, we null it out.
354
355 if (jv != null) {
356
357 jv.doneWithMeta();
358 if (JarVerifier.debug != null) {
359 JarVerifier.debug.println("done with meta!");
360 }
361
362 if (jv.nothingToVerify()) {
363 if (JarVerifier.debug != null) {
364 JarVerifier.debug.println("nothing to verify!");
365 }
366 jv = null;
367 verify = false;
368 }
369 }
370 }
371
372 /*
373 * Reads all the bytes for a given entry. Used to process the
374 * META-INF files.
375 */
376 private byte[] getBytes(ZipEntry ze) throws IOException {
377 byte[] b = new byte[(int) ze.getSize()];
378 DataInputStream is = new DataInputStream(super
379 .getInputStream(ze));
380 is.readFully(b, 0, b.length);
381 is.close();
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 maybeInstantiateVerifier();
401 if (jv == null) {
402 return super .getInputStream(ze);
403 }
404 if (!jvInitialized) {
405 initializeVerifier();
406 jvInitialized = true;
407 // could be set to null after a call to
408 // initializeVerifier if we have nothing to
409 // verify
410 if (jv == null)
411 return super .getInputStream(ze);
412 }
413
414 // wrap a verifier stream around the real stream
415 return new JarVerifier.VerifierStream(
416 getManifestFromReference(),
417 ze instanceof JarFileEntry ? (JarEntry) ze
418 : getJarEntry(ze.getName()), super
419 .getInputStream(ze), jv);
420 }
421
422 // Statics for hand-coded Boyer-Moore search in hasClassPathAttribute()
423 // The bad character shift for "class-path"
424 private static int[] lastOcc;
425 // The good suffix shift for "class-path"
426 private static int[] optoSft;
427 // Initialize the shift arrays to search for "class-path"
428 private static char[] src = { 'c', 'l', 'a', 's', 's', '-', 'p',
429 'a', 't', 'h' };
430 static {
431 lastOcc = new int[128];
432 optoSft = new int[10];
433 lastOcc[(int) 'c'] = 1;
434 lastOcc[(int) 'l'] = 2;
435 lastOcc[(int) 's'] = 5;
436 lastOcc[(int) '-'] = 6;
437 lastOcc[(int) 'p'] = 7;
438 lastOcc[(int) 'a'] = 8;
439 lastOcc[(int) 't'] = 9;
440 lastOcc[(int) 'h'] = 10;
441 for (int i = 0; i < 9; i++)
442 optoSft[i] = 10;
443 optoSft[9] = 1;
444 }
445
446 private JarEntry getManEntry() {
447 if (manEntry == null) {
448 // First look up manifest entry using standard name
449 manEntry = getJarEntry(MANIFEST_NAME);
450 if (manEntry == null) {
451 // If not found, then iterate through all the "META-INF/"
452 // entries to find a match.
453 String[] names = getMetaInfEntryNames();
454 if (names != null) {
455 for (int i = 0; i < names.length; i++) {
456 if (MANIFEST_NAME.equals(names[i]
457 .toUpperCase(Locale.ENGLISH))) {
458 manEntry = getJarEntry(names[i]);
459 break;
460 }
461 }
462 }
463 }
464 }
465 return manEntry;
466 }
467
468 // Returns true iff this jar file has a manifest with a class path
469 // attribute. Returns false if there is no manifest or the manifest
470 // does not contain a "Class-Path" attribute. Currently exported to
471 // core libraries via sun.misc.SharedSecrets.
472 boolean hasClassPathAttribute() throws IOException {
473 if (computedHasClassPathAttribute) {
474 return hasClassPathAttribute;
475 }
476
477 hasClassPathAttribute = false;
478 if (!isKnownToNotHaveClassPathAttribute()) {
479 JarEntry manEntry = getManEntry();
480 if (manEntry != null) {
481 byte[] b = new byte[(int) manEntry.getSize()];
482 DataInputStream dis = new DataInputStream(super
483 .getInputStream(manEntry));
484 dis.readFully(b, 0, b.length);
485 dis.close();
486
487 int last = b.length - src.length;
488 int i = 0;
489 next: while (i <= last) {
490 for (int j = 9; j >= 0; j--) {
491 char c = (char) b[i + j];
492 c = (((c - 'A') | ('Z' - c)) >= 0) ? (char) (c + 32)
493 : c;
494 if (c != src[j]) {
495 i += Math.max(j + 1 - lastOcc[c & 0x7F],
496 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
512 private boolean isKnownToNotHaveClassPathAttribute() {
513 // Optimize away even scanning of manifest for jar files we
514 // deliver which don't have a class-path attribute. If one of
515 // these jars is changed to include such an attribute this code
516 // must be changed.
517 if (javaHome == null) {
518 javaHome = (String) AccessController
519 .doPrivileged(new GetPropertyAction("java.home"));
520 }
521 if (jarNames == null) {
522 String[] names = new String[10];
523 String fileSep = File.separator;
524 int i = 0;
525 names[i++] = fileSep + "rt.jar";
526 names[i++] = fileSep + "sunrsasign.jar";
527 names[i++] = fileSep + "jsse.jar";
528 names[i++] = fileSep + "jce.jar";
529 names[i++] = fileSep + "charsets.jar";
530 names[i++] = fileSep + "dnsns.jar";
531 names[i++] = fileSep + "ldapsec.jar";
532 names[i++] = fileSep + "localedata.jar";
533 names[i++] = fileSep + "sunjce_provider.jar";
534 names[i++] = fileSep + "sunpkcs11.jar";
535 jarNames = names;
536 }
537
538 String name = getName();
539 String localJavaHome = javaHome;
540 if (name.startsWith(localJavaHome)) {
541 String[] names = jarNames;
542 for (int i = 0; i < names.length; i++) {
543 if (name.endsWith(names[i])) {
544 return true;
545 }
546 }
547 }
548 return false;
549 }
550 }
|