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 
26 package java.util.jar;
27 
28 import java.util.zip.*;
29 import java.io.*;
30 import sun.security.util.ManifestEntryVerifier;
31 import 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  */
45 public
46 class 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      */
JarInputStream(InputStream in)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      */
JarInputStream(InputStream in, boolean verify)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 
checkManifest(JarEntry e)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 
getBytes(InputStream is)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      */
getManifest()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      */
getNextEntry()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      */
getNextJarEntry()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      */
read(byte[] b, int off, int len)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      */
createZipEntry(String name)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