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