Home | History | Annotate | Download | only in jar
      1 /*
      2  * Licensed to the Apache Software Foundation (ASF) under one or more
      3  * contributor license agreements.  See the NOTICE file distributed with
      4  * this work for additional information regarding copyright ownership.
      5  * The ASF licenses this file to You under the Apache License, Version 2.0
      6  * (the "License"); you may not use this file except in compliance with
      7  * the License.  You may obtain a copy of the License at
      8  *
      9  *     http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  * Unless required by applicable law or agreed to in writing, software
     12  * distributed under the License is distributed on an "AS IS" BASIS,
     13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  * See the License for the specific language governing permissions and
     15  * limitations under the License.
     16  */
     17 
     18 package java.util.jar;
     19 
     20 import java.io.ByteArrayOutputStream;
     21 import java.io.IOException;
     22 import java.io.InputStream;
     23 import java.io.OutputStream;
     24 import java.util.HashMap;
     25 import java.util.Locale;
     26 import java.util.zip.ZipEntry;
     27 import java.util.zip.ZipInputStream;
     28 import libcore.io.Streams;
     29 
     30 /**
     31  * The input stream from which the JAR file to be read may be fetched. It is
     32  * used like the {@code ZipInputStream}.
     33  *
     34  * @see ZipInputStream
     35  */
     36 // TODO: The semantics provided by this class are really weird. The jar file
     37 // spec does not impose any ordering constraints on the entries of a jar file.
     38 // In particular, the Manifest and META-INF directory *need not appear first*. This
     39 // class will silently skip certificate checks for jar files where the manifest
     40 // isn't the first entry. To do this correctly, we need O(input_stream_length) memory.
     41 public class JarInputStream extends ZipInputStream {
     42 
     43     private Manifest manifest;
     44 
     45     private boolean verified = false;
     46 
     47     private JarEntry currentJarEntry;
     48 
     49     private JarEntry pendingJarEntry;
     50 
     51     private boolean isMeta;
     52 
     53     private JarVerifier verifier;
     54 
     55     private OutputStream verStream;
     56 
     57     /**
     58      * Constructs a new {@code JarInputStream} from an input stream.
     59      *
     60      * @param stream
     61      *            the input stream containing the JAR file.
     62      * @param verify
     63      *            if the file should be verified with a {@code JarVerifier}.
     64      * @throws IOException
     65      *             If an error occurs reading entries from the input stream.
     66      * @see ZipInputStream#ZipInputStream(InputStream)
     67      */
     68     public JarInputStream(InputStream stream, boolean verify) throws IOException {
     69         super(stream);
     70 
     71         verifier = null;
     72         pendingJarEntry = null;
     73         currentJarEntry = null;
     74 
     75         if (getNextJarEntry() == null) {
     76             return;
     77         }
     78 
     79         if (currentJarEntry.getName().equalsIgnoreCase(JarFile.META_DIR)) {
     80             // Fetch the next entry, in the hope that it's the manifest file.
     81             closeEntry();
     82             getNextJarEntry();
     83         }
     84 
     85         if (currentJarEntry.getName().equalsIgnoreCase(JarFile.MANIFEST_NAME)) {
     86             final byte[] manifestBytes = Streams.readFullyNoClose(this);
     87             manifest = new Manifest(manifestBytes, verify);
     88             closeEntry();
     89 
     90             if (verify) {
     91                 HashMap<String, byte[]> metaEntries = new HashMap<String, byte[]>();
     92                 metaEntries.put(JarFile.MANIFEST_NAME, manifestBytes);
     93                 verifier = new JarVerifier("JarInputStream", manifest, metaEntries);
     94             }
     95         }
     96 
     97         // There was no manifest available, so we should return the current
     98         // entry the next time getNextEntry is called.
     99         pendingJarEntry = currentJarEntry;
    100         currentJarEntry = null;
    101 
    102         // If the manifest isn't the first entry, we will not have enough
    103         // information to perform verification on entries that precede it.
    104         //
    105         // TODO: Should we throw if verify == true in this case ?
    106         // TODO: We need all meta entries to be placed before the manifest
    107         // as well.
    108     }
    109 
    110     /**
    111      * Constructs a new {@code JarInputStream} from an input stream.
    112      *
    113      * @param stream
    114      *            the input stream containing the JAR file.
    115      * @throws IOException
    116      *             If an error occurs reading entries from the input stream.
    117      * @see ZipInputStream#ZipInputStream(InputStream)
    118      */
    119     public JarInputStream(InputStream stream) throws IOException {
    120         this(stream, true);
    121     }
    122 
    123     /**
    124      * Returns the {@code Manifest} object associated with this {@code
    125      * JarInputStream} or {@code null} if no manifest entry exists.
    126      *
    127      * @return the MANIFEST specifying the contents of the JAR file.
    128      */
    129     public Manifest getManifest() {
    130         return manifest;
    131     }
    132 
    133     /**
    134      * Returns the next {@code JarEntry} contained in this stream or {@code
    135      * null} if no more entries are present.
    136      *
    137      * @return the next JAR entry.
    138      * @throws IOException
    139      *             if an error occurs while reading the entry.
    140      */
    141     public JarEntry getNextJarEntry() throws IOException {
    142         return (JarEntry) getNextEntry();
    143     }
    144 
    145     /**
    146      * Reads up to {@code byteCount} bytes of decompressed data and stores it in
    147      * {@code buffer} starting at {@code byteOffset}. Returns the number of uncompressed bytes read.
    148      *
    149      * @throws IOException
    150      *             if an IOException occurs.
    151      */
    152     @Override
    153     public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException {
    154         if (currentJarEntry == null) {
    155             return -1;
    156         }
    157 
    158         int r = super.read(buffer, byteOffset, byteCount);
    159         // verifier can be null if we've been asked not to verify or if
    160         // the manifest wasn't found.
    161         //
    162         // verStream will be null if we're reading the manifest or if we have
    163         // no signatures or if the digest for this entry isn't present in the
    164         // manifest.
    165         if (verifier != null && verStream != null && !verified) {
    166             if (r == -1) {
    167                 // We've hit the end of this stream for the first time, so attempt
    168                 // a verification.
    169                 verified = true;
    170                 if (isMeta) {
    171                     verifier.addMetaEntry(currentJarEntry.getName(),
    172                             ((ByteArrayOutputStream) verStream).toByteArray());
    173                     try {
    174                         verifier.readCertificates();
    175                     } catch (SecurityException e) {
    176                         verifier = null;
    177                         throw e;
    178                     }
    179                 } else {
    180                     ((JarVerifier.VerifierEntry) verStream).verify();
    181                 }
    182             } else {
    183                 verStream.write(buffer, byteOffset, r);
    184             }
    185         }
    186 
    187         return r;
    188     }
    189 
    190     /**
    191      * Returns the next {@code ZipEntry} contained in this stream or {@code
    192      * null} if no more entries are present.
    193      *
    194      * @return the next extracted ZIP entry.
    195      * @throws IOException
    196      *             if an error occurs while reading the entry.
    197      */
    198     @Override
    199     public ZipEntry getNextEntry() throws IOException {
    200         // NOTE: This function must update the value of currentJarEntry
    201         // as a side effect.
    202 
    203         if (pendingJarEntry != null) {
    204             JarEntry pending = pendingJarEntry;
    205             pendingJarEntry = null;
    206             currentJarEntry = pending;
    207             return pending;
    208         }
    209 
    210         currentJarEntry = (JarEntry) super.getNextEntry();
    211         if (currentJarEntry == null) {
    212             return null;
    213         }
    214 
    215         if (verifier != null) {
    216             isMeta = currentJarEntry.getName().toUpperCase(Locale.US).startsWith(JarFile.META_DIR);
    217             if (isMeta) {
    218                 final int entrySize = (int) currentJarEntry.getSize();
    219                 verStream = new ByteArrayOutputStream(entrySize > 0 ? entrySize : 8192);
    220             } else {
    221                 verStream = verifier.initEntry(currentJarEntry.getName());
    222             }
    223         }
    224 
    225         verified = false;
    226         return currentJarEntry;
    227     }
    228 
    229     @Override
    230     public void closeEntry() throws IOException {
    231         // NOTE: This was the old behavior. A call to closeEntry() before the
    232         // first call to getNextEntry should be a no-op. If we don't return early
    233         // here, the super class will close pendingJarEntry for us and reads will
    234         // fail.
    235         if (pendingJarEntry != null) {
    236             return;
    237         }
    238 
    239         super.closeEntry();
    240         currentJarEntry = null;
    241     }
    242 
    243     @Override
    244     protected ZipEntry createZipEntry(String name) {
    245         JarEntry entry = new JarEntry(name);
    246         if (manifest != null) {
    247             entry.setAttributes(manifest.getAttributes(name));
    248         }
    249         return entry;
    250     }
    251 }
    252