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