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                         !isLengthEqual(lengthValue, 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     private static boolean isLengthEqual(String lengthValue, long contentLength) {
883         try {
884             return Long.parseLong(lengthValue) == contentLength;
885         } catch (NumberFormatException e) {
886             return false;
887         }
888     }
889 
890     /**
891      * Invoked when a message with both a "Transfer-Encoding: chunked" and a "Content-Length" header field is detected.
892      * The default behavior is to throw a {@link ContentLengthNotAllowedException} exception, but this method could
893      * be overridden to change the behavior (to, e.g., remove the {@code Content-Length} header value.
894      * <p>
895      * See: <a href="https://www.rfc-editor.org/rfc/rfc9112.html#section-6.1-15">RFC 9112, Section 6.1-15</a>.
896      * <pre>
897      *     A server MAY reject a request that contains both Content-Length and Transfer-Encoding
898      *     or process such a request in accordance with the Transfer-Encoding alone.
899      *     Regardless, the server MUST close the connection after responding to such a request
900      *     to avoid the potential attacks.
901      * </pre>
902      * Since Netty itself cannot track the request/response pairing, it cannot guarantee that the connection is closed
903      * immediately after the response is sent. As such, it is safer to immediately reject the request.
904      * <p>
905      * <strong>Note:</strong> RFC 7230 (the previous HTTP/1.1 RFC) allowed the {@code Content-Length} header to simply
906      * be ignored, in the presence of a {@code Transfer-Encoding} header, but this practice is now obsolete
907      * and considered unsafe.
908      * The RFC 7230 behavior can be restored in the following ways:
909      * <ul>
910      *     <li>
911      *         Process-wide, by setting the {@value PROP_RFC9112_TRANSFER_ENCODING} system property to {@code false}.
912      *     </li>
913      *     <li>
914      *         Configured for a specific decoder, by setting
915      *         {@link HttpDecoderConfig#setUseRfc9112TransferEncoding(boolean)} to {@code false}.
916      *     </li>
917      *     <li>
918      *         Hard-coded for a specific decoder, by overriding this method with an implementation like the following:
919      *         <pre>{@code
920      * @Override
921      * protected void handleTransferEncodingChunkedWithContentLength(HttpMessage message) {
922      *     clearContentLength();
923      *     message.headers().remove(HttpHeaderNames.CONTENT_LENGTH);
924      * }
925      *         }</pre>
926      *     </li>
927      * </ul>
928      * <p>
929      * <strong>Note:</strong> This method is only called for {@code HTTP/1.1} requests. Earlier HTTP protocol versions
930      * do not support the {@code Transfer-Encoding} header, and will reject requests that include it.
931      */
932     @SuppressWarnings("unused")
933     protected void handleTransferEncodingChunkedWithContentLength(HttpMessage message) {
934         clearContentLength();
935         if (useRfc9112TransferEncoding) {
936             throw new ContentLengthNotAllowedException(
937                     "Content-Length are not allowed in HTTP/1.1 messages that contains a Transfer-Encoding header.");
938         } else {
939             message.headers().remove(HttpHeaderNames.CONTENT_LENGTH);
940             if (isDecodingRequest()) {
941                 HttpUtil.setKeepAlive(message, false);
942             }
943         }
944     }
945 
946     protected final void clearContentLength() {
947         contentLength = Long.MIN_VALUE;
948     }
949 
950     private LastHttpContent readTrailingHeaders(ByteBuf buffer) {
951         final HeaderParser headerParser = this.headerParser;
952         ByteBuf line = headerParser.parse(buffer, defaultStrictCRLFCheck);
953         if (line == null) {
954             return null;
955         }
956         LastHttpContent trailer = this.trailer;
957         int lineLength = line.readableBytes();
958         if (lineLength == 0 && trailer == null) {
959             // We have received the empty line which signals the trailer is complete and did not parse any trailers
960             // before. Just return an empty last content to reduce allocations.
961             return LastHttpContent.EMPTY_LAST_CONTENT;
962         }
963 
964         if (trailer == null) {
965             trailer = this.trailer = new DefaultLastHttpContent(Unpooled.EMPTY_BUFFER, trailersFactory);
966         }
967         while (lineLength > 0) {
968             final byte[] lineContent = line.array();
969             final int startLine = line.arrayOffset() + line.readerIndex();
970             final byte firstChar = lineContent[startLine];
971             if (name != null && (firstChar == ' ' || firstChar == '\t')) {
972                 //please do not make one line from below code
973                 //as it breaks +XX:OptimizeStringConcat optimization
974                 String trimmedLine = langAsciiString(lineContent, startLine, lineLength).trim();
975                 String valueStr = value;
976                 value = valueStr + ' ' + trimmedLine;
977             } else {
978                 if (name != null && isPermittedTrailingHeader(name)) {
979                     trailer.trailingHeaders().add(name, value);
980                 }
981                 splitHeader(lineContent, startLine, lineLength);
982             }
983 
984             line = headerParser.parse(buffer, defaultStrictCRLFCheck);
985             if (line == null) {
986                 return null;
987             }
988             lineLength = line.readableBytes();
989         }
990 
991         // Add the last trailer
992         if (name != null && isPermittedTrailingHeader(name)) {
993             trailer.trailingHeaders().add(name, value);
994         }
995 
996         // reset name and value fields
997         name = null;
998         value = null;
999 
1000         this.trailer = null;
1001         return trailer;
1002     }
1003 
1004     /**
1005      * Checks whether the given trailer field name is permitted per RFC 9110 section 6.5
1006      */
1007     private static boolean isPermittedTrailingHeader(final AsciiString name) {
1008         return !HttpHeaderNames.CONTENT_LENGTH.contentEqualsIgnoreCase(name) &&
1009                !HttpHeaderNames.TRANSFER_ENCODING.contentEqualsIgnoreCase(name) &&
1010                !HttpHeaderNames.TRAILER.contentEqualsIgnoreCase(name);
1011     }
1012 
1013     protected abstract boolean isDecodingRequest();
1014     protected abstract HttpMessage createMessage(String[] initialLine) throws Exception;
1015     protected abstract HttpMessage createInvalidMessage();
1016 
1017     /**
1018      * It skips any whitespace char and return the number of skipped bytes.
1019      */
1020     private static int skipWhiteSpaces(byte[] hex, int start, int length) {
1021         for (int i = 0; i < length; i++) {
1022             if (!isWhitespace(hex[start + i])) {
1023                 return i;
1024             }
1025         }
1026         return length;
1027     }
1028 
1029     private static int getChunkSize(byte[] hex, int start, int length) {
1030         // trim the leading bytes of white spaces, if any
1031         final int skipped = skipWhiteSpaces(hex, start, length);
1032         if (skipped == length) {
1033             // empty case
1034             throw new NumberFormatException();
1035         }
1036         start += skipped;
1037         length -= skipped;
1038         long result = 0;
1039         for (int i = 0; i < length; i++) {
1040             final int digit = StringUtil.decodeHexNibble(hex[start + i]);
1041             if (digit == -1) {
1042                 // uncommon path
1043                 final byte b = hex[start + i];
1044                 if (b == ';' || isControlOrWhitespaceAsciiChar(b)) {
1045                     if (i == 0) {
1046                         // empty case
1047                         throw new NumberFormatException("Empty chunk size");
1048                     }
1049                     return (int) result;
1050                 }
1051                 // non-hex char fail-fast path
1052                 throw new NumberFormatException("Invalid character in chunk size");
1053             }
1054             result *= 16;
1055             result += digit;
1056             if (result > Integer.MAX_VALUE) {
1057                 throw new NumberFormatException("Chunk size overflow: " + result);
1058             }
1059         }
1060         return (int) result;
1061     }
1062 
1063     private String[] splitInitialLine(ByteBuf asciiBuffer) {
1064         final byte[] asciiBytes = asciiBuffer.array();
1065 
1066         final int arrayOffset = asciiBuffer.arrayOffset();
1067 
1068         final int startContent = arrayOffset + asciiBuffer.readerIndex();
1069 
1070         final int end = startContent + asciiBuffer.readableBytes();
1071 
1072         byte lastByte = asciiBytes[end - 1];
1073         if (isControlOrWhitespaceAsciiChar(lastByte)) {
1074             if (isDecodingRequest() || !isOWS(lastByte)) {
1075                 // There should no extra control or whitespace char in case of a request.
1076                 // In case of a response there might be a SP if there is no reason-phrase given.
1077                 // See
1078                 //  - https://datatracker.ietf.org/doc/html/rfc2616#section-5.1
1079                 //  - https://datatracker.ietf.org/doc/html/rfc9112#name-status-line
1080                 throw new IllegalArgumentException(
1081                         "Illegal character in request line: 0x" + Integer.toHexString(lastByte));
1082             }
1083         }
1084 
1085         final int aStart = findNonSPLenient(asciiBytes, startContent, end);
1086         final int aEnd = findSPLenient(asciiBytes, aStart, end);
1087 
1088         final int bStart = findNonSPLenient(asciiBytes, aEnd, end);
1089         final int bEnd = findSPLenient(asciiBytes, bStart, end);
1090 
1091         final int cStart = findNonSPLenient(asciiBytes, bEnd, end);
1092         final int cEnd = findEndOfString(asciiBytes, Math.max(cStart - 1, startContent), end);
1093 
1094         return new String[]{
1095                 splitFirstWordInitialLine(asciiBytes, aStart, aEnd - aStart),
1096                 splitSecondWordInitialLine(asciiBytes, bStart, bEnd - bStart),
1097                 cStart < cEnd ? splitThirdWordInitialLine(asciiBytes, cStart, cEnd - cStart) : StringUtil.EMPTY_STRING};
1098     }
1099 
1100     protected String splitFirstWordInitialLine(final byte[] asciiContent, int start, int length) {
1101         return langAsciiString(asciiContent, start, length);
1102     }
1103 
1104     protected String splitSecondWordInitialLine(final byte[] asciiContent, int start, int length) {
1105         return langAsciiString(asciiContent, start, length);
1106     }
1107 
1108     protected String splitThirdWordInitialLine(final byte[] asciiContent, int start, int length) {
1109         return langAsciiString(asciiContent, start, length);
1110     }
1111 
1112     /**
1113      * This method shouldn't exist: look at https://bugs.openjdk.org/browse/JDK-8295496 for more context
1114      */
1115     private static String langAsciiString(final byte[] asciiContent, int start, int length) {
1116         if (length == 0) {
1117             return StringUtil.EMPTY_STRING;
1118         }
1119         // DON'T REMOVE: it helps JIT to use a simpler intrinsic stub for System::arrayCopy based on the call-site
1120         if (start == 0) {
1121             if (length == asciiContent.length) {
1122                 return new String(asciiContent, 0, 0, asciiContent.length);
1123             }
1124             return new String(asciiContent, 0, 0, length);
1125         }
1126         return new String(asciiContent, 0, start, length);
1127     }
1128 
1129     private void splitHeader(byte[] line, int start, int length) {
1130         final int end = start + length;
1131         int nameEnd;
1132         final int nameStart = start;
1133         // hoist this load out of the loop, because it won't change!
1134         final boolean isDecodingRequest = isDecodingRequest();
1135         for (nameEnd = nameStart; nameEnd < end; nameEnd ++) {
1136             byte ch = line[nameEnd];
1137             // https://tools.ietf.org/html/rfc7230#section-3.2.4
1138             //
1139             // No whitespace is allowed between the header field-name and colon. In
1140             // the past, differences in the handling of such whitespace have led to
1141             // security vulnerabilities in request routing and response handling. A
1142             // server MUST reject any received request message that contains
1143             // whitespace between a header field-name and colon with a response code
1144             // of 400 (Bad Request). A proxy MUST remove any such whitespace from a
1145             // response message before forwarding the message downstream.
1146             if (ch == ':' ||
1147                     // In case of decoding a request we will just continue processing and header validation
1148                     // is done in the DefaultHttpHeaders implementation.
1149                     //
1150                     // In the case of decoding a response we will "skip" the whitespace.
1151                     (!isDecodingRequest && isOWS(ch))) {
1152                 break;
1153             }
1154         }
1155 
1156         if (nameEnd == end) {
1157             // There was no colon present at all.
1158             throw new IllegalArgumentException("No colon found");
1159         }
1160         int colonEnd;
1161         for (colonEnd = nameEnd; colonEnd < end; colonEnd ++) {
1162             if (line[colonEnd] == ':') {
1163                 colonEnd ++;
1164                 break;
1165             }
1166         }
1167         name = splitHeaderName(line, nameStart, nameEnd - nameStart);
1168         final int valueStart = findNonWhitespace(line, colonEnd, end);
1169         if (valueStart == end) {
1170             value = StringUtil.EMPTY_STRING;
1171         } else {
1172             final int valueEnd = findEndOfString(line, start, end);
1173             // no need to make uses of the ByteBuf's toString ASCII method here, and risk to get JIT confused
1174             value = langAsciiString(line, valueStart, valueEnd - valueStart);
1175         }
1176     }
1177 
1178     protected AsciiString splitHeaderName(byte[] sb, int start, int length) {
1179         return new AsciiString(sb, start, length, true);
1180     }
1181 
1182     private static int findNonSPLenient(byte[] sb, int offset, int end) {
1183         for (int result = offset; result < end; ++result) {
1184             byte c = sb[result];
1185             // See https://tools.ietf.org/html/rfc7230#section-3.5
1186             if (isSPLenient(c)) {
1187                 continue;
1188             }
1189             if (isWhitespace(c)) {
1190                 // Any other whitespace delimiter is invalid
1191                 throw new IllegalArgumentException("Invalid separator");
1192             }
1193             return result;
1194         }
1195         return end;
1196     }
1197 
1198     private static int findSPLenient(byte[] sb, int offset, int end) {
1199         for (int result = offset; result < end; ++result) {
1200             if (isSPLenient(sb[result])) {
1201                 return result;
1202             }
1203         }
1204         return end;
1205     }
1206 
1207     private static final boolean[] SP_LENIENT_BYTES;
1208     private static final boolean[] LATIN_WHITESPACE;
1209 
1210     static {
1211         // See https://tools.ietf.org/html/rfc7230#section-3.5
1212         SP_LENIENT_BYTES = new boolean[256];
1213         SP_LENIENT_BYTES[128 + ' '] = true;
1214         SP_LENIENT_BYTES[128 + 0x09] = true;
1215         SP_LENIENT_BYTES[128 + 0x0B] = true;
1216         SP_LENIENT_BYTES[128 + 0x0C] = true;
1217         SP_LENIENT_BYTES[128 + 0x0D] = true;
1218         // TO SAVE PERFORMING Character::isWhitespace ceremony
1219         LATIN_WHITESPACE = new boolean[256];
1220         for (byte b = Byte.MIN_VALUE; b < Byte.MAX_VALUE; b++) {
1221             LATIN_WHITESPACE[128 + b] = Character.isWhitespace(b);
1222         }
1223     }
1224 
1225     private static boolean isSPLenient(byte c) {
1226         // See https://tools.ietf.org/html/rfc7230#section-3.5
1227         return SP_LENIENT_BYTES[c + 128];
1228     }
1229 
1230     private static boolean isWhitespace(byte b) {
1231         return LATIN_WHITESPACE[b + 128];
1232     }
1233 
1234     private static int findNonWhitespace(byte[] sb, int offset, int end) {
1235         for (int result = offset; result < end; ++result) {
1236             byte c = sb[result];
1237             if (!isWhitespace(c)) {
1238                 return result;
1239             } else if (!isOWS(c)) {
1240                 // Only OWS is supported for whitespace
1241                 throw new IllegalArgumentException("Invalid separator, only a single space or horizontal tab allowed," +
1242                         " but received a '" + c + "' (0x" + Integer.toHexString(c) + ")");
1243             }
1244         }
1245         return end;
1246     }
1247 
1248     private static int findEndOfString(byte[] sb, int start, int end) {
1249         for (int result = end - 1; result > start; --result) {
1250             if (!isOWS(sb[result])) {
1251                 return result + 1;
1252             }
1253         }
1254         return 0;
1255     }
1256 
1257     private static boolean isOWS(byte ch) {
1258         return ch == ' ' || ch == 0x09;
1259     }
1260 
1261     private static class HeaderParser {
1262         protected final ByteBuf seq;
1263         protected final int maxLength;
1264         int size;
1265 
1266         HeaderParser(ByteBuf seq, int maxLength) {
1267             this.seq = seq;
1268             this.maxLength = maxLength;
1269         }
1270 
1271         public ByteBuf parse(ByteBuf buffer, Runnable strictCRLFCheck) {
1272             final int readableBytes = buffer.readableBytes();
1273             final int readerIndex = buffer.readerIndex();
1274             final int maxBodySize = maxLength - size;
1275             assert maxBodySize >= 0;
1276             // adding 2 to account for both CR (if present) and LF
1277             // don't remove 2L: it's key to cover maxLength = Integer.MAX_VALUE
1278             final long maxBodySizeWithCRLF = maxBodySize + 2L;
1279             final int toProcess = (int) Math.min(maxBodySizeWithCRLF, readableBytes);
1280             final int toIndexExclusive = readerIndex + toProcess;
1281             assert toIndexExclusive >= readerIndex;
1282             final int indexOfLf = buffer.indexOf(readerIndex, toIndexExclusive, HttpConstants.LF);
1283             if (indexOfLf == -1) {
1284                 if (readableBytes > maxBodySize) {
1285                     // TODO: Respond with Bad Request and discard the traffic
1286                     //    or close the connection.
1287                     //       No need to notify the upstream handlers - just log.
1288                     //       If decoding a response, just throw an exception.
1289                     throw newException(maxLength);
1290                 }
1291                 return null;
1292             }
1293             final int endOfSeqIncluded;
1294             if (indexOfLf > readerIndex && buffer.getByte(indexOfLf - 1) == HttpConstants.CR) {
1295                 // Drop CR if we had a CRLF pair
1296                 endOfSeqIncluded = indexOfLf - 1;
1297             } else {
1298                 if (strictCRLFCheck != null) {
1299                     strictCRLFCheck.run();
1300                 }
1301                 endOfSeqIncluded = indexOfLf;
1302             }
1303             final int newSize = endOfSeqIncluded - readerIndex;
1304             if (newSize == 0) {
1305                 seq.clear();
1306                 buffer.readerIndex(indexOfLf + 1);
1307                 return seq;
1308             }
1309             int size = this.size + newSize;
1310             if (size > maxLength) {
1311                 throw newException(maxLength);
1312             }
1313             this.size = size;
1314             seq.clear();
1315             seq.writeBytes(buffer, readerIndex, newSize);
1316             buffer.readerIndex(indexOfLf + 1);
1317             return seq;
1318         }
1319 
1320         public void reset() {
1321             size = 0;
1322         }
1323 
1324         protected TooLongFrameException newException(int maxLength) {
1325             return new TooLongHttpHeaderException("HTTP header is larger than " + maxLength + " bytes.");
1326         }
1327     }
1328 
1329     private final class LineParser extends HeaderParser {
1330 
1331         LineParser(ByteBuf seq, int maxLength) {
1332             super(seq, maxLength);
1333         }
1334 
1335         @Override
1336         public ByteBuf parse(ByteBuf buffer, Runnable strictCRLFCheck) {
1337             // Suppress a warning because HeaderParser.reset() is supposed to be called
1338             reset();
1339             final int readableBytes = buffer.readableBytes();
1340             if (readableBytes == 0) {
1341                 return null;
1342             }
1343             if (currentState == State.SKIP_INITIAL_LINE_CHARS &&
1344                     skipLineChars(buffer, readableBytes, buffer.readerIndex(), strictCRLFCheck)) {
1345                 return null;
1346             }
1347             return super.parse(buffer, strictCRLFCheck);
1348         }
1349 
1350         private boolean skipLineChars(ByteBuf buffer, int readableBytes, int readerIndex, Runnable strictCRLFCheck) {
1351             assert currentState == State.SKIP_INITIAL_LINE_CHARS;
1352             final int maxToSkip = Math.min(maxLength, readableBytes);
1353             final int firstNonLineIndex = buffer.forEachByte(readerIndex, maxToSkip,
1354                     strictCRLFCheck == null ? SKIP_CONTROL_CHARS_BYTES : ByteProcessor.FIND_NON_CRLF);
1355             if (firstNonLineIndex == -1) {
1356                 buffer.skipBytes(maxToSkip);
1357                 if (readableBytes > maxLength) {
1358                     throw newException(maxLength);
1359                 }
1360                 return true;
1361             }
1362             if (strictCRLFCheck != null) {
1363                 final int b = buffer.getByte(firstNonLineIndex) & 0xFF;
1364                 if (Character.isISOControl(b)) {
1365                     strictCRLFCheck.run();
1366                 }
1367             }
1368             // from now on we don't care about control chars
1369             buffer.readerIndex(firstNonLineIndex);
1370             currentState = State.READ_INITIAL;
1371             return false;
1372         }
1373 
1374         @Override
1375         protected TooLongFrameException newException(int maxLength) {
1376             return new TooLongHttpLineException("An HTTP line is larger than " + maxLength + " bytes.");
1377         }
1378     }
1379 
1380     private static final boolean[] ISO_CONTROL_OR_WHITESPACE;
1381 
1382     static {
1383         ISO_CONTROL_OR_WHITESPACE = new boolean[256];
1384         for (byte b = Byte.MIN_VALUE; b < Byte.MAX_VALUE; b++) {
1385             ISO_CONTROL_OR_WHITESPACE[128 + b] = Character.isISOControl(b) || isWhitespace(b);
1386         }
1387     }
1388 
1389     private static final ByteProcessor SKIP_CONTROL_CHARS_BYTES = new ByteProcessor() {
1390         @Override
1391         public boolean process(byte value) {
1392             return ISO_CONTROL_OR_WHITESPACE[128 + value];
1393         }
1394     };
1395 
1396     private static boolean isControlOrWhitespaceAsciiChar(byte b) {
1397         return ISO_CONTROL_OR_WHITESPACE[128 + b];
1398     }
1399 }