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