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