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