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