GZIPInputStream.java revision cc2ba8a2b5b33eaebbe03adceea68abe8576e354
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
26package java.util.zip;
27
28import java.io.SequenceInputStream;
29import java.io.ByteArrayInputStream;
30import java.io.FilterInputStream;
31import java.io.InputStream;
32import java.io.IOException;
33import 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 */
43public
44class 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