1
16 package io.netty.handler.codec.http;
17
18 import static io.netty.util.internal.ObjectUtil.checkPositive;
19 import static io.netty.util.internal.StringUtil.COMMA;
20
21 import io.netty.buffer.ByteBuf;
22 import io.netty.buffer.Unpooled;
23 import io.netty.channel.ChannelHandlerContext;
24 import io.netty.channel.ChannelPipeline;
25 import io.netty.handler.codec.ByteToMessageDecoder;
26 import io.netty.handler.codec.DecoderResult;
27 import io.netty.handler.codec.PrematureChannelClosureException;
28 import io.netty.handler.codec.TooLongFrameException;
29 import io.netty.util.ByteProcessor;
30 import io.netty.util.internal.AppendableCharSequence;
31
32 import java.util.List;
33 import java.util.regex.Pattern;
34
35
124 public abstract class HttpObjectDecoder extends ByteToMessageDecoder {
125 public static final int DEFAULT_MAX_INITIAL_LINE_LENGTH = 4096;
126 public static final int DEFAULT_MAX_HEADER_SIZE = 8192;
127 public static final boolean DEFAULT_CHUNKED_SUPPORTED = true;
128 public static final int DEFAULT_MAX_CHUNK_SIZE = 8192;
129 public static final boolean DEFAULT_VALIDATE_HEADERS = true;
130 public static final int DEFAULT_INITIAL_BUFFER_SIZE = 128;
131 public static final boolean DEFAULT_ALLOW_DUPLICATE_CONTENT_LENGTHS = false;
132
133 private static final String EMPTY_VALUE = "";
134 private static final Pattern COMMA_PATTERN = Pattern.compile(",");
135
136 private final int maxChunkSize;
137 private final boolean chunkedSupported;
138 protected final boolean validateHeaders;
139 private final boolean allowDuplicateContentLengths;
140 private final HeaderParser headerParser;
141 private final LineParser lineParser;
142
143 private HttpMessage message;
144 private long chunkSize;
145 private long contentLength = Long.MIN_VALUE;
146 private volatile boolean resetRequested;
147
148
149 private CharSequence name;
150 private CharSequence value;
151
152 private LastHttpContent trailer;
153
154
158 private enum State {
159 SKIP_CONTROL_CHARS,
160 READ_INITIAL,
161 READ_HEADER,
162 READ_VARIABLE_LENGTH_CONTENT,
163 READ_FIXED_LENGTH_CONTENT,
164 READ_CHUNK_SIZE,
165 READ_CHUNKED_CONTENT,
166 READ_CHUNK_DELIMITER,
167 READ_CHUNK_FOOTER,
168 BAD_MESSAGE,
169 UPGRADED
170 }
171
172 private State currentState = State.SKIP_CONTROL_CHARS;
173
174
179 protected HttpObjectDecoder() {
180 this(DEFAULT_MAX_INITIAL_LINE_LENGTH, DEFAULT_MAX_HEADER_SIZE, DEFAULT_MAX_CHUNK_SIZE,
181 DEFAULT_CHUNKED_SUPPORTED);
182 }
183
184
187 protected HttpObjectDecoder(
188 int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, boolean chunkedSupported) {
189 this(maxInitialLineLength, maxHeaderSize, maxChunkSize, chunkedSupported, DEFAULT_VALIDATE_HEADERS);
190 }
191
192
195 protected HttpObjectDecoder(
196 int maxInitialLineLength, int maxHeaderSize, int maxChunkSize,
197 boolean chunkedSupported, boolean validateHeaders) {
198 this(maxInitialLineLength, maxHeaderSize, maxChunkSize, chunkedSupported, validateHeaders,
199 DEFAULT_INITIAL_BUFFER_SIZE);
200 }
201
202
205 protected HttpObjectDecoder(
206 int maxInitialLineLength, int maxHeaderSize, int maxChunkSize,
207 boolean chunkedSupported, boolean validateHeaders, int initialBufferSize) {
208 this(maxInitialLineLength, maxHeaderSize, maxChunkSize, chunkedSupported, validateHeaders, initialBufferSize,
209 DEFAULT_ALLOW_DUPLICATE_CONTENT_LENGTHS);
210 }
211
212 protected HttpObjectDecoder(
213 int maxInitialLineLength, int maxHeaderSize, int maxChunkSize,
214 boolean chunkedSupported, boolean validateHeaders, int initialBufferSize,
215 boolean allowDuplicateContentLengths) {
216 checkPositive(maxInitialLineLength, "maxInitialLineLength");
217 checkPositive(maxHeaderSize, "maxHeaderSize");
218 checkPositive(maxChunkSize, "maxChunkSize");
219
220 AppendableCharSequence seq = new AppendableCharSequence(initialBufferSize);
221 lineParser = new LineParser(seq, maxInitialLineLength);
222 headerParser = new HeaderParser(seq, maxHeaderSize);
223 this.maxChunkSize = maxChunkSize;
224 this.chunkedSupported = chunkedSupported;
225 this.validateHeaders = validateHeaders;
226 this.allowDuplicateContentLengths = allowDuplicateContentLengths;
227 }
228
229 @Override
230 protected void decode(ChannelHandlerContext ctx, ByteBuf buffer, List<Object> out) throws Exception {
231 if (resetRequested) {
232 resetNow();
233 }
234
235 switch (currentState) {
236 case SKIP_CONTROL_CHARS:
237
238 case READ_INITIAL: try {
239 AppendableCharSequence line = lineParser.parse(buffer);
240 if (line == null) {
241 return;
242 }
243 String[] initialLine = splitInitialLine(line);
244 if (initialLine.length < 3) {
245
246 currentState = State.SKIP_CONTROL_CHARS;
247 return;
248 }
249
250 message = createMessage(initialLine);
251 currentState = State.READ_HEADER;
252
253 } catch (Exception e) {
254 out.add(invalidMessage(buffer, e));
255 return;
256 }
257 case READ_HEADER: try {
258 State nextState = readHeaders(buffer);
259 if (nextState == null) {
260 return;
261 }
262 currentState = nextState;
263 switch (nextState) {
264 case SKIP_CONTROL_CHARS:
265
266
267 out.add(message);
268 out.add(LastHttpContent.EMPTY_LAST_CONTENT);
269 resetNow();
270 return;
271 case READ_CHUNK_SIZE:
272 if (!chunkedSupported) {
273 throw new IllegalArgumentException("Chunked messages not supported");
274 }
275
276 out.add(message);
277 return;
278 default:
279
285 long contentLength = contentLength();
286 if (contentLength == 0 || contentLength == -1 && isDecodingRequest()) {
287 out.add(message);
288 out.add(LastHttpContent.EMPTY_LAST_CONTENT);
289 resetNow();
290 return;
291 }
292
293 assert nextState == State.READ_FIXED_LENGTH_CONTENT ||
294 nextState == State.READ_VARIABLE_LENGTH_CONTENT;
295
296 out.add(message);
297
298 if (nextState == State.READ_FIXED_LENGTH_CONTENT) {
299
300 chunkSize = contentLength;
301 }
302
303
304 return;
305 }
306 } catch (Exception e) {
307 out.add(invalidMessage(buffer, e));
308 return;
309 }
310 case READ_VARIABLE_LENGTH_CONTENT: {
311
312 int toRead = Math.min(buffer.readableBytes(), maxChunkSize);
313 if (toRead > 0) {
314 ByteBuf content = buffer.readRetainedSlice(toRead);
315 out.add(new DefaultHttpContent(content));
316 }
317 return;
318 }
319 case READ_FIXED_LENGTH_CONTENT: {
320 int readLimit = buffer.readableBytes();
321
322
323
324
325
326
327
328 if (readLimit == 0) {
329 return;
330 }
331
332 int toRead = Math.min(readLimit, maxChunkSize);
333 if (toRead > chunkSize) {
334 toRead = (int) chunkSize;
335 }
336 ByteBuf content = buffer.readRetainedSlice(toRead);
337 chunkSize -= toRead;
338
339 if (chunkSize == 0) {
340
341 out.add(new DefaultLastHttpContent(content, validateHeaders));
342 resetNow();
343 } else {
344 out.add(new DefaultHttpContent(content));
345 }
346 return;
347 }
348
352 case READ_CHUNK_SIZE: try {
353 AppendableCharSequence line = lineParser.parse(buffer);
354 if (line == null) {
355 return;
356 }
357 int chunkSize = getChunkSize(line.toString());
358 this.chunkSize = chunkSize;
359 if (chunkSize == 0) {
360 currentState = State.READ_CHUNK_FOOTER;
361 return;
362 }
363 currentState = State.READ_CHUNKED_CONTENT;
364
365 } catch (Exception e) {
366 out.add(invalidChunk(buffer, e));
367 return;
368 }
369 case READ_CHUNKED_CONTENT: {
370 assert chunkSize <= Integer.MAX_VALUE;
371 int toRead = Math.min((int) chunkSize, maxChunkSize);
372 toRead = Math.min(toRead, buffer.readableBytes());
373 if (toRead == 0) {
374 return;
375 }
376 HttpContent chunk = new DefaultHttpContent(buffer.readRetainedSlice(toRead));
377 chunkSize -= toRead;
378
379 out.add(chunk);
380
381 if (chunkSize != 0) {
382 return;
383 }
384 currentState = State.READ_CHUNK_DELIMITER;
385
386 }
387 case READ_CHUNK_DELIMITER: {
388 final int wIdx = buffer.writerIndex();
389 int rIdx = buffer.readerIndex();
390 while (wIdx > rIdx) {
391 byte next = buffer.getByte(rIdx++);
392 if (next == HttpConstants.LF) {
393 currentState = State.READ_CHUNK_SIZE;
394 break;
395 }
396 }
397 buffer.readerIndex(rIdx);
398 return;
399 }
400 case READ_CHUNK_FOOTER: try {
401 LastHttpContent trailer = readTrailingHeaders(buffer);
402 if (trailer == null) {
403 return;
404 }
405 out.add(trailer);
406 resetNow();
407 return;
408 } catch (Exception e) {
409 out.add(invalidChunk(buffer, e));
410 return;
411 }
412 case BAD_MESSAGE: {
413
414 buffer.skipBytes(buffer.readableBytes());
415 break;
416 }
417 case UPGRADED: {
418 int readableBytes = buffer.readableBytes();
419 if (readableBytes > 0) {
420
421
422
423
424 out.add(buffer.readBytes(readableBytes));
425 }
426 break;
427 }
428 }
429 }
430
431 @Override
432 protected void decodeLast(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
433 super.decodeLast(ctx, in, out);
434
435 if (resetRequested) {
436
437
438 resetNow();
439 }
440
441 if (message != null) {
442 boolean chunked = HttpUtil.isTransferEncodingChunked(message);
443 if (currentState == State.READ_VARIABLE_LENGTH_CONTENT && !in.isReadable() && !chunked) {
444
445 out.add(LastHttpContent.EMPTY_LAST_CONTENT);
446 resetNow();
447 return;
448 }
449
450 if (currentState == State.READ_HEADER) {
451
452
453 out.add(invalidMessage(Unpooled.EMPTY_BUFFER,
454 new PrematureChannelClosureException("Connection closed before received headers")));
455 resetNow();
456 return;
457 }
458
459
460 boolean prematureClosure;
461 if (isDecodingRequest() || chunked) {
462
463 prematureClosure = true;
464 } else {
465
466
467
468 prematureClosure = contentLength() > 0;
469 }
470
471 if (!prematureClosure) {
472 out.add(LastHttpContent.EMPTY_LAST_CONTENT);
473 }
474 resetNow();
475 }
476 }
477
478 @Override
479 public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
480 if (evt instanceof HttpExpectationFailedEvent) {
481 switch (currentState) {
482 case READ_FIXED_LENGTH_CONTENT:
483 case READ_VARIABLE_LENGTH_CONTENT:
484 case READ_CHUNK_SIZE:
485 reset();
486 break;
487 default:
488 break;
489 }
490 }
491 super.userEventTriggered(ctx, evt);
492 }
493
494 protected boolean isContentAlwaysEmpty(HttpMessage msg) {
495 if (msg instanceof HttpResponse) {
496 HttpResponse res = (HttpResponse) msg;
497 int code = res.status().code();
498
499
500
501
502
503
504 if (code >= 100 && code < 200) {
505
506 return !(code == 101 && !res.headers().contains(HttpHeaderNames.SEC_WEBSOCKET_ACCEPT)
507 && res.headers().contains(HttpHeaderNames.UPGRADE, HttpHeaderValues.WEBSOCKET, true));
508 }
509
510 switch (code) {
511 case 204: case 304:
512 return true;
513 }
514 }
515 return false;
516 }
517
518
522 protected boolean isSwitchingToNonHttp1Protocol(HttpResponse msg) {
523 if (msg.status().code() != HttpResponseStatus.SWITCHING_PROTOCOLS.code()) {
524 return false;
525 }
526 String newProtocol = msg.headers().get(HttpHeaderNames.UPGRADE);
527 return newProtocol == null ||
528 !newProtocol.contains(HttpVersion.HTTP_1_0.text()) &&
529 !newProtocol.contains(HttpVersion.HTTP_1_1.text());
530 }
531
532
536 public void reset() {
537 resetRequested = true;
538 }
539
540 private void resetNow() {
541 HttpMessage message = this.message;
542 this.message = null;
543 name = null;
544 value = null;
545 contentLength = Long.MIN_VALUE;
546 lineParser.reset();
547 headerParser.reset();
548 trailer = null;
549 if (!isDecodingRequest()) {
550 HttpResponse res = (HttpResponse) message;
551 if (res != null && isSwitchingToNonHttp1Protocol(res)) {
552 currentState = State.UPGRADED;
553 return;
554 }
555 }
556
557 resetRequested = false;
558 currentState = State.SKIP_CONTROL_CHARS;
559 }
560
561 private HttpMessage invalidMessage(ByteBuf in, Exception cause) {
562 currentState = State.BAD_MESSAGE;
563
564
565
566 in.skipBytes(in.readableBytes());
567
568 if (message == null) {
569 message = createInvalidMessage();
570 }
571 message.setDecoderResult(DecoderResult.failure(cause));
572
573 HttpMessage ret = message;
574 message = null;
575 return ret;
576 }
577
578 private HttpContent invalidChunk(ByteBuf in, Exception cause) {
579 currentState = State.BAD_MESSAGE;
580
581
582
583 in.skipBytes(in.readableBytes());
584
585 HttpContent chunk = new DefaultLastHttpContent(Unpooled.EMPTY_BUFFER);
586 chunk.setDecoderResult(DecoderResult.failure(cause));
587 message = null;
588 trailer = null;
589 return chunk;
590 }
591
592 private State readHeaders(ByteBuf buffer) {
593 final HttpMessage message = this.message;
594 final HttpHeaders headers = message.headers();
595
596 AppendableCharSequence line = headerParser.parse(buffer);
597 if (line == null) {
598 return null;
599 }
600 if (line.length() > 0) {
601 do {
602 char firstChar = line.charAtUnsafe(0);
603 if (name != null && (firstChar == ' ' || firstChar == '\t')) {
604
605
606 String trimmedLine = line.toString().trim();
607 String valueStr = String.valueOf(value);
608 value = valueStr + ' ' + trimmedLine;
609 } else {
610 if (name != null) {
611 headers.add(name, value);
612 }
613 splitHeader(line);
614 }
615
616 line = headerParser.parse(buffer);
617 if (line == null) {
618 return null;
619 }
620 } while (line.length() > 0);
621 }
622
623
624 if (name != null) {
625 headers.add(name, value);
626 }
627
628
629 name = null;
630 value = null;
631
632 List<String> contentLengthFields = headers.getAll(HttpHeaderNames.CONTENT_LENGTH);
633
634 if (!contentLengthFields.isEmpty()) {
635
636
637
638
639
640
641
642
643
644
645
646
647
648 boolean multipleContentLengths =
649 contentLengthFields.size() > 1 || contentLengthFields.get(0).indexOf(COMMA) >= 0;
650 if (multipleContentLengths && message.protocolVersion() == HttpVersion.HTTP_1_1) {
651 if (allowDuplicateContentLengths) {
652
653 String firstValue = null;
654 for (String field : contentLengthFields) {
655 String[] tokens = COMMA_PATTERN.split(field, -1);
656 for (String token : tokens) {
657 String trimmed = token.trim();
658 if (firstValue == null) {
659 firstValue = trimmed;
660 } else if (!trimmed.equals(firstValue)) {
661 throw new IllegalArgumentException(
662 "Multiple Content-Length values found: " + contentLengthFields);
663 }
664 }
665 }
666
667 headers.set(HttpHeaderNames.CONTENT_LENGTH, firstValue);
668 contentLength = Long.parseLong(firstValue);
669 } else {
670
671 throw new IllegalArgumentException(
672 "Multiple Content-Length values found: " + contentLengthFields);
673 }
674 } else {
675 contentLength = Long.parseLong(contentLengthFields.get(0));
676 }
677 }
678
679 if (isContentAlwaysEmpty(message)) {
680 HttpUtil.setTransferEncodingChunked(message, false);
681 return State.SKIP_CONTROL_CHARS;
682 } else if (HttpUtil.isTransferEncodingChunked(message)) {
683 if (!contentLengthFields.isEmpty() && message.protocolVersion() == HttpVersion.HTTP_1_1) {
684 handleTransferEncodingChunkedWithContentLength(message);
685 }
686 return State.READ_CHUNK_SIZE;
687 } else if (contentLength() >= 0) {
688 return State.READ_FIXED_LENGTH_CONTENT;
689 } else {
690 return State.READ_VARIABLE_LENGTH_CONTENT;
691 }
692 }
693
694
715 protected void handleTransferEncodingChunkedWithContentLength(HttpMessage message) {
716 message.headers().remove(HttpHeaderNames.CONTENT_LENGTH);
717 contentLength = Long.MIN_VALUE;
718 }
719
720 private long contentLength() {
721 if (contentLength == Long.MIN_VALUE) {
722 contentLength = HttpUtil.getContentLength(message, -1L);
723 }
724 return contentLength;
725 }
726
727 private LastHttpContent readTrailingHeaders(ByteBuf buffer) {
728 AppendableCharSequence line = headerParser.parse(buffer);
729 if (line == null) {
730 return null;
731 }
732 LastHttpContent trailer = this.trailer;
733 if (line.length() == 0 && trailer == null) {
734
735
736 return LastHttpContent.EMPTY_LAST_CONTENT;
737 }
738
739 CharSequence lastHeader = null;
740 if (trailer == null) {
741 trailer = this.trailer = new DefaultLastHttpContent(Unpooled.EMPTY_BUFFER, validateHeaders);
742 }
743 while (line.length() > 0) {
744 char firstChar = line.charAtUnsafe(0);
745 if (lastHeader != null && (firstChar == ' ' || firstChar == '\t')) {
746 List<String> current = trailer.trailingHeaders().getAll(lastHeader);
747 if (!current.isEmpty()) {
748 int lastPos = current.size() - 1;
749
750
751 String lineTrimmed = line.toString().trim();
752 String currentLastPos = current.get(lastPos);
753 current.set(lastPos, currentLastPos + lineTrimmed);
754 }
755 } else {
756 splitHeader(line);
757 CharSequence headerName = name;
758 if (!HttpHeaderNames.CONTENT_LENGTH.contentEqualsIgnoreCase(headerName) &&
759 !HttpHeaderNames.TRANSFER_ENCODING.contentEqualsIgnoreCase(headerName) &&
760 !HttpHeaderNames.TRAILER.contentEqualsIgnoreCase(headerName)) {
761 trailer.trailingHeaders().add(headerName, value);
762 }
763 lastHeader = name;
764
765 name = null;
766 value = null;
767 }
768 line = headerParser.parse(buffer);
769 if (line == null) {
770 return null;
771 }
772 }
773
774 this.trailer = null;
775 return trailer;
776 }
777
778 protected abstract boolean isDecodingRequest();
779 protected abstract HttpMessage createMessage(String[] initialLine) throws Exception;
780 protected abstract HttpMessage createInvalidMessage();
781
782 private static int getChunkSize(String hex) {
783 hex = hex.trim();
784 for (int i = 0; i < hex.length(); i ++) {
785 char c = hex.charAt(i);
786 if (c == ';' || Character.isWhitespace(c) || Character.isISOControl(c)) {
787 hex = hex.substring(0, i);
788 break;
789 }
790 }
791
792 return Integer.parseInt(hex, 16);
793 }
794
795 private static String[] splitInitialLine(AppendableCharSequence sb) {
796 int aStart;
797 int aEnd;
798 int bStart;
799 int bEnd;
800 int cStart;
801 int cEnd;
802
803 aStart = findNonSPLenient(sb, 0);
804 aEnd = findSPLenient(sb, aStart);
805
806 bStart = findNonSPLenient(sb, aEnd);
807 bEnd = findSPLenient(sb, bStart);
808
809 cStart = findNonSPLenient(sb, bEnd);
810 cEnd = findEndOfString(sb);
811
812 return new String[] {
813 sb.subStringUnsafe(aStart, aEnd),
814 sb.subStringUnsafe(bStart, bEnd),
815 cStart < cEnd? sb.subStringUnsafe(cStart, cEnd) : "" };
816 }
817
818 private void splitHeader(AppendableCharSequence sb) {
819 final int length = sb.length();
820 int nameStart;
821 int nameEnd;
822 int colonEnd;
823 int valueStart;
824 int valueEnd;
825
826 nameStart = findNonWhitespace(sb, 0, false);
827 for (nameEnd = nameStart; nameEnd < length; nameEnd ++) {
828 char ch = sb.charAtUnsafe(nameEnd);
829
830
831
832
833
834
835
836
837
838 if (ch == ':' ||
839
840
841
842
843 (!isDecodingRequest() && isOWS(ch))) {
844 break;
845 }
846 }
847
848 if (nameEnd == length) {
849
850 throw new IllegalArgumentException("No colon found");
851 }
852
853 for (colonEnd = nameEnd; colonEnd < length; colonEnd ++) {
854 if (sb.charAtUnsafe(colonEnd) == ':') {
855 colonEnd ++;
856 break;
857 }
858 }
859
860 name = sb.subStringUnsafe(nameStart, nameEnd);
861 valueStart = findNonWhitespace(sb, colonEnd, true);
862 if (valueStart == length) {
863 value = EMPTY_VALUE;
864 } else {
865 valueEnd = findEndOfString(sb);
866 value = sb.subStringUnsafe(valueStart, valueEnd);
867 }
868 }
869
870 private static int findNonSPLenient(AppendableCharSequence sb, int offset) {
871 for (int result = offset; result < sb.length(); ++result) {
872 char c = sb.charAtUnsafe(result);
873
874 if (isSPLenient(c)) {
875 continue;
876 }
877 if (Character.isWhitespace(c)) {
878
879 throw new IllegalArgumentException("Invalid separator");
880 }
881 return result;
882 }
883 return sb.length();
884 }
885
886 private static int findSPLenient(AppendableCharSequence sb, int offset) {
887 for (int result = offset; result < sb.length(); ++result) {
888 if (isSPLenient(sb.charAtUnsafe(result))) {
889 return result;
890 }
891 }
892 return sb.length();
893 }
894
895 private static boolean isSPLenient(char c) {
896
897 return c == ' ' || c == (char) 0x09 || c == (char) 0x0B || c == (char) 0x0C || c == (char) 0x0D;
898 }
899
900 private static int findNonWhitespace(AppendableCharSequence sb, int offset, boolean validateOWS) {
901 for (int result = offset; result < sb.length(); ++result) {
902 char c = sb.charAtUnsafe(result);
903 if (!Character.isWhitespace(c)) {
904 return result;
905 } else if (validateOWS && !isOWS(c)) {
906
907 throw new IllegalArgumentException("Invalid separator, only a single space or horizontal tab allowed," +
908 " but received a '" + c + "'");
909 }
910 }
911 return sb.length();
912 }
913
914 private static int findEndOfString(AppendableCharSequence sb) {
915 for (int result = sb.length() - 1; result > 0; --result) {
916 if (!Character.isWhitespace(sb.charAtUnsafe(result))) {
917 return result + 1;
918 }
919 }
920 return 0;
921 }
922
923 private static boolean isOWS(char ch) {
924 return ch == ' ' || ch == (char) 0x09;
925 }
926
927 private static class HeaderParser implements ByteProcessor {
928 private final AppendableCharSequence seq;
929 private final int maxLength;
930 private int size;
931
932 HeaderParser(AppendableCharSequence seq, int maxLength) {
933 this.seq = seq;
934 this.maxLength = maxLength;
935 }
936
937 public AppendableCharSequence parse(ByteBuf buffer) {
938 final int oldSize = size;
939 seq.reset();
940 int i = buffer.forEachByte(this);
941 if (i == -1) {
942 size = oldSize;
943 return null;
944 }
945 buffer.readerIndex(i + 1);
946 return seq;
947 }
948
949 public void reset() {
950 size = 0;
951 }
952
953 @Override
954 public boolean process(byte value) throws Exception {
955 char nextByte = (char) (value & 0xFF);
956 if (nextByte == HttpConstants.LF) {
957 int len = seq.length();
958
959 if (len >= 1 && seq.charAtUnsafe(len - 1) == HttpConstants.CR) {
960 -- size;
961 seq.setLength(len - 1);
962 }
963 return false;
964 }
965
966 increaseCount();
967
968 seq.append(nextByte);
969 return true;
970 }
971
972 protected final void increaseCount() {
973 if (++ size > maxLength) {
974
975
976
977
978 throw newException(maxLength);
979 }
980 }
981
982 protected TooLongFrameException newException(int maxLength) {
983 return new TooLongFrameException("HTTP header is larger than " + maxLength + " bytes.");
984 }
985 }
986
987 private final class LineParser extends HeaderParser {
988
989 LineParser(AppendableCharSequence seq, int maxLength) {
990 super(seq, maxLength);
991 }
992
993 @Override
994 public AppendableCharSequence parse(ByteBuf buffer) {
995 reset();
996 return super.parse(buffer);
997 }
998
999 @Override
1000 public boolean process(byte value) throws Exception {
1001 if (currentState == State.SKIP_CONTROL_CHARS) {
1002 char c = (char) (value & 0xFF);
1003 if (Character.isISOControl(c) || Character.isWhitespace(c)) {
1004 increaseCount();
1005 return true;
1006 }
1007 currentState = State.READ_INITIAL;
1008 }
1009 return super.process(value);
1010 }
1011
1012 @Override
1013 protected TooLongFrameException newException(int maxLength) {
1014 return new TooLongFrameException("An HTTP line is larger than " + maxLength + " bytes.");
1015 }
1016 }
1017 }
1018