JarInputStream.java revision 51b1b6997fd3f980076b8081f7f1165ccc2a4008
1/*
2 * Copyright (c) 1997, 2011, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package java.util.jar;
27
28import java.util.zip.*;
29import java.io.*;
30import sun.security.util.ManifestEntryVerifier;
31import sun.misc.JarIndex;
32
33/**
34 * The <code>JarInputStream</code> class is used to read the contents of
35 * a JAR file from any input stream. It extends the class
36 * <code>java.util.zip.ZipInputStream</code> with support for reading
37 * an optional <code>Manifest</code> entry. The <code>Manifest</code>
38 * can be used to store meta-information about the JAR file and its entries.
39 *
40 * @author  David Connelly
41 * @see     Manifest
42 * @see     java.util.zip.ZipInputStream
43 * @since   1.2
44 */
45public
46class JarInputStream extends ZipInputStream {
47    private Manifest man;
48    private JarEntry first;
49    private JarVerifier jv;
50    private ManifestEntryVerifier mev;
51    private final boolean doVerify;
52    private boolean tryManifest;
53
54    /**
55     * Creates a new <code>JarInputStream</code> and reads the optional
56     * manifest. If a manifest is present, also attempts to verify
57     * the signatures if the JarInputStream is signed.
58     * @param in the actual input stream
59     * @exception IOException if an I/O error has occurred
60     */
61    public JarInputStream(InputStream in) throws IOException {
62        this(in, true);
63    }
64
65    /**
66     * Creates a new <code>JarInputStream</code> and reads the optional
67     * manifest. If a manifest is present and verify is true, also attempts
68     * to verify the signatures if the JarInputStream is signed.
69     *
70     * @param in the actual input stream
71     * @param verify whether or not to verify the JarInputStream if
72     * it is signed.
73     * @exception IOException if an I/O error has occurred
74     */
75    public JarInputStream(InputStream in, boolean verify) throws IOException {
76        super(in);
77        this.doVerify = verify;
78
79        // This implementation assumes the META-INF/MANIFEST.MF entry
80        // should be either the first or the second entry (when preceded
81        // by the dir META-INF/). It skips the META-INF/ and then
82        // "consumes" the MANIFEST.MF to initialize the Manifest object.
83        JarEntry e = (JarEntry)super.getNextEntry();
84        if (e != null && e.getName().equalsIgnoreCase("META-INF/"))
85            e = (JarEntry)super.getNextEntry();
86        first = checkManifest(e);
87    }
88
89    private JarEntry checkManifest(JarEntry e)
90        throws IOException
91    {
92        if (e != null && JarFile.MANIFEST_NAME.equalsIgnoreCase(e.getName())) {
93            man = new Manifest();
94            byte bytes[] = getBytes(new BufferedInputStream(this));
95            man.read(new ByteArrayInputStream(bytes));
96            closeEntry();
97            if (doVerify) {
98                jv = new JarVerifier(bytes);
99                mev = new ManifestEntryVerifier(man);
100            }
101            return (JarEntry)super.getNextEntry();
102        }
103        return e;
104    }
105
106    private byte[] getBytes(InputStream is)
107        throws IOException
108    {
109        byte[] buffer = new byte[8192];
110        ByteArrayOutputStream baos = new ByteArrayOutputStream(2048);
111        int n;
112        while ((n = is.read(buffer, 0, buffer.length)) != -1) {
113            baos.write(buffer, 0, n);
114        }
115        return baos.toByteArray();
116    }
117
118    /**
119     * Returns the <code>Manifest</code> for this JAR file, or
120     * <code>null</code> if none.
121     *
122     * @return the <code>Manifest</code> for this JAR file, or
123     *         <code>null</code> if none.
124     */
125    public Manifest getManifest() {
126        return man;
127    }
128
129    /**
130     * Reads the next ZIP file entry and positions the stream at the
131     * beginning of the entry data. If verification has been enabled,
132     * any invalid signature detected while positioning the stream for
133     * the next entry will result in an exception.
134     * @exception ZipException if a ZIP file error has occurred
135     * @exception IOException if an I/O error has occurred
136     * @exception SecurityException if any of the jar file entries
137     *         are incorrectly signed.
138     */
139    public ZipEntry getNextEntry() throws IOException {
140        JarEntry e;
141        if (first == null) {
142            e = (JarEntry)super.getNextEntry();
143            if (tryManifest) {
144                e = checkManifest(e);
145                tryManifest = false;
146            }
147        } else {
148            e = first;
149            if (first.getName().equalsIgnoreCase(JarIndex.INDEX_NAME))
150                tryManifest = true;
151            first = null;
152        }
153        if (jv != null && e != null) {
154            // At this point, we might have parsed all the meta-inf
155            // entries and have nothing to verify. If we have
156            // nothing to verify, get rid of the JarVerifier object.
157            if (jv.nothingToVerify() == true) {
158                jv = null;
159                mev = null;
160            } else {
161                jv.beginEntry(e, mev);
162            }
163        }
164        return e;
165    }
166
167    /**
168     * Reads the next JAR file entry and positions the stream at the
169     * beginning of the entry data. If verification has been enabled,
170     * any invalid signature detected while positioning the stream for
171     * the next entry will result in an exception.
172     * @return the next JAR file entry, or null if there are no more entries
173     * @exception ZipException if a ZIP file error has occurred
174     * @exception IOException if an I/O error has occurred
175     * @exception SecurityException if any of the jar file entries
176     *         are incorrectly signed.
177     */
178    public JarEntry getNextJarEntry() throws IOException {
179        return (JarEntry)getNextEntry();
180    }
181
182    /**
183     * Reads from the current JAR file entry into an array of bytes.
184     * If <code>len</code> is not zero, the method
185     * blocks until some input is available; otherwise, no
186     * bytes are read and <code>0</code> is returned.
187     * If verification has been enabled, any invalid signature
188     * on the current entry will be reported at some point before the
189     * end of the entry is reached.
190     * @param b the buffer into which the data is read
191     * @param off the start offset in the destination array <code>b</code>
192     * @param len the maximum number of bytes to read
193     * @return the actual number of bytes read, or -1 if the end of the
194     *         entry is reached
195     * @exception  NullPointerException If <code>b</code> is <code>null</code>.
196     * @exception  IndexOutOfBoundsException If <code>off</code> is negative,
197     * <code>len</code> is negative, or <code>len</code> is greater than
198     * <code>b.length - off</code>
199     * @exception ZipException if a ZIP file error has occurred
200     * @exception IOException if an I/O error has occurred
201     * @exception SecurityException if any of the jar file entries
202     *         are incorrectly signed.
203     */
204    public int read(byte[] b, int off, int len) throws IOException {
205        int n;
206        if (first == null) {
207            n = super.read(b, off, len);
208        } else {
209            n = -1;
210        }
211        if (jv != null) {
212            jv.update(n, b, off, len, mev);
213        }
214        return n;
215    }
216
217    /**
218     * Creates a new <code>JarEntry</code> (<code>ZipEntry</code>) for the
219     * specified JAR file entry name. The manifest attributes of
220     * the specified JAR file entry name will be copied to the new
221     * <CODE>JarEntry</CODE>.
222     *
223     * @param name the name of the JAR/ZIP file entry
224     * @return the <code>JarEntry</code> object just created
225     */
226    protected ZipEntry createZipEntry(String name) {
227        JarEntry e = new JarEntry(name);
228        if (man != null) {
229            e.attr = man.getAttributes(name);
230        }
231        return e;
232    }
233}
234