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  *   http://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 static io.netty.util.internal.ObjectUtil.checkPositive;
19 import static io.netty.util.internal.StringUtil.COMMA;
20
21 import io.netty.buffer.ByteBuf;
22 import io.netty.buffer.Unpooled;
23 import io.netty.channel.ChannelHandlerContext;
24 import io.netty.channel.ChannelPipeline;
25 import io.netty.handler.codec.ByteToMessageDecoder;
26 import io.netty.handler.codec.DecoderResult;
27 import io.netty.handler.codec.PrematureChannelClosureException;
28 import io.netty.handler.codec.TooLongFrameException;
29 import io.netty.util.ByteProcessor;
30 import io.netty.util.internal.AppendableCharSequence;
31
32 import java.util.List;
33 import java.util.regex.Pattern;
34
35 /**
36  * Decodes {@link ByteBuf}s into {@link HttpMessage}s and
37  * {@link HttpContent}s.
38  *
39  * <h3>Parameters that prevents excessive memory consumption</h3>
40  * <table border="1">
41  * <tr>
42  * <th>Name</th><th>Default value</th><th>Meaning</th>
43  * </tr>
44  * <tr>
45  * <td>{@code maxInitialLineLength}</td>
46  * <td>{@value #DEFAULT_MAX_INITIAL_LINE_LENGTH}</td>
47  * <td>The maximum length of the initial line
48  *     (e.g. {@code "GET / HTTP/1.0"} or {@code "HTTP/1.0 200 OK"})
49  *     If the length of the initial line exceeds this value, a
50  *     {@link TooLongFrameException} will be raised.</td>
51  * </tr>
52  * <tr>
53  * <td>{@code maxHeaderSize}</td>
54  * <td>{@value #DEFAULT_MAX_HEADER_SIZE}</td>
55  * <td>The maximum length of all headers.  If the sum of the length of each
56  *     header exceeds this value, a {@link TooLongFrameException} will be raised.</td>
57  * </tr>
58  * <tr>
59  * <td>{@code maxChunkSize}</td>
60  * <td>{@value #DEFAULT_MAX_CHUNK_SIZE}</td>
61  * <td>The maximum length of the content or each chunk.  If the content length
62  *     (or the length of each chunk) exceeds this value, the content or chunk
63  *     will be split into multiple {@link HttpContent}s whose length is
64  *     {@code maxChunkSize} at maximum.</td>
65  * </tr>
66  * </table>
67  *
68  * <h3>Parameters that control parsing behavior</h3>
69  * <table border="1">
70  * <tr>
71  * <th>Name</th><th>Default value</th><th>Meaning</th>
72  * </tr>
73  * <tr>
74  * <td>{@code allowDuplicateContentLengths}</td>
75  * <td>{@value #DEFAULT_ALLOW_DUPLICATE_CONTENT_LENGTHS}</td>
76  * <td>When set to {@code false}, will reject any messages that contain multiple Content-Length header fields.
77  *     When set to {@code true}, will allow multiple Content-Length headers only if they are all the same decimal value.
78  *     The duplicated field-values will be replaced with a single valid Content-Length field.
79  *     See <a href="https://tools.ietf.org/html/rfc7230#section-3.3.2">RFC 7230, Section 3.3.2</a>.</td>
80  * </tr>
81  * </table>
82  *
83  * <h3>Chunked Content</h3>
84  *
85  * If the content of an HTTP message is greater than {@code maxChunkSize} or
86  * the transfer encoding of the HTTP message is 'chunked', this decoder
87  * generates one {@link HttpMessage} instance and its following
88  * {@link HttpContent}s per single HTTP message to avoid excessive memory
89  * consumption. For example, the following HTTP message:
90  * <pre>
91  * GET / HTTP/1.1
92  * Transfer-Encoding: chunked
93  *
94  * 1a
95  * abcdefghijklmnopqrstuvwxyz
96  * 10
97  * 1234567890abcdef
98  * 0
99  * Content-MD5: ...
100  * <i>[blank line]</i>
101  * </pre>
102  * triggers {@link HttpRequestDecoder} to generate 3 objects:
103  * <ol>
104  * <li>An {@link HttpRequest},</li>
105  * <li>The first {@link HttpContent} whose content is {@code 'abcdefghijklmnopqrstuvwxyz'},</li>
106  * <li>The second {@link LastHttpContent} whose content is {@code '1234567890abcdef'}, which marks
107  * the end of the content.</li>
108  * </ol>
109  *
110  * If you prefer not to handle {@link HttpContent}s by yourself for your
111  * convenience, insert {@link HttpObjectAggregator} after this decoder in the
112  * {@link ChannelPipeline}.  However, please note that your server might not
113  * be as memory efficient as without the aggregator.
114  *
115  * <h3>Extensibility</h3>
116  *
117  * Please note that this decoder is designed to be extended to implement
118  * a protocol derived from HTTP, such as
119  * <a href="http://en.wikipedia.org/wiki/Real_Time_Streaming_Protocol">RTSP</a> and
120  * <a href="http://en.wikipedia.org/wiki/Internet_Content_Adaptation_Protocol">ICAP</a>.
121  * To implement the decoder of such a derived protocol, extend this class and
122  * implement all abstract methods properly.
123  */

