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