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