001/* GZIPInputStream.java - Input filter for reading gzip file
002   Copyright (C) 1999, 2000, 2001, 2002, 2004 Free Software Foundation, Inc.
003
004This file is part of GNU Classpath.
005
006GNU Classpath is free software; you can redistribute it and/or modify
007it under the terms of the GNU General Public License as published by
008the Free Software Foundation; either version 2, or (at your option)
009any later version.
010
011GNU Classpath is distributed in the hope that it will be useful, but
012WITHOUT ANY WARRANTY; without even the implied warranty of
013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014General Public License for more details.
015
016You should have received a copy of the GNU General Public License
017along with GNU Classpath; see the file COPYING.  If not, write to the
018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
01902110-1301 USA.
020
021Linking this library statically or dynamically with other modules is
022making a combined work based on this library.  Thus, the terms and
023conditions of the GNU General Public License cover the whole
024combination.
025
026As a special exception, the copyright holders of this library give you
027permission to link this library with independent modules to produce an
028executable, regardless of the license terms of these independent
029modules, and to copy and distribute the resulting executable under
030terms of your choice, provided that you also meet, for each linked
031independent module, the terms and conditions of the license of that
032module.  An independent module is a module which is not derived from
033or based on this library.  If you modify this library, you may extend
034this exception to your version of the library, but you are not
035obligated to do so.  If you do not wish to do so, delete this
036exception statement from your version. */
037
038
039package java.util.zip;
040
041import java.io.EOFException;
042import java.io.IOException;
043import java.io.InputStream;
044
045/**
046 * This filter stream is used to decompress a "GZIP" format stream.
047 * The "GZIP" format is described in RFC 1952.
048 *
049 * @author John Leuner
050 * @author Tom Tromey
051 * @since JDK 1.1
052 */
053public class GZIPInputStream
054  extends InflaterInputStream
055{
056  /**
057   * The magic number found at the start of a GZIP stream.
058   */
059  public static final int GZIP_MAGIC = 0x8b1f;
060
061  /**
062   * The mask for bit 0 of the flag byte.
063   */
064  static final int FTEXT = 0x1;
065
066  /**
067   * The mask for bit 1 of the flag byte.
068   */
069  static final int FHCRC = 0x2;
070
071  /**
072   * The mask for bit 2 of the flag byte.
073   */
074  static final int FEXTRA = 0x4;
075
076  /**
077   * The mask for bit 3 of the flag byte.
078   */
079  static final int FNAME = 0x8;
080
081  /**
082   * The mask for bit 4 of the flag byte.
083   */
084  static final int FCOMMENT = 0x10;
085
086  /**
087   * The CRC-32 checksum value for uncompressed data.
088   */
089  protected CRC32 crc;
090
091  /**
092   * Indicates whether or not the end of the stream has been reached.
093   */
094  protected boolean eos;
095
096  /**
097   * Indicates whether or not the GZIP header has been read in.
098   */
099  private boolean readGZIPHeader;
100
101  /**
102   * Creates a GZIPInputStream with the default buffer size.
103   *
104   * @param in The stream to read compressed data from
105   *           (in GZIP format).
106   *
107   * @throws IOException if an error occurs during an I/O operation.
108   */
109  public GZIPInputStream(InputStream in)
110    throws IOException
111  {
112    this(in, 4096);
113  }
114
115  /**
116   * Creates a GZIPInputStream with the specified buffer size.
117   *
118   * @param in The stream to read compressed data from
119   *           (in GZIP format).
120   * @param size The size of the buffer to use.
121   *
122   * @throws IOException if an error occurs during an I/O operation.
123   * @throws IllegalArgumentException if <code>size</code>
124   * is less than or equal to 0.
125   */
126  public GZIPInputStream(InputStream in, int size)
127    throws IOException
128  {
129    super(in, new Inflater(true), size);
130    crc = new CRC32();
131    readHeader();
132  }
133
134  /**
135   * Closes the input stream.
136   *
137   * @throws IOException if an error occurs during an I/O operation.
138   */
139  public void close()
140    throws IOException
141  {
142    // Nothing to do here.
143    super.close();
144  }
145
146  /**
147   * Reads in GZIP-compressed data and stores it in uncompressed form
148   * into an array of bytes.  The method will block until either
149   * enough input data becomes available or the compressed stream
150   * reaches its end.
151   *
152   * @param buf the buffer into which the uncompressed data will
153   *            be stored.
154   * @param offset the offset indicating where in <code>buf</code>
155   *               the uncompressed data should be placed.
156   * @param len the number of uncompressed bytes to be read.
157   */
158  public int read(byte[] buf, int offset, int len) throws IOException
159  {
160    // We first have to slurp in the GZIP header, then we feed all the
161    // rest of the data to the superclass.
162    //
163    // As we do that we continually update the CRC32. Once the data is
164    // finished, we check the CRC32.
165    //
166    // This means we don't need our own buffer, as everything is done
167    // in the superclass.
168    if (!readGZIPHeader)
169      readHeader();
170
171    if (eos)
172      return -1;
173
174    //  System.err.println("GZIPIS.read(byte[], off, len ... " + offset + " and len " + len);
175
176    /* We don't have to read the header,
177     * so we just grab data from the superclass.
178     */
179    int numRead = super.read(buf, offset, len);
180    if (numRead > 0)
181      crc.update(buf, offset, numRead);
182
183    if (inf.finished())
184      readFooter();
185    return numRead;
186  }
187
188
189  /**
190   * Reads in the GZIP header.
191   */
192  private void readHeader() throws IOException
193  {
194    /* 1. Check the two magic bytes */
195    CRC32 headCRC = new CRC32();
196    int magic = in.read();
197    if (magic < 0)
198    {
199      eos = true;
200      return;
201    }
202    int magic2 = in.read();
203    if ((magic + (magic2 << 8)) != GZIP_MAGIC)
204      throw new IOException("Error in GZIP header, bad magic code");
205    headCRC.update(magic);
206    headCRC.update(magic2);
207
208    /* 2. Check the compression type (must be 8) */
209    int CM = in.read();
210    if (CM != Deflater.DEFLATED)
211      throw new IOException("Error in GZIP header, data not in deflate format");
212    headCRC.update(CM);
213
214    /* 3. Check the flags */
215    int flags = in.read();
216    if (flags < 0)
217      throw new EOFException("Early EOF in GZIP header");
218    headCRC.update(flags);
219
220    /*    This flag byte is divided into individual bits as follows:
221
222          bit 0   FTEXT
223          bit 1   FHCRC
224          bit 2   FEXTRA
225          bit 3   FNAME
226          bit 4   FCOMMENT
227          bit 5   reserved
228          bit 6   reserved
229          bit 7   reserved
230    */
231
232    /* 3.1 Check the reserved bits are zero */
233    if ((flags & 0xd0) != 0)
234      throw new IOException("Reserved flag bits in GZIP header != 0");
235
236    /* 4.-6. Skip the modification time, extra flags, and OS type */
237    for (int i=0; i< 6; i++)
238    {
239      int readByte = in.read();
240      if (readByte < 0)
241        throw new EOFException("Early EOF in GZIP header");
242      headCRC.update(readByte);
243    }
244
245    /* 7. Read extra field */
246    if ((flags & FEXTRA) != 0)
247    {
248      /* Skip subfield id */
249      for (int i=0; i< 2; i++)
250      {
251        int readByte = in.read();
252        if (readByte < 0)
253          throw new EOFException("Early EOF in GZIP header");
254        headCRC.update(readByte);
255      }
256      if (in.read() < 0 || in.read() < 0)
257        throw new EOFException("Early EOF in GZIP header");
258
259      int len1, len2, extraLen;
260      len1 = in.read();
261      len2 = in.read();
262      if ((len1 < 0) || (len2 < 0))
263        throw new EOFException("Early EOF in GZIP header");
264      headCRC.update(len1);
265      headCRC.update(len2);
266
267      extraLen = (len1 << 8) | len2;
268      for (int i = 0; i < extraLen;i++)
269      {
270        int readByte = in.read();
271        if (readByte < 0)
272          throw new EOFException("Early EOF in GZIP header");
273        headCRC.update(readByte);
274      }
275    }
276
277    /* 8. Read file name */
278    if ((flags & FNAME) != 0)
279    {
280      int readByte;
281      while ( (readByte = in.read()) > 0)
282        headCRC.update(readByte);
283      if (readByte < 0)
284        throw new EOFException("Early EOF in GZIP file name");
285      headCRC.update(readByte);
286    }
287
288    /* 9. Read comment */
289    if ((flags & FCOMMENT) != 0)
290    {
291      int readByte;
292      while ( (readByte = in.read()) > 0)
293        headCRC.update(readByte);
294
295      if (readByte < 0)
296        throw new EOFException("Early EOF in GZIP comment");
297      headCRC.update(readByte);
298    }
299
300    /* 10. Read header CRC */
301    if ((flags & FHCRC) != 0)
302    {
303      int tempByte;
304      int crcval = in.read();
305      if (crcval < 0)
306        throw new EOFException("Early EOF in GZIP header");
307
308      tempByte = in.read();
309      if (tempByte < 0)
310        throw new EOFException("Early EOF in GZIP header");
311
312      crcval = (crcval << 8) | tempByte;
313      if (crcval != ((int) headCRC.getValue() & 0xffff))
314        throw new IOException("Header CRC value mismatch");
315    }
316
317    readGZIPHeader = true;
318    //System.err.println("Read GZIP header");
319  }
320
321  private void readFooter() throws IOException
322  {
323    byte[] footer = new byte[8];
324    int avail = inf.getRemaining();
325    if (avail > 8)
326      avail = 8;
327    System.arraycopy(buf, len - inf.getRemaining(), footer, 0, avail);
328    int needed = 8 - avail;
329    while (needed > 0)
330    {
331      int count = in.read(footer, 8-needed, needed);
332      if (count <= 0)
333        throw new EOFException("Early EOF in GZIP footer");
334      needed -= count; //Jewel Jan 16
335    }
336
337    int crcval = (footer[0] & 0xff) | ((footer[1] & 0xff) << 8)
338      | ((footer[2] & 0xff) << 16) | (footer[3] << 24);
339    if (crcval != (int) crc.getValue())
340      throw new IOException("GZIP crc sum mismatch, theirs \""
341                            + Integer.toHexString(crcval)
342                            + "\" and ours \""
343                            + Integer.toHexString( (int) crc.getValue()));
344
345    int total = (footer[4] & 0xff) | ((footer[5] & 0xff) << 8)
346      | ((footer[6] & 0xff) << 16) | (footer[7] << 24);
347    if (total != inf.getTotalOut())
348      throw new IOException("Number of bytes mismatch");
349
350    /* FIXME" XXX Should we support multiple members.
351     * Difficult, since there may be some bytes still in buf
352     */
353    eos = true;
354  }
355}