1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.commons.compress.compressors.gzip;
20
21 import java.io.BufferedInputStream;
22 import java.io.ByteArrayOutputStream;
23 import java.io.DataInput;
24 import java.io.DataInputStream;
25 import java.io.EOFException;
26 import java.io.IOException;
27 import java.io.InputStream;
28 import java.util.zip.CRC32;
29 import java.util.zip.DataFormatException;
30 import java.util.zip.Deflater;
31 import java.util.zip.Inflater;
32
33 import org.apache.commons.compress.compressors.CompressorInputStream;
34 import org.apache.commons.compress.utils.ByteUtils;
35 import org.apache.commons.compress.utils.InputStreamStatistics;
36 import org.apache.commons.io.input.CountingInputStream;
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70 public class GzipCompressorInputStream extends CompressorInputStream implements InputStreamStatistics {
71
72
73
74 private static final int FHCRC = 0x02;
75 private static final int FEXTRA = 0x04;
76 private static final int FNAME = 0x08;
77 private static final int FCOMMENT = 0x10;
78 private static final int FRESERVED = 0xE0;
79
80
81
82
83
84
85
86
87
88
89 public static boolean matches(final byte[] signature, final int length) {
90 return length >= 2 && signature[0] == 31 && signature[1] == -117;
91 }
92
93 private static byte[] readToNull(final DataInput inData) throws IOException {
94 try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
95 int b;
96 while ((b = inData.readUnsignedByte()) != 0) {
97 bos.write(b);
98 }
99 return bos.toByteArray();
100 }
101 }
102
103 private final CountingInputStream countingStream;
104
105
106
107 private final InputStream in;
108
109
110 private final boolean decompressConcatenated;
111
112
113 private final byte[] buf = new byte[8192];
114
115
116 private int bufUsed;
117
118
119 private Inflater inf = new Inflater(true);
120
121
122 private final CRC32 crc = new CRC32();
123
124
125 private boolean endReached;
126
127
128 private final byte[] oneByte = new byte[1];
129
130 private final GzipParameters parameters = new GzipParameters();
131
132
133
134
135
136
137
138
139
140
141 public GzipCompressorInputStream(final InputStream inputStream) throws IOException {
142 this(inputStream, false);
143 }
144
145
146
147
148
149
150
151
152
153
154
155
156
157 public GzipCompressorInputStream(final InputStream inputStream, final boolean decompressConcatenated) throws IOException {
158 countingStream = new CountingInputStream(inputStream);
159
160
161 if (countingStream.markSupported()) {
162 in = countingStream;
163 } else {
164 in = new BufferedInputStream(countingStream);
165 }
166
167 this.decompressConcatenated = decompressConcatenated;
168 init(true);
169 }
170
171
172
173
174
175
176 @Override
177 public void close() throws IOException {
178 if (inf != null) {
179 inf.end();
180 inf = null;
181 }
182
183 if (this.in != System.in) {
184 this.in.close();
185 }
186 }
187
188
189
190
191 @Override
192 public long getCompressedCount() {
193 return countingStream.getByteCount();
194 }
195
196
197
198
199
200
201
202 public GzipParameters getMetaData() {
203 return parameters;
204 }
205
206 private boolean init(final boolean isFirstMember) throws IOException {
207 assert isFirstMember || decompressConcatenated;
208
209
210 final int magic0 = in.read();
211
212
213
214 if (magic0 == -1 && !isFirstMember) {
215 return false;
216 }
217
218 if (magic0 != 31 || in.read() != 139) {
219 throw new IOException(isFirstMember ? "Input is not in the .gz format" : "Garbage after a valid .gz stream");
220 }
221
222
223 final DataInput inData = new DataInputStream(in);
224 final int method = inData.readUnsignedByte();
225 if (method != Deflater.DEFLATED) {
226 throw new IOException("Unsupported compression method " + method + " in the .gz header");
227 }
228
229 final int flg = inData.readUnsignedByte();
230 if ((flg & FRESERVED) != 0) {
231 throw new IOException("Reserved flags are set in the .gz header");
232 }
233
234 parameters.setModificationTime(ByteUtils.fromLittleEndian(inData, 4) * 1000);
235 switch (inData.readUnsignedByte()) {
236 case 2:
237 parameters.setCompressionLevel(Deflater.BEST_COMPRESSION);
238 break;
239 case 4:
240 parameters.setCompressionLevel(Deflater.BEST_SPEED);
241 break;
242 default:
243
244 break;
245 }
246 parameters.setOperatingSystem(inData.readUnsignedByte());
247
248
249 if ((flg & FEXTRA) != 0) {
250 int xlen = inData.readUnsignedByte();
251 xlen |= inData.readUnsignedByte() << 8;
252
253
254
255
256 while (xlen-- > 0) {
257 inData.readUnsignedByte();
258 }
259 }
260
261
262 if ((flg & FNAME) != 0) {
263 parameters.setFileName(new String(readToNull(inData), GzipUtils.GZIP_ENCODING));
264 }
265
266
267 if ((flg & FCOMMENT) != 0) {
268 parameters.setComment(new String(readToNull(inData), GzipUtils.GZIP_ENCODING));
269 }
270
271
272
273
274
275
276 if ((flg & FHCRC) != 0) {
277 inData.readShort();
278 }
279
280
281 inf.reset();
282 crc.reset();
283
284 return true;
285 }
286
287 @Override
288 public int read() throws IOException {
289 return read(oneByte, 0, 1) == -1 ? -1 : oneByte[0] & 0xFF;
290 }
291
292
293
294
295
296
297 @Override
298 public int read(final byte[] b, int off, int len) throws IOException {
299 if (len == 0) {
300 return 0;
301 }
302 if (endReached) {
303 return -1;
304 }
305
306 int size = 0;
307
308 while (len > 0) {
309 if (inf.needsInput()) {
310
311
312 in.mark(buf.length);
313
314 bufUsed = in.read(buf);
315 if (bufUsed == -1) {
316 throw new EOFException();
317 }
318
319 inf.setInput(buf, 0, bufUsed);
320 }
321
322 final int ret;
323 try {
324 ret = inf.inflate(b, off, len);
325 } catch (final DataFormatException e) {
326 throw new IOException("Gzip-compressed data is corrupt");
327 }
328
329 crc.update(b, off, ret);
330 off += ret;
331 len -= ret;
332 size += ret;
333 count(ret);
334
335 if (inf.finished()) {
336
337
338 in.reset();
339
340 final int skipAmount = bufUsed - inf.getRemaining();
341 if (org.apache.commons.io.IOUtils.skip(in, skipAmount) != skipAmount) {
342 throw new IOException();
343 }
344
345 bufUsed = 0;
346
347 final DataInput inData = new DataInputStream(in);
348
349
350 final long crcStored = ByteUtils.fromLittleEndian(inData, 4);
351
352 if (crcStored != crc.getValue()) {
353 throw new IOException("Gzip-compressed data is corrupt " + "(CRC32 error)");
354 }
355
356
357 final long isize = ByteUtils.fromLittleEndian(inData, 4);
358
359 if (isize != (inf.getBytesWritten() & 0xffffffffL)) {
360 throw new IOException("Gzip-compressed data is corrupt" + "(uncompressed size mismatch)");
361 }
362
363
364 if (!decompressConcatenated || !init(false)) {
365 inf.end();
366 inf = null;
367 endReached = true;
368 return size == 0 ? -1 : size;
369 }
370 }
371 }
372
373 return size;
374 }
375 }