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.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     private Inflater inflater;
40     private final byte[] dictionary;
41
42     // GZIP related
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     /**
66      * Creates a new instance with the default wrapper ({@link ZlibWrapper#ZLIB}).
67      */

68     public JdkZlibDecoder() {
69         this(ZlibWrapper.ZLIB, nullfalse, 0);
70     }
71
72     /**
73      * Creates a new instance with the default wrapper ({@link ZlibWrapper#ZLIB})
74      * and the specified maximum buffer allocation.
75      *
76      * @param maxAllocation
77      *          Maximum size of the decompression buffer. Must be >= 0.
78      *          If zero, maximum size is decided by the {@link ByteBufAllocator}.
79      */

80     public JdkZlibDecoder(int maxAllocation) {
81         this(ZlibWrapper.ZLIB, nullfalse, maxAllocation);
82     }
83
84     /**
85      * Creates a new instance with the specified preset dictionary. The wrapper
86      * is always {@link ZlibWrapper#ZLIB} because it is the only format that
87      * supports the preset dictionary.
88      */

89     public JdkZlibDecoder(byte[] dictionary) {
90         this(ZlibWrapper.ZLIB, dictionary, false, 0);
91     }
92
93     /**
94      * Creates a new instance with the specified preset dictionary and maximum buffer allocation.
95      * The wrapper is always {@link ZlibWrapper#ZLIB} because it is the only format that
96      * supports the preset dictionary.
97      *
98      * @param maxAllocation
99      *          Maximum size of the decompression buffer. Must be >= 0.
100      *          If zero, maximum size is decided by the {@link ByteBufAllocator}.
101      */

102     public JdkZlibDecoder(byte[] dictionary, int maxAllocation) {
103         this(ZlibWrapper.ZLIB, dictionary, false, maxAllocation);
104     }
105
106     /**
107      * Creates a new instance with the specified wrapper.
108      * Be aware that only {@link ZlibWrapper#GZIP}, {@link ZlibWrapper#ZLIB} and {@link ZlibWrapper#NONE} are
109      * supported atm.
110      */

111     public JdkZlibDecoder(ZlibWrapper wrapper) {
112         this(wrapper, nullfalse, 0);
113     }
114
115     /**
116      * Creates a new instance with the specified wrapper and maximum buffer allocation.
117      * Be aware that only {@link ZlibWrapper#GZIP}, {@link ZlibWrapper#ZLIB} and {@link ZlibWrapper#NONE} are
118      * supported atm.
119      *
120      * @param maxAllocation
121      *          Maximum size of the decompression buffer. Must be >= 0.
122      *          If zero, maximum size is decided by the {@link ByteBufAllocator}.
123      */

124     public JdkZlibDecoder(ZlibWrapper wrapper, int maxAllocation) {
125         this(wrapper, nullfalse, 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                 // Postpone the decision until decode(...) is called.
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             // Skip data received after finished.
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             // First two bytes are needed to decide if it's a ZLIB stream.
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             // Some bytes may have been consumed, and so we must re-set the number of readable bytes.
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// Do not decode anymore.
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                 // read magic numbers
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                 // mtime (int)
335                 crc.update(in, in.readerIndex(), 4);
336                 in.skipBytes(4);
337
338                 crc.update(in.readUnsignedByte()); // extra flags
339                 crc.update(in.readUnsignedByte()); // operating system
340
341                 gzipState = GzipState.FLG_READ;
342                 // fall through
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                 // fall through
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                 // fall through
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                 // fall through
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                 // fall through
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                 // fall through
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         // read ISIZE and verify
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     /*
447      * Returns true if the cmf_flg parameter (think: first two bytes of a zlib stream)
448      * indicates that this is a zlib stream.
449      * <p>
450      * You can lookup the details in the ZLIB RFC:
451      * <a href="http://tools.ietf.org/html/rfc1950#section-2.2">RFC 1950</a>.
452      */

453     private static boolean looksLikeZlib(short cmf_flg) {
454         return (cmf_flg & 0x7800) == 0x7800 &&
455                 cmf_flg % 31 == 0;
456     }
457 }
458