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 = Arrays.copyOf(bytes, decoded);
250 }
251 else
252 {
253
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
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
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 }