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.ByteArrayOutputStream;
21import java.io.IOException;
22import java.io.InputStream;
23import java.io.OutputStream;
24import java.util.zip.ZipEntry;
25import java.util.zip.ZipInputStream;
26
27import org.apache.harmony.archive.util.Util;
28
29/**
30 * The input stream from which the JAR file to be read may be fetched. It is
31 * used like the {@code ZipInputStream}.
32 *
33 * @see ZipInputStream
34 * @since Android 1.0
35 */
36public class JarInputStream extends ZipInputStream {
37
38    private Manifest manifest;
39
40    private boolean eos = false;
41
42    private JarEntry mEntry;
43
44    private JarEntry jarEntry;
45
46    private boolean isMeta;
47
48    private JarVerifier verifier;
49
50    private OutputStream verStream;
51
52    /**
53     * Constructs a new {@code JarInputStream} from an input stream.
54     *
55     * @param stream
56     *            the input stream containing the JAR file.
57     * @param verify
58     *            if the file should be verified with a {@code JarVerifier}.
59     * @throws IOException
60     *             If an error occurs reading entries from the input stream.
61     * @see ZipInputStream#ZipInputStream(InputStream)
62     * @since Android 1.0
63     */
64    public JarInputStream(InputStream stream, boolean verify)
65            throws IOException {
66        super(stream);
67        if (verify) {
68            verifier = new JarVerifier("JarInputStream"); //$NON-NLS-1$
69        }
70        if ((mEntry = getNextJarEntry()) == null) {
71            return;
72        }
73        String name = Util.toASCIIUpperCase(mEntry.getName());
74        if (name.equals(JarFile.META_DIR)) {
75            mEntry = null; // modifies behavior of getNextJarEntry()
76            closeEntry();
77            mEntry = getNextJarEntry();
78            name = mEntry.getName().toUpperCase();
79        }
80        if (name.equals(JarFile.MANIFEST_NAME)) {
81            mEntry = null;
82            manifest = new Manifest(this, verify);
83            closeEntry();
84            if (verify) {
85                verifier.setManifest(manifest);
86                if (manifest != null) {
87                    verifier.mainAttributesChunk = manifest
88                            .getMainAttributesChunk();
89                }
90            }
91
92        } else {
93            Attributes temp = new Attributes(3);
94            temp.map.put("hidden", null); //$NON-NLS-1$
95            mEntry.setAttributes(temp);
96            /*
97             * if not from the first entry, we will not get enough
98             * information,so no verify will be taken out.
99             */
100            verifier = null;
101        }
102    }
103
104    /**
105     * Constructs a new {@code JarInputStream} from an input stream.
106     *
107     * @param stream
108     *            the input stream containing the JAR file.
109     * @throws IOException
110     *             If an error occurs reading entries from the input stream.
111     * @see ZipInputStream#ZipInputStream(InputStream)
112     * @since Android 1.0
113     */
114    public JarInputStream(InputStream stream) throws IOException {
115        this(stream, true);
116    }
117
118    /**
119     * Returns the {@code Manifest} object associated with this {@code
120     * JarInputStream} or {@code null} if no manifest entry exists.
121     *
122     * @return the MANIFEST specifying the contents of the JAR file.
123     * @since Android 1.0
124     */
125    public Manifest getManifest() {
126        return manifest;
127    }
128
129    /**
130     * Returns the next {@code JarEntry} contained in this stream or {@code
131     * null} if no more entries are present.
132     *
133     * @return the next JAR entry.
134     * @throws IOException
135     *             if an error occurs while reading the entry.
136     * @since Android 1.0
137     */
138    public JarEntry getNextJarEntry() throws IOException {
139        return (JarEntry) getNextEntry();
140    }
141
142    /**
143     * Reads up to {@code length} of decompressed data and stores it in
144     * {@code buffer} starting at {@code offset}.
145     *
146     * @param buffer
147     *            Buffer to store into
148     * @param offset
149     *            offset in buffer to store at
150     * @param length
151     *            number of bytes to store
152     * @return Number of uncompressed bytes read
153     * @throws IOException
154     *             if an IOException occurs.
155     * @since Android 1.0
156     */
157    @Override
158    public int read(byte[] buffer, int offset, int length) throws IOException {
159        if (mEntry != null) {
160            return -1;
161        }
162        int r = super.read(buffer, offset, length);
163        if (verStream != null && !eos) {
164            if (r == -1) {
165                eos = true;
166                if (verifier != null) {
167                    if (isMeta) {
168                        verifier.addMetaEntry(jarEntry.getName(),
169                                ((ByteArrayOutputStream) verStream)
170                                        .toByteArray());
171                        try {
172                            verifier.readCertificates();
173                        } catch (SecurityException e) {
174                            verifier = null;
175                            throw e;
176                        }
177                    } else {
178                        verifier.verifySignatures(
179                                (JarVerifier.VerifierEntry) verStream,
180                                jarEntry);
181                    }
182                }
183            } else {
184                verStream.write(buffer, offset, r);
185            }
186        }
187        return r;
188    }
189
190    /**
191     * Returns the next {@code ZipEntry} contained in this stream or {@code
192     * null} if no more entries are present.
193     *
194     * @return the next extracted ZIP entry.
195     * @throws IOException
196     *             if an error occurs while reading the entry.
197     * @since Android 1.0
198     */
199    @Override
200    public ZipEntry getNextEntry() throws IOException {
201        if (mEntry != null) {
202            jarEntry = mEntry;
203            mEntry = null;
204            jarEntry.setAttributes(null);
205        } else {
206            jarEntry = (JarEntry) super.getNextEntry();
207            if (jarEntry == null) {
208                return null;
209            }
210            if (verifier != null) {
211                isMeta = Util.toASCIIUpperCase(jarEntry.getName()).startsWith(
212                        JarFile.META_DIR);
213                if (isMeta) {
214                    verStream = new ByteArrayOutputStream();
215                } else {
216                    verStream = verifier.initEntry(jarEntry.getName());
217                }
218            }
219        }
220        eos = false;
221        return jarEntry;
222    }
223
224    @Override
225    protected ZipEntry createZipEntry(String name) {
226        JarEntry entry = new JarEntry(name);
227        if (manifest != null) {
228            entry.setAttributes(manifest.getAttributes(name));
229        }
230        return entry;
231    }
232}
233