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 e) {
568 throw new ErrorDataDecoderException(e);
569 } catch (UnsupportedCharsetException e) {
570 throw new ErrorDataDecoderException(e);
571 }
572 }
573 Attribute nameAttribute = currentFieldAttributes.get(HttpHeaderValues.NAME);
574 if (currentAttribute == null) {
575 Attribute lengthAttribute = currentFieldAttributes
576 .get(HttpHeaderNames.CONTENT_LENGTH);
577 long size;
578 try {
579 size = lengthAttribute != null? Long.parseLong(lengthAttribute
580 .getValue()) : 0L;
581 } catch (IOException e) {
582 throw new ErrorDataDecoderException(e);
583 } catch (NumberFormatException ignored) {
584 size = 0;
585 }
586 try {
587 if (size > 0) {
588 currentAttribute = factory.createAttribute(request,
589 cleanString(nameAttribute.getValue()), size);
590 } else {
591 currentAttribute = factory.createAttribute(request,
592 cleanString(nameAttribute.getValue()));
593 }
594 } catch (NullPointerException e) {
595 throw new ErrorDataDecoderException(e);
596 } catch (IllegalArgumentException e) {
597 throw new ErrorDataDecoderException(e);
598 } catch (IOException e) {
599 throw new ErrorDataDecoderException(e);
600 }
601 if (localCharset != null) {
602 currentAttribute.setCharset(localCharset);
603 }
604 }
605
606 if (!loadDataMultipartOptimized(undecodedChunk, multipartDataBoundary, currentAttribute)) {
607
608 return null;
609 }
610 Attribute finalAttribute = currentAttribute;
611 currentAttribute = null;
612 currentFieldAttributes = null;
613
614 currentStatus = MultiPartStatus.HEADERDELIMITER;
615 return finalAttribute;
616 }
617 case FILEUPLOAD: {
618
619 return getFileUpload(multipartDataBoundary);
620 }
621 case MIXEDDELIMITER: {
622
623
624 return findMultipartDelimiter(multipartMixedBoundary, MultiPartStatus.MIXEDDISPOSITION,
625 MultiPartStatus.HEADERDELIMITER);
626 }
627 case MIXEDDISPOSITION: {
628 return findMultipartDisposition();
629 }
630 case MIXEDFILEUPLOAD: {
631
632 return getFileUpload(multipartMixedBoundary);
633 }
634 case PREEPILOGUE:
635 return null;
636 case EPILOGUE:
637 return null;
638 default:
639 throw new ErrorDataDecoderException("Shouldn't reach here.");
640 }
641 }
642
643
644
645
646
647
648 private static void skipControlCharacters(ByteBuf undecodedChunk) {
649 if (!undecodedChunk.hasArray()) {
650 try {
651 skipControlCharactersStandard(undecodedChunk);
652 } catch (IndexOutOfBoundsException e1) {
653 throw new NotEnoughDataDecoderException(e1);
654 }
655 return;
656 }
657 SeekAheadOptimize sao = new SeekAheadOptimize(undecodedChunk);
658 while (sao.pos < sao.limit) {
659 char c = (char) (sao.bytes[sao.pos++] & 0xFF);
660 if (!Character.isISOControl(c) && !Character.isWhitespace(c)) {
661 sao.setReadPosition(1);
662 return;
663 }
664 }
665 throw new NotEnoughDataDecoderException("Access out of bounds");
666 }
667
668 private static void skipControlCharactersStandard(ByteBuf undecodedChunk) {
669 for (;;) {
670 char c = (char) undecodedChunk.readUnsignedByte();
671 if (!Character.isISOControl(c) && !Character.isWhitespace(c)) {
672 undecodedChunk.readerIndex(undecodedChunk.readerIndex() - 1);
673 break;
674 }
675 }
676 }
677
678
679
680
681
682
683
684
685
686
687
688
689
690 private InterfaceHttpData findMultipartDelimiter(String delimiter, MultiPartStatus dispositionStatus,
691 MultiPartStatus closeDelimiterStatus) {
692
693 int readerIndex = undecodedChunk.readerIndex();
694 try {
695 skipControlCharacters(undecodedChunk);
696 } catch (NotEnoughDataDecoderException ignored) {
697 undecodedChunk.readerIndex(readerIndex);
698 return null;
699 }
700 skipOneLine();
701 String newline;
702 try {
703 newline = readDelimiterOptimized(undecodedChunk, delimiter, charset);
704 } catch (NotEnoughDataDecoderException ignored) {
705 undecodedChunk.readerIndex(readerIndex);
706 return null;
707 }
708 if (newline.equals(delimiter)) {
709 currentStatus = dispositionStatus;
710 return decodeMultipart(dispositionStatus);
711 }
712 if (newline.equals(delimiter + "--")) {
713
714 currentStatus = closeDelimiterStatus;
715 if (currentStatus == MultiPartStatus.HEADERDELIMITER) {
716
717
718 currentFieldAttributes = null;
719 return decodeMultipart(MultiPartStatus.HEADERDELIMITER);
720 }
721 return null;
722 }
723 undecodedChunk.readerIndex(readerIndex);
724 throw new ErrorDataDecoderException("No Multipart delimiter found");
725 }
726
727
728
729
730
731
732
733 private InterfaceHttpData findMultipartDisposition() {
734 int readerIndex = undecodedChunk.readerIndex();
735 if (currentStatus == MultiPartStatus.DISPOSITION) {
736 currentFieldAttributes = new TreeMap<CharSequence, Attribute>(CaseIgnoringComparator.INSTANCE);
737 }
738
739 while (!skipOneLine()) {
740 String newline;
741 try {
742 skipControlCharacters(undecodedChunk);
743 newline = readLineOptimized(undecodedChunk, charset);
744 } catch (NotEnoughDataDecoderException ignored) {
745 undecodedChunk.readerIndex(readerIndex);
746 return null;
747 }
748 String[] contents = splitMultipartHeader(newline);
749 if (HttpHeaderNames.CONTENT_DISPOSITION.contentEqualsIgnoreCase(contents[0])) {
750 boolean checkSecondArg;
751 if (currentStatus == MultiPartStatus.DISPOSITION) {
752 checkSecondArg = HttpHeaderValues.FORM_DATA.contentEqualsIgnoreCase(contents[1]);
753 } else {
754 checkSecondArg = HttpHeaderValues.ATTACHMENT.contentEqualsIgnoreCase(contents[1])
755 || HttpHeaderValues.FILE.contentEqualsIgnoreCase(contents[1]);
756 }
757 if (checkSecondArg) {
758
759 for (int i = 2; i < contents.length; i++) {
760 String[] values = contents[i].split("=", 2);
761 Attribute attribute;
762 try {
763 attribute = getContentDispositionAttribute(values);
764 } catch (NullPointerException e) {
765 throw new ErrorDataDecoderException(e);
766 } catch (IllegalArgumentException e) {
767 throw new ErrorDataDecoderException(e);
768 }
769 currentFieldAttributes.put(attribute.getName(), attribute);
770 }
771 }
772 } else if (HttpHeaderNames.CONTENT_TRANSFER_ENCODING.contentEqualsIgnoreCase(contents[0])) {
773 Attribute attribute;
774 try {
775 attribute = factory.createAttribute(request, HttpHeaderNames.CONTENT_TRANSFER_ENCODING.toString(),
776 cleanString(contents[1]));
777 } catch (NullPointerException e) {
778 throw new ErrorDataDecoderException(e);
779 } catch (IllegalArgumentException e) {
780 throw new ErrorDataDecoderException(e);
781 }
782
783 currentFieldAttributes.put(HttpHeaderNames.CONTENT_TRANSFER_ENCODING, attribute);
784 } else if (HttpHeaderNames.CONTENT_LENGTH.contentEqualsIgnoreCase(contents[0])) {
785 Attribute attribute;
786 try {
787 attribute = factory.createAttribute(request, HttpHeaderNames.CONTENT_LENGTH.toString(),
788 cleanString(contents[1]));
789 } catch (NullPointerException e) {
790 throw new ErrorDataDecoderException(e);
791 } catch (IllegalArgumentException e) {
792 throw new ErrorDataDecoderException(e);
793 }
794
795 currentFieldAttributes.put(HttpHeaderNames.CONTENT_LENGTH, attribute);
796 } else if (HttpHeaderNames.CONTENT_TYPE.contentEqualsIgnoreCase(contents[0])) {
797
798 if (HttpHeaderValues.MULTIPART_MIXED.contentEqualsIgnoreCase(contents[1])) {
799 if (currentStatus == MultiPartStatus.DISPOSITION) {
800 String values = StringUtil.substringAfter(contents[2], '=');
801 multipartMixedBoundary = "--" + values;
802 currentStatus = MultiPartStatus.MIXEDDELIMITER;
803 return decodeMultipart(MultiPartStatus.MIXEDDELIMITER);
804 } else {
805 throw new ErrorDataDecoderException("Mixed Multipart found in a previous Mixed Multipart");
806 }
807 } else {
808 for (int i = 1; i < contents.length; i++) {
809 final String charsetHeader = HttpHeaderValues.CHARSET.toString();
810 if (contents[i].regionMatches(true, 0, charsetHeader, 0, charsetHeader.length())) {
811 String values = StringUtil.substringAfter(contents[i], '=');
812 Attribute attribute;
813 try {
814 attribute = factory.createAttribute(request, charsetHeader, cleanString(values));
815 } catch (NullPointerException e) {
816 throw new ErrorDataDecoderException(e);
817 } catch (IllegalArgumentException e) {
818 throw new ErrorDataDecoderException(e);
819 }
820 currentFieldAttributes.put(HttpHeaderValues.CHARSET, attribute);
821 } else if (contents[i].contains("=")) {
822 String name = StringUtil.substringBefore(contents[i], '=');
823 String values = StringUtil.substringAfter(contents[i], '=');
824 Attribute attribute;
825 try {
826 attribute = factory.createAttribute(request, cleanString(name), values);
827 } catch (NullPointerException e) {
828 throw new ErrorDataDecoderException(e);
829 } catch (IllegalArgumentException e) {
830 throw new ErrorDataDecoderException(e);
831 }
832 currentFieldAttributes.put(name, attribute);
833 } else {
834 Attribute attribute;
835 try {
836 attribute = factory.createAttribute(request,
837 cleanString(contents[0]), contents[i]);
838 } catch (NullPointerException e) {
839 throw new ErrorDataDecoderException(e);
840 } catch (IllegalArgumentException e) {
841 throw new ErrorDataDecoderException(e);
842 }
843 currentFieldAttributes.put(attribute.getName(), attribute);
844 }
845 }
846 }
847 }
848 }
849
850 Attribute filenameAttribute = currentFieldAttributes.get(HttpHeaderValues.FILENAME);
851 if (currentStatus == MultiPartStatus.DISPOSITION) {
852 if (filenameAttribute != null) {
853
854 currentStatus = MultiPartStatus.FILEUPLOAD;
855
856 return decodeMultipart(MultiPartStatus.FILEUPLOAD);
857 } else {
858
859 currentStatus = MultiPartStatus.FIELD;
860
861 return decodeMultipart(MultiPartStatus.FIELD);
862 }
863 } else {
864 if (filenameAttribute != null) {
865
866 currentStatus = MultiPartStatus.MIXEDFILEUPLOAD;
867
868 return decodeMultipart(MultiPartStatus.MIXEDFILEUPLOAD);
869 } else {
870
871 throw new ErrorDataDecoderException("Filename not found");
872 }
873 }
874 }
875
876 private static final String FILENAME_ENCODED = HttpHeaderValues.FILENAME.toString() + '*';
877
878 private Attribute getContentDispositionAttribute(String... values) {
879 String name = cleanString(values[0]);
880 String value = values[1];
881
882
883 if (HttpHeaderValues.FILENAME.contentEquals(name)) {
884
885 int last = value.length() - 1;
886 if (last > 0 &&
887 value.charAt(0) == HttpConstants.DOUBLE_QUOTE &&
888 value.charAt(last) == HttpConstants.DOUBLE_QUOTE) {
889 value = value.substring(1, last);
890 }
891 } else if (FILENAME_ENCODED.equals(name)) {
892 try {
893 name = HttpHeaderValues.FILENAME.toString();
894 String[] split = cleanString(value).split("'", 3);
895 value = QueryStringDecoder.decodeComponent(split[2], Charset.forName(split[0]));
896 } catch (ArrayIndexOutOfBoundsException e) {
897 throw new ErrorDataDecoderException(e);
898 } catch (UnsupportedCharsetException e) {
899 throw new ErrorDataDecoderException(e);
900 }
901 } else {
902
903 value = cleanString(value);
904 }
905 return factory.createAttribute(request, name, value);
906 }
907
908
909
910
911
912
913
914
915
916 protected InterfaceHttpData getFileUpload(String delimiter) {
917
918
919 Attribute encoding = currentFieldAttributes.get(HttpHeaderNames.CONTENT_TRANSFER_ENCODING);
920 Charset localCharset = charset;
921
922 TransferEncodingMechanism mechanism = TransferEncodingMechanism.BIT7;
923 if (encoding != null) {
924 String code;
925 try {
926
927
928
929
930 code = encoding.getValue().toLowerCase(Locale.US);
931 } catch (IOException e) {
932 throw new ErrorDataDecoderException(e);
933 }
934 if (code.equals(HttpPostBodyUtil.TransferEncodingMechanism.BIT7.value())) {
935 localCharset = CharsetUtil.US_ASCII;
936 } else if (code.equals(HttpPostBodyUtil.TransferEncodingMechanism.BIT8.value())) {
937 localCharset = CharsetUtil.ISO_8859_1;
938 mechanism = TransferEncodingMechanism.BIT8;
939 } else if (code.equals(HttpPostBodyUtil.TransferEncodingMechanism.BINARY.value())) {
940
941 mechanism = TransferEncodingMechanism.BINARY;
942 } else {
943 throw new ErrorDataDecoderException("TransferEncoding Unknown: " + code);
944 }
945 }
946 Attribute charsetAttribute = currentFieldAttributes.get(HttpHeaderValues.CHARSET);
947 if (charsetAttribute != null) {
948 try {
949 localCharset = Charset.forName(charsetAttribute.getValue());
950 } catch (IOException e) {
951 throw new ErrorDataDecoderException(e);
952 } catch (UnsupportedCharsetException e) {
953 throw new ErrorDataDecoderException(e);
954 }
955 }
956 if (currentFileUpload == null) {
957 Attribute filenameAttribute = currentFieldAttributes.get(HttpHeaderValues.FILENAME);
958 Attribute nameAttribute = currentFieldAttributes.get(HttpHeaderValues.NAME);
959 Attribute contentTypeAttribute = currentFieldAttributes.get(HttpHeaderNames.CONTENT_TYPE);
960 Attribute lengthAttribute = currentFieldAttributes.get(HttpHeaderNames.CONTENT_LENGTH);
961 long size;
962 try {
963 size = lengthAttribute != null ? Long.parseLong(lengthAttribute.getValue()) : 0L;
964 } catch (IOException e) {
965 throw new ErrorDataDecoderException(e);
966 } catch (NumberFormatException ignored) {
967 size = 0;
968 }
969 try {
970 String contentType;
971 if (contentTypeAttribute != null) {
972 contentType = contentTypeAttribute.getValue();
973 } else {
974 contentType = HttpPostBodyUtil.DEFAULT_BINARY_CONTENT_TYPE;
975 }
976 currentFileUpload = factory.createFileUpload(request,
977 cleanString(nameAttribute.getValue()), cleanString(filenameAttribute.getValue()),
978 cleanMimeContentType(contentType), mechanism.value(), localCharset,
979 size);
980 } catch (NullPointerException e) {
981 throw new ErrorDataDecoderException(e);
982 } catch (IllegalArgumentException e) {
983 throw new ErrorDataDecoderException(e);
984 } catch (IOException e) {
985 throw new ErrorDataDecoderException(e);
986 }
987 }
988
989 if (!loadDataMultipartOptimized(undecodedChunk, delimiter, currentFileUpload)) {
990
991 return null;
992 }
993 if (currentFileUpload.isCompleted()) {
994
995 if (currentStatus == MultiPartStatus.FILEUPLOAD) {
996 currentStatus = MultiPartStatus.HEADERDELIMITER;
997 currentFieldAttributes = null;
998 } else {
999 currentStatus = MultiPartStatus.MIXEDDELIMITER;
1000 cleanMixedAttributes();
1001 }
1002 FileUpload fileUpload = currentFileUpload;
1003 currentFileUpload = null;
1004 return fileUpload;
1005 }
1006
1007
1008
1009 return null;
1010 }
1011
1012
1013
1014
1015
1016 @Override
1017 public void destroy() {
1018
1019 cleanFiles();
1020
1021 for (InterfaceHttpData httpData : bodyListHttpData) {
1022
1023 if (httpData.refCnt() > 0) {
1024 httpData.release();
1025 }
1026 }
1027
1028 destroyed = true;
1029
1030 if (undecodedChunk != null && undecodedChunk.refCnt() > 0) {
1031 undecodedChunk.release();
1032 undecodedChunk = null;
1033 }
1034 }
1035
1036
1037
1038
1039 @Override
1040 public void cleanFiles() {
1041 checkDestroyed();
1042
1043 factory.cleanRequestHttpData(request);
1044 }
1045
1046
1047
1048
1049 @Override
1050 public void removeHttpDataFromClean(InterfaceHttpData data) {
1051 checkDestroyed();
1052
1053 factory.removeHttpDataFromClean(request, data);
1054 }
1055
1056
1057
1058
1059
1060 private void cleanMixedAttributes() {
1061 currentFieldAttributes.remove(HttpHeaderValues.CHARSET);
1062 currentFieldAttributes.remove(HttpHeaderNames.CONTENT_LENGTH);
1063 currentFieldAttributes.remove(HttpHeaderNames.CONTENT_TRANSFER_ENCODING);
1064 currentFieldAttributes.remove(HttpHeaderNames.CONTENT_TYPE);
1065 currentFieldAttributes.remove(HttpHeaderValues.FILENAME);
1066 }
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076 private static String readLineOptimized(ByteBuf undecodedChunk, Charset charset) {
1077 int readerIndex = undecodedChunk.readerIndex();
1078 ByteBuf line = null;
1079 try {
1080 if (undecodedChunk.isReadable()) {
1081 int posLfOrCrLf = HttpPostBodyUtil.findLineBreak(undecodedChunk, undecodedChunk.readerIndex());
1082 if (posLfOrCrLf <= 0) {
1083 throw new NotEnoughDataDecoderException();
1084 }
1085 try {
1086 line = undecodedChunk.alloc().heapBuffer(posLfOrCrLf);
1087 line.writeBytes(undecodedChunk, posLfOrCrLf);
1088
1089 byte nextByte = undecodedChunk.readByte();
1090 if (nextByte == HttpConstants.CR) {
1091
1092 undecodedChunk.readByte();
1093 }
1094 return line.toString(charset);
1095 } finally {
1096 line.release();
1097 }
1098 }
1099 } catch (IndexOutOfBoundsException e) {
1100 undecodedChunk.readerIndex(readerIndex);
1101 throw new NotEnoughDataDecoderException(e);
1102 }
1103 undecodedChunk.readerIndex(readerIndex);
1104 throw new NotEnoughDataDecoderException();
1105 }
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122 private static String readDelimiterOptimized(ByteBuf undecodedChunk, String delimiter, Charset charset) {
1123 final int readerIndex = undecodedChunk.readerIndex();
1124 final byte[] bdelimiter = delimiter.getBytes(charset);
1125 final int delimiterLength = bdelimiter.length;
1126 try {
1127 int delimiterPos = HttpPostBodyUtil.findDelimiter(undecodedChunk, readerIndex, bdelimiter, false);
1128 if (delimiterPos < 0) {
1129
1130 undecodedChunk.readerIndex(readerIndex);
1131 throw new NotEnoughDataDecoderException();
1132 }
1133 StringBuilder sb = new StringBuilder(delimiter);
1134 undecodedChunk.readerIndex(readerIndex + delimiterPos + delimiterLength);
1135
1136 if (undecodedChunk.isReadable()) {
1137 byte nextByte = undecodedChunk.readByte();
1138
1139 if (nextByte == HttpConstants.CR) {
1140 nextByte = undecodedChunk.readByte();
1141 if (nextByte == HttpConstants.LF) {
1142 return sb.toString();
1143 } else {
1144
1145
1146 undecodedChunk.readerIndex(readerIndex);
1147 throw new NotEnoughDataDecoderException();
1148 }
1149 } else if (nextByte == HttpConstants.LF) {
1150 return sb.toString();
1151 } else if (nextByte == '-') {
1152 sb.append('-');
1153
1154 nextByte = undecodedChunk.readByte();
1155 if (nextByte == '-') {
1156 sb.append('-');
1157
1158 if (undecodedChunk.isReadable()) {
1159 nextByte = undecodedChunk.readByte();
1160 if (nextByte == HttpConstants.CR) {
1161 nextByte = undecodedChunk.readByte();
1162 if (nextByte == HttpConstants.LF) {
1163 return sb.toString();
1164 } else {
1165
1166
1167 undecodedChunk.readerIndex(readerIndex);
1168 throw new NotEnoughDataDecoderException();
1169 }
1170 } else if (nextByte == HttpConstants.LF) {
1171 return sb.toString();
1172 } else {
1173
1174
1175
1176 undecodedChunk.readerIndex(undecodedChunk.readerIndex() - 1);
1177 return sb.toString();
1178 }
1179 }
1180
1181
1182
1183
1184 return sb.toString();
1185 }
1186
1187
1188 }
1189 }
1190 } catch (IndexOutOfBoundsException e) {
1191 undecodedChunk.readerIndex(readerIndex);
1192 throw new NotEnoughDataDecoderException(e);
1193 }
1194 undecodedChunk.readerIndex(readerIndex);
1195 throw new NotEnoughDataDecoderException();
1196 }
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207 private static void rewriteCurrentBuffer(ByteBuf buffer, int lengthToSkip) {
1208 if (lengthToSkip == 0) {
1209 return;
1210 }
1211 final int readerIndex = buffer.readerIndex();
1212 final int readableBytes = buffer.readableBytes();
1213 if (readableBytes == lengthToSkip) {
1214 buffer.readerIndex(readerIndex);
1215 buffer.writerIndex(readerIndex);
1216 return;
1217 }
1218 buffer.setBytes(readerIndex, buffer, readerIndex + lengthToSkip, readableBytes - lengthToSkip);
1219 buffer.readerIndex(readerIndex);
1220 buffer.writerIndex(readerIndex + readableBytes - lengthToSkip);
1221 }
1222
1223
1224
1225
1226
1227
1228
1229 private static boolean loadDataMultipartOptimized(ByteBuf undecodedChunk, String delimiter, HttpData httpData) {
1230 if (!undecodedChunk.isReadable()) {
1231 return false;
1232 }
1233 final int startReaderIndex = undecodedChunk.readerIndex();
1234 final byte[] bdelimiter = delimiter.getBytes(httpData.getCharset());
1235 int posDelimiter = HttpPostBodyUtil.findDelimiter(undecodedChunk, startReaderIndex, bdelimiter, true);
1236 if (posDelimiter < 0) {
1237
1238
1239
1240
1241 int readableBytes = undecodedChunk.readableBytes();
1242 int lastPosition = readableBytes - bdelimiter.length - 1;
1243 if (lastPosition < 0) {
1244
1245 lastPosition = 0;
1246 }
1247 posDelimiter = HttpPostBodyUtil.findLastLineBreak(undecodedChunk, startReaderIndex + lastPosition);
1248
1249
1250 if (posDelimiter < 0 &&
1251 httpData.definedLength() == httpData.length() + readableBytes - 1 &&
1252 undecodedChunk.getByte(readableBytes + startReaderIndex - 1) == HttpConstants.CR) {
1253
1254 lastPosition = 0;
1255 posDelimiter = readableBytes - 1;
1256 }
1257 if (posDelimiter < 0) {
1258
1259 ByteBuf content = undecodedChunk.copy();
1260 try {
1261 httpData.addContent(content, false);
1262 } catch (IOException e) {
1263 throw new ErrorDataDecoderException(e);
1264 }
1265 undecodedChunk.readerIndex(startReaderIndex);
1266 undecodedChunk.writerIndex(startReaderIndex);
1267 return false;
1268 }
1269
1270 posDelimiter += lastPosition;
1271 if (posDelimiter == 0) {
1272
1273 return false;
1274 }
1275
1276 ByteBuf content = undecodedChunk.copy(startReaderIndex, posDelimiter);
1277 try {
1278 httpData.addContent(content, false);
1279 } catch (IOException e) {
1280 throw new ErrorDataDecoderException(e);
1281 }
1282 rewriteCurrentBuffer(undecodedChunk, posDelimiter);
1283 return false;
1284 }
1285
1286 ByteBuf content = undecodedChunk.copy(startReaderIndex, posDelimiter);
1287 try {
1288 httpData.addContent(content, true);
1289 } catch (IOException e) {
1290 throw new ErrorDataDecoderException(e);
1291 }
1292 rewriteCurrentBuffer(undecodedChunk, posDelimiter);
1293 return true;
1294 }
1295
1296
1297
1298
1299
1300
1301 private static String cleanString(String field) {
1302 int size = field.length();
1303 StringBuilder sb = new StringBuilder(size);
1304 for (int i = 0; i < size; i++) {
1305 char nextChar = field.charAt(i);
1306 switch (nextChar) {
1307 case HttpConstants.COLON:
1308 case HttpConstants.COMMA:
1309 case HttpConstants.EQUALS:
1310 case HttpConstants.SEMICOLON:
1311 case HttpConstants.HT:
1312 sb.append(HttpConstants.SP_CHAR);
1313 break;
1314 case HttpConstants.DOUBLE_QUOTE:
1315 case HttpConstants.BACKSLASH:
1316
1317 break;
1318 default:
1319 if (nextChar < HttpConstants.SP || nextChar == HttpConstants.DEL) {
1320 sb.append(HttpConstants.SP_CHAR);
1321 } else {
1322 sb.append(nextChar);
1323 }
1324 break;
1325 }
1326 }
1327 return sb.toString().trim();
1328 }
1329
1330
1331
1332
1333
1334
1335
1336 private static String cleanMimeContentType(String contentType) {
1337 if (!FileUploadUtil.isContentTypeForMultiPartValid(contentType)) {
1338
1339
1340 return HttpPostBodyUtil.DEFAULT_BINARY_CONTENT_TYPE;
1341 }
1342 return contentType;
1343 }
1344
1345
1346
1347
1348
1349
1350 private boolean skipOneLine() {
1351 if (!undecodedChunk.isReadable()) {
1352 return false;
1353 }
1354 byte nextByte = undecodedChunk.readByte();
1355 if (nextByte == HttpConstants.CR) {
1356 if (!undecodedChunk.isReadable()) {
1357 undecodedChunk.readerIndex(undecodedChunk.readerIndex() - 1);
1358 return false;
1359 }
1360 nextByte = undecodedChunk.readByte();
1361 if (nextByte == HttpConstants.LF) {
1362 return true;
1363 }
1364 undecodedChunk.readerIndex(undecodedChunk.readerIndex() - 2);
1365 return false;
1366 }
1367 if (nextByte == HttpConstants.LF) {
1368 return true;
1369 }
1370 undecodedChunk.readerIndex(undecodedChunk.readerIndex() - 1);
1371 return false;
1372 }
1373
1374
1375
1376
1377
1378
1379
1380 private static String[] splitMultipartHeader(String sb) {
1381 ArrayList<String> headers = new ArrayList<String>(1);
1382 int nameStart;
1383 int nameEnd;
1384 int colonEnd;
1385 int valueStart;
1386 int valueEnd;
1387 nameStart = HttpPostBodyUtil.findNonWhitespace(sb, 0);
1388 for (nameEnd = nameStart; nameEnd < sb.length(); nameEnd++) {
1389 char ch = sb.charAt(nameEnd);
1390 if (ch == ':' || Character.isWhitespace(ch)) {
1391 break;
1392 }
1393 }
1394 for (colonEnd = nameEnd; colonEnd < sb.length(); colonEnd++) {
1395 if (sb.charAt(colonEnd) == ':') {
1396 colonEnd++;
1397 break;
1398 }
1399 }
1400 valueStart = HttpPostBodyUtil.findNonWhitespace(sb, colonEnd);
1401 valueEnd = HttpPostBodyUtil.findEndOfString(sb);
1402 headers.add(sb.substring(nameStart, nameEnd));
1403 String svalue = (valueStart >= valueEnd) ? StringUtil.EMPTY_STRING : sb.substring(valueStart, valueEnd);
1404 String[] values;
1405 if (svalue.indexOf(';') >= 0) {
1406 values = splitMultipartHeaderValues(svalue);
1407 } else {
1408 values = svalue.split(",");
1409 }
1410 for (String value : values) {
1411 headers.add(value.trim());
1412 }
1413 String[] array = new String[headers.size()];
1414 for (int i = 0; i < headers.size(); i++) {
1415 array[i] = headers.get(i);
1416 }
1417 return array;
1418 }
1419
1420
1421
1422
1423
1424 private static String[] splitMultipartHeaderValues(String svalue) {
1425 List<String> values = InternalThreadLocalMap.get().arrayList(1);
1426 boolean inQuote = false;
1427 boolean escapeNext = false;
1428 int start = 0;
1429 for (int i = 0; i < svalue.length(); i++) {
1430 char c = svalue.charAt(i);
1431 if (inQuote) {
1432 if (escapeNext) {
1433 escapeNext = false;
1434 } else {
1435 if (c == '\\') {
1436 escapeNext = true;
1437 } else if (c == '"') {
1438 inQuote = false;
1439 }
1440 }
1441 } else {
1442 if (c == '"') {
1443 inQuote = true;
1444 } else if (c == ';') {
1445 values.add(svalue.substring(start, i));
1446 start = i + 1;
1447 }
1448 }
1449 }
1450 values.add(svalue.substring(start));
1451 return values.toArray(EmptyArrays.EMPTY_STRINGS);
1452 }
1453
1454
1455
1456
1457
1458
1459
1460
1461 int getCurrentAllocatedCapacity() {
1462 return undecodedChunk.capacity();
1463 }
1464 }