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