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    *   http://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.channel.ChannelHandlerContext;
20  
21  import java.util.List;
22  import java.util.zip.CRC32;
23  import java.util.zip.DataFormatException;
24  import java.util.zip.Deflater;
25  import java.util.zip.Inflater;
26  
27  /**
28   * Decompress a {@link ByteBuf} using the inflate algorithm.
29   */
30  public class JdkZlibDecoder extends ZlibDecoder {
31      private static final int FHCRC = 0x02;
32      private static final int FEXTRA = 0x04;
33      private static final int FNAME = 0x08;
34      private static final int FCOMMENT = 0x10;
35      private static final int FRESERVED = 0xE0;
36  
37      private Inflater inflater;
38      private final byte[] dictionary;
39  
40      // GZIP related
41      private final CRC32 crc;
42  
43      private enum GzipState {
44          HEADER_START,
45          HEADER_END,
46          FLG_READ,
47          XLEN_READ,
48          SKIP_FNAME,
49          SKIP_COMMENT,
50          PROCESS_FHCRC,
51          FOOTER_START,
52      }
53  
54      private GzipState gzipState = GzipState.HEADER_START;
55      private int flags = -1;
56      private int xlen = -1;
57  
58      private volatile boolean finished;
59  
60      private boolean decideZlibOrNone;
61  
62      /**
63       * Creates a new instance with the default wrapper ({@link ZlibWrapper#ZLIB}).
64       */
65      public JdkZlibDecoder() {
66          this(ZlibWrapper.ZLIB, null);
67      }
68  
69      /**
70       * Creates a new instance with the specified preset dictionary. The wrapper
71       * is always {@link ZlibWrapper#ZLIB} because it is the only format that
72       * supports the preset dictionary.
73       */
74      public JdkZlibDecoder(byte[] dictionary) {
75          this(ZlibWrapper.ZLIB, dictionary);
76      }
77  
78      /**
79       * Creates a new instance with the specified wrapper.
80       * Be aware that only {@link ZlibWrapper#GZIP}, {@link ZlibWrapper#ZLIB} and {@link ZlibWrapper#NONE} are
81       * supported atm.
82       */
83      public JdkZlibDecoder(ZlibWrapper wrapper) {
84          this(wrapper, null);
85      }
86  
87      private JdkZlibDecoder(ZlibWrapper wrapper, byte[] dictionary) {
88          if (wrapper == null) {
89              throw new NullPointerException("wrapper");
90          }
91          switch (wrapper) {
92              case GZIP:
93                  inflater = new Inflater(true);
94                  crc = new CRC32();
95                  break;
96              case NONE:
97                  inflater = new Inflater(true);
98                  crc = null;
99                  break;
100             case ZLIB:
101                 inflater = new Inflater();
102                 crc = null;
103                 break;
104             case ZLIB_OR_NONE:
105                 // Postpone the decision until decode(...) is called.
106                 decideZlibOrNone = true;
107                 crc = null;
108                 break;
109             default:
110                 throw new IllegalArgumentException("Only GZIP or ZLIB is supported, but you used " + wrapper);
111         }
112         this.dictionary = dictionary;
113     }
114 
115     @Override
116     public boolean isClosed() {
117         return finished;
118     }
119 
120     @Override
121     protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
122         if (finished) {
123             // Skip data received after finished.
124             in.skipBytes(in.readableBytes());
125             return;
126         }
127 
128         int readableBytes = in.readableBytes();
129         if (readableBytes == 0) {
130             return;
131         }
132 
133         if (decideZlibOrNone) {
134             // First two bytes are needed to decide if it's a ZLIB stream.
135             if (readableBytes < 2) {
136                 return;
137             }
138 
139             boolean nowrap = !looksLikeZlib(in.getShort(in.readerIndex()));
140             inflater = new Inflater(nowrap);
141             decideZlibOrNone = false;
142         }
143 
144         if (crc != null) {
145             switch (gzipState) {
146                 case FOOTER_START:
147                     if (readGZIPFooter(in)) {
148                         finished = true;
149                     }
150                     return;
151                 default:
152                     if (gzipState != GzipState.HEADER_END) {
153                         if (!readGZIPHeader(in)) {
154                             return;
155                         }
156                     }
157             }
158             // Some bytes may have been consumed, and so we must re-set the number of readable bytes.
159             readableBytes = in.readableBytes();
160         }
161 
162         if (in.hasArray()) {
163             inflater.setInput(in.array(), in.arrayOffset() + in.readerIndex(), readableBytes);
164         } else {
165             byte[] array = new byte[readableBytes];
166             in.getBytes(in.readerIndex(), array);
167             inflater.setInput(array);
168         }
169 
170         int maxOutputLength = inflater.getRemaining() << 1;
171         ByteBuf decompressed = ctx.alloc().heapBuffer(maxOutputLength);
172         try {
173             boolean readFooter = false;
174             byte[] outArray = decompressed.array();
175             while (!inflater.needsInput()) {
176                 int writerIndex = decompressed.writerIndex();
177                 int outIndex = decompressed.arrayOffset() + writerIndex;
178                 int length = decompressed.writableBytes();
179 
180                 if (length == 0) {
181                     // completely filled the buffer allocate a new one and start to fill it
182                     out.add(decompressed);
183                     decompressed = ctx.alloc().heapBuffer(maxOutputLength);
184                     outArray = decompressed.array();
185                     continue;
186                 }
187 
188                 int outputLength = inflater.inflate(outArray, outIndex, length);
189                 if (outputLength > 0) {
190                     decompressed.writerIndex(writerIndex + outputLength);
191                     if (crc != null) {
192                         crc.update(outArray, outIndex, outputLength);
193                     }
194                 } else {
195                     if (inflater.needsDictionary()) {
196                         if (dictionary == null) {
197                             throw new DecompressionException(
198                                     "decompression failure, unable to set dictionary as non was specified");
199                         }
200                         inflater.setDictionary(dictionary);
201                     }
202                 }
203 
204                 if (inflater.finished()) {
205                     if (crc == null) {
206                         finished = true; // Do not decode anymore.
207                     } else {
208                         readFooter = true;
209                     }
210                     break;
211                 }
212             }
213 
214             in.skipBytes(readableBytes - inflater.getRemaining());
215 
216             if (readFooter) {
217                 gzipState = GzipState.FOOTER_START;
218                 if (readGZIPFooter(in)) {
219                     finished = true;
220                 }
221             }
222         } catch (DataFormatException e) {
223             throw new DecompressionException("decompression failure", e);
224         } finally {
225 
226             if (decompressed.isReadable()) {
227                 out.add(decompressed);
228             } else {
229                 decompressed.release();
230             }
231         }
232     }
233 
234     @Override
235     protected void handlerRemoved0(ChannelHandlerContext ctx) throws Exception {
236         super.handlerRemoved0(ctx);
237         if (inflater != null) {
238             inflater.end();
239         }
240     }
241 
242     private boolean readGZIPHeader(ByteBuf in) {
243         switch (gzipState) {
244             case HEADER_START:
245                 if (in.readableBytes() < 10) {
246                     return false;
247                 }
248                 // read magic numbers
249                 int magic0 = in.readByte();
250                 int magic1 = in.readByte();
251 
252                 if (magic0 != 31) {
253                     throw new DecompressionException("Input is not in the GZIP format");
254                 }
255                 crc.update(magic0);
256                 crc.update(magic1);
257 
258                 int method = in.readUnsignedByte();
259                 if (method != Deflater.DEFLATED) {
260                     throw new DecompressionException("Unsupported compression method "
261                             + method + " in the GZIP header");
262                 }
263                 crc.update(method);
264 
265                 flags = in.readUnsignedByte();
266                 crc.update(flags);
267 
268                 if ((flags & FRESERVED) != 0) {
269                     throw new DecompressionException(
270                             "Reserved flags are set in the GZIP header");
271                 }
272 
273                 // mtime (int)
274                 crc.update(in.readByte());
275                 crc.update(in.readByte());
276                 crc.update(in.readByte());
277                 crc.update(in.readByte());
278 
279                 crc.update(in.readUnsignedByte()); // extra flags
280                 crc.update(in.readUnsignedByte()); // operating system
281 
282                 gzipState = GzipState.FLG_READ;
283             case FLG_READ:
284                 if ((flags & FEXTRA) != 0) {
285                     if (in.readableBytes() < 2) {
286                         return false;
287                     }
288                     int xlen1 = in.readUnsignedByte();
289                     int xlen2 = in.readUnsignedByte();
290                     crc.update(xlen1);
291                     crc.update(xlen2);
292 
293                     xlen |= xlen1 << 8 | xlen2;
294                 }
295                 gzipState = GzipState.XLEN_READ;
296             case XLEN_READ:
297                 if (xlen != -1) {
298                     if (in.readableBytes() < xlen) {
299                         return false;
300                     }
301                     byte[] xtra = new byte[xlen];
302                     in.readBytes(xtra);
303                     crc.update(xtra);
304                 }
305                 gzipState = GzipState.SKIP_FNAME;
306             case SKIP_FNAME:
307                 if ((flags & FNAME) != 0) {
308                     if (!in.isReadable()) {
309                         return false;
310                     }
311                     do {
312                         int b = in.readUnsignedByte();
313                         crc.update(b);
314                         if (b == 0x00) {
315                             break;
316                         }
317                     } while (in.isReadable());
318                 }
319                 gzipState = GzipState.SKIP_COMMENT;
320             case SKIP_COMMENT:
321                 if ((flags & FCOMMENT) != 0) {
322                     if (!in.isReadable()) {
323                         return false;
324                     }
325                     do {
326                         int b = in.readUnsignedByte();
327                         crc.update(b);
328                         if (b == 0x00) {
329                             break;
330                         }
331                     } while (in.isReadable());
332                 }
333                 gzipState = GzipState.PROCESS_FHCRC;
334             case PROCESS_FHCRC:
335                 if ((flags & FHCRC) != 0) {
336                     if (in.readableBytes() < 4) {
337                         return false;
338                     }
339                     verifyCrc(in);
340                 }
341                 crc.reset();
342                 gzipState = GzipState.HEADER_END;
343             case HEADER_END:
344                 return true;
345             default:
346                 throw new IllegalStateException();
347         }
348     }
349 
350     private boolean readGZIPFooter(ByteBuf buf) {
351         if (buf.readableBytes() < 8) {
352             return false;
353         }
354 
355         verifyCrc(buf);
356 
357         // read ISIZE and verify
358         int dataLength = 0;
359         for (int i = 0; i < 4; ++i) {
360             dataLength |= buf.readUnsignedByte() << i * 8;
361         }
362         int readLength = inflater.getTotalOut();
363         if (dataLength != readLength) {
364             throw new DecompressionException(
365                     "Number of bytes mismatch. Expected: " + dataLength + ", Got: " + readLength);
366         }
367         return true;
368     }
369 
370     private void verifyCrc(ByteBuf in) {
371         long crcValue = 0;
372         for (int i = 0; i < 4; ++i) {
373             crcValue |= (long) in.readUnsignedByte() << i * 8;
374         }
375         long readCrc = crc.getValue();
376         if (crcValue != readCrc) {
377             throw new DecompressionException(
378                     "CRC value missmatch. Expected: " + crcValue + ", Got: " + readCrc);
379         }
380     }
381 
382     /*
383      * Returns true if the cmf_flg parameter (think: first two bytes of a zlib stream)
384      * indicates that this is a zlib stream.
385      * <p>
386      * You can lookup the details in the ZLIB RFC:
387      * <a href="http://tools.ietf.org/html/rfc1950#section-2.2">RFC 1950</a>.
388      */
389     private static boolean looksLikeZlib(short cmf_flg) {
390         return (cmf_flg & 0x7800) == 0x7800 &&
391                 cmf_flg % 31 == 0;
392     }
393 }