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