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.
5 #include "net/http/http_stream_parser.h"
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 "googleurl/src/gurl.h"
14 #include "net/base/io_buffer.h"
15 #include "net/base/net_errors.h"
16 #include "net/base/test_completion_callback.h"
17 #include "net/base/upload_bytes_element_reader.h"
18 #include "net/base/upload_data_stream.h"
19 #include "net/base/upload_file_element_reader.h"
20 #include "net/http/http_request_headers.h"
21 #include "net/http/http_request_info.h"
22 #include "net/http/http_response_info.h"
23 #include "net/socket/client_socket_handle.h"
24 #include "net/socket/socket_test_util.h"
25 #include "testing/gtest/include/gtest/gtest.h"
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
;
34 // The empty payload is how the last chunk is encoded.
35 TEST(HttpStreamParser
, EncodeChunk_EmptyPayload
) {
36 char output
[kOutputSize
];
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
));
46 TEST(HttpStreamParser
, EncodeChunk_ShortPayload
) {
47 char output
[kOutputSize
];
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
));
58 TEST(HttpStreamParser
, EncodeChunk_LargePayload
) {
59 char output
[kOutputSize
];
61 const std::string
kPayload(1000, '\xff'); // '\xff' x 1000.
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
));
70 TEST(HttpStreamParser
, EncodeChunk_FullPayload
) {
71 char output
[kOutputSize
];
73 const std::string
kPayload(kMaxPayloadSize
, '\xff');
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
));
82 TEST(HttpStreamParser
, EncodeChunk_TooLargePayload
) {
83 char output
[kOutputSize
];
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
);
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
));
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()));
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()));
118 TEST(HttpStreamParser
, ShouldMergeRequestHeadersAndBody_FileBody
) {
119 ScopedVector
<UploadElementReader
> element_readers
;
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(),
128 element_readers
.push_back(
129 new UploadFileElementReader(base::MessageLoopProxy::current().get(),
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()));
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()));
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()));
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()));
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()));
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
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
178 static const char kChunk1
[] = "Chunk 1";
179 static const char kChunk2
[] = "Chunky 2";
180 static const char kChunk3
[] = "Test 3";
182 MockWrite writes
[] = {
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"),
194 // The size of the response body, as reflected in the Content-Length of the
196 static const int kBodySize
= 8;
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
205 UploadDataStream
upload_stream(UploadDataStream::CHUNKED
, 0);
206 upload_stream
.AppendChunk(kChunk1
, arraysize(kChunk1
) - 1, false);
207 ASSERT_EQ(OK
, upload_stream
.Init(CompletionCallback()));
209 DeterministicSocketData
data(reads
, arraysize(reads
),
210 writes
, arraysize(writes
));
211 data
.set_connect_data(MockConnect(SYNCHRONOUS
, OK
));
213 scoped_ptr
<DeterministicMockTCPClientSocket
> transport(
214 new DeterministicMockTCPClientSocket(NULL
, &data
));
215 data
.set_delegate(transport
->AsWeakPtr());
217 TestCompletionCallback callback
;
218 int rv
= transport
->Connect(callback
.callback());
219 rv
= callback
.GetResult(rv
);
222 scoped_ptr
<ClientSocketHandle
> socket_handle(new ClientSocketHandle
);
223 socket_handle
->set_socket(transport
.release());
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
;
231 scoped_refptr
<GrowableIOBuffer
> read_buffer(new GrowableIOBuffer
);
232 HttpStreamParser
parser(
233 socket_handle
.get(), &request_info
, read_buffer
.get(), BoundNetLog());
235 HttpRequestHeaders request_headers
;
236 request_headers
.SetHeader("Host", "localhost");
237 request_headers
.SetHeader("Transfer-Encoding", "chunked");
238 request_headers
.SetHeader("Connection", "keep-alive");
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
);
247 // Complete the initial request write. Additionally, this should enqueue the
250 ASSERT_FALSE(callback
.have_result());
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());
257 // Complete writing the first chunk, which should then enqueue the second
258 // chunk for writing and return, because it is set to complete
261 ASSERT_FALSE(callback
.have_result());
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
267 ASSERT_FALSE(callback
.have_result());
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());
274 // Finalize writing the last chunk, which will enqueue the trailer.
276 ASSERT_FALSE(callback
.have_result());
278 // Finalize writing the trailer.
280 ASSERT_TRUE(callback
.have_result());
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();
288 // Attempt to read the response status and the response headers.
289 rv
= parser
.ReadResponseHeaders(callback
.callback());
290 ASSERT_EQ(ERR_IO_PENDING
, rv
);
293 ASSERT_TRUE(callback
.have_result());
294 rv
= callback
.WaitForResult();
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
);
304 ASSERT_TRUE(callback
.have_result());
305 rv
= callback
.WaitForResult();
306 ASSERT_EQ(kBodySize
, rv
);
309 TEST(HttpStreamParser
, TruncatedHeaders
) {
310 MockRead truncated_status_reads
[] = {
311 MockRead(SYNCHRONOUS
, 1, "HTTP/1.1 20"),
312 MockRead(SYNCHRONOUS
, 0, 2), // EOF
315 MockRead truncated_after_status_reads
[] = {
316 MockRead(SYNCHRONOUS
, 1, "HTTP/1.1 200 Ok\r\n"),
317 MockRead(SYNCHRONOUS
, 0, 2), // EOF
320 MockRead truncated_in_header_reads
[] = {
321 MockRead(SYNCHRONOUS
, 1, "HTTP/1.1 200 Ok\r\nHead"),
322 MockRead(SYNCHRONOUS
, 0, 2), // EOF
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
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
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
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
,
349 MockWrite writes
[] = {
350 MockWrite(SYNCHRONOUS
, 0, "GET / HTTP/1.1\r\n\r\n"),
359 for (size_t protocol
= 0; protocol
< NUM_PROTOCOLS
; protocol
++) {
360 SCOPED_TRACE(protocol
);
362 for (size_t i
= 0; i
< arraysize(reads
); i
++) {
364 DeterministicSocketData
data(reads
[i
], 2, writes
, arraysize(writes
));
365 data
.set_connect_data(MockConnect(SYNCHRONOUS
, OK
));
368 scoped_ptr
<DeterministicMockTCPClientSocket
> transport(
369 new DeterministicMockTCPClientSocket(NULL
, &data
));
370 data
.set_delegate(transport
->AsWeakPtr());
372 TestCompletionCallback callback
;
373 int rv
= transport
->Connect(callback
.callback());
374 rv
= callback
.GetResult(rv
);
377 scoped_ptr
<ClientSocketHandle
> socket_handle(new ClientSocketHandle
);
378 socket_handle
->set_socket(transport
.release());
380 HttpRequestInfo request_info
;
381 request_info
.method
= "GET";
382 if (protocol
== HTTP
) {
383 request_info
.url
= GURL("http://localhost");
385 request_info
.url
= GURL("https://localhost");
387 request_info
.load_flags
= LOAD_NORMAL
;
389 scoped_refptr
<GrowableIOBuffer
> read_buffer(new GrowableIOBuffer
);
390 HttpStreamParser
parser(
391 socket_handle
.get(), &request_info
, read_buffer
.get(), BoundNetLog());
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());
399 rv
= parser
.ReadResponseHeaders(callback
.callback());
400 if (i
== arraysize(reads
) - 1) {
402 EXPECT_TRUE(response_info
.headers
.get());
404 if (protocol
== HTTP
) {
405 EXPECT_EQ(ERR_CONNECTION_CLOSED
, rv
);
406 EXPECT_TRUE(response_info
.headers
.get());
408 EXPECT_EQ(ERR_RESPONSE_HEADERS_TRUNCATED
, rv
);
409 EXPECT_FALSE(response_info
.headers
.get());