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