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