1   /*
2    * %W% %E%
3    *
4    * Copyright (c) 2006, 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.SequenceInputStream;
11  import java.io.ByteArrayInputStream;
12  import java.io.InputStream;
13  import java.io.IOException;
14  import java.io.EOFException;
15  
16  /**
17   * This class implements a stream filter for reading compressed data in
18   * the GZIP file format.
19   *
20   * @see     InflaterInputStream
21   * @version     %I%, %G%
22   * @author  David Connelly
23   *
24   */
25  public
26  class GZIPInputStream extends InflaterInputStream {
27      /**
28       * CRC-32 for uncompressed data.
29       */
30      protected CRC32 crc = new CRC32();
31  
32      /**
33       * Indicates end of input stream.
34       */
35      protected boolean eos;
36  
37      private boolean closed = false;
38      
39      /**
40       * Check to make sure that this stream has not been closed
41       */
42      private void ensureOpen() throws IOException {
43      if (closed) {
44          throw new IOException("Stream closed");
45          }
46      }
47  
48      /**
49       * Creates a new input stream with the specified buffer size.
50       * @param in the input stream
51       * @param size the input buffer size
52       * @exception IOException if an I/O error has occurred
53       * @exception IllegalArgumentException if size is <= 0
54       */
55      public GZIPInputStream(InputStream in, int size) throws IOException {
56      super(in, new Inflater(true), size);
57          usesDefaultInflater = true;
58              readHeader(in);
59      }
60  
61      /**
62       * Creates a new input stream with a default buffer size.
63       * @param in the input stream
64       * @exception IOException if an I/O error has occurred
65       */
66      public GZIPInputStream(InputStream in) throws IOException {
67      this(in, 512);
68      }
69  
70      /**
71       * Reads uncompressed data into an array of bytes. If <code>len</code> is not
72       * zero, the method will block until some input can be decompressed; otherwise,
73       * no bytes are read and <code>0</code> is returned.
74       * @param buf the buffer into which the data is read
75       * @param off the start offset in the destination array <code>b</code>
76       * @param len the maximum number of bytes read
77       * @return  the actual number of bytes read, or -1 if the end of the
78       *      compressed input stream is reached
79       * @exception  NullPointerException If <code>buf</code> is <code>null</code>.
80       * @exception  IndexOutOfBoundsException If <code>off</code> is negative, 
81       * <code>len</code> is negative, or <code>len</code> is greater than 
82       * <code>buf.length - off</code>
83       * @exception IOException if an I/O error has occurred or the compressed
84       *                input data is corrupt
85       */
86      public int read(byte[] buf, int off, int len) throws IOException {
87          ensureOpen();
88          if (eos) {
89              return -1;
90          }
91  
92          int n = super.read(buf, off, len);
93          if (n == -1) {
94              if (readTrailer()){
95                  eos = true;
96              }
97              else{
98                  return this.read(buf, off, len);
99              }
100         } else {
101             crc.update(buf, off, n);
102         }
103         return n;
104     }
105 
106     /**
107      * Closes this input stream and releases any system resources associated
108      * with the stream.
109      * @exception IOException if an I/O error has occurred
110      */
111     public void close() throws IOException {
112         if (!closed) {
113             super.close();  
114             eos = true;
115             closed = true;
116         }
117     }
118 
119     /**
120      * GZIP header magic number.
121      */
122     public final static int GZIP_MAGIC = 0x8b1f;
123 
124     /*
125      * File header flags.
126      */
127     private final static int FTEXT  = 1;    // Extra text
128     private final static int FHCRC  = 2;    // Header CRC
129     private final static int FEXTRA = 4;    // Extra field
130     private final static int FNAME  = 8;    // File name
131     private final static int FCOMMENT   = 16;   // File comment
132 
133     /*
134      * Reads GZIP member header and returns the total byte number
135      * of this member header. 
136      */
137     private int readHeader(InputStream this_in) throws IOException {
138         CheckedInputStream in = new CheckedInputStream(this_in, crc);
139 
140         crc.reset();
141         // Check header magic
142         if (readUShort(in) != GZIP_MAGIC) {
143             throw new IOException("Not in GZIP format");
144         }
145         // Check compression method
146         if (readUByte(in) != 8) {
147             throw new IOException("Unsupported compression method");
148         }
149         // Read flags
150         int flg = readUByte(in);
151         // Skip MTIME, XFL, and OS fields
152         skipBytes(in, 6);
153         int n = 2 + 2 + 6;
154 
155         // Skip optional extra field
156         if ((flg & FEXTRA) == FEXTRA) {
157             skipBytes(in, readUShort(in));
158             int m = readUShort(in);
159             skipBytes(in, m);
160             n += m + 2;
161         }
162         // Skip optional file name
163         if ((flg & FNAME) == FNAME) {
164             do {
165                 n++;
166             } while (readUByte(in) != 0);
167         }
168         // Skip optional file comment
169         if ((flg & FCOMMENT) == FCOMMENT) {
170             do {
171                 n++;
172             } while (readUByte(in) != 0);
173         }
174         // Check optional header CRC
175         if ((flg & FHCRC) == FHCRC) {
176             int v = (int)crc.getValue() & 0xffff;
177             if (readUShort(in) != v) {
178                 throw new IOException("Corrupt GZIP header");
179             }
180             n += 2;
181         }
182 
183         crc.reset();
184         return n;
185     }
186 
187    /*
188     * Reads GZIP member trailer and returns true if the eos
189     * reached, false if there are more (concatenated gzip
190     * data set)
191     */ 
192     private boolean readTrailer() throws IOException {
193         InputStream in = this.in;
194         int n = inf.getRemaining();
195         if (n > 0) {
196             in = new SequenceInputStream(
197                         new ByteArrayInputStream(buf, len - n, n), in);
198         }
199         // Uses left-to-right evaluation order
200         if ((readUInt(in) != crc.getValue()) ||
201             // rfc1952; ISIZE is the input size modulo 2^32
202             (readUInt(in) != (inf.getBytesWritten() & 0xffffffffL)))
203             throw new IOException("Corrupt GZIP trailer");
204 
205         
206         // If there are more bytes available in "in" or
207         // the leftover in the "inf" is > 26 bytes:
208         // this.trailer(8) + next.header.min(10) + next.trailer(8)
209         // try concatenated case
210         if (this.in.available() > 0 || n > 26) {
211             int m = 8;                  // this.trailer
212             try {
213                 m += readHeader(in);    // next.header
214             } catch (IOException ze) {
215                 return true;  // ignore any malformed, do nothing
216             }
217 
218             inf.reset();
219             if (n > m)
220                 inf.setInput(buf, len - n + m, n - m);
221             return false;
222         }
223 
224         return true;
225     }
226 
227     /*
228      * Reads unsigned integer in Intel byte order.
229      */
230     private long readUInt(InputStream in) throws IOException {
231     long s = readUShort(in);
232     return ((long)readUShort(in) << 16) | s;
233     }
234 
235     /*
236      * Reads unsigned short in Intel byte order.
237      */
238     private int readUShort(InputStream in) throws IOException {
239     int b = readUByte(in);
240     return ((int)readUByte(in) << 8) | b;
241     }
242 
243     /*
244      * Reads unsigned byte.
245      */
246     private int readUByte(InputStream in) throws IOException {
247     int b = in.read();
248     if (b == -1) {
249         throw new EOFException();
250     }
251         if (b < -1 || b > 255) {
252             // Report on this.in, not argument in; see read{Header, Trailer}.
253             throw new IOException(this.in.getClass().getName()
254                 + ".read() returned value out of range -1..255: " + b);
255         }
256     return b;
257     }
258 
259 
260     private byte[] tmpbuf = new byte[128];
261 
262     /*
263      * Skips bytes of input data blocking until all bytes are skipped.
264      * Does not assume that the input stream is capable of seeking.
265      */
266     private void skipBytes(InputStream in, int n) throws IOException {
267     while (n > 0) {
268         int len = in.read(tmpbuf, 0, n < tmpbuf.length ? n : tmpbuf.length);
269         if (len == -1) {
270         throw new EOFException();
271         }
272         n -= len;
273     }
274     }
275 }
276