View Javadoc
1   /*
2    * Copyright 2013 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
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  /**
30   * Decompress a {@link ByteBuf} using the inflate algorithm.
31   */
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      /**
40       * Smallest buffer we hand to {@link Inflater#inflate(byte[], int, int)}. The number of remaining input bytes is
41       * only a hint for how much output to expect: the inflater may still hold decoded data that did not fit into the
42       * previous output buffer, and by then it may have consumed all input bytes already
43       * ({@link Inflater#getRemaining()} == 0). Inflating into a zero-sized buffer can never make progress.
44       */
45      private static final int MIN_OUTPUT_BUFFER_SIZE = 512;
46  
47      private Inflater inflater;
48      private final byte[] dictionary;
49  
50      // GZIP related
51      private final ByteBufChecksum crc;
52      private final boolean decompressConcatenated;
53  
54      private enum GzipState {
55          HEADER_START,
56          HEADER_END,
57          FLG_READ,
58          XLEN_READ,
59          SKIP_FNAME,
60          SKIP_COMMENT,
61          PROCESS_FHCRC,
62          FOOTER_START,
63      }
64  
65      private GzipState gzipState = GzipState.HEADER_START;
66      private int flags = -1;
67      private int xlen = -1;
68      private boolean needsRead;
69  
70      private static final int DEFAULT_MAX_FORWARD_BYTES = CompressionUtil.DEFAULT_MAX_FORWARD_BYTES;
71      private final int maxForwardBytes;
72  
73      private volatile boolean finished;
74  
75      private boolean decideZlibOrNone;
76  
77      /**
78       * Creates a new instance with the default wrapper ({@link ZlibWrapper#ZLIB}).
79       *
80       * @deprecated Use {@link JdkZlibDecoder#JdkZlibDecoder(int)}.
81       */
82      @Deprecated
83      public JdkZlibDecoder() {
84          this(ZlibWrapper.ZLIB, null, false, 0);
85      }
86  
87      /**
88       * Creates a new instance with the default wrapper ({@link ZlibWrapper#ZLIB})
89       * and the specified maximum buffer allocation.
90       *
91       * @param maxAllocation
92       *          Maximum size of the decompression buffer. Must be >= 0.
93       *          If zero, maximum size is decided by the {@link ByteBufAllocator}.
94       */
95      public JdkZlibDecoder(int maxAllocation) {
96          this(ZlibWrapper.ZLIB, null, false, maxAllocation);
97      }
98  
99      /**
100      * Creates a new instance with the specified preset dictionary. The wrapper
101      * is always {@link ZlibWrapper#ZLIB} because it is the only format that
102      * supports the preset dictionary.
103      *
104      * @deprecated Use {@link JdkZlibDecoder#JdkZlibDecoder(byte[], int)}.
105      */
106     @Deprecated
107     public JdkZlibDecoder(byte[] dictionary) {
108         this(ZlibWrapper.ZLIB, dictionary, false, 0);
109     }
110 
111     /**
112      * Creates a new instance with the specified preset dictionary and maximum buffer allocation.
113      * The wrapper is always {@link ZlibWrapper#ZLIB} because it is the only format that
114      * supports the preset dictionary.
115      *
116      * @param maxAllocation
117      *          Maximum size of the decompression buffer. Must be >= 0.
118      *          If zero, maximum size is decided by the {@link ByteBufAllocator}.
119      */
120     public JdkZlibDecoder(byte[] dictionary, int maxAllocation) {
121         this(ZlibWrapper.ZLIB, dictionary, false, maxAllocation);
122     }
123 
124     /**
125      * Creates a new instance with the specified wrapper.
126      * Be aware that only {@link ZlibWrapper#GZIP}, {@link ZlibWrapper#ZLIB} and {@link ZlibWrapper#NONE} are
127      * supported atm.
128      *
129      * @deprecated Use {@link JdkZlibDecoder#JdkZlibDecoder(ZlibWrapper, int)}.
130      */
131     @Deprecated
132     public JdkZlibDecoder(ZlibWrapper wrapper) {
133         this(wrapper, null, false, 0);
134     }
135 
136     /**
137      * Creates a new instance with the specified wrapper and maximum buffer allocation.
138      * Be aware that only {@link ZlibWrapper#GZIP}, {@link ZlibWrapper#ZLIB} and {@link ZlibWrapper#NONE} are
139      * supported atm.
140      *
141      * @param maxAllocation
142      *          Maximum size of the decompression buffer. Must be >= 0.
143      *          If zero, maximum size is decided by the {@link ByteBufAllocator}.
144      */
145     public JdkZlibDecoder(ZlibWrapper wrapper, int maxAllocation) {
146         this(wrapper, null, false, maxAllocation);
147     }
148 
149     /**
150      * @deprecated Use {@link JdkZlibDecoder#JdkZlibDecoder(ZlibWrapper, boolean, int)}.
151      */
152     @Deprecated
153     public JdkZlibDecoder(ZlibWrapper wrapper, boolean decompressConcatenated) {
154         this(wrapper, null, decompressConcatenated, 0);
155     }
156 
157     public JdkZlibDecoder(ZlibWrapper wrapper, boolean decompressConcatenated, int maxAllocation) {
158         this(wrapper, null, decompressConcatenated, maxAllocation);
159     }
160 
161     /**
162      * @deprecated Use {@link JdkZlibDecoder#JdkZlibDecoder(boolean, int)}.
163      */
164     @Deprecated
165     public JdkZlibDecoder(boolean decompressConcatenated) {
166         this(ZlibWrapper.GZIP, null, decompressConcatenated, 0);
167     }
168 
169     public JdkZlibDecoder(boolean decompressConcatenated, int maxAllocation) {
170         this(ZlibWrapper.GZIP, null, decompressConcatenated, maxAllocation);
171     }
172 
173     private JdkZlibDecoder(ZlibWrapper wrapper, byte[] dictionary, boolean decompressConcatenated, int maxAllocation) {
174         super(maxAllocation);
175         this.maxForwardBytes = maxAllocation > 0 ? maxAllocation : DEFAULT_MAX_FORWARD_BYTES;
176 
177         ObjectUtil.checkNotNull(wrapper, "wrapper");
178 
179         this.decompressConcatenated = decompressConcatenated;
180         switch (wrapper) {
181             case GZIP:
182                 inflater = new Inflater(true);
183                 crc = ByteBufChecksum.wrapChecksum(new CRC32());
184                 break;
185             case NONE:
186                 inflater = new Inflater(true);
187                 crc = null;
188                 break;
189             case ZLIB:
190                 inflater = new Inflater();
191                 crc = null;
192                 break;
193             case ZLIB_OR_NONE:
194                 // Postpone the decision until decode(...) is called.
195                 decideZlibOrNone = true;
196                 crc = null;
197                 break;
198             default:
199                 throw new IllegalArgumentException("Only GZIP or ZLIB is supported, but you used " + wrapper);
200         }
201         this.dictionary = dictionary;
202     }
203 
204     @Override
205     public boolean isClosed() {
206         return finished;
207     }
208 
209     @Override
210     protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
211         needsRead = true;
212         if (finished) {
213             // Skip data received after finished.
214             in.skipBytes(in.readableBytes());
215             return;
216         }
217 
218         int readableBytes = in.readableBytes();
219         if (readableBytes == 0) {
220             return;
221         }
222 
223         if (decideZlibOrNone) {
224             // First two bytes are needed to decide if it's a ZLIB stream.
225             if (readableBytes < 2) {
226                 return;
227             }
228 
229             boolean nowrap = !looksLikeZlib(in.getShort(in.readerIndex()));
230             inflater = new Inflater(nowrap);
231             decideZlibOrNone = false;
232         }
233 
234         if (crc != null) {
235             if (gzipState != GzipState.HEADER_END) {
236                 if (gzipState == GzipState.FOOTER_START) {
237                     if (!handleGzipFooter(in)) {
238                         // Either there was not enough data or the input is finished.
239                         return;
240                     }
241                     // If we consumed the footer we will start with the header again.
242                     assert gzipState == GzipState.HEADER_START;
243                 }
244                 if (!readGZIPHeader(in)) {
245                     // There was not enough data readable to read the GZIP header.
246                     return;
247                 }
248                 // Some bytes may have been consumed, and so we must re-set the number of readable bytes.
249                 readableBytes = in.readableBytes();
250                 if (readableBytes == 0) {
251                     return;
252                 }
253             }
254         }
255 
256         if (inflater.needsInput()) {
257             if (in.hasArray()) {
258                 inflater.setInput(in.array(), in.arrayOffset() + in.readerIndex(), readableBytes);
259             } else {
260                 byte[] array = new byte[readableBytes];
261                 in.getBytes(in.readerIndex(), array);
262                 inflater.setInput(array);
263             }
264         }
265 
266         ByteBuf decompressed = prepareDecompressBuffer(ctx, null, preferredOutputBufferSize());
267         try {
268             boolean readFooter = false;
269             // If this is true the last inflate(...) filled the output buffer completely, so the inflater may still
270             // hold decoded data even once all input bytes were consumed. needsInput() only tells us that the input
271             // was consumed, not that all output was produced, so on its own it would end the loop too early and
272             // the pending bytes would be dropped together with the input we skip below.
273             boolean pendingOutput = false;
274             while (pendingOutput || !inflater.needsInput()) {
275                 byte[] outArray = decompressed.array();
276                 int writerIndex = decompressed.writerIndex();
277                 int outIndex = decompressed.arrayOffset() + writerIndex;
278                 int writable = decompressed.writableBytes();
279                 int outputLength = inflater.inflate(outArray, outIndex, writable);
280                 pendingOutput = outputLength == writable;
281                 if (outputLength > 0) {
282                     decompressed.writerIndex(writerIndex + outputLength);
283                     if (crc != null) {
284                         crc.update(outArray, outIndex, outputLength);
285                     }
286                     if (maxAllocation == 0 && decompressed.readableBytes() >= maxForwardBytes) {
287                         // Forward the buffer once it exceeds the threshold to bound memory
288                         // while avoiding excessive fireChannelRead calls.
289                         ByteBuf buffer = decompressed;
290                         decompressed = null;
291                         needsRead = false;
292                         ctx.fireChannelRead(buffer);
293                     }
294                 } else if (inflater.needsDictionary()) {
295                     if (dictionary == null) {
296                         throw new DecompressionException(
297                                 "decompression failure, unable to set dictionary as non was specified");
298                     }
299                     try {
300                         inflater.setDictionary(dictionary);
301                     } catch (IllegalArgumentException e) {
302                         // The dictionary ID in the stream header does not match the configured dictionary.
303                         throw new DecompressionException("decompression failure, dictionary mismatch", e);
304                     }
305                 }
306 
307                 if (inflater.finished()) {
308                     if (crc == null) {
309                         finished = true; // Do not decode anymore.
310                     } else {
311                         readFooter = true;
312                     }
313                     break;
314                 } else {
315                     decompressed = prepareDecompressBuffer(ctx, decompressed, preferredOutputBufferSize());
316                 }
317             }
318 
319             in.skipBytes(readableBytes - inflater.getRemaining());
320 
321             if (readFooter) {
322                 gzipState = GzipState.FOOTER_START;
323                 handleGzipFooter(in);
324             }
325         } catch (DataFormatException e) {
326             throw new DecompressionException("decompression failure", e);
327         } finally {
328             if (decompressed != null) {
329                 if (decompressed.isReadable()) {
330                     needsRead = false;
331                     ctx.fireChannelRead(decompressed);
332                 } else {
333                     decompressed.release();
334                 }
335             }
336         }
337     }
338 
339     /**
340      * The size we ask {@link #prepareDecompressBuffer(ChannelHandlerContext, ByteBuf, int)} for. Twice the remaining
341      * input is a good guess for how much output is still coming, but it must never be zero because
342      * {@link Inflater#inflate(byte[], int, int)} cannot write anything then. Any {@code maxAllocation} the user
343      * configured is still applied by {@code prepareDecompressBuffer(...)} and so keeps its meaning.
344      */
345     private int preferredOutputBufferSize() {
346         return Math.max(inflater.getRemaining() << 1, MIN_OUTPUT_BUFFER_SIZE);
347     }
348 
349     private boolean handleGzipFooter(ByteBuf in) {
350         if (readGZIPFooter(in)) {
351             finished = !decompressConcatenated;
352 
353             if (!finished) {
354                 inflater.reset();
355                 crc.reset();
356                 xlen = -1;
357                 gzipState = GzipState.HEADER_START;
358                 return true;
359             }
360         }
361         return false;
362     }
363 
364     @Override
365     protected void decompressionBufferExhausted(ByteBuf buffer) {
366         finished = true;
367     }
368 
369     @Override
370     protected void handlerRemoved0(ChannelHandlerContext ctx) throws Exception {
371         super.handlerRemoved0(ctx);
372         if (inflater != null) {
373             inflater.end();
374         }
375     }
376 
377     private boolean readGZIPHeader(ByteBuf in) {
378         switch (gzipState) {
379             case HEADER_START:
380                 if (in.readableBytes() < 10) {
381                     return false;
382                 }
383                 // read magic numbers
384                 int magic0 = in.readUnsignedByte();
385                 int magic1 = in.readUnsignedByte();
386 
387                 if (magic0 != 31 || magic1 != 139) {
388                     throw new DecompressionException("Input is not in the GZIP format");
389                 }
390                 crc.update(magic0);
391                 crc.update(magic1);
392 
393                 int method = in.readUnsignedByte();
394                 if (method != Deflater.DEFLATED) {
395                     throw new DecompressionException("Unsupported compression method "
396                             + method + " in the GZIP header");
397                 }
398                 crc.update(method);
399 
400                 flags = in.readUnsignedByte();
401                 crc.update(flags);
402 
403                 if ((flags & FRESERVED) != 0) {
404                     throw new DecompressionException(
405                             "Reserved flags are set in the GZIP header");
406                 }
407 
408                 // mtime (int)
409                 crc.update(in, in.readerIndex(), 4);
410                 in.skipBytes(4);
411 
412                 crc.update(in.readUnsignedByte()); // extra flags
413                 crc.update(in.readUnsignedByte()); // operating system
414 
415                 gzipState = GzipState.FLG_READ;
416                 // fall through
417             case FLG_READ:
418                 if ((flags & FEXTRA) != 0) {
419                     if (in.readableBytes() < 2) {
420                         return false;
421                     }
422                     int xlen1 = in.readUnsignedByte();
423                     int xlen2 = in.readUnsignedByte();
424                     crc.update(xlen1);
425                     crc.update(xlen2);
426 
427                     // XLEN is a little-endian unsigned 16-bit value (RFC 1952), so xlen1 is the
428                     // low byte and xlen2 the high byte. This must be an assignment, not |=: xlen
429                     // starts at the -1 "no extra field" sentinel, and OR-ing into -1 (0xFFFFFFFF)
430                     // would leave it -1, so the extra field was never skipped.
431                     xlen = xlen2 << 8 | xlen1;
432                 }
433                 gzipState = GzipState.XLEN_READ;
434                 // fall through
435             case XLEN_READ:
436                 if (xlen != -1) {
437                     if (in.readableBytes() < xlen) {
438                         return false;
439                     }
440                     crc.update(in, in.readerIndex(), xlen);
441                     in.skipBytes(xlen);
442                 }
443                 gzipState = GzipState.SKIP_FNAME;
444                 // fall through
445             case SKIP_FNAME:
446                 if (!skipIfNeeded(in, FNAME)) {
447                     return false;
448                 }
449                 gzipState = GzipState.SKIP_COMMENT;
450                 // fall through
451             case SKIP_COMMENT:
452                 if (!skipIfNeeded(in, FCOMMENT)) {
453                     return false;
454                 }
455                 gzipState = GzipState.PROCESS_FHCRC;
456                 // fall through
457             case PROCESS_FHCRC:
458                 if ((flags & FHCRC) != 0) {
459                     if (!verifyCrc16(in)) {
460                         return false;
461                     }
462                 }
463                 crc.reset();
464                 gzipState = GzipState.HEADER_END;
465                 // fall through
466             case HEADER_END:
467                 return true;
468             default:
469                 throw new IllegalStateException();
470         }
471     }
472 
473     /**
474      * Skip bytes in the input if needed until we find the end marker {@code 0x00}.
475      * @param   in the input
476      * @param   flagMask the mask that should be present in the {@code flags} when we need to skip bytes.
477      * @return  {@code true} if the operation is complete and we can move to the next state, {@code false} if we need
478      *          the retry again once we have more readable bytes.
479      */
480     private boolean skipIfNeeded(ByteBuf in, int flagMask) {
481         if ((flags & flagMask) != 0) {
482             for (;;) {
483                 if (!in.isReadable()) {
484                     // We didnt find the end yet, need to retry again once more data is readable
485                     return false;
486                 }
487                 int b = in.readUnsignedByte();
488                 crc.update(b);
489                 if (b == 0x00) {
490                     break;
491                 }
492             }
493         }
494         // Skip is handled, we can move to the next processing state.
495         return true;
496     }
497 
498     /**
499      * Read the GZIP footer.
500      *
501      * @param   in the input.
502      * @return  {@code true} if the footer could be read, {@code false} if the read could not be performed as
503      *          the input {@link ByteBuf} doesn't have enough readable bytes (8 bytes).
504      */
505     private boolean readGZIPFooter(ByteBuf in) {
506         if (in.readableBytes() < 8) {
507             return false;
508         }
509 
510         boolean enoughData = verifyCrc(in);
511         assert enoughData;
512 
513         // read ISIZE and verify
514         int dataLength = 0;
515         for (int i = 0; i < 4; ++i) {
516             dataLength |= in.readUnsignedByte() << i * 8;
517         }
518         int readLength = inflater.getTotalOut();
519         if (dataLength != readLength) {
520             throw new DecompressionException(
521                     "Number of bytes mismatch. Expected: " + dataLength + ", Got: " + readLength);
522         }
523         return true;
524     }
525 
526     /**
527      * Verifies CRC.
528      *
529      * @param   in the input.
530      * @return  {@code true} if verification could be performed, {@code false} if verification could not be performed as
531      *          the input {@link ByteBuf} doesn't have enough readable bytes (4 bytes).
532      */
533     private boolean verifyCrc(ByteBuf in) {
534         if (in.readableBytes() < 4) {
535             return false;
536         }
537         long crcValue = 0;
538         for (int i = 0; i < 4; ++i) {
539             crcValue |= (long) in.readUnsignedByte() << i * 8;
540         }
541         long readCrc = crc.getValue();
542         if (crcValue != readCrc) {
543             throw new DecompressionException(
544                     "CRC value mismatch. Expected: " + crcValue + ", Got: " + readCrc);
545         }
546         return true;
547     }
548 
549     private boolean verifyCrc16(ByteBuf in) {
550         if (in.readableBytes() < 2) {
551             return false;
552         }
553         long readCrc32 = crc.getValue();
554         long crc16Value = 0;
555         long readCrc16 = 0; // the two least significant bytes from the CRC32
556         for (int i = 0; i < 2; ++i) {
557             crc16Value |= (long) in.readUnsignedByte() << (i * 8);
558             readCrc16 |= ((readCrc32 >> (i * 8)) & 0xff) << (i * 8);
559         }
560 
561         if (crc16Value != readCrc16) {
562             throw new DecompressionException(
563                     "CRC16 value mismatch. Expected: " + crc16Value + ", Got: " + readCrc16);
564         }
565         return true;
566     }
567 
568     /*
569      * Returns true if the cmf_flg parameter (think: first two bytes of a zlib stream)
570      * indicates that this is a zlib stream.
571      * <p>
572      * You can lookup the details in the ZLIB RFC:
573      * <a href="https://tools.ietf.org/html/rfc1950#section-2.2">RFC 1950</a>.
574      */
575     private static boolean looksLikeZlib(short cmf_flg) {
576         return (cmf_flg & 0x7800) == 0x7800 &&
577                 cmf_flg % 31 == 0;
578     }
579 
580     @Override
581     public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
582         // Discard bytes of the cumulation buffer if needed.
583         discardSomeReadBytes();
584 
585         if (needsRead && !ctx.channel().config().isAutoRead()) {
586             ctx.read();
587         }
588         ctx.fireChannelReadComplete();
589     }
590 }