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