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.List;
33 import java.util.concurrent.atomic.AtomicBoolean;
34
35 import static io.netty.util.internal.ObjectUtil.checkNotNull;
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
146
147 public abstract class HttpObjectDecoder extends ByteToMessageDecoder {
148 public static final int DEFAULT_MAX_INITIAL_LINE_LENGTH = 4096;
149 public static final int DEFAULT_MAX_HEADER_SIZE = 8192;
150 public static final boolean DEFAULT_CHUNKED_SUPPORTED = true;
151 public static final boolean DEFAULT_ALLOW_PARTIAL_CHUNKS = true;
152 public static final int DEFAULT_MAX_CHUNK_SIZE = 8192;
153 public static final boolean DEFAULT_VALIDATE_HEADERS = true;
154 public static final int DEFAULT_INITIAL_BUFFER_SIZE = 128;
155 public static final boolean DEFAULT_ALLOW_DUPLICATE_CONTENT_LENGTHS = false;
156 public static final boolean DEFAULT_STRICT_LINE_PARSING =
157 SystemPropertyUtil.getBoolean("io.netty.handler.codec.http.defaultStrictLineParsing", true);
158 public static final String PROP_RFC9112_TRANSFER_ENCODING = "io.netty.handler.codec.http.rfc9112TransferEncoding";
159 public static final boolean RFC9112_TRANSFER_ENCODING =
160 SystemPropertyUtil.getBoolean(PROP_RFC9112_TRANSFER_ENCODING, true);
161
162 private static final Runnable THROW_INVALID_CHUNK_EXTENSION = new Runnable() {
163 @Override
164 public void run() {
165 throw new InvalidChunkExtensionException();
166 }
167 };
168
169 private static final Runnable THROW_INVALID_LINE_SEPARATOR = new Runnable() {
170 @Override
171 public void run() {
172 throw new InvalidLineSeparatorException();
173 }
174 };
175 private static final TransferEncodingNotAllowedException TRANSFER_ENCODING_NOT_ALLOWED =
176 ThrowableUtil.unknownStackTrace(
177 new TransferEncodingNotAllowedException(
178 "The Transfer-Encoding header is only allowed in HTTP/1.1 or newer"),
179 HttpObjectDecoder.class,
180 "readHeaders(ByteBuf)");
181
182 private final int maxChunkSize;
183 private final boolean chunkedSupported;
184 private final boolean allowPartialChunks;
185
186
187
188 @Deprecated
189 protected final boolean validateHeaders;
190 protected final HttpHeadersFactory headersFactory;
191 protected final HttpHeadersFactory trailersFactory;
192 private final boolean allowDuplicateContentLengths;
193 private final boolean useRfc9112TransferEncoding;
194 private final ByteBuf parserScratchBuffer;
195 private final Runnable defaultStrictCRLFCheck;
196 private final HeaderParser headerParser;
197 private final LineParser lineParser;
198
199 private HttpMessage message;
200 private long chunkSize;
201 private long contentLength = Long.MIN_VALUE;
202 private boolean chunked;
203 private boolean isSwitchingToNonHttp1Protocol;
204
205 private final AtomicBoolean resetRequested = new AtomicBoolean();
206
207
208 private AsciiString name;
209 private String value;
210 private LastHttpContent trailer;
211
212 @Override
213 protected void handlerRemoved0(ChannelHandlerContext ctx) throws Exception {
214 try {
215 parserScratchBuffer.release();
216 } finally {
217 super.handlerRemoved0(ctx);
218 }
219 }
220
221
222
223
224
225 private enum State {
226 SKIP_INITIAL_LINE_CHARS,
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_INITIAL_LINE_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_INITIAL_LINE_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 SKIP_INITIAL_LINE_CHARS:
622 case READ_INITIAL:
623 case BAD_MESSAGE:
624 case UPGRADED:
625
626 break;
627 default:
628 throw new IllegalStateException("Unhandled state " + currentState);
629 }
630 }
631
632 @Override
633 public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
634 if (evt instanceof HttpExpectationFailedEvent) {
635 switch (currentState) {
636 case READ_FIXED_LENGTH_CONTENT:
637 case READ_VARIABLE_LENGTH_CONTENT:
638 case READ_CHUNK_SIZE:
639 reset();
640 break;
641 default:
642 break;
643 }
644 }
645 super.userEventTriggered(ctx, evt);
646 }
647
648 private void addCurrentMessage(List<Object> out) {
649 HttpMessage message = this.message;
650 assert message != null;
651 this.message = null;
652 out.add(message);
653 }
654
655 protected boolean isContentAlwaysEmpty(HttpMessage msg) {
656 if (msg instanceof HttpResponse) {
657 HttpResponse res = (HttpResponse) msg;
658 final HttpResponseStatus status = res.status();
659 final int code = status.code();
660 final HttpStatusClass statusClass = status.codeClass();
661
662
663
664
665
666
667 if (statusClass == HttpStatusClass.INFORMATIONAL) {
668
669 return !(code == 101 && !res.headers().contains(HttpHeaderNames.SEC_WEBSOCKET_ACCEPT)
670 && res.headers().contains(HttpHeaderNames.UPGRADE, HttpHeaderValues.WEBSOCKET, true));
671 }
672
673 switch (code) {
674 case 204: case 304:
675 return true;
676 default:
677 return false;
678 }
679 }
680 return false;
681 }
682
683
684
685
686
687 protected boolean isSwitchingToNonHttp1Protocol(HttpResponse msg) {
688 if (msg.status().code() != HttpResponseStatus.SWITCHING_PROTOCOLS.code()) {
689 return false;
690 }
691 String newProtocol = msg.headers().get(HttpHeaderNames.UPGRADE);
692 return newProtocol == null ||
693 !newProtocol.contains(HttpVersion.HTTP_1_0.text()) &&
694 !newProtocol.contains(HttpVersion.HTTP_1_1.text());
695 }
696
697
698
699
700
701 public void reset() {
702 resetRequested.lazySet(true);
703 }
704
705 private void resetNow() {
706 message = null;
707 name = null;
708 value = null;
709 clearContentLength();
710 chunked = false;
711 lineParser.reset();
712 headerParser.reset();
713 trailer = null;
714 if (isSwitchingToNonHttp1Protocol) {
715 isSwitchingToNonHttp1Protocol = false;
716 currentState = State.UPGRADED;
717 return;
718 }
719
720 resetRequested.lazySet(false);
721 currentState = State.SKIP_INITIAL_LINE_CHARS;
722 }
723
724 private HttpMessage invalidMessage(HttpMessage current, ByteBuf in, Exception cause) {
725 currentState = State.BAD_MESSAGE;
726 message = null;
727 trailer = null;
728
729
730
731 in.skipBytes(in.readableBytes());
732
733 if (current == null) {
734 current = createInvalidMessage();
735 }
736 current.setDecoderResult(DecoderResult.failure(cause));
737
738 return current;
739 }
740
741 private static void checkChunkExtensions(ByteBuf line) {
742 int extensionsStart = line.bytesBefore((byte) ';');
743 if (extensionsStart == -1) {
744 return;
745 }
746 HttpChunkLineValidatingByteProcessor processor = new HttpChunkLineValidatingByteProcessor();
747 line.forEachByte(processor);
748 processor.finish();
749 }
750
751 private HttpContent invalidChunk(ByteBuf in, Exception cause) {
752 currentState = State.BAD_MESSAGE;
753 message = null;
754 trailer = null;
755
756
757
758 in.skipBytes(in.readableBytes());
759
760 HttpContent chunk = new DefaultLastHttpContent(Unpooled.EMPTY_BUFFER);
761 chunk.setDecoderResult(DecoderResult.failure(cause));
762 return chunk;
763 }
764
765 private State readHeaders(ByteBuf buffer) {
766 final HttpMessage message = this.message;
767 final HttpHeaders headers = message.headers();
768
769 final HeaderParser headerParser = this.headerParser;
770
771 ByteBuf line = headerParser.parse(buffer, defaultStrictCRLFCheck);
772 if (line == null) {
773 return null;
774 }
775 int lineLength = line.readableBytes();
776 while (lineLength > 0) {
777 final byte[] lineContent = line.array();
778 final int startLine = line.arrayOffset() + line.readerIndex();
779 final byte firstChar = lineContent[startLine];
780 if (name != null && (firstChar == ' ' || firstChar == '\t')) {
781
782
783 String trimmedLine = langAsciiString(lineContent, startLine, lineLength).trim();
784 String valueStr = value;
785 value = valueStr + ' ' + trimmedLine;
786 } else {
787 if (name != null) {
788 headers.add(name, value);
789 }
790 splitHeader(lineContent, startLine, lineLength);
791 }
792
793 line = headerParser.parse(buffer, defaultStrictCRLFCheck);
794 if (line == null) {
795 return null;
796 }
797 lineLength = line.readableBytes();
798 }
799
800
801 if (name != null) {
802 headers.add(name, value);
803 }
804
805
806 name = null;
807 value = null;
808
809
810 HttpMessageDecoderResult decoderResult = new HttpMessageDecoderResult(lineParser.size, headerParser.size);
811 message.setDecoderResult(decoderResult);
812
813 List<String> contentLengthFields = headers.getAll(HttpHeaderNames.CONTENT_LENGTH);
814 if (!contentLengthFields.isEmpty()) {
815 HttpVersion version = message.protocolVersion();
816 boolean isHttp10OrEarlier = version.majorVersion() < 1 || (version.majorVersion() == 1
817 && version.minorVersion() == 0);
818
819
820 contentLength = HttpUtil.normalizeAndGetContentLength(contentLengthFields,
821 isHttp10OrEarlier, allowDuplicateContentLengths);
822 if (contentLength != -1) {
823 String lengthValue = contentLengthFields.get(0).trim();
824 if (contentLengthFields.size() > 1 ||
825 !lengthValue.equals(Long.toString(contentLength))) {
826 headers.set(HttpHeaderNames.CONTENT_LENGTH, contentLength);
827 }
828 }
829 } else {
830
831
832 contentLength = HttpUtil.getWebSocketContentLength(message);
833 }
834 if (!isDecodingRequest() && message instanceof HttpResponse) {
835 HttpResponse res = (HttpResponse) message;
836 this.isSwitchingToNonHttp1Protocol = isSwitchingToNonHttp1Protocol(res);
837 }
838 if (isContentAlwaysEmpty(message)) {
839 HttpUtil.setTransferEncodingChunked(message, false);
840 return State.SKIP_CONTROL_CHARS;
841 }
842 boolean hasTransferEncoding = message.headers().contains(HttpHeaderNames.TRANSFER_ENCODING);
843 if (hasTransferEncoding &&
844 message.protocolVersion() != HttpVersion.HTTP_1_1 &&
845 useRfc9112TransferEncoding) {
846
847
848 throw TRANSFER_ENCODING_NOT_ALLOWED;
849 }
850 boolean isTransferEncodingChunked = HttpUtil.isTransferEncodingChunked(message);
851
852
853 if (hasTransferEncoding && !isTransferEncodingChunked && isDecodingRequest()) {
854 throw new IllegalArgumentException("The final transfer coding must be chunked for HTTP requests");
855 }
856 if (isTransferEncodingChunked) {
857 this.chunked = true;
858
859
860
861
862
863
864
865 if (!isLastTransferEncodingChunked(headers.getAll(HttpHeaderNames.TRANSFER_ENCODING))) {
866 throw new IllegalArgumentException(
867 "chunked must be the last encoding present in the Transfer-Encoding header");
868 }
869 if (message.protocolVersion() == HttpVersion.HTTP_1_1) {
870 if (!contentLengthFields.isEmpty()) {
871 handleTransferEncodingChunkedWithContentLength(message);
872 }
873 }
874 return State.READ_CHUNK_SIZE;
875 }
876 if (contentLength >= 0) {
877 return State.READ_FIXED_LENGTH_CONTENT;
878 }
879 return State.READ_VARIABLE_LENGTH_CONTENT;
880 }
881
882 private static boolean isLastTransferEncodingChunked(List<String> transferEncodingFields) {
883 boolean chunkedSeen = false;
884 boolean lastChunked = false;
885 int chunkedLength = HttpHeaderValues.CHUNKED.length();
886 for (int i = 0; i < transferEncodingFields.size(); ++i) {
887 String value = transferEncodingFields.get(i);
888 int start = 0;
889 while (start <= value.length()) {
890 int comma = value.indexOf(',', start);
891 int end = comma == -1 ? value.length() : comma;
892 while (start < end && (value.charAt(start) == ' ' || value.charAt(start) == '\t')) {
893 ++start;
894 }
895 while (end > start && (value.charAt(end - 1) == ' ' || value.charAt(end - 1) == '\t')) {
896 --end;
897 }
898 if (start < end) {
899 lastChunked = end - start == chunkedLength &&
900 HttpHeaderValues.CHUNKED.regionMatches(true, 0, value, start, chunkedLength);
901 if (lastChunked) {
902 if (chunkedSeen) {
903 throw new IllegalArgumentException(
904 "chunked transfer coding must not be applied more than once");
905 }
906 chunkedSeen = true;
907 }
908 }
909 if (comma == -1) {
910 break;
911 }
912 start = comma + 1;
913 }
914 }
915 return lastChunked;
916 }
917
918 private static boolean isLengthEqual(String lengthValue, long contentLength) {
919 try {
920 return Long.parseLong(lengthValue) == contentLength;
921 } catch (NumberFormatException e) {
922 return false;
923 }
924 }
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973 @SuppressWarnings("unused")
974 protected void handleTransferEncodingChunkedWithContentLength(HttpMessage message) {
975 clearContentLength();
976 if (useRfc9112TransferEncoding) {
977 throw new ContentLengthNotAllowedException(
978 "Content-Length are not allowed in HTTP/1.1 messages that contains a Transfer-Encoding header.");
979 } else {
980 message.headers().remove(HttpHeaderNames.CONTENT_LENGTH);
981 if (isDecodingRequest()) {
982 HttpUtil.setKeepAlive(message, false);
983 }
984 }
985 }
986
987 protected final void clearContentLength() {
988 contentLength = Long.MIN_VALUE;
989 }
990
991 private LastHttpContent readTrailingHeaders(ByteBuf buffer) {
992 final HeaderParser headerParser = this.headerParser;
993 ByteBuf line = headerParser.parse(buffer, defaultStrictCRLFCheck);
994 if (line == null) {
995 return null;
996 }
997 LastHttpContent trailer = this.trailer;
998 int lineLength = line.readableBytes();
999 if (lineLength == 0 && trailer == null) {
1000
1001
1002 return LastHttpContent.EMPTY_LAST_CONTENT;
1003 }
1004
1005 if (trailer == null) {
1006 trailer = this.trailer = new DefaultLastHttpContent(Unpooled.EMPTY_BUFFER, trailersFactory);
1007 }
1008 while (lineLength > 0) {
1009 final byte[] lineContent = line.array();
1010 final int startLine = line.arrayOffset() + line.readerIndex();
1011 final byte firstChar = lineContent[startLine];
1012 if (name != null && (firstChar == ' ' || firstChar == '\t')) {
1013
1014
1015 String trimmedLine = langAsciiString(lineContent, startLine, lineLength).trim();
1016 String valueStr = value;
1017 value = valueStr + ' ' + trimmedLine;
1018 } else {
1019 if (name != null && isPermittedTrailingHeader(name)) {
1020 trailer.trailingHeaders().add(name, value);
1021 }
1022 splitHeader(lineContent, startLine, lineLength);
1023 }
1024
1025 line = headerParser.parse(buffer, defaultStrictCRLFCheck);
1026 if (line == null) {
1027 return null;
1028 }
1029 lineLength = line.readableBytes();
1030 }
1031
1032
1033 if (name != null && isPermittedTrailingHeader(name)) {
1034 trailer.trailingHeaders().add(name, value);
1035 }
1036
1037
1038 name = null;
1039 value = null;
1040
1041 this.trailer = null;
1042 return trailer;
1043 }
1044
1045
1046
1047
1048 private static boolean isPermittedTrailingHeader(final AsciiString name) {
1049 return !HttpHeaderNames.CONTENT_LENGTH.contentEqualsIgnoreCase(name) &&
1050 !HttpHeaderNames.TRANSFER_ENCODING.contentEqualsIgnoreCase(name) &&
1051 !HttpHeaderNames.TRAILER.contentEqualsIgnoreCase(name);
1052 }
1053
1054 protected abstract boolean isDecodingRequest();
1055 protected abstract HttpMessage createMessage(String[] initialLine) throws Exception;
1056 protected abstract HttpMessage createInvalidMessage();
1057
1058 private static int getChunkSize(byte[] hex, int start, int length) {
1059 if (length == 0) {
1060 throw new NumberFormatException("Empty chunk size");
1061 }
1062 long result = 0;
1063 for (int i = 0; i < length; i++) {
1064 final int digit = StringUtil.decodeHexNibble(hex[start + i]);
1065 if (digit == -1) {
1066
1067
1068
1069 byte b = hex[start + i];
1070 int j = 0;
1071 while (b == HttpConstants.SP || b == HttpConstants.HT) {
1072 int index = i + (++j);
1073 if (index >= length) {
1074 throw new NumberFormatException("Invalid chunk size; expected extensions");
1075 }
1076 b = hex[start + index];
1077 }
1078 if (b == ';') {
1079 if (i == 0) {
1080 throw new NumberFormatException("Empty chunk size");
1081 }
1082 return (int) result;
1083 }
1084
1085 throw new NumberFormatException("Invalid character in chunk size");
1086 }
1087 result *= 16;
1088 result += digit;
1089 if (result > Integer.MAX_VALUE) {
1090 throw new NumberFormatException("Chunk size overflow: " + result);
1091 }
1092 }
1093 return (int) result;
1094 }
1095
1096 private String[] splitInitialLine(ByteBuf asciiBuffer) {
1097 final byte[] asciiBytes = asciiBuffer.array();
1098
1099 final int arrayOffset = asciiBuffer.arrayOffset();
1100
1101 final int startContent = arrayOffset + asciiBuffer.readerIndex();
1102
1103 final int end = startContent + asciiBuffer.readableBytes();
1104
1105 byte lastByte = asciiBytes[end - 1];
1106 if (isControlOrWhitespaceAsciiChar(lastByte)) {
1107 if (isDecodingRequest() || !isOWS(lastByte)) {
1108
1109
1110
1111
1112
1113 throw new IllegalArgumentException(
1114 "Illegal character in request line: 0x" + Integer.toHexString(lastByte));
1115 }
1116 }
1117
1118 final int aStart = findNonSPLenient(asciiBytes, startContent, end);
1119 final int aEnd = findSPLenient(asciiBytes, aStart, end);
1120
1121 final int bStart = findNonSPLenient(asciiBytes, aEnd, end);
1122 final int bEnd = findSPLenient(asciiBytes, bStart, end);
1123
1124 final int cStart = findNonSPLenient(asciiBytes, bEnd, end);
1125 final int cEnd = findEndOfString(asciiBytes, Math.max(cStart - 1, startContent), end);
1126
1127 return new String[]{
1128 splitFirstWordInitialLine(asciiBytes, aStart, aEnd - aStart),
1129 splitSecondWordInitialLine(asciiBytes, bStart, bEnd - bStart),
1130 cStart < cEnd ? splitThirdWordInitialLine(asciiBytes, cStart, cEnd - cStart) : StringUtil.EMPTY_STRING};
1131 }
1132
1133 protected String splitFirstWordInitialLine(final byte[] asciiContent, int start, int length) {
1134 return langAsciiString(asciiContent, start, length);
1135 }
1136
1137 protected String splitSecondWordInitialLine(final byte[] asciiContent, int start, int length) {
1138 return langAsciiString(asciiContent, start, length);
1139 }
1140
1141 protected String splitThirdWordInitialLine(final byte[] asciiContent, int start, int length) {
1142 return langAsciiString(asciiContent, start, length);
1143 }
1144
1145
1146
1147
1148 private static String langAsciiString(final byte[] asciiContent, int start, int length) {
1149 if (length == 0) {
1150 return StringUtil.EMPTY_STRING;
1151 }
1152
1153 if (start == 0) {
1154 if (length == asciiContent.length) {
1155 return new String(asciiContent, 0, 0, asciiContent.length);
1156 }
1157 return new String(asciiContent, 0, 0, length);
1158 }
1159 return new String(asciiContent, 0, start, length);
1160 }
1161
1162 private void splitHeader(byte[] line, int start, int length) {
1163 final int end = start + length;
1164 int nameEnd;
1165 final int nameStart = start;
1166
1167 final boolean isDecodingRequest = isDecodingRequest();
1168 for (nameEnd = nameStart; nameEnd < end; nameEnd ++) {
1169 byte ch = line[nameEnd];
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179 if (ch == ':' ||
1180
1181
1182
1183
1184 (!isDecodingRequest && isOWS(ch))) {
1185 break;
1186 }
1187 }
1188
1189 if (nameEnd == end) {
1190
1191 throw new IllegalArgumentException("No colon found");
1192 }
1193 int colonEnd;
1194 for (colonEnd = nameEnd; colonEnd < end; colonEnd ++) {
1195 if (line[colonEnd] == ':') {
1196 colonEnd ++;
1197 break;
1198 }
1199 }
1200 name = splitHeaderName(line, nameStart, nameEnd - nameStart);
1201 final int valueStart = findNonWhitespace(line, colonEnd, end);
1202 if (valueStart == end) {
1203 value = StringUtil.EMPTY_STRING;
1204 } else {
1205 final int valueEnd = findEndOfString(line, start, end);
1206
1207 value = langAsciiString(line, valueStart, valueEnd - valueStart);
1208 }
1209 }
1210
1211 protected AsciiString splitHeaderName(byte[] sb, int start, int length) {
1212 return new AsciiString(sb, start, length, true);
1213 }
1214
1215 private static int findNonSPLenient(byte[] sb, int offset, int end) {
1216 for (int result = offset; result < end; ++result) {
1217 byte c = sb[result];
1218
1219 if (isSPLenient(c)) {
1220 continue;
1221 }
1222 if (isWhitespace(c)) {
1223
1224 throw new IllegalArgumentException("Invalid separator");
1225 }
1226 return result;
1227 }
1228 return end;
1229 }
1230
1231 private static int findSPLenient(byte[] sb, int offset, int end) {
1232 for (int result = offset; result < end; ++result) {
1233 if (isSPLenient(sb[result])) {
1234 return result;
1235 }
1236 }
1237 return end;
1238 }
1239
1240 private static final boolean[] SP_LENIENT_BYTES;
1241 private static final boolean[] LATIN_WHITESPACE;
1242
1243 static {
1244
1245 SP_LENIENT_BYTES = new boolean[256];
1246 SP_LENIENT_BYTES[128 + ' '] = true;
1247 SP_LENIENT_BYTES[128 + 0x09] = true;
1248 SP_LENIENT_BYTES[128 + 0x0B] = true;
1249 SP_LENIENT_BYTES[128 + 0x0C] = true;
1250 SP_LENIENT_BYTES[128 + 0x0D] = true;
1251
1252 LATIN_WHITESPACE = new boolean[256];
1253 for (byte b = Byte.MIN_VALUE; b < Byte.MAX_VALUE; b++) {
1254 LATIN_WHITESPACE[128 + b] = Character.isWhitespace(b);
1255 }
1256 }
1257
1258 private static boolean isSPLenient(byte c) {
1259
1260 return SP_LENIENT_BYTES[c + 128];
1261 }
1262
1263 private static boolean isWhitespace(byte b) {
1264 return LATIN_WHITESPACE[b + 128];
1265 }
1266
1267 private static int findNonWhitespace(byte[] sb, int offset, int end) {
1268 for (int result = offset; result < end; ++result) {
1269 byte c = sb[result];
1270 if (!isWhitespace(c)) {
1271 return result;
1272 } else if (!isOWS(c)) {
1273
1274 throw new IllegalArgumentException("Invalid separator, only a single space or horizontal tab allowed," +
1275 " but received a '" + c + "' (0x" + Integer.toHexString(c) + ")");
1276 }
1277 }
1278 return end;
1279 }
1280
1281 private static int findEndOfString(byte[] sb, int start, int end) {
1282 for (int result = end - 1; result > start; --result) {
1283 if (!isOWS(sb[result])) {
1284 return result + 1;
1285 }
1286 }
1287 return 0;
1288 }
1289
1290 private static boolean isOWS(byte ch) {
1291 return ch == ' ' || ch == 0x09;
1292 }
1293
1294 private static class HeaderParser {
1295 protected final ByteBuf seq;
1296 protected final int maxLength;
1297 int size;
1298
1299 HeaderParser(ByteBuf seq, int maxLength) {
1300 this.seq = seq;
1301 this.maxLength = maxLength;
1302 }
1303
1304 public ByteBuf parse(ByteBuf buffer, Runnable strictCRLFCheck) {
1305 final int readableBytes = buffer.readableBytes();
1306 final int readerIndex = buffer.readerIndex();
1307 final int maxBodySize = maxLength - size;
1308 assert maxBodySize >= 0;
1309
1310
1311 final long maxBodySizeWithCRLF = maxBodySize + 2L;
1312 final int toProcess = (int) Math.min(maxBodySizeWithCRLF, readableBytes);
1313 final int toIndexExclusive = readerIndex + toProcess;
1314 assert toIndexExclusive >= readerIndex;
1315 final int indexOfLf = buffer.indexOf(readerIndex, toIndexExclusive, HttpConstants.LF);
1316 if (indexOfLf == -1) {
1317 if (readableBytes > maxBodySize) {
1318
1319
1320
1321
1322 throw newException(maxLength);
1323 }
1324 return null;
1325 }
1326 final int endOfSeqIncluded;
1327 if (indexOfLf > readerIndex && buffer.getByte(indexOfLf - 1) == HttpConstants.CR) {
1328
1329 endOfSeqIncluded = indexOfLf - 1;
1330 } else {
1331 if (strictCRLFCheck != null) {
1332 strictCRLFCheck.run();
1333 }
1334 endOfSeqIncluded = indexOfLf;
1335 }
1336 final int newSize = endOfSeqIncluded - readerIndex;
1337 if (newSize == 0) {
1338 seq.clear();
1339 buffer.readerIndex(indexOfLf + 1);
1340 return seq;
1341 }
1342 int size = this.size + newSize;
1343 if (size > maxLength) {
1344 throw newException(maxLength);
1345 }
1346 this.size = size;
1347 seq.clear();
1348 seq.writeBytes(buffer, readerIndex, newSize);
1349 buffer.readerIndex(indexOfLf + 1);
1350 return seq;
1351 }
1352
1353 public void reset() {
1354 size = 0;
1355 }
1356
1357 protected TooLongFrameException newException(int maxLength) {
1358 return new TooLongHttpHeaderException("HTTP header is larger than " + maxLength + " bytes.");
1359 }
1360 }
1361
1362 private final class LineParser extends HeaderParser {
1363
1364 LineParser(ByteBuf seq, int maxLength) {
1365 super(seq, maxLength);
1366 }
1367
1368 @Override
1369 public ByteBuf parse(ByteBuf buffer, Runnable strictCRLFCheck) {
1370
1371 reset();
1372 final int readableBytes = buffer.readableBytes();
1373 if (readableBytes == 0) {
1374 return null;
1375 }
1376 if (currentState == State.SKIP_INITIAL_LINE_CHARS &&
1377 skipLineChars(buffer, readableBytes, buffer.readerIndex(), strictCRLFCheck)) {
1378 return null;
1379 }
1380 return super.parse(buffer, strictCRLFCheck);
1381 }
1382
1383 private boolean skipLineChars(ByteBuf buffer, int readableBytes, int readerIndex, Runnable strictCRLFCheck) {
1384 assert currentState == State.SKIP_INITIAL_LINE_CHARS;
1385 final int maxToSkip = Math.min(maxLength, readableBytes);
1386 final int firstNonLineIndex = buffer.forEachByte(readerIndex, maxToSkip,
1387 strictCRLFCheck == null ? SKIP_CONTROL_CHARS_BYTES : ByteProcessor.FIND_NON_CRLF);
1388 if (firstNonLineIndex == -1) {
1389 buffer.skipBytes(maxToSkip);
1390 if (readableBytes > maxLength) {
1391 throw newException(maxLength);
1392 }
1393 return true;
1394 }
1395 if (strictCRLFCheck != null) {
1396 final int b = buffer.getByte(firstNonLineIndex) & 0xFF;
1397 if (Character.isISOControl(b)) {
1398 strictCRLFCheck.run();
1399 }
1400 }
1401
1402 buffer.readerIndex(firstNonLineIndex);
1403 currentState = State.READ_INITIAL;
1404 return false;
1405 }
1406
1407 @Override
1408 protected TooLongFrameException newException(int maxLength) {
1409 return new TooLongHttpLineException("An HTTP line is larger than " + maxLength + " bytes.");
1410 }
1411 }
1412
1413 private static final boolean[] ISO_CONTROL_OR_WHITESPACE;
1414
1415 static {
1416 ISO_CONTROL_OR_WHITESPACE = new boolean[256];
1417 for (byte b = Byte.MIN_VALUE; b < Byte.MAX_VALUE; b++) {
1418 ISO_CONTROL_OR_WHITESPACE[128 + b] = Character.isISOControl(b) || isWhitespace(b);
1419 }
1420 }
1421
1422 private static final ByteProcessor SKIP_CONTROL_CHARS_BYTES = new ByteProcessor() {
1423 @Override
1424 public boolean process(byte value) {
1425 return ISO_CONTROL_OR_WHITESPACE[128 + value];
1426 }
1427 };
1428
1429 private static boolean isControlOrWhitespaceAsciiChar(byte b) {
1430 return ISO_CONTROL_OR_WHITESPACE[128 + b];
1431 }
1432 }