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