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                         !isLengthEqual(lengthValue, 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     private static boolean isLengthEqual(String lengthValue, long contentLength) {
842         try {
843             return Long.parseLong(lengthValue) == contentLength;
844         } catch (NumberFormatException e) {
845             return false;
846         }
847     }
848 
849     /**
850      * Invoked when a message with both a "Transfer-Encoding: chunked" and a "Content-Length" header field is detected.
851      * The default behavior is to <i>remove</i> the Content-Length field, but this method could be overridden
852      * to change the behavior (to, e.g., throw an exception and produce an invalid message).
853      * <p>
854      * See: https://tools.ietf.org/html/rfc7230#section-3.3.3
855      * <pre>
856      *     If a message is received with both a Transfer-Encoding and a
857      *     Content-Length header field, the Transfer-Encoding overrides the
858      *     Content-Length.  Such a message might indicate an attempt to
859      *     perform request smuggling (Section 9.5) or response splitting
860      *     (Section 9.4) and ought to be handled as an error.  A sender MUST
861      *     remove the received Content-Length field prior to forwarding such
862      *     a message downstream.
863      * </pre>
864      * Also see:
865      * https://github.com/apache/tomcat/blob/b693d7c1981fa7f51e58bc8c8e72e3fe80b7b773/
866      * java/org/apache/coyote/http11/Http11Processor.java#L747-L755
867      * https://github.com/nginx/nginx/blob/0ad4393e30c119d250415cb769e3d8bc8dce5186/
868      * src/http/ngx_http_request.c#L1946-L1953
869      */
870     protected void handleTransferEncodingChunkedWithContentLength(HttpMessage message) {
871         message.headers().remove(HttpHeaderNames.CONTENT_LENGTH);
872         contentLength = Long.MIN_VALUE;
873     }
874 
875     private LastHttpContent readTrailingHeaders(ByteBuf buffer) {
876         final HeaderParser headerParser = this.headerParser;
877         ByteBuf line = headerParser.parse(buffer, defaultStrictCRLFCheck);
878         if (line == null) {
879             return null;
880         }
881         LastHttpContent trailer = this.trailer;
882         int lineLength = line.readableBytes();
883         if (lineLength == 0 && trailer == null) {
884             // We have received the empty line which signals the trailer is complete and did not parse any trailers
885             // before. Just return an empty last content to reduce allocations.
886             return LastHttpContent.EMPTY_LAST_CONTENT;
887         }
888 
889         if (trailer == null) {
890             trailer = this.trailer = new DefaultLastHttpContent(Unpooled.EMPTY_BUFFER, trailersFactory);
891         }
892         while (lineLength > 0) {
893             final byte[] lineContent = line.array();
894             final int startLine = line.arrayOffset() + line.readerIndex();
895             final byte firstChar = lineContent[startLine];
896             if (name != null && (firstChar == ' ' || firstChar == '\t')) {
897                 //please do not make one line from below code
898                 //as it breaks +XX:OptimizeStringConcat optimization
899                 String trimmedLine = langAsciiString(lineContent, startLine, lineLength).trim();
900                 String valueStr = value;
901                 value = valueStr + ' ' + trimmedLine;
902             } else {
903                 if (name != null && isPermittedTrailingHeader(name)) {
904                     trailer.trailingHeaders().add(name, value);
905                 }
906                 splitHeader(lineContent, startLine, lineLength);
907             }
908 
909             line = headerParser.parse(buffer, defaultStrictCRLFCheck);
910             if (line == null) {
911                 return null;
912             }
913             lineLength = line.readableBytes();
914         }
915 
916         // Add the last trailer
917         if (name != null && isPermittedTrailingHeader(name)) {
918             trailer.trailingHeaders().add(name, value);
919         }
920 
921         // reset name and value fields
922         name = null;
923         value = null;
924 
925         this.trailer = null;
926         return trailer;
927     }
928 
929     /**
930      * Checks whether the given trailer field name is permitted per RFC 9110 section 6.5
931      */
932     private static boolean isPermittedTrailingHeader(final AsciiString name) {
933         return !HttpHeaderNames.CONTENT_LENGTH.contentEqualsIgnoreCase(name) &&
934                !HttpHeaderNames.TRANSFER_ENCODING.contentEqualsIgnoreCase(name) &&
935                !HttpHeaderNames.TRAILER.contentEqualsIgnoreCase(name);
936     }
937 
938     protected abstract boolean isDecodingRequest();
939     protected abstract HttpMessage createMessage(String[] initialLine) throws Exception;
940     protected abstract HttpMessage createInvalidMessage();
941 
942     /**
943      * It skips any whitespace char and return the number of skipped bytes.
944      */
945     private static int skipWhiteSpaces(byte[] hex, int start, int length) {
946         for (int i = 0; i < length; i++) {
947             if (!isWhitespace(hex[start + i])) {
948                 return i;
949             }
950         }
951         return length;
952     }
953 
954     private static int getChunkSize(byte[] hex, int start, int length) {
955         // trim the leading bytes of white spaces, if any
956         final int skipped = skipWhiteSpaces(hex, start, length);
957         if (skipped == length) {
958             // empty case
959             throw new NumberFormatException();
960         }
961         start += skipped;
962         length -= skipped;
963         int result = 0;
964         for (int i = 0; i < length; i++) {
965             final int digit = StringUtil.decodeHexNibble(hex[start + i]);
966             if (digit == -1) {
967                 // uncommon path
968                 final byte b = hex[start + i];
969                 if (b == ';' || isControlOrWhitespaceAsciiChar(b)) {
970                     if (i == 0) {
971                         // empty case
972                         throw new NumberFormatException("Empty chunk size");
973                     }
974                     return result;
975                 }
976                 // non-hex char fail-fast path
977                 throw new NumberFormatException("Invalid character in chunk size");
978             }
979             result *= 16;
980             result += digit;
981             if (result < 0) {
982                 throw new NumberFormatException("Chunk size overflow: " + result);
983             }
984         }
985         return result;
986     }
987 
988     private String[] splitInitialLine(ByteBuf asciiBuffer) {
989         final byte[] asciiBytes = asciiBuffer.array();
990 
991         final int arrayOffset = asciiBuffer.arrayOffset();
992 
993         final int startContent = arrayOffset + asciiBuffer.readerIndex();
994 
995         final int end = startContent + asciiBuffer.readableBytes();
996 
997         byte lastByte = asciiBytes[end - 1];
998         if (isControlOrWhitespaceAsciiChar(lastByte)) {
999             if (isDecodingRequest() || !isOWS(lastByte)) {
1000                 // There should no extra control or whitespace char in case of a request.
1001                 // In case of a response there might be a SP if there is no reason-phrase given.
1002                 // See
1003                 //  - https://datatracker.ietf.org/doc/html/rfc2616#section-5.1
1004                 //  - https://datatracker.ietf.org/doc/html/rfc9112#name-status-line
1005                 throw new IllegalArgumentException(
1006                         "Illegal character in request line: 0x" + Integer.toHexString(lastByte));
1007             }
1008         }
1009 
1010         final int aStart = findNonSPLenient(asciiBytes, startContent, end);
1011         final int aEnd = findSPLenient(asciiBytes, aStart, end);
1012 
1013         final int bStart = findNonSPLenient(asciiBytes, aEnd, end);
1014         final int bEnd = findSPLenient(asciiBytes, bStart, end);
1015 
1016         final int cStart = findNonSPLenient(asciiBytes, bEnd, end);
1017         final int cEnd = findEndOfString(asciiBytes, Math.max(cStart - 1, startContent), end);
1018 
1019         return new String[]{
1020                 splitFirstWordInitialLine(asciiBytes, aStart, aEnd - aStart),
1021                 splitSecondWordInitialLine(asciiBytes, bStart, bEnd - bStart),
1022                 cStart < cEnd ? splitThirdWordInitialLine(asciiBytes, cStart, cEnd - cStart) : StringUtil.EMPTY_STRING};
1023     }
1024 
1025     protected String splitFirstWordInitialLine(final byte[] asciiContent, int start, int length) {
1026         return langAsciiString(asciiContent, start, length);
1027     }
1028 
1029     protected String splitSecondWordInitialLine(final byte[] asciiContent, int start, int length) {
1030         return langAsciiString(asciiContent, start, length);
1031     }
1032 
1033     protected String splitThirdWordInitialLine(final byte[] asciiContent, int start, int length) {
1034         return langAsciiString(asciiContent, start, length);
1035     }
1036 
1037     /**
1038      * This method shouldn't exist: look at https://bugs.openjdk.org/browse/JDK-8295496 for more context
1039      */
1040     private static String langAsciiString(final byte[] asciiContent, int start, int length) {
1041         if (length == 0) {
1042             return StringUtil.EMPTY_STRING;
1043         }
1044         // DON'T REMOVE: it helps JIT to use a simpler intrinsic stub for System::arrayCopy based on the call-site
1045         if (start == 0) {
1046             if (length == asciiContent.length) {
1047                 return new String(asciiContent, 0, 0, asciiContent.length);
1048             }
1049             return new String(asciiContent, 0, 0, length);
1050         }
1051         return new String(asciiContent, 0, start, length);
1052     }
1053 
1054     private void splitHeader(byte[] line, int start, int length) {
1055         final int end = start + length;
1056         int nameEnd;
1057         final int nameStart = start;
1058         // hoist this load out of the loop, because it won't change!
1059         final boolean isDecodingRequest = isDecodingRequest();
1060         for (nameEnd = nameStart; nameEnd < end; nameEnd ++) {
1061             byte ch = line[nameEnd];
1062             // https://tools.ietf.org/html/rfc7230#section-3.2.4
1063             //
1064             // No whitespace is allowed between the header field-name and colon. In
1065             // the past, differences in the handling of such whitespace have led to
1066             // security vulnerabilities in request routing and response handling. A
1067             // server MUST reject any received request message that contains
1068             // whitespace between a header field-name and colon with a response code
1069             // of 400 (Bad Request). A proxy MUST remove any such whitespace from a
1070             // response message before forwarding the message downstream.
1071             if (ch == ':' ||
1072                     // In case of decoding a request we will just continue processing and header validation
1073                     // is done in the DefaultHttpHeaders implementation.
1074                     //
1075                     // In the case of decoding a response we will "skip" the whitespace.
1076                     (!isDecodingRequest && isOWS(ch))) {
1077                 break;
1078             }
1079         }
1080 
1081         if (nameEnd == end) {
1082             // There was no colon present at all.
1083             throw new IllegalArgumentException("No colon found");
1084         }
1085         int colonEnd;
1086         for (colonEnd = nameEnd; colonEnd < end; colonEnd ++) {
1087             if (line[colonEnd] == ':') {
1088                 colonEnd ++;
1089                 break;
1090             }
1091         }
1092         name = splitHeaderName(line, nameStart, nameEnd - nameStart);
1093         final int valueStart = findNonWhitespace(line, colonEnd, end);
1094         if (valueStart == end) {
1095             value = StringUtil.EMPTY_STRING;
1096         } else {
1097             final int valueEnd = findEndOfString(line, start, end);
1098             // no need to make uses of the ByteBuf's toString ASCII method here, and risk to get JIT confused
1099             value = langAsciiString(line, valueStart, valueEnd - valueStart);
1100         }
1101     }
1102 
1103     protected AsciiString splitHeaderName(byte[] sb, int start, int length) {
1104         return new AsciiString(sb, start, length, true);
1105     }
1106 
1107     private static int findNonSPLenient(byte[] sb, int offset, int end) {
1108         for (int result = offset; result < end; ++result) {
1109             byte c = sb[result];
1110             // See https://tools.ietf.org/html/rfc7230#section-3.5
1111             if (isSPLenient(c)) {
1112                 continue;
1113             }
1114             if (isWhitespace(c)) {
1115                 // Any other whitespace delimiter is invalid
1116                 throw new IllegalArgumentException("Invalid separator");
1117             }
1118             return result;
1119         }
1120         return end;
1121     }
1122 
1123     private static int findSPLenient(byte[] sb, int offset, int end) {
1124         for (int result = offset; result < end; ++result) {
1125             if (isSPLenient(sb[result])) {
1126                 return result;
1127             }
1128         }
1129         return end;
1130     }
1131 
1132     private static final boolean[] SP_LENIENT_BYTES;
1133     private static final boolean[] LATIN_WHITESPACE;
1134 
1135     static {
1136         // See https://tools.ietf.org/html/rfc7230#section-3.5
1137         SP_LENIENT_BYTES = new boolean[256];
1138         SP_LENIENT_BYTES[128 + ' '] = true;
1139         SP_LENIENT_BYTES[128 + 0x09] = true;
1140         SP_LENIENT_BYTES[128 + 0x0B] = true;
1141         SP_LENIENT_BYTES[128 + 0x0C] = true;
1142         SP_LENIENT_BYTES[128 + 0x0D] = true;
1143         // TO SAVE PERFORMING Character::isWhitespace ceremony
1144         LATIN_WHITESPACE = new boolean[256];
1145         for (byte b = Byte.MIN_VALUE; b < Byte.MAX_VALUE; b++) {
1146             LATIN_WHITESPACE[128 + b] = Character.isWhitespace(b);
1147         }
1148     }
1149 
1150     private static boolean isSPLenient(byte c) {
1151         // See https://tools.ietf.org/html/rfc7230#section-3.5
1152         return SP_LENIENT_BYTES[c + 128];
1153     }
1154 
1155     private static boolean isWhitespace(byte b) {
1156         return LATIN_WHITESPACE[b + 128];
1157     }
1158 
1159     private static int findNonWhitespace(byte[] sb, int offset, int end) {
1160         for (int result = offset; result < end; ++result) {
1161             byte c = sb[result];
1162             if (!isWhitespace(c)) {
1163                 return result;
1164             } else if (!isOWS(c)) {
1165                 // Only OWS is supported for whitespace
1166                 throw new IllegalArgumentException("Invalid separator, only a single space or horizontal tab allowed," +
1167                         " but received a '" + c + "' (0x" + Integer.toHexString(c) + ")");
1168             }
1169         }
1170         return end;
1171     }
1172 
1173     private static int findEndOfString(byte[] sb, int start, int end) {
1174         for (int result = end - 1; result > start; --result) {
1175             if (!isOWS(sb[result])) {
1176                 return result + 1;
1177             }
1178         }
1179         return 0;
1180     }
1181 
1182     private static boolean isOWS(byte ch) {
1183         return ch == ' ' || ch == 0x09;
1184     }
1185 
1186     private static class HeaderParser {
1187         protected final ByteBuf seq;
1188         protected final int maxLength;
1189         int size;
1190 
1191         HeaderParser(ByteBuf seq, int maxLength) {
1192             this.seq = seq;
1193             this.maxLength = maxLength;
1194         }
1195 
1196         public ByteBuf parse(ByteBuf buffer, Runnable strictCRLFCheck) {
1197             final int readableBytes = buffer.readableBytes();
1198             final int readerIndex = buffer.readerIndex();
1199             final int maxBodySize = maxLength - size;
1200             assert maxBodySize >= 0;
1201             // adding 2 to account for both CR (if present) and LF
1202             // don't remove 2L: it's key to cover maxLength = Integer.MAX_VALUE
1203             final long maxBodySizeWithCRLF = maxBodySize + 2L;
1204             final int toProcess = (int) Math.min(maxBodySizeWithCRLF, readableBytes);
1205             final int toIndexExclusive = readerIndex + toProcess;
1206             assert toIndexExclusive >= readerIndex;
1207             final int indexOfLf = buffer.indexOf(readerIndex, toIndexExclusive, HttpConstants.LF);
1208             if (indexOfLf == -1) {
1209                 if (readableBytes > maxBodySize) {
1210                     // TODO: Respond with Bad Request and discard the traffic
1211                     //    or close the connection.
1212                     //       No need to notify the upstream handlers - just log.
1213                     //       If decoding a response, just throw an exception.
1214                     throw newException(maxLength);
1215                 }
1216                 return null;
1217             }
1218             final int endOfSeqIncluded;
1219             if (indexOfLf > readerIndex && buffer.getByte(indexOfLf - 1) == HttpConstants.CR) {
1220                 // Drop CR if we had a CRLF pair
1221                 endOfSeqIncluded = indexOfLf - 1;
1222             } else {
1223                 if (strictCRLFCheck != null) {
1224                     strictCRLFCheck.run();
1225                 }
1226                 endOfSeqIncluded = indexOfLf;
1227             }
1228             final int newSize = endOfSeqIncluded - readerIndex;
1229             if (newSize == 0) {
1230                 seq.clear();
1231                 buffer.readerIndex(indexOfLf + 1);
1232                 return seq;
1233             }
1234             int size = this.size + newSize;
1235             if (size > maxLength) {
1236                 throw newException(maxLength);
1237             }
1238             this.size = size;
1239             seq.clear();
1240             seq.writeBytes(buffer, readerIndex, newSize);
1241             buffer.readerIndex(indexOfLf + 1);
1242             return seq;
1243         }
1244 
1245         public void reset() {
1246             size = 0;
1247         }
1248 
1249         protected TooLongFrameException newException(int maxLength) {
1250             return new TooLongHttpHeaderException("HTTP header is larger than " + maxLength + " bytes.");
1251         }
1252     }
1253 
1254     private final class LineParser extends HeaderParser {
1255 
1256         LineParser(ByteBuf seq, int maxLength) {
1257             super(seq, maxLength);
1258         }
1259 
1260         @Override
1261         public ByteBuf parse(ByteBuf buffer, Runnable strictCRLFCheck) {
1262             // Suppress a warning because HeaderParser.reset() is supposed to be called
1263             reset();
1264             final int readableBytes = buffer.readableBytes();
1265             if (readableBytes == 0) {
1266                 return null;
1267             }
1268             if (currentState == State.SKIP_CONTROL_CHARS &&
1269                     skipControlChars(buffer, readableBytes, buffer.readerIndex())) {
1270                 return null;
1271             }
1272             return super.parse(buffer, strictCRLFCheck);
1273         }
1274 
1275         private boolean skipControlChars(ByteBuf buffer, int readableBytes, int readerIndex) {
1276             assert currentState == State.SKIP_CONTROL_CHARS;
1277             final int maxToSkip = Math.min(maxLength, readableBytes);
1278             final int firstNonControlIndex = buffer.forEachByte(readerIndex, maxToSkip, SKIP_CONTROL_CHARS_BYTES);
1279             if (firstNonControlIndex == -1) {
1280                 buffer.skipBytes(maxToSkip);
1281                 if (readableBytes > maxLength) {
1282                     throw newException(maxLength);
1283                 }
1284                 return true;
1285             }
1286             // from now on we don't care about control chars
1287             buffer.readerIndex(firstNonControlIndex);
1288             currentState = State.READ_INITIAL;
1289             return false;
1290         }
1291 
1292         @Override
1293         protected TooLongFrameException newException(int maxLength) {
1294             return new TooLongHttpLineException("An HTTP line is larger than " + maxLength + " bytes.");
1295         }
1296     }
1297 
1298     private static final boolean[] ISO_CONTROL_OR_WHITESPACE;
1299 
1300     static {
1301         ISO_CONTROL_OR_WHITESPACE = new boolean[256];
1302         for (byte b = Byte.MIN_VALUE; b < Byte.MAX_VALUE; b++) {
1303             ISO_CONTROL_OR_WHITESPACE[128 + b] = Character.isISOControl(b) || isWhitespace(b);
1304         }
1305     }
1306 
1307     private static final ByteProcessor SKIP_CONTROL_CHARS_BYTES = new ByteProcessor() {
1308 
1309         @Override
1310         public boolean process(byte value) {
1311             return ISO_CONTROL_OR_WHITESPACE[128 + value];
1312         }
1313     };
1314 
1315     private static boolean isControlOrWhitespaceAsciiChar(byte b) {
1316         return ISO_CONTROL_OR_WHITESPACE[128 + b];
1317     }
1318 }