1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
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
41 private final CRC32 crc;
42 private final boolean decompressConcatenated;
43
44 private enum GzipState {
45 HEADER_START,
46 HEADER_END,
47 FLG_READ,
48 XLEN_READ,
49 SKIP_FNAME,
50 SKIP_COMMENT,
51 PROCESS_FHCRC,
52 FOOTER_START,
53 }
54
55 private GzipState gzipState = GzipState.HEADER_START;
56 private int flags = -1;
57 private int xlen = -1;
58
59 private volatile boolean finished;
60
61 private boolean decideZlibOrNone;
62
63
64
65
66 public JdkZlibDecoder() {
67 this(ZlibWrapper.ZLIB, null, false);
68 }
69
70
71
72
73
74
75 public JdkZlibDecoder(byte[] dictionary) {
76 this(ZlibWrapper.ZLIB, dictionary, false);
77 }
78
79
80
81
82
83
84 public JdkZlibDecoder(ZlibWrapper wrapper) {
85 this(wrapper, null, false);
86 }
87
88 public JdkZlibDecoder(ZlibWrapper wrapper, boolean decompressConcatenated) {
89 this(wrapper, null, decompressConcatenated);
90 }
91
92 public JdkZlibDecoder(boolean decompressConcatenated) {
93 this(ZlibWrapper.GZIP, null, decompressConcatenated);
94 }
95
96 private JdkZlibDecoder(ZlibWrapper wrapper, byte[] dictionary, boolean decompressConcatenated) {
97 if (wrapper == null) {
98 throw new NullPointerException("wrapper");
99 }
100 this.decompressConcatenated = decompressConcatenated;
101 switch (wrapper) {
102 case GZIP:
103 inflater = new Inflater(true);
104 crc = new CRC32();
105 break;
106 case NONE:
107 inflater = new Inflater(true);
108 crc = null;
109 break;
110 case ZLIB:
111 inflater = new Inflater();
112 crc = null;
113 break;
114 case ZLIB_OR_NONE:
115
116 decideZlibOrNone = true;
117 crc = null;
118 break;
119 default:
120 throw new IllegalArgumentException("Only GZIP or ZLIB is supported, but you used " + wrapper);
121 }
122 this.dictionary = dictionary;
123 }
124
125 @Override
126 public boolean isClosed() {
127 return finished;
128 }
129
130 @Override
131 protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
132 if (finished) {
133
134 in.skipBytes(in.readableBytes());
135 return;
136 }
137
138 int readableBytes = in.readableBytes();
139 if (readableBytes == 0) {
140 return;
141 }
142
143 if (decideZlibOrNone) {
144
145 if (readableBytes < 2) {
146 return;
147 }
148
149 boolean nowrap = !looksLikeZlib(in.getShort(in.readerIndex()));
150 inflater = new Inflater(nowrap);
151 decideZlibOrNone = false;
152 }
153
154 if (crc != null) {
155 switch (gzipState) {
156 case FOOTER_START:
157 if (readGZIPFooter(in)) {
158 finished = true;
159 }
160 return;
161 default:
162 if (gzipState != GzipState.HEADER_END) {
163 if (!readGZIPHeader(in)) {
164 return;
165 }
166 }
167 }
168
169 readableBytes = in.readableBytes();
170 }
171
172 if (in.hasArray()) {
173 inflater.setInput(in.array(), in.arrayOffset() + in.readerIndex(), readableBytes);
174 } else {
175 byte[] array = new byte[readableBytes];
176 in.getBytes(in.readerIndex(), array);
177 inflater.setInput(array);
178 }
179
180 ByteBuf decompressed = ctx.alloc().heapBuffer(inflater.getRemaining() << 1);
181 try {
182 boolean readFooter = false;
183 while (!inflater.needsInput()) {
184 byte[] outArray = decompressed.array();
185 int writerIndex = decompressed.writerIndex();
186 int outIndex = decompressed.arrayOffset() + writerIndex;
187 int outputLength = inflater.inflate(outArray, outIndex, decompressed.writableBytes());
188 if (outputLength > 0) {
189 decompressed.writerIndex(writerIndex + outputLength);
190 if (crc != null) {
191 crc.update(outArray, outIndex, outputLength);
192 }
193 } else {
194 if (inflater.needsDictionary()) {
195 if (dictionary == null) {
196 throw new DecompressionException(
197 "decompression failure, unable to set dictionary as non was specified");
198 }
199 inflater.setDictionary(dictionary);
200 }
201 }
202
203 if (inflater.finished()) {
204 if (crc == null) {
205 finished = true;
206 } else {
207 readFooter = true;
208 }
209 break;
210 } else {
211 decompressed.ensureWritable(inflater.getRemaining() << 1);
212 }
213 }
214
215 in.skipBytes(readableBytes - inflater.getRemaining());
216
217 if (readFooter) {
218 gzipState = GzipState.FOOTER_START;
219 if (readGZIPFooter(in)) {
220 finished = !decompressConcatenated;
221
222 if (!finished) {
223 inflater.reset();
224 crc.reset();
225 gzipState = GzipState.HEADER_START;
226 }
227 }
228 }
229 } catch (DataFormatException e) {
230 throw new DecompressionException("decompression failure", e);
231 } finally {
232
233 if (decompressed.isReadable()) {
234 out.add(decompressed);
235 } else {
236 decompressed.release();
237 }
238 }
239 }
240
241 @Override
242 protected void handlerRemoved0(ChannelHandlerContext ctx) throws Exception {
243 super.handlerRemoved0(ctx);
244 if (inflater != null) {
245 inflater.end();
246 }
247 }
248
249 private boolean readGZIPHeader(ByteBuf in) {
250 switch (gzipState) {
251 case HEADER_START:
252 if (in.readableBytes() < 10) {
253 return false;
254 }
255
256 int magic0 = in.readByte();
257 int magic1 = in.readByte();
258
259 if (magic0 != 31) {
260 throw new DecompressionException("Input is not in the GZIP format");
261 }
262 crc.update(magic0);
263 crc.update(magic1);
264
265 int method = in.readUnsignedByte();
266 if (method != Deflater.DEFLATED) {
267 throw new DecompressionException("Unsupported compression method "
268 + method + " in the GZIP header");
269 }
270 crc.update(method);
271
272 flags = in.readUnsignedByte();
273 crc.update(flags);
274
275 if ((flags & FRESERVED) != 0) {
276 throw new DecompressionException(
277 "Reserved flags are set in the GZIP header");
278 }
279
280
281 crc.update(in.readByte());
282 crc.update(in.readByte());
283 crc.update(in.readByte());
284 crc.update(in.readByte());
285
286 crc.update(in.readUnsignedByte());
287 crc.update(in.readUnsignedByte());
288
289 gzipState = GzipState.FLG_READ;
290
291 case FLG_READ:
292 if ((flags & FEXTRA) != 0) {
293 if (in.readableBytes() < 2) {
294 return false;
295 }
296 int xlen1 = in.readUnsignedByte();
297 int xlen2 = in.readUnsignedByte();
298 crc.update(xlen1);
299 crc.update(xlen2);
300
301 xlen |= xlen1 << 8 | xlen2;
302 }
303 gzipState = GzipState.XLEN_READ;
304
305 case XLEN_READ:
306 if (xlen != -1) {
307 if (in.readableBytes() < xlen) {
308 return false;
309 }
310 byte[] xtra = new byte[xlen];
311 in.readBytes(xtra);
312 crc.update(xtra);
313 }
314 gzipState = GzipState.SKIP_FNAME;
315
316 case SKIP_FNAME:
317 if ((flags & FNAME) != 0) {
318 if (!in.isReadable()) {
319 return false;
320 }
321 do {
322 int b = in.readUnsignedByte();
323 crc.update(b);
324 if (b == 0x00) {
325 break;
326 }
327 } while (in.isReadable());
328 }
329 gzipState = GzipState.SKIP_COMMENT;
330
331 case SKIP_COMMENT:
332 if ((flags & FCOMMENT) != 0) {
333 if (!in.isReadable()) {
334 return false;
335 }
336 do {
337 int b = in.readUnsignedByte();
338 crc.update(b);
339 if (b == 0x00) {
340 break;
341 }
342 } while (in.isReadable());
343 }
344 gzipState = GzipState.PROCESS_FHCRC;
345
346 case PROCESS_FHCRC:
347 if ((flags & FHCRC) != 0) {
348 if (in.readableBytes() < 4) {
349 return false;
350 }
351 verifyCrc(in);
352 }
353 crc.reset();
354 gzipState = GzipState.HEADER_END;
355
356 case HEADER_END:
357 return true;
358 default:
359 throw new IllegalStateException();
360 }
361 }
362
363 private boolean readGZIPFooter(ByteBuf buf) {
364 if (buf.readableBytes() < 8) {
365 return false;
366 }
367
368 verifyCrc(buf);
369
370
371 int dataLength = 0;
372 for (int i = 0; i < 4; ++i) {
373 dataLength |= buf.readUnsignedByte() << i * 8;
374 }
375 int readLength = inflater.getTotalOut();
376 if (dataLength != readLength) {
377 throw new DecompressionException(
378 "Number of bytes mismatch. Expected: " + dataLength + ", Got: " + readLength);
379 }
380 return true;
381 }
382
383 private void verifyCrc(ByteBuf in) {
384 long crcValue = 0;
385 for (int i = 0; i < 4; ++i) {
386 crcValue |= (long) in.readUnsignedByte() << i * 8;
387 }
388 long readCrc = crc.getValue();
389 if (crcValue != readCrc) {
390 throw new DecompressionException(
391 "CRC value mismatch. Expected: " + crcValue + ", Got: " + readCrc);
392 }
393 }
394
395
396
397
398
399
400
401
402 private static boolean looksLikeZlib(short cmf_flg) {
403 return (cmf_flg & 0x7800) == 0x7800 &&
404 cmf_flg % 31 == 0;
405 }
406 }