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