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