124 public abstract class HttpObjectDecoder extends ByteToMessageDecoder {
125     public static final int DEFAULT_MAX_INITIAL_LINE_LENGTH = 4096;
126     public static final int DEFAULT_MAX_HEADER_SIZE = 8192;
127     public static final boolean DEFAULT_CHUNKED_SUPPORTED = true;
128     public static final int DEFAULT_MAX_CHUNK_SIZE = 8192;
129     public static final boolean DEFAULT_VALIDATE_HEADERS = true;
130     public static final int DEFAULT_INITIAL_BUFFER_SIZE = 128;
131     public static final boolean DEFAULT_ALLOW_DUPLICATE_CONTENT_LENGTHS = false;
132
133     private static final String EMPTY_VALUE = "";
134     private static final Pattern COMMA_PATTERN = Pattern.compile(",");
135
136     private final int maxChunkSize;
137     private final boolean chunkedSupported;
138     protected final boolean validateHeaders;
139     private final boolean allowDuplicateContentLengths;
140     private final HeaderParser headerParser;
141     private final LineParser lineParser;
142
143     private HttpMessage message;
144     private long chunkSize;
145     private long contentLength = Long.MIN_VALUE;
146     private volatile boolean resetRequested;
147
148     // These will be updated by splitHeader(...)
149     private CharSequence name;
150     private CharSequence value;
151
152     private LastHttpContent trailer;
153
154     /**
155      * The internal state of {@link HttpObjectDecoder}.
156      * <em>Internal use only</em>.
157      */

158     private enum State {
159         SKIP_CONTROL_CHARS,
160         READ_INITIAL,
161         READ_HEADER,
162         READ_VARIABLE_LENGTH_CONTENT,
163         READ_FIXED_LENGTH_CONTENT,
164         READ_CHUNK_SIZE,
165         READ_CHUNKED_CONTENT,
166         READ_CHUNK_DELIMITER,
167         READ_CHUNK_FOOTER,
168         BAD_MESSAGE,
169         UPGRADED
170     }
171
172     private State currentState = State.SKIP_CONTROL_CHARS;
173
174     /**
175      * Creates a new instance with the default
176      * {@code maxInitialLineLength (4096}}, {@code maxHeaderSize (8192)}, and
177      * {@code maxChunkSize (8192)}.
178      */

179     protected HttpObjectDecoder() {
180         this(DEFAULT_MAX_INITIAL_LINE_LENGTH, DEFAULT_MAX_HEADER_SIZE, DEFAULT_MAX_CHUNK_SIZE,
181              DEFAULT_CHUNKED_SUPPORTED);
182     }
183
184     /**
185      * Creates a new instance with the specified parameters.
186      */

187     protected HttpObjectDecoder(
188             int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, boolean chunkedSupported) {
189         this(maxInitialLineLength, maxHeaderSize, maxChunkSize, chunkedSupported, DEFAULT_VALIDATE_HEADERS);
190     }
191
192     /**
193      * Creates a new instance with the specified parameters.
194      */

195     protected HttpObjectDecoder(
196             int maxInitialLineLength, int maxHeaderSize, int maxChunkSize,
197             boolean chunkedSupported, boolean validateHeaders) {
198         this(maxInitialLineLength, maxHeaderSize, maxChunkSize, chunkedSupported, validateHeaders,
199              DEFAULT_INITIAL_BUFFER_SIZE);
200     }
201
202     /**
203      * Creates a new instance with the specified parameters.
204      */

205     protected HttpObjectDecoder(
206             int maxInitialLineLength, int maxHeaderSize, int maxChunkSize,
207             boolean chunkedSupported, boolean validateHeaders, int initialBufferSize) {
208         this(maxInitialLineLength, maxHeaderSize, maxChunkSize, chunkedSupported, validateHeaders, initialBufferSize,
209              DEFAULT_ALLOW_DUPLICATE_CONTENT_LENGTHS);
210     }
211
212     protected HttpObjectDecoder(
213             int maxInitialLineLength, int maxHeaderSize, int maxChunkSize,
214             boolean chunkedSupported, boolean validateHeaders, int initialBufferSize,
215             boolean allowDuplicateContentLengths) {
216         checkPositive(maxInitialLineLength, "maxInitialLineLength");
217         checkPositive(maxHeaderSize, "maxHeaderSize");
218         checkPositive(maxChunkSize, "maxChunkSize");
219
220         AppendableCharSequence seq = new AppendableCharSequence(initialBufferSize);
221         lineParser = new LineParser(seq, maxInitialLineLength);
222         headerParser = new HeaderParser(seq, maxHeaderSize);
223         this.maxChunkSize = maxChunkSize;
224         this.chunkedSupported = chunkedSupported;
225         this.validateHeaders = validateHeaders;
226         this.allowDuplicateContentLengths = allowDuplicateContentLengths;
227     }
228
229     @Override
230     protected void decode(ChannelHandlerContext ctx, ByteBuf buffer, List<Object> out) throws Exception {
231         if (resetRequested) {
232             resetNow();
233         }
234
235         switch (currentState) {
236         case SKIP_CONTROL_CHARS:
237             // Fall-through
238         case READ_INITIAL: try {
239             AppendableCharSequence line = lineParser.parse(buffer);
240             if (line == null) {
241                 return;
242             }
243             String[] initialLine = splitInitialLine(line);
244             if (initialLine.length < 3) {
245                 // Invalid initial line - ignore.
246                 currentState = State.SKIP_CONTROL_CHARS;
247                 return;
248             }
249
250             message = createMessage(initialLine);
251             currentState = State.READ_HEADER;
252             // fall-through
253         } catch (Exception e) {
254             out.add(invalidMessage(buffer, e));
255             return;
256         }
257         case READ_HEADER: try {
258             State nextState = readHeaders(buffer);
259             if (nextState == null) {
260                 return;
261             }
262             currentState = nextState;
263             switch (nextState) {
264             case SKIP_CONTROL_CHARS:
265                 // fast-path
266                 // No content is expected.
267                 out.add(message);
268                 out.add(LastHttpContent.EMPTY_LAST_CONTENT);
269                 resetNow();
270                 return;
271             case READ_CHUNK_SIZE:
272                 if (!chunkedSupported) {
273                     throw new IllegalArgumentException("Chunked messages not supported");
274                 }
275                 // Chunked encoding - generate HttpMessage first.  HttpChunks will follow.
276                 out.add(message);
277                 return;
278             default:
279                 /**
280                  * <a href="https://tools.ietf.org/html/rfc7230#section-3.3.3">RFC 7230, 3.3.3</a> states that if a
281                  * request does not have either a transfer-encoding or a content-length header then the message body
282                  * length is 0. However for a response the body length is the number of octets received prior to the
283                  * server closing the connection. So we treat this as variable length chunked encoding.
284                  */

285                 long contentLength = contentLength();
286                 if (contentLength == 0 || contentLength == -1 && isDecodingRequest()) {
287                     out.add(message);
288                     out.add(LastHttpContent.EMPTY_LAST_CONTENT);
289                     resetNow();
290                     return;
291                 }
292
293                 assert nextState == State.READ_FIXED_LENGTH_CONTENT ||
294                         nextState == State.READ_VARIABLE_LENGTH_CONTENT;
295
296                 out.add(message);
297
298                 if (nextState == State.READ_FIXED_LENGTH_CONTENT) {
299                     // chunkSize will be decreased as the READ_FIXED_LENGTH_CONTENT state reads data chunk by chunk.
300                     chunkSize = contentLength;
301                 }
302
303                 // We return here, this forces decode to be called again where we will decode the content
304                 return;
305             }
306         } catch (Exception e) {
307             out.add(invalidMessage(buffer, e));
308             return;
309         }
310         case READ_VARIABLE_LENGTH_CONTENT: {
311             // Keep reading data as a chunk until the end of connection is reached.
312             int toRead = Math.min(buffer.readableBytes(), maxChunkSize);
313             if (toRead > 0) {
314                 ByteBuf content = buffer.readRetainedSlice(toRead);
315                 out.add(new DefaultHttpContent(content));
316             }
317             return;
318         }
319         case READ_FIXED_LENGTH_CONTENT: {
320             int readLimit = buffer.readableBytes();
321
322             // Check if the buffer is readable first as we use the readable byte count
323             // to create the HttpChunk. This is needed as otherwise we may end up with
324             // create an HttpChunk instance that contains an empty buffer and so is
325             // handled like it is the last HttpChunk.
326             //
327             // See https://github.com/netty/netty/issues/433
328             if (readLimit == 0) {
329                 return;
330             }
331
332             int toRead = Math.min(readLimit, maxChunkSize);
333             if (toRead > chunkSize) {
334                 toRead = (int) chunkSize;
335             }
336             ByteBuf content = buffer.readRetainedSlice(toRead);
337             chunkSize -= toRead;
338
339             if (chunkSize == 0) {
340                 // Read all content.
341                 out.add(new DefaultLastHttpContent(content, validateHeaders));
342                 resetNow();
343             } else {
344                 out.add(new DefaultHttpContent(content));
345             }
346             return;
347         }
348         /**
349          * everything else after this point takes care of reading chunked content. basically, read chunk size,
350          * read chunk, read and ignore the CRLF and repeat until 0
351          */

352         case READ_CHUNK_SIZE: try {
353             AppendableCharSequence line = lineParser.parse(buffer);
354             if (line == null) {
355                 return;
356             }
357             int chunkSize = getChunkSize(line.toString());
358             this.chunkSize = chunkSize;
359             if (chunkSize == 0) {
360                 currentState = State.READ_CHUNK_FOOTER;
361                 return;
362             }
363             currentState = State.READ_CHUNKED_CONTENT;
364             // fall-through
365         } catch (Exception e) {
366             out.add(invalidChunk(buffer, e));
367             return;
368         }
369         case READ_CHUNKED_CONTENT: {
370             assert chunkSize <= Integer.MAX_VALUE;
371             int toRead = Math.min((int) chunkSize, maxChunkSize);
372             toRead = Math.min(toRead, buffer.readableBytes());
373             if (toRead == 0) {
374                 return;
375             }
376             HttpContent chunk = new DefaultHttpContent(buffer.readRetainedSlice(toRead));
377             chunkSize -= toRead;
378
379             out.add(chunk);
380
381             if (chunkSize != 0) {
382                 return;
383             }
384             currentState = State.READ_CHUNK_DELIMITER;
385             // fall-through
386         }
387         case READ_CHUNK_DELIMITER: {
388             final int wIdx = buffer.writerIndex();
389             int rIdx = buffer.readerIndex();
390             while (wIdx > rIdx) {
391                 byte next = buffer.getByte(rIdx++);
392                 if (next == HttpConstants.LF) {
393                     currentState = State.READ_CHUNK_SIZE;
394                     break;
395                 }
396             }
397             buffer.readerIndex(rIdx);
398             return;
399         }
400         case READ_CHUNK_FOOTER: try {
401             LastHttpContent trailer = readTrailingHeaders(buffer);
402             if (trailer == null) {
403                 return;
404             }
405             out.add(trailer);
406             resetNow();
407             return;
408         } catch (Exception e) {
409             out.add(invalidChunk(buffer, e));
410             return;
411         }
412         case BAD_MESSAGE: {
413             // Keep discarding until disconnection.
414             buffer.skipBytes(buffer.readableBytes());
415             break;
416         }
417         case UPGRADED: {
418             int readableBytes = buffer.readableBytes();
419             if (readableBytes > 0) {
420                 // Keep on consuming as otherwise we may trigger an DecoderException,
421                 // other handler will replace this codec with the upgraded protocol codec to
422                 // take the traffic over at some point then.
423                 // See https://github.com/netty/netty/issues/2173
424                 out.add(buffer.readBytes(readableBytes));
425             }
426             break;
427         }
428         }
429     }
430
431     @Override
432     protected void decodeLast(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
433         super.decodeLast(ctx, in, out);
434
435         if (resetRequested) {
436             // If a reset was requested by decodeLast() we need to do it now otherwise we may produce a
437             // LastHttpContent while there was already one.
438             resetNow();
439         }
440         // Handle the last unfinished message.
441         if (message != null) {
442             boolean chunked = HttpUtil.isTransferEncodingChunked(message);
443             if (currentState == State.READ_VARIABLE_LENGTH_CONTENT && !in.isReadable() && !chunked) {
444                 // End of connection.
445                 out.add(LastHttpContent.EMPTY_LAST_CONTENT);
446                 resetNow();
447                 return;
448             }
449
450             if (currentState == State.READ_HEADER) {
451                 // If we are still in the state of reading headers we need to create a new invalid message that
452                 // signals that the connection was closed before we received the headers.
453                 out.add(invalidMessage(Unpooled.EMPTY_BUFFER,
454                         new PrematureChannelClosureException("Connection closed before received headers")));
455                 resetNow();
456                 return;
457             }
458
459             // Check if the closure of the connection signifies the end of the content.
460             boolean prematureClosure;
461             if (isDecodingRequest() || chunked) {
462                 // The last request did not wait for a response.
463                 prematureClosure = true;
464             } else {
465                 // Compare the length of the received content and the 'Content-Length' header.
466                 // If the 'Content-Length' header is absent, the length of the content is determined by the end of the
467                 // connection, so it is perfectly fine.
468                 prematureClosure = contentLength() > 0;
469             }
470
471             if (!prematureClosure) {
472                 out.add(LastHttpContent.EMPTY_LAST_CONTENT);
473             }
474             resetNow();
475         }
476     }
477
478     @Override
479     public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
480         if (evt instanceof HttpExpectationFailedEvent) {
481             switch (currentState) {
482             case READ_FIXED_LENGTH_CONTENT:
483             case READ_VARIABLE_LENGTH_CONTENT:
484             case READ_CHUNK_SIZE:
485                 reset();
486                 break;
487             default:
488                 break;
489             }
490         }
491         super.userEventTriggered(ctx, evt);
492     }
493
494     protected boolean isContentAlwaysEmpty(HttpMessage msg) {
495         if (msg instanceof HttpResponse) {
496             HttpResponse res = (HttpResponse) msg;
497             int code = res.status().code();
498
499             // Correctly handle return codes of 1xx.
500             //
501             // See:
502             //     - http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html Section 4.4
503             //     - https://github.com/netty/netty/issues/222
504             if (code >= 100 && code < 200) {
505                 // One exception: Hixie 76 websocket handshake response
506                 return !(code == 101 && !res.headers().contains(HttpHeaderNames.SEC_WEBSOCKET_ACCEPT)
507                          && res.headers().contains(HttpHeaderNames.UPGRADE, HttpHeaderValues.WEBSOCKET, true));
508             }
509
510             switch (code) {
511             case 204: case 304:
512                 return true;
513             }
514         }
515         return false;
516     }
517
518     /**
519      * Returns true if the server switched to a different protocol than HTTP/1.0 or HTTP/1.1, e.g. HTTP/2 or Websocket.
520      * Returns false if the upgrade happened in a different layer, e.g. upgrade from HTTP/1.1 to HTTP/1.1 over TLS.
521      */

