Home | History | Annotate | Download | only in http
      1 // Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "net/http/http_chunked_decoder.h"
      6 
      7 #include "base/basictypes.h"
      8 #include "base/memory/scoped_ptr.h"
      9 #include "net/base/net_errors.h"
     10 #include "testing/gtest/include/gtest/gtest.h"
     11 
     12 namespace net {
     13 
     14 namespace {
     15 
     16 typedef testing::Test HttpChunkedDecoderTest;
     17 
     18 void RunTest(const char* inputs[], size_t num_inputs,
     19              const char* expected_output,
     20              bool expected_eof,
     21              int bytes_after_eof) {
     22   HttpChunkedDecoder decoder;
     23   EXPECT_FALSE(decoder.reached_eof());
     24 
     25   std::string result;
     26 
     27   for (size_t i = 0; i < num_inputs; ++i) {
     28     std::string input = inputs[i];
     29     int n = decoder.FilterBuf(&input[0], static_cast<int>(input.size()));
     30     EXPECT_GE(n, 0);
     31     if (n > 0)
     32       result.append(input.data(), n);
     33   }
     34 
     35   EXPECT_EQ(expected_output, result);
     36   EXPECT_EQ(expected_eof, decoder.reached_eof());
     37   EXPECT_EQ(bytes_after_eof, decoder.bytes_after_eof());
     38 }
     39 
     40 // Feed the inputs to the decoder, until it returns an error.
     41 void RunTestUntilFailure(const char* inputs[],
     42                          size_t num_inputs,
     43                          size_t fail_index) {
     44   HttpChunkedDecoder decoder;
     45   EXPECT_FALSE(decoder.reached_eof());
     46 
     47   for (size_t i = 0; i < num_inputs; ++i) {
     48     std::string input = inputs[i];
     49     int n = decoder.FilterBuf(&input[0], static_cast<int>(input.size()));
     50     if (n < 0) {
     51       EXPECT_EQ(ERR_INVALID_CHUNKED_ENCODING, n);
     52       EXPECT_EQ(fail_index, i);
     53       return;
     54     }
     55   }
     56   FAIL();  // We should have failed on the |fail_index| iteration of the loop.
     57 }
     58 
     59 TEST(HttpChunkedDecoderTest, Basic) {
     60   const char* inputs[] = {
     61     "B\r\nhello hello\r\n0\r\n\r\n"
     62   };
     63   RunTest(inputs, arraysize(inputs), "hello hello", true, 0);
     64 }
     65 
     66 TEST(HttpChunkedDecoderTest, OneChunk) {
     67   const char* inputs[] = {
     68     "5\r\nhello\r\n"
     69   };
     70   RunTest(inputs, arraysize(inputs), "hello", false, 0);
     71 }
     72 
     73 TEST(HttpChunkedDecoderTest, Typical) {
     74   const char* inputs[] = {
     75     "5\r\nhello\r\n",
     76     "1\r\n \r\n",
     77     "5\r\nworld\r\n",
     78     "0\r\n\r\n"
     79   };
     80   RunTest(inputs, arraysize(inputs), "hello world", true, 0);
     81 }
     82 
     83 TEST(HttpChunkedDecoderTest, Incremental) {
     84   const char* inputs[] = {
     85     "5",
     86     "\r",
     87     "\n",
     88     "hello",
     89     "\r",
     90     "\n",
     91     "0",
     92     "\r",
     93     "\n",
     94     "\r",
     95     "\n"
     96   };
     97   RunTest(inputs, arraysize(inputs), "hello", true, 0);
     98 }
     99 
    100 // Same as above, but group carriage returns with previous input.
    101 TEST(HttpChunkedDecoderTest, Incremental2) {
    102   const char* inputs[] = {
    103     "5\r",
    104     "\n",
    105     "hello\r",
    106     "\n",
    107     "0\r",
    108     "\n\r",
    109     "\n"
    110   };
    111   RunTest(inputs, arraysize(inputs), "hello", true, 0);
    112 }
    113 
    114 TEST(HttpChunkedDecoderTest, LF_InsteadOf_CRLF) {
    115   // Compatibility: [RFC 2616 - Invalid]
    116   // {Firefox3} - Valid
    117   // {IE7, Safari3.1, Opera9.51} - Invalid
    118   const char* inputs[] = {
    119     "5\nhello\n",
    120     "1\n \n",
    121     "5\nworld\n",
    122     "0\n\n"
    123   };
    124   RunTest(inputs, arraysize(inputs), "hello world", true, 0);
    125 }
    126 
    127 TEST(HttpChunkedDecoderTest, Extensions) {
    128   const char* inputs[] = {
    129     "5;x=0\r\nhello\r\n",
    130     "0;y=\"2 \"\r\n\r\n"
    131   };
    132   RunTest(inputs, arraysize(inputs), "hello", true, 0);
    133 }
    134 
    135 TEST(HttpChunkedDecoderTest, Trailers) {
    136   const char* inputs[] = {
    137     "5\r\nhello\r\n",
    138     "0\r\n",
    139     "Foo: 1\r\n",
    140     "Bar: 2\r\n",
    141     "\r\n"
    142   };
    143   RunTest(inputs, arraysize(inputs), "hello", true, 0);
    144 }
    145 
    146 TEST(HttpChunkedDecoderTest, TrailersUnfinished) {
    147   const char* inputs[] = {
    148     "5\r\nhello\r\n",
    149     "0\r\n",
    150     "Foo: 1\r\n"
    151   };
    152   RunTest(inputs, arraysize(inputs), "hello", false, 0);
    153 }
    154 
    155 TEST(HttpChunkedDecoderTest, InvalidChunkSize_TooBig) {
    156   const char* inputs[] = {
    157     // This chunked body is not terminated.
    158     // However we will fail decoding because the chunk-size
    159     // number is larger than we can handle.
    160     "48469410265455838241\r\nhello\r\n",
    161     "0\r\n\r\n"
    162   };
    163   RunTestUntilFailure(inputs, arraysize(inputs), 0);
    164 }
    165 
    166 TEST(HttpChunkedDecoderTest, InvalidChunkSize_0X) {
    167   const char* inputs[] = {
    168     // Compatibility [RFC 2616 - Invalid]:
    169     // {Safari3.1, IE7} - Invalid
    170     // {Firefox3, Opera 9.51} - Valid
    171     "0x5\r\nhello\r\n",
    172     "0\r\n\r\n"
    173   };
    174   RunTestUntilFailure(inputs, arraysize(inputs), 0);
    175 }
    176 
    177 TEST(HttpChunkedDecoderTest, ChunkSize_TrailingSpace) {
    178   const char* inputs[] = {
    179     // Compatibility [RFC 2616 - Invalid]:
    180     // {IE7, Safari3.1, Firefox3, Opera 9.51} - Valid
    181     //
    182     // At least yahoo.com depends on this being valid.
    183     "5      \r\nhello\r\n",
    184     "0\r\n\r\n"
    185   };
    186   RunTest(inputs, arraysize(inputs), "hello", true, 0);
    187 }
    188 
    189 TEST(HttpChunkedDecoderTest, InvalidChunkSize_TrailingTab) {
    190   const char* inputs[] = {
    191     // Compatibility [RFC 2616 - Invalid]:
    192     // {IE7, Safari3.1, Firefox3, Opera 9.51} - Valid
    193     "5\t\r\nhello\r\n",
    194     "0\r\n\r\n"
    195   };
    196   RunTestUntilFailure(inputs, arraysize(inputs), 0);
    197 }
    198 
    199 TEST(HttpChunkedDecoderTest, InvalidChunkSize_TrailingFormFeed) {
    200   const char* inputs[] = {
    201     // Compatibility [RFC 2616- Invalid]:
    202     // {Safari3.1} - Invalid
    203     // {IE7, Firefox3, Opera 9.51} - Valid
    204     "5\f\r\nhello\r\n",
    205     "0\r\n\r\n"
    206   };
    207   RunTestUntilFailure(inputs, arraysize(inputs), 0);
    208 }
    209 
    210 TEST(HttpChunkedDecoderTest, InvalidChunkSize_TrailingVerticalTab) {
    211   const char* inputs[] = {
    212     // Compatibility [RFC 2616 - Invalid]:
    213     // {Safari 3.1} - Invalid
    214     // {IE7, Firefox3, Opera 9.51} - Valid
    215     "5\v\r\nhello\r\n",
    216     "0\r\n\r\n"
    217   };
    218   RunTestUntilFailure(inputs, arraysize(inputs), 0);
    219 }
    220 
    221 TEST(HttpChunkedDecoderTest, InvalidChunkSize_TrailingNonHexDigit) {
    222   const char* inputs[] = {
    223     // Compatibility [RFC 2616 - Invalid]:
    224     // {Safari 3.1} - Invalid
    225     // {IE7, Firefox3, Opera 9.51} - Valid
    226     "5H\r\nhello\r\n",
    227     "0\r\n\r\n"
    228   };
    229   RunTestUntilFailure(inputs, arraysize(inputs), 0);
    230 }
    231 
    232 TEST(HttpChunkedDecoderTest, InvalidChunkSize_LeadingSpace) {
    233   const char* inputs[] = {
    234     // Compatibility [RFC 2616 - Invalid]:
    235     // {IE7} - Invalid
    236     // {Safari 3.1, Firefox3, Opera 9.51} - Valid
    237     " 5\r\nhello\r\n",
    238     "0\r\n\r\n"
    239   };
    240   RunTestUntilFailure(inputs, arraysize(inputs), 0);
    241 }
    242 
    243 TEST(HttpChunkedDecoderTest, InvalidLeadingSeparator) {
    244   const char* inputs[] = {
    245     "\r\n5\r\nhello\r\n",
    246     "0\r\n\r\n"
    247   };
    248   RunTestUntilFailure(inputs, arraysize(inputs), 0);
    249 }
    250 
    251 TEST(HttpChunkedDecoderTest, InvalidChunkSize_NoSeparator) {
    252   const char* inputs[] = {
    253     "5\r\nhello",
    254     "1\r\n \r\n",
    255     "0\r\n\r\n"
    256   };
    257   RunTestUntilFailure(inputs, arraysize(inputs), 1);
    258 }
    259 
    260 TEST(HttpChunkedDecoderTest, InvalidChunkSize_Negative) {
    261   const char* inputs[] = {
    262     "8\r\n12345678\r\n-5\r\nhello\r\n",
    263     "0\r\n\r\n"
    264   };
    265   RunTestUntilFailure(inputs, arraysize(inputs), 0);
    266 }
    267 
    268 TEST(HttpChunkedDecoderTest, InvalidChunkSize_Plus) {
    269   const char* inputs[] = {
    270     // Compatibility [RFC 2616 - Invalid]:
    271     // {IE7, Safari 3.1} - Invalid
    272     // {Firefox3, Opera 9.51} - Valid
    273     "+5\r\nhello\r\n",
    274     "0\r\n\r\n"
    275   };
    276   RunTestUntilFailure(inputs, arraysize(inputs), 0);
    277 }
    278 
    279 TEST(HttpChunkedDecoderTest, InvalidConsecutiveCRLFs) {
    280   const char* inputs[] = {
    281     "5\r\nhello\r\n",
    282     "\r\n\r\n\r\n\r\n",
    283     "0\r\n\r\n"
    284   };
    285   RunTestUntilFailure(inputs, arraysize(inputs), 1);
    286 }
    287 
    288 TEST(HttpChunkedDecoderTest, ExcessiveChunkLen) {
    289   const char* inputs[] = {
    290     "c0000000\r\nhello\r\n"
    291   };
    292   RunTestUntilFailure(inputs, arraysize(inputs), 0);
    293 }
    294 
    295 TEST(HttpChunkedDecoderTest, BasicExtraData) {
    296   const char* inputs[] = {
    297     "5\r\nhello\r\n0\r\n\r\nextra bytes"
    298   };
    299   RunTest(inputs, arraysize(inputs), "hello", true, 11);
    300 }
    301 
    302 TEST(HttpChunkedDecoderTest, IncrementalExtraData) {
    303   const char* inputs[] = {
    304     "5",
    305     "\r",
    306     "\n",
    307     "hello",
    308     "\r",
    309     "\n",
    310     "0",
    311     "\r",
    312     "\n",
    313     "\r",
    314     "\nextra bytes"
    315   };
    316   RunTest(inputs, arraysize(inputs), "hello", true, 11);
    317 }
    318 
    319 TEST(HttpChunkedDecoderTest, MultipleExtraDataBlocks) {
    320   const char* inputs[] = {
    321     "5\r\nhello\r\n0\r\n\r\nextra",
    322     " bytes"
    323   };
    324   RunTest(inputs, arraysize(inputs), "hello", true, 11);
    325 }
    326 
    327 // Test when the line with the chunk length is too long.
    328 TEST(HttpChunkedDecoderTest, LongChunkLengthLine) {
    329   int big_chunk_length = HttpChunkedDecoder::kMaxLineBufLen;
    330   scoped_ptr<char[]> big_chunk(new char[big_chunk_length + 1]);
    331   memset(big_chunk.get(), '0', big_chunk_length);
    332   big_chunk[big_chunk_length] = 0;
    333   const char* inputs[] = {
    334     big_chunk.get(),
    335     "5"
    336   };
    337   RunTestUntilFailure(inputs, arraysize(inputs), 1);
    338 }
    339 
    340 // Test when the extension portion of the line with the chunk length is too
    341 // long.
    342 TEST(HttpChunkedDecoderTest, LongLengthLengthLine) {
    343   int big_chunk_length = HttpChunkedDecoder::kMaxLineBufLen;
    344   scoped_ptr<char[]> big_chunk(new char[big_chunk_length + 1]);
    345   memset(big_chunk.get(), '0', big_chunk_length);
    346   big_chunk[big_chunk_length] = 0;
    347   const char* inputs[] = {
    348     "5;",
    349     big_chunk.get()
    350   };
    351   RunTestUntilFailure(inputs, arraysize(inputs), 1);
    352 }
    353 
    354 }  // namespace
    355 
    356 }  // namespace net
    357