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