View Javadoc
1   /*
2    * Copyright 2012 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package io.netty.handler.codec.http;
17  
18  import io.netty.buffer.ByteBuf;
19  import io.netty.buffer.Unpooled;
20  import io.netty.channel.ChannelHandlerContext;
21  import io.netty.channel.ChannelPipeline;
22  import io.netty.handler.codec.ByteToMessageDecoder;
23  import io.netty.handler.codec.DecoderResult;
24  import io.netty.handler.codec.PrematureChannelClosureException;
25  import io.netty.handler.codec.TooLongFrameException;
26  import io.netty.util.AsciiString;
27  import io.netty.util.ByteProcessor;
28  import io.netty.util.internal.StringUtil;
29  import io.netty.util.internal.SystemPropertyUtil;
30  
31  import java.util.List;
32  import java.util.concurrent.atomic.AtomicBoolean;
33  
34  import static io.netty.util.internal.ObjectUtil.checkNotNull;
35  
36  /**
37   * Decodes {@link ByteBuf}s into {@link HttpMessage}s and
38   * {@link HttpContent}s.
39   *
40   * <h3>Parameters that prevents excessive memory consumption</h3>
41   * <table border="1">
42   * <tr>
43   * <th>Name</th><th>Default value</th><th>Meaning</th>
44   * </tr>
45   * <tr>
46   * <td>{@code maxInitialLineLength}</td>
47   * <td>{@value #DEFAULT_MAX_INITIAL_LINE_LENGTH}</td>
48   * <td>The maximum length of the initial line
49   *     (e.g. {@code "GET / HTTP/1.0"} or {@code "HTTP/1.0 200 OK"})
50   *     If the length of the initial line exceeds this value, a
51   *     {@link TooLongHttpLineException} will be raised.</td>
52   * </tr>
53   * <tr>
54   * <td>{@code maxHeaderSize}</td>
55   * <td>{@value #DEFAULT_MAX_HEADER_SIZE}</td>
56   * <td>The maximum length of all headers.  If the sum of the length of each
57   *     header exceeds this value, a {@link TooLongHttpHeaderException} will be raised.</td>
58   * </tr>
59   * <tr>
60   * <td>{@code maxChunkSize}</td>
61   * <td>{@value #DEFAULT_MAX_CHUNK_SIZE}</td>
62   * <td>The maximum length of the content or each chunk.  If the content length
63   *     (or the length of each chunk) exceeds this value, the content or chunk
64   *     will be split into multiple {@link HttpContent}s whose length is
65   *     {@code maxChunkSize} at maximum.</td>
66   * </tr>
67   * </table>
68   *
69   * <h3>Parameters that control parsing behavior</h3>
70   * <table border="1">
71   * <tr>
72   * <th>Name</th><th>Default value</th><th>Meaning</th>
73   * </tr>
74   * <tr>
75   * <td>{@code allowDuplicateContentLengths}</td>
76   * <td>{@value #DEFAULT_ALLOW_DUPLICATE_CONTENT_LENGTHS}</td>
77   * <td>When set to {@code false}, will reject any messages that contain multiple Content-Length header fields.
78   *     When set to {@code true}, will allow multiple Content-Length headers only if they are all the same decimal value.
79   *     The duplicated field-values will be replaced with a single valid Content-Length field.
80   *     See <a href="https://tools.ietf.org/html/rfc7230#section-3.3.2">RFC 7230, Section 3.3.2</a>.</td>
81   * </tr>
82   * <tr>
83   * <td>{@code allowPartialChunks}</td>
84   * <td>{@value #DEFAULT_ALLOW_PARTIAL_CHUNKS}</td>
85   * <td>If the length of a chunk exceeds the {@link ByteBuf}s readable bytes and {@code allowPartialChunks}
86   *     is set to {@code true}, the chunk will be split into multiple {@link HttpContent}s.
87   *     Otherwise, if the chunk size does not exceed {@code maxChunkSize} and {@code allowPartialChunks}
88   *     is set to {@code false}, the {@link ByteBuf} is not decoded into an {@link HttpContent} until
89   *     the readable bytes are greater or equal to the chunk size.</td>
90   * </tr>
91   * </table>
92   *
93   * <h3>Chunked Content</h3>
94   *
95   * If the content of an HTTP message is greater than {@code maxChunkSize} or
96   * the transfer encoding of the HTTP message is 'chunked', this decoder
97   * generates one {@link HttpMessage} instance and its following
98   * {@link HttpContent}s per single HTTP message to avoid excessive memory
99   * consumption. For example, the following HTTP message:
100  * <pre>
101  * GET / HTTP/1.1
102  * Transfer-Encoding: chunked
103  *
104  * 1a
105  * abcdefghijklmnopqrstuvwxyz
106  * 10
107  * 1234567890abcdef
108  * 0
109  * Content-MD5: ...
110  * <i>[blank line]</i>
111  * </pre>
112  * triggers {@link HttpRequestDecoder} to generate 3 objects:
113  * <ol>
114  * <li>An {@link HttpRequest},</li>
115  * <li>The first {@link HttpContent} whose content is {@code 'abcdefghijklmnopqrstuvwxyz'},</li>
116  * <li>The second {@link LastHttpContent} whose content is {@code '1234567890abcdef'}, which marks
117  * the end of the content.</li>
118  * </ol>
119  *
120  * If you prefer not to handle {@link HttpContent}s by yourself for your
121  * convenience, insert {@link HttpObjectAggregator} after this decoder in the
122  * {@link ChannelPipeline}.  However, please note that your server might not
123  * be as memory efficient as without the aggregator.
124  *
125  * <h3>Extensibility</h3>
126  *
127  * Please note that this decoder is designed to be extended to implement
128  * a protocol derived from HTTP, such as
129  * <a href="https://en.wikipedia.org/wiki/Real_Time_Streaming_Protocol">RTSP</a> and
130  * <a href="https://en.wikipedia.org/wiki/Internet_Content_Adaptation_Protocol">ICAP</a>.
131  * To implement the decoder of such a derived protocol, extend this class and
132  * implement all abstract methods properly.
133  *
134  * <h3>Header Validation</h3>
135  *
136  * It is recommended to always enable header validation.
137  * <p>
138  * Without header validation, your system can become vulnerable to
139  * <a href="https://cwe.mitre.org/data/definitions/113.html">
140  *     CWE-113: Improper Neutralization of CRLF Sequences in HTTP Headers ('HTTP Response Splitting')
141  * </a>.
142  * <p>
143  * This recommendation stands even when both peers in the HTTP exchange are trusted,
144  * as it helps with defence-in-depth.
145  */
146 public abstract class HttpObjectDecoder extends ByteToMessageDecoder {
147     public static final int DEFAULT_MAX_INITIAL_LINE_LENGTH = 4096;
148     public static final int DEFAULT_MAX_HEADER_SIZE = 8192;
149     public static final boolean DEFAULT_CHUNKED_SUPPORTED = true;
150     public static final boolean DEFAULT_ALLOW_PARTIAL_CHUNKS = true;
151     public static final int DEFAULT_MAX_CHUNK_SIZE = 8192;
152     public static final boolean DEFAULT_VALIDATE_HEADERS = true;
153     public static final int DEFAULT_INITIAL_BUFFER_SIZE = 128;
154     public static final boolean DEFAULT_ALLOW_DUPLICATE_CONTENT_LENGTHS = false;
155     public static final boolean DEFAULT_STRICT_LINE_PARSING =
156             SystemPropertyUtil.getBoolean("io.netty.handler.codec.http.defaultStrictLineParsing", true);
157 
158     private static final Runnable THROW_INVALID_CHUNK_EXTENSION = new Runnable() {
159         @Override
160         public void run() {
161             throw new InvalidChunkExtensionException();
162         }
163     };
164 
165     private static final Runnable THROW_INVALID_LINE_SEPARATOR = new Runnable() {
166         @Override
167         public void run() {
168             throw new InvalidLineSeparatorException();
169         }
170     };
171 
172     private final int maxChunkSize;
173     private final boolean chunkedSupported;
174     private final boolean allowPartialChunks;
175     /**
176      * This field is no longer used. It is only kept around for backwards compatibility purpose.
177      */
178     @Deprecated
179     protected final boolean validateHeaders;
180     protected final HttpHeadersFactory headersFactory;
181     protected final HttpHeadersFactory trailersFactory;
182     private final boolean allowDuplicateContentLengths;
183     private final ByteBuf parserScratchBuffer;
184     private final Runnable defaultStrictCRLFCheck;
185     private final HeaderParser headerParser;
186     private final LineParser lineParser;
187 
188     private HttpMessage message;
189     private long chunkSize;
190     private long contentLength = Long.MIN_VALUE;
191     private boolean chunked;
192     private boolean isSwitchingToNonHttp1Protocol;
193 
194     private final AtomicBoolean resetRequested = new AtomicBoolean();
195 
196     // These will be updated by splitHeader(...)
197     private AsciiString name;
198     private String value;
199     private LastHttpContent trailer;
200 
201     @Override
202     protected void handlerRemoved0(ChannelHandlerContext ctx) throws Exception {
203         try {
204             parserScratchBuffer.release();
205         } finally {
206             super.handlerRemoved0(ctx);
207         }
208     }
209 
210     /**
211      * The internal state of {@link HttpObjectDecoder}.
212      * <em>Internal use only</em>.
213      */
214     private enum State {
215         SKIP_CONTROL_CHARS,
216         READ_INITIAL,
217         READ_HEADER,
218         READ_VARIABLE_LENGTH_CONTENT,
219         READ_FIXED_LENGTH_CONTENT,
220         READ_CHUNK_SIZE,
221         READ_CHUNKED_CONTENT,
222         READ_CHUNK_DELIMITER,
223         READ_CHUNK_FOOTER,
224         BAD_MESSAGE,
225         UPGRADED
226     }
227 
228     private State currentState = State.SKIP_CONTROL_CHARS;
229 
230     /**
231      * Creates a new instance with the default
232      * {@code maxInitialLineLength (4096)}, {@code maxHeaderSize (8192)}, and
233      * {@code maxChunkSize (8192)}.
234      */
235     protected HttpObjectDecoder() {
236         this(new HttpDecoderConfig());
237     }
238 
239     /**
240      * Creates a new instance with the specified parameters.
241      *
242      * @deprecated Use {@link #HttpObjectDecoder(HttpDecoderConfig)} instead.
243      */
244     @Deprecated
245     protected HttpObjectDecoder(
246             int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, boolean chunkedSupported) {
247         this(new HttpDecoderConfig()
248                 .setMaxInitialLineLength(maxInitialLineLength)
249                 .setMaxHeaderSize(maxHeaderSize)
250                 .setMaxChunkSize(maxChunkSize)
251                 .setChunkedSupported(chunkedSupported));
252     }
253 
254     /**
255      * Creates a new instance with the specified parameters.
256      *
257      * @deprecated Use {@link #HttpObjectDecoder(HttpDecoderConfig)} instead.
258      */
259     @Deprecated
260     protected HttpObjectDecoder(
261             int maxInitialLineLength, int maxHeaderSize, int maxChunkSize,
262             boolean chunkedSupported, boolean validateHeaders) {
263         this(new HttpDecoderConfig()
264                 .setMaxInitialLineLength(maxInitialLineLength)
265                 .setMaxHeaderSize(maxHeaderSize)
266                 .setMaxChunkSize(maxChunkSize)
267                 .setChunkedSupported(chunkedSupported)
268                 .setValidateHeaders(validateHeaders));
269     }
270 
271     /**
272      * Creates a new instance with the specified parameters.
273      *
274      * @deprecated Use {@link #HttpObjectDecoder(HttpDecoderConfig)} instead.
275      */
276     @Deprecated
277     protected HttpObjectDecoder(
278             int maxInitialLineLength, int maxHeaderSize, int maxChunkSize,
279             boolean chunkedSupported, boolean validateHeaders, int initialBufferSize) {
280         this(new HttpDecoderConfig()
281                 .setMaxInitialLineLength(maxInitialLineLength)
282                 .setMaxHeaderSize(maxHeaderSize)
283                 .setMaxChunkSize(maxChunkSize)
284                 .setChunkedSupported(chunkedSupported)
285                 .setValidateHeaders(validateHeaders)
286                 .setInitialBufferSize(initialBufferSize));
287     }
288 
289     /**
290      * Creates a new instance with the specified parameters.
291      *
292      * @deprecated Use {@link #HttpObjectDecoder(HttpDecoderConfig)} instead.
293      */
294     @Deprecated
295     protected HttpObjectDecoder(
296             int maxInitialLineLength, int maxHeaderSize, int maxChunkSize,
297             boolean chunkedSupported, boolean validateHeaders, int initialBufferSize,
298             boolean allowDuplicateContentLengths) {
299         this(new HttpDecoderConfig()
300                 .setMaxInitialLineLength(maxInitialLineLength)
301                 .setMaxHeaderSize(maxHeaderSize)
302                 .setMaxChunkSize(maxChunkSize)
303                 .setChunkedSupported(chunkedSupported)
304                 .setValidateHeaders(validateHeaders)
305                 .setInitialBufferSize(initialBufferSize)
306                 .setAllowDuplicateContentLengths(allowDuplicateContentLengths));
307     }
308 
309     /**
310      * Creates a new instance with the specified parameters.
311      *
312      * @deprecated Use {@link #HttpObjectDecoder(HttpDecoderConfig)} instead.
313      */
314     @Deprecated
315     protected HttpObjectDecoder(
316             int maxInitialLineLength, int maxHeaderSize, int maxChunkSize,
317             boolean chunkedSupported, boolean validateHeaders, int initialBufferSize,
318             boolean allowDuplicateContentLengths, boolean allowPartialChunks) {
319         this(new HttpDecoderConfig()
320                 .setMaxInitialLineLength(maxInitialLineLength)
321                 .setMaxHeaderSize(maxHeaderSize)
322                 .setMaxChunkSize(maxChunkSize)
323                 .setChunkedSupported(chunkedSupported)
324                 .setValidateHeaders(validateHeaders)
325                 .setInitialBufferSize(initialBufferSize)
326                 .setAllowDuplicateContentLengths(allowDuplicateContentLengths)
327                 .setAllowPartialChunks(allowPartialChunks));
328     }
329 
330     /**
331      * Creates a new instance with the specified configuration.
332      */
333     protected HttpObjectDecoder(HttpDecoderConfig config) {
334         checkNotNull(config, "config");
335 
336         parserScratchBuffer = Unpooled.buffer(config.getInitialBufferSize());
337         defaultStrictCRLFCheck = config.isStrictLineParsing() ? THROW_INVALID_LINE_SEPARATOR : null;
338         lineParser = new LineParser(parserScratchBuffer, config.getMaxInitialLineLength());
339         headerParser = new HeaderParser(parserScratchBuffer, config.getMaxHeaderSize());
340         maxChunkSize = config.getMaxChunkSize();
341         chunkedSupported = config.isChunkedSupported();
342         headersFactory = config.getHeadersFactory();
343         trailersFactory = config.getTrailersFactory();
344         validateHeaders = isValidating(headersFactory);
345         allowDuplicateContentLengths = config.isAllowDuplicateContentLengths();
346         allowPartialChunks = config.isAllowPartialChunks();
347     }
348 
349     protected boolean isValidating(HttpHeadersFactory headersFactory) {
350         if (headersFactory instanceof DefaultHttpHeadersFactory) {
351             DefaultHttpHeadersFactory builder = (DefaultHttpHeadersFactory) headersFactory;
352             return builder.isValidatingHeaderNames() || builder.isValidatingHeaderValues();
353         }
354         return true; // We can't actually tell in this case, but we assume some validation is taking place.
355     }
356 
357     @Override
358     protected void decode(ChannelHandlerContext ctx, ByteBuf buffer, List<Object> out) throws Exception {
359         if (resetRequested.get()) {
360             resetNow();
361         }
362 
363         switch (currentState) {
364         case SKIP_CONTROL_CHARS:
365             // Fall-through
366         case READ_INITIAL: try {
367             ByteBuf line = lineParser.parse(buffer, defaultStrictCRLFCheck);
368             if (line == null) {
369                 return;
370             }
371             final String[] initialLine = splitInitialLine(line);
372             assert initialLine.length == 3 : "initialLine::length must be 3";
373 
374             message = createMessage(initialLine);
375             currentState = State.READ_HEADER;
376             // fall-through
377         } catch (Exception e) {
378             out.add(invalidMessage(message, buffer, e));
379             return;
380         }
381         case READ_HEADER: try {
382             State nextState = readHeaders(buffer);
383             if (nextState == null) {
384                 return;
385             }
386             currentState = nextState;
387             switch (nextState) {
388             case SKIP_CONTROL_CHARS:
389                 // fast-path
390                 // No content is expected.
391                 addCurrentMessage(out);
392                 out.add(LastHttpContent.EMPTY_LAST_CONTENT);
393                 resetNow();
394                 return;
395             case READ_CHUNK_SIZE:
396                 if (!chunkedSupported) {
397                     throw new IllegalArgumentException("Chunked messages not supported");
398                 }
399                 // Chunked encoding - generate HttpMessage first.  HttpChunks will follow.
400                 addCurrentMessage(out);
401                 return;
402             default:
403                 /*
404                  * RFC 7230, 3.3.3 (https://tools.ietf.org/html/rfc7230#section-3.3.3) states that if a
405                  * request does not have either a transfer-encoding or a content-length header then the message body
406                  * length is 0. However, for a response the body length is the number of octets received prior to the
407                  * server closing the connection. So we treat this as variable length chunked encoding.
408                  */
409                 if (contentLength == 0 || contentLength == -1 && isDecodingRequest()) {
410                     addCurrentMessage(out);
411                     out.add(LastHttpContent.EMPTY_LAST_CONTENT);
412                     resetNow();
413                     return;
414                 }
415 
416                 assert nextState == State.READ_FIXED_LENGTH_CONTENT ||
417                         nextState == State.READ_VARIABLE_LENGTH_CONTENT;
418 
419                 addCurrentMessage(out);
420 
421                 if (nextState == State.READ_FIXED_LENGTH_CONTENT) {
422                     // chunkSize will be decreased as the READ_FIXED_LENGTH_CONTENT state reads data chunk by chunk.
423                     chunkSize = contentLength;
424                 }
425 
426                 // We return here, this forces decode to be called again where we will decode the content
427                 return;
428             }
429         } catch (Exception e) {
430             out.add(invalidMessage(message, buffer, e));
431             return;
432         }
433         case READ_VARIABLE_LENGTH_CONTENT: {
434             // Keep reading data as a chunk until the end of connection is reached.
435             int toRead = Math.min(buffer.readableBytes(), maxChunkSize);
436             if (toRead > 0) {
437                 ByteBuf content = buffer.readRetainedSlice(toRead);
438                 out.add(new DefaultHttpContent(content));
439             }
440             return;
441         }
442         case READ_FIXED_LENGTH_CONTENT: {
443             int readLimit = buffer.readableBytes();
444 
445             // Check if the buffer is readable first as we use the readable byte count
446             // to create the HttpChunk. This is needed as otherwise we may end up with
447             // create an HttpChunk instance that contains an empty buffer and so is
448             // handled like it is the last HttpChunk.
449             //
450             // See https://github.com/netty/netty/issues/433
451             if (readLimit == 0) {
452                 return;
453             }
454 
455             int toRead = Math.min(readLimit, maxChunkSize);
456             if (toRead > chunkSize) {
457                 toRead = (int) chunkSize;
458             }
459             ByteBuf content = buffer.readRetainedSlice(toRead);
460             chunkSize -= toRead;
461 
462             if (chunkSize == 0) {
463                 // Read all content.
464                 out.add(new DefaultLastHttpContent(content, trailersFactory));
465                 resetNow();
466             } else {
467                 out.add(new DefaultHttpContent(content));
468             }
469             return;
470         }
471         /*
472          * Everything else after this point takes care of reading chunked content. Basically, read chunk size,
473          * read chunk, read and ignore the CRLF and repeat until 0
474          */
475         case READ_CHUNK_SIZE: try {
476             ByteBuf line = lineParser.parse(buffer, THROW_INVALID_CHUNK_EXTENSION);
477             if (line == null) {
478                 return;
479             }
480             checkChunkExtensions(line);
481             int chunkSize = getChunkSize(line.array(), line.arrayOffset() + line.readerIndex(), line.readableBytes());
482             this.chunkSize = chunkSize;
483             if (chunkSize == 0) {
484                 currentState = State.READ_CHUNK_FOOTER;
485                 return;
486             }
487             currentState = State.READ_CHUNKED_CONTENT;
488             // fall-through
489         } catch (Exception e) {
490             out.add(invalidChunk(buffer, e));
491             return;
492         }
493         case READ_CHUNKED_CONTENT: {
494             assert chunkSize <= Integer.MAX_VALUE;
495             int toRead = Math.min((int) chunkSize, maxChunkSize);
496             if (!allowPartialChunks && buffer.readableBytes() < toRead) {
497                 return;
498             }
499             toRead = Math.min(toRead, buffer.readableBytes());
500             if (toRead == 0) {
501                 return;
502             }
503             HttpContent chunk = new DefaultHttpContent(buffer.readRetainedSlice(toRead));
504             chunkSize -= toRead;
505 
506             out.add(chunk);
507 
508             if (chunkSize != 0) {
509                 return;
510             }
511             currentState = State.READ_CHUNK_DELIMITER;
512             // fall-through
513         }
514         case READ_CHUNK_DELIMITER: {
515             if (buffer.readableBytes() >= 2) {
516                 int rIdx = buffer.readerIndex();
517                 if (buffer.getByte(rIdx) == HttpConstants.CR &&
518                         buffer.getByte(rIdx + 1) == HttpConstants.LF) {
519                     buffer.skipBytes(2);
520                     currentState = State.READ_CHUNK_SIZE;
521                 } else {
522                     out.add(invalidChunk(buffer, new InvalidChunkTerminationException()));
523                 }
524             }
525             return;
526         }
527         case READ_CHUNK_FOOTER: try {
528             LastHttpContent trailer = readTrailingHeaders(buffer);
529             if (trailer == null) {
530                 return;
531             }
532             out.add(trailer);
533             resetNow();
534             return;
535         } catch (Exception e) {
536             out.add(invalidChunk(buffer, e));
537             return;
538         }
539         case BAD_MESSAGE: {
540             // Keep discarding until disconnection.
541             buffer.skipBytes(buffer.readableBytes());
542             break;
543         }
544         case UPGRADED: {
545             int readableBytes = buffer.readableBytes();
546             if (readableBytes > 0) {
547                 // Keep on consuming as otherwise we may trigger an DecoderException,
548                 // other handler will replace this codec with the upgraded protocol codec to
549                 // take the traffic over at some point then.
550                 // See https://github.com/netty/netty/issues/2173
551                 out.add(buffer.readBytes(readableBytes));
552             }
553             break;
554         }
555         default:
556             break;
557         }
558     }
559 
560     @Override
561     protected void decodeLast(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
562         super.decodeLast(ctx, in, out);
563 
564         if (resetRequested.get()) {
565             // If a reset was requested by decodeLast() we need to do it now otherwise we may produce a
566             // LastHttpContent while there was already one.
567             resetNow();
568         }
569 
570         // Handle the last unfinished message.
571         switch (currentState) {
572             case READ_VARIABLE_LENGTH_CONTENT:
573                 if (!chunked && !in.isReadable()) {
574                     // End of connection.
575                     out.add(LastHttpContent.EMPTY_LAST_CONTENT);
576                     resetNow();
577                 }
578                 return;
579             case READ_HEADER:
580                 // If we are still in the state of reading headers we need to create a new invalid message that
581                 // signals that the connection was closed before we received the headers.
582                 out.add(invalidMessage(message, Unpooled.EMPTY_BUFFER,
583                         new PrematureChannelClosureException("Connection closed before received headers")));
584                 resetNow();
585                 return;
586             case READ_CHUNK_DELIMITER: // fall-trough
587             case READ_CHUNK_FOOTER: // fall-trough
588             case READ_CHUNKED_CONTENT: // fall-trough
589             case READ_CHUNK_SIZE: // fall-trough
590             case READ_FIXED_LENGTH_CONTENT:
591                 // Check if the closure of the connection signifies the end of the content.
592                 boolean prematureClosure;
593                 if (isDecodingRequest() || chunked) {
594                     // The last request did not wait for a response.
595                     prematureClosure = true;
596                 } else {
597                     // Compare the length of the received content and the 'Content-Length' header.
598                     // If the 'Content-Length' header is absent, the length of the content is determined by the end of
599                     // the connection, so it is perfectly fine.
600                     prematureClosure = contentLength > 0;
601                 }
602                 if (!prematureClosure) {
603                     out.add(LastHttpContent.EMPTY_LAST_CONTENT);
604                 }
605                 resetNow();
606                 return;
607             case SKIP_CONTROL_CHARS: // fall-trough
608             case READ_INITIAL:// fall-trough
609             case BAD_MESSAGE: // fall-trough
610             case UPGRADED: // fall-trough
611                 // Do nothing
612                 break;
613             default:
614                 throw new IllegalStateException("Unhandled state " + currentState);
615         }
616     }
617 
618     @Override
619     public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
620         if (evt instanceof HttpExpectationFailedEvent) {
621             switch (currentState) {
622             case READ_FIXED_LENGTH_CONTENT:
623             case READ_VARIABLE_LENGTH_CONTENT:
624             case READ_CHUNK_SIZE:
625                 reset();
626                 break;
627             default:
628                 break;
629             }
630         }
631         super.userEventTriggered(ctx, evt);
632     }
633 
634     private void addCurrentMessage(List<Object> out) {
635         HttpMessage message = this.message;
636         assert message != null;
637         this.message = null;
638         out.add(message);
639     }
640 
641     protected boolean isContentAlwaysEmpty(HttpMessage msg) {
642         if (msg instanceof HttpResponse) {
643             HttpResponse res = (HttpResponse) msg;
644             final HttpResponseStatus status = res.status();
645             final int code = status.code();
646             final HttpStatusClass statusClass = status.codeClass();
647 
648             // Correctly handle return codes of 1xx.
649             //
650             // See:
651             //     - https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html Section 4.4
652             //     - https://github.com/netty/netty/issues/222
653             if (statusClass == HttpStatusClass.INFORMATIONAL) {
654                 // One exception: Hixie 76 websocket handshake response
655                 return !(code == 101 && !res.headers().contains(HttpHeaderNames.SEC_WEBSOCKET_ACCEPT)
656                          && res.headers().contains(HttpHeaderNames.UPGRADE, HttpHeaderValues.WEBSOCKET, true));
657             }
658 
659             switch (code) {
660             case 204: case 304:
661                 return true;
662             default:
663                 return false;
664             }
665         }
666         return false;
667     }
668 
669     /**
670      * Returns true if the server switched to a different protocol than HTTP/1.0 or HTTP/1.1, e.g. HTTP/2 or Websocket.
671      * Returns false if the upgrade happened in a different layer, e.g. upgrade from HTTP/1.1 to HTTP/1.1 over TLS.
672      */
673     protected boolean isSwitchingToNonHttp1Protocol(HttpResponse msg) {
674         if (msg.status().code() != HttpResponseStatus.SWITCHING_PROTOCOLS.code()) {
675             return false;
676         }
677         String newProtocol = msg.headers().get(HttpHeaderNames.UPGRADE);
678         return newProtocol == null ||
679                 !newProtocol.contains(HttpVersion.HTTP_1_0.text()) &&
680                 !newProtocol.contains(HttpVersion.HTTP_1_1.text());
681     }
682 
683     /**
684      * Resets the state of the decoder so that it is ready to decode a new message.
685      * This method is useful for handling a rejected request with {@code Expect: 100-continue} header.
686      */
687     public void reset() {
688         resetRequested.lazySet(true);
689     }
690 
691     private void resetNow() {
692         message = null;
693         name = null;
694         value = null;
695         contentLength = Long.MIN_VALUE;
696         chunked = false;
697         lineParser.reset();
698         headerParser.reset();
699         trailer = null;
700         if (isSwitchingToNonHttp1Protocol) {
701             isSwitchingToNonHttp1Protocol = false;
702             currentState = State.UPGRADED;
703             return;
704         }
705 
706         resetRequested.lazySet(false);
707         currentState = State.SKIP_CONTROL_CHARS;
708     }
709 
710     private HttpMessage invalidMessage(HttpMessage current, ByteBuf in, Exception cause) {
711         currentState = State.BAD_MESSAGE;
712         message = null;
713         trailer = null;
714 
715         // Advance the readerIndex so that ByteToMessageDecoder does not complain
716         // when we produced an invalid message without consuming anything.
717         in.skipBytes(in.readableBytes());
718 
719         if (current == null) {
720             current = createInvalidMessage();
721         }
722         current.setDecoderResult(DecoderResult.failure(cause));
723 
724         return current;
725     }
726 
727     private static void checkChunkExtensions(ByteBuf line) {
728         int extensionsStart = line.bytesBefore((byte) ';');
729         if (extensionsStart == -1) {
730             return;
731         }
732         HttpChunkLineValidatingByteProcessor processor = new HttpChunkLineValidatingByteProcessor();
733         line.forEachByte(processor);
734         processor.finish();
735     }
736 
737     private HttpContent invalidChunk(ByteBuf in, Exception cause) {
738         currentState = State.BAD_MESSAGE;
739         message = null;
740         trailer = null;
741 
742         // Advance the readerIndex so that ByteToMessageDecoder does not complain
743         // when we produced an invalid message without consuming anything.
744         in.skipBytes(in.readableBytes());
745 
746         HttpContent chunk = new DefaultLastHttpContent(Unpooled.EMPTY_BUFFER);
747         chunk.setDecoderResult(DecoderResult.failure(cause));
748         return chunk;
749     }
750 
751     private State readHeaders(ByteBuf buffer) {
752         final HttpMessage message = this.message;
753         final HttpHeaders headers = message.headers();
754 
755         final HeaderParser headerParser = this.headerParser;
756 
757         ByteBuf line = headerParser.parse(buffer, defaultStrictCRLFCheck);
758         if (line == null) {
759             return null;
760         }
761         int lineLength = line.readableBytes();
762         while (lineLength > 0) {
763             final byte[] lineContent = line.array();
764             final int startLine = line.arrayOffset() + line.readerIndex();
765             final byte firstChar = lineContent[startLine];
766             if (name != null && (firstChar == ' ' || firstChar == '\t')) {
767                 //please do not make one line from below code
768                 //as it breaks +XX:OptimizeStringConcat optimization
769                 String trimmedLine = langAsciiString(lineContent, startLine, lineLength).trim();
770                 String valueStr = value;
771                 value = valueStr + ' ' + trimmedLine;
772             } else {
773                 if (name != null) {
774                     headers.add(name, value);
775                 }
776                 splitHeader(lineContent, startLine, lineLength);
777             }
778 
779             line = headerParser.parse(buffer, defaultStrictCRLFCheck);
780             if (line == null) {
781                 return null;
782             }
783             lineLength = line.readableBytes();
784         }
785 
786         // Add the last header.
787         if (name != null) {
788             headers.add(name, value);
789         }
790 
791         // reset name and value fields
792         name = null;
793         value = null;
794 
795         // Done parsing initial line and headers. Set decoder result.
796         HttpMessageDecoderResult decoderResult = new HttpMessageDecoderResult(lineParser.size, headerParser.size);
797         message.setDecoderResult(decoderResult);
798 
799         List<String> contentLengthFields = headers.getAll(HttpHeaderNames.CONTENT_LENGTH);
800         if (!contentLengthFields.isEmpty()) {
801             HttpVersion version = message.protocolVersion();
802             boolean isHttp10OrEarlier = version.majorVersion() < 1 || (version.majorVersion() == 1
803                     && version.minorVersion() == 0);
804             // Guard against multiple Content-Length headers as stated in
805             // https://tools.ietf.org/html/rfc7230#section-3.3.2:
806             contentLength = HttpUtil.normalizeAndGetContentLength(contentLengthFields,
807                     isHttp10OrEarlier, allowDuplicateContentLengths);
808             if (contentLength != -1) {
809                 String lengthValue = contentLengthFields.get(0).trim();
810                 if (contentLengthFields.size() > 1 || // don't unnecessarily re-order headers
811                         !lengthValue.equals(Long.toString(contentLength))) {
812                     headers.set(HttpHeaderNames.CONTENT_LENGTH, contentLength);
813                 }
814             }
815         } else {
816             // We know the content length if it's a Web Socket message even if
817             // Content-Length header is missing.
818             contentLength = HttpUtil.getWebSocketContentLength(message);
819         }
820         if (!isDecodingRequest() && message instanceof HttpResponse) {
821             HttpResponse res = (HttpResponse) message;
822             this.isSwitchingToNonHttp1Protocol = isSwitchingToNonHttp1Protocol(res);
823         }
824         if (isContentAlwaysEmpty(message)) {
825             HttpUtil.setTransferEncodingChunked(message, false);
826             return State.SKIP_CONTROL_CHARS;
827         }
828         if (HttpUtil.isTransferEncodingChunked(message)) {
829             this.chunked = true;
830             if (!contentLengthFields.isEmpty() && message.protocolVersion() == HttpVersion.HTTP_1_1) {
831                 handleTransferEncodingChunkedWithContentLength(message);
832             }
833             return State.READ_CHUNK_SIZE;
834         }
835         if (contentLength >= 0) {
836             return State.READ_FIXED_LENGTH_CONTENT;
837         }
838         return State.READ_VARIABLE_LENGTH_CONTENT;
839     }
840 
841     /**
842      * Invoked when a message with both a "Transfer-Encoding: chunked" and a "Content-Length" header field is detected.
843      * The default behavior is to <i>remove</i> the Content-Length field, but this method could be overridden
844      * to change the behavior (to, e.g., throw an exception and produce an invalid message).
845      * <p>
846      * See: https://tools.ietf.org/html/rfc7230#section-3.3.3
847      * <pre>
848      *     If a message is received with both a Transfer-Encoding and a
849      *     Content-Length header field, the Transfer-Encoding overrides the
850      *     Content-Length.  Such a message might indicate an attempt to
851      *     perform request smuggling (Section 9.5) or response splitting
852      *     (Section 9.4) and ought to be handled as an error.  A sender MUST
853      *     remove the received Content-Length field prior to forwarding such
854      *     a message downstream.
855      * </pre>
856      * Also see:
857      * https://github.com/apache/tomcat/blob/b693d7c1981fa7f51e58bc8c8e72e3fe80b7b773/
858      * java/org/apache/coyote/http11/Http11Processor.java#L747-L755
859      * https://github.com/nginx/nginx/blob/0ad4393e30c119d250415cb769e3d8bc8dce5186/
860      * src/http/ngx_http_request.c#L1946-L1953
861      */
862     protected void handleTransferEncodingChunkedWithContentLength(HttpMessage message) {
863         message.headers().remove(HttpHeaderNames.CONTENT_LENGTH);
864         contentLength = Long.MIN_VALUE;
865     }
866 
867     private LastHttpContent readTrailingHeaders(ByteBuf buffer) {
868         final HeaderParser headerParser = this.headerParser;
869         ByteBuf line = headerParser.parse(buffer, defaultStrictCRLFCheck);
870         if (line == null) {
871             return null;
872         }
873         LastHttpContent trailer = this.trailer;
874         int lineLength = line.readableBytes();
875         if (lineLength == 0 && trailer == null) {
876             // We have received the empty line which signals the trailer is complete and did not parse any trailers
877             // before. Just return an empty last content to reduce allocations.
878             return LastHttpContent.EMPTY_LAST_CONTENT;
879         }
880 
881         if (trailer == null) {
882             trailer = this.trailer = new DefaultLastHttpContent(Unpooled.EMPTY_BUFFER, trailersFactory);
883         }
884         while (lineLength > 0) {
885             final byte[] lineContent = line.array();
886             final int startLine = line.arrayOffset() + line.readerIndex();
887             final byte firstChar = lineContent[startLine];
888             if (name != null && (firstChar == ' ' || firstChar == '\t')) {
889                 //please do not make one line from below code
890                 //as it breaks +XX:OptimizeStringConcat optimization
891                 String trimmedLine = langAsciiString(lineContent, startLine, lineLength).trim();
892                 String valueStr = value;
893                 value = valueStr + ' ' + trimmedLine;
894             } else {
895                 if (name != null && isPermittedTrailingHeader(name)) {
896                     trailer.trailingHeaders().add(name, value);
897                 }
898                 splitHeader(lineContent, startLine, lineLength);
899             }
900 
901             line = headerParser.parse(buffer, defaultStrictCRLFCheck);
902             if (line == null) {
903                 return null;
904             }
905             lineLength = line.readableBytes();
906         }
907 
908         // Add the last trailer
909         if (name != null && isPermittedTrailingHeader(name)) {
910             trailer.trailingHeaders().add(name, value);
911         }
912 
913         // reset name and value fields
914         name = null;
915         value = null;
916 
917         this.trailer = null;
918         return trailer;
919     }
920 
921     /**
922      * Checks whether the given trailer field name is permitted per RFC 9110 section 6.5
923      */
924     private static boolean isPermittedTrailingHeader(final AsciiString name) {
925         return !HttpHeaderNames.CONTENT_LENGTH.contentEqualsIgnoreCase(name) &&
926                !HttpHeaderNames.TRANSFER_ENCODING.contentEqualsIgnoreCase(name) &&
927                !HttpHeaderNames.TRAILER.contentEqualsIgnoreCase(name);
928     }
929 
930     protected abstract boolean isDecodingRequest();
931     protected abstract HttpMessage createMessage(String[] initialLine) throws Exception;
932     protected abstract HttpMessage createInvalidMessage();
933 
934     /**
935      * It skips any whitespace char and return the number of skipped bytes.
936      */
937     private static int skipWhiteSpaces(byte[] hex, int start, int length) {
938         for (int i = 0; i < length; i++) {
939             if (!isWhitespace(hex[start + i])) {
940                 return i;
941             }
942         }
943         return length;
944     }
945 
946     private static int getChunkSize(byte[] hex, int start, int length) {
947         // trim the leading bytes of white spaces, if any
948         final int skipped = skipWhiteSpaces(hex, start, length);
949         if (skipped == length) {
950             // empty case
951             throw new NumberFormatException();
952         }
953         start += skipped;
954         length -= skipped;
955         int result = 0;
956         for (int i = 0; i < length; i++) {
957             final int digit = StringUtil.decodeHexNibble(hex[start + i]);
958             if (digit == -1) {
959                 // uncommon path
960                 final byte b = hex[start + i];
961                 if (b == ';' || isControlOrWhitespaceAsciiChar(b)) {
962                     if (i == 0) {
963                         // empty case
964                         throw new NumberFormatException("Empty chunk size");
965                     }
966                     return result;
967                 }
968                 // non-hex char fail-fast path
969                 throw new NumberFormatException("Invalid character in chunk size");
970             }
971             result *= 16;
972             result += digit;
973             if (result < 0) {
974                 throw new NumberFormatException("Chunk size overflow: " + result);
975             }
976         }
977         return result;
978     }
979 
980     private String[] splitInitialLine(ByteBuf asciiBuffer) {
981         final byte[] asciiBytes = asciiBuffer.array();
982 
983         final int arrayOffset = asciiBuffer.arrayOffset();
984 
985         final int startContent = arrayOffset + asciiBuffer.readerIndex();
986 
987         final int end = startContent + asciiBuffer.readableBytes();
988 
989         byte lastByte = asciiBytes[end - 1];
990         if (isControlOrWhitespaceAsciiChar(lastByte)) {
991             if (isDecodingRequest() || !isOWS(lastByte)) {
992                 // There should no extra control or whitespace char in case of a request.
993                 // In case of a response there might be a SP if there is no reason-phrase given.
994                 // See
995                 //  - https://datatracker.ietf.org/doc/html/rfc2616#section-5.1
996                 //  - https://datatracker.ietf.org/doc/html/rfc9112#name-status-line
997                 throw new IllegalArgumentException(
998                         "Illegal character in request line: 0x" + Integer.toHexString(lastByte));
999             }
1000         }
1001 
1002         final int aStart = findNonSPLenient(asciiBytes, startContent, end);
1003         final int aEnd = findSPLenient(asciiBytes, aStart, end);
1004 
1005         final int bStart = findNonSPLenient(asciiBytes, aEnd, end);
1006         final int bEnd = findSPLenient(asciiBytes, bStart, end);
1007 
1008         final int cStart = findNonSPLenient(asciiBytes, bEnd, end);
1009         final int cEnd = findEndOfString(asciiBytes, Math.max(cStart - 1, startContent), end);
1010 
1011         return new String[]{
1012                 splitFirstWordInitialLine(asciiBytes, aStart, aEnd - aStart),
1013                 splitSecondWordInitialLine(asciiBytes, bStart, bEnd - bStart),
1014                 cStart < cEnd ? splitThirdWordInitialLine(asciiBytes, cStart, cEnd - cStart) : StringUtil.EMPTY_STRING};
1015     }
1016 
1017     protected String splitFirstWordInitialLine(final byte[] asciiContent, int start, int length) {
1018         return langAsciiString(asciiContent, start, length);
1019     }
1020 
1021     protected String splitSecondWordInitialLine(final byte[] asciiContent, int start, int length) {
1022         return langAsciiString(asciiContent, start, length);
1023     }
1024 
1025     protected String splitThirdWordInitialLine(final byte[] asciiContent, int start, int length) {
1026         return langAsciiString(asciiContent, start, length);
1027     }
1028 
1029     /**
1030      * This method shouldn't exist: look at https://bugs.openjdk.org/browse/JDK-8295496 for more context
1031      */
1032     private static String langAsciiString(final byte[] asciiContent, int start, int length) {
1033         if (length == 0) {
1034             return StringUtil.EMPTY_STRING;
1035         }
1036         // DON'T REMOVE: it helps JIT to use a simpler intrinsic stub for System::arrayCopy based on the call-site
1037         if (start == 0) {
1038             if (length == asciiContent.length) {
1039                 return new String(asciiContent, 0, 0, asciiContent.length);
1040             }
1041             return new String(asciiContent, 0, 0, length);
1042         }
1043         return new String(asciiContent, 0, start, length);
1044     }
1045 
1046     private void splitHeader(byte[] line, int start, int length) {
1047         final int end = start + length;
1048         int nameEnd;
1049         final int nameStart = start;
1050         // hoist this load out of the loop, because it won't change!
1051         final boolean isDecodingRequest = isDecodingRequest();
1052         for (nameEnd = nameStart; nameEnd < end; nameEnd ++) {
1053             byte ch = line[nameEnd];
1054             // https://tools.ietf.org/html/rfc7230#section-3.2.4
1055             //
1056             // No whitespace is allowed between the header field-name and colon. In
1057             // the past, differences in the handling of such whitespace have led to
1058             // security vulnerabilities in request routing and response handling. A
1059             // server MUST reject any received request message that contains
1060             // whitespace between a header field-name and colon with a response code
1061             // of 400 (Bad Request). A proxy MUST remove any such whitespace from a
1062             // response message before forwarding the message downstream.
1063             if (ch == ':' ||
1064                     // In case of decoding a request we will just continue processing and header validation
1065                     // is done in the DefaultHttpHeaders implementation.
1066                     //
1067                     // In the case of decoding a response we will "skip" the whitespace.
1068                     (!isDecodingRequest && isOWS(ch))) {
1069                 break;
1070             }
1071         }
1072 
1073         if (nameEnd == end) {
1074             // There was no colon present at all.
1075             throw new IllegalArgumentException("No colon found");
1076         }
1077         int colonEnd;
1078         for (colonEnd = nameEnd; colonEnd < end; colonEnd ++) {
1079             if (line[colonEnd] == ':') {
1080                 colonEnd ++;
1081                 break;
1082             }
1083         }
1084         name = splitHeaderName(line, nameStart, nameEnd - nameStart);
1085         final int valueStart = findNonWhitespace(line, colonEnd, end);
1086         if (valueStart == end) {
1087             value = StringUtil.EMPTY_STRING;
1088         } else {
1089             final int valueEnd = findEndOfString(line, start, end);
1090             // no need to make uses of the ByteBuf's toString ASCII method here, and risk to get JIT confused
1091             value = langAsciiString(line, valueStart, valueEnd - valueStart);
1092         }
1093     }
1094 
1095     protected AsciiString splitHeaderName(byte[] sb, int start, int length) {
1096         return new AsciiString(sb, start, length, true);
1097     }
1098 
1099     private static int findNonSPLenient(byte[] sb, int offset, int end) {
1100         for (int result = offset; result < end; ++result) {
1101             byte c = sb[result];
1102             // See https://tools.ietf.org/html/rfc7230#section-3.5
1103             if (isSPLenient(c)) {
1104                 continue;
1105             }
1106             if (isWhitespace(c)) {
1107                 // Any other whitespace delimiter is invalid
1108                 throw new IllegalArgumentException("Invalid separator");
1109             }
1110             return result;
1111         }
1112         return end;
1113     }
1114 
1115     private static int findSPLenient(byte[] sb, int offset, int end) {
1116         for (int result = offset; result < end; ++result) {
1117             if (isSPLenient(sb[result])) {
1118                 return result;
1119             }
1120         }
1121         return end;
1122     }
1123 
1124     private static final boolean[] SP_LENIENT_BYTES;
1125     private static final boolean[] LATIN_WHITESPACE;
1126 
1127     static {
1128         // See https://tools.ietf.org/html/rfc7230#section-3.5
1129         SP_LENIENT_BYTES = new boolean[256];
1130         SP_LENIENT_BYTES[128 + ' '] = true;
1131         SP_LENIENT_BYTES[128 + 0x09] = true;
1132         SP_LENIENT_BYTES[128 + 0x0B] = true;
1133         SP_LENIENT_BYTES[128 + 0x0C] = true;
1134         SP_LENIENT_BYTES[128 + 0x0D] = true;
1135         // TO SAVE PERFORMING Character::isWhitespace ceremony
1136         LATIN_WHITESPACE = new boolean[256];
1137         for (byte b = Byte.MIN_VALUE; b < Byte.MAX_VALUE; b++) {
1138             LATIN_WHITESPACE[128 + b] = Character.isWhitespace(b);
1139         }
1140     }
1141 
1142     private static boolean isSPLenient(byte c) {
1143         // See https://tools.ietf.org/html/rfc7230#section-3.5
1144         return SP_LENIENT_BYTES[c + 128];
1145     }
1146 
1147     private static boolean isWhitespace(byte b) {
1148         return LATIN_WHITESPACE[b + 128];
1149     }
1150 
1151     private static int findNonWhitespace(byte[] sb, int offset, int end) {
1152         for (int result = offset; result < end; ++result) {
1153             byte c = sb[result];
1154             if (!isWhitespace(c)) {
1155                 return result;
1156             } else if (!isOWS(c)) {
1157                 // Only OWS is supported for whitespace
1158                 throw new IllegalArgumentException("Invalid separator, only a single space or horizontal tab allowed," +
1159                         " but received a '" + c + "' (0x" + Integer.toHexString(c) + ")");
1160             }
1161         }
1162         return end;
1163     }
1164 
1165     private static int findEndOfString(byte[] sb, int start, int end) {
1166         for (int result = end - 1; result > start; --result) {
1167             if (!isOWS(sb[result])) {
1168                 return result + 1;
1169             }
1170         }
1171         return 0;
1172     }
1173 
1174     private static boolean isOWS(byte ch) {
1175         return ch == ' ' || ch == 0x09;
1176     }
1177 
1178     private static class HeaderParser {
1179         protected final ByteBuf seq;
1180         protected final int maxLength;
1181         int size;
1182 
1183         HeaderParser(ByteBuf seq, int maxLength) {
1184             this.seq = seq;
1185             this.maxLength = maxLength;
1186         }
1187 
1188         public ByteBuf parse(ByteBuf buffer, Runnable strictCRLFCheck) {
1189             final int readableBytes = buffer.readableBytes();
1190             final int readerIndex = buffer.readerIndex();
1191             final int maxBodySize = maxLength - size;
1192             assert maxBodySize >= 0;
1193             // adding 2 to account for both CR (if present) and LF
1194             // don't remove 2L: it's key to cover maxLength = Integer.MAX_VALUE
1195             final long maxBodySizeWithCRLF = maxBodySize + 2L;
1196             final int toProcess = (int) Math.min(maxBodySizeWithCRLF, readableBytes);
1197             final int toIndexExclusive = readerIndex + toProcess;
1198             assert toIndexExclusive >= readerIndex;
1199             final int indexOfLf = buffer.indexOf(readerIndex, toIndexExclusive, HttpConstants.LF);
1200             if (indexOfLf == -1) {
1201                 if (readableBytes > maxBodySize) {
1202                     // TODO: Respond with Bad Request and discard the traffic
1203                     //    or close the connection.
1204                     //       No need to notify the upstream handlers - just log.
1205                     //       If decoding a response, just throw an exception.
1206                     throw newException(maxLength);
1207                 }
1208                 return null;
1209             }
1210             final int endOfSeqIncluded;
1211             if (indexOfLf > readerIndex && buffer.getByte(indexOfLf - 1) == HttpConstants.CR) {
1212                 // Drop CR if we had a CRLF pair
1213                 endOfSeqIncluded = indexOfLf - 1;
1214             } else {
1215                 if (strictCRLFCheck != null) {
1216                     strictCRLFCheck.run();
1217                 }
1218                 endOfSeqIncluded = indexOfLf;
1219             }
1220             final int newSize = endOfSeqIncluded - readerIndex;
1221             if (newSize == 0) {
1222                 seq.clear();
1223                 buffer.readerIndex(indexOfLf + 1);
1224                 return seq;
1225             }
1226             int size = this.size + newSize;
1227             if (size > maxLength) {
1228                 throw newException(maxLength);
1229             }
1230             this.size = size;
1231             seq.clear();
1232             seq.writeBytes(buffer, readerIndex, newSize);
1233             buffer.readerIndex(indexOfLf + 1);
1234             return seq;
1235         }
1236 
1237         public void reset() {
1238             size = 0;
1239         }
1240 
1241         protected TooLongFrameException newException(int maxLength) {
1242             return new TooLongHttpHeaderException("HTTP header is larger than " + maxLength + " bytes.");
1243         }
1244     }
1245 
1246     private final class LineParser extends HeaderParser {
1247 
1248         LineParser(ByteBuf seq, int maxLength) {
1249             super(seq, maxLength);
1250         }
1251 
1252         @Override
1253         public ByteBuf parse(ByteBuf buffer, Runnable strictCRLFCheck) {
1254             // Suppress a warning because HeaderParser.reset() is supposed to be called
1255             reset();
1256             final int readableBytes = buffer.readableBytes();
1257             if (readableBytes == 0) {
1258                 return null;
1259             }
1260             if (currentState == State.SKIP_CONTROL_CHARS &&
1261                     skipControlChars(buffer, readableBytes, buffer.readerIndex())) {
1262                 return null;
1263             }
1264             return super.parse(buffer, strictCRLFCheck);
1265         }
1266 
1267         private boolean skipControlChars(ByteBuf buffer, int readableBytes, int readerIndex) {
1268             assert currentState == State.SKIP_CONTROL_CHARS;
1269             final int maxToSkip = Math.min(maxLength, readableBytes);
1270             final int firstNonControlIndex = buffer.forEachByte(readerIndex, maxToSkip, SKIP_CONTROL_CHARS_BYTES);
1271             if (firstNonControlIndex == -1) {
1272                 buffer.skipBytes(maxToSkip);
1273                 if (readableBytes > maxLength) {
1274                     throw newException(maxLength);
1275                 }
1276                 return true;
1277             }
1278             // from now on we don't care about control chars
1279             buffer.readerIndex(firstNonControlIndex);
1280             currentState = State.READ_INITIAL;
1281             return false;
1282         }
1283 
1284         @Override
1285         protected TooLongFrameException newException(int maxLength) {
1286             return new TooLongHttpLineException("An HTTP line is larger than " + maxLength + " bytes.");
1287         }
1288     }
1289 
1290     private static final boolean[] ISO_CONTROL_OR_WHITESPACE;
1291 
1292     static {
1293         ISO_CONTROL_OR_WHITESPACE = new boolean[256];
1294         for (byte b = Byte.MIN_VALUE; b < Byte.MAX_VALUE; b++) {
1295             ISO_CONTROL_OR_WHITESPACE[128 + b] = Character.isISOControl(b) || isWhitespace(b);
1296         }
1297     }
1298 
1299     private static final ByteProcessor SKIP_CONTROL_CHARS_BYTES = new ByteProcessor() {
1300 
1301         @Override
1302         public boolean process(byte value) {
1303             return ISO_CONTROL_OR_WHITESPACE[128 + value];
1304         }
1305     };
1306 
1307     private static boolean isControlOrWhitespaceAsciiChar(byte b) {
1308         return ISO_CONTROL_OR_WHITESPACE[128 + b];
1309     }
1310 }