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