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.Unpooled;
20 import io.netty.channel.ChannelHandlerContext;
21 import io.netty.channel.ChannelPipeline;
22 import io.netty.handler.codec.ByteToMessageDecoder;
23 import io.netty.handler.codec.DecoderResult;
24 import io.netty.handler.codec.PrematureChannelClosureException;
25 import io.netty.handler.codec.TooLongFrameException;
26 import io.netty.util.AsciiString;
27 import io.netty.util.ByteProcessor;
28 import io.netty.util.internal.StringUtil;
29
30 import java.util.List;
31 import java.util.concurrent.atomic.AtomicBoolean;
32
33 import static io.netty.util.internal.ObjectUtil.checkNotNull;
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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145 public abstract class HttpObjectDecoder extends ByteToMessageDecoder {
146 public static final int DEFAULT_MAX_INITIAL_LINE_LENGTH = 4096;
147 public static final int DEFAULT_MAX_HEADER_SIZE = 8192;
148 public static final boolean DEFAULT_CHUNKED_SUPPORTED = true;
149 public static final boolean DEFAULT_ALLOW_PARTIAL_CHUNKS = true;
150 public static final int DEFAULT_MAX_CHUNK_SIZE = 8192;
151 public static final boolean DEFAULT_VALIDATE_HEADERS = true;
152 public static final int DEFAULT_INITIAL_BUFFER_SIZE = 128;
153 public static final boolean DEFAULT_ALLOW_DUPLICATE_CONTENT_LENGTHS = false;
154 private final int maxChunkSize;
155 private final boolean chunkedSupported;
156 private final boolean allowPartialChunks;
157
158
159
160 @Deprecated
161 protected final boolean validateHeaders;
162 protected final HttpHeadersFactory headersFactory;
163 protected final HttpHeadersFactory trailersFactory;
164 private final boolean allowDuplicateContentLengths;
165 private final ByteBuf parserScratchBuffer;
166 private final HeaderParser headerParser;
167 private final LineParser lineParser;
168
169 private HttpMessage message;
170 private long chunkSize;
171 private long contentLength = Long.MIN_VALUE;
172 private boolean chunked;
173 private boolean isSwitchingToNonHttp1Protocol;
174
175 private final AtomicBoolean resetRequested = new AtomicBoolean();
176
177
178 private AsciiString name;
179 private String value;
180 private LastHttpContent trailer;
181
182 @Override
183 protected void handlerRemoved0(ChannelHandlerContext ctx) throws Exception {
184 try {
185 parserScratchBuffer.release();
186 } finally {
187 super.handlerRemoved0(ctx);
188 }
189 }
190
191
192
193
194
195 private enum State {
196 SKIP_CONTROL_CHARS,
197 READ_INITIAL,
198 READ_HEADER,
199 READ_VARIABLE_LENGTH_CONTENT,
200 READ_FIXED_LENGTH_CONTENT,
201 READ_CHUNK_SIZE,
202 READ_CHUNKED_CONTENT,
203 READ_CHUNK_DELIMITER,
204 READ_CHUNK_FOOTER,
205 BAD_MESSAGE,
206 UPGRADED
207 }
208
209 private State currentState = State.SKIP_CONTROL_CHARS;
210
211
212
213
214
215
216 protected HttpObjectDecoder() {
217 this(new HttpDecoderConfig());
218 }
219
220
221
222
223
224
225 @Deprecated
226 protected HttpObjectDecoder(
227 int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, boolean chunkedSupported) {
228 this(new HttpDecoderConfig()
229 .setMaxInitialLineLength(maxInitialLineLength)
230 .setMaxHeaderSize(maxHeaderSize)
231 .setMaxChunkSize(maxChunkSize)
232 .setChunkedSupported(chunkedSupported));
233 }
234
235
236
237
238
239
240 @Deprecated
241 protected HttpObjectDecoder(
242 int maxInitialLineLength, int maxHeaderSize, int maxChunkSize,
243 boolean chunkedSupported, boolean validateHeaders) {
244 this(new HttpDecoderConfig()
245 .setMaxInitialLineLength(maxInitialLineLength)
246 .setMaxHeaderSize(maxHeaderSize)
247 .setMaxChunkSize(maxChunkSize)
248 .setChunkedSupported(chunkedSupported)
249 .setValidateHeaders(validateHeaders));
250 }
251
252
253
254
255
256
257 @Deprecated
258 protected HttpObjectDecoder(
259 int maxInitialLineLength, int maxHeaderSize, int maxChunkSize,
260 boolean chunkedSupported, boolean validateHeaders, int initialBufferSize) {
261 this(new HttpDecoderConfig()
262 .setMaxInitialLineLength(maxInitialLineLength)
263 .setMaxHeaderSize(maxHeaderSize)
264 .setMaxChunkSize(maxChunkSize)
265 .setChunkedSupported(chunkedSupported)
266 .setValidateHeaders(validateHeaders)
267 .setInitialBufferSize(initialBufferSize));
268 }
269
270
271
272
273
274
275 @Deprecated
276 protected HttpObjectDecoder(
277 int maxInitialLineLength, int maxHeaderSize, int maxChunkSize,
278 boolean chunkedSupported, boolean validateHeaders, int initialBufferSize,
279 boolean allowDuplicateContentLengths) {
280 this(new HttpDecoderConfig()
281 .setMaxInitialLineLength(maxInitialLineLength)
282 .setMaxHeaderSize(maxHeaderSize)
283 .setMaxChunkSize(maxChunkSize)
284 .setChunkedSupported(chunkedSupported)
285 .setValidateHeaders(validateHeaders)
286 .setInitialBufferSize(initialBufferSize)
287 .setAllowDuplicateContentLengths(allowDuplicateContentLengths));
288 }
289
290
291
292
293
294
295 @Deprecated
296 protected HttpObjectDecoder(
297 int maxInitialLineLength, int maxHeaderSize, int maxChunkSize,
298 boolean chunkedSupported, boolean validateHeaders, int initialBufferSize,
299 boolean allowDuplicateContentLengths, boolean allowPartialChunks) {
300 this(new HttpDecoderConfig()
301 .setMaxInitialLineLength(maxInitialLineLength)
302 .setMaxHeaderSize(maxHeaderSize)
303 .setMaxChunkSize(maxChunkSize)
304 .setChunkedSupported(chunkedSupported)
305 .setValidateHeaders(validateHeaders)
306 .setInitialBufferSize(initialBufferSize)
307 .setAllowDuplicateContentLengths(allowDuplicateContentLengths)
308 .setAllowPartialChunks(allowPartialChunks));
309 }
310
311
312
313
314 protected HttpObjectDecoder(HttpDecoderConfig config) {
315 checkNotNull(config, "config");
316
317 parserScratchBuffer = Unpooled.buffer(config.getInitialBufferSize());
318 lineParser = new LineParser(parserScratchBuffer, config.getMaxInitialLineLength());
319 headerParser = new HeaderParser(parserScratchBuffer, config.getMaxHeaderSize());
320 maxChunkSize = config.getMaxChunkSize();
321 chunkedSupported = config.isChunkedSupported();
322 headersFactory = config.getHeadersFactory();
323 trailersFactory = config.getTrailersFactory();
324 validateHeaders = isValidating(headersFactory);
325 allowDuplicateContentLengths = config.isAllowDuplicateContentLengths();
326 allowPartialChunks = config.isAllowPartialChunks();
327 }
328
329 protected boolean isValidating(HttpHeadersFactory headersFactory) {
330 if (headersFactory instanceof DefaultHttpHeadersFactory) {
331 DefaultHttpHeadersFactory builder = (DefaultHttpHeadersFactory) headersFactory;
332 return builder.isValidatingHeaderNames() || builder.isValidatingHeaderValues();
333 }
334 return true;
335 }
336
337 @Override
338 protected void decode(ChannelHandlerContext ctx, ByteBuf buffer, List<Object> out) throws Exception {
339 if (resetRequested.get()) {
340 resetNow();
341 }
342
343 switch (currentState) {
344 case SKIP_CONTROL_CHARS:
345
346 case READ_INITIAL: try {
347 ByteBuf line = lineParser.parse(buffer);
348 if (line == null) {
349 return;
350 }
351 final String[] initialLine = splitInitialLine(line);
352 assert initialLine.length == 3 : "initialLine::length must be 3";
353
354 message = createMessage(initialLine);
355 currentState = State.READ_HEADER;
356
357 } catch (Exception e) {
358 out.add(invalidMessage(message, buffer, e));
359 return;
360 }
361 case READ_HEADER: try {
362 State nextState = readHeaders(buffer);
363 if (nextState == null) {
364 return;
365 }
366 currentState = nextState;
367 switch (nextState) {
368 case SKIP_CONTROL_CHARS:
369
370
371 addCurrentMessage(out);
372 out.add(LastHttpContent.EMPTY_LAST_CONTENT);
373 resetNow();
374 return;
375 case READ_CHUNK_SIZE:
376 if (!chunkedSupported) {
377 throw new IllegalArgumentException("Chunked messages not supported");
378 }
379
380 addCurrentMessage(out);
381 return;
382 default:
383
384
385
386
387
388
389 if (contentLength == 0 || contentLength == -1 && isDecodingRequest()) {
390 addCurrentMessage(out);
391 out.add(LastHttpContent.EMPTY_LAST_CONTENT);
392 resetNow();
393 return;
394 }
395
396 assert nextState == State.READ_FIXED_LENGTH_CONTENT ||
397 nextState == State.READ_VARIABLE_LENGTH_CONTENT;
398
399 addCurrentMessage(out);
400
401 if (nextState == State.READ_FIXED_LENGTH_CONTENT) {
402
403 chunkSize = contentLength;
404 }
405
406
407 return;
408 }
409 } catch (Exception e) {
410 out.add(invalidMessage(message, buffer, e));
411 return;
412 }
413 case READ_VARIABLE_LENGTH_CONTENT: {
414
415 int toRead = Math.min(buffer.readableBytes(), maxChunkSize);
416 if (toRead > 0) {
417 ByteBuf content = buffer.readRetainedSlice(toRead);
418 out.add(new DefaultHttpContent(content));
419 }
420 return;
421 }
422 case READ_FIXED_LENGTH_CONTENT: {
423 int readLimit = buffer.readableBytes();
424
425
426
427
428
429
430
431 if (readLimit == 0) {
432 return;
433 }
434
435 int toRead = Math.min(readLimit, maxChunkSize);
436 if (toRead > chunkSize) {
437 toRead = (int) chunkSize;
438 }
439 ByteBuf content = buffer.readRetainedSlice(toRead);
440 chunkSize -= toRead;
441
442 if (chunkSize == 0) {
443
444 out.add(new DefaultLastHttpContent(content, trailersFactory));
445 resetNow();
446 } else {
447 out.add(new DefaultHttpContent(content));
448 }
449 return;
450 }
451
452
453
454
455 case READ_CHUNK_SIZE: try {
456 ByteBuf line = lineParser.parse(buffer);
457 if (line == null) {
458 return;
459 }
460 int chunkSize = getChunkSize(line.array(), line.arrayOffset() + line.readerIndex(), line.readableBytes());
461 this.chunkSize = chunkSize;
462 if (chunkSize == 0) {
463 currentState = State.READ_CHUNK_FOOTER;
464 return;
465 }
466 currentState = State.READ_CHUNKED_CONTENT;
467
468 } catch (Exception e) {
469 out.add(invalidChunk(buffer, e));
470 return;
471 }
472 case READ_CHUNKED_CONTENT: {
473 assert chunkSize <= Integer.MAX_VALUE;
474 int toRead = Math.min((int) chunkSize, maxChunkSize);
475 if (!allowPartialChunks && buffer.readableBytes() < toRead) {
476 return;
477 }
478 toRead = Math.min(toRead, buffer.readableBytes());
479 if (toRead == 0) {
480 return;
481 }
482 HttpContent chunk = new DefaultHttpContent(buffer.readRetainedSlice(toRead));
483 chunkSize -= toRead;
484
485 out.add(chunk);
486
487 if (chunkSize != 0) {
488 return;
489 }
490 currentState = State.READ_CHUNK_DELIMITER;
491
492 }
493 case READ_CHUNK_DELIMITER: {
494 final int wIdx = buffer.writerIndex();
495 int rIdx = buffer.readerIndex();
496 while (wIdx > rIdx) {
497 byte next = buffer.getByte(rIdx++);
498 if (next == HttpConstants.LF) {
499 currentState = State.READ_CHUNK_SIZE;
500 break;
501 }
502 }
503 buffer.readerIndex(rIdx);
504 return;
505 }
506 case READ_CHUNK_FOOTER: try {
507 LastHttpContent trailer = readTrailingHeaders(buffer);
508 if (trailer == null) {
509 return;
510 }
511 out.add(trailer);
512 resetNow();
513 return;
514 } catch (Exception e) {
515 out.add(invalidChunk(buffer, e));
516 return;
517 }
518 case BAD_MESSAGE: {
519
520 buffer.skipBytes(buffer.readableBytes());
521 break;
522 }
523 case UPGRADED: {
524 int readableBytes = buffer.readableBytes();
525 if (readableBytes > 0) {
526
527
528
529
530 out.add(buffer.readBytes(readableBytes));
531 }
532 break;
533 }
534 default:
535 break;
536 }
537 }
538
539 @Override
540 protected void decodeLast(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
541 super.decodeLast(ctx, in, out);
542
543 if (resetRequested.get()) {
544
545
546 resetNow();
547 }
548
549
550 switch (currentState) {
551 case READ_VARIABLE_LENGTH_CONTENT:
552 if (!chunked && !in.isReadable()) {
553
554 out.add(LastHttpContent.EMPTY_LAST_CONTENT);
555 resetNow();
556 }
557 return;
558 case READ_HEADER:
559
560
561 out.add(invalidMessage(message, Unpooled.EMPTY_BUFFER,
562 new PrematureChannelClosureException("Connection closed before received headers")));
563 resetNow();
564 return;
565 case READ_CHUNK_DELIMITER:
566 case READ_CHUNK_FOOTER:
567 case READ_CHUNKED_CONTENT:
568 case READ_CHUNK_SIZE:
569 case READ_FIXED_LENGTH_CONTENT:
570
571 boolean prematureClosure;
572 if (isDecodingRequest() || chunked) {
573
574 prematureClosure = true;
575 } else {
576
577
578
579 prematureClosure = contentLength > 0;
580 }
581 if (!prematureClosure) {
582 out.add(LastHttpContent.EMPTY_LAST_CONTENT);
583 }
584 resetNow();
585 return;
586 case SKIP_CONTROL_CHARS:
587 case READ_INITIAL:
588 case BAD_MESSAGE:
589 case UPGRADED:
590
591 break;
592 default:
593 throw new IllegalStateException("Unhandled state " + currentState);
594 }
595 }
596
597 @Override
598 public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
599 if (evt instanceof HttpExpectationFailedEvent) {
600 switch (currentState) {
601 case READ_FIXED_LENGTH_CONTENT:
602 case READ_VARIABLE_LENGTH_CONTENT:
603 case READ_CHUNK_SIZE:
604 reset();
605 break;
606 default:
607 break;
608 }
609 }
610 super.userEventTriggered(ctx, evt);
611 }
612
613 private void addCurrentMessage(List<Object> out) {
614 HttpMessage message = this.message;
615 assert message != null;
616 this.message = null;
617 out.add(message);
618 }
619
620 protected boolean isContentAlwaysEmpty(HttpMessage msg) {
621 if (msg instanceof HttpResponse) {
622 HttpResponse res = (HttpResponse) msg;
623 final HttpResponseStatus status = res.status();
624 final int code = status.code();
625 final HttpStatusClass statusClass = status.codeClass();
626
627
628
629
630
631
632 if (statusClass == HttpStatusClass.INFORMATIONAL) {
633
634 return !(code == 101 && !res.headers().contains(HttpHeaderNames.SEC_WEBSOCKET_ACCEPT)
635 && res.headers().contains(HttpHeaderNames.UPGRADE, HttpHeaderValues.WEBSOCKET, true));
636 }
637
638 switch (code) {
639 case 204: case 304:
640 return true;
641 default:
642 return false;
643 }
644 }
645 return false;
646 }
647
648
649
650
651
652 protected boolean isSwitchingToNonHttp1Protocol(HttpResponse msg) {
653 if (msg.status().code() != HttpResponseStatus.SWITCHING_PROTOCOLS.code()) {
654 return false;
655 }
656 String newProtocol = msg.headers().get(HttpHeaderNames.UPGRADE);
657 return newProtocol == null ||
658 !newProtocol.contains(HttpVersion.HTTP_1_0.text()) &&
659 !newProtocol.contains(HttpVersion.HTTP_1_1.text());
660 }
661
662
663
664
665
666 public void reset() {
667 resetRequested.lazySet(true);
668 }
669
670 private void resetNow() {
671 message = null;
672 name = null;
673 value = null;
674 contentLength = Long.MIN_VALUE;
675 chunked = false;
676 lineParser.reset();
677 headerParser.reset();
678 trailer = null;
679 if (isSwitchingToNonHttp1Protocol) {
680 isSwitchingToNonHttp1Protocol = false;
681 currentState = State.UPGRADED;
682 return;
683 }
684
685 resetRequested.lazySet(false);
686 currentState = State.SKIP_CONTROL_CHARS;
687 }
688
689 private HttpMessage invalidMessage(HttpMessage current, ByteBuf in, Exception cause) {
690 currentState = State.BAD_MESSAGE;
691 message = null;
692 trailer = null;
693
694
695
696 in.skipBytes(in.readableBytes());
697
698 if (current == null) {
699 current = createInvalidMessage();
700 }
701 current.setDecoderResult(DecoderResult.failure(cause));
702
703 return current;
704 }
705
706 private HttpContent invalidChunk(ByteBuf in, Exception cause) {
707 currentState = State.BAD_MESSAGE;
708 message = null;
709 trailer = null;
710
711
712
713 in.skipBytes(in.readableBytes());
714
715 HttpContent chunk = new DefaultLastHttpContent(Unpooled.EMPTY_BUFFER);
716 chunk.setDecoderResult(DecoderResult.failure(cause));
717 return chunk;
718 }
719
720 private State readHeaders(ByteBuf buffer) {
721 final HttpMessage message = this.message;
722 final HttpHeaders headers = message.headers();
723
724 final HeaderParser headerParser = this.headerParser;
725
726 ByteBuf line = headerParser.parse(buffer);
727 if (line == null) {
728 return null;
729 }
730 int lineLength = line.readableBytes();
731 while (lineLength > 0) {
732 final byte[] lineContent = line.array();
733 final int startLine = line.arrayOffset() + line.readerIndex();
734 final byte firstChar = lineContent[startLine];
735 if (name != null && (firstChar == ' ' || firstChar == '\t')) {
736
737
738 String trimmedLine = langAsciiString(lineContent, startLine, lineLength).trim();
739 String valueStr = value;
740 value = valueStr + ' ' + trimmedLine;
741 } else {
742 if (name != null) {
743 headers.add(name, value);
744 }
745 splitHeader(lineContent, startLine, lineLength);
746 }
747
748 line = headerParser.parse(buffer);
749 if (line == null) {
750 return null;
751 }
752 lineLength = line.readableBytes();
753 }
754
755
756 if (name != null) {
757 headers.add(name, value);
758 }
759
760
761 name = null;
762 value = null;
763
764
765 HttpMessageDecoderResult decoderResult = new HttpMessageDecoderResult(lineParser.size, headerParser.size);
766 message.setDecoderResult(decoderResult);
767
768 List<String> contentLengthFields = headers.getAll(HttpHeaderNames.CONTENT_LENGTH);
769 if (!contentLengthFields.isEmpty()) {
770 HttpVersion version = message.protocolVersion();
771 boolean isHttp10OrEarlier = version.majorVersion() < 1 || (version.majorVersion() == 1
772 && version.minorVersion() == 0);
773
774
775 contentLength = HttpUtil.normalizeAndGetContentLength(contentLengthFields,
776 isHttp10OrEarlier, allowDuplicateContentLengths);
777 if (contentLength != -1) {
778 String lengthValue = contentLengthFields.get(0).trim();
779 if (contentLengthFields.size() > 1 ||
780 !lengthValue.equals(Long.toString(contentLength))) {
781 headers.set(HttpHeaderNames.CONTENT_LENGTH, contentLength);
782 }
783 }
784 } else {
785
786
787 contentLength = HttpUtil.getWebSocketContentLength(message);
788 }
789 if (!isDecodingRequest() && message instanceof HttpResponse) {
790 HttpResponse res = (HttpResponse) message;
791 this.isSwitchingToNonHttp1Protocol = isSwitchingToNonHttp1Protocol(res);
792 }
793 if (isContentAlwaysEmpty(message)) {
794 HttpUtil.setTransferEncodingChunked(message, false);
795 return State.SKIP_CONTROL_CHARS;
796 }
797 if (HttpUtil.isTransferEncodingChunked(message)) {
798 this.chunked = true;
799 if (!contentLengthFields.isEmpty() && message.protocolVersion() == HttpVersion.HTTP_1_1) {
800 handleTransferEncodingChunkedWithContentLength(message);
801 }
802 return State.READ_CHUNK_SIZE;
803 }
804 if (contentLength >= 0) {
805 return State.READ_FIXED_LENGTH_CONTENT;
806 }
807 return State.READ_VARIABLE_LENGTH_CONTENT;
808 }
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831 protected void handleTransferEncodingChunkedWithContentLength(HttpMessage message) {
832 message.headers().remove(HttpHeaderNames.CONTENT_LENGTH);
833 contentLength = Long.MIN_VALUE;
834 }
835
836 private LastHttpContent readTrailingHeaders(ByteBuf buffer) {
837 final HeaderParser headerParser = this.headerParser;
838 ByteBuf line = headerParser.parse(buffer);
839 if (line == null) {
840 return null;
841 }
842 LastHttpContent trailer = this.trailer;
843 int lineLength = line.readableBytes();
844 if (lineLength == 0 && trailer == null) {
845
846
847 return LastHttpContent.EMPTY_LAST_CONTENT;
848 }
849
850 CharSequence lastHeader = null;
851 if (trailer == null) {
852 trailer = this.trailer = new DefaultLastHttpContent(Unpooled.EMPTY_BUFFER, trailersFactory);
853 }
854 while (lineLength > 0) {
855 final byte[] lineContent = line.array();
856 final int startLine = line.arrayOffset() + line.readerIndex();
857 final byte firstChar = lineContent[startLine];
858 if (lastHeader != null && (firstChar == ' ' || firstChar == '\t')) {
859 List<String> current = trailer.trailingHeaders().getAll(lastHeader);
860 if (!current.isEmpty()) {
861 int lastPos = current.size() - 1;
862
863
864 String lineTrimmed = langAsciiString(lineContent, startLine, line.readableBytes()).trim();
865 String currentLastPos = current.get(lastPos);
866 current.set(lastPos, currentLastPos + lineTrimmed);
867 }
868 } else {
869 splitHeader(lineContent, startLine, lineLength);
870 AsciiString headerName = name;
871 if (!HttpHeaderNames.CONTENT_LENGTH.contentEqualsIgnoreCase(headerName) &&
872 !HttpHeaderNames.TRANSFER_ENCODING.contentEqualsIgnoreCase(headerName) &&
873 !HttpHeaderNames.TRAILER.contentEqualsIgnoreCase(headerName)) {
874 trailer.trailingHeaders().add(headerName, value);
875 }
876 lastHeader = name;
877
878 name = null;
879 value = null;
880 }
881 line = headerParser.parse(buffer);
882 if (line == null) {
883 return null;
884 }
885 lineLength = line.readableBytes();
886 }
887
888 this.trailer = null;
889 return trailer;
890 }
891
892 protected abstract boolean isDecodingRequest();
893 protected abstract HttpMessage createMessage(String[] initialLine) throws Exception;
894 protected abstract HttpMessage createInvalidMessage();
895
896
897
898
899 private static int skipWhiteSpaces(byte[] hex, int start, int length) {
900 for (int i = 0; i < length; i++) {
901 if (!isWhitespace(hex[start + i])) {
902 return i;
903 }
904 }
905 return length;
906 }
907
908 private static int getChunkSize(byte[] hex, int start, int length) {
909
910 final int skipped = skipWhiteSpaces(hex, start, length);
911 if (skipped == length) {
912
913 throw new NumberFormatException();
914 }
915 start += skipped;
916 length -= skipped;
917 int result = 0;
918 for (int i = 0; i < length; i++) {
919 final int digit = StringUtil.decodeHexNibble(hex[start + i]);
920 if (digit == -1) {
921
922 final byte b = hex[start + i];
923 if (b == ';' || isControlOrWhitespaceAsciiChar(b)) {
924 if (i == 0) {
925
926 throw new NumberFormatException("Empty chunk size");
927 }
928 return result;
929 }
930
931 throw new NumberFormatException("Invalid character in chunk size");
932 }
933 result *= 16;
934 result += digit;
935 if (result < 0) {
936 throw new NumberFormatException("Chunk size overflow: " + result);
937 }
938 }
939 return result;
940 }
941
942 private String[] splitInitialLine(ByteBuf asciiBuffer) {
943 final byte[] asciiBytes = asciiBuffer.array();
944
945 final int arrayOffset = asciiBuffer.arrayOffset();
946
947 final int startContent = arrayOffset + asciiBuffer.readerIndex();
948
949 final int end = startContent + asciiBuffer.readableBytes();
950
951 byte lastByte = asciiBytes[end - 1];
952 if (isControlOrWhitespaceAsciiChar(lastByte)) {
953 if (isDecodingRequest() || !isOWS(lastByte)) {
954
955
956
957
958
959 throw new IllegalArgumentException(
960 "Illegal character in request line: 0x" + Integer.toHexString(lastByte));
961 }
962 }
963
964 final int aStart = findNonSPLenient(asciiBytes, startContent, end);
965 final int aEnd = findSPLenient(asciiBytes, aStart, end);
966
967 final int bStart = findNonSPLenient(asciiBytes, aEnd, end);
968 final int bEnd = findSPLenient(asciiBytes, bStart, end);
969
970 final int cStart = findNonSPLenient(asciiBytes, bEnd, end);
971 final int cEnd = findEndOfString(asciiBytes, Math.max(cStart - 1, startContent), end);
972
973 return new String[]{
974 splitFirstWordInitialLine(asciiBytes, aStart, aEnd - aStart),
975 splitSecondWordInitialLine(asciiBytes, bStart, bEnd - bStart),
976 cStart < cEnd ? splitThirdWordInitialLine(asciiBytes, cStart, cEnd - cStart) : StringUtil.EMPTY_STRING};
977 }
978
979 protected String splitFirstWordInitialLine(final byte[] asciiContent, int start, int length) {
980 return langAsciiString(asciiContent, start, length);
981 }
982
983 protected String splitSecondWordInitialLine(final byte[] asciiContent, int start, int length) {
984 return langAsciiString(asciiContent, start, length);
985 }
986
987 protected String splitThirdWordInitialLine(final byte[] asciiContent, int start, int length) {
988 return langAsciiString(asciiContent, start, length);
989 }
990
991
992
993
994 private static String langAsciiString(final byte[] asciiContent, int start, int length) {
995 if (length == 0) {
996 return StringUtil.EMPTY_STRING;
997 }
998
999 if (start == 0) {
1000 if (length == asciiContent.length) {
1001 return new String(asciiContent, 0, 0, asciiContent.length);
1002 }
1003 return new String(asciiContent, 0, 0, length);
1004 }
1005 return new String(asciiContent, 0, start, length);
1006 }
1007
1008 private void splitHeader(byte[] line, int start, int length) {
1009 final int end = start + length;
1010 int nameEnd;
1011 final int nameStart = start;
1012
1013 final boolean isDecodingRequest = isDecodingRequest();
1014 for (nameEnd = nameStart; nameEnd < end; nameEnd ++) {
1015 byte ch = line[nameEnd];
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025 if (ch == ':' ||
1026
1027
1028
1029
1030 (!isDecodingRequest && isOWS(ch))) {
1031 break;
1032 }
1033 }
1034
1035 if (nameEnd == end) {
1036
1037 throw new IllegalArgumentException("No colon found");
1038 }
1039 int colonEnd;
1040 for (colonEnd = nameEnd; colonEnd < end; colonEnd ++) {
1041 if (line[colonEnd] == ':') {
1042 colonEnd ++;
1043 break;
1044 }
1045 }
1046 name = splitHeaderName(line, nameStart, nameEnd - nameStart);
1047 final int valueStart = findNonWhitespace(line, colonEnd, end);
1048 if (valueStart == end) {
1049 value = StringUtil.EMPTY_STRING;
1050 } else {
1051 final int valueEnd = findEndOfString(line, start, end);
1052
1053 value = langAsciiString(line, valueStart, valueEnd - valueStart);
1054 }
1055 }
1056
1057 protected AsciiString splitHeaderName(byte[] sb, int start, int length) {
1058 return new AsciiString(sb, start, length, true);
1059 }
1060
1061 private static int findNonSPLenient(byte[] sb, int offset, int end) {
1062 for (int result = offset; result < end; ++result) {
1063 byte c = sb[result];
1064
1065 if (isSPLenient(c)) {
1066 continue;
1067 }
1068 if (isWhitespace(c)) {
1069
1070 throw new IllegalArgumentException("Invalid separator");
1071 }
1072 return result;
1073 }
1074 return end;
1075 }
1076
1077 private static int findSPLenient(byte[] sb, int offset, int end) {
1078 for (int result = offset; result < end; ++result) {
1079 if (isSPLenient(sb[result])) {
1080 return result;
1081 }
1082 }
1083 return end;
1084 }
1085
1086 private static final boolean[] SP_LENIENT_BYTES;
1087 private static final boolean[] LATIN_WHITESPACE;
1088
1089 static {
1090
1091 SP_LENIENT_BYTES = new boolean[256];
1092 SP_LENIENT_BYTES[128 + ' '] = true;
1093 SP_LENIENT_BYTES[128 + 0x09] = true;
1094 SP_LENIENT_BYTES[128 + 0x0B] = true;
1095 SP_LENIENT_BYTES[128 + 0x0C] = true;
1096 SP_LENIENT_BYTES[128 + 0x0D] = true;
1097
1098 LATIN_WHITESPACE = new boolean[256];
1099 for (byte b = Byte.MIN_VALUE; b < Byte.MAX_VALUE; b++) {
1100 LATIN_WHITESPACE[128 + b] = Character.isWhitespace(b);
1101 }
1102 }
1103
1104 private static boolean isSPLenient(byte c) {
1105
1106 return SP_LENIENT_BYTES[c + 128];
1107 }
1108
1109 private static boolean isWhitespace(byte b) {
1110 return LATIN_WHITESPACE[b + 128];
1111 }
1112
1113 private static int findNonWhitespace(byte[] sb, int offset, int end) {
1114 for (int result = offset; result < end; ++result) {
1115 byte c = sb[result];
1116 if (!isWhitespace(c)) {
1117 return result;
1118 } else if (!isOWS(c)) {
1119
1120 throw new IllegalArgumentException("Invalid separator, only a single space or horizontal tab allowed," +
1121 " but received a '" + c + "' (0x" + Integer.toHexString(c) + ")");
1122 }
1123 }
1124 return end;
1125 }
1126
1127 private static int findEndOfString(byte[] sb, int start, int end) {
1128 for (int result = end - 1; result > start; --result) {
1129 if (!isOWS(sb[result])) {
1130 return result + 1;
1131 }
1132 }
1133 return 0;
1134 }
1135
1136 private static boolean isOWS(byte ch) {
1137 return ch == ' ' || ch == 0x09;
1138 }
1139
1140 private static class HeaderParser {
1141 protected final ByteBuf seq;
1142 protected final int maxLength;
1143 int size;
1144
1145 HeaderParser(ByteBuf seq, int maxLength) {
1146 this.seq = seq;
1147 this.maxLength = maxLength;
1148 }
1149
1150 public ByteBuf parse(ByteBuf buffer) {
1151 final int readableBytes = buffer.readableBytes();
1152 final int readerIndex = buffer.readerIndex();
1153 final int maxBodySize = maxLength - size;
1154 assert maxBodySize >= 0;
1155
1156
1157 final long maxBodySizeWithCRLF = maxBodySize + 2L;
1158 final int toProcess = (int) Math.min(maxBodySizeWithCRLF, readableBytes);
1159 final int toIndexExclusive = readerIndex + toProcess;
1160 assert toIndexExclusive >= readerIndex;
1161 final int indexOfLf = buffer.indexOf(readerIndex, toIndexExclusive, HttpConstants.LF);
1162 if (indexOfLf == -1) {
1163 if (readableBytes > maxBodySize) {
1164
1165
1166
1167
1168 throw newException(maxLength);
1169 }
1170 return null;
1171 }
1172 final int endOfSeqIncluded;
1173 if (indexOfLf > readerIndex && buffer.getByte(indexOfLf - 1) == HttpConstants.CR) {
1174
1175 endOfSeqIncluded = indexOfLf - 1;
1176 } else {
1177 endOfSeqIncluded = indexOfLf;
1178 }
1179 final int newSize = endOfSeqIncluded - readerIndex;
1180 if (newSize == 0) {
1181 seq.clear();
1182 buffer.readerIndex(indexOfLf + 1);
1183 return seq;
1184 }
1185 int size = this.size + newSize;
1186 if (size > maxLength) {
1187 throw newException(maxLength);
1188 }
1189 this.size = size;
1190 seq.clear();
1191 seq.writeBytes(buffer, readerIndex, newSize);
1192 buffer.readerIndex(indexOfLf + 1);
1193 return seq;
1194 }
1195
1196 public void reset() {
1197 size = 0;
1198 }
1199
1200 protected TooLongFrameException newException(int maxLength) {
1201 return new TooLongHttpHeaderException("HTTP header is larger than " + maxLength + " bytes.");
1202 }
1203 }
1204
1205 private final class LineParser extends HeaderParser {
1206
1207 LineParser(ByteBuf seq, int maxLength) {
1208 super(seq, maxLength);
1209 }
1210
1211 @Override
1212 public ByteBuf parse(ByteBuf buffer) {
1213
1214 reset();
1215 final int readableBytes = buffer.readableBytes();
1216 if (readableBytes == 0) {
1217 return null;
1218 }
1219 final int readerIndex = buffer.readerIndex();
1220 if (currentState == State.SKIP_CONTROL_CHARS && skipControlChars(buffer, readableBytes, readerIndex)) {
1221 return null;
1222 }
1223 return super.parse(buffer);
1224 }
1225
1226 private boolean skipControlChars(ByteBuf buffer, int readableBytes, int readerIndex) {
1227 assert currentState == State.SKIP_CONTROL_CHARS;
1228 final int maxToSkip = Math.min(maxLength, readableBytes);
1229 final int firstNonControlIndex = buffer.forEachByte(readerIndex, maxToSkip, SKIP_CONTROL_CHARS_BYTES);
1230 if (firstNonControlIndex == -1) {
1231 buffer.skipBytes(maxToSkip);
1232 if (readableBytes > maxLength) {
1233 throw newException(maxLength);
1234 }
1235 return true;
1236 }
1237
1238 buffer.readerIndex(firstNonControlIndex);
1239 currentState = State.READ_INITIAL;
1240 return false;
1241 }
1242
1243 @Override
1244 protected TooLongFrameException newException(int maxLength) {
1245 return new TooLongHttpLineException("An HTTP line is larger than " + maxLength + " bytes.");
1246 }
1247 }
1248
1249 private static final boolean[] ISO_CONTROL_OR_WHITESPACE;
1250
1251 static {
1252 ISO_CONTROL_OR_WHITESPACE = new boolean[256];
1253 for (byte b = Byte.MIN_VALUE; b < Byte.MAX_VALUE; b++) {
1254 ISO_CONTROL_OR_WHITESPACE[128 + b] = Character.isISOControl(b) || isWhitespace(b);
1255 }
1256 }
1257
1258 private static final ByteProcessor SKIP_CONTROL_CHARS_BYTES = new ByteProcessor() {
1259
1260 @Override
1261 public boolean process(byte value) {
1262 return ISO_CONTROL_OR_WHITESPACE[128 + value];
1263 }
1264 };
1265
1266 private static boolean isControlOrWhitespaceAsciiChar(byte b) {
1267 return ISO_CONTROL_OR_WHITESPACE[128 + b];
1268 }
1269 }