1/*
2 * Copyright (C) 2013 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17
18package java.util.jar;
19
20import dalvik.system.CloseGuard;
21import java.io.IOException;
22import java.io.InputStream;
23import java.io.RandomAccessFile;
24import java.security.cert.Certificate;
25import java.util.HashMap;
26import java.util.Iterator;
27import java.util.zip.Inflater;
28import java.util.zip.ZipEntry;
29import java.util.zip.ZipFile;
30import libcore.io.IoUtils;
31import libcore.io.Streams;
32
33/**
34 * A subset of the JarFile API implemented as a thin wrapper over
35 * system/core/libziparchive.
36 *
37 * @hide for internal use only. Not API compatible (or as forgiving) as
38 *        {@link java.util.jar.JarFile}
39 */
40public final class StrictJarFile {
41
42    private final long nativeHandle;
43
44    // NOTE: It's possible to share a file descriptor with the native
45    // code, at the cost of some additional complexity.
46    private final RandomAccessFile raf;
47
48    private final Manifest manifest;
49    private final JarVerifier verifier;
50
51    private final boolean isSigned;
52
53    private final CloseGuard guard = CloseGuard.get();
54    private boolean closed;
55
56    public StrictJarFile(String fileName) throws IOException {
57        this.nativeHandle = nativeOpenJarFile(fileName);
58        this.raf = new RandomAccessFile(fileName, "r");
59
60        try {
61            // Read the MANIFEST and signature files up front and try to
62            // parse them. We never want to accept a JAR File with broken signatures
63            // or manifests, so it's best to throw as early as possible.
64            HashMap<String, byte[]> metaEntries = getMetaEntries();
65            this.manifest = new Manifest(metaEntries.get(JarFile.MANIFEST_NAME), true);
66            this.verifier = new JarVerifier(fileName, manifest, metaEntries);
67
68            isSigned = verifier.readCertificates() && verifier.isSignedJar();
69        } catch (IOException ioe) {
70            nativeClose(this.nativeHandle);
71            throw ioe;
72        }
73
74        guard.open("close");
75    }
76
77    public Manifest getManifest() {
78        return manifest;
79    }
80
81    public Iterator<ZipEntry> iterator() throws IOException {
82        return new EntryIterator(nativeHandle, "");
83    }
84
85    public ZipEntry findEntry(String name) {
86        return nativeFindEntry(nativeHandle, name);
87    }
88
89    /**
90     * Return all certificate chains for a given {@link ZipEntry} belonging to this jar.
91     * This method MUST be called only after fully exhausting the InputStream belonging
92     * to this entry.
93     *
94     * Returns {@code null} if this jar file isn't signed or if this method is
95     * called before the stream is processed.
96     */
97    public Certificate[][] getCertificateChains(ZipEntry ze) {
98        if (isSigned) {
99            return verifier.getCertificateChains(ze.getName());
100        }
101
102        return null;
103    }
104
105    /**
106     * Return all certificates for a given {@link ZipEntry} belonging to this jar.
107     * This method MUST be called only after fully exhausting the InputStream belonging
108     * to this entry.
109     *
110     * Returns {@code null} if this jar file isn't signed or if this method is
111     * called before the stream is processed.
112     *
113     * @deprecated Switch callers to use getCertificateChains instead
114     */
115    @Deprecated
116    public Certificate[] getCertificates(ZipEntry ze) {
117        if (isSigned) {
118            Certificate[][] certChains = verifier.getCertificateChains(ze.getName());
119
120            // Measure number of certs.
121            int count = 0;
122            for (Certificate[] chain : certChains) {
123                count += chain.length;
124            }
125
126            // Create new array and copy all the certs into it.
127            Certificate[] certs = new Certificate[count];
128            int i = 0;
129            for (Certificate[] chain : certChains) {
130                System.arraycopy(chain, 0, certs, i, chain.length);
131                i += chain.length;
132            }
133
134            return certs;
135        }
136
137        return null;
138    }
139
140    public InputStream getInputStream(ZipEntry ze) {
141        final InputStream is = getZipInputStream(ze);
142
143        if (isSigned) {
144            JarVerifier.VerifierEntry entry = verifier.initEntry(ze.getName());
145            if (entry == null) {
146                return is;
147            }
148
149            return new JarFile.JarFileInputStream(is, ze.getSize(), entry);
150        }
151
152        return is;
153    }
154
155    public void close() throws IOException {
156        if (!closed) {
157            guard.close();
158
159            nativeClose(nativeHandle);
160            IoUtils.closeQuietly(raf);
161            closed = true;
162        }
163    }
164
165    private InputStream getZipInputStream(ZipEntry ze) {
166        if (ze.getMethod() == ZipEntry.STORED) {
167            return new ZipFile.RAFStream(raf, ze.getDataOffset(),
168                    ze.getDataOffset() + ze.getSize());
169        } else {
170            final ZipFile.RAFStream wrapped = new ZipFile.RAFStream(
171                    raf, ze.getDataOffset(), ze.getDataOffset() + ze.getCompressedSize());
172
173            int bufSize = Math.max(1024, (int) Math.min(ze.getSize(), 65535L));
174            return new ZipFile.ZipInflaterInputStream(wrapped, new Inflater(true), bufSize, ze);
175        }
176    }
177
178    static final class EntryIterator implements Iterator<ZipEntry> {
179        private final long iterationHandle;
180        private ZipEntry nextEntry;
181
182        EntryIterator(long nativeHandle, String prefix) throws IOException {
183            iterationHandle = nativeStartIteration(nativeHandle, prefix);
184        }
185
186        public ZipEntry next() {
187            if (nextEntry != null) {
188                final ZipEntry ze = nextEntry;
189                nextEntry = null;
190                return ze;
191            }
192
193            return nativeNextEntry(iterationHandle);
194        }
195
196        public boolean hasNext() {
197            if (nextEntry != null) {
198                return true;
199            }
200
201            final ZipEntry ze = nativeNextEntry(iterationHandle);
202            if (ze == null) {
203                return false;
204            }
205
206            nextEntry = ze;
207            return true;
208        }
209
210        public void remove() {
211            throw new UnsupportedOperationException();
212        }
213    }
214
215    private HashMap<String, byte[]> getMetaEntries() throws IOException {
216        HashMap<String, byte[]> metaEntries = new HashMap<String, byte[]>();
217
218        Iterator<ZipEntry> entryIterator = new EntryIterator(nativeHandle, "META-INF/");
219        while (entryIterator.hasNext()) {
220            final ZipEntry entry = entryIterator.next();
221            metaEntries.put(entry.getName(), Streams.readFully(getInputStream(entry)));
222        }
223
224        return metaEntries;
225    }
226
227    private static native long nativeOpenJarFile(String fileName) throws IOException;
228    private static native long nativeStartIteration(long nativeHandle, String prefix);
229    private static native ZipEntry nativeNextEntry(long iterationHandle);
230    private static native ZipEntry nativeFindEntry(long nativeHandle, String entryName);
231    private static native void nativeClose(long nativeHandle);
232}
233