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                     inflater.setDictionary(dictionary);
300                 }
301 
302                 if (inflater.finished()) {
303                     if (crc == null) {
304                         finished = true; // Do not decode anymore.
305                     } else {
306                         readFooter = true;
307                     }
308                     break;
309                 } else {
310                     decompressed = prepareDecompressBuffer(ctx, decompressed, preferredOutputBufferSize());
311                 }
312             }
313 
314             in.skipBytes(readableBytes - inflater.getRemaining());
315 
316             if (readFooter) {
317                 gzipState = GzipState.FOOTER_START;
318                 handleGzipFooter(in);
319             }
320         } catch (DataFormatException e) {
321             throw new DecompressionException("decompression failure", e);
322         } finally {
323             if (decompressed != null) {
324                 if (decompressed.isReadable()) {
325                     needsRead = false;
326                     ctx.fireChannelRead(decompressed);
327                 } else {
328                     decompressed.release();
329                 }
330             }
331         }
332     }
333 
334     /**
335      * The size we ask {@link #prepareDecompressBuffer(ChannelHandlerContext, ByteBuf, int)} for. Twice the remaining
336      * input is a good guess for how much output is still coming, but it must never be zero because
337      * {@link Inflater#inflate(byte[], int, int)} cannot write anything then. Any {@code maxAllocation} the user
338      * configured is still applied by {@code prepareDecompressBuffer(...)} and so keeps its meaning.
339      */
340     private int preferredOutputBufferSize() {
341         return Math.max(inflater.getRemaining() << 1, MIN_OUTPUT_BUFFER_SIZE);
342     }
343 
344     private boolean handleGzipFooter(ByteBuf in) {
345         if (readGZIPFooter(in)) {
346             finished = !decompressConcatenated;
347 
348             if (!finished) {
349                 inflater.reset();
350                 crc.reset();
351                 xlen = -1;
352                 gzipState = GzipState.HEADER_START;
353                 return true;
354             }
355         }
356         return false;
357     }
358 
359     @Override
360     protected void decompressionBufferExhausted(ByteBuf buffer) {
361         finished = true;
362     }
363 
364     @Override
365     protected void handlerRemoved0(ChannelHandlerContext ctx) throws Exception {
366         super.handlerRemoved0(ctx);
367         if (inflater != null) {
368             inflater.end();
369         }
370     }
371 
372     private boolean readGZIPHeader(ByteBuf in) {
373         switch (gzipState) {
374             case HEADER_START:
375                 if (in.readableBytes() < 10) {
376                     return false;
377                 }
378                 // read magic numbers
379                 int magic0 = in.readByte();
380                 int magic1 = in.readByte();
381 
382                 if (magic0 != 31) {
383                     throw new DecompressionException("Input is not in the GZIP format");
384                 }
385                 crc.update(magic0);
386                 crc.update(magic1);
387 
388                 int method = in.readUnsignedByte();
389                 if (method != Deflater.DEFLATED) {
390                     throw new DecompressionException("Unsupported compression method "
391                             + method + " in the GZIP header");
392                 }
393                 crc.update(method);
394 
395                 flags = in.readUnsignedByte();
396                 crc.update(flags);
397 
398                 if ((flags & FRESERVED) != 0) {
399                     throw new DecompressionException(
400                             "Reserved flags are set in the GZIP header");
401                 }
402 
403                 // mtime (int)
404                 crc.update(in, in.readerIndex(), 4);
405                 in.skipBytes(4);
406 
407                 crc.update(in.readUnsignedByte()); // extra flags
408                 crc.update(in.readUnsignedByte()); // operating system
409 
410                 gzipState = GzipState.FLG_READ;
411                 // fall through
412             case FLG_READ:
413                 if ((flags & FEXTRA) != 0) {
414                     if (in.readableBytes() < 2) {
415                         return false;
416                     }
417                     int xlen1 = in.readUnsignedByte();
418                     int xlen2 = in.readUnsignedByte();
419                     crc.update(xlen1);
420                     crc.update(xlen2);
421 
422                     // XLEN is a little-endian unsigned 16-bit value (RFC 1952), so xlen1 is the
423                     // low byte and xlen2 the high byte. This must be an assignment, not |=: xlen
424                     // starts at the -1 "no extra field" sentinel, and OR-ing into -1 (0xFFFFFFFF)
425                     // would leave it -1, so the extra field was never skipped.
426                     xlen = xlen2 << 8 | xlen1;
427                 }
428                 gzipState = GzipState.XLEN_READ;
429                 // fall through
430             case XLEN_READ:
431                 if (xlen != -1) {
432                     if (in.readableBytes() < xlen) {
433                         return false;
434                     }
435                     crc.update(in, in.readerIndex(), xlen);
436                     in.skipBytes(xlen);
437                 }
438                 gzipState = GzipState.SKIP_FNAME;
439                 // fall through
440             case SKIP_FNAME:
441                 if (!skipIfNeeded(in, FNAME)) {
442                     return false;
443                 }
444                 gzipState = GzipState.SKIP_COMMENT;
445                 // fall through
446             case SKIP_COMMENT:
447                 if (!skipIfNeeded(in, FCOMMENT)) {
448                     return false;
449                 }
450                 gzipState = GzipState.PROCESS_FHCRC;
451                 // fall through
452             case PROCESS_FHCRC:
453                 if ((flags & FHCRC) != 0) {
454                     if (!verifyCrc16(in)) {
455                         return false;
456                     }
457                 }
458                 crc.reset();
459                 gzipState = GzipState.HEADER_END;
460                 // fall through
461             case HEADER_END:
462                 return true;
463             default:
464                 throw new IllegalStateException();
465         }
466     }
467 
468     /**
469      * Skip bytes in the input if needed until we find the end marker {@code 0x00}.
470      * @param   in the input
471      * @param   flagMask the mask that should be present in the {@code flags} when we need to skip bytes.
472      * @return  {@code true} if the operation is complete and we can move to the next state, {@code false} if we need
473      *          the retry again once we have more readable bytes.
474      */
475     private boolean skipIfNeeded(ByteBuf in, int flagMask) {
476         if ((flags & flagMask) != 0) {
477             for (;;) {
478                 if (!in.isReadable()) {
479                     // We didnt find the end yet, need to retry again once more data is readable
480                     return false;
481                 }
482                 int b = in.readUnsignedByte();
483                 crc.update(b);
484                 if (b == 0x00) {
485                     break;
486                 }
487             }
488         }
489         // Skip is handled, we can move to the next processing state.
490         return true;
491     }
492 
493     /**
494      * Read the GZIP footer.
495      *
496      * @param   in the input.
497      * @return  {@code true} if the footer could be read, {@code false} if the read could not be performed as
498      *          the input {@link ByteBuf} doesn't have enough readable bytes (8 bytes).
499      */
500     private boolean readGZIPFooter(ByteBuf in) {
501         if (in.readableBytes() < 8) {
502             return false;
503         }
504 
505         boolean enoughData = verifyCrc(in);
506         assert enoughData;
507 
508         // read ISIZE and verify
509         int dataLength = in.readIntLE();
510         int readLength = inflater.getTotalOut();
511         if (dataLength != readLength) {
512             throw new DecompressionException(
513                     "Number of bytes mismatch. Expected: " + dataLength + ", Got: " + readLength);
514         }
515         return true;
516     }
517 
518     /**
519      * Verifies CRC.
520      *
521      * @param   in the input.
522      * @return  {@code true} if verification could be performed, {@code false} if verification could not be performed as
523      *          the input {@link ByteBuf} doesn't have enough readable bytes (4 bytes).
524      */
525     private boolean verifyCrc(ByteBuf in) {
526         if (in.readableBytes() < 4) {
527             return false;
528         }
529         long crcValue = in.readUnsignedIntLE();
530 
531         long readCrc = crc.getValue();
532         if (crcValue != readCrc) {
533             throw new DecompressionException(
534                     "CRC value mismatch. Expected: " + crcValue + ", Got: " + readCrc);
535         }
536         return true;
537     }
538 
539     private boolean verifyCrc16(ByteBuf in) {
540         if (in.readableBytes() < 2) {
541             return false;
542         }
543 
544         int crc16Value = in.readUnsignedShortLE();
545         // the two least significant bytes from the CRC32
546         int readCrc16 = (int) (crc.getValue() & 0xFFFF);
547 
548         if (crc16Value != readCrc16) {
549             throw new DecompressionException(
550                     "CRC16 value mismatch. Expected: " + crc16Value + ", Got: " + readCrc16);
551         }
552         return true;
553     }
554 
555     /*
556      * Returns true if the cmf_flg parameter (think: first two bytes of a zlib stream)
557      * indicates that this is a zlib stream.
558      * <p>
559      * You can lookup the details in the ZLIB RFC:
560      * <a href="https://tools.ietf.org/html/rfc1950#section-2.2">RFC 1950</a>.
561      */
562     private static boolean looksLikeZlib(short cmf_flg) {
563         return (cmf_flg & 0x7800) == 0x7800 &&
564                 cmf_flg % 31 == 0;
565     }
566 
567     @Override
568     public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
569         // Discard bytes of the cumulation buffer if needed.
570         discardSomeReadBytes();
571 
572         if (needsRead && !ctx.channel().config().isAutoRead()) {
573             ctx.read();
574         }
575         ctx.fireChannelReadComplete();
576     }
577 }