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.PlatformDependent;
38 import io.netty.util.internal.StringUtil;
39
40 import java.io.File;
41 import java.io.IOException;
42 import java.io.UnsupportedEncodingException;
43 import java.net.URLEncoder;
44 import java.nio.charset.Charset;
45 import java.util.ArrayList;
46 import java.util.List;
47 import java.util.ListIterator;
48 import java.util.Map;
49 import java.util.regex.Pattern;
50
51 import static io.netty.buffer.Unpooled.wrappedBuffer;
52 import static io.netty.util.internal.ObjectUtil.checkNotNull;
53 import static java.util.AbstractMap.SimpleImmutableEntry;
54
55
56
57
58
59
60
61
62
63
64
65
66
67 public class HttpPostRequestEncoder implements ChunkedInput<HttpContent> {
68
69
70
71
72 public enum EncoderMode {
73
74
75
76
77 RFC1738,
78
79
80
81
82 RFC3986,
83
84
85
86
87
88
89
90
91
92
93 HTML5
94 }
95
96 @SuppressWarnings("rawtypes")
97 private static final Map.Entry[] percentEncodings;
98
99 static {
100 percentEncodings = new Map.Entry[] {
101 new SimpleImmutableEntry<Pattern, String>(Pattern.compile("\\*"), "%2A"),
102 new SimpleImmutableEntry<Pattern, String>(Pattern.compile("\\+"), "%20"),
103 new SimpleImmutableEntry<Pattern, String>(Pattern.compile("~"), "%7E")
104 };
105 }
106
107
108
109
110 private final HttpDataFactory factory;
111
112
113
114
115 private final HttpRequest request;
116
117
118
119
120 private final Charset charset;
121
122
123
124
125 private boolean isChunked;
126
127
128
129
130 private final List<InterfaceHttpData> bodyListDatas;
131
132
133
134 final List<InterfaceHttpData> multipartHttpDatas;
135
136
137
138
139 private final boolean isMultipart;
140
141
142
143
144 String multipartDataBoundary;
145
146
147
148
149 String multipartMixedBoundary;
150
151
152
153 private boolean headerFinalized;
154
155 private final EncoderMode encoderMode;
156
157
158
159
160
161
162
163
164
165
166
167
168 public HttpPostRequestEncoder(HttpRequest request, boolean multipart) throws ErrorDataEncoderException {
169 this(new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE), request, multipart,
170 HttpConstants.DEFAULT_CHARSET, EncoderMode.RFC1738);
171 }
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186 public HttpPostRequestEncoder(HttpDataFactory factory, HttpRequest request, boolean multipart)
187 throws ErrorDataEncoderException {
188 this(factory, request, multipart, HttpConstants.DEFAULT_CHARSET, EncoderMode.RFC1738);
189 }
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208 public HttpPostRequestEncoder(
209 HttpDataFactory factory, HttpRequest request, boolean multipart, Charset charset,
210 EncoderMode encoderMode)
211 throws ErrorDataEncoderException {
212 this.request = checkNotNull(request, "request");
213 this.charset = checkNotNull(charset, "charset");
214 this.factory = checkNotNull(factory, "factory");
215 if (HttpMethod.TRACE.equals(request.method())) {
216 throw new ErrorDataEncoderException("Cannot create a Encoder if request is a TRACE");
217 }
218
219 bodyListDatas = new ArrayList<InterfaceHttpData>();
220
221 isLastChunk = false;
222 isLastChunkSent = false;
223 isMultipart = multipart;
224 multipartHttpDatas = new ArrayList<InterfaceHttpData>();
225 this.encoderMode = encoderMode;
226 if (isMultipart) {
227 initDataMultipart();
228 }
229 }
230
231
232
233
234 public void cleanFiles() {
235 factory.cleanRequestHttpData(request);
236 }
237
238
239
240
241 private boolean isLastChunk;
242
243
244
245 private boolean isLastChunkSent;
246
247
248
249 private FileUpload currentFileUpload;
250
251
252
253 private boolean duringMixedMode;
254
255
256
257 private long globalBodySize;
258
259
260
261 private long globalProgress;
262
263
264
265
266
267
268 public boolean isMultipart() {
269 return isMultipart;
270 }
271
272
273
274
275 private void initDataMultipart() {
276 multipartDataBoundary = getNewMultipartDelimiter();
277 }
278
279
280
281
282 private void initMixedMultipart() {
283 multipartMixedBoundary = getNewMultipartDelimiter();
284 }
285
286
287
288
289
290 private static String getNewMultipartDelimiter() {
291
292 return Long.toHexString(PlatformDependent.threadLocalRandom().nextLong());
293 }
294
295
296
297
298
299
300 public List<InterfaceHttpData> getBodyListAttributes() {
301 return bodyListDatas;
302 }
303
304
305
306
307
308
309
310
311
312 public void setBodyHttpDatas(List<InterfaceHttpData> datas) throws ErrorDataEncoderException {
313 if (datas == null) {
314 throw new NullPointerException("datas");
315 }
316 globalBodySize = 0;
317 bodyListDatas.clear();
318 currentFileUpload = null;
319 duringMixedMode = false;
320 multipartHttpDatas.clear();
321 for (InterfaceHttpData data : datas) {
322 addBodyHttpData(data);
323 }
324 }
325
326
327
328
329
330
331
332
333
334
335
336
337
338 public void addBodyAttribute(String name, String value) throws ErrorDataEncoderException {
339 String svalue = value != null? value : StringUtil.EMPTY_STRING;
340 Attribute data = factory.createAttribute(request, checkNotNull(name, "name"), svalue);
341 addBodyHttpData(data);
342 }
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360 public void addBodyFileUpload(String name, File file, String contentType, boolean isText)
361 throws ErrorDataEncoderException {
362 addBodyFileUpload(name, file.getName(), file, contentType, isText);
363 }
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384 public void addBodyFileUpload(String name, String filename, File file, String contentType, boolean isText)
385 throws ErrorDataEncoderException {
386 checkNotNull(name, "name");
387 checkNotNull(file, "file");
388 if (filename == null) {
389 filename = StringUtil.EMPTY_STRING;
390 }
391 String scontentType = contentType;
392 String contentTransferEncoding = null;
393 if (contentType == null) {
394 if (isText) {
395 scontentType = HttpPostBodyUtil.DEFAULT_TEXT_CONTENT_TYPE;
396 } else {
397 scontentType = HttpPostBodyUtil.DEFAULT_BINARY_CONTENT_TYPE;
398 }
399 }
400 if (!isText) {
401 contentTransferEncoding = HttpPostBodyUtil.TransferEncodingMechanism.BINARY.value();
402 }
403 FileUpload fileUpload = factory.createFileUpload(request, name, filename, scontentType,
404 contentTransferEncoding, null, file.length());
405 try {
406 fileUpload.setContent(file);
407 } catch (IOException e) {
408 throw new ErrorDataEncoderException(e);
409 }
410 addBodyHttpData(fileUpload);
411 }
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429 public void addBodyFileUploads(String name, File[] file, String[] contentType, boolean[] isText)
430 throws ErrorDataEncoderException {
431 if (file.length != contentType.length && file.length != isText.length) {
432 throw new IllegalArgumentException("Different array length");
433 }
434 for (int i = 0; i < file.length; i++) {
435 addBodyFileUpload(name, file[i], contentType[i], isText[i]);
436 }
437 }
438
439
440
441
442
443
444
445
446
447 public void addBodyHttpData(InterfaceHttpData data) throws ErrorDataEncoderException {
448 if (headerFinalized) {
449 throw new ErrorDataEncoderException("Cannot add value once finalized");
450 }
451 bodyListDatas.add(checkNotNull(data, "data"));
452 if (!isMultipart) {
453 if (data instanceof Attribute) {
454 Attribute attribute = (Attribute) data;
455 try {
456
457 String key = encodeAttribute(attribute.getName(), charset);
458 String value = encodeAttribute(attribute.getValue(), charset);
459 Attribute newattribute = factory.createAttribute(request, key, value);
460 multipartHttpDatas.add(newattribute);
461 globalBodySize += newattribute.getName().length() + 1 + newattribute.length() + 1;
462 } catch (IOException e) {
463 throw new ErrorDataEncoderException(e);
464 }
465 } else if (data instanceof FileUpload) {
466
467 FileUpload fileUpload = (FileUpload) data;
468
469 String key = encodeAttribute(fileUpload.getName(), charset);
470 String value = encodeAttribute(fileUpload.getFilename(), charset);
471 Attribute newattribute = factory.createAttribute(request, key, value);
472 multipartHttpDatas.add(newattribute);
473 globalBodySize += newattribute.getName().length() + 1 + newattribute.length() + 1;
474 }
475 return;
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
509 if (data instanceof Attribute) {
510 if (duringMixedMode) {
511 InternalAttribute internal = new InternalAttribute(charset);
512 internal.addValue("\r\n--" + multipartMixedBoundary + "--");
513 multipartHttpDatas.add(internal);
514 multipartMixedBoundary = null;
515 currentFileUpload = null;
516 duringMixedMode = false;
517 }
518 InternalAttribute internal = new InternalAttribute(charset);
519 if (!multipartHttpDatas.isEmpty()) {
520
521 internal.addValue("\r\n");
522 }
523 internal.addValue("--" + multipartDataBoundary + "\r\n");
524
525 Attribute attribute = (Attribute) data;
526 internal.addValue(HttpHeaderNames.CONTENT_DISPOSITION + ": " + HttpHeaderValues.FORM_DATA + "; "
527 + HttpHeaderValues.NAME + "=\"" + attribute.getName() + "\"\r\n");
528
529 internal.addValue(HttpHeaderNames.CONTENT_LENGTH + ": " +
530 attribute.length() + "\r\n");
531 Charset localcharset = attribute.getCharset();
532 if (localcharset != null) {
533
534 internal.addValue(HttpHeaderNames.CONTENT_TYPE + ": " +
535 HttpPostBodyUtil.DEFAULT_TEXT_CONTENT_TYPE + "; " +
536 HttpHeaderValues.CHARSET + '='
537 + localcharset.name() + "\r\n");
538 }
539
540 internal.addValue("\r\n");
541 multipartHttpDatas.add(internal);
542 multipartHttpDatas.add(data);
543 globalBodySize += attribute.length() + internal.size();
544 } else if (data instanceof FileUpload) {
545 FileUpload fileUpload = (FileUpload) data;
546 InternalAttribute internal = new InternalAttribute(charset);
547 if (!multipartHttpDatas.isEmpty()) {
548
549 internal.addValue("\r\n");
550 }
551 boolean localMixed;
552 if (duringMixedMode) {
553 if (currentFileUpload != null && currentFileUpload.getName().equals(fileUpload.getName())) {
554
555
556 localMixed = true;
557 } else {
558
559
560
561
562
563 internal.addValue("--" + multipartMixedBoundary + "--");
564 multipartHttpDatas.add(internal);
565 multipartMixedBoundary = null;
566
567
568 internal = new InternalAttribute(charset);
569 internal.addValue("\r\n");
570 localMixed = false;
571
572 currentFileUpload = fileUpload;
573 duringMixedMode = false;
574 }
575 } else {
576 if (encoderMode != EncoderMode.HTML5 && currentFileUpload != null
577 && currentFileUpload.getName().equals(fileUpload.getName())) {
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598 initMixedMultipart();
599 InternalAttribute pastAttribute = (InternalAttribute) multipartHttpDatas.get(multipartHttpDatas
600 .size() - 2);
601
602 globalBodySize -= pastAttribute.size();
603 StringBuilder replacement = new StringBuilder(
604 139 + multipartDataBoundary.length() + multipartMixedBoundary.length() * 2 +
605 fileUpload.getFilename().length() + fileUpload.getName().length())
606
607 .append("--")
608 .append(multipartDataBoundary)
609 .append("\r\n")
610
611 .append(HttpHeaderNames.CONTENT_DISPOSITION)
612 .append(": ")
613 .append(HttpHeaderValues.FORM_DATA)
614 .append("; ")
615 .append(HttpHeaderValues.NAME)
616 .append("=\"")
617 .append(fileUpload.getName())
618 .append("\"\r\n")
619
620 .append(HttpHeaderNames.CONTENT_TYPE)
621 .append(": ")
622 .append(HttpHeaderValues.MULTIPART_MIXED)
623 .append("; ")
624 .append(HttpHeaderValues.BOUNDARY)
625 .append('=')
626 .append(multipartMixedBoundary)
627 .append("\r\n\r\n")
628
629 .append("--")
630 .append(multipartMixedBoundary)
631 .append("\r\n")
632
633 .append(HttpHeaderNames.CONTENT_DISPOSITION)
634 .append(": ")
635 .append(HttpHeaderValues.ATTACHMENT);
636
637 if (!fileUpload.getFilename().isEmpty()) {
638 replacement.append("; ")
639 .append(HttpHeaderValues.FILENAME)
640 .append("=\"")
641 .append(fileUpload.getFilename())
642 .append('"');
643 }
644
645 replacement.append("\r\n");
646
647 pastAttribute.setValue(replacement.toString(), 1);
648 pastAttribute.setValue("", 2);
649
650
651 globalBodySize += pastAttribute.size();
652
653
654
655
656
657 localMixed = true;
658 duringMixedMode = true;
659 } else {
660
661
662
663 localMixed = false;
664 currentFileUpload = fileUpload;
665 duringMixedMode = false;
666 }
667 }
668
669 if (localMixed) {
670
671
672 internal.addValue("--" + multipartMixedBoundary + "\r\n");
673
674 if (fileUpload.getFilename().isEmpty()) {
675
676 internal.addValue(HttpHeaderNames.CONTENT_DISPOSITION + ": "
677 + HttpHeaderValues.ATTACHMENT + "\r\n");
678 } else {
679
680 internal.addValue(HttpHeaderNames.CONTENT_DISPOSITION + ": "
681 + HttpHeaderValues.ATTACHMENT + "; "
682 + HttpHeaderValues.FILENAME + "=\"" + fileUpload.getFilename() + "\"\r\n");
683 }
684 } else {
685 internal.addValue("--" + multipartDataBoundary + "\r\n");
686
687 if (fileUpload.getFilename().isEmpty()) {
688
689 internal.addValue(HttpHeaderNames.CONTENT_DISPOSITION + ": " + HttpHeaderValues.FORM_DATA + "; "
690 + HttpHeaderValues.NAME + "=\"" + fileUpload.getName() + "\"\r\n");
691 } else {
692
693
694 internal.addValue(HttpHeaderNames.CONTENT_DISPOSITION + ": " + HttpHeaderValues.FORM_DATA + "; "
695 + HttpHeaderValues.NAME + "=\"" + fileUpload.getName() + "\"; "
696 + HttpHeaderValues.FILENAME + "=\"" + fileUpload.getFilename() + "\"\r\n");
697 }
698 }
699
700 internal.addValue(HttpHeaderNames.CONTENT_LENGTH + ": " +
701 fileUpload.length() + "\r\n");
702
703
704
705 internal.addValue(HttpHeaderNames.CONTENT_TYPE + ": " + fileUpload.getContentType());
706 String contentTransferEncoding = fileUpload.getContentTransferEncoding();
707 if (contentTransferEncoding != null
708 && contentTransferEncoding.equals(HttpPostBodyUtil.TransferEncodingMechanism.BINARY.value())) {
709 internal.addValue("\r\n" + HttpHeaderNames.CONTENT_TRANSFER_ENCODING + ": "
710 + HttpPostBodyUtil.TransferEncodingMechanism.BINARY.value() + "\r\n\r\n");
711 } else if (fileUpload.getCharset() != null) {
712 internal.addValue("; " + HttpHeaderValues.CHARSET + '=' + fileUpload.getCharset().name() + "\r\n\r\n");
713 } else {
714 internal.addValue("\r\n\r\n");
715 }
716 multipartHttpDatas.add(internal);
717 multipartHttpDatas.add(data);
718 globalBodySize += fileUpload.length() + internal.size();
719 }
720 }
721
722
723
724
725 private ListIterator<InterfaceHttpData> iterator;
726
727
728
729
730
731
732
733
734
735
736
737 public HttpRequest finalizeRequest() throws ErrorDataEncoderException {
738
739 if (!headerFinalized) {
740 if (isMultipart) {
741 InternalAttribute internal = new InternalAttribute(charset);
742 if (duringMixedMode) {
743 internal.addValue("\r\n--" + multipartMixedBoundary + "--");
744 }
745 internal.addValue("\r\n--" + multipartDataBoundary + "--\r\n");
746 multipartHttpDatas.add(internal);
747 multipartMixedBoundary = null;
748 currentFileUpload = null;
749 duringMixedMode = false;
750 globalBodySize += internal.size();
751 }
752 headerFinalized = true;
753 } else {
754 throw new ErrorDataEncoderException("Header already encoded");
755 }
756
757 HttpHeaders headers = request.headers();
758 List<String> contentTypes = headers.getAll(HttpHeaderNames.CONTENT_TYPE);
759 List<String> transferEncoding = headers.getAll(HttpHeaderNames.TRANSFER_ENCODING);
760 if (contentTypes != null) {
761 headers.remove(HttpHeaderNames.CONTENT_TYPE);
762 for (String contentType : contentTypes) {
763
764 String lowercased = contentType.toLowerCase();
765 if (lowercased.startsWith(HttpHeaderValues.MULTIPART_FORM_DATA.toString()) ||
766 lowercased.startsWith(HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED.toString())) {
767
768 } else {
769 headers.add(HttpHeaderNames.CONTENT_TYPE, contentType);
770 }
771 }
772 }
773 if (isMultipart) {
774 String value = HttpHeaderValues.MULTIPART_FORM_DATA + "; " + HttpHeaderValues.BOUNDARY + '='
775 + multipartDataBoundary;
776 headers.add(HttpHeaderNames.CONTENT_TYPE, value);
777 } else {
778
779 headers.add(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED);
780 }
781
782 long realSize = globalBodySize;
783 if (!isMultipart) {
784 realSize -= 1;
785 }
786 iterator = multipartHttpDatas.listIterator();
787
788 headers.set(HttpHeaderNames.CONTENT_LENGTH, String.valueOf(realSize));
789 if (realSize > HttpPostBodyUtil.chunkSize || isMultipart) {
790 isChunked = true;
791 if (transferEncoding != null) {
792 headers.remove(HttpHeaderNames.TRANSFER_ENCODING);
793 for (CharSequence v : transferEncoding) {
794 if (HttpHeaderValues.CHUNKED.contentEqualsIgnoreCase(v)) {
795
796 } else {
797 headers.add(HttpHeaderNames.TRANSFER_ENCODING, v);
798 }
799 }
800 }
801 HttpUtil.setTransferEncodingChunked(request, true);
802
803
804 return new WrappedHttpRequest(request);
805 } else {
806
807 HttpContent chunk = nextChunk();
808 if (request instanceof FullHttpRequest) {
809 FullHttpRequest fullRequest = (FullHttpRequest) request;
810 ByteBuf chunkContent = chunk.content();
811 if (fullRequest.content() != chunkContent) {
812 fullRequest.content().clear().writeBytes(chunkContent);
813 chunkContent.release();
814 }
815 return fullRequest;
816 } else {
817 return new WrappedFullHttpRequest(request, chunk);
818 }
819 }
820 }
821
822
823
824
825 public boolean isChunked() {
826 return isChunked;
827 }
828
829
830
831
832
833
834
835
836 @SuppressWarnings("unchecked")
837 private String encodeAttribute(String s, Charset charset) throws ErrorDataEncoderException {
838 if (s == null) {
839 return "";
840 }
841 try {
842 String encoded = URLEncoder.encode(s, charset.name());
843 if (encoderMode == EncoderMode.RFC3986) {
844 for (Map.Entry<Pattern, String> entry : percentEncodings) {
845 String replacement = entry.getValue();
846 encoded = entry.getKey().matcher(encoded).replaceAll(replacement);
847 }
848 }
849 return encoded;
850 } catch (UnsupportedEncodingException e) {
851 throw new ErrorDataEncoderException(charset.name(), e);
852 }
853 }
854
855
856
857
858 private ByteBuf currentBuffer;
859
860
861
862 private InterfaceHttpData currentData;
863
864
865
866 private boolean isKey = true;
867
868
869
870
871
872 private ByteBuf fillByteBuf() {
873 int length = currentBuffer.readableBytes();
874 if (length > HttpPostBodyUtil.chunkSize) {
875 return currentBuffer.readRetainedSlice(HttpPostBodyUtil.chunkSize);
876 } else {
877
878 ByteBuf slice = currentBuffer;
879 currentBuffer = null;
880 return slice;
881 }
882 }
883
884
885
886
887
888
889
890
891
892
893
894 private HttpContent encodeNextChunkMultipart(int sizeleft) throws ErrorDataEncoderException {
895 if (currentData == null) {
896 return null;
897 }
898 ByteBuf buffer;
899 if (currentData instanceof InternalAttribute) {
900 buffer = ((InternalAttribute) currentData).toByteBuf();
901 currentData = null;
902 } else {
903 try {
904 buffer = ((HttpData) currentData).getChunk(sizeleft);
905 } catch (IOException e) {
906 throw new ErrorDataEncoderException(e);
907 }
908 if (buffer.capacity() == 0) {
909
910 currentData = null;
911 return null;
912 }
913 }
914 if (currentBuffer == null) {
915 currentBuffer = buffer;
916 } else {
917 currentBuffer = wrappedBuffer(currentBuffer, buffer);
918 }
919 if (currentBuffer.readableBytes() < HttpPostBodyUtil.chunkSize) {
920 currentData = null;
921 return null;
922 }
923 buffer = fillByteBuf();
924 return new DefaultHttpContent(buffer);
925 }
926
927
928
929
930
931
932
933
934
935
936
937 private HttpContent encodeNextChunkUrlEncoded(int sizeleft) throws ErrorDataEncoderException {
938 if (currentData == null) {
939 return null;
940 }
941 int size = sizeleft;
942 ByteBuf buffer;
943
944
945 if (isKey) {
946 String key = currentData.getName();
947 buffer = wrappedBuffer(key.getBytes());
948 isKey = false;
949 if (currentBuffer == null) {
950 currentBuffer = wrappedBuffer(buffer, wrappedBuffer("=".getBytes()));
951 } else {
952 currentBuffer = wrappedBuffer(currentBuffer, buffer, wrappedBuffer("=".getBytes()));
953 }
954
955 size -= buffer.readableBytes() + 1;
956 if (currentBuffer.readableBytes() >= HttpPostBodyUtil.chunkSize) {
957 buffer = fillByteBuf();
958 return new DefaultHttpContent(buffer);
959 }
960 }
961
962
963 try {
964 buffer = ((HttpData) currentData).getChunk(size);
965 } catch (IOException e) {
966 throw new ErrorDataEncoderException(e);
967 }
968
969
970 ByteBuf delimiter = null;
971 if (buffer.readableBytes() < size) {
972 isKey = true;
973 delimiter = iterator.hasNext() ? wrappedBuffer("&".getBytes()) : null;
974 }
975
976
977 if (buffer.capacity() == 0) {
978 currentData = null;
979 if (currentBuffer == null) {
980 currentBuffer = delimiter;
981 } else {
982 if (delimiter != null) {
983 currentBuffer = wrappedBuffer(currentBuffer, delimiter);
984 }
985 }
986 if (currentBuffer.readableBytes() >= HttpPostBodyUtil.chunkSize) {
987 buffer = fillByteBuf();
988 return new DefaultHttpContent(buffer);
989 }
990 return null;
991 }
992
993
994 if (currentBuffer == null) {
995 if (delimiter != null) {
996 currentBuffer = wrappedBuffer(buffer, delimiter);
997 } else {
998 currentBuffer = buffer;
999 }
1000 } else {
1001 if (delimiter != null) {
1002 currentBuffer = wrappedBuffer(currentBuffer, buffer, delimiter);
1003 } else {
1004 currentBuffer = wrappedBuffer(currentBuffer, buffer);
1005 }
1006 }
1007
1008
1009 if (currentBuffer.readableBytes() < HttpPostBodyUtil.chunkSize) {
1010 currentData = null;
1011 isKey = true;
1012 return null;
1013 }
1014
1015 buffer = fillByteBuf();
1016 return new DefaultHttpContent(buffer);
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 }