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.Unpooled;
20 import io.netty.handler.codec.http.HttpConstants;
21 import io.netty.handler.codec.http.HttpContent;
22 import io.netty.handler.codec.http.HttpRequest;
23 import io.netty.handler.codec.http.LastHttpContent;
24 import io.netty.handler.codec.http.QueryStringDecoder;
25 import io.netty.handler.codec.http.HttpHeaderValues;
26 import io.netty.handler.codec.http.HttpHeaderNames;
27 import io.netty.handler.codec.http.multipart.HttpPostBodyUtil.SeekAheadOptimize;
28 import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.EndOfDataDecoderException;
29 import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.ErrorDataDecoderException;
30 import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.MultiPartStatus;
31 import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.NotEnoughDataDecoderException;
32 import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.TooManyFormFieldsException;
33 import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.TooLongFormFieldException;
34 import io.netty.util.ByteProcessor;
35 import io.netty.util.internal.PlatformDependent;
36 import io.netty.util.internal.StringUtil;
37
38 import java.io.IOException;
39 import java.nio.charset.Charset;
40 import java.util.ArrayList;
41 import java.util.List;
42 import java.util.Map;
43 import java.util.TreeMap;
44
45 import static io.netty.util.internal.ObjectUtil.*;
46
47
48
49
50
51
52
53 public class HttpPostStandardRequestDecoder implements InterfaceHttpPostRequestDecoder {
54
55
56
57
58 private final HttpDataFactory factory;
59
60
61
62
63 private final HttpRequest request;
64
65
66
67
68 private final Charset charset;
69
70
71
72
73 private final int maxFields;
74
75
76
77
78 private final int maxBufferedBytes;
79
80
81
82
83 private boolean isLastChunk;
84
85
86
87
88 private final List<InterfaceHttpData> bodyListHttpData = new ArrayList<InterfaceHttpData>();
89
90
91
92
93 private final Map<String, List<InterfaceHttpData>> bodyMapHttpData = new TreeMap<String, List<InterfaceHttpData>>(
94 CaseIgnoringComparator.INSTANCE);
95
96
97
98
99 private ByteBuf undecodedChunk;
100
101
102
103
104 private int bodyListHttpDataRank;
105
106
107
108
109 private MultiPartStatus currentStatus = MultiPartStatus.NOTSTARTED;
110
111
112
113
114 private Attribute currentAttribute;
115
116 private boolean destroyed;
117
118 private int discardThreshold = HttpPostRequestDecoder.DEFAULT_DISCARD_THRESHOLD;
119
120
121
122
123
124
125
126
127
128
129
130 public HttpPostStandardRequestDecoder(HttpRequest request) {
131 this(new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE), request, HttpConstants.DEFAULT_CHARSET);
132 }
133
134
135
136
137
138
139
140
141
142
143
144
145
146 public HttpPostStandardRequestDecoder(HttpDataFactory factory, HttpRequest request) {
147 this(factory, request, HttpConstants.DEFAULT_CHARSET);
148 }
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164 public HttpPostStandardRequestDecoder(HttpDataFactory factory, HttpRequest request, Charset charset) {
165 this(factory, request, charset, HttpPostRequestDecoder.DEFAULT_MAX_FIELDS,
166 HttpPostRequestDecoder.DEFAULT_MAX_BUFFERED_BYTES);
167 }
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187 public HttpPostStandardRequestDecoder(HttpDataFactory factory, HttpRequest request, Charset charset,
188 int maxFields, int maxBufferedBytes) {
189 this.request = checkNotNull(request, "request");
190 this.charset = checkNotNull(charset, "charset");
191 this.factory = checkNotNull(factory, "factory");
192 this.maxFields = maxFields;
193 this.maxBufferedBytes = maxBufferedBytes;
194 try {
195 if (request instanceof HttpContent) {
196
197
198 offer((HttpContent) request);
199 } else {
200 parseBody();
201 }
202 } catch (Throwable e) {
203 destroy();
204 PlatformDependent.throwException(e);
205 }
206 }
207
208 private void checkDestroyed() {
209 if (destroyed) {
210 throw new IllegalStateException(HttpPostStandardRequestDecoder.class.getSimpleName()
211 + " was destroyed already");
212 }
213 }
214
215
216
217
218
219
220 @Override
221 public boolean isMultipart() {
222 checkDestroyed();
223 return false;
224 }
225
226
227
228
229
230
231 @Override
232 public void setDiscardThreshold(int discardThreshold) {
233 this.discardThreshold = checkPositiveOrZero(discardThreshold, "discardThreshold");
234 }
235
236
237
238
239 @Override
240 public int getDiscardThreshold() {
241 return discardThreshold;
242 }
243
244
245
246
247
248
249
250
251
252
253
254 @Override
255 public List<InterfaceHttpData> getBodyHttpDatas() {
256 checkDestroyed();
257
258 if (!isLastChunk) {
259 throw new NotEnoughDataDecoderException();
260 }
261 return bodyListHttpData;
262 }
263
264
265
266
267
268
269
270
271
272
273
274
275 @Override
276 public List<InterfaceHttpData> getBodyHttpDatas(String name) {
277 checkDestroyed();
278
279 if (!isLastChunk) {
280 throw new NotEnoughDataDecoderException();
281 }
282 return bodyMapHttpData.get(name);
283 }
284
285
286
287
288
289
290
291
292
293
294
295
296
297 @Override
298 public InterfaceHttpData getBodyHttpData(String name) {
299 checkDestroyed();
300
301 if (!isLastChunk) {
302 throw new NotEnoughDataDecoderException();
303 }
304 List<InterfaceHttpData> list = bodyMapHttpData.get(name);
305 if (list != null) {
306 return list.get(0);
307 }
308 return null;
309 }
310
311
312
313
314
315
316
317
318
319
320 @Override
321 public HttpPostStandardRequestDecoder offer(HttpContent content) {
322 checkDestroyed();
323
324 if (content instanceof LastHttpContent) {
325 isLastChunk = true;
326 }
327
328 ByteBuf buf = content.content();
329 if (undecodedChunk == null) {
330 undecodedChunk =
331
332
333
334
335 buf.alloc().buffer(buf.readableBytes()).writeBytes(buf);
336 } else {
337 undecodedChunk.writeBytes(buf);
338 }
339 parseBody();
340 if (maxBufferedBytes > 0 && undecodedChunk != null && undecodedChunk.readableBytes() > maxBufferedBytes) {
341 throw new TooLongFormFieldException();
342 }
343 if (undecodedChunk != null && undecodedChunk.writerIndex() > discardThreshold) {
344 if (undecodedChunk.refCnt() == 1) {
345
346 undecodedChunk.discardReadBytes();
347 } else {
348
349
350 ByteBuf buffer = undecodedChunk.alloc().buffer(undecodedChunk.readableBytes());
351 buffer.writeBytes(undecodedChunk);
352 undecodedChunk.release();
353 undecodedChunk = buffer;
354 }
355 }
356 return this;
357 }
358
359
360
361
362
363
364
365
366
367
368
369 @Override
370 public boolean hasNext() {
371 checkDestroyed();
372
373 if (currentStatus == MultiPartStatus.EPILOGUE) {
374
375 if (bodyListHttpDataRank >= bodyListHttpData.size()) {
376 throw new EndOfDataDecoderException();
377 }
378 }
379 return !bodyListHttpData.isEmpty() && bodyListHttpDataRank < bodyListHttpData.size();
380 }
381
382
383
384
385
386
387
388
389
390
391
392
393
394 @Override
395 public InterfaceHttpData next() {
396 checkDestroyed();
397
398 if (hasNext()) {
399 return bodyListHttpData.get(bodyListHttpDataRank++);
400 }
401 return null;
402 }
403
404 @Override
405 public InterfaceHttpData currentPartialHttpData() {
406 return currentAttribute;
407 }
408
409
410
411
412
413
414
415
416 private void parseBody() {
417 if (currentStatus == MultiPartStatus.PREEPILOGUE || currentStatus == MultiPartStatus.EPILOGUE) {
418 if (isLastChunk) {
419 currentStatus = MultiPartStatus.EPILOGUE;
420 }
421 return;
422 }
423 parseBodyAttributes();
424 }
425
426
427
428
429 protected void addHttpData(InterfaceHttpData data) {
430 if (data == null) {
431 return;
432 }
433 if (maxFields > 0 && bodyListHttpData.size() >= maxFields) {
434 throw new TooManyFormFieldsException();
435 }
436 List<InterfaceHttpData> datas = bodyMapHttpData.get(data.getName());
437 if (datas == null) {
438 datas = new ArrayList<InterfaceHttpData>(1);
439 bodyMapHttpData.put(data.getName(), datas);
440 }
441 datas.add(data);
442 bodyListHttpData.add(data);
443 }
444
445
446
447
448
449
450
451
452
453 private void parseBodyAttributesStandard() {
454 int firstpos = undecodedChunk.readerIndex();
455 int currentpos = firstpos;
456 int equalpos;
457 int ampersandpos;
458 if (currentStatus == MultiPartStatus.NOTSTARTED) {
459 currentStatus = MultiPartStatus.DISPOSITION;
460 }
461 boolean contRead = true;
462 try {
463 while (undecodedChunk.isReadable() && contRead) {
464 char read = (char) undecodedChunk.readUnsignedByte();
465 currentpos++;
466 switch (currentStatus) {
467 case DISPOSITION:
468 if (read == '=') {
469 currentStatus = MultiPartStatus.FIELD;
470 equalpos = currentpos - 1;
471 String key = decodeAttribute(undecodedChunk.toString(firstpos, equalpos - firstpos, charset),
472 charset);
473 currentAttribute = factory.createAttribute(request, key);
474 firstpos = currentpos;
475 } else if (read == '&' ||
476 (isLastChunk && !undecodedChunk.isReadable() && hasFormBody())) {
477 currentStatus = MultiPartStatus.DISPOSITION;
478 ampersandpos = read == '&' ? currentpos - 1 : currentpos;
479 String key = decodeAttribute(
480 undecodedChunk.toString(firstpos, ampersandpos - firstpos, charset), charset);
481
482
483
484
485 if (!key.isEmpty()) {
486 currentAttribute = factory.createAttribute(request, key);
487 currentAttribute.setValue("");
488 addHttpData(currentAttribute);
489 }
490 currentAttribute = null;
491 firstpos = currentpos;
492 contRead = true;
493 }
494 break;
495 case FIELD:
496 if (read == '&') {
497 currentStatus = MultiPartStatus.DISPOSITION;
498 ampersandpos = currentpos - 1;
499 setFinalBuffer(undecodedChunk.retainedSlice(firstpos, ampersandpos - firstpos));
500 firstpos = currentpos;
501 contRead = true;
502 } else if (read == HttpConstants.CR) {
503 if (undecodedChunk.isReadable()) {
504 read = (char) undecodedChunk.readUnsignedByte();
505 currentpos++;
506 if (read == HttpConstants.LF) {
507 currentStatus = MultiPartStatus.PREEPILOGUE;
508 ampersandpos = currentpos - 2;
509 setFinalBuffer(undecodedChunk.retainedSlice(firstpos, ampersandpos - firstpos));
510 firstpos = currentpos;
511 contRead = false;
512 } else {
513
514 throw new ErrorDataDecoderException("Bad end of line");
515 }
516 } else {
517 currentpos--;
518 }
519 } else if (read == HttpConstants.LF) {
520 currentStatus = MultiPartStatus.PREEPILOGUE;
521 ampersandpos = currentpos - 1;
522 setFinalBuffer(undecodedChunk.retainedSlice(firstpos, ampersandpos - firstpos));
523 firstpos = currentpos;
524 contRead = false;
525 }
526 break;
527 default:
528
529 contRead = false;
530 }
531 }
532 if (isLastChunk && currentAttribute != null) {
533
534 ampersandpos = currentpos;
535 if (ampersandpos > firstpos) {
536 setFinalBuffer(undecodedChunk.retainedSlice(firstpos, ampersandpos - firstpos));
537 } else if (!currentAttribute.isCompleted()) {
538 setFinalBuffer(Unpooled.EMPTY_BUFFER);
539 }
540 firstpos = currentpos;
541 currentStatus = MultiPartStatus.EPILOGUE;
542 } else if (contRead && currentAttribute != null && currentStatus == MultiPartStatus.FIELD) {
543
544 currentAttribute.addContent(undecodedChunk.retainedSlice(firstpos, currentpos - firstpos),
545 false);
546 firstpos = currentpos;
547 }
548 undecodedChunk.readerIndex(firstpos);
549 } catch (ErrorDataDecoderException e) {
550
551 undecodedChunk.readerIndex(firstpos);
552 throw e;
553 } catch (IOException e) {
554
555 undecodedChunk.readerIndex(firstpos);
556 throw new ErrorDataDecoderException(e);
557 } catch (IllegalArgumentException e) {
558
559 undecodedChunk.readerIndex(firstpos);
560 throw new ErrorDataDecoderException(e);
561 }
562 }
563
564
565
566
567
568
569
570
571
572 private void parseBodyAttributes() {
573 if (undecodedChunk == null) {
574 return;
575 }
576 parseBodyAttributesStandard();
577 }
578
579 private void setFinalBuffer(ByteBuf buffer) throws IOException {
580 currentAttribute.addContent(buffer, true);
581 ByteBuf decodedBuf = decodeAttribute(currentAttribute.getByteBuf(), charset);
582 if (decodedBuf != null) {
583 currentAttribute.setContent(decodedBuf);
584 }
585 addHttpData(currentAttribute);
586 currentAttribute = null;
587 }
588
589
590
591
592
593
594 private static String decodeAttribute(String s, Charset charset) {
595 try {
596 return QueryStringDecoder.decodeComponent(s, charset);
597 } catch (IllegalArgumentException e) {
598 throw new ErrorDataDecoderException("Bad string: '" + s + '\'', e);
599 }
600 }
601
602 private static ByteBuf decodeAttribute(ByteBuf b, Charset charset) {
603 int firstEscaped = b.forEachByte(new UrlEncodedDetector());
604 if (firstEscaped == -1) {
605 return null;
606 }
607
608 ByteBuf buf = b.alloc().buffer(b.readableBytes());
609 UrlDecoder urlDecode = new UrlDecoder(buf);
610 int idx = b.forEachByte(urlDecode);
611 if (urlDecode.nextEscapedIdx != 0) {
612 if (idx == -1) {
613 idx = b.readableBytes() - 1;
614 }
615 idx -= urlDecode.nextEscapedIdx - 1;
616 buf.release();
617 throw new ErrorDataDecoderException(
618 String.format("Invalid hex byte at index '%d' in string: '%s'", idx, b.toString(charset)));
619 }
620
621 return buf;
622 }
623
624
625
626
627
628 @Override
629 public void destroy() {
630
631 cleanFiles();
632
633 for (InterfaceHttpData httpData : bodyListHttpData) {
634
635 if (httpData.refCnt() > 0) {
636 httpData.release();
637 }
638 }
639
640 destroyed = true;
641
642 if (undecodedChunk != null && undecodedChunk.refCnt() > 0) {
643 undecodedChunk.release();
644 undecodedChunk = null;
645 }
646 }
647
648
649
650
651 @Override
652 public void cleanFiles() {
653 checkDestroyed();
654
655 factory.cleanRequestHttpData(request);
656 }
657
658
659
660
661 @Override
662 public void removeHttpDataFromClean(InterfaceHttpData data) {
663 checkDestroyed();
664
665 factory.removeHttpDataFromClean(request, data);
666 }
667
668
669
670
671 private boolean hasFormBody() {
672 String contentHeaderValue = request.headers().get(HttpHeaderNames.CONTENT_TYPE);
673 if (contentHeaderValue == null) {
674 return false;
675 }
676 return HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED.contentEquals(contentHeaderValue)
677 || HttpHeaderValues.MULTIPART_FORM_DATA.contentEquals(contentHeaderValue);
678 }
679
680 private static final class UrlEncodedDetector implements ByteProcessor {
681 @Override
682 public boolean process(byte value) throws Exception {
683 return value != '%' && value != '+';
684 }
685 }
686
687 private static final class UrlDecoder implements ByteProcessor {
688
689 private final ByteBuf output;
690 private int nextEscapedIdx;
691 private byte hiByte;
692
693 UrlDecoder(ByteBuf output) {
694 this.output = output;
695 }
696
697 @Override
698 public boolean process(byte value) {
699 if (nextEscapedIdx != 0) {
700 if (nextEscapedIdx == 1) {
701 hiByte = value;
702 ++nextEscapedIdx;
703 } else {
704 int hi = StringUtil.decodeHexNibble((char) hiByte);
705 int lo = StringUtil.decodeHexNibble((char) value);
706 if (hi == -1 || lo == -1) {
707 ++nextEscapedIdx;
708 return false;
709 }
710 output.writeByte((hi << 4) + lo);
711 nextEscapedIdx = 0;
712 }
713 } else if (value == '%') {
714 nextEscapedIdx = 1;
715 } else if (value == '+') {
716 output.writeByte(' ');
717 } else {
718 output.writeByte(value);
719 }
720 return true;
721 }
722 }
723 }