1 //
2 // ========================================================================
3 // Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
4 // ------------------------------------------------------------------------
5 // All rights reserved. This program and the accompanying materials
6 // are made available under the terms of the Eclipse Public License v1.0
7 // and Apache License v2.0 which accompanies this distribution.
8 //
9 // The Eclipse Public License is available at
10 // http://www.eclipse.org/legal/epl-v10.html
11 //
12 // The Apache License v2.0 is available at
13 // http://www.opensource.org/licenses/apache2.0.php
14 //
15 // You may elect to redistribute this code under either of these licenses.
16 // ========================================================================
17 //
18
19 package org.eclipse.jetty.client;
20
21 import java.nio.ByteBuffer;
22 import java.util.Arrays;
23 import java.util.zip.DataFormatException;
24 import java.util.zip.Inflater;
25 import java.util.zip.ZipException;
26
27 import org.eclipse.jetty.util.BufferUtil;
28
29 /**
30 * {@link ContentDecoder} for the "gzip" encoding.
31 */
32 public class GZIPContentDecoder implements ContentDecoder
33 {
34 private final Inflater inflater = new Inflater(true);
35 private final byte[] bytes;
36 private byte[] output;
37 private State state;
38 private int size;
39 private int value;
40 private byte flags;
41
42 public GZIPContentDecoder()
43 {
44 this(2048);
45 }
46
47 public GZIPContentDecoder(int bufferSize)
48 {
49 this.bytes = new byte[bufferSize];
50 reset();
51 }
52
53 /**
54 * {@inheritDoc}
55 * <p>If the decoding did not produce any output, for example because it consumed gzip header
56 * or trailer bytes, it returns a buffer with zero capacity.</p>
57 * <p>This method never returns null.</p>
58 * <p>The given {@code buffer}'s position will be modified to reflect the bytes consumed during
59 * the decoding.</p>
60 * <p>The decoding may be finished without consuming the buffer completely if the buffer contains
61 * gzip bytes plus other bytes (either plain or gzipped).</p>
62 */
63 @Override
64 public ByteBuffer decode(ByteBuffer buffer)
65 {
66 try
67 {
68 while (buffer.hasRemaining())
69 {
70 byte currByte = buffer.get();
71 switch (state)
72 {
73 case INITIAL:
74 {
75 buffer.position(buffer.position() - 1);
76 state = State.ID;
77 break;
78 }
79 case ID:
80 {
81 value += (currByte & 0xFF) << 8 * size;
82 ++size;
83 if (size == 2)
84 {
85 if (value != 0x8B1F)
86 throw new ZipException("Invalid gzip bytes");
87 state = State.CM;
88 }
89 break;
90 }
91 case CM:
92 {
93 if ((currByte & 0xFF) != 0x08)
94 throw new ZipException("Invalid gzip compression method");
95 state = State.FLG;
96 break;
97 }
98 case FLG:
99 {
100 flags = currByte;
101 state = State.MTIME;
102 size = 0;
103 value = 0;
104 break;
105 }
106 case MTIME:
107 {
108 // Skip the 4 MTIME bytes
109 ++size;
110 if (size == 4)
111 state = State.XFL;
112 break;
113 }
114 case XFL:
115 {
116 // Skip XFL
117 state = State.OS;
118 break;
119 }
120 case OS:
121 {
122 // Skip OS
123 state = State.FLAGS;
124 break;
125 }
126 case FLAGS:
127 {
128 buffer.position(buffer.position() - 1);
129 if ((flags & 0x04) == 0x04)
130 {
131 state = State.EXTRA_LENGTH;
132 size = 0;
133 value = 0;
134 }
135 else if ((flags & 0x08) == 0x08)
136 state = State.NAME;
137 else if ((flags & 0x10) == 0x10)
138 state = State.COMMENT;
139 else if ((flags & 0x2) == 0x2)
140 {
141 state = State.HCRC;
142 size = 0;
143 value = 0;
144 }
145 else
146 state = State.DATA;
147 break;
148 }
149 case EXTRA_LENGTH:
150 {
151 value += (currByte & 0xFF) << 8 * size;
152 ++size;
153 if (size == 2)
154 state = State.EXTRA;
155 break;
156 }
157 case EXTRA:
158 {
159 // Skip EXTRA bytes
160 --value;
161 if (value == 0)
162 {
163 // Clear the EXTRA flag and loop on the flags
164 flags &= ~0x04;
165 state = State.FLAGS;
166 }
167 break;
168 }
169 case NAME:
170 {
171 // Skip NAME bytes
172 if (currByte == 0)
173 {
174 // Clear the NAME flag and loop on the flags
175 flags &= ~0x08;
176 state = State.FLAGS;
177 }
178 break;
179 }
180 case COMMENT:
181 {
182 // Skip COMMENT bytes
183 if (currByte == 0)
184 {
185 // Clear the COMMENT flag and loop on the flags
186 flags &= ~0x10;
187 state = State.FLAGS;
188 }
189 break;
190 }
191 case HCRC:
192 {
193 // Skip HCRC
194 ++size;
195 if (size == 2)
196 {
197 // Clear the HCRC flag and loop on the flags
198 flags &= ~0x02;
199 state = State.FLAGS;
200 }
201 break;
202 }
203 case DATA:
204 {
205 buffer.position(buffer.position() - 1);
206 while (true)
207 {
208 int decoded = inflate(bytes);
209 if (decoded == 0)
210 {
211 if (inflater.needsInput())
212 {
213 if (buffer.hasRemaining())
214 {
215 byte[] input = new byte[buffer.remaining()];
216 buffer.get(input);
217 inflater.setInput(input);
218 }
219 else
220 {
221 if (output != null)
222 {
223 ByteBuffer result = ByteBuffer.wrap(output);
224 output = null;
225 return result;
226 }
227 break;
228 }
229 }
230 else if (inflater.finished())
231 {
232 int remaining = inflater.getRemaining();
233 buffer.position(buffer.limit() - remaining);
234 state = State.CRC;
235 size = 0;
236 value = 0;
237 break;
238 }
239 else
240 {
241 throw new ZipException("Invalid inflater state");
242 }
243 }
244 else
245 {
246 if (output == null)
247 {
248 // Save the inflated bytes and loop to see if we have finished
249 output = Arrays.copyOf(bytes, decoded);
250 }
251 else
252 {
253 // Accumulate inflated bytes and loop to see if we have finished
254 byte[] newOutput = Arrays.copyOf(output, output.length + decoded);
255 System.arraycopy(bytes, 0, newOutput, output.length, decoded);
256 output = newOutput;
257 }
258 }
259 }
260 break;
261 }
262 case CRC:
263 {
264 value += (currByte & 0xFF) << 8 * size;
265 ++size;
266 if (size == 4)
267 {
268 // From RFC 1952, compliant decoders need not to verify the CRC
269 state = State.ISIZE;
270 size = 0;
271 value = 0;
272 }
273 break;
274 }
275 case ISIZE:
276 {
277 value += (currByte & 0xFF) << 8 * size;
278 ++size;
279 if (size == 4)
280 {
281 if (value != inflater.getBytesWritten())
282 throw new ZipException("Invalid input size");
283
284 ByteBuffer result = output == null ? BufferUtil.EMPTY_BUFFER : ByteBuffer.wrap(output);
285 reset();
286 return result;
287 }
288 break;
289 }
290 default:
291 throw new ZipException();
292 }
293 }
294 return BufferUtil.EMPTY_BUFFER;
295 }
296 catch (ZipException x)
297 {
298 throw new RuntimeException(x);
299 }
300 }
301
302 private int inflate(byte[] bytes) throws ZipException
303 {
304 try
305 {
306 return inflater.inflate(bytes);
307 }
308 catch (DataFormatException x)
309 {
310 throw new ZipException(x.getMessage());
311 }
312 }
313
314 private void reset()
315 {
316 inflater.reset();
317 Arrays.fill(bytes, (byte)0);
318 output = null;
319 state = State.INITIAL;
320 size = 0;
321 value = 0;
322 flags = 0;
323 }
324
325 protected boolean isFinished()
326 {
327 return state == State.INITIAL;
328 }
329
330 /**
331 * Specialized {@link ContentDecoder.Factory} for the "gzip" encoding.
332 */
333 public static class Factory extends ContentDecoder.Factory
334 {
335 private final int bufferSize;
336
337 public Factory()
338 {
339 this(2048);
340 }
341
342 public Factory(int bufferSize)
343 {
344 super("gzip");
345 this.bufferSize = bufferSize;
346 }
347
348 @Override
349 public ContentDecoder newContentDecoder()
350 {
351 return new GZIPContentDecoder(bufferSize);
352 }
353 }
354
355 private enum State
356 {
357 INITIAL, ID, CM, FLG, MTIME, XFL, OS, FLAGS, EXTRA_LENGTH, EXTRA, NAME, COMMENT, HCRC, DATA, CRC, ISIZE
358 }
359 }