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 org.jboss.netty.handler.codec.http;
    17
    18  import java.util.List;
    19
    20  import org.jboss.netty.buffer.ChannelBuffer;
    21  import org.jboss.netty.buffer.ChannelBuffers;
    22  import org.jboss.netty.channel.Channel;
    23  import org.jboss.netty.channel.ChannelHandlerContext;
    24  import org.jboss.netty.channel.ChannelPipeline;
    25  import org.jboss.netty.handler.codec.frame.TooLongFrameException;
    26  import org.jboss.netty.handler.codec.replay.ReplayingDecoder;
    27
    28  /**
    29   * Decodes {@link ChannelBuffer}s into {@link HttpMessage}s and
    30   * {@link HttpChunk}s.
    31   *
    32   * <h3>Parameters that prevents excessive memory consumption</h3>
    33   * <table border="1">
    34   * <tr>
    35   * <th>Name</th><th>Meaning</th>
    36   * </tr>
    37   * <tr>
    38   * <td>{@code maxInitialLineLength}</td>
    39   * <td>The maximum length of the initial line
    40   *     (e.g. {@code "GET / HTTP/1.0"} or {@code "HTTP/1.0 200 OK"})
    41   *     If the length of the initial line exceeds this value, a
    42   *     {@link TooLongFrameException} will be raised.</td>
    43   * </tr>
    44   * <tr>
    45   * <td>{@code maxHeaderSize}</td>
    46   * <td>The maximum length of all headers.  If the sum of the length of each
    47   *     header exceeds this value, a {@link TooLongFrameException} will be raised.</td>
    48   * </tr>
    49   * <tr>
    50   * <td>{@code maxChunkSize}</td>
    51   * <td>The maximum length of the content or each chunk.  If the content length
    52   *     (or the length of each chunk) exceeds this value, the content or chunk
    53   *     will be split into multiple {@link HttpChunk}s whose length is
    54   *     {@code maxChunkSize} at maximum.</td>
    55   * </tr>
    56   * </table>
    57   *
    58   * <h3>Chunked Content</h3>
    59   *
    60   * If the content of an HTTP message is greater than {@code maxChunkSize} or
    61   * the transfer encoding of the HTTP message is 'chunked', this decoder
    62   * generates one {@link HttpMessage} instance and its following
    63   * {@link HttpChunk}s per single HTTP message to avoid excessive memory
    64   * consumption. For example, the following HTTP message:
    65   * <pre>
    66   * GET / HTTP/1.1
    67   * Transfer-Encoding: chunked
    68   *
    69   * 1a
    70   * abcdefghijklmnopqrstuvwxyz
    71   * 10
    72   * 1234567890abcdef
    73   * 0
    74   * Content-MD5: ...
    75   * <i>[blank line]</i>
    76   * </pre>
    77   * triggers {@link HttpRequestDecoder} to generate 4 objects:
    78   * <ol>
    79   * <li>An {@link HttpRequest} whose {@link HttpMessage#isChunked() chunked}
    80   *     property is {@code true},</li>
    81   * <li>The first {@link HttpChunk} whose content is {@code 'abcdefghijklmnopqrstuvwxyz'},</li>
    82   * <li>The second {@link HttpChunk} whose content is {@code '1234567890abcdef'}, and</li>
    83   * <li>An {@link HttpChunkTrailer} which marks the end of the content.</li>
    84   * </ol>
    85   *
    86   * If you prefer not to handle {@link HttpChunk}s by yourself for your
    87   * convenience, insert {@link HttpChunkAggregator} after this decoder in the
    88   * {@link ChannelPipeline}.  However, please note that your server might not
    89   * be as memory efficient as without the aggregator.
    90   *
    91   * <h3>Extensibility</h3>
    92   *
    93   * Please note that this decoder is designed to be extended to implement
    94   * a protocol derived from HTTP, such as
    95   * <a href="http://en.wikipedia.org/wiki/Real_Time_Streaming_Protocol">RTSP</a> and
    96   * <a href="http://en.wikipedia.org/wiki/Internet_Content_Adaptation_Protocol">ICAP</a>.
    97   * To implement the decoder of such a derived protocol, extend this class and
    98   * implement all abstract methods properly.
    99   * @apiviz.landmark
    100  */
    101 public abstract class HttpMessageDecoder extends ReplayingDecoder<HttpMessageDecoder.State> {
    102
    103     private final int maxInitialLineLength;
    104     private final int maxHeaderSize;
    105     private final int maxChunkSize;
    106     private HttpMessage message;
    107     private ChannelBuffer content;
    108     private long chunkSize;
    109     private int headerSize;
    110     private int contentRead;
    111
    112     /**
    113      * The internal state of {@link HttpMessageDecoder}.
    114      * <em>Internal use only</em>.
    115      * @apiviz.exclude
    116      */
    117     protected static enum State {
    118         SKIP_CONTROL_CHARS,
    119         READ_INITIAL,
    120         READ_HEADER,
    121         READ_VARIABLE_LENGTH_CONTENT,
    122         READ_VARIABLE_LENGTH_CONTENT_AS_CHUNKS,
    123         READ_FIXED_LENGTH_CONTENT,
    124         READ_FIXED_LENGTH_CONTENT_AS_CHUNKS,
    125         READ_CHUNK_SIZE,
    126         READ_CHUNKED_CONTENT,
    127         READ_CHUNKED_CONTENT_AS_CHUNKS,
    128         READ_CHUNK_DELIMITER,
    129         READ_CHUNK_FOOTER;
    130     }
    131
    132     /**
    133      * Creates a new instance with the default
    134      * {@code maxInitialLineLength (4096}}, {@code maxHeaderSize (8192)}, and
    135      * {@code maxChunkSize (8192)}.
    136      */
    137     protected HttpMessageDecoder() {
    138         this(4096, 8192, 8192);
    139     }
    140
    141     /**
    142      * Creates a new instance with the specified parameters.
    143      */
    144     protected HttpMessageDecoder(
    145             int maxInitialLineLength, int maxHeaderSize, int maxChunkSize) {
    146
    147         super(State.SKIP_CONTROL_CHARS, true);
    148
    149         if (maxInitialLineLength <= 0) {
    150             throw new IllegalArgumentException(
    151                     "maxInitialLineLength must be a positive integer: " +
    152                     maxInitialLineLength);
    153         }
    154         if (maxHeaderSize <= 0) {
    155             throw new IllegalArgumentException(
    156                     "maxHeaderSize must be a positive integer: " +
    157                     maxHeaderSize);
    158         }
    159         if (maxChunkSize < 0) {
    160             throw new IllegalArgumentException(
    161                     "maxChunkSize must be a positive integer: " +
    162                     maxChunkSize);
    163         }
    164         this.maxInitialLineLength = maxInitialLineLength;
    165         this.maxHeaderSize = maxHeaderSize;
    166         this.maxChunkSize = maxChunkSize;
    167     }
    168
    169     @Override
    170     protected Object decode(
    171             ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer, State state) throws Exception {
    172         switch (state) {
    173         case SKIP_CONTROL_CHARS: {
    174             try {
    175                 skipControlCharacters(buffer);
    176                 checkpoint(State.READ_INITIAL);
    177             } finally {
    178                 checkpoint();
    179             }
    180         }
    181         case READ_INITIAL: {
    182             String[] initialLine = splitInitialLine(readLine(buffer, maxInitialLineLength));
    183             if (initialLine.length < 3) {
    184                 // Invalid initial line - ignore.
    185                 checkpoint(State.SKIP_CONTROL_CHARS);
    186                 return null;
    187             }
    188
    189             message = createMessage(initialLine);
    190             checkpoint(State.READ_HEADER);
    191         }
    192         case READ_HEADER: {
    193             State nextState = readHeaders(buffer);
    194             checkpoint(nextState);
    195             if (nextState == State.READ_CHUNK_SIZE) {
    196                 // Chunked encoding
    197                 message.setChunked(true);
    198                 // Generate HttpMessage first.  HttpChunks will follow.
    199                 return message;
    200             } else if (nextState == State.SKIP_CONTROL_CHARS) {
    201                 // No content is expected.
    202                 // Remove the headers which are not supposed to be present not
    203                 // to confuse subsequent handlers.
    204                 message.removeHeader(HttpHeaders.Names.TRANSFER_ENCODING);
    205                 return message;
    206             } else {
    207                 long contentLength = HttpHeaders.getContentLength(message, -1);
    208                 if (contentLength == 0 || contentLength == -1 && isDecodingRequest()) {
    209                     content = ChannelBuffers.EMPTY_BUFFER;
    210                     return reset();
    211                 }
    212
    213                 switch (nextState) {
    214                 case READ_FIXED_LENGTH_CONTENT:
    215                     if (contentLength > maxChunkSize || HttpHeaders.is100ContinueExpected(message)) {
    216                         // Generate HttpMessage first.  HttpChunks will follow.
    217                         checkpoint(State.READ_FIXED_LENGTH_CONTENT_AS_CHUNKS);
    218                         message.setChunked(true);
    219                         // chunkSize will be decreased as the READ_FIXED_LENGTH_CONTENT_AS_CHUNKS
    220                         // state reads data chunk by chunk.
    221                         chunkSize = HttpHeaders.getContentLength(message, -1);
    222                         return message;
    223                     }
    224                     break;
    225                 case READ_VARIABLE_LENGTH_CONTENT:
    226                     if (buffer.readableBytes() > maxChunkSize || HttpHeaders.is100ContinueExpected(message)) {
    227                         // Generate HttpMessage first.  HttpChunks will follow.
    228                         checkpoint(State.READ_VARIABLE_LENGTH_CONTENT_AS_CHUNKS);
    229                         message.setChunked(true);
    230                         return message;
    231                     }
    232                     break;
    233                 default:
    234                     throw new IllegalStateException("Unexpected state: " + nextState);
    235                 }
    236             }
    237             // We return null here, this forces decode to be called again where we will decode the content
    238             return null;
    239         }
    240         case READ_VARIABLE_LENGTH_CONTENT: {
    241             int toRead = actualReadableBytes();
    242             if (toRead > maxChunkSize) {
    243                 toRead = maxChunkSize;
    244             }
    245             if (!message.isChunked()) {
    246                 message.setChunked(true);
    247                 return new Object[] {message, new DefaultHttpChunk(buffer.readBytes(toRead))};
    248             } else {
    249                 return new DefaultHttpChunk(buffer.readBytes(toRead));
    250             }
    251         }
    252         case READ_VARIABLE_LENGTH_CONTENT_AS_CHUNKS: {
    253             // Keep reading data as a chunk until the end of connection is reached.
    254             int toRead = actualReadableBytes();
    255             if (toRead > maxChunkSize) {
    256                 toRead = maxChunkSize;
    257             }
    258             HttpChunk chunk = new DefaultHttpChunk(buffer.readBytes(toRead));
    259
    260             if (!buffer.readable()) {
    261                 // Reached to the end of the connection.
    262                 reset();
    263                 if (!chunk.isLast()) {
    264                     // Append the last chunk.
    265                     return new Object[] { chunk, HttpChunk.LAST_CHUNK };
    266                 }
    267             }
    268             return chunk;
    269         }
    270         case READ_FIXED_LENGTH_CONTENT: {
    271             return readFixedLengthContent(buffer);
    272         }
    273         case READ_FIXED_LENGTH_CONTENT_AS_CHUNKS: {
    274             assert chunkSize <= Integer.MAX_VALUE;
    275             int chunkSize = (int) this.chunkSize;
    276             int readLimit = actualReadableBytes();
    277
    278             // Check if the buffer is readable first as we use the readable byte count
    279             // to create the HttpChunk. This is needed as otherwise we may end up with
    280             // create a HttpChunk instance that contains an empty buffer and so is
    281             // handled like it is the last HttpChunk.
    282             //
    283             // See https://github.com/netty/netty/issues/433
    284             if (readLimit == 0) {
    285                 return null;
    286             }
    287
    288             int toRead = chunkSize;
    289             if (toRead > maxChunkSize) {
    290                 toRead = maxChunkSize;
    291             }
    292             if (toRead > readLimit) {
    293                 toRead = readLimit;
    294             }
    295             HttpChunk chunk = new DefaultHttpChunk(buffer.readBytes(toRead));
    296             if (chunkSize > toRead) {
    297                 chunkSize -= toRead;
    298             } else {
    299                 chunkSize = 0;
    300             }
    301             this.chunkSize = chunkSize;
    302
    303             if (chunkSize == 0) {
    304                 // Read all content.
    305                 reset();
    306                 if (!chunk.isLast()) {
    307                     // Append the last chunk.
    308                     return new Object[] { chunk, HttpChunk.LAST_CHUNK };
    309                 }
    310             }
    311             return chunk;
    312         }
    313         /**
    314          * everything else after this point takes care of reading chunked content. basically, read chunk size,
    315          * read chunk, read and ignore the CRLF and repeat until 0
    316          */
    317         case READ_CHUNK_SIZE: {
    318             String line = readLine(buffer, maxInitialLineLength);
    319             int chunkSize = getChunkSize(line);
    320             this.chunkSize = chunkSize;
    321             if (chunkSize == 0) {
    322                 checkpoint(State.READ_CHUNK_FOOTER);
    323                 return null;
    324             } else if (chunkSize > maxChunkSize) {
    325                 // A chunk is too large. Split them into multiple chunks again.
    326                 checkpoint(State.READ_CHUNKED_CONTENT_AS_CHUNKS);
    327             } else {
    328                 checkpoint(State.READ_CHUNKED_CONTENT);
    329             }
    330         }
    331         case READ_CHUNKED_CONTENT: {
    332             assert chunkSize <= Integer.MAX_VALUE;
    333             HttpChunk chunk = new DefaultHttpChunk(buffer.readBytes((int) chunkSize));
    334             checkpoint(State.READ_CHUNK_DELIMITER);
    335             return chunk;
    336         }
    337         case READ_CHUNKED_CONTENT_AS_CHUNKS: {
    338             assert chunkSize <= Integer.MAX_VALUE;
    339             int chunkSize = (int) this.chunkSize;
    340             int readLimit = actualReadableBytes();
    341
    342             // Check if the buffer is readable first as we use the readable byte count
    343             // to create the HttpChunk. This is needed as otherwise we may end up with
    344             // create a HttpChunk instance that contains an empty buffer and so is
    345             // handled like it is the last HttpChunk.
    346             //
    347             // See https://github.com/netty/netty/issues/433
    348             if (readLimit == 0) {
    349                 return null;
    350             }
    351
    352             int toRead = chunkSize;
    353             if (toRead > maxChunkSize) {
    354                 toRead = maxChunkSize;
    355             }
    356             if (toRead > readLimit) {
    357                 toRead = readLimit;
    358             }
    359             HttpChunk chunk = new DefaultHttpChunk(buffer.readBytes(toRead));
    360             if (chunkSize > toRead) {
    361                 chunkSize -= toRead;
    362             } else {
    363                 chunkSize = 0;
    364             }
    365             this.chunkSize = chunkSize;
    366
    367             if (chunkSize == 0) {
    368                 // Read all content.
    369                 checkpoint(State.READ_CHUNK_DELIMITER);
    370             }
    371
    372             if (!chunk.isLast()) {
    373                 return chunk;
    374             }
    375         }
    376         case READ_CHUNK_DELIMITER: {
    377             for (;;) {
    378                 byte next = buffer.readByte();
    379                 if (next == HttpConstants.CR) {
    380                     if (buffer.readByte() == HttpConstants.LF) {
    381                         checkpoint(State.READ_CHUNK_SIZE);
    382                         return null;
    383                     }
    384                 } else if (next == HttpConstants.LF) {
    385                     checkpoint(State.READ_CHUNK_SIZE);
    386                     return null;
    387                 }
    388             }
    389         }
    390         case READ_CHUNK_FOOTER: {
    391             HttpChunkTrailer trailer = readTrailingHeaders(buffer);
    392             if (maxChunkSize == 0) {
    393                 // Chunked encoding disabled.
    394                 return reset();
    395             } else {
    396                 reset();
    397                 // The last chunk, which is empty
    398                 return trailer;
    399             }
    400         }
    401         default: {
    402             throw new Error("Shouldn't reach here.");
    403         }
    404
    405         }
    406     }
    407
    408     protected boolean isContentAlwaysEmpty(HttpMessage msg) {
    409         if (msg instanceof HttpResponse) {
    410             HttpResponse res = (HttpResponse) msg;
    411             int code = res.getStatus().getCode();
    412
    413             // Correctly handle return codes of 1xx.
    414             //
    415             // See:
    416             //     - http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html Section 4.4
    417             //     - https://github.com/netty/netty/issues/222
    418             if (code >= 100 && code < 200) {
    419                 if (code == 101 && !res.containsHeader(HttpHeaders.Names.SEC_WEBSOCKET_ACCEPT)) {
    420                     // It's Hixie 76 websocket handshake response
    421                     return false;
    422                 }
    423                 return true;
    424             }
    425
    426             switch (code) {
    427             case 204: case 205: case 304:
    428                 return true;
    429             }
    430         }
    431         return false;
    432     }
    433
    434
    435     private Object reset() {
    436         HttpMessage message = this.message;
    437         ChannelBuffer content = this.content;
    438
    439         if (content != null) {
    440             message.setContent(content);
    441             this.content = null;
    442         }
    443         this.message = null;
    444
    445         checkpoint(State.SKIP_CONTROL_CHARS);
    446         return message;
    447     }
    448
    449     private static void skipControlCharacters(ChannelBuffer buffer) {
    450         for (;;) {
    451             char c = (char) buffer.readUnsignedByte();
    452             if (!Character.isISOControl(c) &&
    453                 !Character.isWhitespace(c)) {
    454                 buffer.readerIndex(buffer.readerIndex() - 1);
    455                 break;
    456             }
    457         }
    458     }
    459
    460     private Object readFixedLengthContent(ChannelBuffer buffer) {
    461         //we have a content-length so we just read the correct number of bytes
    462         long length = HttpHeaders.getContentLength(message, -1);
    463         assert length <= Integer.MAX_VALUE;
    464         int toRead = (int) length - contentRead;
    465         if (toRead > actualReadableBytes()) {
    466             toRead = actualReadableBytes();
    467         }
    468         contentRead = contentRead + toRead;
    469         if (length < contentRead) {
    470             if (!message.isChunked()) {
    471                 message.setChunked(true);
    472                 return new Object[] {message, new DefaultHttpChunk(read(buffer, toRead))};
    473             } else {
    474                 return new DefaultHttpChunk(read(buffer, toRead));
    475             }
    476         }
    477         if (content == null) {
    478             content = read(buffer, (int) length);
    479         } else {
    480             content.writeBytes(buffer.readBytes((int) length));
    481         }
    482         return reset();
    483     }
    484
    485     /**
    486      * Try to do an optimized "read" of len from the given {@link ChannelBuffer}.
    487      *
    488      * This is part of #412 to safe byte copies
    489      *
    490      */
    491     private ChannelBuffer read(ChannelBuffer buffer, int len) {
    492         ChannelBuffer internal = internalBuffer();
    493         if (internal.readableBytes() >= len) {
    494             int index = internal.readerIndex();
    495             ChannelBuffer buf = internal.slice(index, len);
    496
    497             // update the readerindex so an the next read its on the correct position
    498             buffer.readerIndex(index + len);
    499             return buf;
    500         } else {
    501             return buffer.readBytes(len);
    502         }
    503     }
    504
    505     private State readHeaders(ChannelBuffer buffer) throws TooLongFrameException {
    506         headerSize = 0;
    507         final HttpMessage message = this.message;
    508         String line = readHeader(buffer);
    509         String name = null;
    510         String value = null;
    511         if (line.length() != 0) {
    512             message.clearHeaders();
    513             do {
    514                 char firstChar = line.charAt(0);
    515                 if (name != null && (firstChar == ' ' || firstChar == '\t')) {
    516                     value = value + ' ' + line.trim();
    517                 } else {
    518                     if (name != null) {
    519                         message.addHeader(name, value);
    520                     }
    521                     String[] header = splitHeader(line);
    522                     name = header[0];
    523                     value = header[1];
    524                 }
    525
    526                 line = readHeader(buffer);
    527             } while (line.length() != 0);
    528
    529             // Add the last header.
    530             if (name != null) {
    531                 message.addHeader(name, value);
    532             }
    533         }
    534
    535         State nextState;
    536
    537         if (isContentAlwaysEmpty(message)) {
    538             nextState = State.SKIP_CONTROL_CHARS;
    539         } else if (message.isChunked()) {
    540             // HttpMessage.isChunked() returns true when either:
    541             // 1) HttpMessage.setChunked(true) was called or
    542             // 2) 'Transfer-Encoding' is 'chunked'.
    543             // Because this decoder did not call HttpMessage.setChunked(true)
    544             // yet, HttpMessage.isChunked() should return true only when
    545             // 'Transfer-Encoding' is 'chunked'.
    546             nextState = State.READ_CHUNK_SIZE;
    547         } else if (HttpHeaders.getContentLength(message, -1) >= 0) {
    548             nextState = State.READ_FIXED_LENGTH_CONTENT;
    549         } else {
    550             nextState = State.READ_VARIABLE_LENGTH_CONTENT;
    551         }
    552         return nextState;
    553     }
    554
    555     private HttpChunkTrailer readTrailingHeaders(ChannelBuffer buffer) throws TooLongFrameException {
    556         headerSize = 0;
    557         String line = readHeader(buffer);
    558         String lastHeader = null;
    559         if (line.length() != 0) {
    560             HttpChunkTrailer trailer = new DefaultHttpChunkTrailer();
    561             do {
    562                 char firstChar = line.charAt(0);
    563                 if (lastHeader != null && (firstChar == ' ' || firstChar == '\t')) {
    564                     List<String> current = trailer.getHeaders(lastHeader);
    565                     if (current.size() != 0) {
    566                         int lastPos = current.size() - 1;
    567                         String newString = current.get(lastPos) + line.trim();
    568                         current.set(lastPos, newString);
    569                     } else {
    570                         // Content-Length, Transfer-Encoding, or Trailer
    571                     }
    572                 } else {
    573                     String[] header = splitHeader(line);
    574                     String name = header[0];
    575                     if (!name.equalsIgnoreCase(HttpHeaders.Names.CONTENT_LENGTH) &&
    576                         !name.equalsIgnoreCase(HttpHeaders.Names.TRANSFER_ENCODING) &&
    577                         !name.equalsIgnoreCase(HttpHeaders.Names.TRAILER)) {
    578                         trailer.addHeader(name, header[1]);
    579                     }
    580                     lastHeader = name;
    581                 }
    582
    583                 line = readHeader(buffer);
    584             } while (line.length() != 0);
    585
    586             return trailer;
    587         }
    588
    589         return HttpChunk.LAST_CHUNK;
    590     }
    591
    592     private String readHeader(ChannelBuffer buffer) throws TooLongFrameException {
    593         StringBuilder sb = new StringBuilder(64);
    594         int headerSize = this.headerSize;
    595
    596         loop:
    597         for (;;) {
    598             char nextByte = (char) buffer.readByte();
    599             headerSize ++;
    600
    601             switch (nextByte) {
    602             case HttpConstants.CR:
    603                 nextByte = (char) buffer.readByte();
    604                 headerSize ++;
    605                 if (nextByte == HttpConstants.LF) {
    606                     break loop;
    607                 }
    608                 break;
    609             case HttpConstants.LF:
    610                 break loop;
    611             }
    612
    613             // Abort decoding if the header part is too large.
    614             if (headerSize >= maxHeaderSize) {
    615                 // TODO: Respond with Bad Request and discard the traffic
    616                 //    or close the connection.
    617                 //       No need to notify the upstream handlers - just log.
    618                 //       If decoding a response, just throw an exception.
    619                 throw new TooLongFrameException(
    620                         "HTTP header is larger than " +
    621                         maxHeaderSize + " bytes.");
    622
    623             }
    624
    625             sb.append(nextByte);
    626         }
    627
    628         this.headerSize = headerSize;
    629         return sb.toString();
    630     }
    631
    632     protected abstract boolean isDecodingRequest();
    633     protected abstract HttpMessage createMessage(String[] initialLine) throws Exception;
    634
    635     private static int getChunkSize(String hex) {
    636         hex = hex.trim();
    637         for (int i = 0; i < hex.length(); i ++) {
    638             char c = hex.charAt(i);
    639             if (c == ';' || Character.isWhitespace(c) || Character.isISOControl(c)) {
    640                 hex = hex.substring(0, i);
    641                 break;
    642             }
    643         }
    644
    645         return Integer.parseInt(hex, 16);
    646     }
    647
    648     private static String readLine(ChannelBuffer buffer, int maxLineLength) throws TooLongFrameException {
    649         StringBuilder sb = new StringBuilder(64);
    650         int lineLength = 0;
    651         while (true) {
    652             byte nextByte = buffer.readByte();
    653             if (nextByte == HttpConstants.CR) {
    654                 nextByte = buffer.readByte();
    655                 if (nextByte == HttpConstants.LF) {
    656                     return sb.toString();
    657                 }
    658             } else if (nextByte == HttpConstants.LF) {
    659                 return sb.toString();
    660             } else {
    661                 if (lineLength >= maxLineLength) {
    662                     // TODO: Respond with Bad Request and discard the traffic
    663                     //    or close the connection.
    664                     //       No need to notify the upstream handlers - just log.
    665                     //       If decoding a response, just throw an exception.
    666                     throw new TooLongFrameException(
    667                             "An HTTP line is larger than " + maxLineLength +
    668                             " bytes.");
    669                 }
    670                 lineLength ++;
    671                 sb.append((char) nextByte);
    672             }
    673         }
    674     }
    675
    676     private static String[] splitInitialLine(String sb) {
    677         int aStart;
    678         int aEnd;
    679         int bStart;
    680         int bEnd;
    681         int cStart;
    682         int cEnd;
    683
    684         aStart = findNonWhitespace(sb, 0);
    685         aEnd = findWhitespace(sb, aStart);
    686
    687         bStart = findNonWhitespace(sb, aEnd);
    688         bEnd = findWhitespace(sb, bStart);
    689
    690         cStart = findNonWhitespace(sb, bEnd);
    691         cEnd = findEndOfString(sb);
    692
    693         return new String[] {
    694                 sb.substring(aStart, aEnd),
    695                 sb.substring(bStart, bEnd),
    696                 cStart < cEnd? sb.substring(cStart, cEnd) : "" };
    697     }
    698
    699     private static String[] splitHeader(String sb) {
    700         final int length = sb.length();
    701         int nameStart;
    702         int nameEnd;
    703         int colonEnd;
    704         int valueStart;
    705         int valueEnd;
    706
    707         nameStart = findNonWhitespace(sb, 0);
    708         for (nameEnd = nameStart; nameEnd < length; nameEnd ++) {
    709             char ch = sb.charAt(nameEnd);
    710             if (ch == ':' || Character.isWhitespace(ch)) {
    711                 break;
    712             }
    713         }
    714
    715         for (colonEnd = nameEnd; colonEnd < length; colonEnd ++) {
    716             if (sb.charAt(colonEnd) == ':') {
    717                 colonEnd ++;
    718                 break;
    719             }
    720         }
    721
    722         valueStart = findNonWhitespace(sb, colonEnd);
    723         if (valueStart == length) {
    724             return new String[] {
    725                     sb.substring(nameStart, nameEnd),
    726                     ""
    727             };
    728         }
    729
    730         valueEnd = findEndOfString(sb);
    731         return new String[] {
    732                 sb.substring(nameStart, nameEnd),
    733                 sb.substring(valueStart, valueEnd)
    734         };
    735     }
    736
    737     private static int findNonWhitespace(String sb, int offset) {
    738         int result;
    739         for (result = offset; result < sb.length(); result ++) {
    740             if (!Character.isWhitespace(sb.charAt(result))) {
    741                 break;
    742             }
    743         }
    744         return result;
    745     }
    746
    747     private static int findWhitespace(String sb, int offset) {
    748         int result;
    749         for (result = offset; result < sb.length(); result ++) {
    750             if (Character.isWhitespace(sb.charAt(result))) {
    751                 break;
    752             }
    753         }
    754         return result;
    755     }
    756
    757     private static int findEndOfString(String sb) {
    758         int result;
    759         for (result = sb.length(); result > 0; result --) {
    760             if (!Character.isWhitespace(sb.charAt(result - 1))) {
    761                 break;
    762             }
    763         }
    764         return result;
    765     }
    766 }