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