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 * @since 1.1
42 *
43 */
44 public
45 class GZIPInputStream extends InflaterInputStream {
46 /**
47 * CRC-32 for uncompressed data.
48 */
49 protected CRC32 crc = new CRC32();
50
51 /**
52 * Indicates end of input stream.
53 */
54 protected boolean eos;
55
56 private boolean closed = false;
57
58 /**
59 * Check to make sure that this stream has not been closed
60 */
61 private void ensureOpen() throws IOException {
62 if (closed) {
63 throw new IOException("Stream closed");
64 }
65 }
66
67 /**
68 * Creates a new input stream with the specified buffer size.
69 * @param in the input stream
70 * @param size the input buffer size
71 *
72 * @exception ZipException if a GZIP format error has occurred or the
73 * compression method used is unsupported
74 * @exception IOException if an I/O error has occurred
75 * @exception IllegalArgumentException if {@code size <= 0}
76 */
77 public GZIPInputStream(InputStream in, int size) throws IOException {
78 super(in, new Inflater(true), size);
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 static final int GZIP_MAGIC = 0x8b1f;
147
148 /*
149 * File header flags.
150 */
151 private static final int FTEXT = 1; // Extra text
152 private static final int FHCRC = 2; // Header CRC
153 private static final int FEXTRA = 4; // Extra field
154 private static final int FNAME = 8; // File name
155 private static final 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