diff options
Diffstat (limited to 'net/spdy/spdy_network_transaction_unittest.cc')
-rw-r--r-- | net/spdy/spdy_network_transaction_unittest.cc | 1084 |
1 files changed, 1084 insertions, 0 deletions
diff --git a/net/spdy/spdy_network_transaction_unittest.cc b/net/spdy/spdy_network_transaction_unittest.cc new file mode 100644 index 0000000..0c879f7 --- /dev/null +++ b/net/spdy/spdy_network_transaction_unittest.cc @@ -0,0 +1,1084 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/spdy/spdy_network_transaction.h" + +#include "base/basictypes.h" +#include "base/ref_counted.h" +#include "net/base/completion_callback.h" +#include "net/base/load_log_unittest.h" +#include "net/base/mock_host_resolver.h" +#include "net/base/ssl_config_service_defaults.h" +#include "net/base/test_completion_callback.h" +#include "net/base/upload_data.h" +#include "net/http/http_network_session.h" +#include "net/http/http_transaction_unittest.h" +#include "net/proxy/proxy_config_service_fixed.h" +#include "net/socket/socket_test_util.h" +#include "net/spdy/spdy_protocol.h" +#include "testing/platform_test.h" + +//----------------------------------------------------------------------------- + +namespace net { + +namespace { + +// Create a proxy service which fails on all requests (falls back to direct). +ProxyService* CreateNullProxyService() { + return ProxyService::CreateNull(); +} + +// Helper to manage the lifetimes of the dependencies for a +// FlipNetworkTransaction. +class SessionDependencies { + public: + // Default set of dependencies -- "null" proxy service. + SessionDependencies() + : host_resolver(new MockHostResolver), + proxy_service(CreateNullProxyService()), + ssl_config_service(new SSLConfigServiceDefaults), + flip_session_pool(new FlipSessionPool) { + // Note: The CancelledTransaction test does cleanup by running all tasks + // in the message loop (RunAllPending). Unfortunately, that doesn't clean + // up tasks on the host resolver thread; and TCPConnectJob is currently + // not cancellable. Using synchronous lookups allows the test to shutdown + // cleanly. Until we have cancellable TCPConnectJobs, use synchronous + // lookups. + host_resolver->set_synchronous_mode(true); + } + + // Custom proxy service dependency. + explicit SessionDependencies(ProxyService* proxy_service) + : host_resolver(new MockHostResolver), + proxy_service(proxy_service), + ssl_config_service(new SSLConfigServiceDefaults), + flip_session_pool(new FlipSessionPool) {} + + scoped_refptr<MockHostResolverBase> host_resolver; + scoped_refptr<ProxyService> proxy_service; + scoped_refptr<SSLConfigService> ssl_config_service; + MockClientSocketFactory socket_factory; + scoped_refptr<FlipSessionPool> flip_session_pool; +}; + +ProxyService* CreateFixedProxyService(const std::string& proxy) { + ProxyConfig proxy_config; + proxy_config.proxy_rules.ParseFromString(proxy); + return ProxyService::CreateFixed(proxy_config); +} + + +HttpNetworkSession* CreateSession(SessionDependencies* session_deps) { + return new HttpNetworkSession(NULL, + session_deps->host_resolver, + session_deps->proxy_service, + &session_deps->socket_factory, + session_deps->ssl_config_service, + session_deps->flip_session_pool); +} + +// Chop a frame into an array of MockWrites. +// |data| is the frame to chop. +// |length| is the length of the frame to chop. +// |num_chunks| is the number of chunks to create. +MockWrite* ChopFrame(const char* data, int length, int num_chunks) { + MockWrite* chunks = new MockWrite[num_chunks + 1]; + int chunk_size = length / num_chunks; + for (int index = 0; index < num_chunks; index++) { + const char* ptr = data + (index * chunk_size); + if (index == num_chunks - 1) + chunk_size += length % chunk_size; // The last chunk takes the remainder. + chunks[index] = MockWrite(true, ptr, chunk_size); + } + chunks[num_chunks] = MockWrite(true, 0, 0); + return chunks; +} + +// ---------------------------------------------------------------------------- + +static const unsigned char kGetSyn[] = { + 0x80, 0x01, 0x00, 0x01, // header + 0x01, 0x00, 0x00, 0x45, // FIN, len + 0x00, 0x00, 0x00, 0x01, // stream id + 0xc0, 0x00, 0x00, 0x03, // 4 headers + 0x00, 0x06, 'm', 'e', 't', 'h', 'o', 'd', + 0x00, 0x03, 'G', 'E', 'T', + 0x00, 0x03, 'u', 'r', 'l', + 0x00, 0x16, 'h', 't', 't', 'p', ':', '/', '/', 'w', 'w', 'w', + '.', 'g', 'o', 'o', 'g', 'l', 'e', '.', 'c', 'o', + 'm', '/', + 0x00, 0x07, 'v', 'e', 'r', 's', 'i', 'o', 'n', + 0x00, 0x08, 'H', 'T', 'T', 'P', '/', '1', '.', '1', +}; + +static const unsigned char kGetSynCompressed[] = { + 0x80, 0x01, 0x00, 0x01, 0x01, 0x00, 0x00, 0x43, + 0x00, 0x00, 0x00, 0x01, 0xc0, 0x00, 0x78, 0xbb, + 0xdf, 0xa2, 0x51, 0xb2, 0x62, 0x60, 0x66, 0x60, + 0xcb, 0x05, 0xe6, 0xc3, 0xfc, 0x14, 0x06, 0x66, + 0x77, 0xd7, 0x10, 0x06, 0x66, 0x90, 0xa0, 0x58, + 0x46, 0x49, 0x49, 0x81, 0x95, 0xbe, 0x3e, 0x30, + 0xe2, 0xf5, 0xd2, 0xf3, 0xf3, 0xd3, 0x73, 0x52, + 0xf5, 0x92, 0xf3, 0x73, 0xf5, 0x19, 0xd8, 0xa1, + 0x1a, 0x19, 0x38, 0x60, 0xe6, 0x01, 0x00, 0x00, + 0x00, 0xff, 0xff +}; + +static const unsigned char kGetSynReply[] = { + 0x80, 0x01, 0x00, 0x02, // header + 0x00, 0x00, 0x00, 0x45, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x04, // 4 headers + 0x00, 0x05, 'h', 'e', 'l', 'l', 'o', // "hello" + 0x00, 0x03, 'b', 'y', 'e', // "bye" + 0x00, 0x06, 's', 't', 'a', 't', 'u', 's', // "status" + 0x00, 0x03, '2', '0', '0', // "200" + 0x00, 0x03, 'u', 'r', 'l', // "url" + 0x00, 0x0a, '/', 'i', 'n', 'd', 'e', 'x', '.', 'p', 'h', 'p', // "/index... + 0x00, 0x07, 'v', 'e', 'r', 's', 'i', 'o', 'n', // "version" + 0x00, 0x08, 'H', 'T', 'T', 'P', '/', '1', '.', '1', // "HTTP/1.1" +}; + +static const unsigned char kGetBodyFrame[] = { + 0x00, 0x00, 0x00, 0x01, // header + 0x01, 0x00, 0x00, 0x06, // FIN, length + 'h', 'e', 'l', 'l', 'o', '!', // "hello" +}; + +static const unsigned char kPostSyn[] = { + 0x80, 0x01, 0x00, 0x01, // header + 0x00, 0x00, 0x00, 0x46, // flags, len + 0x00, 0x00, 0x00, 0x01, // stream id + 0xc0, 0x00, 0x00, 0x03, // 4 headers + 0x00, 0x06, 'm', 'e', 't', 'h', 'o', 'd', + 0x00, 0x04, 'P', 'O', 'S', 'T', + 0x00, 0x03, 'u', 'r', 'l', + 0x00, 0x16, 'h', 't', 't', 'p', ':', '/', '/', 'w', 'w', 'w', + '.', 'g', 'o', 'o', 'g', 'l', 'e', '.', 'c', 'o', + 'm', '/', + 0x00, 0x07, 'v', 'e', 'r', 's', 'i', 'o', 'n', + 0x00, 0x08, 'H', 'T', 'T', 'P', '/', '1', '.', '1', +}; + +static const unsigned char kPostUploadFrame[] = { + 0x00, 0x00, 0x00, 0x01, // header + 0x01, 0x00, 0x00, 0x0c, // FIN flag + 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', '\0' +}; + +// The response +static const unsigned char kPostSynReply[] = { + 0x80, 0x01, 0x00, 0x02, // header + 0x00, 0x00, 0x00, 0x45, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x04, // 4 headers + 0x00, 0x05, 'h', 'e', 'l', 'l', 'o', // "hello" + 0x00, 0x03, 'b', 'y', 'e', // "bye" + 0x00, 0x06, 's', 't', 'a', 't', 'u', 's', // "status" + 0x00, 0x03, '2', '0', '0', // "200" + 0x00, 0x03, 'u', 'r', 'l', // "url" + // "/index.php" + 0x00, 0x0a, '/', 'i', 'n', 'd', 'e', 'x', '.', 'p', 'h', 'p', + 0x00, 0x07, 'v', 'e', 'r', 's', 'i', 'o', 'n', // "version" + 0x00, 0x08, 'H', 'T', 'T', 'P', '/', '1', '.', '1', // "HTTP/1.1" +}; + +static const unsigned char kPostBodyFrame[] = { + 0x00, 0x00, 0x00, 0x01, // header + 0x01, 0x00, 0x00, 0x06, // FIN, length + 'h', 'e', 'l', 'l', 'o', '!', // "hello" +}; + +} // namespace + +// A DataProvider where the client must write a request before the reads (e.g. +// the response) will complete. +class DelayedSocketData : public StaticSocketDataProvider, + public base::RefCounted<DelayedSocketData> { + public: + // |reads| the list of MockRead completions. + // |write_delay| the number of MockWrites to complete before allowing + // a MockRead to complete. + // |writes| the list of MockWrite completions. + // Note: All MockReads and MockWrites must be async. + // Note: The MockRead and MockWrite lists musts end with a EOF + // e.g. a MockRead(true, 0, 0); + DelayedSocketData(MockRead* reads, int write_delay, MockWrite* writes) + : StaticSocketDataProvider(reads, writes), + write_delay_(write_delay), + ALLOW_THIS_IN_INITIALIZER_LIST(factory_(this)) { + DCHECK_GE(write_delay_, 0); + } + + // |connect| the result for the connect phase. + // |reads| the list of MockRead completions. + // |write_delay| the number of MockWrites to complete before allowing + // a MockRead to complete. + // |writes| the list of MockWrite completions. + // Note: All MockReads and MockWrites must be async. + // Note: The MockRead and MockWrite lists musts end with a EOF + // e.g. a MockRead(true, 0, 0); + DelayedSocketData(const MockConnect& connect, MockRead* reads, + int write_delay, MockWrite* writes) + : StaticSocketDataProvider(reads, writes), + write_delay_(write_delay), + ALLOW_THIS_IN_INITIALIZER_LIST(factory_(this)) { + DCHECK_GE(write_delay_, 0); + set_connect_data(connect); + } + + virtual MockRead GetNextRead() { + if (write_delay_) + return MockRead(true, ERR_IO_PENDING); + return StaticSocketDataProvider::GetNextRead(); + } + + virtual MockWriteResult OnWrite(const std::string& data) { + MockWriteResult rv = StaticSocketDataProvider::OnWrite(data); + // Now that our write has completed, we can allow reads to continue. + if (!--write_delay_) + MessageLoop::current()->PostDelayedTask(FROM_HERE, + factory_.NewRunnableMethod(&DelayedSocketData::CompleteRead), 100); + return rv; + } + + virtual void Reset() { + set_socket(NULL); + factory_.RevokeAll(); + StaticSocketDataProvider::Reset(); + } + + void CompleteRead() { + if (socket()) + socket()->OnReadComplete(GetNextRead()); + } + + private: + int write_delay_; + ScopedRunnableMethodFactory<DelayedSocketData> factory_; +}; + +class FlipNetworkTransactionTest : public PlatformTest { + protected: + virtual void SetUp() { + // By default, all tests turn off compression. + EnableCompression(false); + } + + virtual void TearDown() { + // Empty the current queue. + MessageLoop::current()->RunAllPending(); + PlatformTest::TearDown(); + } + + void KeepAliveConnectionResendRequestTest(const MockRead& read_failure); + + struct TransactionHelperResult { + int rv; + std::string status_line; + std::string response_data; + HttpResponseInfo response_info; + }; + + void EnableCompression(bool enabled) { + flip::FlipFramer::set_enable_compression_default(enabled); + } + + TransactionHelperResult TransactionHelper(const HttpRequestInfo& request, + DelayedSocketData* data, + LoadLog* log) { + TransactionHelperResult out; + + // We disable SSL for this test. + FlipSession::SetSSLMode(false); + + SessionDependencies session_deps; + scoped_ptr<FlipNetworkTransaction> trans( + new FlipNetworkTransaction(CreateSession(&session_deps))); + + session_deps.socket_factory.AddSocketDataProvider(data); + + TestCompletionCallback callback; + + int rv = trans->Start(&request, &callback, log); + EXPECT_EQ(ERR_IO_PENDING, rv); + + out.rv = callback.WaitForResult(); + if (out.rv != OK) + return out; + + const HttpResponseInfo* response = trans->GetResponseInfo(); + EXPECT_TRUE(response->headers != NULL); + EXPECT_TRUE(response->was_fetched_via_spdy); + out.status_line = response->headers->GetStatusLine(); + out.response_info = *response; // Make a copy so we can verify. + + rv = ReadTransaction(trans.get(), &out.response_data); + EXPECT_EQ(OK, rv); + + // Verify that we consumed all test data. + EXPECT_TRUE(data->at_read_eof()); + EXPECT_TRUE(data->at_write_eof()); + + return out; + } + + void ConnectStatusHelperWithExpectedStatus(const MockRead& status, + int expected_status); + + void ConnectStatusHelper(const MockRead& status); +}; + +//----------------------------------------------------------------------------- + +// Verify FlipNetworkTransaction constructor. +TEST_F(FlipNetworkTransactionTest, Constructor) { + SessionDependencies session_deps; + scoped_refptr<HttpNetworkSession> session = + CreateSession(&session_deps); + scoped_ptr<HttpTransaction> trans(new FlipNetworkTransaction(session)); +} + +TEST_F(FlipNetworkTransactionTest, Get) { + MockWrite writes[] = { + MockWrite(true, reinterpret_cast<const char*>(kGetSyn), + arraysize(kGetSyn)), + MockWrite(true, 0, 0) // EOF + }; + + MockRead reads[] = { + MockRead(true, reinterpret_cast<const char*>(kGetSynReply), + arraysize(kGetSynReply)), + MockRead(true, reinterpret_cast<const char*>(kGetBodyFrame), + arraysize(kGetBodyFrame)), + MockRead(true, 0, 0) // EOF + }; + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + scoped_refptr<DelayedSocketData> data( + new DelayedSocketData(reads, 1, writes)); + TransactionHelperResult out = TransactionHelper(request, data.get(), NULL); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!", out.response_data); +} + +// Test that a simple POST works. +TEST_F(FlipNetworkTransactionTest, Post) { + static const char upload[] = { "hello world" }; + + // Setup the request + HttpRequestInfo request; + request.method = "POST"; + request.url = GURL("http://www.google.com/"); + request.upload_data = new UploadData(); + request.upload_data->AppendBytes(upload, sizeof(upload)); + + MockWrite writes[] = { + MockWrite(true, reinterpret_cast<const char*>(kPostSyn), + arraysize(kPostSyn)), + MockWrite(true, reinterpret_cast<const char*>(kPostUploadFrame), + arraysize(kPostUploadFrame)), + MockWrite(true, 0, 0) // EOF + }; + + MockRead reads[] = { + MockRead(true, reinterpret_cast<const char*>(kPostSynReply), + arraysize(kPostSynReply)), + MockRead(true, reinterpret_cast<const char*>(kPostBodyFrame), + arraysize(kPostBodyFrame)), + MockRead(true, 0, 0) // EOF + }; + + scoped_refptr<DelayedSocketData> data( + new DelayedSocketData(reads, 2, writes)); + TransactionHelperResult out = TransactionHelper(request, data.get(), NULL); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!", out.response_data); +} + +// Test that a simple POST works. +TEST_F(FlipNetworkTransactionTest, EmptyPost) { +static const unsigned char kEmptyPostSyn[] = { + 0x80, 0x01, 0x00, 0x01, // header + 0x01, 0x00, 0x00, 0x46, // flags, len + 0x00, 0x00, 0x00, 0x01, // stream id + 0xc0, 0x00, 0x00, 0x03, // 4 headers + 0x00, 0x06, 'm', 'e', 't', 'h', 'o', 'd', + 0x00, 0x04, 'P', 'O', 'S', 'T', + 0x00, 0x03, 'u', 'r', 'l', + 0x00, 0x16, 'h', 't', 't', 'p', ':', '/', '/', 'w', 'w', 'w', + '.', 'g', 'o', 'o', 'g', 'l', 'e', '.', 'c', 'o', + 'm', '/', + 0x00, 0x07, 'v', 'e', 'r', 's', 'i', 'o', 'n', + 0x00, 0x08, 'H', 'T', 'T', 'P', '/', '1', '.', '1', +}; + + // Setup the request + HttpRequestInfo request; + request.method = "POST"; + request.url = GURL("http://www.google.com/"); + // Create an empty UploadData. + request.upload_data = new UploadData(); + + MockWrite writes[] = { + MockWrite(true, reinterpret_cast<const char*>(kEmptyPostSyn), + arraysize(kEmptyPostSyn)), + MockWrite(true, 0, 0) // EOF + }; + + MockRead reads[] = { + MockRead(true, reinterpret_cast<const char*>(kPostSynReply), + arraysize(kPostSynReply)), + MockRead(true, reinterpret_cast<const char*>(kPostBodyFrame), + arraysize(kGetBodyFrame)), + MockRead(true, 0, 0) // EOF + }; + + scoped_refptr<DelayedSocketData> data( + new DelayedSocketData(reads, 1, writes)); + + TransactionHelperResult out = TransactionHelper(request, data, NULL); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!", out.response_data); +} + +// Test that the transaction doesn't crash when we don't have a reply. +TEST_F(FlipNetworkTransactionTest, ResponseWithoutSynReply) { + MockRead reads[] = { + MockRead(true, reinterpret_cast<const char*>(kPostBodyFrame), + arraysize(kPostBodyFrame)), + MockRead(true, 0, 0) // EOF + }; + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + scoped_refptr<DelayedSocketData> data( + new DelayedSocketData(reads, 1, NULL)); + TransactionHelperResult out = TransactionHelper(request, data.get(), NULL); + EXPECT_EQ(ERR_SYN_REPLY_NOT_RECEIVED, out.rv); +} + +TEST_F(FlipNetworkTransactionTest, CancelledTransaction) { + MockWrite writes[] = { + MockWrite(true, reinterpret_cast<const char*>(kGetSyn), + arraysize(kGetSyn)), + MockRead(true, 0, 0) // EOF + }; + + MockRead reads[] = { + MockRead(true, reinterpret_cast<const char*>(kGetSynReply), + arraysize(kGetSynReply)), + // This following read isn't used by the test, except during the + // RunAllPending() call at the end since the FlipSession survives the + // FlipNetworkTransaction and still tries to continue Read()'ing. Any + // MockRead will do here. + MockRead(true, 0, 0) // EOF + }; + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + + // We disable SSL for this test. + FlipSession::SetSSLMode(false); + + SessionDependencies session_deps; + scoped_ptr<FlipNetworkTransaction> trans( + new FlipNetworkTransaction(CreateSession(&session_deps))); + + StaticSocketDataProvider data(reads, writes); + session_deps.socket_factory.AddSocketDataProvider(&data); + + TestCompletionCallback callback; + + int rv = trans->Start(&request, &callback, NULL); + EXPECT_EQ(ERR_IO_PENDING, rv); + trans.reset(); // Cancel the transaction. + + // Flush the MessageLoop while the SessionDependencies (in particular, the + // MockClientSocketFactory) are still alive. + MessageLoop::current()->RunAllPending(); +} + +// Verify that various SynReply headers parse correctly through the +// HTTP layer. +TEST_F(FlipNetworkTransactionTest, SynReplyHeaders) { + // This uses a multi-valued cookie header. + static const unsigned char syn_reply1[] = { + 0x80, 0x01, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x4c, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x04, + 0x00, 0x06, 'c', 'o', 'o', 'k', 'i', 'e', + 0x00, 0x09, 'v', 'a', 'l', '1', '\0', + 'v', 'a', 'l', '2', + 0x00, 0x06, 's', 't', 'a', 't', 'u', 's', + 0x00, 0x03, '2', '0', '0', + 0x00, 0x03, 'u', 'r', 'l', + 0x00, 0x0a, '/', 'i', 'n', 'd', 'e', 'x', '.', 'p', 'h', 'p', + 0x00, 0x07, 'v', 'e', 'r', 's', 'i', 'o', 'n', + 0x00, 0x08, 'H', 'T', 'T', 'P', '/', '1', '.', '1', + }; + + // This is the minimalist set of headers. + static const unsigned char syn_reply2[] = { + 0x80, 0x01, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x39, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x04, + 0x00, 0x06, 's', 't', 'a', 't', 'u', 's', + 0x00, 0x03, '2', '0', '0', + 0x00, 0x03, 'u', 'r', 'l', + 0x00, 0x0a, '/', 'i', 'n', 'd', 'e', 'x', '.', 'p', 'h', 'p', + 0x00, 0x07, 'v', 'e', 'r', 's', 'i', 'o', 'n', + 0x00, 0x08, 'H', 'T', 'T', 'P', '/', '1', '.', '1', + }; + + // Headers with a comma separated list. + static const unsigned char syn_reply3[] = { + 0x80, 0x01, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x4c, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x04, + 0x00, 0x06, 'c', 'o', 'o', 'k', 'i', 'e', + 0x00, 0x09, 'v', 'a', 'l', '1', ',', 'v', 'a', 'l', '2', + 0x00, 0x06, 's', 't', 'a', 't', 'u', 's', + 0x00, 0x03, '2', '0', '0', + 0x00, 0x03, 'u', 'r', 'l', + 0x00, 0x0a, '/', 'i', 'n', 'd', 'e', 'x', '.', 'p', 'h', 'p', + 0x00, 0x07, 'v', 'e', 'r', 's', 'i', 'o', 'n', + 0x00, 0x08, 'H', 'T', 'T', 'P', '/', '1', '.', '1', + }; + + struct SynReplyTests { + const unsigned char* syn_reply; + int syn_reply_length; + const char* expected_headers; + } test_cases[] = { + // Test the case of a multi-valued cookie. When the value is delimited + // with NUL characters, it needs to be unfolded into multiple headers. + { syn_reply1, sizeof(syn_reply1), + "cookie: val1\n" + "cookie: val2\n" + "status: 200\n" + "url: /index.php\n" + "version: HTTP/1.1\n" + }, + // This is the simplest set of headers possible. + { syn_reply2, sizeof(syn_reply2), + "status: 200\n" + "url: /index.php\n" + "version: HTTP/1.1\n" + }, + // Test that a comma delimited list is NOT interpreted as a multi-value + // name/value pair. The comma-separated list is just a single value. + { syn_reply3, sizeof(syn_reply3), + "cookie: val1,val2\n" + "status: 200\n" + "url: /index.php\n" + "version: HTTP/1.1\n" + } + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) { + MockWrite writes[] = { + MockWrite(true, reinterpret_cast<const char*>(kGetSyn), + arraysize(kGetSyn)), + MockWrite(true, 0, 0) // EOF + }; + + MockRead reads[] = { + MockRead(true, reinterpret_cast<const char*>(test_cases[i].syn_reply), + test_cases[i].syn_reply_length), + MockRead(true, reinterpret_cast<const char*>(kGetBodyFrame), + arraysize(kGetBodyFrame)), + MockRead(true, 0, 0) // EOF + }; + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + scoped_refptr<DelayedSocketData> data( + new DelayedSocketData(reads, 1, writes)); + TransactionHelperResult out = TransactionHelper(request, data.get(), NULL); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!", out.response_data); + + scoped_refptr<HttpResponseHeaders> headers = out.response_info.headers; + EXPECT_TRUE(headers.get() != NULL); + void* iter = NULL; + std::string name, value, lines; + while (headers->EnumerateHeaderLines(&iter, &name, &value)) { + lines.append(name); + lines.append(": "); + lines.append(value); + lines.append("\n"); + } + EXPECT_EQ(std::string(test_cases[i].expected_headers), lines); + } +} + +// Verify that we don't crash on invalid SynReply responses. +TEST_F(FlipNetworkTransactionTest, InvalidSynReply) { + static const unsigned char kSynReplyMissingStatus[] = { + 0x80, 0x01, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x3f, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x04, + 0x00, 0x06, 'c', 'o', 'o', 'k', 'i', 'e', + 0x00, 0x09, 'v', 'a', 'l', '1', '\0', + 'v', 'a', 'l', '2', + 0x00, 0x03, 'u', 'r', 'l', + 0x00, 0x0a, '/', 'i', 'n', 'd', 'e', 'x', '.', 'p', 'h', 'p', + 0x00, 0x07, 'v', 'e', 'r', 's', 'i', 'o', 'n', + 0x00, 0x08, 'H', 'T', 'T', 'P', '/', '1', '.', '1', + }; + + static const unsigned char kSynReplyMissingVersion[] = { + 0x80, 0x01, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x26, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x04, + 0x00, 0x06, 's', 't', 'a', 't', 'u', 's', + 0x00, 0x03, '2', '0', '0', + 0x00, 0x03, 'u', 'r', 'l', + 0x00, 0x0a, '/', 'i', 'n', 'd', 'e', 'x', '.', 'p', 'h', 'p', + }; + + struct SynReplyTests { + const unsigned char* syn_reply; + int syn_reply_length; + } test_cases[] = { + { kSynReplyMissingStatus, arraysize(kSynReplyMissingStatus) }, + { kSynReplyMissingVersion, arraysize(kSynReplyMissingVersion) } + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) { + MockWrite writes[] = { + MockWrite(true, reinterpret_cast<const char*>(kGetSyn), + arraysize(kGetSyn)), + MockWrite(true, 0, 0) // EOF + }; + + MockRead reads[] = { + MockRead(true, reinterpret_cast<const char*>(test_cases[i].syn_reply), + test_cases[i].syn_reply_length), + MockRead(true, reinterpret_cast<const char*>(kGetBodyFrame), + arraysize(kGetBodyFrame)), + MockRead(true, 0, 0) // EOF + }; + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + scoped_refptr<DelayedSocketData> data( + new DelayedSocketData(reads, 1, writes)); + TransactionHelperResult out = TransactionHelper(request, data.get(), NULL); + EXPECT_EQ(ERR_INVALID_RESPONSE, out.rv); + } +} + +// Verify that we don't crash on some corrupt frames. +TEST_F(FlipNetworkTransactionTest, CorruptFrameSessionError) { + static const unsigned char kSynReplyMassiveLength[] = { + 0x80, 0x01, 0x00, 0x02, + 0x0f, 0x11, 0x11, 0x26, // This is the length field with a big number + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x04, + 0x00, 0x06, 's', 't', 'a', 't', 'u', 's', + 0x00, 0x03, '2', '0', '0', + 0x00, 0x03, 'u', 'r', 'l', + 0x00, 0x0a, '/', 'i', 'n', 'd', 'e', 'x', '.', 'p', 'h', 'p', + }; + + struct SynReplyTests { + const unsigned char* syn_reply; + int syn_reply_length; + } test_cases[] = { + { kSynReplyMassiveLength, arraysize(kSynReplyMassiveLength) } + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) { + MockWrite writes[] = { + MockWrite(true, reinterpret_cast<const char*>(kGetSyn), + arraysize(kGetSyn)), + MockWrite(true, 0, 0) // EOF + }; + + MockRead reads[] = { + MockRead(true, reinterpret_cast<const char*>(test_cases[i].syn_reply), + test_cases[i].syn_reply_length), + MockRead(true, reinterpret_cast<const char*>(kGetBodyFrame), + arraysize(kGetBodyFrame)), + MockRead(true, 0, 0) // EOF + }; + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + scoped_refptr<DelayedSocketData> data( + new DelayedSocketData(reads, 1, writes)); + TransactionHelperResult out = TransactionHelper(request, data.get(), NULL); + EXPECT_EQ(ERR_FLIP_PROTOCOL_ERROR, out.rv); + } +} + +TEST_F(FlipNetworkTransactionTest, ServerPush) { + // Reply with the X-Associated-Content header. + static const unsigned char syn_reply[] = { + 0x80, 0x01, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x71, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x04, + 0x00, 0x14, 'X', '-', 'A', 's', 's', 'o', 'c', 'i', 'a', 't', + 'e', 'd', '-', 'C', 'o', 'n', 't', 'e', 'n', 't', + 0x00, 0x20, '1', '?', '?', 'h', 't', 't', 'p', ':', '/', '/', 'w', 'w', + 'w', '.', 'g', 'o', 'o', 'g', 'l', 'e', '.', 'c', 'o', 'm', + '/', 'f', 'o', 'o', '.', 'd', 'a', 't', + 0x00, 0x06, 's', 't', 'a', 't', 'u', 's', + 0x00, 0x03, '2', '0', '0', + 0x00, 0x03, 'u', 'r', 'l', + 0x00, 0x0a, '/', 'i', 'n', 'd', 'e', 'x', '.', 'p', 'h', 'p', + 0x00, 0x07, 'v', 'e', 'r', 's', 'i', 'o', 'n', + 0x00, 0x08, 'H', 'T', 'T', 'P', '/', '1', '.', '1', + }; + + // Syn for the X-Associated-Content (foo.dat) + static const unsigned char syn_push[] = { + 0x80, 0x01, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x47, + 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x04, + 0x00, 0x04, 'p', 'a', 't', 'h', + 0x00, 0x08, '/', 'f', 'o', 'o', '.', 'd', 'a', 't', + 0x00, 0x06, 's', 't', 'a', 't', 'u', 's', + 0x00, 0x03, '2', '0', '0', + 0x00, 0x03, 'u', 'r', 'l', + 0x00, 0x08, '/', 'f', 'o', 'o', '.', 'd', 'a', 't', + 0x00, 0x07, 'v', 'e', 'r', 's', 'i', 'o', 'n', + 0x00, 0x08, 'H', 'T', 'T', 'P', '/', '1', '.', '1', + }; + + // Body for stream 2 + static const unsigned char body_frame_2[] = { + 0x00, 0x00, 0x00, 0x02, + 0x01, 0x00, 0x00, 0x07, + 'g', 'o', 'o', 'd', 'b', 'y', 'e', + }; + + MockWrite writes[] = { + MockWrite(true, reinterpret_cast<const char*>(kGetSyn), + arraysize(kGetSyn)), + MockWrite(true, 0, 0) // EOF + }; + + MockRead reads[] = { + MockRead(true, reinterpret_cast<const char*>(syn_reply), + arraysize(syn_reply)), + MockRead(true, reinterpret_cast<const char*>(kGetBodyFrame), + arraysize(kGetBodyFrame)), + MockRead(true, ERR_IO_PENDING), // Force a pause + MockRead(true, reinterpret_cast<const char*>(syn_push), + arraysize(syn_push)), + MockRead(true, reinterpret_cast<const char*>(body_frame_2), + arraysize(body_frame_2)), + MockRead(true, ERR_IO_PENDING), // Force a pause + MockRead(true, 0, 0) // EOF + }; + + // We disable SSL for this test. + FlipSession::SetSSLMode(false); + + enum TestTypes { + // Simulate that the server sends the first request, notifying the client + // that it *will* push the second stream. But the client issues the + // request for the second stream before the push data arrives. + PUSH_AFTER_REQUEST, + // Simulate that the server is sending the pushed stream data before the + // client requests it. The FlipSession will buffer the response and then + // deliver the data when the client does make the request. + PUSH_BEFORE_REQUEST, + DONE + }; + + for (int test_type = PUSH_AFTER_REQUEST; test_type != DONE; ++test_type) { + // Setup a mock session. + SessionDependencies session_deps; + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + scoped_refptr<DelayedSocketData> data( + new DelayedSocketData(reads, 1, writes)); + session_deps.socket_factory.AddSocketDataProvider(data.get()); + + // Issue the first request + { + FlipNetworkTransaction trans(session.get()); + + // Issue the first request. + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + TestCompletionCallback callback; + int rv = trans.Start(&request, &callback, NULL); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(rv, OK); + + // Verify the SYN_REPLY. + const HttpResponseInfo* response = trans.GetResponseInfo(); + EXPECT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + + if (test_type == PUSH_BEFORE_REQUEST) + data->CompleteRead(); + + // Verify the body. + std::string response_data; + rv = ReadTransaction(&trans, &response_data); + EXPECT_EQ(OK, rv); + EXPECT_EQ("hello!", response_data); + } + + // Issue a second request for the X-Associated-Content. + { + FlipNetworkTransaction trans(session.get()); + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/foo.dat"); + request.load_flags = 0; + TestCompletionCallback callback; + int rv = trans.Start(&request, &callback, NULL); + EXPECT_EQ(ERR_IO_PENDING, rv); + + // In the case where we are Complete the next read now. + if (test_type == PUSH_AFTER_REQUEST) + data->CompleteRead(); + + rv = callback.WaitForResult(); + EXPECT_EQ(rv, OK); + + // Verify the SYN_REPLY. + const HttpResponseInfo* response = trans.GetResponseInfo(); + EXPECT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + + // Verify the body. + std::string response_data; + rv = ReadTransaction(&trans, &response_data); + EXPECT_EQ(OK, rv); + EXPECT_EQ("goodbye", response_data); + } + + // Complete the next read now and teardown. + data->CompleteRead(); + + // Verify that we consumed all test data. + EXPECT_TRUE(data->at_read_eof()); + EXPECT_TRUE(data->at_write_eof()); + } +} + +// Test that we shutdown correctly on write errors. +TEST_F(FlipNetworkTransactionTest, WriteError) { + MockWrite writes[] = { + // We'll write 10 bytes successfully + MockWrite(true, reinterpret_cast<const char*>(kGetSyn), 10), + // Followed by ERROR! + MockWrite(true, ERR_FAILED), + MockWrite(true, 0, 0) // EOF + }; + + MockRead reads[] = { + MockRead(true, reinterpret_cast<const char*>(kGetSynReply), + arraysize(kGetSynReply)), + MockRead(true, reinterpret_cast<const char*>(kGetBodyFrame), + arraysize(kGetBodyFrame)), + MockRead(true, 0, 0) // EOF + }; + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + scoped_refptr<DelayedSocketData> data( + new DelayedSocketData(reads, 2, writes)); + TransactionHelperResult out = TransactionHelper(request, data.get(), NULL); + EXPECT_EQ(ERR_FAILED, out.rv); + data->Reset(); +} + +// Test that partial writes work. +TEST_F(FlipNetworkTransactionTest, PartialWrite) { + // Chop the SYN_STREAM frame into 5 chunks. + const int kChunks = 5; + scoped_array<MockWrite> writes(ChopFrame( + reinterpret_cast<const char*>(kGetSyn), arraysize(kGetSyn), kChunks)); + + MockRead reads[] = { + MockRead(true, reinterpret_cast<const char*>(kGetSynReply), + arraysize(kGetSynReply)), + MockRead(true, reinterpret_cast<const char*>(kGetBodyFrame), + arraysize(kGetBodyFrame)), + MockRead(true, 0, 0) // EOF + }; + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + scoped_refptr<DelayedSocketData> data( + new DelayedSocketData(reads, kChunks, writes.get())); + TransactionHelperResult out = TransactionHelper(request, data.get(), NULL); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!", out.response_data); +} + +TEST_F(FlipNetworkTransactionTest, DISABLED_ConnectFailure) { + MockConnect connects[] = { + MockConnect(true, ERR_NAME_NOT_RESOLVED), + MockConnect(false, ERR_NAME_NOT_RESOLVED), + MockConnect(true, ERR_INTERNET_DISCONNECTED), + MockConnect(false, ERR_INTERNET_DISCONNECTED) + }; + + for (size_t index = 0; index < arraysize(connects); ++index) { + MockWrite writes[] = { + MockWrite(true, reinterpret_cast<const char*>(kGetSyn), + arraysize(kGetSyn)), + MockWrite(true, 0, 0) // EOF + }; + + MockRead reads[] = { + MockRead(true, reinterpret_cast<const char*>(kGetSynReply), + arraysize(kGetSynReply)), + MockRead(true, reinterpret_cast<const char*>(kGetBodyFrame), + arraysize(kGetBodyFrame)), + MockRead(true, 0, 0) // EOF + }; + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + scoped_refptr<DelayedSocketData> data( + new DelayedSocketData(connects[index], reads, 1, writes)); + TransactionHelperResult out = TransactionHelper(request, data.get(), NULL); + EXPECT_EQ(connects[index].result, out.rv); + } +} + +// In this test, we enable compression, but get a uncompressed SynReply from +// the server. Verify that teardown is all clean. +TEST_F(FlipNetworkTransactionTest, DecompressFailureOnSynReply) { + MockWrite writes[] = { + MockWrite(true, reinterpret_cast<const char*>(kGetSynCompressed), + arraysize(kGetSynCompressed)), + MockWrite(true, 0, 0) // EOF + }; + + MockRead reads[] = { + MockRead(true, reinterpret_cast<const char*>(kGetSynReply), + arraysize(kGetSynReply)), + MockRead(true, reinterpret_cast<const char*>(kGetBodyFrame), + arraysize(kGetBodyFrame)), + MockRead(true, 0, 0) // EOF + }; + + // For this test, we turn on the normal compression. + EnableCompression(true); + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + scoped_refptr<DelayedSocketData> data( + new DelayedSocketData(reads, 1, writes)); + TransactionHelperResult out = TransactionHelper(request, data.get(), NULL); + EXPECT_EQ(ERR_SYN_REPLY_NOT_RECEIVED, out.rv); + data->Reset(); + + EnableCompression(false); +} + +// Test that the LoadLog contains good data for a simple GET request. +TEST_F(FlipNetworkTransactionTest, LoadLog) { + MockWrite writes[] = { + MockWrite(true, reinterpret_cast<const char*>(kGetSyn), + arraysize(kGetSyn)), + MockWrite(true, 0, 0) // EOF + }; + + MockRead reads[] = { + MockRead(true, reinterpret_cast<const char*>(kGetSynReply), + arraysize(kGetSynReply)), + MockRead(true, reinterpret_cast<const char*>(kGetBodyFrame), + arraysize(kGetBodyFrame)), + MockRead(true, 0, 0) // EOF + }; + + scoped_refptr<net::LoadLog> log(new net::LoadLog(net::LoadLog::kUnbounded)); + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + scoped_refptr<DelayedSocketData> data( + new DelayedSocketData(reads, 1, writes)); + TransactionHelperResult out = TransactionHelper(request, data.get(), + log); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!", out.response_data); + + // Check that the LoadLog was filled reasonably. + // This test is intentionally non-specific about the exact ordering of + // the log; instead we just check to make sure that certain events exist. + EXPECT_LT(0u, log->entries().size()); + int pos = 0; + // We know the first event at position 0. + EXPECT_TRUE(net::LogContainsBeginEvent( + *log, 0, net::LoadLog::TYPE_FLIP_TRANSACTION_INIT_CONNECTION)); + // For the rest of the events, allow additional events in the middle, + // but expect these to be logged in order. + pos = net::ExpectLogContainsSomewhere(log, 0, + net::LoadLog::TYPE_FLIP_TRANSACTION_INIT_CONNECTION, + net::LoadLog::PHASE_END); + pos = net::ExpectLogContainsSomewhere(log, pos + 1, + net::LoadLog::TYPE_FLIP_TRANSACTION_SEND_REQUEST, + net::LoadLog::PHASE_BEGIN); + pos = net::ExpectLogContainsSomewhere(log, pos + 1, + net::LoadLog::TYPE_FLIP_TRANSACTION_SEND_REQUEST, + net::LoadLog::PHASE_END); + pos = net::ExpectLogContainsSomewhere(log, pos + 1, + net::LoadLog::TYPE_FLIP_TRANSACTION_READ_HEADERS, + net::LoadLog::PHASE_BEGIN); + pos = net::ExpectLogContainsSomewhere(log, pos + 1, + net::LoadLog::TYPE_FLIP_TRANSACTION_READ_HEADERS, + net::LoadLog::PHASE_END); + pos = net::ExpectLogContainsSomewhere(log, pos + 1, + net::LoadLog::TYPE_FLIP_TRANSACTION_READ_BODY, + net::LoadLog::PHASE_BEGIN); + pos = net::ExpectLogContainsSomewhere(log, pos + 1, + net::LoadLog::TYPE_FLIP_TRANSACTION_READ_BODY, + net::LoadLog::PHASE_END); +} + +} // namespace net |