522     protected boolean isSwitchingToNonHttp1Protocol(HttpResponse msg) {
523         if (msg.status().code() != HttpResponseStatus.SWITCHING_PROTOCOLS.code()) {
524             return false;
525         }
526         String newProtocol = msg.headers().get(HttpHeaderNames.UPGRADE);
527         return newProtocol == null ||
528                 !newProtocol.contains(HttpVersion.HTTP_1_0.text()) &&
529                 !newProtocol.contains(HttpVersion.HTTP_1_1.text());
530     }
531
532     /**
533      * Resets the state of the decoder so that it is ready to decode a new message.
534      * This method is useful for handling a rejected request with {@code Expect: 100-continue} header.
535      */

536     public void reset() {
537         resetRequested = true;
538     }
539
540     private void resetNow() {
541         HttpMessage message = this.message;
542         this.message = null;
543         name = null;
544         value = null;
545         contentLength = Long.MIN_VALUE;
546         lineParser.reset();
547         headerParser.reset();
548         trailer = null;
549         if (!isDecodingRequest()) {
550             HttpResponse res = (HttpResponse) message;
551             if (res != null && isSwitchingToNonHttp1Protocol(res)) {
552                 currentState = State.UPGRADED;
553                 return;
554             }
555         }
556
557         resetRequested = false;
558         currentState = State.SKIP_CONTROL_CHARS;
559     }
560
561     private HttpMessage invalidMessage(ByteBuf in, Exception cause) {
562         currentState = State.BAD_MESSAGE;
563
564         // Advance the readerIndex so that ByteToMessageDecoder does not complain
565         // when we produced an invalid message without consuming anything.
566         in.skipBytes(in.readableBytes());
567
568         if (message == null) {
569             message = createInvalidMessage();
570         }
571         message.setDecoderResult(DecoderResult.failure(cause));
572
573         HttpMessage ret = message;
574         message = null;
575         return ret;
576     }
577
578     private HttpContent invalidChunk(ByteBuf in, Exception cause) {
579         currentState = State.BAD_MESSAGE;
580
581         // Advance the readerIndex so that ByteToMessageDecoder does not complain
582         // when we produced an invalid message without consuming anything.
583         in.skipBytes(in.readableBytes());
584
585         HttpContent chunk = new DefaultLastHttpContent(Unpooled.EMPTY_BUFFER);
586         chunk.setDecoderResult(DecoderResult.failure(cause));
587         message = null;
588         trailer = null;
589         return chunk;
590     }
591
592     private State readHeaders(ByteBuf buffer) {
593         final HttpMessage message = this.message;
594         final HttpHeaders headers = message.headers();
595
596         AppendableCharSequence line = headerParser.parse(buffer);
597         if (line == null) {
598             return null;
599         }
600         if (line.length() > 0) {
601             do {
602                 char firstChar = line.charAtUnsafe(0);
603                 if (name != null && (firstChar == ' ' || firstChar == '\t')) {
604                     //please do not make one line from below code
605                     //as it breaks +XX:OptimizeStringConcat optimization
606                     String trimmedLine = line.toString().trim();
607                     String valueStr = String.valueOf(value);
608                     value = valueStr + ' ' + trimmedLine;
609                 } else {
610                     if (name != null) {
611                         headers.add(name, value);
612                     }
613                     splitHeader(line);
614                 }
615
616                 line = headerParser.parse(buffer);
617                 if (line == null) {
618                     return null;
619                 }
620             } while (line.length() > 0);
621         }
622
623         // Add the last header.
624         if (name != null) {
625             headers.add(name, value);
626         }
627
628         // reset name and value fields
629         name = null;
630         value = null;
631
632         List<String> contentLengthFields = headers.getAll(HttpHeaderNames.CONTENT_LENGTH);
633
634         if (!contentLengthFields.isEmpty()) {
635             // Guard against multiple Content-Length headers as stated in
636             // https://tools.ietf.org/html/rfc7230#section-3.3.2:
637             //
638             // If a message is received that has multiple Content-Length header
639             //   fields with field-values consisting of the same decimal value, or a
640             //   single Content-Length header field with a field value containing a
641             //   list of identical decimal values (e.g., "Content-Length: 42, 42"),
642             //   indicating that duplicate Content-Length header fields have been
643             //   generated or combined by an upstream message processor, then the
644             //   recipient MUST either reject the message as invalid or replace the
645             //   duplicated field-values with a single valid Content-Length field
646             //   containing that decimal value prior to determining the message body
647             //   length or forwarding the message.
648             boolean multipleContentLengths =
649                     contentLengthFields.size() > 1 || contentLengthFields.get(0).indexOf(COMMA) >= 0;
650             if (multipleContentLengths && message.protocolVersion() == HttpVersion.HTTP_1_1) {
651                 if (allowDuplicateContentLengths) {
652                     // Find and enforce that all Content-Length values are the same
653                     String firstValue = null;
654                     for (String field : contentLengthFields) {
655                         String[] tokens = COMMA_PATTERN.split(field, -1);
656                         for (String token : tokens) {
657                             String trimmed = token.trim();
658                             if (firstValue == null) {
659                                 firstValue = trimmed;
660                             } else if (!trimmed.equals(firstValue)) {
661                                 throw new IllegalArgumentException(
662                                         "Multiple Content-Length values found: " + contentLengthFields);
663                             }
664                         }
665                     }
666                     // Replace the duplicated field-values with a single valid Content-Length field
667                     headers.set(HttpHeaderNames.CONTENT_LENGTH, firstValue);
668                     contentLength = Long.parseLong(firstValue);
669                 } else {
670                     // Reject the message as invalid
671                     throw new IllegalArgumentException(
672                             "Multiple Content-Length values found: " + contentLengthFields);
673                 }
674             } else {
675                 contentLength = Long.parseLong(contentLengthFields.get(0));
676             }
677         }
678
679         if (isContentAlwaysEmpty(message)) {
680             HttpUtil.setTransferEncodingChunked(message, false);
681             return State.SKIP_CONTROL_CHARS;
682         } else if (HttpUtil.isTransferEncodingChunked(message)) {
683             if (!contentLengthFields.isEmpty() && message.protocolVersion() == HttpVersion.HTTP_1_1) {
684                 handleTransferEncodingChunkedWithContentLength(message);
685             }
686             return State.READ_CHUNK_SIZE;
687         } else if (contentLength() >= 0) {
688             return State.READ_FIXED_LENGTH_CONTENT;
689         } else {
690             return State.READ_VARIABLE_LENGTH_CONTENT;
691         }
692     }
693
694     /**
695      * Invoked when a message with both a "Transfer-Encoding: chunked" and a "Content-Length" header field is detected.
696      * The default behavior is to <i>remove</i> the Content-Length field, but this method could be overridden
697      * to change the behavior (to, e.g., throw an exception and produce an invalid message).
698      * <p>
699      * See: https://tools.ietf.org/html/rfc7230#section-3.3.3
700      * <pre>
701      *     If a message is received with both a Transfer-Encoding and a
702      *     Content-Length header field, the Transfer-Encoding overrides the
703      *     Content-Length.  Such a message might indicate an attempt to
704      *     perform request smuggling (Section 9.5) or response splitting
705      *     (Section 9.4) and ought to be handled as an error.  A sender MUST
706      *     remove the received Content-Length field prior to forwarding such
707      *     a message downstream.
708      * </pre>
709      * Also see:
710      * https://github.com/apache/tomcat/blob/b693d7c1981fa7f51e58bc8c8e72e3fe80b7b773/
711      * java/org/apache/coyote/http11/Http11Processor.java#L747-L755
712      * https://github.com/nginx/nginx/blob/0ad4393e30c119d250415cb769e3d8bc8dce5186/
713      * src/http/ngx_http_request.c#L1946-L1953
714      */

