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 currentData = null;
973 delimiter = iterator.hasNext() ? wrappedBuffer("&".getBytes(charset)) : null;
974 }
975
976
977 if (buffer.capacity() == 0) {
978 isKey = true;
979 currentData = null;
980 if (currentBuffer == null) {
981 if (delimiter == null) {
982 return null;
983 } else {
984 currentBuffer = delimiter;
985 }
986 } else {
987 if (delimiter != null) {
988 currentBuffer = wrappedBuffer(currentBuffer, delimiter);
989 }
990 }
991 if (currentBuffer.readableBytes() >= HttpPostBodyUtil.chunkSize) {
992 buffer = fillByteBuf();
993 return new DefaultHttpContent(buffer);
994 }
995 return null;
996 }
997
998
999 if (currentBuffer == null) {
1000 if (delimiter != null) {
1001 currentBuffer = wrappedBuffer(buffer, delimiter);
1002 } else {
1003 currentBuffer = buffer;
1004 }
1005 } else {
1006 if (delimiter != null) {
1007 currentBuffer = wrappedBuffer(currentBuffer, buffer, delimiter);
1008 } else {
1009 currentBuffer = wrappedBuffer(currentBuffer, buffer);
1010 }
1011 }
1012
1013 if (currentBuffer.readableBytes() >= HttpPostBodyUtil.chunkSize) {
1014 return new DefaultHttpContent(fillByteBuf());
1015 }
1016 return null;
1017 }
1018
1019 @Override
1020 public void close() throws Exception {
1021
1022
1023 }
1024
1025 @Deprecated
1026 @Override
1027 public HttpContent readChunk(ChannelHandlerContext ctx) throws Exception {
1028 return readChunk(ctx.alloc());
1029 }
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039 @Override
1040 public HttpContent readChunk(ByteBufAllocator allocator) throws Exception {
1041 if (isLastChunkSent) {
1042 return null;
1043 } else {
1044 HttpContent nextChunk = nextChunk();
1045 globalProgress += nextChunk.content().readableBytes();
1046 return nextChunk;
1047 }
1048 }
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058 private HttpContent nextChunk() throws ErrorDataEncoderException {
1059 if (isLastChunk) {
1060 isLastChunkSent = true;
1061 return LastHttpContent.EMPTY_LAST_CONTENT;
1062 }
1063
1064 int size = calculateRemainingSize();
1065 if (size <= 0) {
1066
1067 ByteBuf buffer = fillByteBuf();
1068 return new DefaultHttpContent(buffer);
1069 }
1070
1071 if (currentData != null) {
1072
1073 HttpContent chunk;
1074 if (isMultipart) {
1075 chunk = encodeNextChunkMultipart(size);
1076 } else {
1077 chunk = encodeNextChunkUrlEncoded(size);
1078 }
1079 if (chunk != null) {
1080
1081 return chunk;
1082 }
1083 size = calculateRemainingSize();
1084 }
1085 if (!iterator.hasNext()) {
1086 return lastChunk();
1087 }
1088 while (size > 0 && iterator.hasNext()) {
1089 currentData = iterator.next();
1090 HttpContent chunk;
1091 if (isMultipart) {
1092 chunk = encodeNextChunkMultipart(size);
1093 } else {
1094 chunk = encodeNextChunkUrlEncoded(size);
1095 }
1096 if (chunk == null) {
1097
1098 size = calculateRemainingSize();
1099 continue;
1100 }
1101
1102 return chunk;
1103 }
1104
1105 return lastChunk();
1106 }
1107
1108 private int calculateRemainingSize() {
1109 int size = HttpPostBodyUtil.chunkSize;
1110 if (currentBuffer != null) {
1111 size -= currentBuffer.readableBytes();
1112 }
1113 return size;
1114 }
1115
1116 private HttpContent lastChunk() {
1117 isLastChunk = true;
1118 if (currentBuffer == null) {
1119 isLastChunkSent = true;
1120
1121 return LastHttpContent.EMPTY_LAST_CONTENT;
1122 }
1123
1124 ByteBuf buffer = currentBuffer;
1125 currentBuffer = null;
1126 return new DefaultHttpContent(buffer);
1127 }
1128
1129 @Override
1130 public boolean isEndOfInput() throws Exception {
1131 return isLastChunkSent;
1132 }
1133
1134 @Override
1135 public long length() {
1136 return isMultipart? globalBodySize : globalBodySize - 1;
1137 }
1138
1139 @Override
1140 public long progress() {
1141 return globalProgress;
1142 }
1143
1144
1145
1146
1147 public static class ErrorDataEncoderException extends Exception {
1148 private static final long serialVersionUID = 5020247425493164465L;
1149
1150 public ErrorDataEncoderException() {
1151 }
1152
1153 public ErrorDataEncoderException(String msg) {
1154 super(msg);
1155 }
1156
1157 public ErrorDataEncoderException(Throwable cause) {
1158 super(cause);
1159 }
1160
1161 public ErrorDataEncoderException(String msg, Throwable cause) {
1162 super(msg, cause);
1163 }
1164 }
1165
1166 private static class WrappedHttpRequest implements HttpRequest {
1167 private final HttpRequest request;
1168 WrappedHttpRequest(HttpRequest request) {
1169 this.request = request;
1170 }
1171
1172 @Override
1173 public HttpRequest setProtocolVersion(HttpVersion version) {
1174 request.setProtocolVersion(version);
1175 return this;
1176 }
1177
1178 @Override
1179 public HttpRequest setMethod(HttpMethod method) {
1180 request.setMethod(method);
1181 return this;
1182 }
1183
1184 @Override
1185 public HttpRequest setUri(String uri) {
1186 request.setUri(uri);
1187 return this;
1188 }
1189
1190 @Override
1191 public HttpMethod getMethod() {
1192 return request.method();
1193 }
1194
1195 @Override
1196 public HttpMethod method() {
1197 return request.method();
1198 }
1199
1200 @Override
1201 public String getUri() {
1202 return request.uri();
1203 }
1204
1205 @Override
1206 public String uri() {
1207 return request.uri();
1208 }
1209
1210 @Override
1211 public HttpVersion getProtocolVersion() {
1212 return request.protocolVersion();
1213 }
1214
1215 @Override
1216 public HttpVersion protocolVersion() {
1217 return request.protocolVersion();
1218 }
1219
1220 @Override
1221 public HttpHeaders headers() {
1222 return request.headers();
1223 }
1224
1225 @Override
1226 public DecoderResult decoderResult() {
1227 return request.decoderResult();
1228 }
1229
1230 @Override
1231 @Deprecated
1232 public DecoderResult getDecoderResult() {
1233 return request.getDecoderResult();
1234 }
1235
1236 @Override
1237 public void setDecoderResult(DecoderResult result) {
1238 request.setDecoderResult(result);
1239 }
1240 }
1241
1242 private static final class WrappedFullHttpRequest extends WrappedHttpRequest implements FullHttpRequest {
1243 private final HttpContent content;
1244
1245 private WrappedFullHttpRequest(HttpRequest request, HttpContent content) {
1246 super(request);
1247 this.content = content;
1248 }
1249
1250 @Override
1251 public FullHttpRequest setProtocolVersion(HttpVersion version) {
1252 super.setProtocolVersion(version);
1253 return this;
1254 }
1255
1256 @Override
1257 public FullHttpRequest setMethod(HttpMethod method) {
1258 super.setMethod(method);
1259 return this;
1260 }
1261
1262 @Override
1263 public FullHttpRequest setUri(String uri) {
1264 super.setUri(uri);
1265 return this;
1266 }
1267
1268 @Override
1269 public FullHttpRequest copy() {
1270 return replace(content().copy());
1271 }
1272
1273 @Override
1274 public FullHttpRequest duplicate() {
1275 return replace(content().duplicate());
1276 }
1277
1278 @Override
1279 public FullHttpRequest retainedDuplicate() {
1280 return replace(content().retainedDuplicate());
1281 }
1282
1283 @Override
1284 public FullHttpRequest replace(ByteBuf content) {
1285 DefaultFullHttpRequest duplicate = new DefaultFullHttpRequest(protocolVersion(), method(), uri(), content);
1286 duplicate.headers().set(headers());
1287 duplicate.trailingHeaders().set(trailingHeaders());
1288 return duplicate;
1289 }
1290
1291 @Override
1292 public FullHttpRequest retain(int increment) {
1293 content.retain(increment);
1294 return this;
1295 }
1296
1297 @Override
1298 public FullHttpRequest retain() {
1299 content.retain();
1300 return this;
1301 }
1302
1303 @Override
1304 public FullHttpRequest touch() {
1305 content.touch();
1306 return this;
1307 }
1308
1309 @Override
1310 public FullHttpRequest touch(Object hint) {
1311 content.touch(hint);
1312 return this;
1313 }
1314
1315 @Override
1316 public ByteBuf content() {
1317 return content.content();
1318 }
1319
1320 @Override
1321 public HttpHeaders trailingHeaders() {
1322 if (content instanceof LastHttpContent) {
1323 return ((LastHttpContent) content).trailingHeaders();
1324 } else {
1325 return EmptyHttpHeaders.INSTANCE;
1326 }
1327 }
1328
1329 @Override
1330 public int refCnt() {
1331 return content.refCnt();
1332 }
1333
1334 @Override
1335 public boolean release() {
1336 return content.release();
1337 }
1338
1339 @Override
1340 public boolean release(int decrement) {
1341 return content.release(decrement);
1342 }
1343 }
1344 }