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