1   /*
2    * %W% %E%
3    *
4    * Copyright (c) 2006, 2009, Oracle and/or its affiliates. All rights reserved.
5    * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
6    */
7   
8   package java.util.jar;
9   
10  import java.io.*;
11  import java.net.URL;
12  import java.lang.ref.SoftReference;
13  import java.util.*;
14  import java.util.zip.*;
15  import java.security.CodeSigner;
16  import java.security.CodeSource;
17  import java.security.cert.Certificate;
18  import java.security.AccessController;
19  import sun.security.action.GetPropertyAction;
20  import sun.security.util.ManifestEntryVerifier;
21  import sun.misc.SharedSecrets;
22  
23  /**
24   * The <code>JarFile</code> class is used to read the contents of a jar file
25   * from any file that can be opened with <code>java.io.RandomAccessFile</code>.
26   * It extends the class <code>java.util.zip.ZipFile</code> with support
27   * for reading an optional <code>Manifest</code> entry. The
28   * <code>Manifest</code> can be used to specify meta-information about the
29   * jar file and its entries.
30   *
31   * <p> Unless otherwise noted, passing a <tt>null</tt> argument to a constructor
32   * or method in this class will cause a {@link NullPointerException} to be
33   * thrown.
34   *
35   * @author  David Connelly
36   * @version %I%, %G%
37   * @see     Manifest
38   * @see     java.util.zip.ZipFile
39   * @see     java.util.jar.JarEntry
40   * @since   1.2
41   */
42  public
43  class JarFile extends ZipFile {
44      private SoftReference<Manifest> manRef;
45      private JarEntry manEntry;
46      private JarVerifier jv;
47      private boolean jvInitialized;
48      private boolean verify;
49      private boolean computedHasClassPathAttribute;
50      private boolean hasClassPathAttribute;
51  
52      // Set up JavaUtilJarAccess in SharedSecrets
53      static {
54          SharedSecrets.setJavaUtilJarAccess(new JavaUtilJarAccessImpl());
55      }
56  
57      /**
58       * The JAR manifest file name.
59       */
60      public static final String MANIFEST_NAME = "META-INF/MANIFEST.MF";
61  
62      /**
63       * Creates a new <code>JarFile</code> to read from the specified
64       * file <code>name</code>. The <code>JarFile</code> will be verified if
65       * it is signed.
66       * @param name the name of the jar file to be opened for reading
67       * @throws IOException if an I/O error has occurred
68       * @throws SecurityException if access to the file is denied
69       *         by the SecurityManager
70       */
71      public JarFile(String name) throws IOException {
72      this(new File(name), true, ZipFile.OPEN_READ);
73      }
74  
75      /**
76       * Creates a new <code>JarFile</code> to read from the specified
77       * file <code>name</code>.
78       * @param name the name of the jar file to be opened for reading
79       * @param verify whether or not to verify the jar file if
80       * it is signed.
81       * @throws IOException if an I/O error has occurred
82       * @throws SecurityException if access to the file is denied
83       *         by the SecurityManager 
84       */
85      public JarFile(String name, boolean verify) throws IOException {
86          this(new File(name), verify, ZipFile.OPEN_READ);
87      }
88  
89      /**
90       * Creates a new <code>JarFile</code> to read from the specified
91       * <code>File</code> object. The <code>JarFile</code> will be verified if
92       * it is signed.
93       * @param file the jar file to be opened for reading
94       * @throws IOException if an I/O error has occurred
95       * @throws SecurityException if access to the file is denied
96       *         by the SecurityManager
97       */
98      public JarFile(File file) throws IOException {
99      this(file, true, ZipFile.OPEN_READ);
100     }
101 
102 
103     /**
104      * Creates a new <code>JarFile</code> to read from the specified
105      * <code>File</code> object.
106      * @param file the jar file to be opened for reading
107      * @param verify whether or not to verify the jar file if
108      * it is signed.
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, boolean verify) throws IOException {
114     this(file, verify, ZipFile.OPEN_READ);
115     }
116 
117 
118     /**
119      * Creates a new <code>JarFile</code> to read from the specified
120      * <code>File</code> object in the specified mode.  The mode argument
121      * must be either <tt>OPEN_READ</tt> or <tt>OPEN_READ | OPEN_DELETE</tt>.
122      *
123      * @param file the jar file to be opened for reading
124      * @param verify whether or not to verify the jar file if
125      * it is signed.
126      * @param mode the mode in which the file is to be opened
127      * @throws IOException if an I/O error has occurred
128      * @throws IllegalArgumentException
129      *         if the <tt>mode</tt> argument is invalid
130      * @throws SecurityException if access to the file is denied
131      *         by the SecurityManager
132      * @since 1.3
133      */
134     public JarFile(File file, boolean verify, int mode) throws IOException {
135     super(file, mode);
136     this.verify = verify;
137     }
138 
139     /**
140      * Returns the jar file manifest, or <code>null</code> if none.
141      *
142      * @return the jar file manifest, or <code>null</code> if none
143      *
144      * @throws IllegalStateException
145      *         may be thrown if the jar file has been closed
146      */
147     public Manifest getManifest() throws IOException {
148     return getManifestFromReference();
149     }
150 
151     private Manifest getManifestFromReference() throws IOException {
152     Manifest man = manRef != null ? manRef.get() : null;
153 
154     if (man == null) {
155       
156         JarEntry manEntry = getManEntry();
157         
158         // If found then load the manifest
159         if (manEntry != null) {
160         if (verify) {
161             byte[] b = getBytes(manEntry);
162             man = new Manifest(new ByteArrayInputStream(b));
163             if (!jvInitialized) {
164                 jv = new JarVerifier(b);
165             }
166         } else {
167             man = new Manifest(super.getInputStream(manEntry));
168         }
169             manRef = new SoftReference(man);
170         }
171     }
172     return man;
173     }
174 
175     private native String[] getMetaInfEntryNames();
176 
177     /**
178      * Returns the <code>JarEntry</code> for the given entry name or
179      * <code>null</code> if not found.
180      *
181      * @param name the jar file entry name
182      * @return the <code>JarEntry</code> for the given entry name or
183      *         <code>null</code> if not found.
184      *
185      * @throws IllegalStateException
186      *         may be thrown if the jar file has been closed
187      *
188      * @see java.util.jar.JarEntry
189      */
190     public JarEntry getJarEntry(String name) {
191     return (JarEntry)getEntry(name);
192     }
193 
194     /**
195      * Returns the <code>ZipEntry</code> for the given entry name or
196      * <code>null</code> if not found.
197      *
198      * @param name the jar file entry name
199      * @return the <code>ZipEntry</code> for the given entry name or
200      *         <code>null</code> if not found
201      *
202      * @throws IllegalStateException
203      *         may be thrown if the jar file has been closed
204      *
205      * @see java.util.zip.ZipEntry
206      */
207     public ZipEntry getEntry(String name) {
208     ZipEntry ze = super.getEntry(name);
209     if (ze != null) {
210         return new JarFileEntry(ze);
211     }
212     return null;
213     }
214 
215     /**
216      * Returns an enumeration of the zip file entries.
217      */
218     public Enumeration<JarEntry> entries() {
219     final Enumeration enum_ = super.entries();
220     return new Enumeration<JarEntry>() {
221         public boolean hasMoreElements() {
222         return enum_.hasMoreElements();
223         }
224         public JarFileEntry nextElement() {
225         ZipEntry ze = (ZipEntry)enum_.nextElement();
226         return new JarFileEntry(ze);
227         }
228     };
229     }
230 
231     private class JarFileEntry extends JarEntry {
232     JarFileEntry(ZipEntry ze) {
233         super(ze);
234     }
235     public Attributes getAttributes() throws IOException {
236         Manifest man = JarFile.this.getManifest();
237         if (man != null) {
238         return man.getAttributes(getName());
239         } else {
240         return null;
241         }
242     }
243     public Certificate[] getCertificates() {
244             try {
245                 maybeInstantiateVerifier();
246             } catch (IOException e) {
247                 throw new RuntimeException(e);
248             }
249         if (certs == null && jv != null) {
250         certs = jv.getCerts(JarFile.this, this);
251         }
252         return certs == null ? null : (Certificate[]) certs.clone();
253     }
254     public CodeSigner[] getCodeSigners() {
255         try {
256         maybeInstantiateVerifier();
257         } catch (IOException e) {
258         throw new RuntimeException(e);
259         }
260         if (signers == null && jv != null) {
261         signers = jv.getCodeSigners(JarFile.this, this);
262         }
263         return signers == null ? null : (CodeSigner[]) signers.clone();
264     }
265     }
266         
267     /*
268      * Ensures that the JarVerifier has been created if one is
269      * necessary (i.e., the jar appears to be signed.) This is done as
270      * a quick check to avoid processing of the manifest for unsigned
271      * jars.
272      */
273     private void maybeInstantiateVerifier() throws IOException {
274         if (jv != null) {
275             return;
276         }
277 
278         if (verify) {
279             String[] names = getMetaInfEntryNames();
280             if (names != null) {
281                 for (int i = 0; i < names.length; i++) {
282                     String name = names[i].toUpperCase(Locale.ENGLISH);
283                     if (name.endsWith(".DSA") ||
284                         name.endsWith(".RSA") ||
285                         name.endsWith(".SF")) {
286                         // Assume since we found a signature-related file
287                         // that the jar is signed and that we therefore
288                         // need a JarVerifier and Manifest
289                         getManifest();
290                         return;
291                     }
292                 }
293             }
294             // No signature-related files; don't instantiate a
295             // verifier
296             verify = false;
297         }
298     }
299 
300 
301     /*
302      * Initializes the verifier object by reading all the manifest
303      * entries and passing them to the verifier.
304      */
305     private void initializeVerifier() {
306     ManifestEntryVerifier mev = null;
307 
308     // Verify "META-INF/" entries...
309     try {
310         String[] names = getMetaInfEntryNames();
311         if (names != null) {
312         for (int i = 0; i < names.length; i++) {
313             JarEntry e = getJarEntry(names[i]);
314             if (!e.isDirectory()) {
315             if (mev == null) {
316                 mev = new ManifestEntryVerifier
317                 (getManifestFromReference());
318             }
319             byte[] b = getBytes(e);
320             if (b != null && b.length > 0) {
321                 jv.beginEntry(e, mev);
322                 jv.update(b.length, b, 0, b.length, mev);
323                 jv.update(-1, null, 0, 0, mev);
324             }
325             }
326         }
327         }
328     } catch (IOException ex) {
329         // if we had an error parsing any blocks, just
330         // treat the jar file as being unsigned
331         jv = null;
332             verify = false;
333     }
334 
335     // if after initializing the verifier we have nothing
336     // signed, we null it out.
337 
338     if (jv != null) {
339 
340         jv.doneWithMeta();
341         if (JarVerifier.debug != null) {
342         JarVerifier.debug.println("done with meta!"); 
343         }
344 
345         if (jv.nothingToVerify()) {
346         if (JarVerifier.debug != null) {
347             JarVerifier.debug.println("nothing to verify!");
348         }
349         jv = null;
350                 verify = false;
351         }
352     }
353     }
354 
355     /*
356      * Reads all the bytes for a given entry. Used to process the
357      * META-INF files.
358      */
359     private byte[] getBytes(ZipEntry ze) throws IOException {
360     byte[] b = new byte[(int)ze.getSize()];
361     DataInputStream is = new DataInputStream(super.getInputStream(ze));
362     is.readFully(b, 0, b.length);
363     is.close();
364     return b;
365     }
366 
367     /**
368      * Returns an input stream for reading the contents of the specified
369      * zip file entry.
370      * @param ze the zip file entry
371      * @return an input stream for reading the contents of the specified
372      *         zip file entry
373      * @throws ZipException if a zip file format error has occurred
374      * @throws IOException if an I/O error has occurred
375      * @throws SecurityException if any of the jar file entries
376      *         are incorrectly signed.
377      * @throws IllegalStateException
378      *         may be thrown if the jar file has been closed
379      */
380     public synchronized InputStream getInputStream(ZipEntry ze) 
381     throws IOException 
382     {
383         maybeInstantiateVerifier();
384     if (jv == null) {
385         return super.getInputStream(ze);
386     }
387     if (!jvInitialized) {
388         initializeVerifier();
389         jvInitialized = true;
390         // could be set to null after a call to
391         // initializeVerifier if we have nothing to
392         // verify
393         if (jv == null)
394         return super.getInputStream(ze);
395     }
396 
397     // wrap a verifier stream around the real stream
398     return new JarVerifier.VerifierStream(
399         getManifestFromReference(),
400         ze instanceof JarFileEntry ?
401         (JarEntry) ze : getJarEntry(ze.getName()),
402         super.getInputStream(ze),
403         jv);
404     }
405 
406     // Statics for hand-coded Boyer-Moore search in hasClassPathAttribute()
407     // The bad character shift for "class-path"
408     private static int[] lastOcc;
409     // The good suffix shift for "class-path"
410     private static int[] optoSft;
411     // Initialize the shift arrays to search for "class-path"
412     private static char[] src = {'c','l','a','s','s','-','p','a','t','h'};
413     static {
414     lastOcc = new int[128];
415     optoSft = new int[10];
416     lastOcc[(int)'c']=1;
417     lastOcc[(int)'l']=2;
418     lastOcc[(int)'s']=5;
419     lastOcc[(int)'-']=6;
420     lastOcc[(int)'p']=7;
421     lastOcc[(int)'a']=8;
422     lastOcc[(int)'t']=9;
423     lastOcc[(int)'h']=10;
424     for (int i=0; i<9; i++)
425         optoSft[i]=10;
426     optoSft[9]=1;
427     }
428  
429     private JarEntry getManEntry() {
430     if (manEntry == null) {
431         // First look up manifest entry using standard name
432         manEntry = getJarEntry(MANIFEST_NAME);
433             if (manEntry == null) {
434                 // If not found, then iterate through all the "META-INF/"
435                 // entries to find a match.
436                 String[] names = getMetaInfEntryNames();
437                 if (names != null) {
438                     for (int i = 0; i < names.length; i++) {
439                         if (MANIFEST_NAME.equals(
440                                                  names[i].toUpperCase(Locale.ENGLISH))) {
441                             manEntry = getJarEntry(names[i]);
442                             break;
443                         }
444                     }
445                 }
446             }
447     }
448     return manEntry;
449     }
450 
451     // Returns true iff this jar file has a manifest with a class path
452     // attribute. Returns false if there is no manifest or the manifest
453     // does not contain a "Class-Path" attribute. Currently exported to
454     // core libraries via sun.misc.SharedSecrets.
455     boolean hasClassPathAttribute() throws IOException {
456         if (computedHasClassPathAttribute) {
457             return hasClassPathAttribute;
458         }
459 
460         hasClassPathAttribute = false;
461         if (!isKnownToNotHaveClassPathAttribute()) {
462             JarEntry manEntry = getManEntry();
463             if (manEntry != null) {
464                 byte[] b = new byte[(int)manEntry.getSize()];
465                 DataInputStream dis = new DataInputStream(
466                                                           super.getInputStream(manEntry));
467                 dis.readFully(b, 0, b.length);
468                 dis.close();
469  
470                 int last = b.length - src.length;
471                 int i = 0;
472                 next:       
473                 while (i<=last) {
474                     for (int j=9; j>=0; j--) {
475                         char c = (char) b[i+j];
476                         c = (((c-'A')|('Z'-c)) >= 0) ? (char)(c + 32) : c;
477                         if (c != src[j]) {
478                             i += Math.max(j + 1 - lastOcc[c&0x7F], optoSft[j]);
479                             continue next;
480                         }
481                     }
482                     hasClassPathAttribute = true;
483                     break;
484                 }
485             }
486         }
487         computedHasClassPathAttribute = true;
488         return hasClassPathAttribute;
489     }
490 
491     private static String javaHome;
492     private static String[] jarNames;
493     private boolean isKnownToNotHaveClassPathAttribute() {
494         // Optimize away even scanning of manifest for jar files we
495         // deliver which don't have a class-path attribute. If one of
496         // these jars is changed to include such an attribute this code
497         // must be changed.
498         if (javaHome == null) {
499             javaHome = (String) AccessController.doPrivileged(
500                 new GetPropertyAction("java.home"));
501         }
502         if (jarNames == null) {
503             String[] names = new String[10];
504             String fileSep = File.separator;
505             int i = 0;
506             names[i++] = fileSep + "rt.jar";
507             names[i++] = fileSep + "sunrsasign.jar";
508             names[i++] = fileSep + "jsse.jar";
509             names[i++] = fileSep + "jce.jar";
510             names[i++] = fileSep + "charsets.jar";
511             names[i++] = fileSep + "dnsns.jar";
512             names[i++] = fileSep + "ldapsec.jar";
513             names[i++] = fileSep + "localedata.jar";
514             names[i++] = fileSep + "sunjce_provider.jar";
515             names[i++] = fileSep + "sunpkcs11.jar";
516             jarNames = names;
517         }
518 
519         String name = getName();
520         String localJavaHome = javaHome;
521         if (name.startsWith(localJavaHome)) {
522             String[] names = jarNames;
523             for (int i = 0; i < names.length; i++) {
524                 if (name.endsWith(names[i])) {
525                     return true;
526                 }
527             }
528         }
529         return false;
530     }
531 
532     private synchronized void ensureInitialization() {
533     try {
534         maybeInstantiateVerifier();
535     } catch (IOException e) {
536         throw new RuntimeException(e);
537     }
538     if (jv != null && !jvInitialized) {
539         initializeVerifier();
540         jvInitialized = true;
541     }
542     }
543 
544     JarEntry newEntry(ZipEntry ze) {
545     return new JarFileEntry(ze);
546     }
547 
548     Enumeration<String> entryNames(CodeSource[] cs) {
549     ensureInitialization();
550     if (jv != null) {
551         return jv.entryNames(this, cs);
552     }
553     
554     /*
555      * JAR file has no signed content. Is there a non-signing
556      * code source?
557      */
558     boolean includeUnsigned = false;
559     for (int i=0; i < cs.length; i++) {
560         if (cs[i].getCodeSigners() == null) {
561         includeUnsigned = true;
562         break;
563         }
564     }
565     if (includeUnsigned) {
566         return unsignedEntryNames();
567     } else {
568         return new Enumeration<String>() {
569             public boolean hasMoreElements() {
570             return false;
571             }
572             public String nextElement() {
573             throw new NoSuchElementException();
574             }
575         };
576     }
577     }
578         
579     /**
580      * Returns an enumeration of the zip file entries
581      * excluding internal JAR mechanism entries and including
582      * signed entries missing from the ZIP directory.
583      */
584     Enumeration<JarEntry> entries2() {
585     ensureInitialization();
586     if (jv != null) {
587         return jv.entries2(this, super.entries());
588     }
589 
590     // screen out entries which are never signed
591     final Enumeration enum_ = super.entries();
592     return new Enumeration<JarEntry>() {
593         ZipEntry entry;
594         public boolean hasMoreElements() {
595         if (entry != null) {
596             return true;
597         }
598         while (enum_.hasMoreElements()) {
599             ZipEntry ze = (ZipEntry) enum_.nextElement();
600             if (JarVerifier.isSigningRelated(ze.getName())) {
601             continue;
602             }
603             entry = ze;
604             return true;
605         }
606         return false;
607         }
608         public JarFileEntry nextElement() {
609                 if (hasMoreElements()) {
610                      ZipEntry ze = entry;
611                      entry = null;
612                      return new JarFileEntry(ze);
613                  }
614                  throw new NoSuchElementException();
615         }
616     };
617     }
618         
619     CodeSource[] getCodeSources(URL url) {
620     ensureInitialization();
621     if (jv != null) {
622         return jv.getCodeSources(this, url);
623     }
624 
625     /*
626      * JAR file has no signed content. Is there a non-signing
627      * code source?
628      */
629     Enumeration unsigned = unsignedEntryNames();
630     if (unsigned.hasMoreElements()) {
631         return new CodeSource[] { JarVerifier.getUnsignedCS(url) };
632     } else {
633         return null;
634     }
635     }
636 
637     private Enumeration<String> unsignedEntryNames() {
638     final Enumeration entries = entries();
639     return new Enumeration<String>() {
640         String name;
641 
642         /*
643          * Grab entries from ZIP directory but screen out
644          * metadata.
645          */
646         public boolean hasMoreElements() {
647         if (name != null) {
648             return true;
649         }
650         while (entries.hasMoreElements()) {
651             String value;
652             ZipEntry e = (ZipEntry) entries.nextElement();
653             value = e.getName();
654             if (e.isDirectory() || JarVerifier.isSigningRelated(value)) {
655             continue;
656             }
657             name = value;
658             return true;
659         }
660         return false;
661         }
662         public String nextElement() {
663                 if (hasMoreElements()) {
664                      String value = name;
665                      name = null;
666                      return value;
667                  }
668                  throw new NoSuchElementException();
669         }
670     };
671     }
672 
673     CodeSource getCodeSource(URL url, String name) {
674     ensureInitialization();
675     if (jv != null) {
676         if (jv.eagerValidation) {
677         CodeSource cs = null;
678         JarEntry je = getJarEntry(name);
679         if (je != null) {
680                 cs = jv.getCodeSource(url, this, je);
681         } else {
682                 cs = jv.getCodeSource(url, name);
683         }
684         return cs;
685         } else {
686             return jv.getCodeSource(url, name);
687         }
688     }
689 
690     return JarVerifier.getUnsignedCS(url);
691     }
692 
693     void setEagerValidation(boolean eager) {
694     try {
695         maybeInstantiateVerifier();
696     } catch (IOException e) {
697         throw new RuntimeException(e);
698     }
699     if (jv != null) {
700         jv.setEagerValidation(eager);
701     }
702     }
703 
704     List getManifestDigests() {
705     ensureInitialization();
706     if (jv != null) {
707         return jv.getManifestDigests();
708     }
709     return new ArrayList();
710     }
711 }
712