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