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
18package java.util.jar;
19
20import java.io.ByteArrayOutputStream;
21import java.io.IOException;
22import java.io.InputStream;
23import java.io.OutputStream;
24import java.util.HashMap;
25import java.util.Locale;
26import java.util.zip.ZipEntry;
27import java.util.zip.ZipInputStream;
28import 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.
41public 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