1
16 package io.netty.handler.codec.compression;
17
18 import io.netty.buffer.ByteBuf;
19 import io.netty.buffer.ByteBufAllocator;
20 import io.netty.channel.ChannelHandlerContext;
21 import io.netty.util.internal.ObjectUtil;
22
23 import java.util.List;
24 import java.util.zip.CRC32;
25 import java.util.zip.DataFormatException;
26 import java.util.zip.Deflater;
27 import java.util.zip.Inflater;
28
29
32 public class JdkZlibDecoder extends ZlibDecoder {
33 private static final int FHCRC = 0x02;
34 private static final int FEXTRA = 0x04;
35 private static final int FNAME = 0x08;
36 private static final int FCOMMENT = 0x10;
37 private static final int FRESERVED = 0xE0;
38
39 private Inflater inflater;
40 private final byte[] dictionary;
41
42
43 private final ByteBufChecksum crc;
44 private final boolean decompressConcatenated;
45
46 private enum GzipState {
47 HEADER_START,
48 HEADER_END,
49 FLG_READ,
50 XLEN_READ,
51 SKIP_FNAME,
52 SKIP_COMMENT,
53 PROCESS_FHCRC,
54 FOOTER_START,
55 }
56
57 private GzipState gzipState = GzipState.HEADER_START;
58 private int flags = -1;
59 private int xlen = -1;
60
61 private volatile boolean finished;
62
63 private boolean decideZlibOrNone;
64
65
68 public JdkZlibDecoder() {
69 this(ZlibWrapper.ZLIB, null, false, 0);
70 }
71
72
80 public JdkZlibDecoder(int maxAllocation) {
81 this(ZlibWrapper.ZLIB, null, false, maxAllocation);
82 }
83
84
89 public JdkZlibDecoder(byte[] dictionary) {
90 this(ZlibWrapper.ZLIB, dictionary, false, 0);
91 }
92
93
102 public JdkZlibDecoder(byte[] dictionary, int maxAllocation) {
103 this(ZlibWrapper.ZLIB, dictionary, false, maxAllocation);
104 }
105
106
111 public JdkZlibDecoder(ZlibWrapper wrapper) {
112 this(wrapper, null, false, 0);
113 }
114
115
124 public JdkZlibDecoder(ZlibWrapper wrapper, int maxAllocation) {
125 this(wrapper, null, false, maxAllocation);
126 }
127
128 public JdkZlibDecoder(ZlibWrapper wrapper, boolean decompressConcatenated) {
129 this(wrapper, null, decompressConcatenated, 0);
130 }
131
132 public JdkZlibDecoder(ZlibWrapper wrapper, boolean decompressConcatenated, int maxAllocation) {
133 this(wrapper, null, decompressConcatenated, maxAllocation);
134 }
135
136 public JdkZlibDecoder(boolean decompressConcatenated) {
137 this(ZlibWrapper.GZIP, null, decompressConcatenated, 0);
138 }
139
140 public JdkZlibDecoder(boolean decompressConcatenated, int maxAllocation) {
141 this(ZlibWrapper.GZIP, null, decompressConcatenated, maxAllocation);
142 }
143
144 private JdkZlibDecoder(ZlibWrapper wrapper, byte[] dictionary, boolean decompressConcatenated, int maxAllocation) {
145 super(maxAllocation);
146
147 ObjectUtil.checkNotNull(wrapper, "wrapper");
148
149 this.decompressConcatenated = decompressConcatenated;
150 switch (wrapper) {
151 case GZIP:
152 inflater = new Inflater(true);
153 crc = ByteBufChecksum.wrapChecksum(new CRC32());
154 break;
155 case NONE:
156 inflater = new Inflater(true);
157 crc = null;
158 break;
159 case ZLIB:
160 inflater = new Inflater();
161 crc = null;
162 break;
163 case ZLIB_OR_NONE:
164
165 decideZlibOrNone = true;
166 crc = null;
167 break;
168 default:
169 throw new IllegalArgumentException("Only GZIP or ZLIB is supported, but you used " + wrapper);
170 }
171 this.dictionary = dictionary;
172 }
173
174 @Override
175 public boolean isClosed() {
176 return finished;
177 }
178
179 @Override
180 protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
181 if (finished) {
182
183 in.skipBytes(in.readableBytes());
184 return;
185 }
186
187 int readableBytes = in.readableBytes();
188 if (readableBytes == 0) {
189 return;
190 }
191
192 if (decideZlibOrNone) {
193
194 if (readableBytes < 2) {
195 return;
196 }
197
198 boolean nowrap = !looksLikeZlib(in.getShort(in.readerIndex()));
199 inflater = new Inflater(nowrap);
200 decideZlibOrNone = false;
201 }
202
203 if (crc != null) {
204 switch (gzipState) {
205 case FOOTER_START:
206 if (readGZIPFooter(in)) {
207 finished = true;
208 }
209 return;
210 default:
211 if (gzipState != GzipState.HEADER_END) {
212 if (!readGZIPHeader(in)) {
213 return;
214 }
215 }
216 }
217
218 readableBytes = in.readableBytes();
219 }
220
221 if (in.hasArray()) {
222 inflater.setInput(in.array(), in.arrayOffset() + in.readerIndex(), readableBytes);
223 } else {
224 byte[] array = new byte[readableBytes];
225 in.getBytes(in.readerIndex(), array);
226 inflater.setInput(array);
227 }
228
229 ByteBuf decompressed = prepareDecompressBuffer(ctx, null, inflater.getRemaining() << 1);
230 try {
231 boolean readFooter = false;
232 while (!inflater.needsInput()) {
233 byte[] outArray = decompressed.array();
234 int writerIndex = decompressed.writerIndex();
235 int outIndex = decompressed.arrayOffset() + writerIndex;
236 int outputLength = inflater.inflate(outArray, outIndex, decompressed.writableBytes());
237 if (outputLength > 0) {
238 decompressed.writerIndex(writerIndex + outputLength);
239 if (crc != null) {
240 crc.update(outArray, outIndex, outputLength);
241 }
242 } else {
243 if (inflater.needsDictionary()) {
244 if (dictionary == null) {
245 throw new DecompressionException(
246 "decompression failure, unable to set dictionary as non was specified");
247 }
248 inflater.setDictionary(dictionary);
249 }
250 }
251
252 if (inflater.finished()) {
253 if (crc == null) {
254 finished = true;
255 } else {
256 readFooter = true;
257 }
258 break;
259 } else {
260 decompressed = prepareDecompressBuffer(ctx, decompressed, inflater.getRemaining() << 1);
261 }
262 }
263
264 in.skipBytes(readableBytes - inflater.getRemaining());
265
266 if (readFooter) {
267 gzipState = GzipState.FOOTER_START;
268 if (readGZIPFooter(in)) {
269 finished = !decompressConcatenated;
270
271 if (!finished) {
272 inflater.reset();
273 crc.reset();
274 gzipState = GzipState.HEADER_START;
275 }
276 }
277 }
278 } catch (DataFormatException e) {
279 throw new DecompressionException("decompression failure", e);
280 } finally {
281
282 if (decompressed.isReadable()) {
283 out.add(decompressed);
284 } else {
285 decompressed.release();
286 }
287 }
288 }
289
290 @Override
291 protected void decompressionBufferExhausted(ByteBuf buffer) {
292 finished = true;
293 }
294
295 @Override
296 protected void handlerRemoved0(ChannelHandlerContext ctx) throws Exception {
297 super.handlerRemoved0(ctx);
298 if (inflater != null) {
299 inflater.end();
300 }
301 }
302
303 private boolean readGZIPHeader(ByteBuf in) {
304 switch (gzipState) {
305 case HEADER_START:
306 if (in.readableBytes() < 10) {
307 return false;
308 }
309
310 int magic0 = in.readByte();
311 int magic1 = in.readByte();
312
313 if (magic0 != 31) {
314 throw new DecompressionException("Input is not in the GZIP format");
315 }
316 crc.update(magic0);
317 crc.update(magic1);
318
319 int method = in.readUnsignedByte();
320 if (method != Deflater.DEFLATED) {
321 throw new DecompressionException("Unsupported compression method "
322 + method + " in the GZIP header");
323 }
324 crc.update(method);
325
326 flags = in.readUnsignedByte();
327 crc.update(flags);
328
329 if ((flags & FRESERVED) != 0) {
330 throw new DecompressionException(
331 "Reserved flags are set in the GZIP header");
332 }
333
334
335 crc.update(in, in.readerIndex(), 4);
336 in.skipBytes(4);
337
338 crc.update(in.readUnsignedByte());
339 crc.update(in.readUnsignedByte());
340
341 gzipState = GzipState.FLG_READ;
342
343 case FLG_READ:
344 if ((flags & FEXTRA) != 0) {
345 if (in.readableBytes() < 2) {
346 return false;
347 }
348 int xlen1 = in.readUnsignedByte();
349 int xlen2 = in.readUnsignedByte();
350 crc.update(xlen1);
351 crc.update(xlen2);
352
353 xlen |= xlen1 << 8 | xlen2;
354 }
355 gzipState = GzipState.XLEN_READ;
356
357 case XLEN_READ:
358 if (xlen != -1) {
359 if (in.readableBytes() < xlen) {
360 return false;
361 }
362 crc.update(in, in.readerIndex(), xlen);
363 in.skipBytes(xlen);
364 }
365 gzipState = GzipState.SKIP_FNAME;
366
367 case SKIP_FNAME:
368 if ((flags & FNAME) != 0) {
369 if (!in.isReadable()) {
370 return false;
371 }
372 do {
373 int b = in.readUnsignedByte();
374 crc.update(b);
375 if (b == 0x00) {
376 break;
377 }
378 } while (in.isReadable());
379 }
380 gzipState = GzipState.SKIP_COMMENT;
381
382 case SKIP_COMMENT:
383 if ((flags & FCOMMENT) != 0) {
384 if (!in.isReadable()) {
385 return false;
386 }
387 do {
388 int b = in.readUnsignedByte();
389 crc.update(b);
390 if (b == 0x00) {
391 break;
392 }
393 } while (in.isReadable());
394 }
395 gzipState = GzipState.PROCESS_FHCRC;
396
397 case PROCESS_FHCRC:
398 if ((flags & FHCRC) != 0) {
399 if (in.readableBytes() < 4) {
400 return false;
401 }
402 verifyCrc(in);
403 }
404 crc.reset();
405 gzipState = GzipState.HEADER_END;
406
407 case HEADER_END:
408 return true;
409 default:
410 throw new IllegalStateException();
411 }
412 }
413
414 private boolean readGZIPFooter(ByteBuf buf) {
415 if (buf.readableBytes() < 8) {
416 return false;
417 }
418
419 verifyCrc(buf);
420
421
422 int dataLength = 0;
423 for (int i = 0; i < 4; ++i) {
424 dataLength |= buf.readUnsignedByte() << i * 8;
425 }
426 int readLength = inflater.getTotalOut();
427 if (dataLength != readLength) {
428 throw new DecompressionException(
429 "Number of bytes mismatch. Expected: " + dataLength + ", Got: " + readLength);
430 }
431 return true;
432 }
433
434 private void verifyCrc(ByteBuf in) {
435 long crcValue = 0;
436 for (int i = 0; i < 4; ++i) {
437 crcValue |= (long) in.readUnsignedByte() << i * 8;
438 }
439 long readCrc = crc.getValue();
440 if (crcValue != readCrc) {
441 throw new DecompressionException(
442 "CRC value mismatch. Expected: " + crcValue + ", Got: " + readCrc);
443 }
444 }
445
446
453 private static boolean looksLikeZlib(short cmf_flg) {
454 return (cmf_flg & 0x7800) == 0x7800 &&
455 cmf_flg % 31 == 0;
456 }
457 }
458