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