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