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    *   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 io.netty.buffer.ByteBuf;
19  import io.netty.buffer.ByteBufProcessor;
20  import io.netty.buffer.Unpooled;
21  import io.netty.channel.ChannelHandlerContext;
22  import io.netty.channel.ChannelPipeline;
23  import io.netty.handler.codec.ByteToMessageDecoder;
24  import io.netty.handler.codec.DecoderResult;
25  import io.netty.handler.codec.PrematureChannelClosureException;
26  import io.netty.handler.codec.TooLongFrameException;
27  import io.netty.util.internal.AppendableCharSequence;
28  
29  import java.util.List;
30  
31  /**
32   * Decodes {@link ByteBuf}s into {@link HttpMessage}s and
33   * {@link HttpContent}s.
34   *
35   * <h3>Parameters that prevents excessive memory consumption</h3>
36   * <table border="1">
37   * <tr>
38   * <th>Name</th><th>Meaning</th>
39   * </tr>
40   * <tr>
41   * <td>{@code maxInitialLineLength}</td>
42   * <td>The maximum length of the initial line
43   *     (e.g. {@code "GET / HTTP/1.0"} or {@code "HTTP/1.0 200 OK"})
44   *     If the length of the initial line exceeds this value, a
45   *     {@link TooLongFrameException} will be raised.</td>
46   * </tr>
47   * <tr>
48   * <td>{@code maxHeaderSize}</td>
49   * <td>The maximum length of all headers.  If the sum of the length of each
50   *     header exceeds this value, a {@link TooLongFrameException} will be raised.</td>
51   * </tr>
52   * <tr>
53   * <td>{@code maxChunkSize}</td>
54   * <td>The maximum length of the content or each chunk.  If the content length
55   *     (or the length of each chunk) exceeds this value, the content or chunk
56   *     will be split into multiple {@link HttpContent}s whose length is
57   *     {@code maxChunkSize} at maximum.</td>
58   * </tr>
59   * </table>
60   *
61   * <h3>Chunked Content</h3>
62   *
63   * If the content of an HTTP message is greater than {@code maxChunkSize} or
64   * the transfer encoding of the HTTP message is 'chunked', this decoder
65   * generates one {@link HttpMessage} instance and its following
66   * {@link HttpContent}s per single HTTP message to avoid excessive memory
67   * consumption. For example, the following HTTP message:
68   * <pre>
69   * GET / HTTP/1.1
70   * Transfer-Encoding: chunked
71   *
72   * 1a
73   * abcdefghijklmnopqrstuvwxyz
74   * 10
75   * 1234567890abcdef
76   * 0
77   * Content-MD5: ...
78   * <i>[blank line]</i>
79   * </pre>
80   * triggers {@link HttpRequestDecoder} to generate 3 objects:
81   * <ol>
82   * <li>An {@link HttpRequest},</li>
83   * <li>The first {@link HttpContent} whose content is {@code 'abcdefghijklmnopqrstuvwxyz'},</li>
84   * <li>The second {@link LastHttpContent} whose content is {@code '1234567890abcdef'}, which marks
85   * the end of the content.</li>
86   * </ol>
87   *
88   * If you prefer not to handle {@link HttpContent}s by yourself for your
89   * convenience, insert {@link HttpObjectAggregator} after this decoder in the
90   * {@link ChannelPipeline}.  However, please note that your server might not
91   * be as memory efficient as without the aggregator.
92   *
93   * <h3>Extensibility</h3>
94   *
95   * Please note that this decoder is designed to be extended to implement
96   * a protocol derived from HTTP, such as
97   * <a href="http://en.wikipedia.org/wiki/Real_Time_Streaming_Protocol">RTSP</a> and
98   * <a href="http://en.wikipedia.org/wiki/Internet_Content_Adaptation_Protocol">ICAP</a>.
99   * To implement the decoder of such a derived protocol, extend this class and
100  * implement all abstract methods properly.
101  */
102 public abstract class HttpObjectDecoder extends ByteToMessageDecoder {
103     private static final String EMPTY_VALUE = "";
104 
105     private final int maxChunkSize;
106     private final boolean chunkedSupported;
107     protected final boolean validateHeaders;
108     private final HeaderParser headerParser;
109     private final LineParser lineParser;
110 
111     private HttpMessage message;
112     private long chunkSize;
113     private long contentLength = Long.MIN_VALUE;
114     private volatile boolean resetRequested;
115 
116     // These will be updated by splitHeader(...)
117     private CharSequence name;
118     private CharSequence value;
119 
120     private LastHttpContent trailer;
121 
122     /**
123      * The internal state of {@link HttpObjectDecoder}.
124      * <em>Internal use only</em>.
125      */
126     private enum State {
127         SKIP_CONTROL_CHARS,
128         READ_INITIAL,
129         READ_HEADER,
130         READ_VARIABLE_LENGTH_CONTENT,
131         READ_FIXED_LENGTH_CONTENT,
132         READ_CHUNK_SIZE,
133         READ_CHUNKED_CONTENT,
134         READ_CHUNK_DELIMITER,
135         READ_CHUNK_FOOTER,
136         BAD_MESSAGE,
137         UPGRADED
138     }
139 
140     private State currentState = State.SKIP_CONTROL_CHARS;
141 
142     /**
143      * Creates a new instance with the default
144      * {@code maxInitialLineLength (4096}}, {@code maxHeaderSize (8192)}, and
145      * {@code maxChunkSize (8192)}.
146      */
147     protected HttpObjectDecoder() {
148         this(4096, 8192, 8192, true);
149     }
150 
151     /**
152      * Creates a new instance with the specified parameters.
153      */
154     protected HttpObjectDecoder(
155             int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, boolean chunkedSupported) {
156         this(maxInitialLineLength, maxHeaderSize, maxChunkSize, chunkedSupported, true);
157     }
158 
159     /**
160      * Creates a new instance with the specified parameters.
161      */
162     protected HttpObjectDecoder(
163             int maxInitialLineLength, int maxHeaderSize, int maxChunkSize,
164             boolean chunkedSupported, boolean validateHeaders) {
165         this(maxInitialLineLength, maxHeaderSize, maxChunkSize, chunkedSupported, validateHeaders, 128);
166     }
167 
168     protected HttpObjectDecoder(
169             int maxInitialLineLength, int maxHeaderSize, int maxChunkSize,
170             boolean chunkedSupported, boolean validateHeaders, int initialBufferSize) {
171         if (maxInitialLineLength <= 0) {
172             throw new IllegalArgumentException(
173                     "maxInitialLineLength must be a positive integer: " +
174                             maxInitialLineLength);
175         }
176         if (maxHeaderSize <= 0) {
177             throw new IllegalArgumentException(
178                     "maxHeaderSize must be a positive integer: " +
179                             maxHeaderSize);
180         }
181         if (maxChunkSize <= 0) {
182             throw new IllegalArgumentException(
183                     "maxChunkSize must be a positive integer: " +
184                             maxChunkSize);
185         }
186         AppendableCharSequence seq = new AppendableCharSequence(initialBufferSize);
187         lineParser = new LineParser(seq, maxInitialLineLength);
188         headerParser = new HeaderParser(seq, maxHeaderSize);
189         this.maxChunkSize = maxChunkSize;
190         this.chunkedSupported = chunkedSupported;
191         this.validateHeaders = validateHeaders;
192     }
193 
194     @Override
195     protected void decode(ChannelHandlerContext ctx, ByteBuf buffer, List<Object> out) throws Exception {
196         if (resetRequested) {
197             resetNow();
198         }
199 
200         switch (currentState) {
201             case SKIP_CONTROL_CHARS: {
202                 if (!skipControlCharacters(buffer)) {
203                     return;
204                 }
205                 currentState = State.READ_INITIAL;
206             }
207             case READ_INITIAL: try {
208                 AppendableCharSequence line = lineParser.parse(buffer);
209                 if (line == null) {
210                     return;
211                 }
212                 String[] initialLine = splitInitialLine(line);
213                 if (initialLine.length < 3) {
214                     // Invalid initial line - ignore.
215                     currentState = State.SKIP_CONTROL_CHARS;
216                     return;
217                 }
218 
219                 message = createMessage(initialLine);
220                 currentState = State.READ_HEADER;
221                 // fall-through
222             } catch (Exception e) {
223                 out.add(invalidMessage(buffer, e));
224                 return;
225             }
226             case READ_HEADER: try {
227                 State nextState = readHeaders(buffer);
228                 if (nextState == null) {
229                     return;
230                 }
231                 currentState = nextState;
232                 switch (nextState) {
233                     case SKIP_CONTROL_CHARS:
234                         // fast-path
235                         // No content is expected.
236                         out.add(message);
237                         out.add(LastHttpContent.EMPTY_LAST_CONTENT);
238                         resetNow();
239                         return;
240                     case READ_CHUNK_SIZE:
241                         if (!chunkedSupported) {
242                             throw new IllegalArgumentException("Chunked messages not supported");
243                         }
244                         // Chunked encoding - generate HttpMessage first.  HttpChunks will follow.
245                         out.add(message);
246                         return;
247                     default:
248                         /**
249                          * <a href="https://tools.ietf.org/html/rfc7230#section-3.3.3">RFC 7230, 3.3.3</a> states that
250                          * if a request does not have either a transfer-encoding or a content-length header then the
251                          * message body length is 0. However for a response the body length is the number of octets
252                          * received prior to the server closing the connection. So we treat this as variable length
253                          * chunked encoding.
254                          */
255                         long contentLength = contentLength();
256                         if (contentLength == 0 || contentLength == -1 && isDecodingRequest()) {
257                             out.add(message);
258                             out.add(LastHttpContent.EMPTY_LAST_CONTENT);
259                             resetNow();
260                             return;
261                         }
262 
263                         assert nextState == State.READ_FIXED_LENGTH_CONTENT ||
264                                 nextState == State.READ_VARIABLE_LENGTH_CONTENT;
265 
266                         out.add(message);
267 
268                         if (nextState == State.READ_FIXED_LENGTH_CONTENT) {
269                             // chunkSize will be decreased as the READ_FIXED_LENGTH_CONTENT state reads data chunk by
270                             // chunk.
271                             chunkSize = contentLength;
272                         }
273 
274                         // We return here, this forces decode to be called again where we will decode the content
275                         return;
276                 }
277             } catch (Exception e) {
278                 out.add(invalidMessage(buffer, e));
279                 return;
280             }
281             case READ_VARIABLE_LENGTH_CONTENT: {
282                 // Keep reading data as a chunk until the end of connection is reached.
283                 int toRead = Math.min(buffer.readableBytes(), maxChunkSize);
284                 if (toRead > 0) {
285                     ByteBuf content = buffer.readSlice(toRead).retain();
286                     out.add(new DefaultHttpContent(content));
287                 }
288                 return;
289             }
290             case READ_FIXED_LENGTH_CONTENT: {
291                 int readLimit = buffer.readableBytes();
292 
293                 // Check if the buffer is readable first as we use the readable byte count
294                 // to create the HttpChunk. This is needed as otherwise we may end up with
295                 // create a HttpChunk instance that contains an empty buffer and so is
296                 // handled like it is the last HttpChunk.
297                 //
298                 // See https://github.com/netty/netty/issues/433
299                 if (readLimit == 0) {
300                     return;
301                 }
302 
303                 int toRead = Math.min(readLimit, maxChunkSize);
304                 if (toRead > chunkSize) {
305                     toRead = (int) chunkSize;
306                 }
307                 ByteBuf content = buffer.readSlice(toRead).retain();
308                 chunkSize -= toRead;
309 
310                 if (chunkSize == 0) {
311                     // Read all content.
312                     out.add(new DefaultLastHttpContent(content, validateHeaders));
313                     resetNow();
314                 } else {
315                     out.add(new DefaultHttpContent(content));
316                 }
317                 return;
318             }
319             /**
320              * everything else after this point takes care of reading chunked content. basically, read chunk size,
321              * read chunk, read and ignore the CRLF and repeat until 0
322              */
323             case READ_CHUNK_SIZE: try {
324                 AppendableCharSequence line = lineParser.parse(buffer);
325                 if (line == null) {
326                     return;
327                 }
328                 int chunkSize = getChunkSize(line.toString());
329                 this.chunkSize = chunkSize;
330                 if (chunkSize == 0) {
331                     currentState = State.READ_CHUNK_FOOTER;
332                     return;
333                 }
334                 currentState = State.READ_CHUNKED_CONTENT;
335                 // fall-through
336             } catch (Exception e) {
337                 out.add(invalidChunk(buffer, e));
338                 return;
339             }
340             case READ_CHUNKED_CONTENT: {
341                 assert chunkSize <= Integer.MAX_VALUE;
342                 int toRead = Math.min((int) chunkSize, maxChunkSize);
343                 toRead = Math.min(toRead, buffer.readableBytes());
344                 if (toRead == 0) {
345                     return;
346                 }
347                 HttpContent chunk = new DefaultHttpContent(buffer.readSlice(toRead).retain());
348                 chunkSize -= toRead;
349 
350                 out.add(chunk);
351 
352                 if (chunkSize != 0) {
353                     return;
354                 }
355                 currentState = State.READ_CHUNK_DELIMITER;
356                 // fall-through
357             }
358             case READ_CHUNK_DELIMITER: {
359                 final int wIdx = buffer.writerIndex();
360                 int rIdx = buffer.readerIndex();
361                 while (wIdx > rIdx) {
362                     byte next = buffer.getByte(rIdx++);
363                     if (next == HttpConstants.LF) {
364                         currentState = State.READ_CHUNK_SIZE;
365                         break;
366                     }
367                 }
368                 buffer.readerIndex(rIdx);
369                 return;
370             }
371             case READ_CHUNK_FOOTER: try {
372                 LastHttpContent trailer = readTrailingHeaders(buffer);
373                 if (trailer == null) {
374                     return;
375                 }
376                 out.add(trailer);
377                 resetNow();
378                 return;
379             } catch (Exception e) {
380                 out.add(invalidChunk(buffer, e));
381                 return;
382             }
383             case BAD_MESSAGE: {
384                 // Keep discarding until disconnection.
385                 buffer.skipBytes(buffer.readableBytes());
386                 break;
387             }
388             case UPGRADED: {
389                 int readableBytes = buffer.readableBytes();
390                 if (readableBytes > 0) {
391                     // Keep on consuming as otherwise we may trigger an DecoderException,
392                     // other handler will replace this codec with the upgraded protocol codec to
393                     // take the traffic over at some point then.
394                     // See https://github.com/netty/netty/issues/2173
395                     out.add(buffer.readBytes(readableBytes));
396                 }
397                 break;
398             }
399         }
400     }
401 
402     @Override
403     protected void decodeLast(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
404         super.decodeLast(ctx, in, out);
405 
406         if (resetRequested) {
407             // If a reset was requested by decodeLast() we need to do it now otherwise we may produce a
408             // LastHttpContent while there was already one.
409             resetNow();
410         }
411         // Handle the last unfinished message.
412         if (message != null) {
413             boolean chunked = HttpHeaders.isTransferEncodingChunked(message);
414             if (currentState == State.READ_VARIABLE_LENGTH_CONTENT && !in.isReadable() && !chunked) {
415                 // End of connection.
416                 out.add(LastHttpContent.EMPTY_LAST_CONTENT);
417                 resetNow();
418                 return;
419             }
420 
421             if (currentState == State.READ_HEADER) {
422                 // If we are still in the state of reading headers we need to create a new invalid message that
423                 // signals that the connection was closed before we received the headers.
424                 out.add(invalidMessage(Unpooled.EMPTY_BUFFER,
425                         new PrematureChannelClosureException("Connection closed before received headers")));
426                 resetNow();
427                 return;
428             }
429 
430             // Check if the closure of the connection signifies the end of the content.
431             boolean prematureClosure;
432             if (isDecodingRequest() || chunked) {
433                 // The last request did not wait for a response.
434                 prematureClosure = true;
435             } else {
436                 // Compare the length of the received content and the 'Content-Length' header.
437                 // If the 'Content-Length' header is absent, the length of the content is determined by the end of the
438                 // connection, so it is perfectly fine.
439                 prematureClosure = contentLength() > 0;
440             }
441 
442             if (!prematureClosure) {
443                 out.add(LastHttpContent.EMPTY_LAST_CONTENT);
444             }
445             resetNow();
446         }
447     }
448 
449     @Override
450     public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
451         if (evt instanceof HttpExpectationFailedEvent) {
452             switch (currentState) {
453             case READ_FIXED_LENGTH_CONTENT:
454             case READ_VARIABLE_LENGTH_CONTENT:
455             case READ_CHUNK_SIZE:
456                 reset();
457                 break;
458             default:
459                 break;
460             }
461         }
462         super.userEventTriggered(ctx, evt);
463     }
464 
465     protected boolean isContentAlwaysEmpty(HttpMessage msg) {
466         if (msg instanceof HttpResponse) {
467             HttpResponse res = (HttpResponse) msg;
468             int code = res.getStatus().code();
469 
470             // Correctly handle return codes of 1xx.
471             //
472             // See:
473             //     - http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html Section 4.4
474             //     - https://github.com/netty/netty/issues/222
475             if (code >= 100 && code < 200) {
476                 // One exception: Hixie 76 websocket handshake response
477                 return !(code == 101 && !res.headers().contains(HttpHeaders.Names.SEC_WEBSOCKET_ACCEPT)
478                          && res.headers().contains(HttpHeaders.Names.UPGRADE, HttpHeaders.Values.WEBSOCKET, true));
479             }
480 
481             switch (code) {
482                 case 204: case 304:
483                     return true;
484             }
485         }
486         return false;
487     }
488 
489     /**
490      * Returns true if the server switched to a different protocol than HTTP/1.0 or HTTP/1.1, e.g. HTTP/2 or Websocket.
491      * Returns false if the upgrade happened in a different layer, e.g. upgrade from HTTP/1.1 to HTTP/1.1 over TLS.
492      */
493     protected boolean isSwitchingToNonHttp1Protocol(HttpResponse msg) {
494         if (msg.getStatus().code() != HttpResponseStatus.SWITCHING_PROTOCOLS.code()) {
495             return false;
496         }
497         String newProtocol = msg.headers().get(HttpHeaders.Names.UPGRADE);
498         return newProtocol == null ||
499                 !newProtocol.contains(HttpVersion.HTTP_1_0.text()) &&
500                 !newProtocol.contains(HttpVersion.HTTP_1_1.text());
501     }
502 
503     /**
504      * Resets the state of the decoder so that it is ready to decode a new message.
505      * This method is useful for handling a rejected request with {@code Expect: 100-continue} header.
506      */
507     public void reset() {
508         resetRequested = true;
509     }
510 
511     private void resetNow() {
512         HttpMessage message = this.message;
513         this.message = null;
514         name = null;
515         value = null;
516         contentLength = Long.MIN_VALUE;
517         lineParser.reset();
518         headerParser.reset();
519         trailer = null;
520         if (!isDecodingRequest()) {
521             HttpResponse res = (HttpResponse) message;
522             if (res != null && isSwitchingToNonHttp1Protocol(res)) {
523                 currentState = State.UPGRADED;
524                 return;
525             }
526         }
527 
528         resetRequested = false;
529         currentState = State.SKIP_CONTROL_CHARS;
530     }
531 
532     private HttpMessage invalidMessage(ByteBuf in, Exception cause) {
533         currentState = State.BAD_MESSAGE;
534 
535         // Advance the readerIndex so that ByteToMessageDecoder does not complain
536         // when we produced an invalid message without consuming anything.
537         in.skipBytes(in.readableBytes());
538 
539         if (message != null) {
540             message.setDecoderResult(DecoderResult.failure(cause));
541         } else {
542             message = createInvalidMessage();
543             message.setDecoderResult(DecoderResult.failure(cause));
544         }
545 
546         HttpMessage ret = message;
547         message = null;
548         return ret;
549     }
550 
551     private HttpContent invalidChunk(ByteBuf in, Exception cause) {
552         currentState = State.BAD_MESSAGE;
553 
554         // Advance the readerIndex so that ByteToMessageDecoder does not complain
555         // when we produced an invalid message without consuming anything.
556         in.skipBytes(in.readableBytes());
557 
558         HttpContent chunk = new DefaultLastHttpContent(Unpooled.EMPTY_BUFFER);
559         chunk.setDecoderResult(DecoderResult.failure(cause));
560         message = null;
561         trailer = null;
562         return chunk;
563     }
564 
565     private static boolean skipControlCharacters(ByteBuf buffer) {
566         boolean skiped = false;
567         final int wIdx = buffer.writerIndex();
568         int rIdx = buffer.readerIndex();
569         while (wIdx > rIdx) {
570             int c = buffer.getUnsignedByte(rIdx++);
571             if (!Character.isISOControl(c) && !Character.isWhitespace(c)) {
572                 rIdx--;
573                 skiped = true;
574                 break;
575             }
576         }
577         buffer.readerIndex(rIdx);
578         return skiped;
579     }
580 
581     private State readHeaders(ByteBuf buffer) {
582         final HttpMessage message = this.message;
583         final HttpHeaders headers = message.headers();
584 
585         AppendableCharSequence line = headerParser.parse(buffer);
586         if (line == null) {
587             return null;
588         }
589         if (line.length() > 0) {
590             do {
591                 char firstChar = line.charAt(0);
592                 if (name != null && (firstChar == ' ' || firstChar == '\t')) {
593                     //please do not make one line from below code
594                     //as it breaks +XX:OptimizeStringConcat optimization
595                     String trimmedLine = line.toString().trim();
596                     String valueStr = String.valueOf(value);
597                     value = valueStr + ' ' + trimmedLine;
598                 } else {
599                     if (name != null) {
600                         headers.add(name, value);
601                     }
602                     splitHeader(line);
603                 }
604 
605                 line = headerParser.parse(buffer);
606                 if (line == null) {
607                     return null;
608                 }
609             } while (line.length() > 0);
610         }
611 
612         // Add the last header.
613         if (name != null) {
614             headers.add(name, value);
615         }
616         // reset name and value fields
617         name = null;
618         value = null;
619 
620         State nextState;
621 
622         if (isContentAlwaysEmpty(message)) {
623             HttpHeaders.removeTransferEncodingChunked(message);
624             nextState = State.SKIP_CONTROL_CHARS;
625         } else if (HttpHeaders.isTransferEncodingChunked(message)) {
626             nextState = State.READ_CHUNK_SIZE;
627         } else if (contentLength() >= 0) {
628             nextState = State.READ_FIXED_LENGTH_CONTENT;
629         } else {
630             nextState = State.READ_VARIABLE_LENGTH_CONTENT;
631         }
632         return nextState;
633     }
634 
635     private long contentLength() {
636         if (contentLength == Long.MIN_VALUE) {
637             contentLength = HttpHeaders.getContentLength(message, -1);
638         }
639         return contentLength;
640     }
641 
642     private LastHttpContent readTrailingHeaders(ByteBuf buffer) {
643         AppendableCharSequence line = headerParser.parse(buffer);
644         if (line == null) {
645             return null;
646         }
647         CharSequence lastHeader = null;
648         if (line.length() > 0) {
649             LastHttpContent trailer = this.trailer;
650             if (trailer == null) {
651                 trailer = this.trailer = new DefaultLastHttpContent(Unpooled.EMPTY_BUFFER, validateHeaders);
652             }
653             do {
654                 char firstChar = line.charAt(0);
655                 if (lastHeader != null && (firstChar == ' ' || firstChar == '\t')) {
656                     List<String> current = trailer.trailingHeaders().getAll(lastHeader);
657                     if (!current.isEmpty()) {
658                         int lastPos = current.size() - 1;
659                         //please do not make one line from below code
660                         //as it breaks +XX:OptimizeStringConcat optimization
661                         String lineTrimmed = line.toString().trim();
662                         String currentLastPos = current.get(lastPos);
663                         current.set(lastPos, currentLastPos + lineTrimmed);
664                     }
665                 } else {
666                     splitHeader(line);
667                     CharSequence headerName = name;
668                     if (!HttpHeaders.equalsIgnoreCase(HttpHeaders.Names.CONTENT_LENGTH, headerName) &&
669                             !HttpHeaders.equalsIgnoreCase(HttpHeaders.Names.TRANSFER_ENCODING, headerName) &&
670                             !HttpHeaders.equalsIgnoreCase(HttpHeaders.Names.TRAILER, headerName)) {
671                         trailer.trailingHeaders().add(headerName, value);
672                     }
673                     lastHeader = name;
674                     // reset name and value fields
675                     name = null;
676                     value = null;
677                 }
678 
679                 line = headerParser.parse(buffer);
680                 if (line == null) {
681                     return null;
682                 }
683             } while (line.length() > 0);
684 
685             this.trailer = null;
686             return trailer;
687         }
688 
689         return LastHttpContent.EMPTY_LAST_CONTENT;
690     }
691 
692     protected abstract boolean isDecodingRequest();
693     protected abstract HttpMessage createMessage(String[] initialLine) throws Exception;
694     protected abstract HttpMessage createInvalidMessage();
695 
696     private static int getChunkSize(String hex) {
697         hex = hex.trim();
698         for (int i = 0; i < hex.length(); i ++) {
699             char c = hex.charAt(i);
700             if (c == ';' || Character.isWhitespace(c) || Character.isISOControl(c)) {
701                 hex = hex.substring(0, i);
702                 break;
703             }
704         }
705 
706         return Integer.parseInt(hex, 16);
707     }
708 
709     private static String[] splitInitialLine(AppendableCharSequence sb) {
710         int aStart;
711         int aEnd;
712         int bStart;
713         int bEnd;
714         int cStart;
715         int cEnd;
716 
717         aStart = findNonWhitespace(sb, 0);
718         aEnd = findWhitespace(sb, aStart);
719 
720         bStart = findNonWhitespace(sb, aEnd);
721         bEnd = findWhitespace(sb, bStart);
722 
723         cStart = findNonWhitespace(sb, bEnd);
724         cEnd = findEndOfString(sb);
725 
726         return new String[] {
727                 sb.subStringUnsafe(aStart, aEnd),
728                 sb.subStringUnsafe(bStart, bEnd),
729                 cStart < cEnd? sb.subStringUnsafe(cStart, cEnd) : "" };
730     }
731 
732     private void splitHeader(AppendableCharSequence sb) {
733         final int length = sb.length();
734         int nameStart;
735         int nameEnd;
736         int colonEnd;
737         int valueStart;
738         int valueEnd;
739 
740         nameStart = findNonWhitespace(sb, 0);
741         for (nameEnd = nameStart; nameEnd < length; nameEnd ++) {
742             char ch = sb.charAt(nameEnd);
743             if (ch == ':' || Character.isWhitespace(ch)) {
744                 break;
745             }
746         }
747 
748         for (colonEnd = nameEnd; colonEnd < length; colonEnd ++) {
749             if (sb.charAt(colonEnd) == ':') {
750                 colonEnd ++;
751                 break;
752             }
753         }
754 
755         name = sb.subStringUnsafe(nameStart, nameEnd);
756         valueStart = findNonWhitespace(sb, colonEnd);
757         if (valueStart == length) {
758             value = EMPTY_VALUE;
759         } else {
760             valueEnd = findEndOfString(sb);
761             value = sb.subStringUnsafe(valueStart, valueEnd);
762         }
763     }
764 
765     private static int findNonWhitespace(AppendableCharSequence sb, int offset) {
766         for (int result = offset; result < sb.length(); ++result) {
767             if (!Character.isWhitespace(sb.charAtUnsafe(result))) {
768                 return result;
769             }
770         }
771         return sb.length();
772     }
773 
774     private static int findWhitespace(AppendableCharSequence sb, int offset) {
775         for (int result = offset; result < sb.length(); ++result) {
776             if (Character.isWhitespace(sb.charAtUnsafe(result))) {
777                 return result;
778             }
779         }
780         return sb.length();
781     }
782 
783     private static int findEndOfString(AppendableCharSequence sb) {
784         for (int result = sb.length() - 1; result > 0; --result) {
785             if (!Character.isWhitespace(sb.charAtUnsafe(result))) {
786                 return result + 1;
787             }
788         }
789         return 0;
790     }
791 
792     private static class HeaderParser implements ByteBufProcessor {
793         private final AppendableCharSequence seq;
794         private final int maxLength;
795         private int size;
796 
797         HeaderParser(AppendableCharSequence seq, int maxLength) {
798             this.seq = seq;
799             this.maxLength = maxLength;
800         }
801 
802         public AppendableCharSequence parse(ByteBuf buffer) {
803             final int oldSize = size;
804             seq.reset();
805             int i = buffer.forEachByte(this);
806             if (i == -1) {
807                 size = oldSize;
808                 return null;
809             }
810             buffer.readerIndex(i + 1);
811             return seq;
812         }
813 
814         public void reset() {
815             size = 0;
816         }
817 
818         @Override
819         public boolean process(byte value) throws Exception {
820             char nextByte = (char) (value & 0xFF);
821             if (nextByte == HttpConstants.CR) {
822                 return true;
823             }
824             if (nextByte == HttpConstants.LF) {
825                 return false;
826             }
827 
828             if (++ size > maxLength) {
829                 // TODO: Respond with Bad Request and discard the traffic
830                 //    or close the connection.
831                 //       No need to notify the upstream handlers - just log.
832                 //       If decoding a response, just throw an exception.
833                 throw newException(maxLength);
834             }
835 
836             seq.append(nextByte);
837             return true;
838         }
839 
840         protected TooLongFrameException newException(int maxLength) {
841             return new TooLongFrameException("HTTP header is larger than " + maxLength + " bytes.");
842         }
843     }
844 
845     private static final class LineParser extends HeaderParser {
846 
847         LineParser(AppendableCharSequence seq, int maxLength) {
848             super(seq, maxLength);
849         }
850 
851         @Override
852         public AppendableCharSequence parse(ByteBuf buffer) {
853             reset();
854             return super.parse(buffer);
855         }
856 
857         @Override
858         protected TooLongFrameException newException(int maxLength) {
859             return new TooLongFrameException("An HTTP line is larger than " + maxLength + " bytes.");
860         }
861     }
862 }