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 | IllegalArgumentException e) {
554
555 undecodedChunk.readerIndex(firstpos);
556 throw new ErrorDataDecoderException(e);
557 }
558 }
559
560
561
562
563
564
565
566
567
568 private void parseBodyAttributes() {
569 if (undecodedChunk == null) {
570 return;
571 }
572 parseBodyAttributesStandard();
573 }
574
575 private void setFinalBuffer(ByteBuf buffer) throws IOException {
576 currentAttribute.addContent(buffer, true);
577 ByteBuf decodedBuf = decodeAttribute(currentAttribute.getByteBuf(), charset);
578 if (decodedBuf != null) {
579 currentAttribute.setContent(decodedBuf);
580 }
581 addHttpData(currentAttribute);
582 currentAttribute = null;
583 }
584
585
586
587
588
589
590 private static String decodeAttribute(String s, Charset charset) {
591 try {
592 return QueryStringDecoder.decodeComponent(s, charset);
593 } catch (IllegalArgumentException e) {
594 throw new ErrorDataDecoderException("Bad string: '" + s + '\'', e);
595 }
596 }
597
598 private static ByteBuf decodeAttribute(ByteBuf b, Charset charset) {
599 int firstEscaped = b.forEachByte(new UrlEncodedDetector());
600 if (firstEscaped == -1) {
601 return null;
602 }
603
604 ByteBuf buf = b.alloc().buffer(b.readableBytes());
605 UrlDecoder urlDecode = new UrlDecoder(buf);
606 int idx = b.forEachByte(urlDecode);
607 if (urlDecode.nextEscapedIdx != 0) {
608 if (idx == -1) {
609 idx = b.readableBytes() - 1;
610 }
611 idx -= urlDecode.nextEscapedIdx - 1;
612 buf.release();
613 throw new ErrorDataDecoderException(
614 String.format("Invalid hex byte at index '%d' in string: '%s'", idx, b.toString(charset)));
615 }
616
617 return buf;
618 }
619
620
621
622
623
624 @Override
625 public void destroy() {
626
627 cleanFiles();
628
629 for (InterfaceHttpData httpData : bodyListHttpData) {
630
631 if (httpData.refCnt() > 0) {
632 httpData.release();
633 }
634 }
635
636 destroyed = true;
637
638 if (undecodedChunk != null && undecodedChunk.refCnt() > 0) {
639 undecodedChunk.release();
640 undecodedChunk = null;
641 }
642 }
643
644
645
646
647 @Override
648 public void cleanFiles() {
649 checkDestroyed();
650
651 factory.cleanRequestHttpData(request);
652 }
653
654
655
656
657 @Override
658 public void removeHttpDataFromClean(InterfaceHttpData data) {
659 checkDestroyed();
660
661 factory.removeHttpDataFromClean(request, data);
662 }
663
664
665
666
667 private boolean hasFormBody() {
668 String contentHeaderValue = request.headers().get(HttpHeaderNames.CONTENT_TYPE);
669 if (contentHeaderValue == null) {
670 return false;
671 }
672 return HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED.contentEquals(contentHeaderValue)
673 || HttpHeaderValues.MULTIPART_FORM_DATA.contentEquals(contentHeaderValue);
674 }
675
676 private static final class UrlEncodedDetector implements ByteProcessor {
677 @Override
678 public boolean process(byte value) throws Exception {
679 return value != '%' && value != '+';
680 }
681 }
682
683 private static final class UrlDecoder implements ByteProcessor {
684
685 private final ByteBuf output;
686 private int nextEscapedIdx;
687 private byte hiByte;
688
689 UrlDecoder(ByteBuf output) {
690 this.output = output;
691 }
692
693 @Override
694 public boolean process(byte value) {
695 if (nextEscapedIdx != 0) {
696 if (nextEscapedIdx == 1) {
697 hiByte = value;
698 ++nextEscapedIdx;
699 } else {
700 int hi = StringUtil.decodeHexNibble((char) hiByte);
701 int lo = StringUtil.decodeHexNibble((char) value);
702 if (hi == -1 || lo == -1) {
703 ++nextEscapedIdx;
704 return false;
705 }
706 output.writeByte((hi << 4) + lo);
707 nextEscapedIdx = 0;
708 }
709 } else if (value == '%') {
710 nextEscapedIdx = 1;
711 } else if (value == '+') {
712 output.writeByte(' ');
713 } else {
714 output.writeByte(value);
715 }
716 return true;
717 }
718 }
719 }