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
29
30
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
55
56
57
58
59
60
61
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
109 ++size;
110 if (size == 4)
111 state = State.XFL;
112 break;
113 }
114 case XFL:
115 {
116
117 state = State.OS;
118 break;
119 }
120 case OS:
121 {
122
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
160 --value;
161 if (value == 0)
162 {
163
164 flags &= ~0x04;
165 state = State.FLAGS;
166 }
167 break;
168 }
169 case NAME:
170 {
171
172 if (currByte == 0)
173 {
174
175 flags &= ~0x08;
176 state = State.FLAGS;
177 }
178 break;
179 }
180 case COMMENT:
181 {
182
183 if (currByte == 0)
184 {
185
186 flags &= ~0x10;
187 state = State.FLAGS;
188 }
189 break;
190 }
191 case HCRC:
192 {
193
194 ++size;
195 if (size == 2)
196 {
197
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
249 output = new byte[decoded];
250 System.arraycopy(bytes, 0, output, 0, decoded);
251 }
252 else
253 {
254
255 byte[] newOutput = new byte[output.length + decoded];
256 System.arraycopy(output, 0, newOutput, 0, output.length);
257 System.arraycopy(bytes, 0, newOutput, output.length, decoded);
258 output = newOutput;
259 }
260 }
261 }
262 break;
263 }
264 case CRC:
265 {
266 value += (currByte & 0xFF) << 8 * size;
267 ++size;
268 if (size == 4)
269 {
270
271 state = State.ISIZE;
272 size = 0;
273 value = 0;
274 }
275 break;
276 }
277 case ISIZE:
278 {
279 value += (currByte & 0xFF) << 8 * size;
280 ++size;
281 if (size == 4)
282 {
283 if (value != inflater.getBytesWritten())
284 throw new ZipException("Invalid input size");
285
286 ByteBuffer result = output == null ? BufferUtil.EMPTY_BUFFER : ByteBuffer.wrap(output);
287 reset();
288 return result;
289 }
290 break;
291 }
292 default:
293 throw new ZipException();
294 }
295 }
296 return BufferUtil.EMPTY_BUFFER;
297 }
298 catch (ZipException x)
299 {
300 throw new RuntimeException(x);
301 }
302 }
303
304 private int inflate(byte[] bytes) throws ZipException
305 {
306 try
307 {
308 return inflater.inflate(bytes);
309 }
310 catch (DataFormatException x)
311 {
312 throw new ZipException(x.getMessage());
313 }
314 }
315
316 private void reset()
317 {
318 inflater.reset();
319 Arrays.fill(bytes, (byte)0);
320 output = null;
321 state = State.INITIAL;
322 size = 0;
323 value = 0;
324 flags = 0;
325 }
326
327 protected boolean isFinished()
328 {
329 return state == State.INITIAL;
330 }
331
332
333
334
335 public static class Factory extends ContentDecoder.Factory
336 {
337 private final int bufferSize;
338
339 public Factory()
340 {
341 this(2048);
342 }
343
344 public Factory(int bufferSize)
345 {
346 super("gzip");
347 this.bufferSize = bufferSize;
348 }
349
350 @Override
351 public ContentDecoder newContentDecoder()
352 {
353 return new GZIPContentDecoder(bufferSize);
354 }
355 }
356
357 private enum State
358 {
359 INITIAL, ID, CM, FLG, MTIME, XFL, OS, FLAGS, EXTRA_LENGTH, EXTRA, NAME, COMMENT, HCRC, DATA, CRC, ISIZE
360 }
361 }