Home | History | Annotate | Download | only in jar
      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      */
     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 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 
    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      */
    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      */
    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      */
    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