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