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