1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package io.netty.handler.codec.http;
17
18 import io.netty.buffer.ByteBuf;
19 import io.netty.buffer.ByteBufProcessor;
20 import io.netty.buffer.Unpooled;
21 import io.netty.channel.ChannelHandlerContext;
22 import io.netty.channel.ChannelPipeline;
23 import io.netty.handler.codec.ByteToMessageDecoder;
24 import io.netty.handler.codec.DecoderResult;
25 import io.netty.handler.codec.PrematureChannelClosureException;
26 import io.netty.handler.codec.TooLongFrameException;
27 import io.netty.util.internal.AppendableCharSequence;
28
29 import java.util.List;
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102 public abstract class HttpObjectDecoder extends ByteToMessageDecoder {
103 private static final String EMPTY_VALUE = "";
104
105 private final int maxChunkSize;
106 private final boolean chunkedSupported;
107 protected final boolean validateHeaders;
108 private final HeaderParser headerParser;
109 private final LineParser lineParser;
110
111 private HttpMessage message;
112 private long chunkSize;
113 private long contentLength = Long.MIN_VALUE;
114 private volatile boolean resetRequested;
115
116
117 private CharSequence name;
118 private CharSequence value;
119
120 private LastHttpContent trailer;
121
122
123
124
125
126 private enum State {
127 SKIP_CONTROL_CHARS,
128 READ_INITIAL,
129 READ_HEADER,
130 READ_VARIABLE_LENGTH_CONTENT,
131 READ_FIXED_LENGTH_CONTENT,
132 READ_CHUNK_SIZE,
133 READ_CHUNKED_CONTENT,
134 READ_CHUNK_DELIMITER,
135 READ_CHUNK_FOOTER,
136 BAD_MESSAGE,
137 UPGRADED
138 }
139
140 private State currentState = State.SKIP_CONTROL_CHARS;
141
142
143
144
145
146
147 protected HttpObjectDecoder() {
148 this(4096, 8192, 8192, true);
149 }
150
151
152
153
154 protected HttpObjectDecoder(
155 int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, boolean chunkedSupported) {
156 this(maxInitialLineLength, maxHeaderSize, maxChunkSize, chunkedSupported, true);
157 }
158
159
160
161
162 protected HttpObjectDecoder(
163 int maxInitialLineLength, int maxHeaderSize, int maxChunkSize,
164 boolean chunkedSupported, boolean validateHeaders) {
165 this(maxInitialLineLength, maxHeaderSize, maxChunkSize, chunkedSupported, validateHeaders, 128);
166 }
167
168 protected HttpObjectDecoder(
169 int maxInitialLineLength, int maxHeaderSize, int maxChunkSize,
170 boolean chunkedSupported, boolean validateHeaders, int initialBufferSize) {
171 if (maxInitialLineLength <= 0) {
172 throw new IllegalArgumentException(
173 "maxInitialLineLength must be a positive integer: " +
174 maxInitialLineLength);
175 }
176 if (maxHeaderSize <= 0) {
177 throw new IllegalArgumentException(
178 "maxHeaderSize must be a positive integer: " +
179 maxHeaderSize);
180 }
181 if (maxChunkSize <= 0) {
182 throw new IllegalArgumentException(
183 "maxChunkSize must be a positive integer: " +
184 maxChunkSize);
185 }
186 AppendableCharSequence seq = new AppendableCharSequence(initialBufferSize);
187 lineParser = new LineParser(seq, maxInitialLineLength);
188 headerParser = new HeaderParser(seq, maxHeaderSize);
189 this.maxChunkSize = maxChunkSize;
190 this.chunkedSupported = chunkedSupported;
191 this.validateHeaders = validateHeaders;
192 }
193
194 @Override
195 protected void decode(ChannelHandlerContext ctx, ByteBuf buffer, List<Object> out) throws Exception {
196 if (resetRequested) {
197 resetNow();
198 }
199
200 switch (currentState) {
201 case SKIP_CONTROL_CHARS: {
202 if (!skipControlCharacters(buffer)) {
203 return;
204 }
205 currentState = State.READ_INITIAL;
206 }
207 case READ_INITIAL: try {
208 AppendableCharSequence line = lineParser.parse(buffer);
209 if (line == null) {
210 return;
211 }
212 String[] initialLine = splitInitialLine(line);
213 if (initialLine.length < 3) {
214
215 currentState = State.SKIP_CONTROL_CHARS;
216 return;
217 }
218
219 message = createMessage(initialLine);
220 currentState = State.READ_HEADER;
221
222 } catch (Exception e) {
223 out.add(invalidMessage(buffer, e));
224 return;
225 }
226 case READ_HEADER: try {
227 State nextState = readHeaders(buffer);
228 if (nextState == null) {
229 return;
230 }
231 currentState = nextState;
232 switch (nextState) {
233 case SKIP_CONTROL_CHARS:
234
235
236 out.add(message);
237 out.add(LastHttpContent.EMPTY_LAST_CONTENT);
238 resetNow();
239 return;
240 case READ_CHUNK_SIZE:
241 if (!chunkedSupported) {
242 throw new IllegalArgumentException("Chunked messages not supported");
243 }
244
245 out.add(message);
246 return;
247 default:
248
249
250
251
252
253
254
255 long contentLength = contentLength();
256 if (contentLength == 0 || contentLength == -1 && isDecodingRequest()) {
257 out.add(message);
258 out.add(LastHttpContent.EMPTY_LAST_CONTENT);
259 resetNow();
260 return;
261 }
262
263 assert nextState == State.READ_FIXED_LENGTH_CONTENT ||
264 nextState == State.READ_VARIABLE_LENGTH_CONTENT;
265
266 out.add(message);
267
268 if (nextState == State.READ_FIXED_LENGTH_CONTENT) {
269
270
271 chunkSize = contentLength;
272 }
273
274
275 return;
276 }
277 } catch (Exception e) {
278 out.add(invalidMessage(buffer, e));
279 return;
280 }
281 case READ_VARIABLE_LENGTH_CONTENT: {
282
283 int toRead = Math.min(buffer.readableBytes(), maxChunkSize);
284 if (toRead > 0) {
285 ByteBuf content = buffer.readSlice(toRead).retain();
286 out.add(new DefaultHttpContent(content));
287 }
288 return;
289 }
290 case READ_FIXED_LENGTH_CONTENT: {
291 int readLimit = buffer.readableBytes();
292
293
294
295
296
297
298
299 if (readLimit == 0) {
300 return;
301 }
302
303 int toRead = Math.min(readLimit, maxChunkSize);
304 if (toRead > chunkSize) {
305 toRead = (int) chunkSize;
306 }
307 ByteBuf content = buffer.readSlice(toRead).retain();
308 chunkSize -= toRead;
309
310 if (chunkSize == 0) {
311
312 out.add(new DefaultLastHttpContent(content, validateHeaders));
313 resetNow();
314 } else {
315 out.add(new DefaultHttpContent(content));
316 }
317 return;
318 }
319
320
321
322
323 case READ_CHUNK_SIZE: try {
324 AppendableCharSequence line = lineParser.parse(buffer);
325 if (line == null) {
326 return;
327 }
328 int chunkSize = getChunkSize(line.toString());
329 this.chunkSize = chunkSize;
330 if (chunkSize == 0) {
331 currentState = State.READ_CHUNK_FOOTER;
332 return;
333 }
334 currentState = State.READ_CHUNKED_CONTENT;
335
336 } catch (Exception e) {
337 out.add(invalidChunk(buffer, e));
338 return;
339 }
340 case READ_CHUNKED_CONTENT: {
341 assert chunkSize <= Integer.MAX_VALUE;
342 int toRead = Math.min((int) chunkSize, maxChunkSize);
343 toRead = Math.min(toRead, buffer.readableBytes());
344 if (toRead == 0) {
345 return;
346 }
347 HttpContent chunk = new DefaultHttpContent(buffer.readSlice(toRead).retain());
348 chunkSize -= toRead;
349
350 out.add(chunk);
351
352 if (chunkSize != 0) {
353 return;
354 }
355 currentState = State.READ_CHUNK_DELIMITER;
356
357 }
358 case READ_CHUNK_DELIMITER: {
359 final int wIdx = buffer.writerIndex();
360 int rIdx = buffer.readerIndex();
361 while (wIdx > rIdx) {
362 byte next = buffer.getByte(rIdx++);
363 if (next == HttpConstants.LF) {
364 currentState = State.READ_CHUNK_SIZE;
365 break;
366 }
367 }
368 buffer.readerIndex(rIdx);
369 return;
370 }
371 case READ_CHUNK_FOOTER: try {
372 LastHttpContent trailer = readTrailingHeaders(buffer);
373 if (trailer == null) {
374 return;
375 }
376 out.add(trailer);
377 resetNow();
378 return;
379 } catch (Exception e) {
380 out.add(invalidChunk(buffer, e));
381 return;
382 }
383 case BAD_MESSAGE: {
384
385 buffer.skipBytes(buffer.readableBytes());
386 break;
387 }
388 case UPGRADED: {
389 int readableBytes = buffer.readableBytes();
390 if (readableBytes > 0) {
391
392
393
394
395 out.add(buffer.readBytes(readableBytes));
396 }
397 break;
398 }
399 }
400 }
401
402 @Override
403 protected void decodeLast(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
404 super.decodeLast(ctx, in, out);
405
406 if (resetRequested) {
407
408
409 resetNow();
410 }
411
412 if (message != null) {
413 boolean chunked = HttpHeaders.isTransferEncodingChunked(message);
414 if (currentState == State.READ_VARIABLE_LENGTH_CONTENT && !in.isReadable() && !chunked) {
415
416 out.add(LastHttpContent.EMPTY_LAST_CONTENT);
417 resetNow();
418 return;
419 }
420
421 if (currentState == State.READ_HEADER) {
422
423
424 out.add(invalidMessage(Unpooled.EMPTY_BUFFER,
425 new PrematureChannelClosureException("Connection closed before received headers")));
426 resetNow();
427 return;
428 }
429
430
431 boolean prematureClosure;
432 if (isDecodingRequest() || chunked) {
433
434 prematureClosure = true;
435 } else {
436
437
438
439 prematureClosure = contentLength() > 0;
440 }
441
442 if (!prematureClosure) {
443 out.add(LastHttpContent.EMPTY_LAST_CONTENT);
444 }
445 resetNow();
446 }
447 }
448
449 @Override
450 public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
451 if (evt instanceof HttpExpectationFailedEvent) {
452 switch (currentState) {
453 case READ_FIXED_LENGTH_CONTENT:
454 case READ_VARIABLE_LENGTH_CONTENT:
455 case READ_CHUNK_SIZE:
456 reset();
457 break;
458 default:
459 break;
460 }
461 }
462 super.userEventTriggered(ctx, evt);
463 }
464
465 protected boolean isContentAlwaysEmpty(HttpMessage msg) {
466 if (msg instanceof HttpResponse) {
467 HttpResponse res = (HttpResponse) msg;
468 int code = res.getStatus().code();
469
470
471
472
473
474
475 if (code >= 100 && code < 200) {
476
477 return !(code == 101 && !res.headers().contains(HttpHeaders.Names.SEC_WEBSOCKET_ACCEPT)
478 && res.headers().contains(HttpHeaders.Names.UPGRADE, HttpHeaders.Values.WEBSOCKET, true));
479 }
480
481 switch (code) {
482 case 204: case 304:
483 return true;
484 }
485 }
486 return false;
487 }
488
489
490
491
492
493 protected boolean isSwitchingToNonHttp1Protocol(HttpResponse msg) {
494 if (msg.getStatus().code() != HttpResponseStatus.SWITCHING_PROTOCOLS.code()) {
495 return false;
496 }
497 String newProtocol = msg.headers().get(HttpHeaders.Names.UPGRADE);
498 return newProtocol == null ||
499 !newProtocol.contains(HttpVersion.HTTP_1_0.text()) &&
500 !newProtocol.contains(HttpVersion.HTTP_1_1.text());
501 }
502
503
504
505
506
507 public void reset() {
508 resetRequested = true;
509 }
510
511 private void resetNow() {
512 HttpMessage message = this.message;
513 this.message = null;
514 name = null;
515 value = null;
516 contentLength = Long.MIN_VALUE;
517 lineParser.reset();
518 headerParser.reset();
519 trailer = null;
520 if (!isDecodingRequest()) {
521 HttpResponse res = (HttpResponse) message;
522 if (res != null && isSwitchingToNonHttp1Protocol(res)) {
523 currentState = State.UPGRADED;
524 return;
525 }
526 }
527
528 resetRequested = false;
529 currentState = State.SKIP_CONTROL_CHARS;
530 }
531
532 private HttpMessage invalidMessage(ByteBuf in, Exception cause) {
533 currentState = State.BAD_MESSAGE;
534
535
536
537 in.skipBytes(in.readableBytes());
538
539 if (message != null) {
540 message.setDecoderResult(DecoderResult.failure(cause));
541 } else {
542 message = createInvalidMessage();
543 message.setDecoderResult(DecoderResult.failure(cause));
544 }
545
546 HttpMessage ret = message;
547 message = null;
548 return ret;
549 }
550
551 private HttpContent invalidChunk(ByteBuf in, Exception cause) {
552 currentState = State.BAD_MESSAGE;
553
554
555
556 in.skipBytes(in.readableBytes());
557
558 HttpContent chunk = new DefaultLastHttpContent(Unpooled.EMPTY_BUFFER);
559 chunk.setDecoderResult(DecoderResult.failure(cause));
560 message = null;
561 trailer = null;
562 return chunk;
563 }
564
565 private static boolean skipControlCharacters(ByteBuf buffer) {
566 boolean skiped = false;
567 final int wIdx = buffer.writerIndex();
568 int rIdx = buffer.readerIndex();
569 while (wIdx > rIdx) {
570 int c = buffer.getUnsignedByte(rIdx++);
571 if (!Character.isISOControl(c) && !Character.isWhitespace(c)) {
572 rIdx--;
573 skiped = true;
574 break;
575 }
576 }
577 buffer.readerIndex(rIdx);
578 return skiped;
579 }
580
581 private State readHeaders(ByteBuf buffer) {
582 final HttpMessage message = this.message;
583 final HttpHeaders headers = message.headers();
584
585 AppendableCharSequence line = headerParser.parse(buffer);
586 if (line == null) {
587 return null;
588 }
589 if (line.length() > 0) {
590 do {
591 char firstChar = line.charAt(0);
592 if (name != null && (firstChar == ' ' || firstChar == '\t')) {
593
594
595 String trimmedLine = line.toString().trim();
596 String valueStr = String.valueOf(value);
597 value = valueStr + ' ' + trimmedLine;
598 } else {
599 if (name != null) {
600 headers.add(name, value);
601 }
602 splitHeader(line);
603 }
604
605 line = headerParser.parse(buffer);
606 if (line == null) {
607 return null;
608 }
609 } while (line.length() > 0);
610 }
611
612
613 if (name != null) {
614 headers.add(name, value);
615 }
616
617 name = null;
618 value = null;
619
620 State nextState;
621
622 if (isContentAlwaysEmpty(message)) {
623 HttpHeaders.removeTransferEncodingChunked(message);
624 nextState = State.SKIP_CONTROL_CHARS;
625 } else if (HttpHeaders.isTransferEncodingChunked(message)) {
626 nextState = State.READ_CHUNK_SIZE;
627 } else if (contentLength() >= 0) {
628 nextState = State.READ_FIXED_LENGTH_CONTENT;
629 } else {
630 nextState = State.READ_VARIABLE_LENGTH_CONTENT;
631 }
632 return nextState;
633 }
634
635 private long contentLength() {
636 if (contentLength == Long.MIN_VALUE) {
637 contentLength = HttpHeaders.getContentLength(message, -1);
638 }
639 return contentLength;
640 }
641
642 private LastHttpContent readTrailingHeaders(ByteBuf buffer) {
643 AppendableCharSequence line = headerParser.parse(buffer);
644 if (line == null) {
645 return null;
646 }
647 CharSequence lastHeader = null;
648 if (line.length() > 0) {
649 LastHttpContent trailer = this.trailer;
650 if (trailer == null) {
651 trailer = this.trailer = new DefaultLastHttpContent(Unpooled.EMPTY_BUFFER, validateHeaders);
652 }
653 do {
654 char firstChar = line.charAt(0);
655 if (lastHeader != null && (firstChar == ' ' || firstChar == '\t')) {
656 List<String> current = trailer.trailingHeaders().getAll(lastHeader);
657 if (!current.isEmpty()) {
658 int lastPos = current.size() - 1;
659
660
661 String lineTrimmed = line.toString().trim();
662 String currentLastPos = current.get(lastPos);
663 current.set(lastPos, currentLastPos + lineTrimmed);
664 }
665 } else {
666 splitHeader(line);
667 CharSequence headerName = name;
668 if (!HttpHeaders.equalsIgnoreCase(HttpHeaders.Names.CONTENT_LENGTH, headerName) &&
669 !HttpHeaders.equalsIgnoreCase(HttpHeaders.Names.TRANSFER_ENCODING, headerName) &&
670 !HttpHeaders.equalsIgnoreCase(HttpHeaders.Names.TRAILER, headerName)) {
671 trailer.trailingHeaders().add(headerName, value);
672 }
673 lastHeader = name;
674
675 name = null;
676 value = null;
677 }
678
679 line = headerParser.parse(buffer);
680 if (line == null) {
681 return null;
682 }
683 } while (line.length() > 0);
684
685 this.trailer = null;
686 return trailer;
687 }
688
689 return LastHttpContent.EMPTY_LAST_CONTENT;
690 }
691
692 protected abstract boolean isDecodingRequest();
693 protected abstract HttpMessage createMessage(String[] initialLine) throws Exception;
694 protected abstract HttpMessage createInvalidMessage();
695
696 private static int getChunkSize(String hex) {
697 hex = hex.trim();
698 for (int i = 0; i < hex.length(); i ++) {
699 char c = hex.charAt(i);
700 if (c == ';' || Character.isWhitespace(c) || Character.isISOControl(c)) {
701 hex = hex.substring(0, i);
702 break;
703 }
704 }
705
706 return Integer.parseInt(hex, 16);
707 }
708
709 private static String[] splitInitialLine(AppendableCharSequence sb) {
710 int aStart;
711 int aEnd;
712 int bStart;
713 int bEnd;
714 int cStart;
715 int cEnd;
716
717 aStart = findNonWhitespace(sb, 0);
718 aEnd = findWhitespace(sb, aStart);
719
720 bStart = findNonWhitespace(sb, aEnd);
721 bEnd = findWhitespace(sb, bStart);
722
723 cStart = findNonWhitespace(sb, bEnd);
724 cEnd = findEndOfString(sb);
725
726 return new String[] {
727 sb.subStringUnsafe(aStart, aEnd),
728 sb.subStringUnsafe(bStart, bEnd),
729 cStart < cEnd? sb.subStringUnsafe(cStart, cEnd) : "" };
730 }
731
732 private void splitHeader(AppendableCharSequence sb) {
733 final int length = sb.length();
734 int nameStart;
735 int nameEnd;
736 int colonEnd;
737 int valueStart;
738 int valueEnd;
739
740 nameStart = findNonWhitespace(sb, 0);
741 for (nameEnd = nameStart; nameEnd < length; nameEnd ++) {
742 char ch = sb.charAt(nameEnd);
743 if (ch == ':' || Character.isWhitespace(ch)) {
744 break;
745 }
746 }
747
748 for (colonEnd = nameEnd; colonEnd < length; colonEnd ++) {
749 if (sb.charAt(colonEnd) == ':') {
750 colonEnd ++;
751 break;
752 }
753 }
754
755 name = sb.subStringUnsafe(nameStart, nameEnd);
756 valueStart = findNonWhitespace(sb, colonEnd);
757 if (valueStart == length) {
758 value = EMPTY_VALUE;
759 } else {
760 valueEnd = findEndOfString(sb);
761 value = sb.subStringUnsafe(valueStart, valueEnd);
762 }
763 }
764
765 private static int findNonWhitespace(AppendableCharSequence sb, int offset) {
766 for (int result = offset; result < sb.length(); ++result) {
767 if (!Character.isWhitespace(sb.charAtUnsafe(result))) {
768 return result;
769 }
770 }
771 return sb.length();
772 }
773
774 private static int findWhitespace(AppendableCharSequence sb, int offset) {
775 for (int result = offset; result < sb.length(); ++result) {
776 if (Character.isWhitespace(sb.charAtUnsafe(result))) {
777 return result;
778 }
779 }
780 return sb.length();
781 }
782
783 private static int findEndOfString(AppendableCharSequence sb) {
784 for (int result = sb.length() - 1; result > 0; --result) {
785 if (!Character.isWhitespace(sb.charAtUnsafe(result))) {
786 return result + 1;
787 }
788 }
789 return 0;
790 }
791
792 private static class HeaderParser implements ByteBufProcessor {
793 private final AppendableCharSequence seq;
794 private final int maxLength;
795 private int size;
796
797 HeaderParser(AppendableCharSequence seq, int maxLength) {
798 this.seq = seq;
799 this.maxLength = maxLength;
800 }
801
802 public AppendableCharSequence parse(ByteBuf buffer) {
803 final int oldSize = size;
804 seq.reset();
805 int i = buffer.forEachByte(this);
806 if (i == -1) {
807 size = oldSize;
808 return null;
809 }
810 buffer.readerIndex(i + 1);
811 return seq;
812 }
813
814 public void reset() {
815 size = 0;
816 }
817
818 @Override
819 public boolean process(byte value) throws Exception {
820 char nextByte = (char) (value & 0xFF);
821 if (nextByte == HttpConstants.CR) {
822 return true;
823 }
824 if (nextByte == HttpConstants.LF) {
825 return false;
826 }
827
828 if (++ size > maxLength) {
829
830
831
832
833 throw newException(maxLength);
834 }
835
836 seq.append(nextByte);
837 return true;
838 }
839
840 protected TooLongFrameException newException(int maxLength) {
841 return new TooLongFrameException("HTTP header is larger than " + maxLength + " bytes.");
842 }
843 }
844
845 private static final class LineParser extends HeaderParser {
846
847 LineParser(AppendableCharSequence seq, int maxLength) {
848 super(seq, maxLength);
849 }
850
851 @Override
852 public AppendableCharSequence parse(ByteBuf buffer) {
853 reset();
854 return super.parse(buffer);
855 }
856
857 @Override
858 protected TooLongFrameException newException(int maxLength) {
859 return new TooLongFrameException("An HTTP line is larger than " + maxLength + " bytes.");
860 }
861 }
862 }