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