Home | History | Annotate | Download | only in http
      1 // Copyright (c) 2012 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_stream_parser.h"
      6 
      7 #include "base/file_util.h"
      8 #include "base/files/file_path.h"
      9 #include "base/files/scoped_temp_dir.h"
     10 #include "base/memory/ref_counted.h"
     11 #include "base/strings/string_piece.h"
     12 #include "base/strings/stringprintf.h"
     13 #include "net/base/io_buffer.h"
     14 #include "net/base/net_errors.h"
     15 #include "net/base/test_completion_callback.h"
     16 #include "net/base/upload_bytes_element_reader.h"
     17 #include "net/base/upload_data_stream.h"
     18 #include "net/base/upload_file_element_reader.h"
     19 #include "net/http/http_request_headers.h"
     20 #include "net/http/http_request_info.h"
     21 #include "net/http/http_response_info.h"
     22 #include "net/socket/client_socket_handle.h"
     23 #include "net/socket/socket_test_util.h"
     24 #include "testing/gtest/include/gtest/gtest.h"
     25 #include "url/gurl.h"
     26 
     27 namespace net {
     28 
     29 const size_t kOutputSize = 1024;  // Just large enough for this test.
     30 // The number of bytes that can fit in a buffer of kOutputSize.
     31 const size_t kMaxPayloadSize =
     32     kOutputSize - HttpStreamParser::kChunkHeaderFooterSize;
     33 
     34 // The empty payload is how the last chunk is encoded.
     35 TEST(HttpStreamParser, EncodeChunk_EmptyPayload) {
     36   char output[kOutputSize];
     37 
     38   const base::StringPiece kPayload = "";
     39   const base::StringPiece kExpected = "0\r\n\r\n";
     40   const int num_bytes_written =
     41       HttpStreamParser::EncodeChunk(kPayload, output, sizeof(output));
     42   ASSERT_EQ(kExpected.size(), static_cast<size_t>(num_bytes_written));
     43   EXPECT_EQ(kExpected, base::StringPiece(output, num_bytes_written));
     44 }
     45 
     46 TEST(HttpStreamParser, EncodeChunk_ShortPayload) {
     47   char output[kOutputSize];
     48 
     49   const std::string kPayload("foo\x00\x11\x22", 6);
     50   // 11 = payload size + sizeof("6") + CRLF x 2.
     51   const std::string kExpected("6\r\nfoo\x00\x11\x22\r\n", 11);
     52   const int num_bytes_written =
     53       HttpStreamParser::EncodeChunk(kPayload, output, sizeof(output));
     54   ASSERT_EQ(kExpected.size(), static_cast<size_t>(num_bytes_written));
     55   EXPECT_EQ(kExpected, base::StringPiece(output, num_bytes_written));
     56 }
     57 
     58 TEST(HttpStreamParser, EncodeChunk_LargePayload) {
     59   char output[kOutputSize];
     60 
     61   const std::string kPayload(1000, '\xff');  // '\xff' x 1000.
     62   // 3E8 = 1000 in hex.
     63   const std::string kExpected = "3E8\r\n" + kPayload + "\r\n";
     64   const int num_bytes_written =
     65       HttpStreamParser::EncodeChunk(kPayload, output, sizeof(output));
     66   ASSERT_EQ(kExpected.size(), static_cast<size_t>(num_bytes_written));
     67   EXPECT_EQ(kExpected, base::StringPiece(output, num_bytes_written));
     68 }
     69 
     70 TEST(HttpStreamParser, EncodeChunk_FullPayload) {
     71   char output[kOutputSize];
     72 
     73   const std::string kPayload(kMaxPayloadSize, '\xff');
     74   // 3F4 = 1012 in hex.
     75   const std::string kExpected = "3F4\r\n" + kPayload + "\r\n";
     76   const int num_bytes_written =
     77       HttpStreamParser::EncodeChunk(kPayload, output, sizeof(output));
     78   ASSERT_EQ(kExpected.size(), static_cast<size_t>(num_bytes_written));
     79   EXPECT_EQ(kExpected, base::StringPiece(output, num_bytes_written));
     80 }
     81 
     82 TEST(HttpStreamParser, EncodeChunk_TooLargePayload) {
     83   char output[kOutputSize];
     84 
     85   // The payload is one byte larger the output buffer size.
     86   const std::string kPayload(kMaxPayloadSize + 1, '\xff');
     87   const int num_bytes_written =
     88       HttpStreamParser::EncodeChunk(kPayload, output, sizeof(output));
     89   ASSERT_EQ(ERR_INVALID_ARGUMENT, num_bytes_written);
     90 }
     91 
     92 TEST(HttpStreamParser, ShouldMergeRequestHeadersAndBody_NoBody) {
     93   // Shouldn't be merged if upload data is non-existent.
     94   ASSERT_FALSE(HttpStreamParser::ShouldMergeRequestHeadersAndBody(
     95       "some header", NULL));
     96 }
     97 
     98 TEST(HttpStreamParser, ShouldMergeRequestHeadersAndBody_EmptyBody) {
     99   ScopedVector<UploadElementReader> element_readers;
    100   scoped_ptr<UploadDataStream> body(new UploadDataStream(&element_readers, 0));
    101   ASSERT_EQ(OK, body->Init(CompletionCallback()));
    102   // Shouldn't be merged if upload data is empty.
    103   ASSERT_FALSE(HttpStreamParser::ShouldMergeRequestHeadersAndBody(
    104       "some header", body.get()));
    105 }
    106 
    107 TEST(HttpStreamParser, ShouldMergeRequestHeadersAndBody_ChunkedBody) {
    108   const std::string payload = "123";
    109   scoped_ptr<UploadDataStream> body(
    110       new UploadDataStream(UploadDataStream::CHUNKED, 0));
    111   body->AppendChunk(payload.data(), payload.size(), true);
    112   ASSERT_EQ(OK, body->Init(CompletionCallback()));
    113   // Shouldn't be merged if upload data carries chunked data.
    114   ASSERT_FALSE(HttpStreamParser::ShouldMergeRequestHeadersAndBody(
    115       "some header", body.get()));
    116 }
    117 
    118 TEST(HttpStreamParser, ShouldMergeRequestHeadersAndBody_FileBody) {
    119   ScopedVector<UploadElementReader> element_readers;
    120 
    121   // Create an empty temporary file.
    122   base::ScopedTempDir temp_dir;
    123   ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
    124   base::FilePath temp_file_path;
    125   ASSERT_TRUE(file_util::CreateTemporaryFileInDir(temp_dir.path(),
    126                                                   &temp_file_path));
    127 
    128   element_readers.push_back(
    129       new UploadFileElementReader(base::MessageLoopProxy::current().get(),
    130                                   temp_file_path,
    131                                   0,
    132                                   0,
    133                                   base::Time()));
    134 
    135   scoped_ptr<UploadDataStream> body(new UploadDataStream(&element_readers, 0));
    136   TestCompletionCallback callback;
    137   ASSERT_EQ(ERR_IO_PENDING, body->Init(callback.callback()));
    138   ASSERT_EQ(OK, callback.WaitForResult());
    139   // Shouldn't be merged if upload data carries a file, as it's not in-memory.
    140   ASSERT_FALSE(HttpStreamParser::ShouldMergeRequestHeadersAndBody(
    141       "some header", body.get()));
    142 }
    143 
    144 TEST(HttpStreamParser, ShouldMergeRequestHeadersAndBody_SmallBodyInMemory) {
    145   ScopedVector<UploadElementReader> element_readers;
    146   const std::string payload = "123";
    147   element_readers.push_back(new UploadBytesElementReader(
    148       payload.data(), payload.size()));
    149 
    150   scoped_ptr<UploadDataStream> body(new UploadDataStream(&element_readers, 0));
    151   ASSERT_EQ(OK, body->Init(CompletionCallback()));
    152   // Yes, should be merged if the in-memory body is small here.
    153   ASSERT_TRUE(HttpStreamParser::ShouldMergeRequestHeadersAndBody(
    154       "some header", body.get()));
    155 }
    156 
    157 TEST(HttpStreamParser, ShouldMergeRequestHeadersAndBody_LargeBodyInMemory) {
    158   ScopedVector<UploadElementReader> element_readers;
    159   const std::string payload(10000, 'a');  // 'a' x 10000.
    160   element_readers.push_back(new UploadBytesElementReader(
    161       payload.data(), payload.size()));
    162 
    163   scoped_ptr<UploadDataStream> body(new UploadDataStream(&element_readers, 0));
    164   ASSERT_EQ(OK, body->Init(CompletionCallback()));
    165   // Shouldn't be merged if the in-memory body is large here.
    166   ASSERT_FALSE(HttpStreamParser::ShouldMergeRequestHeadersAndBody(
    167       "some header", body.get()));
    168 }
    169 
    170 // Test to ensure the HttpStreamParser state machine does not get confused
    171 // when sending a request with a chunked body, where chunks become available
    172 // asynchronously, over a socket where writes may also complete
    173 // asynchronously.
    174 // This is a regression test for http://crbug.com/132243
    175 TEST(HttpStreamParser, AsyncChunkAndAsyncSocket) {
    176   // The chunks that will be written in the request, as reflected in the
    177   // MockWrites below.
    178   static const char kChunk1[] = "Chunk 1";
    179   static const char kChunk2[] = "Chunky 2";
    180   static const char kChunk3[] = "Test 3";
    181 
    182   MockWrite writes[] = {
    183     MockWrite(ASYNC, 0,
    184               "GET /one.html HTTP/1.1\r\n"
    185               "Host: localhost\r\n"
    186               "Transfer-Encoding: chunked\r\n"
    187               "Connection: keep-alive\r\n\r\n"),
    188     MockWrite(ASYNC, 1, "7\r\nChunk 1\r\n"),
    189     MockWrite(ASYNC, 2, "8\r\nChunky 2\r\n"),
    190     MockWrite(ASYNC, 3, "6\r\nTest 3\r\n"),
    191     MockWrite(ASYNC, 4, "0\r\n\r\n"),
    192   };
    193 
    194   // The size of the response body, as reflected in the Content-Length of the
    195   // MockRead below.
    196   static const int kBodySize = 8;
    197 
    198   MockRead reads[] = {
    199     MockRead(ASYNC, 5, "HTTP/1.1 200 OK\r\n"),
    200     MockRead(ASYNC, 6, "Content-Length: 8\r\n\r\n"),
    201     MockRead(ASYNC, 7, "one.html"),
    202     MockRead(SYNCHRONOUS, 0, 8),  // EOF
    203   };
    204 
    205   UploadDataStream upload_stream(UploadDataStream::CHUNKED, 0);
    206   upload_stream.AppendChunk(kChunk1, arraysize(kChunk1) - 1, false);
    207   ASSERT_EQ(OK, upload_stream.Init(CompletionCallback()));
    208 
    209   DeterministicSocketData data(reads, arraysize(reads),
    210                                writes, arraysize(writes));
    211   data.set_connect_data(MockConnect(SYNCHRONOUS, OK));
    212 
    213   scoped_ptr<DeterministicMockTCPClientSocket> transport(
    214       new DeterministicMockTCPClientSocket(NULL, &data));
    215   data.set_delegate(transport->AsWeakPtr());
    216 
    217   TestCompletionCallback callback;
    218   int rv = transport->Connect(callback.callback());
    219   rv = callback.GetResult(rv);
    220   ASSERT_EQ(OK, rv);
    221 
    222   scoped_ptr<ClientSocketHandle> socket_handle(new ClientSocketHandle);
    223   socket_handle->set_socket(transport.release());
    224 
    225   HttpRequestInfo request_info;
    226   request_info.method = "GET";
    227   request_info.url = GURL("http://localhost");
    228   request_info.load_flags = LOAD_NORMAL;
    229   request_info.upload_data_stream = &upload_stream;
    230 
    231   scoped_refptr<GrowableIOBuffer> read_buffer(new GrowableIOBuffer);
    232   HttpStreamParser parser(
    233       socket_handle.get(), &request_info, read_buffer.get(), BoundNetLog());
    234 
    235   HttpRequestHeaders request_headers;
    236   request_headers.SetHeader("Host", "localhost");
    237   request_headers.SetHeader("Transfer-Encoding", "chunked");
    238   request_headers.SetHeader("Connection", "keep-alive");
    239 
    240   HttpResponseInfo response_info;
    241   // This will attempt to Write() the initial request and headers, which will
    242   // complete asynchronously.
    243   rv = parser.SendRequest("GET /one.html HTTP/1.1\r\n", request_headers,
    244                           &response_info, callback.callback());
    245   ASSERT_EQ(ERR_IO_PENDING, rv);
    246 
    247   // Complete the initial request write. Additionally, this should enqueue the
    248   // first chunk.
    249   data.RunFor(1);
    250   ASSERT_FALSE(callback.have_result());
    251 
    252   // Now append another chunk (while the first write is still pending), which
    253   // should not confuse the state machine.
    254   upload_stream.AppendChunk(kChunk2, arraysize(kChunk2) - 1, false);
    255   ASSERT_FALSE(callback.have_result());
    256 
    257   // Complete writing the first chunk, which should then enqueue the second
    258   // chunk for writing and return, because it is set to complete
    259   // asynchronously.
    260   data.RunFor(1);
    261   ASSERT_FALSE(callback.have_result());
    262 
    263   // Complete writing the second chunk. However, because no chunks are
    264   // available yet, no further writes should be called until a new chunk is
    265   // added.
    266   data.RunFor(1);
    267   ASSERT_FALSE(callback.have_result());
    268 
    269   // Add the final chunk. This will enqueue another write, but it will not
    270   // complete due to the async nature.
    271   upload_stream.AppendChunk(kChunk3, arraysize(kChunk3) - 1, true);
    272   ASSERT_FALSE(callback.have_result());
    273 
    274   // Finalize writing the last chunk, which will enqueue the trailer.
    275   data.RunFor(1);
    276   ASSERT_FALSE(callback.have_result());
    277 
    278   // Finalize writing the trailer.
    279   data.RunFor(1);
    280   ASSERT_TRUE(callback.have_result());
    281 
    282   // Warning: This will hang if the callback doesn't already have a result,
    283   // due to the deterministic socket provider. Do not remove the above
    284   // ASSERT_TRUE, which will avoid this hang.
    285   rv = callback.WaitForResult();
    286   ASSERT_EQ(OK, rv);
    287 
    288   // Attempt to read the response status and the response headers.
    289   rv = parser.ReadResponseHeaders(callback.callback());
    290   ASSERT_EQ(ERR_IO_PENDING, rv);
    291   data.RunFor(2);
    292 
    293   ASSERT_TRUE(callback.have_result());
    294   rv = callback.WaitForResult();
    295   ASSERT_GT(rv, 0);
    296 
    297   // Finally, attempt to read the response body.
    298   scoped_refptr<IOBuffer> body_buffer(new IOBuffer(kBodySize));
    299   rv = parser.ReadResponseBody(
    300       body_buffer.get(), kBodySize, callback.callback());
    301   ASSERT_EQ(ERR_IO_PENDING, rv);
    302   data.RunFor(1);
    303 
    304   ASSERT_TRUE(callback.have_result());
    305   rv = callback.WaitForResult();
    306   ASSERT_EQ(kBodySize, rv);
    307 }
    308 
    309 TEST(HttpStreamParser, TruncatedHeaders) {
    310   MockRead truncated_status_reads[] = {
    311     MockRead(SYNCHRONOUS, 1, "HTTP/1.1 20"),
    312     MockRead(SYNCHRONOUS, 0, 2),  // EOF
    313   };
    314 
    315   MockRead truncated_after_status_reads[] = {
    316     MockRead(SYNCHRONOUS, 1, "HTTP/1.1 200 Ok\r\n"),
    317     MockRead(SYNCHRONOUS, 0, 2),  // EOF
    318   };
    319 
    320   MockRead truncated_in_header_reads[] = {
    321     MockRead(SYNCHRONOUS, 1, "HTTP/1.1 200 Ok\r\nHead"),
    322     MockRead(SYNCHRONOUS, 0, 2),  // EOF
    323   };
    324 
    325   MockRead truncated_after_header_reads[] = {
    326     MockRead(SYNCHRONOUS, 1, "HTTP/1.1 200 Ok\r\nHeader: foo\r\n"),
    327     MockRead(SYNCHRONOUS, 0, 2),  // EOF
    328   };
    329 
    330   MockRead truncated_after_final_newline_reads[] = {
    331     MockRead(SYNCHRONOUS, 1, "HTTP/1.1 200 Ok\r\nHeader: foo\r\n\r"),
    332     MockRead(SYNCHRONOUS, 0, 2),  // EOF
    333   };
    334 
    335   MockRead not_truncated_reads[] = {
    336     MockRead(SYNCHRONOUS, 1, "HTTP/1.1 200 Ok\r\nHeader: foo\r\n\r\n"),
    337     MockRead(SYNCHRONOUS, 0, 2),  // EOF
    338   };
    339 
    340   MockRead* reads[] = {
    341     truncated_status_reads,
    342     truncated_after_status_reads,
    343     truncated_in_header_reads,
    344     truncated_after_header_reads,
    345     truncated_after_final_newline_reads,
    346     not_truncated_reads,
    347   };
    348 
    349   MockWrite writes[] = {
    350     MockWrite(SYNCHRONOUS, 0, "GET / HTTP/1.1\r\n\r\n"),
    351   };
    352 
    353   enum {
    354     HTTP = 0,
    355     HTTPS,
    356     NUM_PROTOCOLS,
    357   };
    358 
    359   for (size_t protocol = 0; protocol < NUM_PROTOCOLS; protocol++) {
    360     SCOPED_TRACE(protocol);
    361 
    362     for (size_t i = 0; i < arraysize(reads); i++) {
    363       SCOPED_TRACE(i);
    364       DeterministicSocketData data(reads[i], 2, writes, arraysize(writes));
    365       data.set_connect_data(MockConnect(SYNCHRONOUS, OK));
    366       data.SetStop(3);
    367 
    368       scoped_ptr<DeterministicMockTCPClientSocket> transport(
    369           new DeterministicMockTCPClientSocket(NULL, &data));
    370       data.set_delegate(transport->AsWeakPtr());
    371 
    372       TestCompletionCallback callback;
    373       int rv = transport->Connect(callback.callback());
    374       rv = callback.GetResult(rv);
    375       ASSERT_EQ(OK, rv);
    376 
    377       scoped_ptr<ClientSocketHandle> socket_handle(new ClientSocketHandle);
    378       socket_handle->set_socket(transport.release());
    379 
    380       HttpRequestInfo request_info;
    381       request_info.method = "GET";
    382       if (protocol == HTTP) {
    383         request_info.url = GURL("http://localhost");
    384       } else {
    385         request_info.url = GURL("https://localhost");
    386       }
    387       request_info.load_flags = LOAD_NORMAL;
    388 
    389       scoped_refptr<GrowableIOBuffer> read_buffer(new GrowableIOBuffer);
    390       HttpStreamParser parser(
    391           socket_handle.get(), &request_info, read_buffer.get(), BoundNetLog());
    392 
    393       HttpRequestHeaders request_headers;
    394       HttpResponseInfo response_info;
    395       rv = parser.SendRequest("GET / HTTP/1.1\r\n", request_headers,
    396                               &response_info, callback.callback());
    397       ASSERT_EQ(OK, rv);
    398 
    399       rv = parser.ReadResponseHeaders(callback.callback());
    400       if (i == arraysize(reads) - 1) {
    401         EXPECT_EQ(OK, rv);
    402         EXPECT_TRUE(response_info.headers.get());
    403       } else {
    404         if (protocol == HTTP) {
    405           EXPECT_EQ(ERR_CONNECTION_CLOSED, rv);
    406           EXPECT_TRUE(response_info.headers.get());
    407         } else {
    408           EXPECT_EQ(ERR_RESPONSE_HEADERS_TRUNCATED, rv);
    409           EXPECT_FALSE(response_info.headers.get());
    410         }
    411       }
    412     }
    413   }
    414 }
    415 
    416 }  // namespace net
    417