1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package io.netty.handler.codec.http.multipart;
17
18 import io.netty.buffer.ByteBuf;
19 import io.netty.handler.codec.http.HttpConstants;
20 import io.netty.handler.codec.http.HttpContent;
21 import io.netty.handler.codec.http.HttpRequest;
22 import io.netty.handler.codec.http.LastHttpContent;
23 import io.netty.handler.codec.http.QueryStringDecoder;
24 import io.netty.handler.codec.http.multipart.HttpPostBodyUtil.SeekAheadOptimize;
25 import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.EndOfDataDecoderException;
26 import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.ErrorDataDecoderException;
27 import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.MultiPartStatus;
28 import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.NotEnoughDataDecoderException;
29
30 import java.io.IOException;
31 import java.nio.charset.Charset;
32 import java.util.ArrayList;
33 import java.util.List;
34 import java.util.Map;
35 import java.util.TreeMap;
36
37 import static io.netty.buffer.Unpooled.*;
38 import static io.netty.util.internal.ObjectUtil.*;
39
40
41
42
43
44
45
46 public class HttpPostStandardRequestDecoder implements InterfaceHttpPostRequestDecoder {
47
48
49
50
51 private final HttpDataFactory factory;
52
53
54
55
56 private final HttpRequest request;
57
58
59
60
61 private final Charset charset;
62
63
64
65
66 private boolean isLastChunk;
67
68
69
70
71 private final List<InterfaceHttpData> bodyListHttpData = new ArrayList<InterfaceHttpData>();
72
73
74
75
76 private final Map<String, List<InterfaceHttpData>> bodyMapHttpData = new TreeMap<String, List<InterfaceHttpData>>(
77 CaseIgnoringComparator.INSTANCE);
78
79
80
81
82 private ByteBuf undecodedChunk;
83
84
85
86
87 private int bodyListHttpDataRank;
88
89
90
91
92 private MultiPartStatus currentStatus = MultiPartStatus.NOTSTARTED;
93
94
95
96
97 private Attribute currentAttribute;
98
99 private boolean destroyed;
100
101 private int discardThreshold = HttpPostRequestDecoder.DEFAULT_DISCARD_THRESHOLD;
102
103
104
105
106
107
108
109
110
111
112
113 public HttpPostStandardRequestDecoder(HttpRequest request) {
114 this(new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE), request, HttpConstants.DEFAULT_CHARSET);
115 }
116
117
118
119
120
121
122
123
124
125
126
127
128
129 public HttpPostStandardRequestDecoder(HttpDataFactory factory, HttpRequest request) {
130 this(factory, request, HttpConstants.DEFAULT_CHARSET);
131 }
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147 public HttpPostStandardRequestDecoder(HttpDataFactory factory, HttpRequest request, Charset charset) {
148 this.request = checkNotNull(request, "request");
149 this.charset = checkNotNull(charset, "charset");
150 this.factory = checkNotNull(factory, "factory");
151 if (request instanceof HttpContent) {
152
153
154 offer((HttpContent) request);
155 } else {
156 undecodedChunk = buffer();
157 parseBody();
158 }
159 }
160
161 private void checkDestroyed() {
162 if (destroyed) {
163 throw new IllegalStateException(HttpPostStandardRequestDecoder.class.getSimpleName()
164 + " was destroyed already");
165 }
166 }
167
168
169
170
171
172
173 @Override
174 public boolean isMultipart() {
175 checkDestroyed();
176 return false;
177 }
178
179
180
181
182
183
184 @Override
185 public void setDiscardThreshold(int discardThreshold) {
186 this.discardThreshold = checkPositiveOrZero(discardThreshold, "discardThreshold");
187 }
188
189
190
191
192 @Override
193 public int getDiscardThreshold() {
194 return discardThreshold;
195 }
196
197
198
199
200
201
202
203
204
205
206
207 @Override
208 public List<InterfaceHttpData> getBodyHttpDatas() {
209 checkDestroyed();
210
211 if (!isLastChunk) {
212 throw new NotEnoughDataDecoderException();
213 }
214 return bodyListHttpData;
215 }
216
217
218
219
220
221
222
223
224
225
226
227
228 @Override
229 public List<InterfaceHttpData> getBodyHttpDatas(String name) {
230 checkDestroyed();
231
232 if (!isLastChunk) {
233 throw new NotEnoughDataDecoderException();
234 }
235 return bodyMapHttpData.get(name);
236 }
237
238
239
240
241
242
243
244
245
246
247
248
249
250 @Override
251 public InterfaceHttpData getBodyHttpData(String name) {
252 checkDestroyed();
253
254 if (!isLastChunk) {
255 throw new NotEnoughDataDecoderException();
256 }
257 List<InterfaceHttpData> list = bodyMapHttpData.get(name);
258 if (list != null) {
259 return list.get(0);
260 }
261 return null;
262 }
263
264
265
266
267
268
269
270
271
272
273 @Override
274 public HttpPostStandardRequestDecoder offer(HttpContent content) {
275 checkDestroyed();
276
277
278
279
280 ByteBuf buf = content.content();
281 if (undecodedChunk == null) {
282 undecodedChunk = buf.copy();
283 } else {
284 undecodedChunk.writeBytes(buf);
285 }
286 if (content instanceof LastHttpContent) {
287 isLastChunk = true;
288 }
289 parseBody();
290 if (undecodedChunk != null && undecodedChunk.writerIndex() > discardThreshold) {
291 undecodedChunk.discardReadBytes();
292 }
293 return this;
294 }
295
296
297
298
299
300
301
302
303
304
305
306 @Override
307 public boolean hasNext() {
308 checkDestroyed();
309
310 if (currentStatus == MultiPartStatus.EPILOGUE) {
311
312 if (bodyListHttpDataRank >= bodyListHttpData.size()) {
313 throw new EndOfDataDecoderException();
314 }
315 }
316 return !bodyListHttpData.isEmpty() && bodyListHttpDataRank < bodyListHttpData.size();
317 }
318
319
320
321
322
323
324
325
326
327
328
329
330
331 @Override
332 public InterfaceHttpData next() {
333 checkDestroyed();
334
335 if (hasNext()) {
336 return bodyListHttpData.get(bodyListHttpDataRank++);
337 }
338 return null;
339 }
340
341
342
343
344
345
346
347
348 private void parseBody() {
349 if (currentStatus == MultiPartStatus.PREEPILOGUE || currentStatus == MultiPartStatus.EPILOGUE) {
350 if (isLastChunk) {
351 currentStatus = MultiPartStatus.EPILOGUE;
352 }
353 return;
354 }
355 parseBodyAttributes();
356 }
357
358
359
360
361 protected void addHttpData(InterfaceHttpData data) {
362 if (data == null) {
363 return;
364 }
365 List<InterfaceHttpData> datas = bodyMapHttpData.get(data.getName());
366 if (datas == null) {
367 datas = new ArrayList<InterfaceHttpData>(1);
368 bodyMapHttpData.put(data.getName(), datas);
369 }
370 datas.add(data);
371 bodyListHttpData.add(data);
372 }
373
374
375
376
377
378
379
380
381
382 private void parseBodyAttributesStandard() {
383 int firstpos = undecodedChunk.readerIndex();
384 int currentpos = firstpos;
385 int equalpos;
386 int ampersandpos;
387 if (currentStatus == MultiPartStatus.NOTSTARTED) {
388 currentStatus = MultiPartStatus.DISPOSITION;
389 }
390 boolean contRead = true;
391 try {
392 while (undecodedChunk.isReadable() && contRead) {
393 char read = (char) undecodedChunk.readUnsignedByte();
394 currentpos++;
395 switch (currentStatus) {
396 case DISPOSITION:
397 if (read == '=') {
398 currentStatus = MultiPartStatus.FIELD;
399 equalpos = currentpos - 1;
400 String key = decodeAttribute(undecodedChunk.toString(firstpos, equalpos - firstpos, charset),
401 charset);
402 currentAttribute = factory.createAttribute(request, key);
403 firstpos = currentpos;
404 } else if (read == '&') {
405 currentStatus = MultiPartStatus.DISPOSITION;
406 ampersandpos = currentpos - 1;
407 String key = decodeAttribute(
408 undecodedChunk.toString(firstpos, ampersandpos - firstpos, charset), charset);
409 currentAttribute = factory.createAttribute(request, key);
410 currentAttribute.setValue("");
411 addHttpData(currentAttribute);
412 currentAttribute = null;
413 firstpos = currentpos;
414 contRead = true;
415 }
416 break;
417 case FIELD:
418 if (read == '&') {
419 currentStatus = MultiPartStatus.DISPOSITION;
420 ampersandpos = currentpos - 1;
421 setFinalBuffer(undecodedChunk.copy(firstpos, ampersandpos - firstpos));
422 firstpos = currentpos;
423 contRead = true;
424 } else if (read == HttpConstants.CR) {
425 if (undecodedChunk.isReadable()) {
426 read = (char) undecodedChunk.readUnsignedByte();
427 currentpos++;
428 if (read == HttpConstants.LF) {
429 currentStatus = MultiPartStatus.PREEPILOGUE;
430 ampersandpos = currentpos - 2;
431 setFinalBuffer(undecodedChunk.copy(firstpos, ampersandpos - firstpos));
432 firstpos = currentpos;
433 contRead = false;
434 } else {
435
436 throw new ErrorDataDecoderException("Bad end of line");
437 }
438 } else {
439 currentpos--;
440 }
441 } else if (read == HttpConstants.LF) {
442 currentStatus = MultiPartStatus.PREEPILOGUE;
443 ampersandpos = currentpos - 1;
444 setFinalBuffer(undecodedChunk.copy(firstpos, ampersandpos - firstpos));
445 firstpos = currentpos;
446 contRead = false;
447 }
448 break;
449 default:
450
451 contRead = false;
452 }
453 }
454 if (isLastChunk && currentAttribute != null) {
455
456 ampersandpos = currentpos;
457 if (ampersandpos > firstpos) {
458 setFinalBuffer(undecodedChunk.copy(firstpos, ampersandpos - firstpos));
459 } else if (!currentAttribute.isCompleted()) {
460 setFinalBuffer(EMPTY_BUFFER);
461 }
462 firstpos = currentpos;
463 currentStatus = MultiPartStatus.EPILOGUE;
464 undecodedChunk.readerIndex(firstpos);
465 return;
466 }
467 if (contRead && currentAttribute != null) {
468
469 if (currentStatus == MultiPartStatus.FIELD) {
470 currentAttribute.addContent(undecodedChunk.copy(firstpos, currentpos - firstpos),
471 false);
472 firstpos = currentpos;
473 }
474 undecodedChunk.readerIndex(firstpos);
475 } else {
476
477 undecodedChunk.readerIndex(firstpos);
478 }
479 } catch (ErrorDataDecoderException e) {
480
481 undecodedChunk.readerIndex(firstpos);
482 throw e;
483 } catch (IOException e) {
484
485 undecodedChunk.readerIndex(firstpos);
486 throw new ErrorDataDecoderException(e);
487 }
488 }
489
490
491
492
493
494
495
496
497
498 private void parseBodyAttributes() {
499 if (!undecodedChunk.hasArray()) {
500 parseBodyAttributesStandard();
501 return;
502 }
503 SeekAheadOptimize sao = new SeekAheadOptimize(undecodedChunk);
504 int firstpos = undecodedChunk.readerIndex();
505 int currentpos = firstpos;
506 int equalpos;
507 int ampersandpos;
508 if (currentStatus == MultiPartStatus.NOTSTARTED) {
509 currentStatus = MultiPartStatus.DISPOSITION;
510 }
511 boolean contRead = true;
512 try {
513 loop: while (sao.pos < sao.limit) {
514 char read = (char) (sao.bytes[sao.pos++] & 0xFF);
515 currentpos++;
516 switch (currentStatus) {
517 case DISPOSITION:
518 if (read == '=') {
519 currentStatus = MultiPartStatus.FIELD;
520 equalpos = currentpos - 1;
521 String key = decodeAttribute(undecodedChunk.toString(firstpos, equalpos - firstpos, charset),
522 charset);
523 currentAttribute = factory.createAttribute(request, key);
524 firstpos = currentpos;
525 } else if (read == '&') {
526 currentStatus = MultiPartStatus.DISPOSITION;
527 ampersandpos = currentpos - 1;
528 String key = decodeAttribute(
529 undecodedChunk.toString(firstpos, ampersandpos - firstpos, charset), charset);
530 currentAttribute = factory.createAttribute(request, key);
531 currentAttribute.setValue("");
532 addHttpData(currentAttribute);
533 currentAttribute = null;
534 firstpos = currentpos;
535 contRead = true;
536 }
537 break;
538 case FIELD:
539 if (read == '&') {
540 currentStatus = MultiPartStatus.DISPOSITION;
541 ampersandpos = currentpos - 1;
542 setFinalBuffer(undecodedChunk.copy(firstpos, ampersandpos - firstpos));
543 firstpos = currentpos;
544 contRead = true;
545 } else if (read == HttpConstants.CR) {
546 if (sao.pos < sao.limit) {
547 read = (char) (sao.bytes[sao.pos++] & 0xFF);
548 currentpos++;
549 if (read == HttpConstants.LF) {
550 currentStatus = MultiPartStatus.PREEPILOGUE;
551 ampersandpos = currentpos - 2;
552 sao.setReadPosition(0);
553 setFinalBuffer(undecodedChunk.copy(firstpos, ampersandpos - firstpos));
554 firstpos = currentpos;
555 contRead = false;
556 break loop;
557 } else {
558
559 sao.setReadPosition(0);
560 throw new ErrorDataDecoderException("Bad end of line");
561 }
562 } else {
563 if (sao.limit > 0) {
564 currentpos--;
565 }
566 }
567 } else if (read == HttpConstants.LF) {
568 currentStatus = MultiPartStatus.PREEPILOGUE;
569 ampersandpos = currentpos - 1;
570 sao.setReadPosition(0);
571 setFinalBuffer(undecodedChunk.copy(firstpos, ampersandpos - firstpos));
572 firstpos = currentpos;
573 contRead = false;
574 break loop;
575 }
576 break;
577 default:
578
579 sao.setReadPosition(0);
580 contRead = false;
581 break loop;
582 }
583 }
584 if (isLastChunk && currentAttribute != null) {
585
586 ampersandpos = currentpos;
587 if (ampersandpos > firstpos) {
588 setFinalBuffer(undecodedChunk.copy(firstpos, ampersandpos - firstpos));
589 } else if (!currentAttribute.isCompleted()) {
590 setFinalBuffer(EMPTY_BUFFER);
591 }
592 firstpos = currentpos;
593 currentStatus = MultiPartStatus.EPILOGUE;
594 undecodedChunk.readerIndex(firstpos);
595 return;
596 }
597 if (contRead && currentAttribute != null) {
598
599 if (currentStatus == MultiPartStatus.FIELD) {
600 currentAttribute.addContent(undecodedChunk.copy(firstpos, currentpos - firstpos),
601 false);
602 firstpos = currentpos;
603 }
604 undecodedChunk.readerIndex(firstpos);
605 } else {
606
607 undecodedChunk.readerIndex(firstpos);
608 }
609 } catch (ErrorDataDecoderException e) {
610
611 undecodedChunk.readerIndex(firstpos);
612 throw e;
613 } catch (IOException e) {
614
615 undecodedChunk.readerIndex(firstpos);
616 throw new ErrorDataDecoderException(e);
617 } catch (IllegalArgumentException e) {
618
619 undecodedChunk.readerIndex(firstpos);
620 throw new ErrorDataDecoderException(e);
621 }
622 }
623
624 private void setFinalBuffer(ByteBuf buffer) throws IOException {
625 currentAttribute.addContent(buffer, true);
626 String value = decodeAttribute(currentAttribute.getByteBuf().toString(charset), charset);
627 currentAttribute.setValue(value);
628 addHttpData(currentAttribute);
629 currentAttribute = null;
630 }
631
632
633
634
635
636
637 private static String decodeAttribute(String s, Charset charset) {
638 try {
639 return QueryStringDecoder.decodeComponent(s, charset);
640 } catch (IllegalArgumentException e) {
641 throw new ErrorDataDecoderException("Bad string: '" + s + '\'', e);
642 }
643 }
644
645
646
647
648
649 @Override
650 public void destroy() {
651 checkDestroyed();
652 cleanFiles();
653 destroyed = true;
654
655 if (undecodedChunk != null && undecodedChunk.refCnt() > 0) {
656 undecodedChunk.release();
657 undecodedChunk = null;
658 }
659
660
661 for (int i = bodyListHttpDataRank; i < bodyListHttpData.size(); i++) {
662 bodyListHttpData.get(i).release();
663 }
664 }
665
666
667
668
669 @Override
670 public void cleanFiles() {
671 checkDestroyed();
672
673 factory.cleanRequestHttpDatas(request);
674 }
675
676
677
678
679 @Override
680 public void removeHttpDataFromClean(InterfaceHttpData data) {
681 checkDestroyed();
682
683 factory.removeHttpDataFromClean(request, data);
684 }
685 }