715     protected void handleTransferEncodingChunkedWithContentLength(HttpMessage message) {
716         message.headers().remove(HttpHeaderNames.CONTENT_LENGTH);
717         contentLength = Long.MIN_VALUE;
718     }
719
720     private long contentLength() {
721         if (contentLength == Long.MIN_VALUE) {
722             contentLength = HttpUtil.getContentLength(message, -1L);
723         }
724         return contentLength;
725     }
726
727     private LastHttpContent readTrailingHeaders(ByteBuf buffer) {
728         AppendableCharSequence line = headerParser.parse(buffer);
729         if (line == null) {
730             return null;
731         }
732         LastHttpContent trailer = this.trailer;
733         if (line.length() == 0 && trailer == null) {
734             // We have received the empty line which signals the trailer is complete and did not parse any trailers
735             // before. Just return an empty last content to reduce allocations.
736             return LastHttpContent.EMPTY_LAST_CONTENT;
737         }
738
739         CharSequence lastHeader = null;
740         if (trailer == null) {
741             trailer = this.trailer = new DefaultLastHttpContent(Unpooled.EMPTY_BUFFER, validateHeaders);
742         }
743         while (line.length() > 0) {
744             char firstChar = line.charAtUnsafe(0);
745             if (lastHeader != null && (firstChar == ' ' || firstChar == '\t')) {
746                 List<String> current = trailer.trailingHeaders().getAll(lastHeader);
747                 if (!current.isEmpty()) {
748                     int lastPos = current.size() - 1;
749                     //please do not make one line from below code
750                     //as it breaks +XX:OptimizeStringConcat optimization
751                     String lineTrimmed = line.toString().trim();
752                     String currentLastPos = current.get(lastPos);
753                     current.set(lastPos, currentLastPos + lineTrimmed);
754                 }
755             } else {
756                 splitHeader(line);
757                 CharSequence headerName = name;
758                 if (!HttpHeaderNames.CONTENT_LENGTH.contentEqualsIgnoreCase(headerName) &&
759                         !HttpHeaderNames.TRANSFER_ENCODING.contentEqualsIgnoreCase(headerName) &&
760                         !HttpHeaderNames.TRAILER.contentEqualsIgnoreCase(headerName)) {
761                     trailer.trailingHeaders().add(headerName, value);
762                 }
763                 lastHeader = name;
764                 // reset name and value fields
765                 name = null;
766                 value = null;
767             }
768             line = headerParser.parse(buffer);
769             if (line == null) {
770                 return null;
771             }
772         }
773
774         this.trailer = null;
775         return trailer;
776     }
777
778     protected abstract boolean isDecodingRequest();
779     protected abstract HttpMessage createMessage(String[] initialLine) throws Exception;
780     protected abstract HttpMessage createInvalidMessage();
781
782     private static int getChunkSize(String hex) {
783         hex = hex.trim();
784         for (int i = 0; i < hex.length(); i ++) {
785             char c = hex.charAt(i);
786             if (c == ';' || Character.isWhitespace(c) || Character.isISOControl(c)) {
787                 hex = hex.substring(0, i);
788                 break;
789             }
790         }
791
792         return Integer.parseInt(hex, 16);
793     }
794
795     private static String[] splitInitialLine(AppendableCharSequence sb) {
796         int aStart;
797         int aEnd;
798         int bStart;
799         int bEnd;
800         int cStart;
801         int cEnd;
802
803         aStart = findNonSPLenient(sb, 0);
804         aEnd = findSPLenient(sb, aStart);
805
806         bStart = findNonSPLenient(sb, aEnd);
807         bEnd = findSPLenient(sb, bStart);
808
809         cStart = findNonSPLenient(sb, bEnd);
810         cEnd = findEndOfString(sb);
811
812         return new String[] {
813                 sb.subStringUnsafe(aStart, aEnd),
814                 sb.subStringUnsafe(bStart, bEnd),
815                 cStart < cEnd? sb.subStringUnsafe(cStart, cEnd) : "" };
816     }
817
818     private void splitHeader(AppendableCharSequence sb) {
819         final int length = sb.length();
820         int nameStart;
821         int nameEnd;
822         int colonEnd;
823         int valueStart;
824         int valueEnd;
825
826         nameStart = findNonWhitespace(sb, 0, false);
827         for (nameEnd = nameStart; nameEnd < length; nameEnd ++) {
828             char ch = sb.charAtUnsafe(nameEnd);
829             // https://tools.ietf.org/html/rfc7230#section-3.2.4
830             //
831             // No whitespace is allowed between the header field-name and colon. In
832             // the past, differences in the handling of such whitespace have led to
833             // security vulnerabilities in request routing and response handling. A
834             // server MUST reject any received request message that contains
835             // whitespace between a header field-name and colon with a response code
836             // of 400 (Bad Request). A proxy MUST remove any such whitespace from a
837             // response message before forwarding the message downstream.
838             if (ch == ':' ||
839                     // In case of decoding a request we will just continue processing and header validation
840                     // is done in the DefaultHttpHeaders implementation.
841                     //
842                     // In the case of decoding a response we will "skip" the whitespace.
843                     (!isDecodingRequest() && isOWS(ch))) {
844                 break;
845             }
846         }
847
848         if (nameEnd == length) {
849             // There was no colon present at all.
850             throw new IllegalArgumentException("No colon found");
851         }
852
853         for (colonEnd = nameEnd; colonEnd < length; colonEnd ++) {
854             if (sb.charAtUnsafe(colonEnd) == ':') {
855                 colonEnd ++;
856                 break;
857             }
858         }
859
860         name = sb.subStringUnsafe(nameStart, nameEnd);
861         valueStart = findNonWhitespace(sb, colonEnd, true);
862         if (valueStart == length) {
863             value = EMPTY_VALUE;
864         } else {
865             valueEnd = findEndOfString(sb);
866             value = sb.subStringUnsafe(valueStart, valueEnd);
867         }
868     }
869
870     private static int findNonSPLenient(AppendableCharSequence sb, int offset) {
871         for (int result = offset; result < sb.length(); ++result) {
872             char c = sb.charAtUnsafe(result);
873             // See https://tools.ietf.org/html/rfc7230#section-3.5
874             if (isSPLenient(c)) {
875                 continue;
876             }
877             if (Character.isWhitespace(c)) {
878                 // Any other whitespace delimiter is invalid
879                 throw new IllegalArgumentException("Invalid separator");
880             }
881             return result;
882         }
883         return sb.length();
884     }
885
886     private static int findSPLenient(AppendableCharSequence sb, int offset) {
887         for (int result = offset; result < sb.length(); ++result) {
888             if (isSPLenient(sb.charAtUnsafe(result))) {
889                 return result;
890             }
891         }
892         return sb.length();
893     }
894
895     private static boolean isSPLenient(char c) {
896         // See https://tools.ietf.org/html/rfc7230#section-3.5
897         return c == ' ' || c == (char) 0x09 || c == (char) 0x0B || c == (char) 0x0C || c == (char) 0x0D;
898     }
899
900     private static int findNonWhitespace(AppendableCharSequence sb, int offset, boolean validateOWS) {
901         for (int result = offset; result < sb.length(); ++result) {
902             char c = sb.charAtUnsafe(result);
903             if (!Character.isWhitespace(c)) {
904                 return result;
905             } else if (validateOWS && !isOWS(c)) {
906                 // Only OWS is supported for whitespace
907                 throw new IllegalArgumentException("Invalid separator, only a single space or horizontal tab allowed," +
908                         " but received a '" + c + "'");
909             }
910         }
911         return sb.length();
912     }
913
914     private static int findEndOfString(AppendableCharSequence sb) {
915         for (int result = sb.length() - 1; result > 0; --result) {
916             if (!Character.isWhitespace(sb.charAtUnsafe(result))) {
917                 return result + 1;
918             }
919         }
920         return 0;
921     }
922
923     private static boolean isOWS(char ch) {
924         return ch == ' ' || ch == (char) 0x09;
925     }
926
927     private static class HeaderParser implements ByteProcessor {
928         private final AppendableCharSequence seq;
929         private final int maxLength;
930         private int size;
931
932         HeaderParser(AppendableCharSequence seq, int maxLength) {
933             this.seq = seq;
934             this.maxLength = maxLength;
935         }
936
937         public AppendableCharSequence parse(ByteBuf buffer) {
938             final int oldSize = size;
939             seq.reset();
940             int i = buffer.forEachByte(this);
941             if (i == -1) {
942                 size = oldSize;
943                 return null;
944             }
945             buffer.readerIndex(i + 1);
946             return seq;
947         }
948
949         public void reset() {
950             size = 0;
951         }
952
953         @Override
954         public boolean process(byte value) throws Exception {
955             char nextByte = (char) (value & 0xFF);
956             if (nextByte == HttpConstants.LF) {
957                 int len = seq.length();
958                 // Drop CR if we had a CRLF pair
959                 if (len >= 1 && seq.charAtUnsafe(len - 1) == HttpConstants.CR) {
960                     -- size;
961                     seq.setLength(len - 1);
962                 }
963                 return false;
964             }
965
966             increaseCount();
967
968             seq.append(nextByte);
969             return true;
970         }
971
972         protected final void increaseCount() {
973             if (++ size > maxLength) {
974                 // TODO: Respond with Bad Request and discard the traffic
975                 //    or close the connection.
976                 //       No need to notify the upstream handlers - just log.
977                 //       If decoding a response, just throw an exception.
978                 throw newException(maxLength);
979             }
980         }
981
982         protected TooLongFrameException newException(int maxLength) {
983             return new TooLongFrameException("HTTP header is larger than " + maxLength + " bytes.");
984         }
985     }
986
987     private final class LineParser extends HeaderParser {
988
989         LineParser(AppendableCharSequence seq, int maxLength) {
990             super(seq, maxLength);
991         }
992
993         @Override
994         public AppendableCharSequence parse(ByteBuf buffer) {
995             reset();
996             return super.parse(buffer);
997         }
998
999         @Override
1000         public boolean process(byte value) throws Exception {
1001             if (currentState == State.SKIP_CONTROL_CHARS) {
1002                 char c = (char) (value & 0xFF);
1003                 if (Character.isISOControl(c) || Character.isWhitespace(c)) {
1004                     increaseCount();
1005                     return true;
1006                 }
1007                 currentState = State.READ_INITIAL;
1008             }
1009             return super.process(value);
1010         }
1011
1012         @Override
1013         protected TooLongFrameException newException(int maxLength) {
1014             return new TooLongFrameException("An HTTP line is larger than " + maxLength + " bytes.");
1015         }
1016     }
1017 }
1018