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