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