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