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