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