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