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.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.ByteProcessor;
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 if a
250                  * request does not have either a transfer-encoding or a content-length header then the message body
251                  * length is 0. However for a response the body length is the number of octets received prior to the
252                  * server closing the connection. So we treat this as variable length chunked encoding.
253                  */
254                 long contentLength = contentLength();
255                 if (contentLength == 0 || contentLength == -1 && isDecodingRequest()) {
256                     out.add(message);
257                     out.add(LastHttpContent.EMPTY_LAST_CONTENT);
258                     resetNow();
259                     return;
260                 }
261 
262                 assert nextState == State.READ_FIXED_LENGTH_CONTENT ||
263                         nextState == State.READ_VARIABLE_LENGTH_CONTENT;
264 
265                 out.add(message);
266 
267                 if (nextState == State.READ_FIXED_LENGTH_CONTENT) {
268                     // chunkSize will be decreased as the READ_FIXED_LENGTH_CONTENT state reads data chunk by chunk.
269                     chunkSize = contentLength;
270                 }
271 
272                 // We return here, this forces decode to be called again where we will decode the content
273                 return;
274             }
275         } catch (Exception e) {
276             out.add(invalidMessage(buffer, e));
277             return;
278         }
279         case READ_VARIABLE_LENGTH_CONTENT: {
280             // Keep reading data as a chunk until the end of connection is reached.
281             int toRead = Math.min(buffer.readableBytes(), maxChunkSize);
282             if (toRead > 0) {
283                 ByteBuf content = buffer.readRetainedSlice(toRead);
284                 out.add(new DefaultHttpContent(content));
285             }
286             return;
287         }
288         case READ_FIXED_LENGTH_CONTENT: {
289             int readLimit = buffer.readableBytes();
290 
291             // Check if the buffer is readable first as we use the readable byte count
292             // to create the HttpChunk. This is needed as otherwise we may end up with
293             // create a HttpChunk instance that contains an empty buffer and so is
294             // handled like it is the last HttpChunk.
295             //
296             // See https://github.com/netty/netty/issues/433
297             if (readLimit == 0) {
298                 return;
299             }
300 
301             int toRead = Math.min(readLimit, maxChunkSize);
302             if (toRead > chunkSize) {
303                 toRead = (int) chunkSize;
304             }
305             ByteBuf content = buffer.readRetainedSlice(toRead);
306             chunkSize -= toRead;
307 
308             if (chunkSize == 0) {
309                 // Read all content.
310                 out.add(new DefaultLastHttpContent(content, validateHeaders));
311                 resetNow();
312             } else {
313                 out.add(new DefaultHttpContent(content));
314             }
315             return;
316         }
317         /**
318          * everything else after this point takes care of reading chunked content. basically, read chunk size,
319          * read chunk, read and ignore the CRLF and repeat until 0
320          */
321         case READ_CHUNK_SIZE: try {
322             AppendableCharSequence line = lineParser.parse(buffer);
323             if (line == null) {
324                 return;
325             }
326             int chunkSize = getChunkSize(line.toString());
327             this.chunkSize = chunkSize;
328             if (chunkSize == 0) {
329                 currentState = State.READ_CHUNK_FOOTER;
330                 return;
331             }
332             currentState = State.READ_CHUNKED_CONTENT;
333             // fall-through
334         } catch (Exception e) {
335             out.add(invalidChunk(buffer, e));
336             return;
337         }
338         case READ_CHUNKED_CONTENT: {
339             assert chunkSize <= Integer.MAX_VALUE;
340             int toRead = Math.min((int) chunkSize, maxChunkSize);
341             toRead = Math.min(toRead, buffer.readableBytes());
342             if (toRead == 0) {
343                 return;
344             }
345             HttpContent chunk = new DefaultHttpContent(buffer.readRetainedSlice(toRead));
346             chunkSize -= toRead;
347 
348             out.add(chunk);
349 
350             if (chunkSize != 0) {
351                 return;
352             }
353             currentState = State.READ_CHUNK_DELIMITER;
354             // fall-through
355         }
356         case READ_CHUNK_DELIMITER: {
357             final int wIdx = buffer.writerIndex();
358             int rIdx = buffer.readerIndex();
359             while (wIdx > rIdx) {
360                 byte next = buffer.getByte(rIdx++);
361                 if (next == HttpConstants.LF) {
362                     currentState = State.READ_CHUNK_SIZE;
363                     break;
364                 }
365             }
366             buffer.readerIndex(rIdx);
367             return;
368         }
369         case READ_CHUNK_FOOTER: try {
370             LastHttpContent trailer = readTrailingHeaders(buffer);
371             if (trailer == null) {
372                 return;
373             }
374             out.add(trailer);
375             resetNow();
376             return;
377         } catch (Exception e) {
378             out.add(invalidChunk(buffer, e));
379             return;
380         }
381         case BAD_MESSAGE: {
382             // Keep discarding until disconnection.
383             buffer.skipBytes(buffer.readableBytes());
384             break;
385         }
386         case UPGRADED: {
387             int readableBytes = buffer.readableBytes();
388             if (readableBytes > 0) {
389                 // Keep on consuming as otherwise we may trigger an DecoderException,
390                 // other handler will replace this codec with the upgraded protocol codec to
391                 // take the traffic over at some point then.
392                 // See https://github.com/netty/netty/issues/2173
393                 out.add(buffer.readBytes(readableBytes));
394             }
395             break;
396         }
397         }
398     }
399 
400     @Override
401     protected void decodeLast(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
402         super.decodeLast(ctx, in, out);
403 
404         if (resetRequested) {
405             // If a reset was requested by decodeLast() we need to do it now otherwise we may produce a
406             // LastHttpContent while there was already one.
407             resetNow();
408         }
409         // Handle the last unfinished message.
410         if (message != null) {
411             boolean chunked = HttpUtil.isTransferEncodingChunked(message);
412             if (currentState == State.READ_VARIABLE_LENGTH_CONTENT && !in.isReadable() && !chunked) {
413                 // End of connection.
414                 out.add(LastHttpContent.EMPTY_LAST_CONTENT);
415                 resetNow();
416                 return;
417             }
418 
419             if (currentState == State.READ_HEADER) {
420                 // If we are still in the state of reading headers we need to create a new invalid message that
421                 // signals that the connection was closed before we received the headers.
422                 out.add(invalidMessage(Unpooled.EMPTY_BUFFER,
423                         new PrematureChannelClosureException("Connection closed before received headers")));
424                 resetNow();
425                 return;
426             }
427 
428             // Check if the closure of the connection signifies the end of the content.
429             boolean prematureClosure;
430             if (isDecodingRequest() || chunked) {
431                 // The last request did not wait for a response.
432                 prematureClosure = true;
433             } else {
434                 // Compare the length of the received content and the 'Content-Length' header.
435                 // If the 'Content-Length' header is absent, the length of the content is determined by the end of the
436                 // connection, so it is perfectly fine.
437                 prematureClosure = contentLength() > 0;
438             }
439 
440             if (!prematureClosure) {
441                 out.add(LastHttpContent.EMPTY_LAST_CONTENT);
442             }
443             resetNow();
444         }
445     }
446 
447     @Override
448     public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
449         if (evt instanceof HttpExpectationFailedEvent) {
450             switch (currentState) {
451             case READ_FIXED_LENGTH_CONTENT:
452             case READ_VARIABLE_LENGTH_CONTENT:
453             case READ_CHUNK_SIZE:
454                 reset();
455                 break;
456             default:
457                 break;
458             }
459         }
460         super.userEventTriggered(ctx, evt);
461     }
462 
463     protected boolean isContentAlwaysEmpty(HttpMessage msg) {
464         if (msg instanceof HttpResponse) {
465             HttpResponse res = (HttpResponse) msg;
466             int code = res.status().code();
467 
468             // Correctly handle return codes of 1xx.
469             //
470             // See:
471             //     - http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html Section 4.4
472             //     - https://github.com/netty/netty/issues/222
473             if (code >= 100 && code < 200) {
474                 // One exception: Hixie 76 websocket handshake response
475                 return !(code == 101 && !res.headers().contains(HttpHeaderNames.SEC_WEBSOCKET_ACCEPT)
476                          && res.headers().contains(HttpHeaderNames.UPGRADE, HttpHeaderValues.WEBSOCKET, true));
477             }
478 
479             switch (code) {
480             case 204: case 304:
481                 return true;
482             }
483         }
484         return false;
485     }
486 
487     /**
488      * Returns true if the server switched to a different protocol than HTTP/1.0 or HTTP/1.1, e.g. HTTP/2 or Websocket.
489      * Returns false if the upgrade happened in a different layer, e.g. upgrade from HTTP/1.1 to HTTP/1.1 over TLS.
490      */
491     protected boolean isSwitchingToNonHttp1Protocol(HttpResponse msg) {
492         if (msg.status().code() != HttpResponseStatus.SWITCHING_PROTOCOLS.code()) {
493             return false;
494         }
495         String newProtocol = msg.headers().get(HttpHeaderNames.UPGRADE);
496         return newProtocol == null ||
497                 !newProtocol.contains(HttpVersion.HTTP_1_0.text()) &&
498                 !newProtocol.contains(HttpVersion.HTTP_1_1.text());
499     }
500 
501     /**
502      * Resets the state of the decoder so that it is ready to decode a new message.
503      * This method is useful for handling a rejected request with {@code Expect: 100-continue} header.
504      */
505     public void reset() {
506         resetRequested = true;
507     }
508 
509     private void resetNow() {
510         HttpMessage message = this.message;
511         this.message = null;
512         name = null;
513         value = null;
514         contentLength = Long.MIN_VALUE;
515         lineParser.reset();
516         headerParser.reset();
517         trailer = null;
518         if (!isDecodingRequest()) {
519             HttpResponse res = (HttpResponse) message;
520             if (res != null && isSwitchingToNonHttp1Protocol(res)) {
521                 currentState = State.UPGRADED;
522                 return;
523             }
524         }
525 
526         resetRequested = false;
527         currentState = State.SKIP_CONTROL_CHARS;
528     }
529 
530     private HttpMessage invalidMessage(ByteBuf in, Exception cause) {
531         currentState = State.BAD_MESSAGE;
532 
533         // Advance the readerIndex so that ByteToMessageDecoder does not complain
534         // when we produced an invalid message without consuming anything.
535         in.skipBytes(in.readableBytes());
536 
537         if (message != null) {
538             message.setDecoderResult(DecoderResult.failure(cause));
539         } else {
540             message = createInvalidMessage();
541             message.setDecoderResult(DecoderResult.failure(cause));
542         }
543 
544         HttpMessage ret = message;
545         message = null;
546         return ret;
547     }
548 
549     private HttpContent invalidChunk(ByteBuf in, Exception cause) {
550         currentState = State.BAD_MESSAGE;
551 
552         // Advance the readerIndex so that ByteToMessageDecoder does not complain
553         // when we produced an invalid message without consuming anything.
554         in.skipBytes(in.readableBytes());
555 
556         HttpContent chunk = new DefaultLastHttpContent(Unpooled.EMPTY_BUFFER);
557         chunk.setDecoderResult(DecoderResult.failure(cause));
558         message = null;
559         trailer = null;
560         return chunk;
561     }
562 
563     private static boolean skipControlCharacters(ByteBuf buffer) {
564         boolean skiped = false;
565         final int wIdx = buffer.writerIndex();
566         int rIdx = buffer.readerIndex();
567         while (wIdx > rIdx) {
568             int c = buffer.getUnsignedByte(rIdx++);
569             if (!Character.isISOControl(c) && !Character.isWhitespace(c)) {
570                 rIdx--;
571                 skiped = true;
572                 break;
573             }
574         }
575         buffer.readerIndex(rIdx);
576         return skiped;
577     }
578 
579     private State readHeaders(ByteBuf buffer) {
580         final HttpMessage message = this.message;
581         final HttpHeaders headers = message.headers();
582 
583         AppendableCharSequence line = headerParser.parse(buffer);
584         if (line == null) {
585             return null;
586         }
587         if (line.length() > 0) {
588             do {
589                 char firstChar = line.charAt(0);
590                 if (name != null && (firstChar == ' ' || firstChar == '\t')) {
591                     //please do not make one line from below code
592                     //as it breaks +XX:OptimizeStringConcat optimization
593                     String trimmedLine = line.toString().trim();
594                     String valueStr = String.valueOf(value);
595                     value = valueStr + ' ' + trimmedLine;
596                 } else {
597                     if (name != null) {
598                         headers.add(name, value);
599                     }
600                     splitHeader(line);
601                 }
602 
603                 line = headerParser.parse(buffer);
604                 if (line == null) {
605                     return null;
606                 }
607             } while (line.length() > 0);
608         }
609 
610         // Add the last header.
611         if (name != null) {
612             headers.add(name, value);
613         }
614         // reset name and value fields
615         name = null;
616         value = null;
617 
618         State nextState;
619 
620         if (isContentAlwaysEmpty(message)) {
621             HttpUtil.setTransferEncodingChunked(message, false);
622             nextState = State.SKIP_CONTROL_CHARS;
623         } else if (HttpUtil.isTransferEncodingChunked(message)) {
624             nextState = State.READ_CHUNK_SIZE;
625         } else if (contentLength() >= 0) {
626             nextState = State.READ_FIXED_LENGTH_CONTENT;
627         } else {
628             nextState = State.READ_VARIABLE_LENGTH_CONTENT;
629         }
630         return nextState;
631     }
632 
633     private long contentLength() {
634         if (contentLength == Long.MIN_VALUE) {
635             contentLength = HttpUtil.getContentLength(message, -1L);
636         }
637         return contentLength;
638     }
639 
640     private LastHttpContent readTrailingHeaders(ByteBuf buffer) {
641         AppendableCharSequence line = headerParser.parse(buffer);
642         if (line == null) {
643             return null;
644         }
645         CharSequence lastHeader = null;
646         if (line.length() > 0) {
647             LastHttpContent trailer = this.trailer;
648             if (trailer == null) {
649                 trailer = this.trailer = new DefaultLastHttpContent(Unpooled.EMPTY_BUFFER, validateHeaders);
650             }
651             do {
652                 char firstChar = line.charAt(0);
653                 if (lastHeader != null && (firstChar == ' ' || firstChar == '\t')) {
654                     List<String> current = trailer.trailingHeaders().getAll(lastHeader);
655                     if (!current.isEmpty()) {
656                         int lastPos = current.size() - 1;
657                         //please do not make one line from below code
658                         //as it breaks +XX:OptimizeStringConcat optimization
659                         String lineTrimmed = line.toString().trim();
660                         String currentLastPos = current.get(lastPos);
661                         current.set(lastPos, currentLastPos + lineTrimmed);
662                     }
663                 } else {
664                     splitHeader(line);
665                     CharSequence headerName = name;
666                     if (!HttpHeaderNames.CONTENT_LENGTH.contentEqualsIgnoreCase(headerName) &&
667                         !HttpHeaderNames.TRANSFER_ENCODING.contentEqualsIgnoreCase(headerName) &&
668                         !HttpHeaderNames.TRAILER.contentEqualsIgnoreCase(headerName)) {
669                         trailer.trailingHeaders().add(headerName, value);
670                     }
671                     lastHeader = name;
672                     // reset name and value fields
673                     name = null;
674                     value = null;
675                 }
676 
677                 line = headerParser.parse(buffer);
678                 if (line == null) {
679                     return null;
680                 }
681             } while (line.length() > 0);
682 
683             this.trailer = null;
684             return trailer;
685         }
686 
687         return LastHttpContent.EMPTY_LAST_CONTENT;
688     }
689 
690     protected abstract boolean isDecodingRequest();
691     protected abstract HttpMessage createMessage(String[] initialLine) throws Exception;
692     protected abstract HttpMessage createInvalidMessage();
693 
694     private static int getChunkSize(String hex) {
695         hex = hex.trim();
696         for (int i = 0; i < hex.length(); i ++) {
697             char c = hex.charAt(i);
698             if (c == ';' || Character.isWhitespace(c) || Character.isISOControl(c)) {
699                 hex = hex.substring(0, i);
700                 break;
701             }
702         }
703 
704         return Integer.parseInt(hex, 16);
705     }
706 
707     private static String[] splitInitialLine(AppendableCharSequence sb) {
708         int aStart;
709         int aEnd;
710         int bStart;
711         int bEnd;
712         int cStart;
713         int cEnd;
714 
715         aStart = findNonWhitespace(sb, 0);
716         aEnd = findWhitespace(sb, aStart);
717 
718         bStart = findNonWhitespace(sb, aEnd);
719         bEnd = findWhitespace(sb, bStart);
720 
721         cStart = findNonWhitespace(sb, bEnd);
722         cEnd = findEndOfString(sb);
723 
724         return new String[] {
725                 sb.subStringUnsafe(aStart, aEnd),
726                 sb.subStringUnsafe(bStart, bEnd),
727                 cStart < cEnd? sb.subStringUnsafe(cStart, cEnd) : "" };
728     }
729 
730     private void splitHeader(AppendableCharSequence sb) {
731         final int length = sb.length();
732         int nameStart;
733         int nameEnd;
734         int colonEnd;
735         int valueStart;
736         int valueEnd;
737 
738         nameStart = findNonWhitespace(sb, 0);
739         for (nameEnd = nameStart; nameEnd < length; nameEnd ++) {
740             char ch = sb.charAt(nameEnd);
741             if (ch == ':' || Character.isWhitespace(ch)) {
742                 break;
743             }
744         }
745 
746         for (colonEnd = nameEnd; colonEnd < length; colonEnd ++) {
747             if (sb.charAt(colonEnd) == ':') {
748                 colonEnd ++;
749                 break;
750             }
751         }
752 
753         name = sb.subStringUnsafe(nameStart, nameEnd);
754         valueStart = findNonWhitespace(sb, colonEnd);
755         if (valueStart == length) {
756             value = EMPTY_VALUE;
757         } else {
758             valueEnd = findEndOfString(sb);
759             value = sb.subStringUnsafe(valueStart, valueEnd);
760         }
761     }
762 
763     private static int findNonWhitespace(AppendableCharSequence sb, int offset) {
764         for (int result = offset; result < sb.length(); ++result) {
765             if (!Character.isWhitespace(sb.charAtUnsafe(result))) {
766                 return result;
767             }
768         }
769         return sb.length();
770     }
771 
772     private static int findWhitespace(AppendableCharSequence sb, int offset) {
773         for (int result = offset; result < sb.length(); ++result) {
774             if (Character.isWhitespace(sb.charAtUnsafe(result))) {
775                 return result;
776             }
777         }
778         return sb.length();
779     }
780 
781     private static int findEndOfString(AppendableCharSequence sb) {
782         for (int result = sb.length() - 1; result > 0; --result) {
783             if (!Character.isWhitespace(sb.charAtUnsafe(result))) {
784                 return result + 1;
785             }
786         }
787         return 0;
788     }
789 
790     private static class HeaderParser implements ByteProcessor {
791         private final AppendableCharSequence seq;
792         private final int maxLength;
793         private int size;
794 
795         HeaderParser(AppendableCharSequence seq, int maxLength) {
796             this.seq = seq;
797             this.maxLength = maxLength;
798         }
799 
800         public AppendableCharSequence parse(ByteBuf buffer) {
801             final int oldSize = size;
802             seq.reset();
803             int i = buffer.forEachByte(this);
804             if (i == -1) {
805                 size = oldSize;
806                 return null;
807             }
808             buffer.readerIndex(i + 1);
809             return seq;
810         }
811 
812         public void reset() {
813             size = 0;
814         }
815 
816         @Override
817         public boolean process(byte value) throws Exception {
818             char nextByte = (char) (value & 0xFF);
819             if (nextByte == HttpConstants.CR) {
820                 return true;
821             }
822             if (nextByte == HttpConstants.LF) {
823                 return false;
824             }
825 
826             if (++ size > maxLength) {
827                 // TODO: Respond with Bad Request and discard the traffic
828                 //    or close the connection.
829                 //       No need to notify the upstream handlers - just log.
830                 //       If decoding a response, just throw an exception.
831                 throw newException(maxLength);
832             }
833 
834             seq.append(nextByte);
835             return true;
836         }
837 
838         protected TooLongFrameException newException(int maxLength) {
839             return new TooLongFrameException("HTTP header is larger than " + maxLength + " bytes.");
840         }
841     }
842 
843     private static final class LineParser extends HeaderParser {
844 
845         LineParser(AppendableCharSequence seq, int maxLength) {
846             super(seq, maxLength);
847         }
848 
849         @Override
850         public AppendableCharSequence parse(ByteBuf buffer) {
851             reset();
852             return super.parse(buffer);
853         }
854 
855         @Override
856         protected TooLongFrameException newException(int maxLength) {
857             return new TooLongFrameException("An HTTP line is larger than " + maxLength + " bytes.");
858         }
859     }
860 }