1   /*
2    * %W% %E%
3    *
4    * Copyright (c) 2009, Oracle and/or its affiliates. All rights reserved.
5    * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
6    */
7   
8   package java.util.zip;
9   
10  import java.io.InputStream;
11  import java.io.IOException;
12  import java.io.EOFException;
13  import java.io.UnsupportedEncodingException;
14  import java.io.PushbackInputStream;
15  import sun.security.action.GetPropertyAction;
16  import java.util.jar.JarInputStream;
17  
18  
19  /**
20   * This class implements an input stream filter for reading files in the
21   * ZIP file format. Includes support for both compressed and uncompressed
22   * entries.
23   *
24   * @author  David Connelly
25   * @version %I%, %G%
26   */
27  public
28  class ZipInputStream extends InflaterInputStream implements ZipConstants {
29      private ZipEntry entry;
30      private int flag;
31      private CRC32 crc = new CRC32();
32      private long remaining;
33      private byte[] tmpbuf = new byte[512];
34  
35      private static final int STORED = ZipEntry.STORED;
36      private static final int DEFLATED = ZipEntry.DEFLATED;
37  
38      private boolean closed = false;
39      // this flag is set to true after EOF has reached for
40      // one entry
41      private boolean entryEOF = false;
42      // Used to decide what encoding to use while reading zip entries 
43      private static final String fileEncoding = java.security.AccessController
44          .doPrivileged(new GetPropertyAction("sun.zip.encoding"));
45  
46  
47      /**
48       * Check to make sure that this stream has not been closed
49       */
50      private void ensureOpen() throws IOException {
51      if (closed) {
52          throw new IOException("Stream closed");
53          }
54      }
55  
56      /**
57       * Creates a new ZIP input stream.
58       * @param in the actual input stream
59       */
60      public ZipInputStream(InputStream in) {
61      super(new PushbackInputStream(in, 512), new Inflater(true), 512);
62          usesDefaultInflater = true;
63          if(in == null) {
64              throw new NullPointerException("in is null");
65          }
66      }
67  
68      /**
69       * Reads the next ZIP file entry and positions the stream at the
70       * beginning of the entry data.
71       * @return the next ZIP file entry, or null if there are no more entries
72       * @exception ZipException if a ZIP file error has occurred
73       * @exception IOException if an I/O error has occurred
74       */
75      public ZipEntry getNextEntry() throws IOException {
76          ensureOpen();
77      if (entry != null) {
78          closeEntry();
79      }
80      crc.reset();
81      inf.reset();
82      if ((entry = readLOC()) == null) {
83          return null;
84      }
85      if (entry.method == STORED) {
86          remaining = entry.size;
87      }
88          entryEOF = false;
89      return entry;
90      }
91  
92      /**
93       * Closes the current ZIP entry and positions the stream for reading the
94       * next entry.
95       * @exception ZipException if a ZIP file error has occurred
96       * @exception IOException if an I/O error has occurred
97       */
98      public void closeEntry() throws IOException {
99          ensureOpen();
100     while (read(tmpbuf, 0, tmpbuf.length) != -1) ;
101         entryEOF = true;
102     }
103 
104     /**
105      * Returns 0 after EOF has reached for the current entry data,
106      * otherwise always return 1.
107      * <p>
108      * Programs should not count on this method to return the actual number
109      * of bytes that could be read without blocking.
110      *
111      * @return     1 before EOF and 0 after EOF has reached for current entry.
112      * @exception  IOException  if an I/O error occurs.
113      *
114      */
115     public int available() throws IOException {
116         ensureOpen();
117         if (entryEOF) {
118             return 0;
119         } else {
120             return 1;
121         }
122     }
123 
124     /**
125      * Reads from the current ZIP entry into an array of bytes.
126      * If <code>len</code> is not zero, the method
127      * blocks until some input is available; otherwise, no
128      * bytes are read and <code>0</code> is returned.
129      * @param b the buffer into which the data is read
130      * @param off the start offset in the destination array <code>b</code>
131      * @param len the maximum number of bytes read
132      * @return the actual number of bytes read, or -1 if the end of the
133      *         entry is reached
134      * @exception  NullPointerException If <code>b</code> is <code>null</code>.
135      * @exception  IndexOutOfBoundsException If <code>off</code> is negative,
136      * <code>len</code> is negative, or <code>len</code> is greater than
137      * <code>b.length - off</code>
138      * @exception ZipException if a ZIP file error has occurred
139      * @exception IOException if an I/O error has occurred
140      */
141     public int read(byte[] b, int off, int len) throws IOException {
142         ensureOpen();
143         if (off < 0 || len < 0 || off > b.length - len) {
144         throw new IndexOutOfBoundsException();
145     } else if (len == 0) {
146         return 0;
147     }
148 
149     if (entry == null) {
150         return -1;
151     }
152     switch (entry.method) {
153     case DEFLATED:
154         len = super.read(b, off, len);
155         if (len == -1) {
156         readEnd(entry);
157                 entryEOF = true;
158         entry = null;
159         } else {
160         crc.update(b, off, len);
161         }
162         return len;
163     case STORED:
164         if (remaining <= 0) {
165                 entryEOF = true;
166         entry = null;
167         return -1;
168         }
169         if (len > remaining) {
170         len = (int)remaining;
171         }
172         len = in.read(b, off, len);
173         if (len == -1) {
174         throw new ZipException("unexpected EOF");
175         }
176         crc.update(b, off, len);
177         remaining -= len;
178         if (remaining == 0 && entry.crc != crc.getValue()) {
179         throw new ZipException(
180             "invalid entry CRC (expected 0x" + Long.toHexString(entry.crc) +
181             " but got 0x" + Long.toHexString(crc.getValue()) + ")");
182         }
183         return len;
184     default:
185         throw new ZipException("invalid compression method");
186     }
187     }
188 
189     /**
190      * Skips specified number of bytes in the current ZIP entry.
191      * @param n the number of bytes to skip
192      * @return the actual number of bytes skipped
193      * @exception ZipException if a ZIP file error has occurred
194      * @exception IOException if an I/O error has occurred
195      * @exception IllegalArgumentException if n < 0
196      */
197     public long skip(long n) throws IOException {
198         if (n < 0) {
199             throw new IllegalArgumentException("negative skip length");
200         }
201         ensureOpen();
202     int max = (int)Math.min(n, Integer.MAX_VALUE);
203     int total = 0;
204     while (total < max) {
205         int len = max - total;
206         if (len > tmpbuf.length) {
207         len = tmpbuf.length;
208         }
209         len = read(tmpbuf, 0, len);
210         if (len == -1) {
211                 entryEOF = true;
212         break;
213         }
214         total += len;
215     }
216     return total;
217     }
218 
219     /**
220      * Closes this input stream and releases any system resources associated
221      * with the stream.
222      * @exception IOException if an I/O error has occurred
223      */
224     public void close() throws IOException {
225         if (!closed) {
226         super.close();
227             closed = true;
228         }
229     }
230 
231     private byte[] b = new byte[256];
232 
233     /*
234      * Reads local file (LOC) header for next entry.
235      */
236     private ZipEntry readLOC() throws IOException {
237     try {
238         readFully(tmpbuf, 0, LOCHDR);
239     } catch (EOFException e) {
240         return null;
241     }
242     if (get32(tmpbuf, 0) != LOCSIG) {
243         return null;
244     }
245     // get the entry name and create the ZipEntry first
246     int len = get16(tmpbuf, LOCNAM);
247         int blen = b.length;
248         if (len > blen) {
249             do
250                 blen = blen * 2;
251             while (len > blen);
252             b = new byte[blen];
253         }
254     readFully(b, 0, len);
255     String name = getFileName(b, len);
256 
257         ZipEntry e = createZipEntry(name);
258     // now get the remaining fields for the entry
259     flag = get16(tmpbuf, LOCFLG);
260     if ((flag & 1) == 1) {
261         throw new ZipException("encrypted ZIP entry not supported");
262     }
263     e.method = get16(tmpbuf, LOCHOW);
264     e.time = get32(tmpbuf, LOCTIM);
265     if ((flag & 8) == 8) {
266         /* "Data Descriptor" present */
267         if (e.method != DEFLATED) {
268         throw new ZipException(
269             "only DEFLATED entries can have EXT descriptor");
270         }
271     } else {
272         e.crc = get32(tmpbuf, LOCCRC);
273         e.csize = get32(tmpbuf, LOCSIZ);
274         e.size = get32(tmpbuf, LOCLEN);
275     }
276     len = get16(tmpbuf, LOCEXT);
277     if (len > 0) {
278         byte[] bb = new byte[len];
279         readFully(bb, 0, len);
280         e.setExtra(bb);
281     }
282     return e;
283     }
284 
285     /*
286      * Fetches a UTF8-encoded String from the specified byte array.
287      */
288     private static String getUTF8String(byte[] b, int off, int len) {
289     // First, count the number of characters in the sequence
290     int count = 0;
291     int max = off + len;
292     int i = off;
293     while (i < max) {
294         int c = b[i++] & 0xff;
295         switch (c >> 4) {
296         case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:
297         // 0xxxxxxx
298         count++;
299         break;
300         case 12: case 13:
301         // 110xxxxx 10xxxxxx
302         if ((int)(b[i++] & 0xc0) != 0x80) {
303             throw new IllegalArgumentException();
304         }
305         count++;
306         break;
307         case 14:
308         // 1110xxxx 10xxxxxx 10xxxxxx
309         if (((int)(b[i++] & 0xc0) != 0x80) ||
310             ((int)(b[i++] & 0xc0) != 0x80)) {
311             throw new IllegalArgumentException();
312         }
313         count++;
314         break;
315         default:
316         // 10xxxxxx, 1111xxxx
317         throw new IllegalArgumentException();
318         }
319     }
320     if (i != max) {
321         throw new IllegalArgumentException();
322     }
323     // Now decode the characters...
324     char[] cs = new char[count];
325     i = 0;
326     while (off < max) {
327         int c = b[off++] & 0xff;
328         switch (c >> 4) {
329         case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:
330         // 0xxxxxxx
331         cs[i++] = (char)c;
332         break;
333         case 12: case 13:
334         // 110xxxxx 10xxxxxx
335         cs[i++] = (char)(((c & 0x1f) << 6) | (b[off++] & 0x3f));
336         break;
337         case 14:
338         // 1110xxxx 10xxxxxx 10xxxxxx
339         int t = (b[off++] & 0x3f) << 6;
340         cs[i++] = (char)(((c & 0x0f) << 12) | t | (b[off++] & 0x3f));
341         break;
342         default:
343         // 10xxxxxx, 1111xxxx
344         throw new IllegalArgumentException();
345         }
346     }
347     return new String(cs, 0, count);
348     }
349 
350     /**
351      * Creates a new <code>ZipEntry</code> object for the specified
352      * entry name.
353      *
354      * @param name the ZIP file entry name
355      * @return the ZipEntry just created
356      */
357     protected ZipEntry createZipEntry(String name) {
358     return new ZipEntry(name);
359     }
360 
361     /*
362      * Reads end of deflated entry as well as EXT descriptor if present.
363      */
364     private void readEnd(ZipEntry e) throws IOException {
365     int n = inf.getRemaining();
366     if (n > 0) {
367         ((PushbackInputStream)in).unread(buf, len - n, n);
368     }
369     if ((flag & 8) == 8) {
370         /* "Data Descriptor" present */
371         readFully(tmpbuf, 0, EXTHDR);
372         long sig = get32(tmpbuf, 0);
373             if (sig != EXTSIG) { // no EXTSIG present
374                 e.crc = sig;
375                 e.csize = get32(tmpbuf, EXTSIZ - EXTCRC);
376                 e.size = get32(tmpbuf, EXTLEN - EXTCRC);
377                 ((PushbackInputStream)in).unread(
378                                            tmpbuf, EXTHDR - EXTCRC - 1, EXTCRC);
379             } else {
380                 e.crc = get32(tmpbuf, EXTCRC);
381                 e.csize = get32(tmpbuf, EXTSIZ);
382                 e.size = get32(tmpbuf, EXTLEN);
383             }
384     }
385     if (e.size != inf.getBytesWritten()) {
386         throw new ZipException(
387         "invalid entry size (expected " + e.size +
388         " but got " + inf.getBytesWritten() + " bytes)");
389     }
390     if (e.csize != inf.getBytesRead()) {
391         throw new ZipException(
392         "invalid entry compressed size (expected " + e.csize +
393         " but got " + inf.getBytesRead() + " bytes)");
394     }
395     if (e.crc != crc.getValue()) {
396         throw new ZipException(
397         "invalid entry CRC (expected 0x" + Long.toHexString(e.crc) +
398         " but got 0x" + Long.toHexString(crc.getValue()) + ")");
399     }
400     }
401 
402     /*
403      * Reads bytes, blocking until all bytes are read.
404      */
405     private void readFully(byte[] b, int off, int len) throws IOException {
406     while (len > 0) {
407         int n = in.read(b, off, len);
408         if (n == -1) {
409         throw new EOFException();
410         }
411         off += n;
412         len -= n;
413     }
414     }
415 
416     /*
417      * Fetches unsigned 16-bit value from byte array at specified offset.
418      * The bytes are assumed to be in Intel (little-endian) byte order.
419      */
420     private static final int get16(byte b[], int off) {
421     return (b[off] & 0xff) | ((b[off+1] & 0xff) << 8);
422     }
423 
424     /*
425      * Fetches unsigned 32-bit value from byte array at specified offset.
426      * The bytes are assumed to be in Intel (little-endian) byte order.
427      */
428     private static final long get32(byte b[], int off) {
429     return get16(b, off) | ((long)get16(b, off+2) << 16);
430     }
431 
432     private String getFileName(byte[] b, int len) throws IOException {
433     String name;
434     try {
435         if (fileEncoding == null || fileEncoding.equals("") || this instanceof JarInputStream) {
436         name = getUTF8String(b, 0, len);
437         } else {
438         if (fileEncoding.equalsIgnoreCase("default")) {
439             // use platforms's default encoding
440             name = new String(b, 0, len);
441         } else {
442             try {
443                 name = new String(b, 0, len, fileEncoding);
444             } catch (UnsupportedEncodingException UEE) {
445             throw new ZipException("Unable to encode entry " +
446                 "name (sun.zip.encoding is " + fileEncoding + ")");
447             }
448         }
449         }
450     } catch (IllegalArgumentException e) {
451         // Alt encoding that can be used  if
452         // the initial decoding attempt fails.
453         String altEncoding = java.security.AccessController
454         .doPrivileged(new GetPropertyAction("sun.zip.altEncoding"));
455         if (altEncoding == null || altEncoding.equals("") ||
456                 this instanceof JarInputStream) {
457                 throw e;
458             }
459         if (altEncoding.equalsIgnoreCase("default")) {
460         // use platform's default encoding
461         name = new String(b, 0, len);
462         } else {
463         try {
464             // use the specified encoding
465             name = new String(b, 0, len, altEncoding);
466         } catch (UnsupportedEncodingException UEE) {
467             throw new ZipException("Unable to encode entry " +
468             "name (sun.zip.altEncoding is " + altEncoding + ")");
469         }
470         }
471         
472     }
473     return name;
474     } 
475 }
476