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.buffer.ByteBufAllocator;
20 import io.netty.channel.ChannelHandlerContext;
21 import io.netty.handler.codec.DecoderResult;
22 import io.netty.handler.codec.http.DefaultFullHttpRequest;
23 import io.netty.handler.codec.http.DefaultHttpContent;
24 import io.netty.handler.codec.http.EmptyHttpHeaders;
25 import io.netty.handler.codec.http.FullHttpRequest;
26 import io.netty.handler.codec.http.HttpConstants;
27 import io.netty.handler.codec.http.HttpContent;
28 import io.netty.handler.codec.http.HttpHeaderNames;
29 import io.netty.handler.codec.http.HttpHeaderValues;
30 import io.netty.handler.codec.http.HttpHeaders;
31 import io.netty.handler.codec.http.HttpMethod;
32 import io.netty.handler.codec.http.HttpRequest;
33 import io.netty.handler.codec.http.HttpUtil;
34 import io.netty.handler.codec.http.HttpVersion;
35 import io.netty.handler.codec.http.LastHttpContent;
36 import io.netty.handler.stream.ChunkedInput;
37 import io.netty.util.internal.ObjectUtil;
38 import io.netty.util.internal.PlatformDependent;
39 import io.netty.util.internal.StringUtil;
40
41 import java.io.File;
42 import java.io.IOException;
43 import java.io.UnsupportedEncodingException;
44 import java.net.URLEncoder;
45 import java.nio.charset.Charset;
46 import java.util.ArrayList;
47 import java.util.List;
48 import java.util.ListIterator;
49 import java.util.Map;
50 import java.util.regex.Pattern;
51
52 import static io.netty.buffer.Unpooled.wrappedBuffer;
53 import static io.netty.util.internal.ObjectUtil.checkNotNull;
54 import static java.util.AbstractMap.SimpleImmutableEntry;
55
56
57
58
59
60
61
62
63
64
65
66
67
68 public class HttpPostRequestEncoder implements ChunkedInput<HttpContent> {
69
70
71
72
73 public enum EncoderMode {
74
75
76
77
78 RFC1738,
79
80
81
82
83 RFC3986,
84
85
86
87
88
89
90
91
92
93
94 HTML5
95 }
96
97 @SuppressWarnings("rawtypes")
98 private static final Map.Entry[] percentEncodings;
99
100 static {
101 percentEncodings = new Map.Entry[] {
102 new SimpleImmutableEntry<Pattern, String>(Pattern.compile("\\*"), "%2A"),
103 new SimpleImmutableEntry<Pattern, String>(Pattern.compile("\\+"), "%20"),
104 new SimpleImmutableEntry<Pattern, String>(Pattern.compile("~"), "%7E")
105 };
106 }
107
108
109
110
111 private final HttpDataFactory factory;
112
113
114
115
116 private final HttpRequest request;
117
118
119
120
121 private final Charset charset;
122
123
124
125
126 private boolean isChunked;
127
128
129
130
131 private final List<InterfaceHttpData> bodyListDatas;
132
133
134
135 final List<InterfaceHttpData> multipartHttpDatas;
136
137
138
139
140 private final boolean isMultipart;
141
142
143
144
145 String multipartDataBoundary;
146
147
148
149
150 String multipartMixedBoundary;
151
152
153
154 private boolean headerFinalized;
155
156 private final EncoderMode encoderMode;
157
158
159
160
161
162
163
164
165
166
167
168
169 public HttpPostRequestEncoder(HttpRequest request, boolean multipart) throws ErrorDataEncoderException {
170 this(new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE), request, multipart,
171 HttpConstants.DEFAULT_CHARSET, EncoderMode.RFC1738);
172 }
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187 public HttpPostRequestEncoder(HttpDataFactory factory, HttpRequest request, boolean multipart)
188 throws ErrorDataEncoderException {
189 this(factory, request, multipart, HttpConstants.DEFAULT_CHARSET, EncoderMode.RFC1738);
190 }
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209 public HttpPostRequestEncoder(
210 HttpDataFactory factory, HttpRequest request, boolean multipart, Charset charset,
211 EncoderMode encoderMode)
212 throws ErrorDataEncoderException {
213 this.request = checkNotNull(request, "request");
214 this.charset = checkNotNull(charset, "charset");
215 this.factory = checkNotNull(factory, "factory");
216 if (HttpMethod.TRACE.equals(request.method())) {
217 throw new ErrorDataEncoderException("Cannot create a Encoder if request is a TRACE");
218 }
219
220 bodyListDatas = new ArrayList<InterfaceHttpData>();
221
222 isLastChunk = false;
223 isLastChunkSent = false;
224 isMultipart = multipart;
225 multipartHttpDatas = new ArrayList<InterfaceHttpData>();
226 this.encoderMode = encoderMode;
227 if (isMultipart) {
228 initDataMultipart();
229 }
230 }
231
232
233
234
235 public void cleanFiles() {
236 factory.cleanRequestHttpData(request);
237 }
238
239
240
241
242 private boolean isLastChunk;
243
244
245
246 private boolean isLastChunkSent;
247
248
249
250 private FileUpload currentFileUpload;
251
252
253
254 private boolean duringMixedMode;
255
256
257
258 private long globalBodySize;
259
260
261
262 private long globalProgress;
263
264
265
266
267
268
269 public boolean isMultipart() {
270 return isMultipart;
271 }
272
273
274
275
276 private void initDataMultipart() {
277 multipartDataBoundary = getNewMultipartDelimiter();
278 }
279
280
281
282
283 private void initMixedMultipart() {
284 multipartMixedBoundary = getNewMultipartDelimiter();
285 }
286
287
288
289
290
291 private static String getNewMultipartDelimiter() {
292
293 return Long.toHexString(PlatformDependent.threadLocalRandom().nextLong());
294 }
295
296
297
298
299
300
301 public List<InterfaceHttpData> getBodyListAttributes() {
302 return bodyListDatas;
303 }
304
305
306
307
308
309
310
311
312
313 public void setBodyHttpDatas(List<InterfaceHttpData> datas) throws ErrorDataEncoderException {
314 ObjectUtil.checkNotNull(datas, "datas");
315 globalBodySize = 0;
316 bodyListDatas.clear();
317 currentFileUpload = null;
318 duringMixedMode = false;
319 multipartHttpDatas.clear();
320 for (InterfaceHttpData data : datas) {
321 addBodyHttpData(data);
322 }
323 }
324
325
326
327
328
329
330
331
332
333
334
335
336
337 public void addBodyAttribute(String name, String value) throws ErrorDataEncoderException {
338 String svalue = value != null? value : StringUtil.EMPTY_STRING;
339 Attribute data = factory.createAttribute(request, checkNotNull(name, "name"), svalue);
340 addBodyHttpData(data);
341 }
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359 public void addBodyFileUpload(String name, File file, String contentType, boolean isText)
360 throws ErrorDataEncoderException {
361 addBodyFileUpload(name, file.getName(), file, contentType, isText);
362 }
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383 public void addBodyFileUpload(String name, String filename, File file, String contentType, boolean isText)
384 throws ErrorDataEncoderException {
385 checkNotNull(name, "name");
386 checkNotNull(file, "file");
387 if (filename == null) {
388 filename = StringUtil.EMPTY_STRING;
389 }
390 String scontentType = contentType;
391 String contentTransferEncoding = null;
392 if (contentType == null) {
393 if (isText) {
394 scontentType = HttpPostBodyUtil.DEFAULT_TEXT_CONTENT_TYPE;
395 } else {
396 scontentType = HttpPostBodyUtil.DEFAULT_BINARY_CONTENT_TYPE;
397 }
398 }
399 if (!isText) {
400 contentTransferEncoding = HttpPostBodyUtil.TransferEncodingMechanism.BINARY.value();
401 }
402 FileUpload fileUpload = factory.createFileUpload(request, name, filename, scontentType,
403 contentTransferEncoding, null, file.length());
404 try {
405 fileUpload.setContent(file);
406 } catch (IOException e) {
407 throw new ErrorDataEncoderException(e);
408 }
409 addBodyHttpData(fileUpload);
410 }
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428 public void addBodyFileUploads(String name, File[] file, String[] contentType, boolean[] isText)
429 throws ErrorDataEncoderException {
430 if (file.length != contentType.length && file.length != isText.length) {
431 throw new IllegalArgumentException("Different array length");
432 }
433 for (int i = 0; i < file.length; i++) {
434 addBodyFileUpload(name, file[i], contentType[i], isText[i]);
435 }
436 }
437
438
439
440
441
442
443
444
445
446 public void addBodyHttpData(InterfaceHttpData data) throws ErrorDataEncoderException {
447 if (headerFinalized) {
448 throw new ErrorDataEncoderException("Cannot add value once finalized");
449 }
450 bodyListDatas.add(checkNotNull(data, "data"));
451 if (!isMultipart) {
452 if (data instanceof Attribute) {
453 Attribute attribute = (Attribute) data;
454 try {
455
456 String key = encodeAttribute(attribute.getName(), charset);
457 String value = encodeAttribute(attribute.getValue(), charset);
458 Attribute newattribute = factory.createAttribute(request, key, value);
459 multipartHttpDatas.add(newattribute);
460 globalBodySize += newattribute.getName().length() + 1 + newattribute.length() + 1;
461 } catch (IOException e) {
462 throw new ErrorDataEncoderException(e);
463 }
464 } else if (data instanceof FileUpload) {
465
466 FileUpload fileUpload = (FileUpload) data;
467
468 String key = encodeAttribute(fileUpload.getName(), charset);
469 String value = encodeAttribute(fileUpload.getFilename(), charset);
470 Attribute newattribute = factory.createAttribute(request, key, value);
471 multipartHttpDatas.add(newattribute);
472 globalBodySize += newattribute.getName().length() + 1 + newattribute.length() + 1;
473 }
474 return;
475 }
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508 if (data instanceof Attribute) {
509 if (duringMixedMode) {
510 InternalAttribute internal = new InternalAttribute(charset);
511 internal.addValue("\r\n--" + multipartMixedBoundary + "--");
512 multipartHttpDatas.add(internal);
513 multipartMixedBoundary = null;
514 currentFileUpload = null;
515 duringMixedMode = false;
516 }
517 InternalAttribute internal = new InternalAttribute(charset);
518 if (!multipartHttpDatas.isEmpty()) {
519
520 internal.addValue("\r\n");
521 }
522 internal.addValue("--" + multipartDataBoundary + "\r\n");
523
524 Attribute attribute = (Attribute) data;
525 internal.addValue(HttpHeaderNames.CONTENT_DISPOSITION + ": " + HttpHeaderValues.FORM_DATA + "; "
526 + HttpHeaderValues.NAME + "=\"" + attribute.getName() + "\"\r\n");
527
528 internal.addValue(HttpHeaderNames.CONTENT_LENGTH + ": " +
529 attribute.length() + "\r\n");
530 Charset localcharset = attribute.getCharset();
531 if (localcharset != null) {
532
533 internal.addValue(HttpHeaderNames.CONTENT_TYPE + ": " +
534 HttpPostBodyUtil.DEFAULT_TEXT_CONTENT_TYPE + "; " +
535 HttpHeaderValues.CHARSET + '='
536 + localcharset.name() + "\r\n");
537 }
538
539 internal.addValue("\r\n");
540 multipartHttpDatas.add(internal);
541 multipartHttpDatas.add(data);
542 globalBodySize += attribute.length() + internal.size();
543 } else if (data instanceof FileUpload) {
544 FileUpload fileUpload = (FileUpload) data;
545 InternalAttribute internal = new InternalAttribute(charset);
546 if (!multipartHttpDatas.isEmpty()) {
547
548 internal.addValue("\r\n");
549 }
550 boolean localMixed;
551 if (duringMixedMode) {
552 if (currentFileUpload != null && currentFileUpload.getName().equals(fileUpload.getName())) {
553
554
555 localMixed = true;
556 } else {
557
558
559
560
561
562 internal.addValue("--" + multipartMixedBoundary + "--");
563 multipartHttpDatas.add(internal);
564 multipartMixedBoundary = null;
565
566
567 internal = new InternalAttribute(charset);
568 internal.addValue("\r\n");
569 localMixed = false;
570
571 currentFileUpload = fileUpload;
572 duringMixedMode = false;
573 }
574 } else {
575 if (encoderMode != EncoderMode.HTML5 && currentFileUpload != null
576 && currentFileUpload.getName().equals(fileUpload.getName())) {
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597 initMixedMultipart();
598 InternalAttribute pastAttribute = (InternalAttribute) multipartHttpDatas.get(multipartHttpDatas
599 .size() - 2);
600
601 globalBodySize -= pastAttribute.size();
602 StringBuilder replacement = new StringBuilder(
603 139 + multipartDataBoundary.length() + multipartMixedBoundary.length() * 2 +
604 fileUpload.getFilename().length() + fileUpload.getName().length())
605
606 .append("--")
607 .append(multipartDataBoundary)
608 .append("\r\n")
609
610 .append(HttpHeaderNames.CONTENT_DISPOSITION)
611 .append(": ")
612 .append(HttpHeaderValues.FORM_DATA)
613 .append("; ")
614 .append(HttpHeaderValues.NAME)
615 .append("=\"")
616 .append(fileUpload.getName())
617 .append("\"\r\n")
618
619 .append(HttpHeaderNames.CONTENT_TYPE)
620 .append(": ")
621 .append(HttpHeaderValues.MULTIPART_MIXED)
622 .append("; ")
623 .append(HttpHeaderValues.BOUNDARY)
624 .append('=')
625 .append(multipartMixedBoundary)
626 .append("\r\n\r\n")
627
628 .append("--")
629 .append(multipartMixedBoundary)
630 .append("\r\n")
631
632 .append(HttpHeaderNames.CONTENT_DISPOSITION)
633 .append(": ")
634 .append(HttpHeaderValues.ATTACHMENT);
635
636 if (!fileUpload.getFilename().isEmpty()) {
637 replacement.append("; ")
638 .append(HttpHeaderValues.FILENAME)
639 .append("=\"")
640 .append(currentFileUpload.getFilename())
641 .append('"');
642 }
643
644 replacement.append("\r\n");
645
646 pastAttribute.setValue(replacement.toString(), 1);
647 pastAttribute.setValue("", 2);
648
649
650 globalBodySize += pastAttribute.size();
651
652
653
654
655
656 localMixed = true;
657 duringMixedMode = true;
658 } else {
659
660
661
662 localMixed = false;
663 currentFileUpload = fileUpload;
664 duringMixedMode = false;
665 }
666 }
667
668 if (localMixed) {
669
670
671 internal.addValue("--" + multipartMixedBoundary + "\r\n");
672
673 if (fileUpload.getFilename().isEmpty()) {
674
675 internal.addValue(HttpHeaderNames.CONTENT_DISPOSITION + ": "
676 + HttpHeaderValues.ATTACHMENT + "\r\n");
677 } else {
678
679 internal.addValue(HttpHeaderNames.CONTENT_DISPOSITION + ": "
680 + HttpHeaderValues.ATTACHMENT + "; "
681 + HttpHeaderValues.FILENAME + "=\"" + fileUpload.getFilename() + "\"\r\n");
682 }
683 } else {
684 internal.addValue("--" + multipartDataBoundary + "\r\n");
685
686 if (fileUpload.getFilename().isEmpty()) {
687
688 internal.addValue(HttpHeaderNames.CONTENT_DISPOSITION + ": " + HttpHeaderValues.FORM_DATA + "; "
689 + HttpHeaderValues.NAME + "=\"" + fileUpload.getName() + "\"\r\n");
690 } else {
691
692
693 internal.addValue(HttpHeaderNames.CONTENT_DISPOSITION + ": " + HttpHeaderValues.FORM_DATA + "; "
694 + HttpHeaderValues.NAME + "=\"" + fileUpload.getName() + "\"; "
695 + HttpHeaderValues.FILENAME + "=\"" + fileUpload.getFilename() + "\"\r\n");
696 }
697 }
698
699 internal.addValue(HttpHeaderNames.CONTENT_LENGTH + ": " +
700 fileUpload.length() + "\r\n");
701
702
703
704 internal.addValue(HttpHeaderNames.CONTENT_TYPE + ": " + fileUpload.getContentType());
705 String contentTransferEncoding = fileUpload.getContentTransferEncoding();
706 if (contentTransferEncoding != null
707 && contentTransferEncoding.equals(HttpPostBodyUtil.TransferEncodingMechanism.BINARY.value())) {
708 internal.addValue("\r\n" + HttpHeaderNames.CONTENT_TRANSFER_ENCODING + ": "
709 + HttpPostBodyUtil.TransferEncodingMechanism.BINARY.value() + "\r\n\r\n");
710 } else if (fileUpload.getCharset() != null) {
711 internal.addValue("; " + HttpHeaderValues.CHARSET + '=' + fileUpload.getCharset().name() + "\r\n\r\n");
712 } else {
713 internal.addValue("\r\n\r\n");
714 }
715 multipartHttpDatas.add(internal);
716 multipartHttpDatas.add(data);
717 globalBodySize += fileUpload.length() + internal.size();
718 }
719 }
720
721
722
723
724 private ListIterator<InterfaceHttpData> iterator;
725
726
727
728
729
730
731
732
733
734
735
736 public HttpRequest finalizeRequest() throws ErrorDataEncoderException {
737
738 if (!headerFinalized) {
739 if (isMultipart) {
740 InternalAttribute internal = new InternalAttribute(charset);
741 if (duringMixedMode) {
742 internal.addValue("\r\n--" + multipartMixedBoundary + "--");
743 }
744 internal.addValue("\r\n--" + multipartDataBoundary + "--\r\n");
745 multipartHttpDatas.add(internal);
746 multipartMixedBoundary = null;
747 currentFileUpload = null;
748 duringMixedMode = false;
749 globalBodySize += internal.size();
750 }
751 headerFinalized = true;
752 } else {
753 throw new ErrorDataEncoderException("Header already encoded");
754 }
755
756 HttpHeaders headers = request.headers();
757 List<String> contentTypes = headers.getAll(HttpHeaderNames.CONTENT_TYPE);
758 List<String> transferEncoding = headers.getAll(HttpHeaderNames.TRANSFER_ENCODING);
759 if (contentTypes != null) {
760 headers.remove(HttpHeaderNames.CONTENT_TYPE);
761 for (String contentType : contentTypes) {
762
763 String lowercased = contentType.toLowerCase();
764 if (lowercased.startsWith(HttpHeaderValues.MULTIPART_FORM_DATA.toString()) ||
765 lowercased.startsWith(HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED.toString())) {
766
767 } else {
768 headers.add(HttpHeaderNames.CONTENT_TYPE, contentType);
769 }
770 }
771 }
772 if (isMultipart) {
773 String value = HttpHeaderValues.MULTIPART_FORM_DATA + "; " + HttpHeaderValues.BOUNDARY + '='
774 + multipartDataBoundary;
775 headers.add(HttpHeaderNames.CONTENT_TYPE, value);
776 } else {
777
778 headers.add(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED);
779 }
780
781 long realSize = globalBodySize;
782 if (!isMultipart) {
783 realSize -= 1;
784 }
785 iterator = multipartHttpDatas.listIterator();
786
787 headers.set(HttpHeaderNames.CONTENT_LENGTH, String.valueOf(realSize));
788 if (realSize > HttpPostBodyUtil.chunkSize || isMultipart) {
789 isChunked = true;
790 if (transferEncoding != null) {
791 headers.remove(HttpHeaderNames.TRANSFER_ENCODING);
792 for (CharSequence v : transferEncoding) {
793 if (HttpHeaderValues.CHUNKED.contentEqualsIgnoreCase(v)) {
794
795 } else {
796 headers.add(HttpHeaderNames.TRANSFER_ENCODING, v);
797 }
798 }
799 }
800 HttpUtil.setTransferEncodingChunked(request, true);
801
802
803 return new WrappedHttpRequest(request);
804 } else {
805
806 HttpContent chunk = nextChunk();
807 if (request instanceof FullHttpRequest) {
808 FullHttpRequest fullRequest = (FullHttpRequest) request;
809 ByteBuf chunkContent = chunk.content();
810 if (fullRequest.content() != chunkContent) {
811 fullRequest.content().clear().writeBytes(chunkContent);
812 chunkContent.release();
813 }
814 return fullRequest;
815 } else {
816 return new WrappedFullHttpRequest(request, chunk);
817 }
818 }
819 }
820
821
822
823
824 public boolean isChunked() {
825 return isChunked;
826 }
827
828
829
830
831
832
833
834
835 @SuppressWarnings("unchecked")
836 private String encodeAttribute(String s, Charset charset) throws ErrorDataEncoderException {
837 if (s == null) {
838 return "";
839 }
840 try {
841 String encoded = URLEncoder.encode(s, charset.name());
842 if (encoderMode == EncoderMode.RFC3986) {
843 for (Map.Entry<Pattern, String> entry : percentEncodings) {
844 String replacement = entry.getValue();
845 encoded = entry.getKey().matcher(encoded).replaceAll(replacement);
846 }
847 }
848 return encoded;
849 } catch (UnsupportedEncodingException e) {
850 throw new ErrorDataEncoderException(charset.name(), e);
851 }
852 }
853
854
855
856
857 private ByteBuf currentBuffer;
858
859
860
861 private InterfaceHttpData currentData;
862
863
864
865 private boolean isKey = true;
866
867
868
869
870
871 private ByteBuf fillByteBuf() {
872 int length = currentBuffer.readableBytes();
873 if (length > HttpPostBodyUtil.chunkSize) {
874 return currentBuffer.readRetainedSlice(HttpPostBodyUtil.chunkSize);
875 } else {
876
877 ByteBuf slice = currentBuffer;
878 currentBuffer = null;
879 return slice;
880 }
881 }
882
883
884
885
886
887
888
889
890
891
892
893 private HttpContent encodeNextChunkMultipart(int sizeleft) throws ErrorDataEncoderException {
894 if (currentData == null) {
895 return null;
896 }
897 ByteBuf buffer;
898 if (currentData instanceof InternalAttribute) {
899 buffer = ((InternalAttribute) currentData).toByteBuf();
900 currentData = null;
901 } else {
902 try {
903 buffer = ((HttpData) currentData).getChunk(sizeleft);
904 } catch (IOException e) {
905 throw new ErrorDataEncoderException(e);
906 }
907 if (buffer.capacity() == 0) {
908
909 currentData = null;
910 return null;
911 }
912 }
913 if (currentBuffer == null) {
914 currentBuffer = buffer;
915 } else {
916 currentBuffer = wrappedBuffer(currentBuffer, buffer);
917 }
918 if (currentBuffer.readableBytes() < HttpPostBodyUtil.chunkSize) {
919 currentData = null;
920 return null;
921 }
922 buffer = fillByteBuf();
923 return new DefaultHttpContent(buffer);
924 }
925
926
927
928
929
930
931
932
933
934
935
936 private HttpContent encodeNextChunkUrlEncoded(int sizeleft) throws ErrorDataEncoderException {
937 if (currentData == null) {
938 return null;
939 }
940 int size = sizeleft;
941 ByteBuf buffer;
942
943
944 if (isKey) {
945 String key = currentData.getName();
946 buffer = wrappedBuffer(key.getBytes(charset));
947 isKey = false;
948 if (currentBuffer == null) {
949 currentBuffer = wrappedBuffer(buffer, wrappedBuffer("=".getBytes(charset)));
950 } else {
951 currentBuffer = wrappedBuffer(currentBuffer, buffer, wrappedBuffer("=".getBytes(charset)));
952 }
953
954 size -= buffer.readableBytes() + 1;
955 if (currentBuffer.readableBytes() >= HttpPostBodyUtil.chunkSize) {
956 buffer = fillByteBuf();
957 return new DefaultHttpContent(buffer);
958 }
959 }
960
961
962 try {
963 buffer = ((HttpData) currentData).getChunk(size);
964 } catch (IOException e) {
965 throw new ErrorDataEncoderException(e);
966 }
967
968
969 ByteBuf delimiter = null;
970 if (buffer.readableBytes() < size) {
971 isKey = true;
972 delimiter = iterator.hasNext() ? wrappedBuffer("&".getBytes(charset)) : null;
973 }
974
975
976 if (buffer.capacity() == 0) {
977 currentData = null;
978 if (currentBuffer == null) {
979 if (delimiter == null) {
980 return null;
981 } else {
982 currentBuffer = delimiter;
983 }
984 } else {
985 if (delimiter != null) {
986 currentBuffer = wrappedBuffer(currentBuffer, delimiter);
987 }
988 }
989 if (currentBuffer.readableBytes() >= HttpPostBodyUtil.chunkSize) {
990 buffer = fillByteBuf();
991 return new DefaultHttpContent(buffer);
992 }
993 return null;
994 }
995
996
997 if (currentBuffer == null) {
998 if (delimiter != null) {
999 currentBuffer = wrappedBuffer(buffer, delimiter);
1000 } else {
1001 currentBuffer = buffer;
1002 }
1003 } else {
1004 if (delimiter != null) {
1005 currentBuffer = wrappedBuffer(currentBuffer, buffer, delimiter);
1006 } else {
1007 currentBuffer = wrappedBuffer(currentBuffer, buffer);
1008 }
1009 }
1010
1011
1012 if (currentBuffer.readableBytes() < HttpPostBodyUtil.chunkSize) {
1013 currentData = null;
1014 isKey = true;
1015 return null;
1016 }
1017
1018 buffer = fillByteBuf();
1019 return new DefaultHttpContent(buffer);
1020 }
1021
1022 @Override
1023 public void close() throws Exception {
1024
1025
1026 }
1027
1028 @Deprecated
1029 @Override
1030 public HttpContent readChunk(ChannelHandlerContext ctx) throws Exception {
1031 return readChunk(ctx.alloc());
1032 }
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042 @Override
1043 public HttpContent readChunk(ByteBufAllocator allocator) throws Exception {
1044 if (isLastChunkSent) {
1045 return null;
1046 } else {
1047 HttpContent nextChunk = nextChunk();
1048 globalProgress += nextChunk.content().readableBytes();
1049 return nextChunk;
1050 }
1051 }
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061 private HttpContent nextChunk() throws ErrorDataEncoderException {
1062 if (isLastChunk) {
1063 isLastChunkSent = true;
1064 return LastHttpContent.EMPTY_LAST_CONTENT;
1065 }
1066
1067 int size = calculateRemainingSize();
1068 if (size <= 0) {
1069
1070 ByteBuf buffer = fillByteBuf();
1071 return new DefaultHttpContent(buffer);
1072 }
1073
1074 if (currentData != null) {
1075
1076 HttpContent chunk;
1077 if (isMultipart) {
1078 chunk = encodeNextChunkMultipart(size);
1079 } else {
1080 chunk = encodeNextChunkUrlEncoded(size);
1081 }
1082 if (chunk != null) {
1083
1084 return chunk;
1085 }
1086 size = calculateRemainingSize();
1087 }
1088 if (!iterator.hasNext()) {
1089 return lastChunk();
1090 }
1091 while (size > 0 && iterator.hasNext()) {
1092 currentData = iterator.next();
1093 HttpContent chunk;
1094 if (isMultipart) {
1095 chunk = encodeNextChunkMultipart(size);
1096 } else {
1097 chunk = encodeNextChunkUrlEncoded(size);
1098 }
1099 if (chunk == null) {
1100
1101 size = calculateRemainingSize();
1102 continue;
1103 }
1104
1105 return chunk;
1106 }
1107
1108 return lastChunk();
1109 }
1110
1111 private int calculateRemainingSize() {
1112 int size = HttpPostBodyUtil.chunkSize;
1113 if (currentBuffer != null) {
1114 size -= currentBuffer.readableBytes();
1115 }
1116 return size;
1117 }
1118
1119 private HttpContent lastChunk() {
1120 isLastChunk = true;
1121 if (currentBuffer == null) {
1122 isLastChunkSent = true;
1123
1124 return LastHttpContent.EMPTY_LAST_CONTENT;
1125 }
1126
1127 ByteBuf buffer = currentBuffer;
1128 currentBuffer = null;
1129 return new DefaultHttpContent(buffer);
1130 }
1131
1132 @Override
1133 public boolean isEndOfInput() throws Exception {
1134 return isLastChunkSent;
1135 }
1136
1137 @Override
1138 public long length() {
1139 return isMultipart? globalBodySize : globalBodySize - 1;
1140 }
1141
1142 @Override
1143 public long progress() {
1144 return globalProgress;
1145 }
1146
1147
1148
1149
1150 public static class ErrorDataEncoderException extends Exception {
1151 private static final long serialVersionUID = 5020247425493164465L;
1152
1153 public ErrorDataEncoderException() {
1154 }
1155
1156 public ErrorDataEncoderException(String msg) {
1157 super(msg);
1158 }
1159
1160 public ErrorDataEncoderException(Throwable cause) {
1161 super(cause);
1162 }
1163
1164 public ErrorDataEncoderException(String msg, Throwable cause) {
1165 super(msg, cause);
1166 }
1167 }
1168
1169 private static class WrappedHttpRequest implements HttpRequest {
1170 private final HttpRequest request;
1171 WrappedHttpRequest(HttpRequest request) {
1172 this.request = request;
1173 }
1174
1175 @Override
1176 public HttpRequest setProtocolVersion(HttpVersion version) {
1177 request.setProtocolVersion(version);
1178 return this;
1179 }
1180
1181 @Override
1182 public HttpRequest setMethod(HttpMethod method) {
1183 request.setMethod(method);
1184 return this;
1185 }
1186
1187 @Override
1188 public HttpRequest setUri(String uri) {
1189 request.setUri(uri);
1190 return this;
1191 }
1192
1193 @Override
1194 public HttpMethod getMethod() {
1195 return request.method();
1196 }
1197
1198 @Override
1199 public HttpMethod method() {
1200 return request.method();
1201 }
1202
1203 @Override
1204 public String getUri() {
1205 return request.uri();
1206 }
1207
1208 @Override
1209 public String uri() {
1210 return request.uri();
1211 }
1212
1213 @Override
1214 public HttpVersion getProtocolVersion() {
1215 return request.protocolVersion();
1216 }
1217
1218 @Override
1219 public HttpVersion protocolVersion() {
1220 return request.protocolVersion();
1221 }
1222
1223 @Override
1224 public HttpHeaders headers() {
1225 return request.headers();
1226 }
1227
1228 @Override
1229 public DecoderResult decoderResult() {
1230 return request.decoderResult();
1231 }
1232
1233 @Override
1234 @Deprecated
1235 public DecoderResult getDecoderResult() {
1236 return request.getDecoderResult();
1237 }
1238
1239 @Override
1240 public void setDecoderResult(DecoderResult result) {
1241 request.setDecoderResult(result);
1242 }
1243 }
1244
1245 private static final class WrappedFullHttpRequest extends WrappedHttpRequest implements FullHttpRequest {
1246 private final HttpContent content;
1247
1248 private WrappedFullHttpRequest(HttpRequest request, HttpContent content) {
1249 super(request);
1250 this.content = content;
1251 }
1252
1253 @Override
1254 public FullHttpRequest setProtocolVersion(HttpVersion version) {
1255 super.setProtocolVersion(version);
1256 return this;
1257 }
1258
1259 @Override
1260 public FullHttpRequest setMethod(HttpMethod method) {
1261 super.setMethod(method);
1262 return this;
1263 }
1264
1265 @Override
1266 public FullHttpRequest setUri(String uri) {
1267 super.setUri(uri);
1268 return this;
1269 }
1270
1271 @Override
1272 public FullHttpRequest copy() {
1273 return replace(content().copy());
1274 }
1275
1276 @Override
1277 public FullHttpRequest duplicate() {
1278 return replace(content().duplicate());
1279 }
1280
1281 @Override
1282 public FullHttpRequest retainedDuplicate() {
1283 return replace(content().retainedDuplicate());
1284 }
1285
1286 @Override
1287 public FullHttpRequest replace(ByteBuf content) {
1288 DefaultFullHttpRequest duplicate = new DefaultFullHttpRequest(protocolVersion(), method(), uri(), content);
1289 duplicate.headers().set(headers());
1290 duplicate.trailingHeaders().set(trailingHeaders());
1291 return duplicate;
1292 }
1293
1294 @Override
1295 public FullHttpRequest retain(int increment) {
1296 content.retain(increment);
1297 return this;
1298 }
1299
1300 @Override
1301 public FullHttpRequest retain() {
1302 content.retain();
1303 return this;
1304 }
1305
1306 @Override
1307 public FullHttpRequest touch() {
1308 content.touch();
1309 return this;
1310 }
1311
1312 @Override
1313 public FullHttpRequest touch(Object hint) {
1314 content.touch(hint);
1315 return this;
1316 }
1317
1318 @Override
1319 public ByteBuf content() {
1320 return content.content();
1321 }
1322
1323 @Override
1324 public HttpHeaders trailingHeaders() {
1325 if (content instanceof LastHttpContent) {
1326 return ((LastHttpContent) content).trailingHeaders();
1327 } else {
1328 return EmptyHttpHeaders.INSTANCE;
1329 }
1330 }
1331
1332 @Override
1333 public int refCnt() {
1334 return content.refCnt();
1335 }
1336
1337 @Override
1338 public boolean release() {
1339 return content.release();
1340 }
1341
1342 @Override
1343 public boolean release(int decrement) {
1344 return content.release(decrement);
1345 }
1346 }
1347 }