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