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