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 !isLengthEqual(lengthValue, 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 private static boolean isLengthEqual(String lengthValue, long contentLength) {
881 try {
882 return Long.parseLong(lengthValue) == contentLength;
883 } catch (NumberFormatException e) {
884 return false;
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
923
924
925
926
927
928
929
930 @SuppressWarnings("unused")
931 protected void handleTransferEncodingChunkedWithContentLength(HttpMessage message) {
932 clearContentLength();
933 if (useRfc9112TransferEncoding) {
934 throw new ContentLengthNotAllowedException(
935 "Content-Length are not allowed in HTTP/1.1 messages that contains a Transfer-Encoding header.");
936 } else {
937 message.headers().remove(HttpHeaderNames.CONTENT_LENGTH);
938 if (isDecodingRequest()) {
939 HttpUtil.setKeepAlive(message, false);
940 }
941 }
942 }
943
944 protected final void clearContentLength() {
945 contentLength = Long.MIN_VALUE;
946 }
947
948 private LastHttpContent readTrailingHeaders(ByteBuf buffer) {
949 final HeaderParser headerParser = this.headerParser;
950 ByteBuf line = headerParser.parse(buffer, defaultStrictCRLFCheck);
951 if (line == null) {
952 return null;
953 }
954 LastHttpContent trailer = this.trailer;
955 int lineLength = line.readableBytes();
956 if (lineLength == 0 && trailer == null) {
957
958
959 return LastHttpContent.EMPTY_LAST_CONTENT;
960 }
961
962 if (trailer == null) {
963 trailer = this.trailer = new DefaultLastHttpContent(Unpooled.EMPTY_BUFFER, trailersFactory);
964 }
965 while (lineLength > 0) {
966 final byte[] lineContent = line.array();
967 final int startLine = line.arrayOffset() + line.readerIndex();
968 final byte firstChar = lineContent[startLine];
969 if (name != null && (firstChar == ' ' || firstChar == '\t')) {
970
971
972 String trimmedLine = langAsciiString(lineContent, startLine, lineLength).trim();
973 String valueStr = value;
974 value = valueStr + ' ' + trimmedLine;
975 } else {
976 if (name != null && isPermittedTrailingHeader(name)) {
977 trailer.trailingHeaders().add(name, value);
978 }
979 splitHeader(lineContent, startLine, lineLength);
980 }
981
982 line = headerParser.parse(buffer, defaultStrictCRLFCheck);
983 if (line == null) {
984 return null;
985 }
986 lineLength = line.readableBytes();
987 }
988
989
990 if (name != null && isPermittedTrailingHeader(name)) {
991 trailer.trailingHeaders().add(name, value);
992 }
993
994
995 name = null;
996 value = null;
997
998 this.trailer = null;
999 return trailer;
1000 }
1001
1002
1003
1004
1005 private static boolean isPermittedTrailingHeader(final AsciiString name) {
1006 return !HttpHeaderNames.CONTENT_LENGTH.contentEqualsIgnoreCase(name) &&
1007 !HttpHeaderNames.TRANSFER_ENCODING.contentEqualsIgnoreCase(name) &&
1008 !HttpHeaderNames.TRAILER.contentEqualsIgnoreCase(name);
1009 }
1010
1011 protected abstract boolean isDecodingRequest();
1012 protected abstract HttpMessage createMessage(String[] initialLine) throws Exception;
1013 protected abstract HttpMessage createInvalidMessage();
1014
1015
1016
1017
1018 private static int skipWhiteSpaces(byte[] hex, int start, int length) {
1019 for (int i = 0; i < length; i++) {
1020 if (!isWhitespace(hex[start + i])) {
1021 return i;
1022 }
1023 }
1024 return length;
1025 }
1026
1027 private static int getChunkSize(byte[] hex, int start, int length) {
1028
1029 final int skipped = skipWhiteSpaces(hex, start, length);
1030 if (skipped == length) {
1031
1032 throw new NumberFormatException();
1033 }
1034 start += skipped;
1035 length -= skipped;
1036 long result = 0;
1037 for (int i = 0; i < length; i++) {
1038 final int digit = StringUtil.decodeHexNibble(hex[start + i]);
1039 if (digit == -1) {
1040
1041 final byte b = hex[start + i];
1042 if (b == ';' || isControlOrWhitespaceAsciiChar(b)) {
1043 if (i == 0) {
1044
1045 throw new NumberFormatException("Empty chunk size");
1046 }
1047 return (int) result;
1048 }
1049
1050 throw new NumberFormatException("Invalid character in chunk size");
1051 }
1052 result *= 16;
1053 result += digit;
1054 if (result > Integer.MAX_VALUE) {
1055 throw new NumberFormatException("Chunk size overflow: " + result);
1056 }
1057 }
1058 return (int) result;
1059 }
1060
1061 private String[] splitInitialLine(ByteBuf asciiBuffer) {
1062 final byte[] asciiBytes = asciiBuffer.array();
1063
1064 final int arrayOffset = asciiBuffer.arrayOffset();
1065
1066 final int startContent = arrayOffset + asciiBuffer.readerIndex();
1067
1068 final int end = startContent + asciiBuffer.readableBytes();
1069
1070 byte lastByte = asciiBytes[end - 1];
1071 if (isControlOrWhitespaceAsciiChar(lastByte)) {
1072 if (isDecodingRequest() || !isOWS(lastByte)) {
1073
1074
1075
1076
1077
1078 throw new IllegalArgumentException(
1079 "Illegal character in request line: 0x" + Integer.toHexString(lastByte));
1080 }
1081 }
1082
1083 final int aStart = findNonSPLenient(asciiBytes, startContent, end);
1084 final int aEnd = findSPLenient(asciiBytes, aStart, end);
1085
1086 final int bStart = findNonSPLenient(asciiBytes, aEnd, end);
1087 final int bEnd = findSPLenient(asciiBytes, bStart, end);
1088
1089 final int cStart = findNonSPLenient(asciiBytes, bEnd, end);
1090 final int cEnd = findEndOfString(asciiBytes, Math.max(cStart - 1, startContent), end);
1091
1092 return new String[]{
1093 splitFirstWordInitialLine(asciiBytes, aStart, aEnd - aStart),
1094 splitSecondWordInitialLine(asciiBytes, bStart, bEnd - bStart),
1095 cStart < cEnd ? splitThirdWordInitialLine(asciiBytes, cStart, cEnd - cStart) : StringUtil.EMPTY_STRING};
1096 }
1097
1098 protected String splitFirstWordInitialLine(final byte[] asciiContent, int start, int length) {
1099 return langAsciiString(asciiContent, start, length);
1100 }
1101
1102 protected String splitSecondWordInitialLine(final byte[] asciiContent, int start, int length) {
1103 return langAsciiString(asciiContent, start, length);
1104 }
1105
1106 protected String splitThirdWordInitialLine(final byte[] asciiContent, int start, int length) {
1107 return langAsciiString(asciiContent, start, length);
1108 }
1109
1110
1111
1112
1113 private static String langAsciiString(final byte[] asciiContent, int start, int length) {
1114 if (length == 0) {
1115 return StringUtil.EMPTY_STRING;
1116 }
1117
1118 if (start == 0) {
1119 if (length == asciiContent.length) {
1120 return new String(asciiContent, 0, 0, asciiContent.length);
1121 }
1122 return new String(asciiContent, 0, 0, length);
1123 }
1124 return new String(asciiContent, 0, start, length);
1125 }
1126
1127 private void splitHeader(byte[] line, int start, int length) {
1128 final int end = start + length;
1129 int nameEnd;
1130 final int nameStart = start;
1131
1132 final boolean isDecodingRequest = isDecodingRequest();
1133 for (nameEnd = nameStart; nameEnd < end; nameEnd ++) {
1134 byte ch = line[nameEnd];
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144 if (ch == ':' ||
1145
1146
1147
1148
1149 (!isDecodingRequest && isOWS(ch))) {
1150 break;
1151 }
1152 }
1153
1154 if (nameEnd == end) {
1155
1156 throw new IllegalArgumentException("No colon found");
1157 }
1158 int colonEnd;
1159 for (colonEnd = nameEnd; colonEnd < end; colonEnd ++) {
1160 if (line[colonEnd] == ':') {
1161 colonEnd ++;
1162 break;
1163 }
1164 }
1165 name = splitHeaderName(line, nameStart, nameEnd - nameStart);
1166 final int valueStart = findNonWhitespace(line, colonEnd, end);
1167 if (valueStart == end) {
1168 value = StringUtil.EMPTY_STRING;
1169 } else {
1170 final int valueEnd = findEndOfString(line, start, end);
1171
1172 value = langAsciiString(line, valueStart, valueEnd - valueStart);
1173 }
1174 }
1175
1176 protected AsciiString splitHeaderName(byte[] sb, int start, int length) {
1177 return new AsciiString(sb, start, length, true);
1178 }
1179
1180 private static int findNonSPLenient(byte[] sb, int offset, int end) {
1181 for (int result = offset; result < end; ++result) {
1182 byte c = sb[result];
1183
1184 if (isSPLenient(c)) {
1185 continue;
1186 }
1187 if (isWhitespace(c)) {
1188
1189 throw new IllegalArgumentException("Invalid separator");
1190 }
1191 return result;
1192 }
1193 return end;
1194 }
1195
1196 private static int findSPLenient(byte[] sb, int offset, int end) {
1197 for (int result = offset; result < end; ++result) {
1198 if (isSPLenient(sb[result])) {
1199 return result;
1200 }
1201 }
1202 return end;
1203 }
1204
1205 private static final boolean[] SP_LENIENT_BYTES;
1206 private static final boolean[] LATIN_WHITESPACE;
1207
1208 static {
1209
1210 SP_LENIENT_BYTES = new boolean[256];
1211 SP_LENIENT_BYTES[128 + ' '] = true;
1212 SP_LENIENT_BYTES[128 + 0x09] = true;
1213 SP_LENIENT_BYTES[128 + 0x0B] = true;
1214 SP_LENIENT_BYTES[128 + 0x0C] = true;
1215 SP_LENIENT_BYTES[128 + 0x0D] = true;
1216
1217 LATIN_WHITESPACE = new boolean[256];
1218 for (byte b = Byte.MIN_VALUE; b < Byte.MAX_VALUE; b++) {
1219 LATIN_WHITESPACE[128 + b] = Character.isWhitespace(b);
1220 }
1221 }
1222
1223 private static boolean isSPLenient(byte c) {
1224
1225 return SP_LENIENT_BYTES[c + 128];
1226 }
1227
1228 private static boolean isWhitespace(byte b) {
1229 return LATIN_WHITESPACE[b + 128];
1230 }
1231
1232 private static int findNonWhitespace(byte[] sb, int offset, int end) {
1233 for (int result = offset; result < end; ++result) {
1234 byte c = sb[result];
1235 if (!isWhitespace(c)) {
1236 return result;
1237 } else if (!isOWS(c)) {
1238
1239 throw new IllegalArgumentException("Invalid separator, only a single space or horizontal tab allowed," +
1240 " but received a '" + c + "' (0x" + Integer.toHexString(c) + ")");
1241 }
1242 }
1243 return end;
1244 }
1245
1246 private static int findEndOfString(byte[] sb, int start, int end) {
1247 for (int result = end - 1; result > start; --result) {
1248 if (!isOWS(sb[result])) {
1249 return result + 1;
1250 }
1251 }
1252 return 0;
1253 }
1254
1255 private static boolean isOWS(byte ch) {
1256 return ch == ' ' || ch == 0x09;
1257 }
1258
1259 private static class HeaderParser {
1260 protected final ByteBuf seq;
1261 protected final int maxLength;
1262 int size;
1263
1264 HeaderParser(ByteBuf seq, int maxLength) {
1265 this.seq = seq;
1266 this.maxLength = maxLength;
1267 }
1268
1269 public ByteBuf parse(ByteBuf buffer, Runnable strictCRLFCheck) {
1270 final int readableBytes = buffer.readableBytes();
1271 final int readerIndex = buffer.readerIndex();
1272 final int maxBodySize = maxLength - size;
1273 assert maxBodySize >= 0;
1274
1275
1276 final long maxBodySizeWithCRLF = maxBodySize + 2L;
1277 final int toProcess = (int) Math.min(maxBodySizeWithCRLF, readableBytes);
1278 final int toIndexExclusive = readerIndex + toProcess;
1279 assert toIndexExclusive >= readerIndex;
1280 final int indexOfLf = buffer.indexOf(readerIndex, toIndexExclusive, HttpConstants.LF);
1281 if (indexOfLf == -1) {
1282 if (readableBytes > maxBodySize) {
1283
1284
1285
1286
1287 throw newException(maxLength);
1288 }
1289 return null;
1290 }
1291 final int endOfSeqIncluded;
1292 if (indexOfLf > readerIndex && buffer.getByte(indexOfLf - 1) == HttpConstants.CR) {
1293
1294 endOfSeqIncluded = indexOfLf - 1;
1295 } else {
1296 if (strictCRLFCheck != null) {
1297 strictCRLFCheck.run();
1298 }
1299 endOfSeqIncluded = indexOfLf;
1300 }
1301 final int newSize = endOfSeqIncluded - readerIndex;
1302 if (newSize == 0) {
1303 seq.clear();
1304 buffer.readerIndex(indexOfLf + 1);
1305 return seq;
1306 }
1307 int size = this.size + newSize;
1308 if (size > maxLength) {
1309 throw newException(maxLength);
1310 }
1311 this.size = size;
1312 seq.clear();
1313 seq.writeBytes(buffer, readerIndex, newSize);
1314 buffer.readerIndex(indexOfLf + 1);
1315 return seq;
1316 }
1317
1318 public void reset() {
1319 size = 0;
1320 }
1321
1322 protected TooLongFrameException newException(int maxLength) {
1323 return new TooLongHttpHeaderException("HTTP header is larger than " + maxLength + " bytes.");
1324 }
1325 }
1326
1327 private final class LineParser extends HeaderParser {
1328
1329 LineParser(ByteBuf seq, int maxLength) {
1330 super(seq, maxLength);
1331 }
1332
1333 @Override
1334 public ByteBuf parse(ByteBuf buffer, Runnable strictCRLFCheck) {
1335
1336 reset();
1337 final int readableBytes = buffer.readableBytes();
1338 if (readableBytes == 0) {
1339 return null;
1340 }
1341 if (currentState == State.SKIP_CONTROL_CHARS &&
1342 skipControlChars(buffer, readableBytes, buffer.readerIndex())) {
1343 return null;
1344 }
1345 return super.parse(buffer, strictCRLFCheck);
1346 }
1347
1348 private boolean skipControlChars(ByteBuf buffer, int readableBytes, int readerIndex) {
1349 assert currentState == State.SKIP_CONTROL_CHARS;
1350 final int maxToSkip = Math.min(maxLength, readableBytes);
1351 final int firstNonControlIndex = buffer.forEachByte(readerIndex, maxToSkip, SKIP_CONTROL_CHARS_BYTES);
1352 if (firstNonControlIndex == -1) {
1353 buffer.skipBytes(maxToSkip);
1354 if (readableBytes > maxLength) {
1355 throw newException(maxLength);
1356 }
1357 return true;
1358 }
1359
1360 buffer.readerIndex(firstNonControlIndex);
1361 currentState = State.READ_INITIAL;
1362 return false;
1363 }
1364
1365 @Override
1366 protected TooLongFrameException newException(int maxLength) {
1367 return new TooLongHttpLineException("An HTTP line is larger than " + maxLength + " bytes.");
1368 }
1369 }
1370
1371 private static final boolean[] ISO_CONTROL_OR_WHITESPACE;
1372
1373 static {
1374 ISO_CONTROL_OR_WHITESPACE = new boolean[256];
1375 for (byte b = Byte.MIN_VALUE; b < Byte.MAX_VALUE; b++) {
1376 ISO_CONTROL_OR_WHITESPACE[128 + b] = Character.isISOControl(b) || isWhitespace(b);
1377 }
1378 }
1379
1380 private static final ByteProcessor SKIP_CONTROL_CHARS_BYTES = new ByteProcessor() {
1381
1382 @Override
1383 public boolean process(byte value) {
1384 return ISO_CONTROL_OR_WHITESPACE[128 + value];
1385 }
1386 };
1387
1388 private static boolean isControlOrWhitespaceAsciiChar(byte b) {
1389 return ISO_CONTROL_OR_WHITESPACE[128 + b];
1390 }
1391 }