1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.jboss.netty.handler.codec.http.multipart;
17
18 import org.jboss.netty.buffer.ChannelBuffer;
19 import org.jboss.netty.buffer.ChannelBuffers;
20 import org.jboss.netty.handler.codec.http.HttpChunk;
21 import org.jboss.netty.handler.codec.http.HttpConstants;
22 import org.jboss.netty.handler.codec.http.HttpRequest;
23 import org.jboss.netty.handler.codec.http.multipart.HttpPostBodyUtil.SeekAheadNoBackArrayException;
24 import org.jboss.netty.handler.codec.http.multipart.HttpPostBodyUtil.SeekAheadOptimize;
25 import org.jboss.netty.handler.codec.http.multipart.HttpPostRequestDecoder.EndOfDataDecoderException;
26 import org.jboss.netty.handler.codec.http.multipart.HttpPostRequestDecoder.ErrorDataDecoderException;
27 import org.jboss.netty.handler.codec.http.multipart.HttpPostRequestDecoder.IncompatibleDataDecoderException;
28 import org.jboss.netty.handler.codec.http.multipart.HttpPostRequestDecoder.MultiPartStatus;
29 import org.jboss.netty.handler.codec.http.multipart.HttpPostRequestDecoder.NotEnoughDataDecoderException;
30 import org.jboss.netty.util.internal.CaseIgnoringComparator;
31
32 import java.io.IOException;
33 import java.io.UnsupportedEncodingException;
34 import java.net.URLDecoder;
35 import java.nio.charset.Charset;
36 import java.util.ArrayList;
37 import java.util.List;
38 import java.util.Map;
39 import java.util.TreeMap;
40
41
42
43
44 @SuppressWarnings({ "deprecation", "RedundantThrowsDeclaration" })
45 public class HttpPostStandardRequestDecoder implements InterfaceHttpPostRequestDecoder {
46
47
48
49 private final HttpDataFactory factory;
50
51
52
53
54 private final HttpRequest request;
55
56
57
58
59 private final Charset charset;
60
61
62
63
64 private boolean isLastChunk;
65
66
67
68
69 private final List<InterfaceHttpData> bodyListHttpData = new ArrayList<InterfaceHttpData>();
70
71
72
73
74 private final Map<String, List<InterfaceHttpData>> bodyMapHttpData = new TreeMap<String, List<InterfaceHttpData>>(
75 CaseIgnoringComparator.INSTANCE);
76
77
78
79
80 private ChannelBuffer undecodedChunk;
81
82
83
84
85 private int bodyListHttpDataRank;
86
87
88
89
90 private MultiPartStatus currentStatus = MultiPartStatus.NOTSTARTED;
91
92
93
94
95 private Attribute currentAttribute;
96
97
98
99
100
101
102
103
104 public HttpPostStandardRequestDecoder(HttpRequest request)
105 throws ErrorDataDecoderException, IncompatibleDataDecoderException {
106 this(new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE),
107 request, HttpConstants.DEFAULT_CHARSET);
108 }
109
110
111
112
113
114
115
116
117
118 public HttpPostStandardRequestDecoder(HttpDataFactory factory, HttpRequest request)
119 throws ErrorDataDecoderException, IncompatibleDataDecoderException {
120 this(factory, request, HttpConstants.DEFAULT_CHARSET);
121 }
122
123
124
125
126
127
128
129
130
131
132 public HttpPostStandardRequestDecoder(HttpDataFactory factory, HttpRequest request,
133 Charset charset) throws ErrorDataDecoderException,
134 IncompatibleDataDecoderException {
135 if (factory == null) {
136 throw new NullPointerException("factory");
137 }
138 if (request == null) {
139 throw new NullPointerException("request");
140 }
141 if (charset == null) {
142 throw new NullPointerException("charset");
143 }
144 this.request = request;
145 this.charset = charset;
146 this.factory = factory;
147 if (!this.request.isChunked()) {
148 undecodedChunk = this.request.getContent();
149 isLastChunk = true;
150 parseBody();
151 }
152 }
153
154 public boolean isMultipart() {
155 return false;
156 }
157
158 public List<InterfaceHttpData> getBodyHttpDatas()
159 throws NotEnoughDataDecoderException {
160 if (!isLastChunk) {
161 throw new NotEnoughDataDecoderException();
162 }
163 return bodyListHttpData;
164 }
165
166 public List<InterfaceHttpData> getBodyHttpDatas(String name)
167 throws NotEnoughDataDecoderException {
168 if (!isLastChunk) {
169 throw new NotEnoughDataDecoderException();
170 }
171 return bodyMapHttpData.get(name);
172 }
173
174 public InterfaceHttpData getBodyHttpData(String name)
175 throws NotEnoughDataDecoderException {
176 if (!isLastChunk) {
177 throw new NotEnoughDataDecoderException();
178 }
179 List<InterfaceHttpData> list = bodyMapHttpData.get(name);
180 if (list != null) {
181 return list.get(0);
182 }
183 return null;
184 }
185
186 public void offer(HttpChunk chunk) throws ErrorDataDecoderException {
187 ChannelBuffer chunked = chunk.getContent();
188 if (undecodedChunk == null) {
189 undecodedChunk = chunked;
190 } else {
191
192
193 undecodedChunk = ChannelBuffers.wrappedBuffer(
194 undecodedChunk, chunked);
195 }
196 if (chunk.isLast()) {
197 isLastChunk = true;
198 }
199 parseBody();
200 }
201
202 public boolean hasNext() throws EndOfDataDecoderException {
203 if (currentStatus == MultiPartStatus.EPILOGUE) {
204
205 if (bodyListHttpDataRank >= bodyListHttpData.size()) {
206 throw new EndOfDataDecoderException();
207 }
208 }
209 return !bodyListHttpData.isEmpty() && bodyListHttpDataRank < bodyListHttpData.size();
210 }
211
212 public InterfaceHttpData next() throws EndOfDataDecoderException {
213 if (hasNext()) {
214 return bodyListHttpData.get(bodyListHttpDataRank++);
215 }
216 return null;
217 }
218
219
220
221
222
223
224 private void parseBody() throws ErrorDataDecoderException {
225 if (currentStatus == MultiPartStatus.PREEPILOGUE ||
226 currentStatus == MultiPartStatus.EPILOGUE) {
227 if (isLastChunk) {
228 currentStatus = MultiPartStatus.EPILOGUE;
229 }
230 return;
231 }
232 parseBodyAttributes();
233 }
234
235
236
237
238 private void addHttpData(InterfaceHttpData data) {
239 if (data == null) {
240 return;
241 }
242 List<InterfaceHttpData> datas = bodyMapHttpData.get(data.getName());
243 if (datas == null) {
244 datas = new ArrayList<InterfaceHttpData>(1);
245 bodyMapHttpData.put(data.getName(), datas);
246 }
247 datas.add(data);
248 bodyListHttpData.add(data);
249 }
250
251
252
253
254
255
256
257
258 private void parseBodyAttributesStandard() throws ErrorDataDecoderException {
259 int firstpos = undecodedChunk.readerIndex();
260 int currentpos = firstpos;
261 int equalpos;
262 int ampersandpos;
263 if (currentStatus == MultiPartStatus.NOTSTARTED) {
264 currentStatus = MultiPartStatus.DISPOSITION;
265 }
266 boolean contRead = true;
267 try {
268 while (undecodedChunk.readable() && contRead) {
269 char read = (char) undecodedChunk.readUnsignedByte();
270 currentpos++;
271 switch (currentStatus) {
272 case DISPOSITION:
273 if (read == '=') {
274 currentStatus = MultiPartStatus.FIELD;
275 equalpos = currentpos - 1;
276 String key = decodeAttribute(
277 undecodedChunk.toString(firstpos, equalpos - firstpos, charset),
278 charset);
279 currentAttribute = factory.createAttribute(request, key);
280 firstpos = currentpos;
281 } else if (read == '&') {
282 currentStatus = MultiPartStatus.DISPOSITION;
283 ampersandpos = currentpos - 1;
284 String key = decodeAttribute(
285 undecodedChunk.toString(firstpos, ampersandpos - firstpos, charset), charset);
286 currentAttribute = factory.createAttribute(request, key);
287 currentAttribute.setValue("");
288 addHttpData(currentAttribute);
289 currentAttribute = null;
290 firstpos = currentpos;
291 contRead = true;
292 }
293 break;
294 case FIELD:
295 if (read == '&') {
296 currentStatus = MultiPartStatus.DISPOSITION;
297 ampersandpos = currentpos - 1;
298 setFinalBuffer(undecodedChunk.slice(firstpos, ampersandpos - firstpos));
299 firstpos = currentpos;
300 contRead = true;
301 } else if (read == HttpConstants.CR) {
302 if (undecodedChunk.readable()) {
303 read = (char) undecodedChunk.readUnsignedByte();
304 currentpos++;
305 if (read == HttpConstants.LF) {
306 currentStatus = MultiPartStatus.PREEPILOGUE;
307 ampersandpos = currentpos - 2;
308 setFinalBuffer(
309 undecodedChunk.slice(firstpos, ampersandpos - firstpos));
310 firstpos = currentpos;
311 contRead = false;
312 } else {
313
314 throw new ErrorDataDecoderException("Bad end of line");
315 }
316 } else {
317 currentpos--;
318 }
319 } else if (read == HttpConstants.LF) {
320 currentStatus = MultiPartStatus.PREEPILOGUE;
321 ampersandpos = currentpos - 1;
322 setFinalBuffer(
323 undecodedChunk.slice(firstpos, ampersandpos - firstpos));
324 firstpos = currentpos;
325 contRead = false;
326 }
327 break;
328 default:
329
330 contRead = false;
331 }
332 }
333 if (isLastChunk && currentAttribute != null) {
334
335 ampersandpos = currentpos;
336 if (ampersandpos > firstpos) {
337 setFinalBuffer(
338 undecodedChunk.slice(firstpos, ampersandpos - firstpos));
339 } else if (! currentAttribute.isCompleted()) {
340 setFinalBuffer(ChannelBuffers.EMPTY_BUFFER);
341 }
342 firstpos = currentpos;
343 currentStatus = MultiPartStatus.EPILOGUE;
344 undecodedChunk.readerIndex(firstpos);
345 return;
346 }
347 if (contRead && currentAttribute != null) {
348
349 if (currentStatus == MultiPartStatus.FIELD) {
350 currentAttribute.addContent(
351 undecodedChunk.slice(firstpos, currentpos - firstpos),
352 false);
353 firstpos = currentpos;
354 }
355 undecodedChunk.readerIndex(firstpos);
356 } else {
357
358 undecodedChunk.readerIndex(firstpos);
359 }
360 } catch (ErrorDataDecoderException e) {
361
362 undecodedChunk.readerIndex(firstpos);
363 throw e;
364 } catch (IOException e) {
365
366 undecodedChunk.readerIndex(firstpos);
367 throw new ErrorDataDecoderException(e);
368 }
369 }
370
371
372
373
374
375
376
377
378 private void parseBodyAttributes() throws ErrorDataDecoderException {
379 SeekAheadOptimize sao;
380 try {
381 sao = new SeekAheadOptimize(undecodedChunk);
382 } catch (SeekAheadNoBackArrayException e1) {
383 parseBodyAttributesStandard();
384 return;
385 }
386 int firstpos = undecodedChunk.readerIndex();
387 int currentpos = firstpos;
388 int equalpos;
389 int ampersandpos;
390 if (currentStatus == MultiPartStatus.NOTSTARTED) {
391 currentStatus = MultiPartStatus.DISPOSITION;
392 }
393 boolean contRead = true;
394 try {
395 loop:
396 while (sao.pos < sao.limit) {
397 char read = (char) (sao.bytes[sao.pos ++] & 0xFF);
398 currentpos ++;
399 switch (currentStatus) {
400 case DISPOSITION:
401 if (read == '=') {
402 currentStatus = MultiPartStatus.FIELD;
403 equalpos = currentpos - 1;
404 String key = decodeAttribute(
405 undecodedChunk.toString(firstpos, equalpos - firstpos, charset),
406 charset);
407 currentAttribute = factory.createAttribute(request, key);
408 firstpos = currentpos;
409 } else if (read == '&') {
410 currentStatus = MultiPartStatus.DISPOSITION;
411 ampersandpos = currentpos - 1;
412 String key = decodeAttribute(
413 undecodedChunk.toString(firstpos, ampersandpos - firstpos, charset), charset);
414 currentAttribute = factory.createAttribute(request, key);
415 currentAttribute.setValue("");
416 addHttpData(currentAttribute);
417 currentAttribute = null;
418 firstpos = currentpos;
419 contRead = true;
420 }
421 break;
422 case FIELD:
423 if (read == '&') {
424 currentStatus = MultiPartStatus.DISPOSITION;
425 ampersandpos = currentpos - 1;
426 setFinalBuffer(undecodedChunk.slice(firstpos, ampersandpos - firstpos));
427 firstpos = currentpos;
428 contRead = true;
429 } else if (read == HttpConstants.CR) {
430 if (sao.pos < sao.limit) {
431 read = (char) (sao.bytes[sao.pos ++] & 0xFF);
432 currentpos++;
433 if (read == HttpConstants.LF) {
434 currentStatus = MultiPartStatus.PREEPILOGUE;
435 ampersandpos = currentpos - 2;
436 sao.setReadPosition(0);
437 setFinalBuffer(
438 undecodedChunk.slice(firstpos, ampersandpos - firstpos));
439 firstpos = currentpos;
440 contRead = false;
441 break loop;
442 } else {
443
444 sao.setReadPosition(0);
445 throw new ErrorDataDecoderException("Bad end of line");
446 }
447 } else {
448 if (sao.limit > 0) {
449 currentpos --;
450 }
451 }
452 } else if (read == HttpConstants.LF) {
453 currentStatus = MultiPartStatus.PREEPILOGUE;
454 ampersandpos = currentpos - 1;
455 sao.setReadPosition(0);
456 setFinalBuffer(
457 undecodedChunk.slice(firstpos, ampersandpos - firstpos));
458 firstpos = currentpos;
459 contRead = false;
460 break loop;
461 }
462 break;
463 default:
464
465 sao.setReadPosition(0);
466 contRead = false;
467 break loop;
468 }
469 }
470 if (isLastChunk && currentAttribute != null) {
471
472 ampersandpos = currentpos;
473 if (ampersandpos > firstpos) {
474 setFinalBuffer(
475 undecodedChunk.slice(firstpos, ampersandpos - firstpos));
476 } else if (! currentAttribute.isCompleted()) {
477 setFinalBuffer(ChannelBuffers.EMPTY_BUFFER);
478 }
479 firstpos = currentpos;
480 currentStatus = MultiPartStatus.EPILOGUE;
481 undecodedChunk.readerIndex(firstpos);
482 return;
483 }
484 if (contRead && currentAttribute != null) {
485
486 if (currentStatus == MultiPartStatus.FIELD) {
487 currentAttribute.addContent(
488 undecodedChunk.slice(firstpos, currentpos - firstpos),
489 false);
490 firstpos = currentpos;
491 }
492 undecodedChunk.readerIndex(firstpos);
493 } else {
494
495 undecodedChunk.readerIndex(firstpos);
496 }
497 } catch (ErrorDataDecoderException e) {
498
499 undecodedChunk.readerIndex(firstpos);
500 throw e;
501 } catch (IOException e) {
502
503 undecodedChunk.readerIndex(firstpos);
504 throw new ErrorDataDecoderException(e);
505 }
506 }
507
508 private void setFinalBuffer(ChannelBuffer buffer) throws ErrorDataDecoderException, IOException {
509 currentAttribute.addContent(buffer, true);
510 String value = decodeAttribute(
511 currentAttribute.getChannelBuffer().toString(charset),
512 charset);
513 currentAttribute.setValue(value);
514 addHttpData(currentAttribute);
515 currentAttribute = null;
516 }
517
518
519
520
521
522 private static String decodeAttribute(String s, Charset charset)
523 throws ErrorDataDecoderException {
524 if (s == null) {
525 return "";
526 }
527 try {
528 return URLDecoder.decode(s, charset.name());
529 } catch (UnsupportedEncodingException e) {
530 throw new ErrorDataDecoderException(charset.toString(), e);
531 } catch (IllegalArgumentException e) {
532 throw new ErrorDataDecoderException("Bad string: '" + s + '\'', e);
533 }
534 }
535
536 public void cleanFiles() {
537 factory.cleanRequestHttpDatas(request);
538 }
539
540 public void removeHttpDataFromClean(InterfaceHttpData data) {
541 factory.removeHttpDataFromClean(request, data);
542 }
543 }