JarFile.java revision 00b71736c88addbe7cb5fa00b194695dde40a3a6
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
27package java.util.jar;
28
29import java.io.*;
30import java.lang.ref.SoftReference;
31import java.net.URL;
32import java.util.*;
33import java.util.zip.*;
34import java.security.CodeSigner;
35import java.security.cert.Certificate;
36import java.security.AccessController;
37import java.security.CodeSource;
38import sun.misc.IOUtils;
39import sun.security.action.GetPropertyAction;
40import sun.security.util.ManifestEntryVerifier;
41/* ----- BEGIN android -----
42import 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 */
63public
64class 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     */
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     */
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     */
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     */
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     */
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     */
173    public Manifest getManifest() throws IOException {
174        return getManifestFromReference();
175    }
176
177    private 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
201    /* ----- BEGIN android -----
202    private native String[] getMetaInfEntryNames();*/
203
204    private String[] getMetaInfEntryNames() {
205        List<String> list = new ArrayList<String>(8);
206
207        Enumeration<? extends ZipEntry> allEntries = entries();
208        while (allEntries.hasMoreElements()) {
209            ZipEntry ze = allEntries.nextElement();
210            if (ze.getName().startsWith(META_DIR)
211                    && ze.getName().length() > META_DIR.length()) {
212                list.add(ze.getName());
213            }
214        }
215        return list.toArray(new String[list.size()]);
216    }
217    // ----- END android -----
218
219    /**
220     * Returns the <code>JarEntry</code> for the given entry name or
221     * <code>null</code> if not found.
222     *
223     * @param name the jar file entry name
224     * @return the <code>JarEntry</code> for the given entry name or
225     *         <code>null</code> if not found.
226     *
227     * @throws IllegalStateException
228     *         may be thrown if the jar file has been closed
229     *
230     * @see java.util.jar.JarEntry
231     */
232    public JarEntry getJarEntry(String name) {
233        return (JarEntry)getEntry(name);
234    }
235
236    /**
237     * Returns the <code>ZipEntry</code> for the given entry name or
238     * <code>null</code> if not found.
239     *
240     * @param name the jar file entry name
241     * @return the <code>ZipEntry</code> for the given entry name or
242     *         <code>null</code> if not found
243     *
244     * @throws IllegalStateException
245     *         may be thrown if the jar file has been closed
246     *
247     * @see java.util.zip.ZipEntry
248     */
249    public ZipEntry getEntry(String name) {
250        ZipEntry ze = super.getEntry(name);
251        if (ze != null) {
252            return new JarFileEntry(ze);
253        }
254        return null;
255    }
256
257    /**
258     * Returns an enumeration of the zip file entries.
259     */
260    public Enumeration<JarEntry> entries() {
261        final Enumeration enum_ = super.entries();
262        return new Enumeration<JarEntry>() {
263            public boolean hasMoreElements() {
264                return enum_.hasMoreElements();
265            }
266            public JarFileEntry nextElement() {
267                ZipEntry ze = (ZipEntry)enum_.nextElement();
268                return new JarFileEntry(ze);
269            }
270        };
271    }
272
273    private class JarFileEntry extends JarEntry {
274        JarFileEntry(ZipEntry ze) {
275            super(ze);
276        }
277        public Attributes getAttributes() throws IOException {
278            Manifest man = JarFile.this.getManifest();
279            if (man != null) {
280                return man.getAttributes(getName());
281            } else {
282                return null;
283            }
284        }
285        public Certificate[] getCertificates() {
286            try {
287                maybeInstantiateVerifier();
288            } catch (IOException e) {
289                throw new RuntimeException(e);
290            }
291            if (certs == null && jv != null) {
292                certs = jv.getCerts(JarFile.this, this);
293            }
294            return certs == null ? null : certs.clone();
295        }
296        public CodeSigner[] getCodeSigners() {
297            try {
298                maybeInstantiateVerifier();
299            } catch (IOException e) {
300                throw new RuntimeException(e);
301            }
302            if (signers == null && jv != null) {
303                signers = jv.getCodeSigners(JarFile.this, this);
304            }
305            return signers == null ? null : signers.clone();
306        }
307    }
308
309    /*
310     * Ensures that the JarVerifier has been created if one is
311     * necessary (i.e., the jar appears to be signed.) This is done as
312     * a quick check to avoid processing of the manifest for unsigned
313     * jars.
314     */
315    private void maybeInstantiateVerifier() throws IOException {
316        if (jv != null) {
317            return;
318        }
319
320        if (verify) {
321            String[] names = getMetaInfEntryNames();
322            if (names != null) {
323                for (int i = 0; i < names.length; i++) {
324                    String name = names[i].toUpperCase(Locale.ENGLISH);
325                    if (name.endsWith(".DSA") ||
326                        name.endsWith(".RSA") ||
327                        name.endsWith(".EC") ||
328                        name.endsWith(".SF")) {
329                        // Assume since we found a signature-related file
330                        // that the jar is signed and that we therefore
331                        // need a JarVerifier and Manifest
332                        getManifest();
333                        return;
334                    }
335                }
336            }
337            // No signature-related files; don't instantiate a
338            // verifier
339            verify = false;
340        }
341    }
342
343
344    /*
345     * Initializes the verifier object by reading all the manifest
346     * entries and passing them to the verifier.
347     */
348    private void initializeVerifier() {
349        ManifestEntryVerifier mev = null;
350
351        // Verify "META-INF/" entries...
352        try {
353            String[] names = getMetaInfEntryNames();
354            if (names != null) {
355                for (int i = 0; i < names.length; i++) {
356                    JarEntry e = getJarEntry(names[i]);
357                    if (e == null) {
358                        throw new JarException("corrupted jar file");
359                    }
360                    if (!e.isDirectory()) {
361                        if (mev == null) {
362                            mev = new ManifestEntryVerifier
363                                (getManifestFromReference());
364                        }
365                        byte[] b = getBytes(e);
366                        if (b != null && b.length > 0) {
367                            jv.beginEntry(e, mev);
368                            jv.update(b.length, b, 0, b.length, mev);
369                            jv.update(-1, null, 0, 0, mev);
370                        }
371                    }
372                }
373            }
374        } catch (IOException ex) {
375            // if we had an error parsing any blocks, just
376            // treat the jar file as being unsigned
377            jv = null;
378            verify = false;
379            if (JarVerifier.debug != null) {
380                JarVerifier.debug.println("jarfile parsing error!");
381                ex.printStackTrace();
382            }
383        }
384
385        // if after initializing the verifier we have nothing
386        // signed, we null it out.
387
388        if (jv != null) {
389
390            jv.doneWithMeta();
391            if (JarVerifier.debug != null) {
392                JarVerifier.debug.println("done with meta!");
393            }
394
395            if (jv.nothingToVerify()) {
396                if (JarVerifier.debug != null) {
397                    JarVerifier.debug.println("nothing to verify!");
398                }
399                jv = null;
400                verify = false;
401            }
402        }
403    }
404
405    /*
406     * Reads all the bytes for a given entry. Used to process the
407     * META-INF files.
408     */
409    private byte[] getBytes(ZipEntry ze) throws IOException {
410        try (InputStream is = super.getInputStream(ze)) {
411            return IOUtils.readFully(is, (int)ze.getSize(), true);
412        }
413    }
414
415    /**
416     * Returns an input stream for reading the contents of the specified
417     * zip file entry.
418     * @param ze the zip file entry
419     * @return an input stream for reading the contents of the specified
420     *         zip file entry
421     * @throws ZipException if a zip file format error has occurred
422     * @throws IOException if an I/O error has occurred
423     * @throws SecurityException if any of the jar file entries
424     *         are incorrectly signed.
425     * @throws IllegalStateException
426     *         may be thrown if the jar file has been closed
427     */
428    public synchronized InputStream getInputStream(ZipEntry ze)
429        throws IOException
430    {
431        maybeInstantiateVerifier();
432        if (jv == null) {
433            return super.getInputStream(ze);
434        }
435        if (!jvInitialized) {
436            initializeVerifier();
437            jvInitialized = true;
438            // could be set to null after a call to
439            // initializeVerifier if we have nothing to
440            // verify
441            if (jv == null)
442                return super.getInputStream(ze);
443        }
444
445        // wrap a verifier stream around the real stream
446        return new JarVerifier.VerifierStream(
447            getManifestFromReference(),
448            ze instanceof JarFileEntry ?
449            (JarEntry) ze : getJarEntry(ze.getName()),
450            super.getInputStream(ze),
451            jv);
452    }
453
454    // Statics for hand-coded Boyer-Moore search in hasClassPathAttribute()
455    // The bad character shift for "class-path"
456    private static int[] lastOcc;
457    // The good suffix shift for "class-path"
458    private static int[] optoSft;
459    // Initialize the shift arrays to search for "class-path"
460    private static char[] src = {'c','l','a','s','s','-','p','a','t','h'};
461    static {
462        lastOcc = new int[128];
463        optoSft = new int[10];
464        lastOcc[(int)'c']=1;
465        lastOcc[(int)'l']=2;
466        lastOcc[(int)'s']=5;
467        lastOcc[(int)'-']=6;
468        lastOcc[(int)'p']=7;
469        lastOcc[(int)'a']=8;
470        lastOcc[(int)'t']=9;
471        lastOcc[(int)'h']=10;
472        for (int i=0; i<9; i++)
473            optoSft[i]=10;
474        optoSft[9]=1;
475    }
476
477    private JarEntry getManEntry() {
478        if (manEntry == null) {
479            // First look up manifest entry using standard name
480            manEntry = getJarEntry(MANIFEST_NAME);
481            if (manEntry == null) {
482                // If not found, then iterate through all the "META-INF/"
483                // entries to find a match.
484                String[] names = getMetaInfEntryNames();
485                if (names != null) {
486                    for (int i = 0; i < names.length; i++) {
487                        if (MANIFEST_NAME.equals(
488                                                 names[i].toUpperCase(Locale.ENGLISH))) {
489                            manEntry = getJarEntry(names[i]);
490                            break;
491                        }
492                    }
493                }
494            }
495        }
496        return manEntry;
497    }
498
499    // Returns true iff this jar file has a manifest with a class path
500    // attribute. Returns false if there is no manifest or the manifest
501    // does not contain a "Class-Path" attribute. Currently exported to
502    // core libraries via sun.misc.SharedSecrets.
503    /**
504     * @hide
505     */
506    public boolean hasClassPathAttribute() throws IOException {
507        if (computedHasClassPathAttribute) {
508            return hasClassPathAttribute;
509        }
510
511        hasClassPathAttribute = false;
512        if (!isKnownToNotHaveClassPathAttribute()) {
513            JarEntry manEntry = getManEntry();
514            if (manEntry != null) {
515                byte[] b = getBytes(manEntry);
516                int last = b.length - src.length;
517                int i = 0;
518                next:
519                while (i<=last) {
520                    for (int j=9; j>=0; j--) {
521                        char c = (char) b[i+j];
522                        c = (((c-'A')|('Z'-c)) >= 0) ? (char)(c + 32) : c;
523                        if (c != src[j]) {
524                            i += Math.max(j + 1 - lastOcc[c&0x7F], optoSft[j]);
525                            continue next;
526                        }
527                    }
528                    hasClassPathAttribute = true;
529                    break;
530                }
531            }
532        }
533        computedHasClassPathAttribute = true;
534        return hasClassPathAttribute;
535    }
536
537    private static String javaHome;
538    private static String[] jarNames;
539    private boolean isKnownToNotHaveClassPathAttribute() {
540        // Optimize away even scanning of manifest for jar files we
541        // deliver which don't have a class-path attribute. If one of
542        // these jars is changed to include such an attribute this code
543        // must be changed.
544        if (javaHome == null) {
545            javaHome = AccessController.doPrivileged(
546                new GetPropertyAction("java.home"));
547        }
548        if (jarNames == null) {
549            String[] names = new String[10];
550            String fileSep = File.separator;
551            int i = 0;
552            names[i++] = fileSep + "rt.jar";
553            names[i++] = fileSep + "sunrsasign.jar";
554            names[i++] = fileSep + "jsse.jar";
555            names[i++] = fileSep + "jce.jar";
556            names[i++] = fileSep + "charsets.jar";
557            names[i++] = fileSep + "dnsns.jar";
558            names[i++] = fileSep + "ldapsec.jar";
559            names[i++] = fileSep + "localedata.jar";
560            names[i++] = fileSep + "sunjce_provider.jar";
561            names[i++] = fileSep + "sunpkcs11.jar";
562            jarNames = names;
563        }
564
565        String name = getName();
566        String localJavaHome = javaHome;
567        if (name.startsWith(localJavaHome)) {
568            String[] names = jarNames;
569            for (int i = 0; i < names.length; i++) {
570                if (name.endsWith(names[i])) {
571                    return true;
572                }
573            }
574        }
575        return false;
576    }
577
578    private synchronized void ensureInitialization() {
579        try {
580            maybeInstantiateVerifier();
581        } catch (IOException e) {
582            throw new RuntimeException(e);
583        }
584        if (jv != null && !jvInitialized) {
585            initializeVerifier();
586            jvInitialized = true;
587        }
588    }
589
590    JarEntry newEntry(ZipEntry ze) {
591        return new JarFileEntry(ze);
592    }
593
594    Enumeration<String> entryNames(CodeSource[] cs) {
595        ensureInitialization();
596        if (jv != null) {
597            return jv.entryNames(this, cs);
598        }
599
600        /*
601         * JAR file has no signed content. Is there a non-signing
602         * code source?
603         */
604        boolean includeUnsigned = false;
605        for (int i = 0; i < cs.length; i++) {
606            if (cs[i].getCodeSigners() == null) {
607                includeUnsigned = true;
608                break;
609            }
610        }
611        if (includeUnsigned) {
612            return unsignedEntryNames();
613        } else {
614            return new Enumeration<String>() {
615
616                public boolean hasMoreElements() {
617                    return false;
618                }
619
620                public String nextElement() {
621                    throw new NoSuchElementException();
622                }
623            };
624        }
625    }
626
627    /**
628     * Returns an enumeration of the zip file entries
629     * excluding internal JAR mechanism entries and including
630     * signed entries missing from the ZIP directory.
631     */
632    Enumeration<JarEntry> entries2() {
633        ensureInitialization();
634        if (jv != null) {
635            return jv.entries2(this, super.entries());
636        }
637
638        // screen out entries which are never signed
639        final Enumeration enum_ = super.entries();
640        return new Enumeration<JarEntry>() {
641
642            ZipEntry entry;
643
644            public boolean hasMoreElements() {
645                if (entry != null) {
646                    return true;
647                }
648                while (enum_.hasMoreElements()) {
649                    ZipEntry ze = (ZipEntry) enum_.nextElement();
650                    if (JarVerifier.isSigningRelated(ze.getName())) {
651                        continue;
652                    }
653                    entry = ze;
654                    return true;
655                }
656                return false;
657            }
658
659            public JarFileEntry nextElement() {
660                if (hasMoreElements()) {
661                    ZipEntry ze = entry;
662                    entry = null;
663                    return new JarFileEntry(ze);
664                }
665                throw new NoSuchElementException();
666            }
667        };
668    }
669
670    CodeSource[] getCodeSources(URL url) {
671        ensureInitialization();
672        if (jv != null) {
673            return jv.getCodeSources(this, url);
674        }
675
676        /*
677         * JAR file has no signed content. Is there a non-signing
678         * code source?
679         */
680        Enumeration unsigned = unsignedEntryNames();
681        if (unsigned.hasMoreElements()) {
682            return new CodeSource[]{JarVerifier.getUnsignedCS(url)};
683        } else {
684            return null;
685        }
686    }
687
688    private Enumeration<String> unsignedEntryNames() {
689        final Enumeration entries = entries();
690        return new Enumeration<String>() {
691
692            String name;
693
694            /*
695             * Grab entries from ZIP directory but screen out
696             * metadata.
697             */
698            public boolean hasMoreElements() {
699                if (name != null) {
700                    return true;
701                }
702                while (entries.hasMoreElements()) {
703                    String value;
704                    ZipEntry e = (ZipEntry) entries.nextElement();
705                    value = e.getName();
706                    if (e.isDirectory() || JarVerifier.isSigningRelated(value)) {
707                        continue;
708                    }
709                    name = value;
710                    return true;
711                }
712                return false;
713            }
714
715            public String nextElement() {
716                if (hasMoreElements()) {
717                    String value = name;
718                    name = null;
719                    return value;
720                }
721                throw new NoSuchElementException();
722            }
723        };
724    }
725
726    CodeSource getCodeSource(URL url, String name) {
727        ensureInitialization();
728        if (jv != null) {
729            if (jv.eagerValidation) {
730                CodeSource cs = null;
731                JarEntry je = getJarEntry(name);
732                if (je != null) {
733                    cs = jv.getCodeSource(url, this, je);
734                } else {
735                    cs = jv.getCodeSource(url, name);
736                }
737                return cs;
738            } else {
739                return jv.getCodeSource(url, name);
740            }
741        }
742
743        return JarVerifier.getUnsignedCS(url);
744    }
745
746    void setEagerValidation(boolean eager) {
747        try {
748            maybeInstantiateVerifier();
749        } catch (IOException e) {
750            throw new RuntimeException(e);
751        }
752        if (jv != null) {
753            jv.setEagerValidation(eager);
754        }
755    }
756
757    List getManifestDigests() {
758        ensureInitialization();
759        if (jv != null) {
760            return jv.getManifestDigests();
761        }
762        return new ArrayList();
763    }
764}
765