Home | History | Annotate | Download | only in jar
      1 /*
      2  * Copyright (C) 2013 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 
     18 package java.util.jar;
     19 
     20 import dalvik.system.CloseGuard;
     21 import java.io.IOException;
     22 import java.io.InputStream;
     23 import java.io.RandomAccessFile;
     24 import java.security.cert.Certificate;
     25 import java.util.HashMap;
     26 import java.util.Iterator;
     27 import java.util.zip.Inflater;
     28 import java.util.zip.ZipEntry;
     29 import java.util.zip.ZipFile;
     30 import libcore.io.IoUtils;
     31 import libcore.io.Streams;
     32 
     33 /**
     34  * A subset of the JarFile API implemented as a thin wrapper over
     35  * system/core/libziparchive.
     36  *
     37  * @hide for internal use only. Not API compatible (or as forgiving) as
     38  *        {@link java.util.jar.JarFile}
     39  */
     40 public final class StrictJarFile {
     41 
     42     private final long nativeHandle;
     43 
     44     // NOTE: It's possible to share a file descriptor with the native
     45     // code, at the cost of some additional complexity.
     46     private final RandomAccessFile raf;
     47 
     48     private final Manifest manifest;
     49     private final JarVerifier verifier;
     50 
     51     private final boolean isSigned;
     52 
     53     private final CloseGuard guard = CloseGuard.get();
     54     private boolean closed;
     55 
     56     public StrictJarFile(String fileName) throws IOException {
     57         this.nativeHandle = nativeOpenJarFile(fileName);
     58         this.raf = new RandomAccessFile(fileName, "r");
     59 
     60         try {
     61             // Read the MANIFEST and signature files up front and try to
     62             // parse them. We never want to accept a JAR File with broken signatures
     63             // or manifests, so it's best to throw as early as possible.
     64             HashMap<String, byte[]> metaEntries = getMetaEntries();
     65             this.manifest = new Manifest(metaEntries.get(JarFile.MANIFEST_NAME), true);
     66             this.verifier = new JarVerifier(fileName, manifest, metaEntries);
     67 
     68             isSigned = verifier.readCertificates() && verifier.isSignedJar();
     69         } catch (IOException ioe) {
     70             nativeClose(this.nativeHandle);
     71             throw ioe;
     72         }
     73 
     74         guard.open("close");
     75     }
     76 
     77     public Manifest getManifest() {
     78         return manifest;
     79     }
     80 
     81     public Iterator<ZipEntry> iterator() throws IOException {
     82         return new EntryIterator(nativeHandle, "");
     83     }
     84 
     85     public ZipEntry findEntry(String name) {
     86         return nativeFindEntry(nativeHandle, name);
     87     }
     88 
     89     /**
     90      * Return all certificate chains for a given {@link ZipEntry} belonging to this jar.
     91      * This method MUST be called only after fully exhausting the InputStream belonging
     92      * to this entry.
     93      *
     94      * Returns {@code null} if this jar file isn't signed or if this method is
     95      * called before the stream is processed.
     96      */
     97     public Certificate[][] getCertificateChains(ZipEntry ze) {
     98         if (isSigned) {
     99             return verifier.getCertificateChains(ze.getName());
    100         }
    101 
    102         return null;
    103     }
    104 
    105     /**
    106      * Return all certificates for a given {@link ZipEntry} belonging to this jar.
    107      * This method MUST be called only after fully exhausting the InputStream belonging
    108      * to this entry.
    109      *
    110      * Returns {@code null} if this jar file isn't signed or if this method is
    111      * called before the stream is processed.
    112      *
    113      * @deprecated Switch callers to use getCertificateChains instead
    114      */
    115     @Deprecated
    116     public Certificate[] getCertificates(ZipEntry ze) {
    117         if (isSigned) {
    118             Certificate[][] certChains = verifier.getCertificateChains(ze.getName());
    119 
    120             // Measure number of certs.
    121             int count = 0;
    122             for (Certificate[] chain : certChains) {
    123                 count += chain.length;
    124             }
    125 
    126             // Create new array and copy all the certs into it.
    127             Certificate[] certs = new Certificate[count];
    128             int i = 0;
    129             for (Certificate[] chain : certChains) {
    130                 System.arraycopy(chain, 0, certs, i, chain.length);
    131                 i += chain.length;
    132             }
    133 
    134             return certs;
    135         }
    136 
    137         return null;
    138     }
    139 
    140     public InputStream getInputStream(ZipEntry ze) {
    141         final InputStream is = getZipInputStream(ze);
    142 
    143         if (isSigned) {
    144             JarVerifier.VerifierEntry entry = verifier.initEntry(ze.getName());
    145             if (entry == null) {
    146                 return is;
    147             }
    148 
    149             return new JarFile.JarFileInputStream(is, ze.getSize(), entry);
    150         }
    151 
    152         return is;
    153     }
    154 
    155     public void close() throws IOException {
    156         if (!closed) {
    157             guard.close();
    158 
    159             nativeClose(nativeHandle);
    160             IoUtils.closeQuietly(raf);
    161             closed = true;
    162         }
    163     }
    164 
    165     private InputStream getZipInputStream(ZipEntry ze) {
    166         if (ze.getMethod() == ZipEntry.STORED) {
    167             return new ZipFile.RAFStream(raf, ze.getDataOffset(),
    168                     ze.getDataOffset() + ze.getSize());
    169         } else {
    170             final ZipFile.RAFStream wrapped = new ZipFile.RAFStream(
    171                     raf, ze.getDataOffset(), ze.getDataOffset() + ze.getCompressedSize());
    172 
    173             int bufSize = Math.max(1024, (int) Math.min(ze.getSize(), 65535L));
    174             return new ZipFile.ZipInflaterInputStream(wrapped, new Inflater(true), bufSize, ze);
    175         }
    176     }
    177 
    178     static final class EntryIterator implements Iterator<ZipEntry> {
    179         private final long iterationHandle;
    180         private ZipEntry nextEntry;
    181 
    182         EntryIterator(long nativeHandle, String prefix) throws IOException {
    183             iterationHandle = nativeStartIteration(nativeHandle, prefix);
    184         }
    185 
    186         public ZipEntry next() {
    187             if (nextEntry != null) {
    188                 final ZipEntry ze = nextEntry;
    189                 nextEntry = null;
    190                 return ze;
    191             }
    192 
    193             return nativeNextEntry(iterationHandle);
    194         }
    195 
    196         public boolean hasNext() {
    197             if (nextEntry != null) {
    198                 return true;
    199             }
    200 
    201             final ZipEntry ze = nativeNextEntry(iterationHandle);
    202             if (ze == null) {
    203                 return false;
    204             }
    205 
    206             nextEntry = ze;
    207             return true;
    208         }
    209 
    210         public void remove() {
    211             throw new UnsupportedOperationException();
    212         }
    213     }
    214 
    215     private HashMap<String, byte[]> getMetaEntries() throws IOException {
    216         HashMap<String, byte[]> metaEntries = new HashMap<String, byte[]>();
    217 
    218         Iterator<ZipEntry> entryIterator = new EntryIterator(nativeHandle, "META-INF/");
    219         while (entryIterator.hasNext()) {
    220             final ZipEntry entry = entryIterator.next();
    221             metaEntries.put(entry.getName(), Streams.readFully(getInputStream(entry)));
    222         }
    223 
    224         return metaEntries;
    225     }
    226 
    227     private static native long nativeOpenJarFile(String fileName) throws IOException;
    228     private static native long nativeStartIteration(long nativeHandle, String prefix);
    229     private static native ZipEntry nativeNextEntry(long iterationHandle);
    230     private static native ZipEntry nativeFindEntry(long nativeHandle, String entryName);
    231     private static native void nativeClose(long nativeHandle);
    232 }
    233