JarFile.java revision f589846f86761ffea3c06ab9d105d3f19328d121
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.File;
21import java.io.FilterInputStream;
22import java.io.IOException;
23import java.io.InputStream;
24import java.util.ArrayList;
25import java.util.Enumeration;
26import java.util.List;
27import java.util.zip.ZipEntry;
28import java.util.zip.ZipFile;
29import libcore.io.Streams;
30
31/**
32 * {@code JarFile} is used to read jar entries and their associated data from
33 * jar files.
34 *
35 * @see JarInputStream
36 * @see JarEntry
37 */
38public class JarFile extends ZipFile {
39
40    /**
41     * The MANIFEST file name.
42     */
43    public static final String MANIFEST_NAME = "META-INF/MANIFEST.MF";
44
45    // The directory containing the manifest.
46    static final String META_DIR = "META-INF/";
47
48    // The manifest after it has been read from the JAR.
49    private Manifest manifest;
50
51    // The entry for the MANIFEST.MF file before it is read.
52    private ZipEntry manifestEntry;
53
54    JarVerifier verifier;
55
56    private boolean closed = false;
57
58    static final class JarFileInputStream extends FilterInputStream {
59        private long count;
60
61        private ZipEntry zipEntry;
62
63        private JarVerifier.VerifierEntry entry;
64
65        private boolean done = false;
66
67        JarFileInputStream(InputStream is, ZipEntry ze,
68                JarVerifier.VerifierEntry e) {
69            super(is);
70            zipEntry = ze;
71            count = zipEntry.getSize();
72            entry = e;
73        }
74
75        @Override
76        public int read() throws IOException {
77            if (done) {
78                return -1;
79            }
80            if (count > 0) {
81                int r = super.read();
82                if (r != -1) {
83                    entry.write(r);
84                    count--;
85                } else {
86                    count = 0;
87                }
88                if (count == 0) {
89                    done = true;
90                    entry.verify();
91                }
92                return r;
93            } else {
94                done = true;
95                entry.verify();
96                return -1;
97            }
98        }
99
100        @Override
101        public int read(byte[] buf, int off, int nbytes) throws IOException {
102            if (done) {
103                return -1;
104            }
105            if (count > 0) {
106                int r = super.read(buf, off, nbytes);
107                if (r != -1) {
108                    int size = r;
109                    if (count < size) {
110                        size = (int) count;
111                    }
112                    entry.write(buf, off, size);
113                    count -= size;
114                } else {
115                    count = 0;
116                }
117                if (count == 0) {
118                    done = true;
119                    entry.verify();
120                }
121                return r;
122            } else {
123                done = true;
124                entry.verify();
125                return -1;
126            }
127        }
128
129        @Override
130        public int available() throws IOException {
131            if (done) {
132                return 0;
133            }
134            return super.available();
135        }
136
137        @Override
138        public long skip(long byteCount) throws IOException {
139            return Streams.skipByReading(this, byteCount);
140        }
141    }
142
143    /**
144     * Create a new {@code JarFile} using the contents of the specified file.
145     *
146     * @param file
147     *            the JAR file as {@link File}.
148     * @throws IOException
149     *             If the file cannot be read.
150     */
151    public JarFile(File file) throws IOException {
152        this(file, true);
153    }
154
155    /**
156     * Create a new {@code JarFile} using the contents of the specified file.
157     *
158     * @param file
159     *            the JAR file as {@link File}.
160     * @param verify
161     *            if this JAR file is signed whether it must be verified.
162     * @throws IOException
163     *             If the file cannot be read.
164     */
165    public JarFile(File file, boolean verify) throws IOException {
166        super(file);
167        if (verify) {
168            verifier = new JarVerifier(file.getPath());
169        }
170        readMetaEntries();
171    }
172
173    /**
174     * Create a new {@code JarFile} using the contents of file.
175     *
176     * @param file
177     *            the JAR file as {@link File}.
178     * @param verify
179     *            if this JAR filed is signed whether it must be verified.
180     * @param mode
181     *            the mode to use, either {@link ZipFile#OPEN_READ OPEN_READ} or
182     *            {@link ZipFile#OPEN_DELETE OPEN_DELETE}.
183     * @throws IOException
184     *             If the file cannot be read.
185     */
186    public JarFile(File file, boolean verify, int mode) throws IOException {
187        super(file, mode);
188        if (verify) {
189            verifier = new JarVerifier(file.getPath());
190        }
191        readMetaEntries();
192    }
193
194    /**
195     * Create a new {@code JarFile} from the contents of the file specified by
196     * filename.
197     *
198     * @param filename
199     *            the file name referring to the JAR file.
200     * @throws IOException
201     *             if file name cannot be opened for reading.
202     */
203    public JarFile(String filename) throws IOException {
204        this(filename, true);
205    }
206
207    /**
208     * Create a new {@code JarFile} from the contents of the file specified by
209     * {@code filename}.
210     *
211     * @param filename
212     *            the file name referring to the JAR file.
213     * @param verify
214     *            if this JAR filed is signed whether it must be verified.
215     * @throws IOException
216     *             If file cannot be opened or read.
217     */
218    public JarFile(String filename, boolean verify) throws IOException {
219        super(filename);
220        if (verify) {
221            verifier = new JarVerifier(filename);
222        }
223        readMetaEntries();
224    }
225
226    /**
227     * Return an enumeration containing the {@code JarEntrys} contained in this
228     * {@code JarFile}.
229     *
230     * @return the {@code Enumeration} containing the JAR entries.
231     * @throws IllegalStateException
232     *             if this {@code JarFile} is closed.
233     */
234    @Override
235    public Enumeration<JarEntry> entries() {
236        class JarFileEnumerator implements Enumeration<JarEntry> {
237            Enumeration<? extends ZipEntry> ze;
238
239            JarFile jf;
240
241            JarFileEnumerator(Enumeration<? extends ZipEntry> zenum, JarFile jf) {
242                ze = zenum;
243                this.jf = jf;
244            }
245
246            public boolean hasMoreElements() {
247                return ze.hasMoreElements();
248            }
249
250            public JarEntry nextElement() {
251                JarEntry je = new JarEntry(ze.nextElement());
252                je.parentJar = jf;
253                return je;
254            }
255        }
256        return new JarFileEnumerator(super.entries(), this);
257    }
258
259    /**
260     * Return the {@code JarEntry} specified by its name or {@code null} if no
261     * such entry exists.
262     *
263     * @param name
264     *            the name of the entry in the JAR file.
265     * @return the JAR entry defined by the name.
266     */
267    public JarEntry getJarEntry(String name) {
268        return (JarEntry) getEntry(name);
269    }
270
271    /**
272     * Returns the {@code Manifest} object associated with this {@code JarFile}
273     * or {@code null} if no MANIFEST entry exists.
274     *
275     * @return the MANIFEST.
276     * @throws IOException
277     *             if an error occurs reading the MANIFEST file.
278     * @throws IllegalStateException
279     *             if the jar file is closed.
280     * @see Manifest
281     */
282    public Manifest getManifest() throws IOException {
283        if (closed) {
284            throw new IllegalStateException("JarFile has been closed");
285        }
286        if (manifest != null) {
287            return manifest;
288        }
289        try {
290            InputStream is = super.getInputStream(manifestEntry);
291            if (verifier != null) {
292                verifier.addMetaEntry(manifestEntry.getName(), Streams.readFully(is));
293                is = super.getInputStream(manifestEntry);
294            }
295            try {
296                manifest = new Manifest(is, verifier != null);
297            } finally {
298                is.close();
299            }
300            manifestEntry = null;  // Can discard the entry now.
301        } catch (NullPointerException e) {
302            manifestEntry = null;
303        }
304        return manifest;
305    }
306
307    /**
308     * Called by the JarFile constructors, this method reads the contents of the
309     * file's META-INF/ directory and picks out the MANIFEST.MF file and
310     * verifier signature files if they exist. Any signature files found are
311     * registered with the verifier.
312     *
313     * @throws IOException
314     *             if there is a problem reading the jar file entries.
315     */
316    private void readMetaEntries() throws IOException {
317        // Get all meta directory entries
318        ZipEntry[] metaEntries = getMetaEntriesImpl();
319        if (metaEntries == null) {
320            verifier = null;
321            return;
322        }
323
324        boolean signed = false;
325
326        for (ZipEntry entry : metaEntries) {
327            String entryName = entry.getName();
328            // Is this the entry for META-INF/MANIFEST.MF ?
329            if (manifestEntry == null && entryName.equalsIgnoreCase(MANIFEST_NAME)) {
330                manifestEntry = entry;
331                // If there is no verifier then we don't need to look any further.
332                if (verifier == null) {
333                    break;
334                }
335            } else {
336                // Is this an entry that the verifier needs?
337                if (verifier != null
338                        && (endsWithIgnoreCase(entryName, ".SF")
339                                || endsWithIgnoreCase(entryName, ".DSA")
340                                || endsWithIgnoreCase(entryName, ".RSA"))) {
341                    signed = true;
342                    InputStream is = super.getInputStream(entry);
343                    verifier.addMetaEntry(entryName, Streams.readFully(is));
344                }
345            }
346        }
347
348        // If there were no signature files, then no verifier work to do.
349        if (!signed) {
350            verifier = null;
351        }
352    }
353
354    private static boolean endsWithIgnoreCase(String s, String suffix) {
355        return s.regionMatches(true, s.length() - suffix.length(), suffix, 0, suffix.length());
356    }
357
358    /**
359     * Return an {@code InputStream} for reading the decompressed contents of
360     * ZIP entry.
361     *
362     * @param ze
363     *            the ZIP entry to be read.
364     * @return the input stream to read from.
365     * @throws IOException
366     *             if an error occurred while creating the input stream.
367     */
368    @Override
369    public InputStream getInputStream(ZipEntry ze) throws IOException {
370        if (manifestEntry != null) {
371            getManifest();
372        }
373        if (verifier != null) {
374            verifier.setManifest(getManifest());
375            if (manifest != null) {
376                verifier.mainAttributesEnd = manifest.getMainAttributesEnd();
377            }
378            if (verifier.readCertificates()) {
379                verifier.removeMetaEntries();
380                if (manifest != null) {
381                    manifest.removeChunks();
382                }
383                if (!verifier.isSignedJar()) {
384                    verifier = null;
385                }
386            }
387        }
388        InputStream in = super.getInputStream(ze);
389        if (in == null) {
390            return null;
391        }
392        if (verifier == null || ze.getSize() == -1) {
393            return in;
394        }
395        JarVerifier.VerifierEntry entry = verifier.initEntry(ze.getName());
396        if (entry == null) {
397            return in;
398        }
399        return new JarFileInputStream(in, ze, entry);
400    }
401
402    /**
403     * Return the {@code JarEntry} specified by name or {@code null} if no such
404     * entry exists.
405     *
406     * @param name
407     *            the name of the entry in the JAR file.
408     * @return the ZIP entry extracted.
409     */
410    @Override
411    public ZipEntry getEntry(String name) {
412        ZipEntry ze = super.getEntry(name);
413        if (ze == null) {
414            return ze;
415        }
416        JarEntry je = new JarEntry(ze);
417        je.parentJar = this;
418        return je;
419    }
420
421    /**
422     * Returns all the ZipEntry's that relate to files in the
423     * JAR's META-INF directory.
424     *
425     * @return the list of ZipEntry's or {@code null} if there are none.
426     */
427    private ZipEntry[] getMetaEntriesImpl() {
428        List<ZipEntry> list = new ArrayList<ZipEntry>(8);
429        Enumeration<? extends ZipEntry> allEntries = entries();
430        while (allEntries.hasMoreElements()) {
431            ZipEntry ze = allEntries.nextElement();
432            if (ze.getName().startsWith(META_DIR)
433                    && ze.getName().length() > META_DIR.length()) {
434                list.add(ze);
435            }
436        }
437        if (list.size() == 0) {
438            return null;
439        }
440        ZipEntry[] result = new ZipEntry[list.size()];
441        list.toArray(result);
442        return result;
443    }
444
445    /**
446     * Closes this {@code JarFile}.
447     *
448     * @throws IOException
449     *             if an error occurs.
450     */
451    @Override
452    public void close() throws IOException {
453        super.close();
454        closed = true;
455    }
456}
457