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