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