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.HttpHeaderNames;
22 import io.netty.handler.codec.http.HttpHeaderValues;
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.HttpPostBodyUtil.TransferEncodingMechanism;
28 import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.EndOfDataDecoderException;
29 import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.ErrorDataDecoderException;
30 import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.MultiPartStatus;
31 import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.NotEnoughDataDecoderException;
32 import io.netty.util.CharsetUtil;
33 import io.netty.util.internal.EmptyArrays;
34 import io.netty.util.internal.InternalThreadLocalMap;
35 import io.netty.util.internal.PlatformDependent;
36 import io.netty.util.internal.StringUtil;
37
38 import java.io.IOException;
39 import java.nio.charset.Charset;
40 import java.nio.charset.IllegalCharsetNameException;
41 import java.nio.charset.UnsupportedCharsetException;
42 import java.util.ArrayList;
43 import java.util.List;
44 import java.util.Locale;
45 import java.util.Map;
46 import java.util.TreeMap;
47
48 import static io.netty.util.internal.ObjectUtil.checkNotNull;
49 import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero;
50
51
52
53
54
55
56
57 public class HttpPostMultipartRequestDecoder implements InterfaceHttpPostRequestDecoder {
58
59
60
61
62 private final HttpDataFactory factory;
63
64
65
66
67 private final HttpRequest request;
68
69
70
71
72 private final int maxFields;
73
74
75
76
77 private final int maxBufferedBytes;
78
79
80
81
82 private Charset charset;
83
84
85
86
87 private boolean isLastChunk;
88
89
90
91
92 private final List<InterfaceHttpData> bodyListHttpData = new ArrayList<InterfaceHttpData>();
93
94
95
96
97 private final Map<String, List<InterfaceHttpData>> bodyMapHttpData = new TreeMap<String, List<InterfaceHttpData>>(
98 CaseIgnoringComparator.INSTANCE);
99
100
101
102
103 private ByteBuf undecodedChunk;
104
105
106
107
108 private int bodyListHttpDataRank;
109
110
111
112
113 private final String multipartDataBoundary;
114
115
116
117
118
119 private String multipartMixedBoundary;
120
121
122
123
124 private MultiPartStatus currentStatus = MultiPartStatus.NOTSTARTED;
125
126
127
128
129 private Map<CharSequence, Attribute> currentFieldAttributes;
130
131
132
133
134 private FileUpload currentFileUpload;
135
136
137
138
139 private Attribute currentAttribute;
140
141 private boolean destroyed;
142
143 private int discardThreshold = HttpPostRequestDecoder.DEFAULT_DISCARD_THRESHOLD;
144
145
146
147
148
149
150
151
152
153
154
155 public HttpPostMultipartRequestDecoder(HttpRequest request) {
156 this(new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE), request, HttpConstants.DEFAULT_CHARSET);
157 }
158
159
160
161
162
163
164
165
166
167
168
169
170
171 public HttpPostMultipartRequestDecoder(HttpDataFactory factory, HttpRequest request) {
172 this(factory, request, HttpConstants.DEFAULT_CHARSET);
173 }
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189 public HttpPostMultipartRequestDecoder(HttpDataFactory factory, HttpRequest request, Charset charset) {
190 this(factory, request, charset, HttpPostRequestDecoder.DEFAULT_MAX_FIELDS,
191 HttpPostRequestDecoder.DEFAULT_MAX_BUFFERED_BYTES);
192 }
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212 public HttpPostMultipartRequestDecoder(HttpDataFactory factory, HttpRequest request, Charset charset,
213 int maxFields, int maxBufferedBytes) {
214 this.request = checkNotNull(request, "request");
215 this.charset = checkNotNull(charset, "charset");
216 this.factory = checkNotNull(factory, "factory");
217 this.maxFields = maxFields;
218 this.maxBufferedBytes = maxBufferedBytes;
219
220
221 String contentTypeValue = this.request.headers().get(HttpHeaderNames.CONTENT_TYPE);
222 if (contentTypeValue == null || contentTypeValue.isEmpty()) {
223
224 throw new ErrorDataDecoderException(
225 "No '" + HttpHeaderNames.CONTENT_TYPE + "' header present, or value is empty.");
226 }
227
228 String[] dataBoundary = HttpPostRequestDecoder.getMultipartDataBoundary(contentTypeValue);
229 if (dataBoundary != null) {
230 multipartDataBoundary = dataBoundary[0];
231 if (dataBoundary.length > 1 && dataBoundary[1] != null) {
232 try {
233 this.charset = Charset.forName(dataBoundary[1]);
234 } catch (IllegalCharsetNameException e) {
235 throw new ErrorDataDecoderException(e);
236 }
237 }
238 } else {
239 multipartDataBoundary = null;
240 }
241 currentStatus = MultiPartStatus.HEADERDELIMITER;
242
243 try {
244 if (request instanceof HttpContent) {
245
246
247 offer((HttpContent) request);
248 } else {
249 parseBody();
250 }
251 } catch (Throwable e) {
252 destroy();
253 PlatformDependent.throwException(e);
254 }
255 }
256
257 private void checkDestroyed() {
258 if (destroyed) {
259 throw new IllegalStateException(HttpPostMultipartRequestDecoder.class.getSimpleName()
260 + " was destroyed already");
261 }
262 }
263
264
265
266
267
268
269 @Override
270 public boolean isMultipart() {
271 checkDestroyed();
272 return true;
273 }
274
275
276
277
278
279
280 @Override
281 public void setDiscardThreshold(int discardThreshold) {
282 this.discardThreshold = checkPositiveOrZero(discardThreshold, "discardThreshold");
283 }
284
285
286
287
288 @Override
289 public int getDiscardThreshold() {
290 return discardThreshold;
291 }
292
293
294
295
296
297
298
299
300
301
302
303 @Override
304 public List<InterfaceHttpData> getBodyHttpDatas() {
305 checkDestroyed();
306
307 if (!isLastChunk) {
308 throw new NotEnoughDataDecoderException();
309 }
310 return bodyListHttpData;
311 }
312
313
314
315
316
317
318
319
320
321
322
323
324 @Override
325 public List<InterfaceHttpData> getBodyHttpDatas(String name) {
326 checkDestroyed();
327
328 if (!isLastChunk) {
329 throw new NotEnoughDataDecoderException();
330 }
331 return bodyMapHttpData.get(name);
332 }
333
334
335
336
337
338
339
340
341
342
343
344
345
346 @Override
347 public InterfaceHttpData getBodyHttpData(String name) {
348 checkDestroyed();
349
350 if (!isLastChunk) {
351 throw new NotEnoughDataDecoderException();
352 }
353 List<InterfaceHttpData> list = bodyMapHttpData.get(name);
354 if (list != null) {
355 return list.get(0);
356 }
357 return null;
358 }
359
360
361
362
363
364
365
366
367
368
369 @Override
370 public HttpPostMultipartRequestDecoder offer(HttpContent content) {
371 checkDestroyed();
372
373 if (content instanceof LastHttpContent) {
374 isLastChunk = true;
375 }
376
377 ByteBuf buf = content.content();
378 if (undecodedChunk == null) {
379 undecodedChunk =
380
381
382
383
384 buf.alloc().buffer(buf.readableBytes()).writeBytes(buf);
385 } else {
386 undecodedChunk.writeBytes(buf);
387 }
388 parseBody();
389 if (maxBufferedBytes > 0 && undecodedChunk != null && undecodedChunk.readableBytes() > maxBufferedBytes) {
390 throw new HttpPostRequestDecoder.TooLongFormFieldException();
391 }
392 if (undecodedChunk != null && undecodedChunk.writerIndex() > discardThreshold) {
393 if (undecodedChunk.refCnt() == 1) {
394
395 undecodedChunk.discardReadBytes();
396 } else {
397
398
399 ByteBuf buffer = undecodedChunk.alloc().buffer(undecodedChunk.readableBytes());
400 buffer.writeBytes(undecodedChunk);
401 undecodedChunk.release();
402 undecodedChunk = buffer;
403 }
404 }
405 return this;
406 }
407
408
409
410
411
412
413
414
415
416
417
418 @Override
419 public boolean hasNext() {
420 checkDestroyed();
421
422 if (currentStatus == MultiPartStatus.EPILOGUE) {
423
424 if (bodyListHttpDataRank >= bodyListHttpData.size()) {
425 throw new EndOfDataDecoderException();
426 }
427 }
428 return !bodyListHttpData.isEmpty() && bodyListHttpDataRank < bodyListHttpData.size();
429 }
430
431
432
433
434
435
436
437
438
439
440
441
442
443 @Override
444 public InterfaceHttpData next() {
445 checkDestroyed();
446
447 if (hasNext()) {
448 return bodyListHttpData.get(bodyListHttpDataRank++);
449 }
450 return null;
451 }
452
453 @Override
454 public InterfaceHttpData currentPartialHttpData() {
455 if (currentFileUpload != null) {
456 return currentFileUpload;
457 } else {
458 return currentAttribute;
459 }
460 }
461
462
463
464
465
466
467
468
469 private void parseBody() {
470 if (currentStatus == MultiPartStatus.PREEPILOGUE || currentStatus == MultiPartStatus.EPILOGUE) {
471 if (isLastChunk) {
472 currentStatus = MultiPartStatus.EPILOGUE;
473 }
474 return;
475 }
476 parseBodyMultipart();
477 }
478
479
480
481
482 protected void addHttpData(InterfaceHttpData data) {
483 if (data == null) {
484 return;
485 }
486 if (maxFields > 0 && bodyListHttpData.size() >= maxFields) {
487 throw new HttpPostRequestDecoder.TooManyFormFieldsException();
488 }
489 List<InterfaceHttpData> datas = bodyMapHttpData.get(data.getName());
490 if (datas == null) {
491 datas = new ArrayList<InterfaceHttpData>(1);
492 bodyMapHttpData.put(data.getName(), datas);
493 }
494 datas.add(data);
495 bodyListHttpData.add(data);
496 }
497
498
499
500
501
502
503
504
505 private void parseBodyMultipart() {
506 if (undecodedChunk == null || undecodedChunk.readableBytes() == 0) {
507
508 return;
509 }
510 InterfaceHttpData data = decodeMultipart(currentStatus);
511 while (data != null) {
512 addHttpData(data);
513 if (currentStatus == MultiPartStatus.PREEPILOGUE || currentStatus == MultiPartStatus.EPILOGUE) {
514 break;
515 }
516 data = decodeMultipart(currentStatus);
517 }
518 }
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536 private InterfaceHttpData decodeMultipart(MultiPartStatus state) {
537 switch (state) {
538 case NOTSTARTED:
539 throw new ErrorDataDecoderException("Should not be called with the current getStatus");
540 case PREAMBLE:
541
542 throw new ErrorDataDecoderException("Should not be called with the current getStatus");
543 case HEADERDELIMITER: {
544
545 return findMultipartDelimiter(multipartDataBoundary, MultiPartStatus.DISPOSITION,
546 MultiPartStatus.PREEPILOGUE);
547 }
548 case DISPOSITION: {
549
550
551
552
553
554
555
556
557
558 return findMultipartDisposition();
559 }
560 case FIELD: {
561
562 Charset localCharset = null;
563 Attribute charsetAttribute = currentFieldAttributes.get(HttpHeaderValues.CHARSET);
564 if (charsetAttribute != null) {
565 try {
566 localCharset = Charset.forName(charsetAttribute.getValue());
567 } catch (IOException | UnsupportedCharsetException e) {
568 throw new ErrorDataDecoderException(e);
569 }
570 }
571 Attribute nameAttribute = currentFieldAttributes.get(HttpHeaderValues.NAME);
572 if (currentAttribute == null) {
573 Attribute lengthAttribute = currentFieldAttributes
574 .get(HttpHeaderNames.CONTENT_LENGTH);
575 long size;
576 try {
577 size = lengthAttribute != null? Long.parseLong(lengthAttribute
578 .getValue()) : 0L;
579 } catch (IOException e) {
580 throw new ErrorDataDecoderException(e);
581 } catch (NumberFormatException ignored) {
582 size = 0;
583 }
584 try {
585 if (size > 0) {
586 currentAttribute = factory.createAttribute(request,
587 cleanString(nameAttribute.getValue()), size);
588 } else {
589 currentAttribute = factory.createAttribute(request,
590 cleanString(nameAttribute.getValue()));
591 }
592 } catch (NullPointerException | IllegalArgumentException | IOException e) {
593 throw new ErrorDataDecoderException(e);
594 }
595 if (localCharset != null) {
596 currentAttribute.setCharset(localCharset);
597 }
598 }
599
600 if (!loadDataMultipartOptimized(undecodedChunk, multipartDataBoundary, currentAttribute)) {
601
602 return null;
603 }
604 Attribute finalAttribute = currentAttribute;
605 currentAttribute = null;
606 currentFieldAttributes = null;
607
608 currentStatus = MultiPartStatus.HEADERDELIMITER;
609 return finalAttribute;
610 }
611 case FILEUPLOAD: {
612
613 return getFileUpload(multipartDataBoundary);
614 }
615 case MIXEDDELIMITER: {
616
617
618 return findMultipartDelimiter(multipartMixedBoundary, MultiPartStatus.MIXEDDISPOSITION,
619 MultiPartStatus.HEADERDELIMITER);
620 }
621 case MIXEDDISPOSITION: {
622 return findMultipartDisposition();
623 }
624 case MIXEDFILEUPLOAD: {
625
626 return getFileUpload(multipartMixedBoundary);
627 }
628 case PREEPILOGUE:
629 return null;
630 case EPILOGUE:
631 return null;
632 default:
633 throw new ErrorDataDecoderException("Shouldn't reach here.");
634 }
635 }
636
637
638
639
640
641
642 private static void skipControlCharacters(ByteBuf undecodedChunk) {
643 if (!undecodedChunk.hasArray()) {
644 try {
645 skipControlCharactersStandard(undecodedChunk);
646 } catch (IndexOutOfBoundsException e1) {
647 throw new NotEnoughDataDecoderException(e1);
648 }
649 return;
650 }
651 SeekAheadOptimize sao = new SeekAheadOptimize(undecodedChunk);
652 while (sao.pos < sao.limit) {
653 char c = (char) (sao.bytes[sao.pos++] & 0xFF);
654 if (!Character.isISOControl(c) && !Character.isWhitespace(c)) {
655 sao.setReadPosition(1);
656 return;
657 }
658 }
659 throw new NotEnoughDataDecoderException("Access out of bounds");
660 }
661
662 private static void skipControlCharactersStandard(ByteBuf undecodedChunk) {
663 for (;;) {
664 char c = (char) undecodedChunk.readUnsignedByte();
665 if (!Character.isISOControl(c) && !Character.isWhitespace(c)) {
666 undecodedChunk.readerIndex(undecodedChunk.readerIndex() - 1);
667 break;
668 }
669 }
670 }
671
672
673
674
675
676
677
678
679
680
681
682
683
684 private InterfaceHttpData findMultipartDelimiter(String delimiter, MultiPartStatus dispositionStatus,
685 MultiPartStatus closeDelimiterStatus) {
686
687 int readerIndex = undecodedChunk.readerIndex();
688 try {
689 skipControlCharacters(undecodedChunk);
690 } catch (NotEnoughDataDecoderException ignored) {
691 undecodedChunk.readerIndex(readerIndex);
692 return null;
693 }
694 skipOneLine();
695 String newline;
696 try {
697 newline = readDelimiterOptimized(undecodedChunk, delimiter, charset);
698 } catch (NotEnoughDataDecoderException ignored) {
699 undecodedChunk.readerIndex(readerIndex);
700 return null;
701 }
702 if (newline.equals(delimiter)) {
703 currentStatus = dispositionStatus;
704 return decodeMultipart(dispositionStatus);
705 }
706 if (newline.equals(delimiter + "--")) {
707
708 currentStatus = closeDelimiterStatus;
709 if (currentStatus == MultiPartStatus.HEADERDELIMITER) {
710
711
712 currentFieldAttributes = null;
713 return decodeMultipart(MultiPartStatus.HEADERDELIMITER);
714 }
715 return null;
716 }
717 undecodedChunk.readerIndex(readerIndex);
718 throw new ErrorDataDecoderException("No Multipart delimiter found");
719 }
720
721
722
723
724
725
726
727 private InterfaceHttpData findMultipartDisposition() {
728 int readerIndex = undecodedChunk.readerIndex();
729 if (currentStatus == MultiPartStatus.DISPOSITION) {
730 currentFieldAttributes = new TreeMap<CharSequence, Attribute>(CaseIgnoringComparator.INSTANCE);
731 }
732
733 while (!skipOneLine()) {
734 String newline;
735 try {
736 skipControlCharacters(undecodedChunk);
737 newline = readLineOptimized(undecodedChunk, charset);
738 } catch (NotEnoughDataDecoderException ignored) {
739 undecodedChunk.readerIndex(readerIndex);
740 return null;
741 }
742 String[] contents = splitMultipartHeader(newline);
743 if (HttpHeaderNames.CONTENT_DISPOSITION.contentEqualsIgnoreCase(contents[0])) {
744 boolean checkSecondArg;
745 if (currentStatus == MultiPartStatus.DISPOSITION) {
746 checkSecondArg = HttpHeaderValues.FORM_DATA.contentEqualsIgnoreCase(contents[1]);
747 } else {
748 checkSecondArg = HttpHeaderValues.ATTACHMENT.contentEqualsIgnoreCase(contents[1])
749 || HttpHeaderValues.FILE.contentEqualsIgnoreCase(contents[1]);
750 }
751 if (checkSecondArg) {
752
753 for (int i = 2; i < contents.length; i++) {
754 String[] values = contents[i].split("=", 2);
755 Attribute attribute;
756 try {
757 attribute = getContentDispositionAttribute(values);
758 } catch (NullPointerException | IllegalArgumentException e) {
759 throw new ErrorDataDecoderException(e);
760 }
761 currentFieldAttributes.put(attribute.getName(), attribute);
762 }
763 }
764 } else if (HttpHeaderNames.CONTENT_TRANSFER_ENCODING.contentEqualsIgnoreCase(contents[0])) {
765 Attribute attribute;
766 try {
767 attribute = factory.createAttribute(request, HttpHeaderNames.CONTENT_TRANSFER_ENCODING.toString(),
768 cleanString(contents[1]));
769 } catch (NullPointerException | IllegalArgumentException e) {
770 throw new ErrorDataDecoderException(e);
771 }
772
773 currentFieldAttributes.put(HttpHeaderNames.CONTENT_TRANSFER_ENCODING, attribute);
774 } else if (HttpHeaderNames.CONTENT_LENGTH.contentEqualsIgnoreCase(contents[0])) {
775 Attribute attribute;
776 try {
777 attribute = factory.createAttribute(request, HttpHeaderNames.CONTENT_LENGTH.toString(),
778 cleanString(contents[1]));
779 } catch (NullPointerException | IllegalArgumentException e) {
780 throw new ErrorDataDecoderException(e);
781 }
782
783 currentFieldAttributes.put(HttpHeaderNames.CONTENT_LENGTH, attribute);
784 } else if (HttpHeaderNames.CONTENT_TYPE.contentEqualsIgnoreCase(contents[0])) {
785
786 if (HttpHeaderValues.MULTIPART_MIXED.contentEqualsIgnoreCase(contents[1])) {
787 if (currentStatus == MultiPartStatus.DISPOSITION) {
788 String values = StringUtil.substringAfter(contents[2], '=');
789 multipartMixedBoundary = "--" + values;
790 currentStatus = MultiPartStatus.MIXEDDELIMITER;
791 return decodeMultipart(MultiPartStatus.MIXEDDELIMITER);
792 } else {
793 throw new ErrorDataDecoderException("Mixed Multipart found in a previous Mixed Multipart");
794 }
795 } else {
796 for (int i = 1; i < contents.length; i++) {
797 final String charsetHeader = HttpHeaderValues.CHARSET.toString();
798 if (contents[i].regionMatches(true, 0, charsetHeader, 0, charsetHeader.length())) {
799 String values = StringUtil.substringAfter(contents[i], '=');
800 Attribute attribute;
801 try {
802 attribute = factory.createAttribute(request, charsetHeader, cleanString(values));
803 } catch (NullPointerException | IllegalArgumentException e) {
804 throw new ErrorDataDecoderException(e);
805 }
806 currentFieldAttributes.put(HttpHeaderValues.CHARSET, attribute);
807 } else if (contents[i].contains("=")) {
808 String name = StringUtil.substringBefore(contents[i], '=');
809 String values = StringUtil.substringAfter(contents[i], '=');
810 Attribute attribute;
811 try {
812 attribute = factory.createAttribute(request, cleanString(name), values);
813 } catch (NullPointerException | IllegalArgumentException e) {
814 throw new ErrorDataDecoderException(e);
815 }
816 currentFieldAttributes.put(name, attribute);
817 } else {
818 Attribute attribute;
819 try {
820 attribute = factory.createAttribute(request,
821 cleanString(contents[0]), contents[i]);
822 } catch (NullPointerException | IllegalArgumentException e) {
823 throw new ErrorDataDecoderException(e);
824 }
825 currentFieldAttributes.put(attribute.getName(), attribute);
826 }
827 }
828 }
829 }
830 }
831
832 Attribute filenameAttribute = currentFieldAttributes.get(HttpHeaderValues.FILENAME);
833 if (currentStatus == MultiPartStatus.DISPOSITION) {
834 if (filenameAttribute != null) {
835
836 currentStatus = MultiPartStatus.FILEUPLOAD;
837
838 return decodeMultipart(MultiPartStatus.FILEUPLOAD);
839 } else {
840
841 currentStatus = MultiPartStatus.FIELD;
842
843 return decodeMultipart(MultiPartStatus.FIELD);
844 }
845 } else {
846 if (filenameAttribute != null) {
847
848 currentStatus = MultiPartStatus.MIXEDFILEUPLOAD;
849
850 return decodeMultipart(MultiPartStatus.MIXEDFILEUPLOAD);
851 } else {
852
853 throw new ErrorDataDecoderException("Filename not found");
854 }
855 }
856 }
857
858 private static final String FILENAME_ENCODED = HttpHeaderValues.FILENAME.toString() + '*';
859
860 private Attribute getContentDispositionAttribute(String... values) {
861 String name = cleanString(values[0]);
862 String value = values[1];
863
864
865 if (HttpHeaderValues.FILENAME.contentEquals(name)) {
866
867 int last = value.length() - 1;
868 if (last > 0 &&
869 value.charAt(0) == HttpConstants.DOUBLE_QUOTE &&
870 value.charAt(last) == HttpConstants.DOUBLE_QUOTE) {
871 value = value.substring(1, last);
872 }
873 } else if (FILENAME_ENCODED.equals(name)) {
874 try {
875 name = HttpHeaderValues.FILENAME.toString();
876 String[] split = cleanString(value).split("'", 3);
877 value = QueryStringDecoder.decodeComponent(split[2], Charset.forName(split[0]));
878 } catch (ArrayIndexOutOfBoundsException | UnsupportedCharsetException e) {
879 throw new ErrorDataDecoderException(e);
880 }
881 } else {
882
883 value = cleanString(value);
884 }
885 return factory.createAttribute(request, name, value);
886 }
887
888
889
890
891
892
893
894
895
896 protected InterfaceHttpData getFileUpload(String delimiter) {
897
898
899 Attribute encoding = currentFieldAttributes.get(HttpHeaderNames.CONTENT_TRANSFER_ENCODING);
900 Charset localCharset = charset;
901
902 TransferEncodingMechanism mechanism = TransferEncodingMechanism.BIT7;
903 if (encoding != null) {
904 String code;
905 try {
906
907
908
909
910 code = encoding.getValue().toLowerCase(Locale.US);
911 } catch (IOException e) {
912 throw new ErrorDataDecoderException(e);
913 }
914 if (code.equals(HttpPostBodyUtil.TransferEncodingMechanism.BIT7.value())) {
915 localCharset = CharsetUtil.US_ASCII;
916 } else if (code.equals(HttpPostBodyUtil.TransferEncodingMechanism.BIT8.value())) {
917 localCharset = CharsetUtil.ISO_8859_1;
918 mechanism = TransferEncodingMechanism.BIT8;
919 } else if (code.equals(HttpPostBodyUtil.TransferEncodingMechanism.BINARY.value())) {
920
921 mechanism = TransferEncodingMechanism.BINARY;
922 } else {
923 throw new ErrorDataDecoderException("TransferEncoding Unknown: " + code);
924 }
925 }
926 Attribute charsetAttribute = currentFieldAttributes.get(HttpHeaderValues.CHARSET);
927 if (charsetAttribute != null) {
928 try {
929 localCharset = Charset.forName(charsetAttribute.getValue());
930 } catch (IOException | UnsupportedCharsetException e) {
931 throw new ErrorDataDecoderException(e);
932 }
933 }
934 if (currentFileUpload == null) {
935 Attribute filenameAttribute = currentFieldAttributes.get(HttpHeaderValues.FILENAME);
936 Attribute nameAttribute = currentFieldAttributes.get(HttpHeaderValues.NAME);
937 Attribute contentTypeAttribute = currentFieldAttributes.get(HttpHeaderNames.CONTENT_TYPE);
938 Attribute lengthAttribute = currentFieldAttributes.get(HttpHeaderNames.CONTENT_LENGTH);
939 long size;
940 try {
941 size = lengthAttribute != null ? Long.parseLong(lengthAttribute.getValue()) : 0L;
942 } catch (IOException e) {
943 throw new ErrorDataDecoderException(e);
944 } catch (NumberFormatException ignored) {
945 size = 0;
946 }
947 try {
948 String contentType;
949 if (contentTypeAttribute != null) {
950 contentType = contentTypeAttribute.getValue();
951 } else {
952 contentType = HttpPostBodyUtil.DEFAULT_BINARY_CONTENT_TYPE;
953 }
954 currentFileUpload = factory.createFileUpload(request,
955 cleanString(nameAttribute.getValue()), cleanString(filenameAttribute.getValue()),
956 cleanMimeContentType(contentType), mechanism.value(), localCharset,
957 size);
958 } catch (NullPointerException | IllegalArgumentException | IOException e) {
959 throw new ErrorDataDecoderException(e);
960 }
961 }
962
963 if (!loadDataMultipartOptimized(undecodedChunk, delimiter, currentFileUpload)) {
964
965 return null;
966 }
967 if (currentFileUpload.isCompleted()) {
968
969 if (currentStatus == MultiPartStatus.FILEUPLOAD) {
970 currentStatus = MultiPartStatus.HEADERDELIMITER;
971 currentFieldAttributes = null;
972 } else {
973 currentStatus = MultiPartStatus.MIXEDDELIMITER;
974 cleanMixedAttributes();
975 }
976 FileUpload fileUpload = currentFileUpload;
977 currentFileUpload = null;
978 return fileUpload;
979 }
980
981
982
983 return null;
984 }
985
986
987
988
989
990 @Override
991 public void destroy() {
992
993 cleanFiles();
994
995 for (InterfaceHttpData httpData : bodyListHttpData) {
996
997 if (httpData.refCnt() > 0) {
998 httpData.release();
999 }
1000 }
1001
1002 destroyed = true;
1003
1004 if (undecodedChunk != null && undecodedChunk.refCnt() > 0) {
1005 undecodedChunk.release();
1006 undecodedChunk = null;
1007 }
1008 }
1009
1010
1011
1012
1013 @Override
1014 public void cleanFiles() {
1015 checkDestroyed();
1016
1017 factory.cleanRequestHttpData(request);
1018 }
1019
1020
1021
1022
1023 @Override
1024 public void removeHttpDataFromClean(InterfaceHttpData data) {
1025 checkDestroyed();
1026
1027 factory.removeHttpDataFromClean(request, data);
1028 }
1029
1030
1031
1032
1033
1034 private void cleanMixedAttributes() {
1035 currentFieldAttributes.remove(HttpHeaderValues.CHARSET);
1036 currentFieldAttributes.remove(HttpHeaderNames.CONTENT_LENGTH);
1037 currentFieldAttributes.remove(HttpHeaderNames.CONTENT_TRANSFER_ENCODING);
1038 currentFieldAttributes.remove(HttpHeaderNames.CONTENT_TYPE);
1039 currentFieldAttributes.remove(HttpHeaderValues.FILENAME);
1040 }
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050 private static String readLineOptimized(ByteBuf undecodedChunk, Charset charset) {
1051 int readerIndex = undecodedChunk.readerIndex();
1052 ByteBuf line = null;
1053 try {
1054 if (undecodedChunk.isReadable()) {
1055 int posLfOrCrLf = HttpPostBodyUtil.findLineBreak(undecodedChunk, undecodedChunk.readerIndex());
1056 if (posLfOrCrLf <= 0) {
1057 throw new NotEnoughDataDecoderException();
1058 }
1059 try {
1060 line = undecodedChunk.alloc().heapBuffer(posLfOrCrLf);
1061 line.writeBytes(undecodedChunk, posLfOrCrLf);
1062
1063 byte nextByte = undecodedChunk.readByte();
1064 if (nextByte == HttpConstants.CR) {
1065
1066 undecodedChunk.readByte();
1067 }
1068 return line.toString(charset);
1069 } finally {
1070 line.release();
1071 }
1072 }
1073 } catch (IndexOutOfBoundsException e) {
1074 undecodedChunk.readerIndex(readerIndex);
1075 throw new NotEnoughDataDecoderException(e);
1076 }
1077 undecodedChunk.readerIndex(readerIndex);
1078 throw new NotEnoughDataDecoderException();
1079 }
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096 private static String readDelimiterOptimized(ByteBuf undecodedChunk, String delimiter, Charset charset) {
1097 final int readerIndex = undecodedChunk.readerIndex();
1098 final byte[] bdelimiter = delimiter.getBytes(charset);
1099 final int delimiterLength = bdelimiter.length;
1100 try {
1101 int delimiterPos = HttpPostBodyUtil.findDelimiter(undecodedChunk, readerIndex, bdelimiter, false);
1102 if (delimiterPos < 0) {
1103
1104 undecodedChunk.readerIndex(readerIndex);
1105 throw new NotEnoughDataDecoderException();
1106 }
1107 StringBuilder sb = new StringBuilder(delimiter);
1108 undecodedChunk.readerIndex(readerIndex + delimiterPos + delimiterLength);
1109
1110 if (undecodedChunk.isReadable()) {
1111 byte nextByte = undecodedChunk.readByte();
1112
1113 if (nextByte == HttpConstants.CR) {
1114 nextByte = undecodedChunk.readByte();
1115 if (nextByte == HttpConstants.LF) {
1116 return sb.toString();
1117 } else {
1118
1119
1120 undecodedChunk.readerIndex(readerIndex);
1121 throw new NotEnoughDataDecoderException();
1122 }
1123 } else if (nextByte == HttpConstants.LF) {
1124 return sb.toString();
1125 } else if (nextByte == '-') {
1126 sb.append('-');
1127
1128 nextByte = undecodedChunk.readByte();
1129 if (nextByte == '-') {
1130 sb.append('-');
1131
1132 if (undecodedChunk.isReadable()) {
1133 nextByte = undecodedChunk.readByte();
1134 if (nextByte == HttpConstants.CR) {
1135 nextByte = undecodedChunk.readByte();
1136 if (nextByte == HttpConstants.LF) {
1137 return sb.toString();
1138 } else {
1139
1140
1141 undecodedChunk.readerIndex(readerIndex);
1142 throw new NotEnoughDataDecoderException();
1143 }
1144 } else if (nextByte == HttpConstants.LF) {
1145 return sb.toString();
1146 } else {
1147
1148
1149
1150 undecodedChunk.readerIndex(undecodedChunk.readerIndex() - 1);
1151 return sb.toString();
1152 }
1153 }
1154
1155
1156
1157
1158 return sb.toString();
1159 }
1160
1161
1162 }
1163 }
1164 } catch (IndexOutOfBoundsException e) {
1165 undecodedChunk.readerIndex(readerIndex);
1166 throw new NotEnoughDataDecoderException(e);
1167 }
1168 undecodedChunk.readerIndex(readerIndex);
1169 throw new NotEnoughDataDecoderException();
1170 }
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181 private static void rewriteCurrentBuffer(ByteBuf buffer, int lengthToSkip) {
1182 if (lengthToSkip == 0) {
1183 return;
1184 }
1185 final int readerIndex = buffer.readerIndex();
1186 final int readableBytes = buffer.readableBytes();
1187 if (readableBytes == lengthToSkip) {
1188 buffer.readerIndex(readerIndex);
1189 buffer.writerIndex(readerIndex);
1190 return;
1191 }
1192 buffer.setBytes(readerIndex, buffer, readerIndex + lengthToSkip, readableBytes - lengthToSkip);
1193 buffer.readerIndex(readerIndex);
1194 buffer.writerIndex(readerIndex + readableBytes - lengthToSkip);
1195 }
1196
1197
1198
1199
1200
1201
1202
1203 private static boolean loadDataMultipartOptimized(ByteBuf undecodedChunk, String delimiter, HttpData httpData) {
1204 if (!undecodedChunk.isReadable()) {
1205 return false;
1206 }
1207 final int startReaderIndex = undecodedChunk.readerIndex();
1208 final byte[] bdelimiter = delimiter.getBytes(httpData.getCharset());
1209 int posDelimiter = HttpPostBodyUtil.findDelimiter(undecodedChunk, startReaderIndex, bdelimiter, true);
1210 if (posDelimiter < 0) {
1211
1212
1213
1214
1215 int readableBytes = undecodedChunk.readableBytes();
1216 int lastPosition = readableBytes - bdelimiter.length - 1;
1217 if (lastPosition < 0) {
1218
1219 lastPosition = 0;
1220 }
1221 posDelimiter = HttpPostBodyUtil.findLastLineBreak(undecodedChunk, startReaderIndex + lastPosition);
1222
1223
1224 if (posDelimiter < 0 &&
1225 httpData.definedLength() == httpData.length() + readableBytes - 1 &&
1226 undecodedChunk.getByte(readableBytes + startReaderIndex - 1) == HttpConstants.CR) {
1227
1228 lastPosition = 0;
1229 posDelimiter = readableBytes - 1;
1230 }
1231 if (posDelimiter < 0) {
1232
1233 ByteBuf content = undecodedChunk.copy();
1234 try {
1235 httpData.addContent(content, false);
1236 } catch (IOException e) {
1237 throw new ErrorDataDecoderException(e);
1238 }
1239 undecodedChunk.readerIndex(startReaderIndex);
1240 undecodedChunk.writerIndex(startReaderIndex);
1241 return false;
1242 }
1243
1244 posDelimiter += lastPosition;
1245 if (posDelimiter == 0) {
1246
1247 return false;
1248 }
1249
1250 ByteBuf content = undecodedChunk.copy(startReaderIndex, posDelimiter);
1251 try {
1252 httpData.addContent(content, false);
1253 } catch (IOException e) {
1254 throw new ErrorDataDecoderException(e);
1255 }
1256 rewriteCurrentBuffer(undecodedChunk, posDelimiter);
1257 return false;
1258 }
1259
1260 ByteBuf content = undecodedChunk.copy(startReaderIndex, posDelimiter);
1261 try {
1262 httpData.addContent(content, true);
1263 } catch (IOException e) {
1264 throw new ErrorDataDecoderException(e);
1265 }
1266 rewriteCurrentBuffer(undecodedChunk, posDelimiter);
1267 return true;
1268 }
1269
1270
1271
1272
1273
1274
1275 private static String cleanString(String field) {
1276 int size = field.length();
1277 StringBuilder sb = new StringBuilder(size);
1278 for (int i = 0; i < size; i++) {
1279 char nextChar = field.charAt(i);
1280 switch (nextChar) {
1281 case HttpConstants.COLON:
1282 case HttpConstants.COMMA:
1283 case HttpConstants.EQUALS:
1284 case HttpConstants.SEMICOLON:
1285 case HttpConstants.HT:
1286 sb.append(HttpConstants.SP_CHAR);
1287 break;
1288 case HttpConstants.DOUBLE_QUOTE:
1289 case HttpConstants.BACKSLASH:
1290
1291 break;
1292 default:
1293 if (nextChar < HttpConstants.SP || nextChar == HttpConstants.DEL) {
1294 sb.append(HttpConstants.SP_CHAR);
1295 } else {
1296 sb.append(nextChar);
1297 }
1298 break;
1299 }
1300 }
1301 return sb.toString().trim();
1302 }
1303
1304
1305
1306
1307
1308
1309
1310 private static String cleanMimeContentType(String contentType) {
1311 if (!FileUploadUtil.isContentTypeForMultiPartValid(contentType)) {
1312
1313
1314 return HttpPostBodyUtil.DEFAULT_BINARY_CONTENT_TYPE;
1315 }
1316 return contentType;
1317 }
1318
1319
1320
1321
1322
1323
1324 private boolean skipOneLine() {
1325 if (!undecodedChunk.isReadable()) {
1326 return false;
1327 }
1328 byte nextByte = undecodedChunk.readByte();
1329 if (nextByte == HttpConstants.CR) {
1330 if (!undecodedChunk.isReadable()) {
1331 undecodedChunk.readerIndex(undecodedChunk.readerIndex() - 1);
1332 return false;
1333 }
1334 nextByte = undecodedChunk.readByte();
1335 if (nextByte == HttpConstants.LF) {
1336 return true;
1337 }
1338 undecodedChunk.readerIndex(undecodedChunk.readerIndex() - 2);
1339 return false;
1340 }
1341 if (nextByte == HttpConstants.LF) {
1342 return true;
1343 }
1344 undecodedChunk.readerIndex(undecodedChunk.readerIndex() - 1);
1345 return false;
1346 }
1347
1348
1349
1350
1351
1352
1353
1354 private static String[] splitMultipartHeader(String sb) {
1355 ArrayList<String> headers = new ArrayList<String>(1);
1356 int nameStart;
1357 int nameEnd;
1358 int colonEnd;
1359 int valueStart;
1360 int valueEnd;
1361 nameStart = HttpPostBodyUtil.findNonWhitespace(sb, 0);
1362 for (nameEnd = nameStart; nameEnd < sb.length(); nameEnd++) {
1363 char ch = sb.charAt(nameEnd);
1364 if (ch == ':' || Character.isWhitespace(ch)) {
1365 break;
1366 }
1367 }
1368 for (colonEnd = nameEnd; colonEnd < sb.length(); colonEnd++) {
1369 if (sb.charAt(colonEnd) == ':') {
1370 colonEnd++;
1371 break;
1372 }
1373 }
1374 valueStart = HttpPostBodyUtil.findNonWhitespace(sb, colonEnd);
1375 valueEnd = HttpPostBodyUtil.findEndOfString(sb);
1376 headers.add(sb.substring(nameStart, nameEnd));
1377 String svalue = (valueStart >= valueEnd) ? StringUtil.EMPTY_STRING : sb.substring(valueStart, valueEnd);
1378 String[] values;
1379 if (svalue.indexOf(';') >= 0) {
1380 values = splitMultipartHeaderValues(svalue);
1381 } else {
1382 values = svalue.split(",");
1383 }
1384 for (String value : values) {
1385 headers.add(value.trim());
1386 }
1387 String[] array = new String[headers.size()];
1388 for (int i = 0; i < headers.size(); i++) {
1389 array[i] = headers.get(i);
1390 }
1391 return array;
1392 }
1393
1394
1395
1396
1397
1398 private static String[] splitMultipartHeaderValues(String svalue) {
1399 List<String> values = InternalThreadLocalMap.get().arrayList(1);
1400 boolean inQuote = false;
1401 boolean escapeNext = false;
1402 int start = 0;
1403 for (int i = 0; i < svalue.length(); i++) {
1404 char c = svalue.charAt(i);
1405 if (inQuote) {
1406 if (escapeNext) {
1407 escapeNext = false;
1408 } else {
1409 if (c == '\\') {
1410 escapeNext = true;
1411 } else if (c == '"') {
1412 inQuote = false;
1413 }
1414 }
1415 } else {
1416 if (c == '"') {
1417 inQuote = true;
1418 } else if (c == ';') {
1419 values.add(svalue.substring(start, i));
1420 start = i + 1;
1421 }
1422 }
1423 }
1424 values.add(svalue.substring(start));
1425 return values.toArray(EmptyArrays.EMPTY_STRINGS);
1426 }
1427
1428
1429
1430
1431
1432
1433
1434
1435 int getCurrentAllocatedCapacity() {
1436 return undecodedChunk.capacity();
1437 }
1438 }