1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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 import org.eclipse.jetty.util.component.Destroyable;
29
30
31
32
33 public class GZIPContentDecoder implements ContentDecoder, Destroyable
34 {
35 private final Inflater inflater = new Inflater(true);
36 private final byte[] bytes;
37 private byte[] output;
38 private State state;
39 private int size;
40 private int value;
41 private byte flags;
42
43 public GZIPContentDecoder()
44 {
45 this(2048);
46 }
47
48 public GZIPContentDecoder(int bufferSize)
49 {
50 this.bytes = new byte[bufferSize];
51 reset();
52 }
53
54
55
56
57
58
59
60
61
62
63
64 @Override
65 public ByteBuffer decode(ByteBuffer buffer)
66 {
67 try
68 {
69 while (buffer.hasRemaining())
70 {
71 byte currByte = buffer.get();
72 switch (state)
73 {
74 case INITIAL:
75 {
76 buffer.position(buffer.position() - 1);
77 state = State.ID;
78 break;
79 }
80 case ID:
81 {
82 value += (currByte & 0xFF) << 8 * size;
83 ++size;
84 if (size == 2)
85 {
86 if (value != 0x8B1F)
87 throw new ZipException("Invalid gzip bytes");
88 state = State.CM;
89 }
90 break;
91 }
92 case CM:
93 {
94 if ((currByte & 0xFF) != 0x08)
95 throw new ZipException("Invalid gzip compression method");
96 state = State.FLG;
97 break;
98 }
99 case FLG:
100 {
101 flags = currByte;
102 state = State.MTIME;
103 size = 0;
104 value = 0;
105 break;
106 }
107 case MTIME:
108 {
109
110 ++size;
111 if (size == 4)
112 state = State.XFL;
113 break;
114 }
115 case XFL:
116 {
117
118 state = State.OS;
119 break;
120 }
121 case OS:
122 {
123
124 state = State.FLAGS;
125 break;
126 }
127 case FLAGS:
128 {
129 buffer.position(buffer.position() - 1);
130 if ((flags & 0x04) == 0x04)
131 {
132 state = State.EXTRA_LENGTH;
133 size = 0;
134 value = 0;
135 }
136 else if ((flags & 0x08) == 0x08)
137 state = State.NAME;
138 else if ((flags & 0x10) == 0x10)
139 state = State.COMMENT;
140 else if ((flags & 0x2) == 0x2)
141 {
142 state = State.HCRC;
143 size = 0;
144 value = 0;
145 }
146 else
147 state = State.DATA;
148 break;
149 }
150 case EXTRA_LENGTH:
151 {
152 value += (currByte & 0xFF) << 8 * size;
153 ++size;
154 if (size == 2)
155 state = State.EXTRA;
156 break;
157 }
158 case EXTRA:
159 {
160
161 --value;
162 if (value == 0)
163 {
164
165 flags &= ~0x04;
166 state = State.FLAGS;
167 }
168 break;
169 }
170 case NAME:
171 {
172
173 if (currByte == 0)
174 {
175
176 flags &= ~0x08;
177 state = State.FLAGS;
178 }
179 break;
180 }
181 case COMMENT:
182 {
183
184 if (currByte == 0)
185 {
186
187 flags &= ~0x10;
188 state = State.FLAGS;
189 }
190 break;
191 }
192 case HCRC:
193 {
194
195 ++size;
196 if (size == 2)
197 {
198
199 flags &= ~0x02;
200 state = State.FLAGS;
201 }
202 break;
203 }
204 case DATA:
205 {
206 buffer.position(buffer.position() - 1);
207 while (true)
208 {
209 int decoded = inflate(bytes);
210 if (decoded == 0)
211 {
212 if (inflater.needsInput())
213 {
214 if (buffer.hasRemaining())
215 {
216 byte[] input = new byte[buffer.remaining()];
217 buffer.get(input);
218 inflater.setInput(input);
219 }
220 else
221 {
222 if (output != null)
223 {
224 ByteBuffer result = ByteBuffer.wrap(output);
225 output = null;
226 return result;
227 }
228 break;
229 }
230 }
231 else if (inflater.finished())
232 {
233 int remaining = inflater.getRemaining();
234 buffer.position(buffer.limit() - remaining);
235 state = State.CRC;
236 size = 0;
237 value = 0;
238 break;
239 }
240 else
241 {
242 throw new ZipException("Invalid inflater state");
243 }
244 }
245 else
246 {
247 if (output == null)
248 {
249
250 output = Arrays.copyOf(bytes, decoded);
251 }
252 else
253 {
254
255 byte[] newOutput = Arrays.copyOf(output, output.length + decoded);
256 System.arraycopy(bytes, 0, newOutput, output.length, decoded);
257 output = newOutput;
258 }
259 }
260 }
261 break;
262 }
263 case CRC:
264 {
265 value += (currByte & 0xFF) << 8 * size;
266 ++size;
267 if (size == 4)
268 {
269
270 state = State.ISIZE;
271 size = 0;
272 value = 0;
273 }
274 break;
275 }
276 case ISIZE:
277 {
278 value += (currByte & 0xFF) << 8 * size;
279 ++size;
280 if (size == 4)
281 {
282 if (value != inflater.getBytesWritten())
283 throw new ZipException("Invalid input size");
284
285 ByteBuffer result = output == null ? BufferUtil.EMPTY_BUFFER : ByteBuffer.wrap(output);
286 reset();
287 return result;
288 }
289 break;
290 }
291 default:
292 throw new ZipException();
293 }
294 }
295 return BufferUtil.EMPTY_BUFFER;
296 }
297 catch (ZipException x)
298 {
299 throw new RuntimeException(x);
300 }
301 }
302
303 private int inflate(byte[] bytes) throws ZipException
304 {
305 try
306 {
307 return inflater.inflate(bytes);
308 }
309 catch (DataFormatException x)
310 {
311 throw new ZipException(x.getMessage());
312 }
313 }
314
315 private void reset()
316 {
317 inflater.reset();
318 Arrays.fill(bytes, (byte)0);
319 output = null;
320 state = State.INITIAL;
321 size = 0;
322 value = 0;
323 flags = 0;
324 }
325
326 @Override
327 public void destroy()
328 {
329 inflater.end();
330 }
331
332 protected boolean isFinished()
333 {
334 return state == State.INITIAL;
335 }
336
337
338
339
340 public static class Factory extends ContentDecoder.Factory
341 {
342 private final int bufferSize;
343
344 public Factory()
345 {
346 this(2048);
347 }
348
349 public Factory(int bufferSize)
350 {
351 super("gzip");
352 this.bufferSize = bufferSize;
353 }
354
355 @Override
356 public ContentDecoder newContentDecoder()
357 {
358 return new GZIPContentDecoder(bufferSize);
359 }
360 }
361
362 private enum State
363 {
364 INITIAL, ID, CM, FLG, MTIME, XFL, OS, FLAGS, EXTRA_LENGTH, EXTRA, NAME, COMMENT, HCRC, DATA, CRC, ISIZE
365 }
366 }