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