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