diff options
author | rch@chromium.org <rch@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-03-04 04:12:23 +0000 |
---|---|---|
committer | rch@chromium.org <rch@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-03-04 04:12:23 +0000 |
commit | 448d4ca50bf2216c6217acc13d000f4e451d485a (patch) | |
tree | a747072a0641d9488ef77b2a3c598e17a1f2c604 /net | |
parent | f259ea51f61f8e97117ab142606d33d859de7763 (diff) | |
download | chromium_src-448d4ca50bf2216c6217acc13d000f4e451d485a.zip chromium_src-448d4ca50bf2216c6217acc13d000f4e451d485a.tar.gz chromium_src-448d4ca50bf2216c6217acc13d000f4e451d485a.tar.bz2 |
Fork SPDY/2 and SPDY/3 versions of our SPDY tests, in preparation for landing
spdy 3 framer changes.
Review URL: https://chromiumcodereview.appspot.com/9582034
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@124886 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net')
41 files changed, 39076 insertions, 508 deletions
diff --git a/net/base/upload_data.h b/net/base/upload_data.h index c3ccf83..3bfd427 100644 --- a/net/base/upload_data.h +++ b/net/base/upload_data.h @@ -173,6 +173,12 @@ class NET_EXPORT UploadData FRIEND_TEST_ALL_PREFIXES(UploadDataStreamTest, FileSmallerThanLength); FRIEND_TEST_ALL_PREFIXES(HttpNetworkTransactionTest, UploadFileSmallerThanLength); + FRIEND_TEST_ALL_PREFIXES(HttpNetworkTransactionSpdy2Test, + UploadFileSmallerThanLength); + FRIEND_TEST_ALL_PREFIXES(HttpNetworkTransactionSpdy21Test, + UploadFileSmallerThanLength); + FRIEND_TEST_ALL_PREFIXES(HttpNetworkTransactionSpdy3Test, + UploadFileSmallerThanLength); }; UploadData(); diff --git a/net/http/http_network_transaction.h b/net/http/http_network_transaction.h index 7ad9f7e..f9ae01e 100644 --- a/net/http/http_network_transaction.h +++ b/net/http/http_network_transaction.h @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 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. @@ -85,11 +85,36 @@ class NET_EXPORT_PRIVATE HttpNetworkTransaction HttpStream* stream) OVERRIDE; private: - FRIEND_TEST_ALL_PREFIXES(HttpNetworkTransactionTest, ResetStateForRestart); - FRIEND_TEST_ALL_PREFIXES(SpdyNetworkTransactionTest, WindowUpdateReceived); - FRIEND_TEST_ALL_PREFIXES(SpdyNetworkTransactionTest, WindowUpdateSent); - FRIEND_TEST_ALL_PREFIXES(SpdyNetworkTransactionTest, WindowUpdateOverflow); - FRIEND_TEST_ALL_PREFIXES(SpdyNetworkTransactionTest, FlowControlStallResume); + FRIEND_TEST_ALL_PREFIXES(HttpNetworkTransactionSpdy2Test, + ResetStateForRestart); + FRIEND_TEST_ALL_PREFIXES(HttpNetworkTransactionSpdy21Test, + ResetStateForRestart); + FRIEND_TEST_ALL_PREFIXES(HttpNetworkTransactionSpdy3Test, + ResetStateForRestart); + FRIEND_TEST_ALL_PREFIXES(SpdyNetworkTransactionSpdy2Test, + WindowUpdateReceived); + FRIEND_TEST_ALL_PREFIXES(SpdyNetworkTransactionSpdy2Test, + WindowUpdateSent); + FRIEND_TEST_ALL_PREFIXES(SpdyNetworkTransactionSpdy2Test, + WindowUpdateOverflow); + FRIEND_TEST_ALL_PREFIXES(SpdyNetworkTransactionSpdy2Test, + FlowControlStallResume); + FRIEND_TEST_ALL_PREFIXES(SpdyNetworkTransactionSpdy21Test, + WindowUpdateReceived); + FRIEND_TEST_ALL_PREFIXES(SpdyNetworkTransactionSpdy21Test, + WindowUpdateSent); + FRIEND_TEST_ALL_PREFIXES(SpdyNetworkTransactionSpdy21Test, + WindowUpdateOverflow); + FRIEND_TEST_ALL_PREFIXES(SpdyNetworkTransactionSpdy21Test, + FlowControlStallResume); + FRIEND_TEST_ALL_PREFIXES(SpdyNetworkTransactionSpdy3Test, + WindowUpdateReceived); + FRIEND_TEST_ALL_PREFIXES(SpdyNetworkTransactionSpdy3Test, + WindowUpdateSent); + FRIEND_TEST_ALL_PREFIXES(SpdyNetworkTransactionSpdy3Test, + WindowUpdateOverflow); + FRIEND_TEST_ALL_PREFIXES(SpdyNetworkTransactionSpdy3Test, + FlowControlStallResume); enum State { STATE_CREATE_STREAM, diff --git a/net/http/http_network_transaction_spdy21_unittest.cc b/net/http/http_network_transaction_spdy21_unittest.cc new file mode 100644 index 0000000..3873c84 --- /dev/null +++ b/net/http/http_network_transaction_spdy21_unittest.cc @@ -0,0 +1,9376 @@ +// Copyright (c) 2012 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/http/http_network_transaction.h" + +#include <math.h> // ceil +#include <stdarg.h> +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/memory/scoped_ptr.h" +#include "base/metrics/histogram.h" +#include "base/test/test_file_util.h" +#include "base/utf_string_conversions.h" +#include "net/base/auth.h" +#include "net/base/capturing_net_log.h" +#include "net/base/completion_callback.h" +#include "net/base/host_cache.h" +#include "net/base/mock_host_resolver.h" +#include "net/base/net_log.h" +#include "net/base/net_log_unittest.h" +#include "net/base/request_priority.h" +#include "net/base/ssl_cert_request_info.h" +#include "net/base/ssl_config_service_defaults.h" +#include "net/base/ssl_info.h" +#include "net/base/test_completion_callback.h" +#include "net/base/upload_data.h" +#include "net/http/http_auth_handler_digest.h" +#include "net/http/http_auth_handler_mock.h" +#include "net/http/http_auth_handler_ntlm.h" +#include "net/http/http_basic_stream.h" +#include "net/http/http_net_log_params.h" +#include "net/http/http_network_session.h" +#include "net/http/http_network_session_peer.h" +#include "net/http/http_server_properties_impl.h" +#include "net/http/http_stream.h" +#include "net/http/http_stream_factory.h" +#include "net/http/http_transaction_unittest.h" +#include "net/proxy/proxy_config_service_fixed.h" +#include "net/proxy/proxy_resolver.h" +#include "net/proxy/proxy_service.h" +#include "net/socket/client_socket_factory.h" +#include "net/socket/mock_client_socket_pool_manager.h" +#include "net/socket/socket_test_util.h" +#include "net/socket/ssl_client_socket.h" +#include "net/spdy/spdy_framer.h" +#include "net/spdy/spdy_session.h" +#include "net/spdy/spdy_session_pool.h" +#include "net/spdy/spdy_test_util_spdy2.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/platform_test.h" + +using namespace net::test_spdy2; + +//----------------------------------------------------------------------------- + +namespace { + +const string16 kBar(ASCIIToUTF16("bar")); +const string16 kBar2(ASCIIToUTF16("bar2")); +const string16 kBar3(ASCIIToUTF16("bar3")); +const string16 kBaz(ASCIIToUTF16("baz")); +const string16 kFirst(ASCIIToUTF16("first")); +const string16 kFoo(ASCIIToUTF16("foo")); +const string16 kFoo2(ASCIIToUTF16("foo2")); +const string16 kFoo3(ASCIIToUTF16("foo3")); +const string16 kFou(ASCIIToUTF16("fou")); +const string16 kSecond(ASCIIToUTF16("second")); +const string16 kTestingNTLM(ASCIIToUTF16("testing-ntlm")); +const string16 kWrongPassword(ASCIIToUTF16("wrongpassword")); + +// MakeNextProtos is a utility function that returns a vector of std::strings +// from its arguments. Don't forget to terminate the argument list with a NULL. +std::vector<std::string> MakeNextProtos(const char* a, ...) { + std::vector<std::string> ret; + ret.push_back(a); + + va_list args; + va_start(args, a); + + for (;;) { + const char* value = va_arg(args, const char*); + if (value == NULL) + break; + ret.push_back(value); + } + va_end(args); + + return ret; +} + +// SpdyNextProtos returns a vector of NPN protocol strings for negotiating +// SPDY. +std::vector<std::string> SpdyNextProtos() { + return MakeNextProtos("http/1.1", "spdy/2", "spdy/2.1", NULL); +} + +} // namespace + +namespace net { + +namespace { + +// Helper to manage the lifetimes of the dependencies for a +// HttpNetworkTransaction. +struct SessionDependencies { + // Default set of dependencies -- "null" proxy service. + SessionDependencies() + : host_resolver(new MockHostResolver), + cert_verifier(new CertVerifier), + proxy_service(ProxyService::CreateDirect()), + ssl_config_service(new SSLConfigServiceDefaults), + http_auth_handler_factory( + HttpAuthHandlerFactory::CreateDefault(host_resolver.get())), + net_log(NULL) {} + + // Custom proxy service dependency. + explicit SessionDependencies(ProxyService* proxy_service) + : host_resolver(new MockHostResolver), + cert_verifier(new CertVerifier), + proxy_service(proxy_service), + ssl_config_service(new SSLConfigServiceDefaults), + http_auth_handler_factory( + HttpAuthHandlerFactory::CreateDefault(host_resolver.get())), + net_log(NULL) {} + + scoped_ptr<MockHostResolverBase> host_resolver; + scoped_ptr<CertVerifier> cert_verifier; + scoped_ptr<ProxyService> proxy_service; + scoped_refptr<SSLConfigService> ssl_config_service; + MockClientSocketFactory socket_factory; + scoped_ptr<HttpAuthHandlerFactory> http_auth_handler_factory; + HttpServerPropertiesImpl http_server_properties; + NetLog* net_log; +}; + +HttpNetworkSession* CreateSession(SessionDependencies* session_deps) { + net::HttpNetworkSession::Params params; + params.client_socket_factory = &session_deps->socket_factory; + params.host_resolver = session_deps->host_resolver.get(); + params.cert_verifier = session_deps->cert_verifier.get(); + params.proxy_service = session_deps->proxy_service.get(); + params.ssl_config_service = session_deps->ssl_config_service; + params.http_auth_handler_factory = + session_deps->http_auth_handler_factory.get(); + params.http_server_properties = &session_deps->http_server_properties; + params.net_log = session_deps->net_log; + return new HttpNetworkSession(params); +} + +} // namespace + +class HttpNetworkTransactionSpdy21Test : public PlatformTest { + protected: + struct SimpleGetHelperResult { + int rv; + std::string status_line; + std::string response_data; + }; + + virtual void SetUp() { + NetworkChangeNotifier::NotifyObserversOfIPAddressChangeForTests(); + MessageLoop::current()->RunAllPending(); + spdy::SpdyFramer::set_enable_compression_default(false); + } + + virtual void TearDown() { + NetworkChangeNotifier::NotifyObserversOfIPAddressChangeForTests(); + MessageLoop::current()->RunAllPending(); + spdy::SpdyFramer::set_enable_compression_default(true); + // Empty the current queue. + MessageLoop::current()->RunAllPending(); + PlatformTest::TearDown(); + NetworkChangeNotifier::NotifyObserversOfIPAddressChangeForTests(); + MessageLoop::current()->RunAllPending(); + } + + // Either |write_failure| specifies a write failure or |read_failure| + // specifies a read failure when using a reused socket. In either case, the + // failure should cause the network transaction to resend the request, and the + // other argument should be NULL. + void KeepAliveConnectionResendRequestTest(const MockWrite* write_failure, + const MockRead* read_failure); + + SimpleGetHelperResult SimpleGetHelperForData(StaticSocketDataProvider* data[], + size_t data_count) { + SimpleGetHelperResult out; + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + + SessionDependencies session_deps; + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + for (size_t i = 0; i < data_count; ++i) { + session_deps.socket_factory.AddSocketDataProvider(data[i]); + } + + TestCompletionCallback callback; + + CapturingBoundNetLog log(CapturingNetLog::kUnbounded); + EXPECT_TRUE(log.bound().IsLoggingAllEvents()); + int rv = trans->Start(&request, callback.callback(), log.bound()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + out.rv = callback.WaitForResult(); + if (out.rv != OK) + return out; + + const HttpResponseInfo* response = trans->GetResponseInfo(); + // Can't use ASSERT_* inside helper functions like this, so + // return an error. + if (response == NULL || response->headers == NULL) { + out.rv = ERR_UNEXPECTED; + return out; + } + out.status_line = response->headers->GetStatusLine(); + + EXPECT_EQ("192.0.2.33", response->socket_address.host()); + EXPECT_EQ(0, response->socket_address.port()); + + rv = ReadTransaction(trans.get(), &out.response_data); + EXPECT_EQ(OK, rv); + + net::CapturingNetLog::EntryList entries; + log.GetEntries(&entries); + size_t pos = ExpectLogContainsSomewhere( + entries, 0, NetLog::TYPE_HTTP_TRANSACTION_SEND_REQUEST_HEADERS, + NetLog::PHASE_NONE); + ExpectLogContainsSomewhere( + entries, pos, + NetLog::TYPE_HTTP_TRANSACTION_READ_RESPONSE_HEADERS, + NetLog::PHASE_NONE); + + CapturingNetLog::Entry entry = entries[pos]; + NetLogHttpRequestParameter* request_params = + static_cast<NetLogHttpRequestParameter*>(entry.extra_parameters.get()); + EXPECT_EQ("GET / HTTP/1.1\r\n", request_params->GetLine()); + EXPECT_EQ("Host: www.google.com\r\n" + "Connection: keep-alive\r\n\r\n", + request_params->GetHeaders().ToString()); + + return out; + } + + SimpleGetHelperResult SimpleGetHelper(MockRead data_reads[], + size_t reads_count) { + StaticSocketDataProvider reads(data_reads, reads_count, NULL, 0); + StaticSocketDataProvider* data[] = { &reads }; + return SimpleGetHelperForData(data, 1); + } + + void ConnectStatusHelperWithExpectedStatus(const MockRead& status, + int expected_status); + + void ConnectStatusHelper(const MockRead& status); +}; + +namespace { + +// Fill |str| with a long header list that consumes >= |size| bytes. +void FillLargeHeadersString(std::string* str, int size) { + const char* row = + "SomeHeaderName: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\r\n"; + const int sizeof_row = strlen(row); + const int num_rows = static_cast<int>( + ceil(static_cast<float>(size) / sizeof_row)); + const int sizeof_data = num_rows * sizeof_row; + DCHECK(sizeof_data >= size); + str->reserve(sizeof_data); + + for (int i = 0; i < num_rows; ++i) + str->append(row, sizeof_row); +} + +// Alternative functions that eliminate randomness and dependency on the local +// host name so that the generated NTLM messages are reproducible. +void MockGenerateRandom1(uint8* output, size_t n) { + static const uint8 bytes[] = { + 0x55, 0x29, 0x66, 0x26, 0x6b, 0x9c, 0x73, 0x54 + }; + static size_t current_byte = 0; + for (size_t i = 0; i < n; ++i) { + output[i] = bytes[current_byte++]; + current_byte %= arraysize(bytes); + } +} + +void MockGenerateRandom2(uint8* output, size_t n) { + static const uint8 bytes[] = { + 0x96, 0x79, 0x85, 0xe7, 0x49, 0x93, 0x70, 0xa1, + 0x4e, 0xe7, 0x87, 0x45, 0x31, 0x5b, 0xd3, 0x1f + }; + static size_t current_byte = 0; + for (size_t i = 0; i < n; ++i) { + output[i] = bytes[current_byte++]; + current_byte %= arraysize(bytes); + } +} + +std::string MockGetHostName() { + return "WTC-WIN7"; +} + +template<typename ParentPool> +class CaptureGroupNameSocketPool : public ParentPool { + public: + CaptureGroupNameSocketPool(HostResolver* host_resolver, + CertVerifier* cert_verifier); + + const std::string last_group_name_received() const { + return last_group_name_; + } + + virtual int RequestSocket(const std::string& group_name, + const void* socket_params, + RequestPriority priority, + ClientSocketHandle* handle, + const CompletionCallback& callback, + const BoundNetLog& net_log) { + last_group_name_ = group_name; + return ERR_IO_PENDING; + } + virtual void CancelRequest(const std::string& group_name, + ClientSocketHandle* handle) {} + virtual void ReleaseSocket(const std::string& group_name, + StreamSocket* socket, + int id) {} + virtual void CloseIdleSockets() {} + virtual int IdleSocketCount() const { + return 0; + } + virtual int IdleSocketCountInGroup(const std::string& group_name) const { + return 0; + } + virtual LoadState GetLoadState(const std::string& group_name, + const ClientSocketHandle* handle) const { + return LOAD_STATE_IDLE; + } + virtual base::TimeDelta ConnectionTimeout() const { + return base::TimeDelta(); + } + + private: + std::string last_group_name_; +}; + +typedef CaptureGroupNameSocketPool<TransportClientSocketPool> +CaptureGroupNameTransportSocketPool; +typedef CaptureGroupNameSocketPool<HttpProxyClientSocketPool> +CaptureGroupNameHttpProxySocketPool; +typedef CaptureGroupNameSocketPool<SOCKSClientSocketPool> +CaptureGroupNameSOCKSSocketPool; +typedef CaptureGroupNameSocketPool<SSLClientSocketPool> +CaptureGroupNameSSLSocketPool; + +template<typename ParentPool> +CaptureGroupNameSocketPool<ParentPool>::CaptureGroupNameSocketPool( + HostResolver* host_resolver, + CertVerifier* /* cert_verifier */) + : ParentPool(0, 0, NULL, host_resolver, NULL, NULL) {} + +template<> +CaptureGroupNameHttpProxySocketPool::CaptureGroupNameSocketPool( + HostResolver* host_resolver, + CertVerifier* /* cert_verifier */) + : HttpProxyClientSocketPool(0, 0, NULL, host_resolver, NULL, NULL, NULL) {} + +template<> +CaptureGroupNameSSLSocketPool::CaptureGroupNameSocketPool( + HostResolver* host_resolver, + CertVerifier* cert_verifier) + : SSLClientSocketPool(0, 0, NULL, host_resolver, cert_verifier, NULL, + NULL, NULL, "", NULL, NULL, NULL, NULL, NULL, NULL) {} + +//----------------------------------------------------------------------------- + +// This is the expected return from a current server advertising SPDY. +static const char kAlternateProtocolHttpHeader[] = + "Alternate-Protocol: 443:npn-spdy/2.1\r\n\r\n"; + +// Helper functions for validating that AuthChallengeInfo's are correctly +// configured for common cases. +bool CheckBasicServerAuth(const AuthChallengeInfo* auth_challenge) { + if (!auth_challenge) + return false; + EXPECT_FALSE(auth_challenge->is_proxy); + EXPECT_EQ("www.google.com:80", auth_challenge->challenger.ToString()); + EXPECT_EQ("MyRealm1", auth_challenge->realm); + EXPECT_EQ("basic", auth_challenge->scheme); + return true; +} + +bool CheckBasicProxyAuth(const AuthChallengeInfo* auth_challenge) { + if (!auth_challenge) + return false; + EXPECT_TRUE(auth_challenge->is_proxy); + EXPECT_EQ("myproxy:70", auth_challenge->challenger.ToString()); + EXPECT_EQ("MyRealm1", auth_challenge->realm); + EXPECT_EQ("basic", auth_challenge->scheme); + return true; +} + +bool CheckDigestServerAuth(const AuthChallengeInfo* auth_challenge) { + if (!auth_challenge) + return false; + EXPECT_FALSE(auth_challenge->is_proxy); + EXPECT_EQ("www.google.com:80", auth_challenge->challenger.ToString()); + EXPECT_EQ("digestive", auth_challenge->realm); + EXPECT_EQ("digest", auth_challenge->scheme); + return true; +} + +bool CheckNTLMServerAuth(const AuthChallengeInfo* auth_challenge) { + if (!auth_challenge) + return false; + EXPECT_FALSE(auth_challenge->is_proxy); + EXPECT_EQ("172.22.68.17:80", auth_challenge->challenger.ToString()); + EXPECT_EQ(std::string(), auth_challenge->realm); + EXPECT_EQ("ntlm", auth_challenge->scheme); + return true; +} + +} // namespace + +TEST_F(HttpNetworkTransactionSpdy21Test, Basic) { + SessionDependencies session_deps; + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, SimpleGET) { + MockRead data_reads[] = { + MockRead("HTTP/1.0 200 OK\r\n\r\n"), + MockRead("hello world"), + MockRead(SYNCHRONOUS, OK), + }; + SimpleGetHelperResult out = SimpleGetHelper(data_reads, + arraysize(data_reads)); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.0 200 OK", out.status_line); + EXPECT_EQ("hello world", out.response_data); +} + +// Response with no status line. +TEST_F(HttpNetworkTransactionSpdy21Test, SimpleGETNoHeaders) { + MockRead data_reads[] = { + MockRead("hello world"), + MockRead(SYNCHRONOUS, OK), + }; + SimpleGetHelperResult out = SimpleGetHelper(data_reads, + arraysize(data_reads)); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/0.9 200 OK", out.status_line); + EXPECT_EQ("hello world", out.response_data); +} + +// Allow up to 4 bytes of junk to precede status line. +TEST_F(HttpNetworkTransactionSpdy21Test, StatusLineJunk2Bytes) { + MockRead data_reads[] = { + MockRead("xxxHTTP/1.0 404 Not Found\nServer: blah\n\nDATA"), + MockRead(SYNCHRONOUS, OK), + }; + SimpleGetHelperResult out = SimpleGetHelper(data_reads, + arraysize(data_reads)); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.0 404 Not Found", out.status_line); + EXPECT_EQ("DATA", out.response_data); +} + +// Allow up to 4 bytes of junk to precede status line. +TEST_F(HttpNetworkTransactionSpdy21Test, StatusLineJunk4Bytes) { + MockRead data_reads[] = { + MockRead("\n\nQJHTTP/1.0 404 Not Found\nServer: blah\n\nDATA"), + MockRead(SYNCHRONOUS, OK), + }; + SimpleGetHelperResult out = SimpleGetHelper(data_reads, + arraysize(data_reads)); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.0 404 Not Found", out.status_line); + EXPECT_EQ("DATA", out.response_data); +} + +// Beyond 4 bytes of slop and it should fail to find a status line. +TEST_F(HttpNetworkTransactionSpdy21Test, StatusLineJunk5Bytes) { + MockRead data_reads[] = { + MockRead("xxxxxHTTP/1.1 404 Not Found\nServer: blah"), + MockRead(SYNCHRONOUS, OK), + }; + SimpleGetHelperResult out = SimpleGetHelper(data_reads, + arraysize(data_reads)); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/0.9 200 OK", out.status_line); + EXPECT_EQ("xxxxxHTTP/1.1 404 Not Found\nServer: blah", out.response_data); +} + +// Same as StatusLineJunk4Bytes, except the read chunks are smaller. +TEST_F(HttpNetworkTransactionSpdy21Test, StatusLineJunk4Bytes_Slow) { + MockRead data_reads[] = { + MockRead("\n"), + MockRead("\n"), + MockRead("Q"), + MockRead("J"), + MockRead("HTTP/1.0 404 Not Found\nServer: blah\n\nDATA"), + MockRead(SYNCHRONOUS, OK), + }; + SimpleGetHelperResult out = SimpleGetHelper(data_reads, + arraysize(data_reads)); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.0 404 Not Found", out.status_line); + EXPECT_EQ("DATA", out.response_data); +} + +// Close the connection before enough bytes to have a status line. +TEST_F(HttpNetworkTransactionSpdy21Test, StatusLinePartial) { + MockRead data_reads[] = { + MockRead("HTT"), + MockRead(SYNCHRONOUS, OK), + }; + SimpleGetHelperResult out = SimpleGetHelper(data_reads, + arraysize(data_reads)); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/0.9 200 OK", out.status_line); + EXPECT_EQ("HTT", out.response_data); +} + +// Simulate a 204 response, lacking a Content-Length header, sent over a +// persistent connection. The response should still terminate since a 204 +// cannot have a response body. +TEST_F(HttpNetworkTransactionSpdy21Test, StopsReading204) { + MockRead data_reads[] = { + MockRead("HTTP/1.1 204 No Content\r\n\r\n"), + MockRead("junk"), // Should not be read!! + MockRead(SYNCHRONOUS, OK), + }; + SimpleGetHelperResult out = SimpleGetHelper(data_reads, + arraysize(data_reads)); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 204 No Content", out.status_line); + EXPECT_EQ("", out.response_data); +} + +// A simple request using chunked encoding with some extra data after. +// (Like might be seen in a pipelined response.) +TEST_F(HttpNetworkTransactionSpdy21Test, ChunkedEncoding) { + MockRead data_reads[] = { + MockRead("HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n"), + MockRead("5\r\nHello\r\n"), + MockRead("1\r\n"), + MockRead(" \r\n"), + MockRead("5\r\nworld\r\n"), + MockRead("0\r\n\r\nHTTP/1.1 200 OK\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + SimpleGetHelperResult out = SimpleGetHelper(data_reads, + arraysize(data_reads)); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("Hello world", out.response_data); +} + +// Next tests deal with http://crbug.com/56344. + +TEST_F(HttpNetworkTransactionSpdy21Test, + MultipleContentLengthHeadersNoTransferEncoding) { + MockRead data_reads[] = { + MockRead("HTTP/1.1 200 OK\r\n"), + MockRead("Content-Length: 10\r\n"), + MockRead("Content-Length: 5\r\n\r\n"), + }; + SimpleGetHelperResult out = SimpleGetHelper(data_reads, + arraysize(data_reads)); + EXPECT_EQ(ERR_RESPONSE_HEADERS_MULTIPLE_CONTENT_LENGTH, out.rv); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, + DuplicateContentLengthHeadersNoTransferEncoding) { + MockRead data_reads[] = { + MockRead("HTTP/1.1 200 OK\r\n"), + MockRead("Content-Length: 5\r\n"), + MockRead("Content-Length: 5\r\n\r\n"), + MockRead("Hello"), + }; + SimpleGetHelperResult out = SimpleGetHelper(data_reads, + arraysize(data_reads)); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("Hello", out.response_data); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, + ComplexContentLengthHeadersNoTransferEncoding) { + // More than 2 dupes. + { + MockRead data_reads[] = { + MockRead("HTTP/1.1 200 OK\r\n"), + MockRead("Content-Length: 5\r\n"), + MockRead("Content-Length: 5\r\n"), + MockRead("Content-Length: 5\r\n\r\n"), + MockRead("Hello"), + }; + SimpleGetHelperResult out = SimpleGetHelper(data_reads, + arraysize(data_reads)); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("Hello", out.response_data); + } + // HTTP/1.0 + { + MockRead data_reads[] = { + MockRead("HTTP/1.0 200 OK\r\n"), + MockRead("Content-Length: 5\r\n"), + MockRead("Content-Length: 5\r\n"), + MockRead("Content-Length: 5\r\n\r\n"), + MockRead("Hello"), + }; + SimpleGetHelperResult out = SimpleGetHelper(data_reads, + arraysize(data_reads)); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.0 200 OK", out.status_line); + EXPECT_EQ("Hello", out.response_data); + } + // 2 dupes and one mismatched. + { + MockRead data_reads[] = { + MockRead("HTTP/1.1 200 OK\r\n"), + MockRead("Content-Length: 10\r\n"), + MockRead("Content-Length: 10\r\n"), + MockRead("Content-Length: 5\r\n\r\n"), + }; + SimpleGetHelperResult out = SimpleGetHelper(data_reads, + arraysize(data_reads)); + EXPECT_EQ(ERR_RESPONSE_HEADERS_MULTIPLE_CONTENT_LENGTH, out.rv); + } +} + +TEST_F(HttpNetworkTransactionSpdy21Test, + MultipleContentLengthHeadersTransferEncoding) { + MockRead data_reads[] = { + MockRead("HTTP/1.1 200 OK\r\n"), + MockRead("Content-Length: 666\r\n"), + MockRead("Content-Length: 1337\r\n"), + MockRead("Transfer-Encoding: chunked\r\n\r\n"), + MockRead("5\r\nHello\r\n"), + MockRead("1\r\n"), + MockRead(" \r\n"), + MockRead("5\r\nworld\r\n"), + MockRead("0\r\n\r\nHTTP/1.1 200 OK\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + SimpleGetHelperResult out = SimpleGetHelper(data_reads, + arraysize(data_reads)); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("Hello world", out.response_data); +} + +// Next tests deal with http://crbug.com/98895. + +// Checks that a single Content-Disposition header results in no error. +TEST_F(HttpNetworkTransactionSpdy21Test, SingleContentDispositionHeader) { + MockRead data_reads[] = { + MockRead("HTTP/1.1 200 OK\r\n"), + MockRead("Content-Disposition: attachment;filename=\"salutations.txt\"r\n"), + MockRead("Content-Length: 5\r\n\r\n"), + MockRead("Hello"), + }; + SimpleGetHelperResult out = SimpleGetHelper(data_reads, + arraysize(data_reads)); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("Hello", out.response_data); +} + +// Checks that two identical Content-Disposition headers result in an error. +TEST_F(HttpNetworkTransactionSpdy21Test, + DuplicateIdenticalContentDispositionHeaders) { + MockRead data_reads[] = { + MockRead("HTTP/1.1 200 OK\r\n"), + MockRead("Content-Disposition: attachment;filename=\"greetings.txt\"r\n"), + MockRead("Content-Disposition: attachment;filename=\"greetings.txt\"r\n"), + MockRead("Content-Length: 5\r\n\r\n"), + MockRead("Hello"), + }; + SimpleGetHelperResult out = SimpleGetHelper(data_reads, + arraysize(data_reads)); + EXPECT_EQ(ERR_RESPONSE_HEADERS_MULTIPLE_CONTENT_DISPOSITION, out.rv); +} + +// Checks that two distinct Content-Disposition headers result in an error. +TEST_F(HttpNetworkTransactionSpdy21Test, + DuplicateDistinctContentDispositionHeaders) { + MockRead data_reads[] = { + MockRead("HTTP/1.1 200 OK\r\n"), + MockRead("Content-Disposition: attachment;filename=\"greetings.txt\"r\n"), + MockRead("Content-Disposition: attachment;filename=\"hi.txt\"r\n"), + MockRead("Content-Length: 5\r\n\r\n"), + MockRead("Hello"), + }; + SimpleGetHelperResult out = SimpleGetHelper(data_reads, + arraysize(data_reads)); + EXPECT_EQ(ERR_RESPONSE_HEADERS_MULTIPLE_CONTENT_DISPOSITION, out.rv); +} + +// Checks the behavior of a single Location header. +TEST_F(HttpNetworkTransactionSpdy21Test, SingleLocationHeader) { + MockRead data_reads[] = { + MockRead("HTTP/1.1 302 Redirect\r\n"), + MockRead("Location: http://good.com/\r\n"), + MockRead("Content-Length: 0\r\n\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://redirect.com/"); + request.load_flags = 0; + + SessionDependencies session_deps; + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&data); + + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + EXPECT_EQ(OK, callback.WaitForResult()); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL && response->headers != NULL); + EXPECT_EQ("HTTP/1.1 302 Redirect", response->headers->GetStatusLine()); + std::string url; + EXPECT_TRUE(response->headers->IsRedirect(&url)); + EXPECT_EQ("http://good.com/", url); +} + +// Checks that two identical Location headers result in an error. +TEST_F(HttpNetworkTransactionSpdy21Test, DuplicateIdenticalLocationHeaders) { + MockRead data_reads[] = { + MockRead("HTTP/1.1 302 Redirect\r\n"), + MockRead("Location: http://good.com/\r\n"), + MockRead("Location: http://good.com/\r\n"), + MockRead("Content-Length: 0\r\n\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + SimpleGetHelperResult out = SimpleGetHelper(data_reads, + arraysize(data_reads)); + EXPECT_EQ(ERR_RESPONSE_HEADERS_MULTIPLE_LOCATION, out.rv); +} + +// Checks that two distinct Location headers result in an error. +TEST_F(HttpNetworkTransactionSpdy21Test, DuplicateDistinctLocationHeaders) { + MockRead data_reads[] = { + MockRead("HTTP/1.1 302 Redirect\r\n"), + MockRead("Location: http://good.com/\r\n"), + MockRead("Location: http://evil.com/\r\n"), + MockRead("Content-Length: 0\r\n\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + SimpleGetHelperResult out = SimpleGetHelper(data_reads, + arraysize(data_reads)); + EXPECT_EQ(ERR_RESPONSE_HEADERS_MULTIPLE_LOCATION, out.rv); +} + +// Do a request using the HEAD method. Verify that we don't try to read the +// message body (since HEAD has none). +TEST_F(HttpNetworkTransactionSpdy21Test, Head) { + HttpRequestInfo request; + request.method = "HEAD"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + + SessionDependencies session_deps; + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + MockWrite data_writes1[] = { + MockWrite("HEAD / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n" + "Content-Length: 0\r\n\r\n"), + }; + MockRead data_reads1[] = { + MockRead("HTTP/1.1 404 Not Found\r\n"), + MockRead("Server: Blah\r\n"), + MockRead("Content-Length: 1234\r\n\r\n"), + + // No response body because the test stops reading here. + MockRead(SYNCHRONOUS, ERR_UNEXPECTED), // Should not be reached. + }; + + StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), + data_writes1, arraysize(data_writes1)); + session_deps.socket_factory.AddSocketDataProvider(&data1); + + TestCompletionCallback callback1; + + int rv = trans->Start(&request, callback1.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + + // Check that the headers got parsed. + EXPECT_TRUE(response->headers != NULL); + EXPECT_EQ(1234, response->headers->GetContentLength()); + EXPECT_EQ("HTTP/1.1 404 Not Found", response->headers->GetStatusLine()); + + std::string server_header; + void* iter = NULL; + bool has_server_header = response->headers->EnumerateHeader( + &iter, "Server", &server_header); + EXPECT_TRUE(has_server_header); + EXPECT_EQ("Blah", server_header); + + // Reading should give EOF right away, since there is no message body + // (despite non-zero content-length). + std::string response_data; + rv = ReadTransaction(trans.get(), &response_data); + EXPECT_EQ(OK, rv); + EXPECT_EQ("", response_data); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, ReuseConnection) { + SessionDependencies session_deps; + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + MockRead data_reads[] = { + MockRead("HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\n"), + MockRead("hello"), + MockRead("HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\n"), + MockRead("world"), + MockRead(SYNCHRONOUS, OK), + }; + StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&data); + + const char* const kExpectedResponseData[] = { + "hello", "world" + }; + + for (int i = 0; i < 2; ++i) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + + EXPECT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + + std::string response_data; + rv = ReadTransaction(trans.get(), &response_data); + EXPECT_EQ(OK, rv); + EXPECT_EQ(kExpectedResponseData[i], response_data); + } +} + +TEST_F(HttpNetworkTransactionSpdy21Test, Ignores100) { + HttpRequestInfo request; + request.method = "POST"; + request.url = GURL("http://www.foo.com/"); + request.upload_data = new UploadData; + request.upload_data->AppendBytes("foo", 3); + request.load_flags = 0; + + SessionDependencies session_deps; + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + MockRead data_reads[] = { + MockRead("HTTP/1.0 100 Continue\r\n\r\n"), + MockRead("HTTP/1.0 200 OK\r\n\r\n"), + MockRead("hello world"), + MockRead(SYNCHRONOUS, OK), + }; + StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&data); + + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + + EXPECT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.0 200 OK", response->headers->GetStatusLine()); + + std::string response_data; + rv = ReadTransaction(trans.get(), &response_data); + EXPECT_EQ(OK, rv); + EXPECT_EQ("hello world", response_data); +} + +// This test is almost the same as Ignores100 above, but the response contains +// a 102 instead of a 100. Also, instead of HTTP/1.0 the response is +// HTTP/1.1 and the two status headers are read in one read. +TEST_F(HttpNetworkTransactionSpdy21Test, Ignores1xx) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.foo.com/"); + request.load_flags = 0; + + SessionDependencies session_deps; + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + MockRead data_reads[] = { + MockRead("HTTP/1.1 102 Unspecified status code\r\n\r\n" + "HTTP/1.1 200 OK\r\n\r\n"), + MockRead("hello world"), + MockRead(SYNCHRONOUS, OK), + }; + StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&data); + + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + + EXPECT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + + std::string response_data; + rv = ReadTransaction(trans.get(), &response_data); + EXPECT_EQ(OK, rv); + EXPECT_EQ("hello world", response_data); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, Incomplete100ThenEOF) { + HttpRequestInfo request; + request.method = "POST"; + request.url = GURL("http://www.foo.com/"); + request.load_flags = 0; + + SessionDependencies session_deps; + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + MockRead data_reads[] = { + MockRead(SYNCHRONOUS, "HTTP/1.0 100 Continue\r\n"), + MockRead(ASYNC, 0), + }; + StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&data); + + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + std::string response_data; + rv = ReadTransaction(trans.get(), &response_data); + EXPECT_EQ(OK, rv); + EXPECT_EQ("", response_data); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, EmptyResponse) { + HttpRequestInfo request; + request.method = "POST"; + request.url = GURL("http://www.foo.com/"); + request.load_flags = 0; + + SessionDependencies session_deps; + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + MockRead data_reads[] = { + MockRead(ASYNC, 0), + }; + StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&data); + + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(ERR_EMPTY_RESPONSE, rv); +} + +void HttpNetworkTransactionSpdy21Test::KeepAliveConnectionResendRequestTest( + const MockWrite* write_failure, + const MockRead* read_failure) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.foo.com/"); + request.load_flags = 0; + + SessionDependencies session_deps; + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + // Written data for successfully sending both requests. + MockWrite data1_writes[] = { + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.foo.com\r\n" + "Connection: keep-alive\r\n\r\n"), + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.foo.com\r\n" + "Connection: keep-alive\r\n\r\n") + }; + + // Read results for the first request. + MockRead data1_reads[] = { + MockRead("HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\n"), + MockRead("hello"), + MockRead(ASYNC, OK), + }; + + if (write_failure) { + ASSERT_TRUE(!read_failure); + data1_writes[1] = *write_failure; + } else { + ASSERT_TRUE(read_failure); + data1_reads[2] = *read_failure; + } + + StaticSocketDataProvider data1(data1_reads, arraysize(data1_reads), + data1_writes, arraysize(data1_writes)); + session_deps.socket_factory.AddSocketDataProvider(&data1); + + MockRead data2_reads[] = { + MockRead("HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\n"), + MockRead("world"), + MockRead(ASYNC, OK), + }; + StaticSocketDataProvider data2(data2_reads, arraysize(data2_reads), NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&data2); + + const char* kExpectedResponseData[] = { + "hello", "world" + }; + + for (int i = 0; i < 2; ++i) { + TestCompletionCallback callback; + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + + EXPECT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + + std::string response_data; + rv = ReadTransaction(trans.get(), &response_data); + EXPECT_EQ(OK, rv); + EXPECT_EQ(kExpectedResponseData[i], response_data); + } +} + +TEST_F(HttpNetworkTransactionSpdy21Test, + KeepAliveConnectionNotConnectedOnWrite) { + MockWrite write_failure(ASYNC, ERR_SOCKET_NOT_CONNECTED); + KeepAliveConnectionResendRequestTest(&write_failure, NULL); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, KeepAliveConnectionReset) { + MockRead read_failure(ASYNC, ERR_CONNECTION_RESET); + KeepAliveConnectionResendRequestTest(NULL, &read_failure); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, KeepAliveConnectionEOF) { + MockRead read_failure(SYNCHRONOUS, OK); // EOF + KeepAliveConnectionResendRequestTest(NULL, &read_failure); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, NonKeepAliveConnectionReset) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + + SessionDependencies session_deps; + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + MockRead data_reads[] = { + MockRead(ASYNC, ERR_CONNECTION_RESET), + MockRead("HTTP/1.0 200 OK\r\n\r\n"), // Should not be used + MockRead("hello world"), + MockRead(SYNCHRONOUS, OK), + }; + StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&data); + + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(ERR_CONNECTION_RESET, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + EXPECT_TRUE(response == NULL); +} + +// What do various browsers do when the server closes a non-keepalive +// connection without sending any response header or body? +// +// IE7: error page +// Safari 3.1.2 (Windows): error page +// Firefox 3.0.1: blank page +// Opera 9.52: after five attempts, blank page +// Us with WinHTTP: error page (ERR_INVALID_RESPONSE) +// Us: error page (EMPTY_RESPONSE) +TEST_F(HttpNetworkTransactionSpdy21Test, NonKeepAliveConnectionEOF) { + MockRead data_reads[] = { + MockRead(SYNCHRONOUS, OK), // EOF + MockRead("HTTP/1.0 200 OK\r\n\r\n"), // Should not be used + MockRead("hello world"), + MockRead(SYNCHRONOUS, OK), + }; + SimpleGetHelperResult out = SimpleGetHelper(data_reads, + arraysize(data_reads)); + EXPECT_EQ(ERR_EMPTY_RESPONSE, out.rv); +} + +// Test that we correctly reuse a keep-alive connection after not explicitly +// reading the body. +TEST_F(HttpNetworkTransactionSpdy21Test, KeepAliveAfterUnreadBody) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.foo.com/"); + request.load_flags = 0; + + SessionDependencies session_deps; + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + // Note that because all these reads happen in the same + // StaticSocketDataProvider, it shows that the same socket is being reused for + // all transactions. + MockRead data1_reads[] = { + MockRead("HTTP/1.1 204 No Content\r\n\r\n"), + MockRead("HTTP/1.1 205 Reset Content\r\n\r\n"), + MockRead("HTTP/1.1 304 Not Modified\r\n\r\n"), + MockRead("HTTP/1.1 302 Found\r\n" + "Content-Length: 0\r\n\r\n"), + MockRead("HTTP/1.1 302 Found\r\n" + "Content-Length: 5\r\n\r\n" + "hello"), + MockRead("HTTP/1.1 301 Moved Permanently\r\n" + "Content-Length: 0\r\n\r\n"), + MockRead("HTTP/1.1 301 Moved Permanently\r\n" + "Content-Length: 5\r\n\r\n" + "hello"), + MockRead("HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\n"), + MockRead("hello"), + }; + StaticSocketDataProvider data1(data1_reads, arraysize(data1_reads), NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&data1); + + MockRead data2_reads[] = { + MockRead(SYNCHRONOUS, ERR_UNEXPECTED), // Should not be reached. + }; + StaticSocketDataProvider data2(data2_reads, arraysize(data2_reads), NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&data2); + + const int kNumUnreadBodies = arraysize(data1_reads) - 2; + std::string response_lines[kNumUnreadBodies]; + + for (size_t i = 0; i < arraysize(data1_reads) - 2; ++i) { + TestCompletionCallback callback; + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + + ASSERT_TRUE(response->headers != NULL); + response_lines[i] = response->headers->GetStatusLine(); + + // We intentionally don't read the response bodies. + } + + const char* const kStatusLines[] = { + "HTTP/1.1 204 No Content", + "HTTP/1.1 205 Reset Content", + "HTTP/1.1 304 Not Modified", + "HTTP/1.1 302 Found", + "HTTP/1.1 302 Found", + "HTTP/1.1 301 Moved Permanently", + "HTTP/1.1 301 Moved Permanently", + }; + + COMPILE_ASSERT(kNumUnreadBodies == arraysize(kStatusLines), + forgot_to_update_kStatusLines); + + for (int i = 0; i < kNumUnreadBodies; ++i) + EXPECT_EQ(kStatusLines[i], response_lines[i]); + + TestCompletionCallback callback; + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + std::string response_data; + rv = ReadTransaction(trans.get(), &response_data); + EXPECT_EQ(OK, rv); + EXPECT_EQ("hello", response_data); +} + +// Test the request-challenge-retry sequence for basic auth. +// (basic auth is the easiest to mock, because it has no randomness). +TEST_F(HttpNetworkTransactionSpdy21Test, BasicAuth) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + + SessionDependencies session_deps; + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + MockWrite data_writes1[] = { + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n\r\n"), + }; + + MockRead data_reads1[] = { + MockRead("HTTP/1.0 401 Unauthorized\r\n"), + // Give a couple authenticate options (only the middle one is actually + // supported). + MockRead("WWW-Authenticate: Basic invalid\r\n"), // Malformed. + MockRead("WWW-Authenticate: Basic realm=\"MyRealm1\"\r\n"), + MockRead("WWW-Authenticate: UNSUPPORTED realm=\"FOO\"\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), + // Large content-length -- won't matter, as connection will be reset. + MockRead("Content-Length: 10000\r\n\r\n"), + MockRead(SYNCHRONOUS, ERR_FAILED), + }; + + // After calling trans->RestartWithAuth(), this is the request we should + // be issuing -- the final header line contains the credentials. + MockWrite data_writes2[] = { + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n" + "Authorization: Basic Zm9vOmJhcg==\r\n\r\n"), + }; + + // Lastly, the server responds with the actual content. + MockRead data_reads2[] = { + MockRead("HTTP/1.0 200 OK\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), + MockRead("Content-Length: 100\r\n\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + + StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), + data_writes1, arraysize(data_writes1)); + StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2), + data_writes2, arraysize(data_writes2)); + session_deps.socket_factory.AddSocketDataProvider(&data1); + session_deps.socket_factory.AddSocketDataProvider(&data2); + + TestCompletionCallback callback1; + + int rv = trans->Start(&request, callback1.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(CheckBasicServerAuth(response->auth_challenge.get())); + + TestCompletionCallback callback2; + + rv = trans->RestartWithAuth( + AuthCredentials(kFoo, kBar), callback2.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback2.WaitForResult(); + EXPECT_EQ(OK, rv); + + response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(response->auth_challenge.get() == NULL); + EXPECT_EQ(100, response->headers->GetContentLength()); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, DoNotSendAuth) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = net::LOAD_DO_NOT_SEND_AUTH_DATA; + + SessionDependencies session_deps; + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + MockWrite data_writes[] = { + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n\r\n"), + }; + + MockRead data_reads[] = { + MockRead("HTTP/1.0 401 Unauthorized\r\n"), + MockRead("WWW-Authenticate: Basic realm=\"MyRealm1\"\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), + // Large content-length -- won't matter, as connection will be reset. + MockRead("Content-Length: 10000\r\n\r\n"), + MockRead(SYNCHRONOUS, ERR_FAILED), + }; + + StaticSocketDataProvider data(data_reads, arraysize(data_reads), + data_writes, arraysize(data_writes)); + session_deps.socket_factory.AddSocketDataProvider(&data); + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(0, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(response->auth_challenge.get() == NULL); +} + +// Test the request-challenge-retry sequence for basic auth, over a keep-alive +// connection. +TEST_F(HttpNetworkTransactionSpdy21Test, BasicAuthKeepAlive) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + + SessionDependencies session_deps; + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + MockWrite data_writes1[] = { + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n\r\n"), + + // After calling trans->RestartWithAuth(), this is the request we should + // be issuing -- the final header line contains the credentials. + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n" + "Authorization: Basic Zm9vOmJhcg==\r\n\r\n"), + }; + + MockRead data_reads1[] = { + MockRead("HTTP/1.1 401 Unauthorized\r\n"), + MockRead("WWW-Authenticate: Basic realm=\"MyRealm1\"\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), + MockRead("Content-Length: 14\r\n\r\n"), + MockRead("Unauthorized\r\n"), + + // Lastly, the server responds with the actual content. + MockRead("HTTP/1.1 200 OK\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), + MockRead("Content-Length: 5\r\n\r\n"), + MockRead("Hello"), + }; + + // If there is a regression where we disconnect a Keep-Alive + // connection during an auth roundtrip, we'll end up reading this. + MockRead data_reads2[] = { + MockRead(SYNCHRONOUS, ERR_FAILED), + }; + + StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), + data_writes1, arraysize(data_writes1)); + StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2), + NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&data1); + session_deps.socket_factory.AddSocketDataProvider(&data2); + + TestCompletionCallback callback1; + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + int rv = trans->Start(&request, callback1.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(CheckBasicServerAuth(response->auth_challenge.get())); + + TestCompletionCallback callback2; + + rv = trans->RestartWithAuth( + AuthCredentials(kFoo, kBar), callback2.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback2.WaitForResult(); + EXPECT_EQ(OK, rv); + + response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(response->auth_challenge.get() == NULL); + EXPECT_EQ(5, response->headers->GetContentLength()); +} + +// Test the request-challenge-retry sequence for basic auth, over a keep-alive +// connection and with no response body to drain. +TEST_F(HttpNetworkTransactionSpdy21Test, BasicAuthKeepAliveNoBody) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + + SessionDependencies session_deps; + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + MockWrite data_writes1[] = { + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n\r\n"), + + // After calling trans->RestartWithAuth(), this is the request we should + // be issuing -- the final header line contains the credentials. + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n" + "Authorization: Basic Zm9vOmJhcg==\r\n\r\n"), + }; + + MockRead data_reads1[] = { + MockRead("HTTP/1.1 401 Unauthorized\r\n"), + MockRead("WWW-Authenticate: Basic realm=\"MyRealm1\"\r\n"), + MockRead("Content-Length: 0\r\n\r\n"), // No response body. + + // Lastly, the server responds with the actual content. + MockRead("HTTP/1.1 200 OK\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), + MockRead("Content-Length: 5\r\n\r\n"), + MockRead("hello"), + }; + + // An incorrect reconnect would cause this to be read. + MockRead data_reads2[] = { + MockRead(SYNCHRONOUS, ERR_FAILED), + }; + + StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), + data_writes1, arraysize(data_writes1)); + StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2), + NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&data1); + session_deps.socket_factory.AddSocketDataProvider(&data2); + + TestCompletionCallback callback1; + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + int rv = trans->Start(&request, callback1.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(CheckBasicServerAuth(response->auth_challenge.get())); + + TestCompletionCallback callback2; + + rv = trans->RestartWithAuth( + AuthCredentials(kFoo, kBar), callback2.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback2.WaitForResult(); + EXPECT_EQ(OK, rv); + + response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(response->auth_challenge.get() == NULL); + EXPECT_EQ(5, response->headers->GetContentLength()); +} + +// Test the request-challenge-retry sequence for basic auth, over a keep-alive +// connection and with a large response body to drain. +TEST_F(HttpNetworkTransactionSpdy21Test, BasicAuthKeepAliveLargeBody) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + + SessionDependencies session_deps; + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + MockWrite data_writes1[] = { + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n\r\n"), + + // After calling trans->RestartWithAuth(), this is the request we should + // be issuing -- the final header line contains the credentials. + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n" + "Authorization: Basic Zm9vOmJhcg==\r\n\r\n"), + }; + + // Respond with 5 kb of response body. + std::string large_body_string("Unauthorized"); + large_body_string.append(5 * 1024, ' '); + large_body_string.append("\r\n"); + + MockRead data_reads1[] = { + MockRead("HTTP/1.1 401 Unauthorized\r\n"), + MockRead("WWW-Authenticate: Basic realm=\"MyRealm1\"\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), + // 5134 = 12 + 5 * 1024 + 2 + MockRead("Content-Length: 5134\r\n\r\n"), + MockRead(ASYNC, large_body_string.data(), large_body_string.size()), + + // Lastly, the server responds with the actual content. + MockRead("HTTP/1.1 200 OK\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), + MockRead("Content-Length: 5\r\n\r\n"), + MockRead("hello"), + }; + + // An incorrect reconnect would cause this to be read. + MockRead data_reads2[] = { + MockRead(SYNCHRONOUS, ERR_FAILED), + }; + + StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), + data_writes1, arraysize(data_writes1)); + StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2), + NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&data1); + session_deps.socket_factory.AddSocketDataProvider(&data2); + + TestCompletionCallback callback1; + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + int rv = trans->Start(&request, callback1.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(CheckBasicServerAuth(response->auth_challenge.get())); + + TestCompletionCallback callback2; + + rv = trans->RestartWithAuth( + AuthCredentials(kFoo, kBar), callback2.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback2.WaitForResult(); + EXPECT_EQ(OK, rv); + + response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(response->auth_challenge.get() == NULL); + EXPECT_EQ(5, response->headers->GetContentLength()); +} + +// Test the request-challenge-retry sequence for basic auth, over a keep-alive +// connection, but the server gets impatient and closes the connection. +TEST_F(HttpNetworkTransactionSpdy21Test, BasicAuthKeepAliveImpatientServer) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + + SessionDependencies session_deps; + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + MockWrite data_writes1[] = { + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n\r\n"), + // This simulates the seemingly successful write to a closed connection + // if the bug is not fixed. + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n" + "Authorization: Basic Zm9vOmJhcg==\r\n\r\n"), + }; + + MockRead data_reads1[] = { + MockRead("HTTP/1.1 401 Unauthorized\r\n"), + MockRead("WWW-Authenticate: Basic realm=\"MyRealm1\"\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), + MockRead("Content-Length: 14\r\n\r\n"), + // Tell MockTCPClientSocket to simulate the server closing the connection. + MockRead(SYNCHRONOUS, ERR_TEST_PEER_CLOSE_AFTER_NEXT_MOCK_READ), + MockRead("Unauthorized\r\n"), + MockRead(SYNCHRONOUS, OK), // The server closes the connection. + }; + + // After calling trans->RestartWithAuth(), this is the request we should + // be issuing -- the final header line contains the credentials. + MockWrite data_writes2[] = { + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n" + "Authorization: Basic Zm9vOmJhcg==\r\n\r\n"), + }; + + // Lastly, the server responds with the actual content. + MockRead data_reads2[] = { + MockRead("HTTP/1.1 200 OK\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), + MockRead("Content-Length: 5\r\n\r\n"), + MockRead("hello"), + }; + + StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), + data_writes1, arraysize(data_writes1)); + StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2), + data_writes2, arraysize(data_writes2)); + session_deps.socket_factory.AddSocketDataProvider(&data1); + session_deps.socket_factory.AddSocketDataProvider(&data2); + + TestCompletionCallback callback1; + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + int rv = trans->Start(&request, callback1.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(CheckBasicServerAuth(response->auth_challenge.get())); + + TestCompletionCallback callback2; + + rv = trans->RestartWithAuth( + AuthCredentials(kFoo, kBar), callback2.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback2.WaitForResult(); + EXPECT_EQ(OK, rv); + + response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(response->auth_challenge.get() == NULL); + EXPECT_EQ(5, response->headers->GetContentLength()); +} + +// Test the request-challenge-retry sequence for basic auth, over a connection +// that requires a restart when setting up an SSL tunnel. +TEST_F(HttpNetworkTransactionSpdy21Test, BasicAuthProxyNoKeepAlive) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("https://www.google.com/"); + // when the no authentication data flag is set. + request.load_flags = net::LOAD_DO_NOT_SEND_AUTH_DATA; + + // Configure against proxy server "myproxy:70". + SessionDependencies session_deps(ProxyService::CreateFixed("myproxy:70")); + CapturingBoundNetLog log(CapturingNetLog::kUnbounded); + session_deps.net_log = log.bound().net_log(); + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + // Since we have proxy, should try to establish tunnel. + MockWrite data_writes1[] = { + MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n\r\n"), + + // After calling trans->RestartWithAuth(), this is the request we should + // be issuing -- the final header line contains the credentials. + MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n" + "Proxy-Authorization: Basic Zm9vOmJhcg==\r\n\r\n"), + + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n\r\n"), + }; + + // The proxy responds to the connect with a 407, using a persistent + // connection. + MockRead data_reads1[] = { + // No credentials. + MockRead("HTTP/1.1 407 Proxy Authentication Required\r\n"), + MockRead("Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n"), + MockRead("Proxy-Connection: close\r\n\r\n"), + + MockRead("HTTP/1.1 200 Connection Established\r\n\r\n"), + + MockRead("HTTP/1.1 200 OK\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), + MockRead("Content-Length: 5\r\n\r\n"), + MockRead(SYNCHRONOUS, "hello"), + }; + + StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), + data_writes1, arraysize(data_writes1)); + session_deps.socket_factory.AddSocketDataProvider(&data1); + SSLSocketDataProvider ssl(ASYNC, OK); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); + + TestCompletionCallback callback1; + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + int rv = trans->Start(&request, callback1.callback(), log.bound()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(OK, rv); + net::CapturingNetLog::EntryList entries; + log.GetEntries(&entries); + size_t pos = ExpectLogContainsSomewhere( + entries, 0, NetLog::TYPE_HTTP_TRANSACTION_SEND_TUNNEL_HEADERS, + NetLog::PHASE_NONE); + ExpectLogContainsSomewhere( + entries, pos, + NetLog::TYPE_HTTP_TRANSACTION_READ_TUNNEL_RESPONSE_HEADERS, + NetLog::PHASE_NONE); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_FALSE(response->headers == NULL); + EXPECT_EQ(407, response->headers->response_code()); + EXPECT_TRUE(HttpVersion(1, 1) == response->headers->GetHttpVersion()); + EXPECT_TRUE(CheckBasicProxyAuth(response->auth_challenge.get())); + + TestCompletionCallback callback2; + + rv = trans->RestartWithAuth( + AuthCredentials(kFoo, kBar), callback2.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback2.WaitForResult(); + EXPECT_EQ(OK, rv); + + response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + + EXPECT_TRUE(response->headers->IsKeepAlive()); + EXPECT_EQ(200, response->headers->response_code()); + EXPECT_EQ(5, response->headers->GetContentLength()); + EXPECT_TRUE(HttpVersion(1, 1) == response->headers->GetHttpVersion()); + + // The password prompt info should not be set. + EXPECT_TRUE(response->auth_challenge.get() == NULL); + + trans.reset(); + session->CloseAllConnections(); +} + +// Test the request-challenge-retry sequence for basic auth, over a keep-alive +// proxy connection, when setting up an SSL tunnel. +TEST_F(HttpNetworkTransactionSpdy21Test, BasicAuthProxyKeepAlive) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("https://www.google.com/"); + // Ensure that proxy authentication is attempted even + // when the no authentication data flag is set. + request.load_flags = net::LOAD_DO_NOT_SEND_AUTH_DATA; + + // Configure against proxy server "myproxy:70". + SessionDependencies session_deps(ProxyService::CreateFixed("myproxy:70")); + CapturingBoundNetLog log(CapturingNetLog::kUnbounded); + session_deps.net_log = log.bound().net_log(); + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + // Since we have proxy, should try to establish tunnel. + MockWrite data_writes1[] = { + MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n\r\n"), + + // After calling trans->RestartWithAuth(), this is the request we should + // be issuing -- the final header line contains the credentials. + MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n" + "Proxy-Authorization: Basic Zm9vOmJheg==\r\n\r\n"), + }; + + // The proxy responds to the connect with a 407, using a persistent + // connection. + MockRead data_reads1[] = { + // No credentials. + MockRead("HTTP/1.1 407 Proxy Authentication Required\r\n"), + MockRead("Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n"), + MockRead("Content-Length: 10\r\n\r\n"), + MockRead("0123456789"), + + // Wrong credentials (wrong password). + MockRead("HTTP/1.1 407 Proxy Authentication Required\r\n"), + MockRead("Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n"), + MockRead("Content-Length: 10\r\n\r\n"), + // No response body because the test stops reading here. + MockRead(SYNCHRONOUS, ERR_UNEXPECTED), // Should not be reached. + }; + + StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), + data_writes1, arraysize(data_writes1)); + session_deps.socket_factory.AddSocketDataProvider(&data1); + + TestCompletionCallback callback1; + + int rv = trans->Start(&request, callback1.callback(), log.bound()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(OK, rv); + net::CapturingNetLog::EntryList entries; + log.GetEntries(&entries); + size_t pos = ExpectLogContainsSomewhere( + entries, 0, NetLog::TYPE_HTTP_TRANSACTION_SEND_TUNNEL_HEADERS, + NetLog::PHASE_NONE); + ExpectLogContainsSomewhere( + entries, pos, + NetLog::TYPE_HTTP_TRANSACTION_READ_TUNNEL_RESPONSE_HEADERS, + NetLog::PHASE_NONE); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_FALSE(response->headers == NULL); + EXPECT_TRUE(response->headers->IsKeepAlive()); + EXPECT_EQ(407, response->headers->response_code()); + EXPECT_EQ(10, response->headers->GetContentLength()); + EXPECT_TRUE(HttpVersion(1, 1) == response->headers->GetHttpVersion()); + EXPECT_TRUE(CheckBasicProxyAuth(response->auth_challenge.get())); + + TestCompletionCallback callback2; + + // Wrong password (should be "bar"). + rv = trans->RestartWithAuth( + AuthCredentials(kFoo, kBaz), callback2.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback2.WaitForResult(); + EXPECT_EQ(OK, rv); + + response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_FALSE(response->headers == NULL); + EXPECT_TRUE(response->headers->IsKeepAlive()); + EXPECT_EQ(407, response->headers->response_code()); + EXPECT_EQ(10, response->headers->GetContentLength()); + EXPECT_TRUE(HttpVersion(1, 1) == response->headers->GetHttpVersion()); + EXPECT_TRUE(CheckBasicProxyAuth(response->auth_challenge.get())); + + // Flush the idle socket before the NetLog and HttpNetworkTransaction go + // out of scope. + session->CloseAllConnections(); +} + +// Test that we don't read the response body when we fail to establish a tunnel, +// even if the user cancels the proxy's auth attempt. +TEST_F(HttpNetworkTransactionSpdy21Test, BasicAuthProxyCancelTunnel) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("https://www.google.com/"); + request.load_flags = 0; + + // Configure against proxy server "myproxy:70". + SessionDependencies session_deps(ProxyService::CreateFixed("myproxy:70")); + + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + // Since we have proxy, should try to establish tunnel. + MockWrite data_writes[] = { + MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n\r\n"), + }; + + // The proxy responds to the connect with a 407. + MockRead data_reads[] = { + MockRead("HTTP/1.1 407 Proxy Authentication Required\r\n"), + MockRead("Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n"), + MockRead("Content-Length: 10\r\n\r\n"), + MockRead(SYNCHRONOUS, ERR_UNEXPECTED), // Should not be reached. + }; + + StaticSocketDataProvider data(data_reads, arraysize(data_reads), + data_writes, arraysize(data_writes)); + session_deps.socket_factory.AddSocketDataProvider(&data); + + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + + EXPECT_TRUE(response->headers->IsKeepAlive()); + EXPECT_EQ(407, response->headers->response_code()); + EXPECT_EQ(10, response->headers->GetContentLength()); + EXPECT_TRUE(HttpVersion(1, 1) == response->headers->GetHttpVersion()); + + std::string response_data; + rv = ReadTransaction(trans.get(), &response_data); + EXPECT_EQ(ERR_TUNNEL_CONNECTION_FAILED, rv); + + // Flush the idle socket before the HttpNetworkTransaction goes out of scope. + session->CloseAllConnections(); +} + +// Test when a server (non-proxy) returns a 407 (proxy-authenticate). +// The request should fail with ERR_UNEXPECTED_PROXY_AUTH. +TEST_F(HttpNetworkTransactionSpdy21Test, UnexpectedProxyAuth) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + + // We are using a DIRECT connection (i.e. no proxy) for this session. + SessionDependencies session_deps; + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + MockWrite data_writes1[] = { + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n\r\n"), + }; + + MockRead data_reads1[] = { + MockRead("HTTP/1.0 407 Proxy Auth required\r\n"), + MockRead("Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n"), + // Large content-length -- won't matter, as connection will be reset. + MockRead("Content-Length: 10000\r\n\r\n"), + MockRead(SYNCHRONOUS, ERR_FAILED), + }; + + StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), + data_writes1, arraysize(data_writes1)); + session_deps.socket_factory.AddSocketDataProvider(&data1); + + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(ERR_UNEXPECTED_PROXY_AUTH, rv); +} + +// Tests when an HTTPS server (non-proxy) returns a 407 (proxy-authentication) +// through a non-authenticating proxy. The request should fail with +// ERR_UNEXPECTED_PROXY_AUTH. +// Note that it is impossible to detect if an HTTP server returns a 407 through +// a non-authenticating proxy - there is nothing to indicate whether the +// response came from the proxy or the server, so it is treated as if the proxy +// issued the challenge. +TEST_F(HttpNetworkTransactionSpdy21Test, + HttpsServerRequestsProxyAuthThroughProxy) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("https://www.google.com/"); + + SessionDependencies session_deps(ProxyService::CreateFixed("myproxy:70")); + CapturingBoundNetLog log(CapturingNetLog::kUnbounded); + session_deps.net_log = log.bound().net_log(); + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + // Since we have proxy, should try to establish tunnel. + MockWrite data_writes1[] = { + MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n\r\n"), + + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n\r\n"), + }; + + MockRead data_reads1[] = { + MockRead("HTTP/1.1 200 Connection Established\r\n\r\n"), + + MockRead("HTTP/1.1 407 Unauthorized\r\n"), + MockRead("Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n"), + MockRead("\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + + StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), + data_writes1, arraysize(data_writes1)); + session_deps.socket_factory.AddSocketDataProvider(&data1); + SSLSocketDataProvider ssl(ASYNC, OK); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); + + TestCompletionCallback callback1; + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + int rv = trans->Start(&request, callback1.callback(), log.bound()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(ERR_UNEXPECTED_PROXY_AUTH, rv); + net::CapturingNetLog::EntryList entries; + log.GetEntries(&entries); + size_t pos = ExpectLogContainsSomewhere( + entries, 0, NetLog::TYPE_HTTP_TRANSACTION_SEND_TUNNEL_HEADERS, + NetLog::PHASE_NONE); + ExpectLogContainsSomewhere( + entries, pos, + NetLog::TYPE_HTTP_TRANSACTION_READ_TUNNEL_RESPONSE_HEADERS, + NetLog::PHASE_NONE); +} + +// Test a simple get through an HTTPS Proxy. +TEST_F(HttpNetworkTransactionSpdy21Test, HttpsProxyGet) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + + // Configure against https proxy server "proxy:70". + SessionDependencies session_deps(ProxyService::CreateFixed( + "https://proxy:70")); + CapturingBoundNetLog log(CapturingNetLog::kUnbounded); + session_deps.net_log = log.bound().net_log(); + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + // Since we have proxy, should use full url + MockWrite data_writes1[] = { + MockWrite("GET http://www.google.com/ HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n\r\n"), + }; + + MockRead data_reads1[] = { + MockRead("HTTP/1.1 200 OK\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), + MockRead("Content-Length: 100\r\n\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + + StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), + data_writes1, arraysize(data_writes1)); + session_deps.socket_factory.AddSocketDataProvider(&data1); + SSLSocketDataProvider ssl(ASYNC, OK); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); + + TestCompletionCallback callback1; + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + int rv = trans->Start(&request, callback1.callback(), log.bound()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + + EXPECT_TRUE(response->headers->IsKeepAlive()); + EXPECT_EQ(200, response->headers->response_code()); + EXPECT_EQ(100, response->headers->GetContentLength()); + EXPECT_TRUE(HttpVersion(1, 1) == response->headers->GetHttpVersion()); + + // The password prompt info should not be set. + EXPECT_TRUE(response->auth_challenge.get() == NULL); +} + +// Test a SPDY get through an HTTPS Proxy. +TEST_F(HttpNetworkTransactionSpdy21Test, HttpsProxySpdyGet) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + + // Configure against https proxy server "proxy:70". + SessionDependencies session_deps(ProxyService::CreateFixed( + "https://proxy:70")); + CapturingBoundNetLog log(CapturingNetLog::kUnbounded); + session_deps.net_log = log.bound().net_log(); + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + // fetch http://www.google.com/ via SPDY + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST, + false)); + MockWrite spdy_writes[] = { CreateMockWrite(*req) }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> data(ConstructSpdyBodyFrame(1, true)); + MockRead spdy_reads[] = { + CreateMockRead(*resp), + CreateMockRead(*data), + MockRead(ASYNC, 0, 0), + }; + + scoped_ptr<DelayedSocketData> spdy_data( + new DelayedSocketData( + 1, // wait for one write to finish before reading. + spdy_reads, arraysize(spdy_reads), + spdy_writes, arraysize(spdy_writes))); + session_deps.socket_factory.AddSocketDataProvider(spdy_data.get()); + + SSLSocketDataProvider ssl(ASYNC, OK); + ssl.SetNextProto(SSLClientSocket::kProtoSPDY21); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); + + TestCompletionCallback callback1; + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + int rv = trans->Start(&request, callback1.callback(), log.bound()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + + std::string response_data; + ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data)); + EXPECT_EQ(kUploadData, response_data); +} + +// Test a SPDY get through an HTTPS Proxy. +TEST_F(HttpNetworkTransactionSpdy21Test, HttpsProxySpdyGetWithProxyAuth) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + + // Configure against https proxy server "myproxy:70". + SessionDependencies session_deps( + ProxyService::CreateFixed("https://myproxy:70")); + CapturingBoundNetLog log(CapturingNetLog::kUnbounded); + session_deps.net_log = log.bound().net_log(); + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + // The first request will be a bare GET, the second request will be a + // GET with a Proxy-Authorization header. + scoped_ptr<spdy::SpdyFrame> req_get( + ConstructSpdyGet(NULL, 0, false, 1, LOWEST, false)); + const char* const kExtraAuthorizationHeaders[] = { + "proxy-authorization", + "Basic Zm9vOmJhcg==", + }; + scoped_ptr<spdy::SpdyFrame> req_get_authorization( + ConstructSpdyGet( + kExtraAuthorizationHeaders, arraysize(kExtraAuthorizationHeaders)/2, + false, 3, LOWEST, false)); + MockWrite spdy_writes[] = { + CreateMockWrite(*req_get, 1), + CreateMockWrite(*req_get_authorization, 4), + }; + + // The first response is a 407 proxy authentication challenge, and the second + // response will be a 200 response since the second request includes a valid + // Authorization header. + const char* const kExtraAuthenticationHeaders[] = { + "proxy-authenticate", + "Basic realm=\"MyRealm1\"" + }; + scoped_ptr<spdy::SpdyFrame> resp_authentication( + ConstructSpdySynReplyError( + "407 Proxy Authentication Required", + kExtraAuthenticationHeaders, arraysize(kExtraAuthenticationHeaders)/2, + 1)); + scoped_ptr<spdy::SpdyFrame> body_authentication( + ConstructSpdyBodyFrame(1, true)); + scoped_ptr<spdy::SpdyFrame> resp_data(ConstructSpdyGetSynReply(NULL, 0, 3)); + scoped_ptr<spdy::SpdyFrame> body_data(ConstructSpdyBodyFrame(3, true)); + MockRead spdy_reads[] = { + CreateMockRead(*resp_authentication, 2), + CreateMockRead(*body_authentication, 3), + CreateMockRead(*resp_data, 5), + CreateMockRead(*body_data, 6), + MockRead(ASYNC, 0, 7), + }; + + scoped_ptr<OrderedSocketData> data( + new OrderedSocketData(spdy_reads, arraysize(spdy_reads), + spdy_writes, arraysize(spdy_writes))); + session_deps.socket_factory.AddSocketDataProvider(data.get()); + + SSLSocketDataProvider ssl(ASYNC, OK); + ssl.SetNextProto(SSLClientSocket::kProtoSPDY21); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); + + TestCompletionCallback callback1; + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + int rv = trans->Start(&request, callback1.callback(), log.bound()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* const response = trans->GetResponseInfo(); + + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers != NULL); + EXPECT_EQ(407, response->headers->response_code()); + EXPECT_TRUE(response->was_fetched_via_spdy); + EXPECT_TRUE(CheckBasicProxyAuth(response->auth_challenge.get())); + + TestCompletionCallback callback2; + + rv = trans->RestartWithAuth( + AuthCredentials(kFoo, kBar), callback2.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback2.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* const response_restart = trans->GetResponseInfo(); + + ASSERT_TRUE(response_restart != NULL); + ASSERT_TRUE(response_restart->headers != NULL); + EXPECT_EQ(200, response_restart->headers->response_code()); + // The password prompt info should not be set. + EXPECT_TRUE(response_restart->auth_challenge.get() == NULL); +} + +// Test a SPDY CONNECT through an HTTPS Proxy to an HTTPS (non-SPDY) Server. +TEST_F(HttpNetworkTransactionSpdy21Test, HttpsProxySpdyConnectHttps) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("https://www.google.com/"); + request.load_flags = 0; + + // Configure against https proxy server "proxy:70". + SessionDependencies session_deps(ProxyService::CreateFixed( + "https://proxy:70")); + CapturingBoundNetLog log(CapturingNetLog::kUnbounded); + session_deps.net_log = log.bound().net_log(); + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + // CONNECT to www.google.com:443 via SPDY + scoped_ptr<spdy::SpdyFrame> connect(ConstructSpdyConnect(NULL, 0, 1)); + // fetch https://www.google.com/ via HTTP + + const char get[] = "GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n\r\n"; + scoped_ptr<spdy::SpdyFrame> wrapped_get( + ConstructSpdyBodyFrame(1, get, strlen(get), false)); + scoped_ptr<spdy::SpdyFrame> conn_resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + const char resp[] = "HTTP/1.1 200 OK\r\n" + "Content-Length: 10\r\n\r\n"; + scoped_ptr<spdy::SpdyFrame> wrapped_get_resp( + ConstructSpdyBodyFrame(1, resp, strlen(resp), false)); + scoped_ptr<spdy::SpdyFrame> wrapped_body( + ConstructSpdyBodyFrame(1, "1234567890", 10, false)); + scoped_ptr<spdy::SpdyFrame> window_update( + ConstructSpdyWindowUpdate(1, wrapped_get_resp->length())); + + MockWrite spdy_writes[] = { + CreateMockWrite(*connect, 1), + CreateMockWrite(*wrapped_get, 3), + CreateMockWrite(*window_update, 5) + }; + + MockRead spdy_reads[] = { + CreateMockRead(*conn_resp, 2, ASYNC), + CreateMockRead(*wrapped_get_resp, 4, ASYNC), + CreateMockRead(*wrapped_body, 6, ASYNC), + CreateMockRead(*wrapped_body, 7, ASYNC), + MockRead(ASYNC, 0, 8), + }; + + scoped_ptr<OrderedSocketData> spdy_data( + new OrderedSocketData( + spdy_reads, arraysize(spdy_reads), + spdy_writes, arraysize(spdy_writes))); + session_deps.socket_factory.AddSocketDataProvider(spdy_data.get()); + + SSLSocketDataProvider ssl(ASYNC, OK); + ssl.SetNextProto(SSLClientSocket::kProtoSPDY21); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); + SSLSocketDataProvider ssl2(ASYNC, OK); + ssl2.was_npn_negotiated = false; + ssl2.protocol_negotiated = SSLClientSocket::kProtoUnknown; + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl2); + + TestCompletionCallback callback1; + + int rv = trans->Start(&request, callback1.callback(), log.bound()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + + std::string response_data; + ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data)); + EXPECT_EQ("1234567890", response_data); +} + +// Test a SPDY CONNECT through an HTTPS Proxy to a SPDY server. +TEST_F(HttpNetworkTransactionSpdy21Test, HttpsProxySpdyConnectSpdy) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("https://www.google.com/"); + request.load_flags = 0; + + // Configure against https proxy server "proxy:70". + SessionDependencies session_deps(ProxyService::CreateFixed( + "https://proxy:70")); + CapturingBoundNetLog log(CapturingNetLog::kUnbounded); + session_deps.net_log = log.bound().net_log(); + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + // CONNECT to www.google.com:443 via SPDY + scoped_ptr<spdy::SpdyFrame> connect(ConstructSpdyConnect(NULL, 0, 1)); + // fetch https://www.google.com/ via SPDY + const char* const kMyUrl = "https://www.google.com/"; + scoped_ptr<spdy::SpdyFrame> get(ConstructSpdyGet(kMyUrl, false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> wrapped_get(ConstructWrappedSpdyFrame(get, 1)); + scoped_ptr<spdy::SpdyFrame> conn_resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> get_resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> wrapped_get_resp( + ConstructWrappedSpdyFrame(get_resp, 1)); + scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true)); + scoped_ptr<spdy::SpdyFrame> wrapped_body(ConstructWrappedSpdyFrame(body, 1)); + scoped_ptr<spdy::SpdyFrame> window_update_get_resp( + ConstructSpdyWindowUpdate(1, wrapped_get_resp->length())); + scoped_ptr<spdy::SpdyFrame> window_update_body( + ConstructSpdyWindowUpdate(1, wrapped_body->length())); + + MockWrite spdy_writes[] = { + CreateMockWrite(*connect, 1), + CreateMockWrite(*wrapped_get, 3), + CreateMockWrite(*window_update_get_resp, 5), + CreateMockWrite(*window_update_body, 7), + }; + + MockRead spdy_reads[] = { + CreateMockRead(*conn_resp, 2, ASYNC), + CreateMockRead(*wrapped_get_resp, 4, ASYNC), + CreateMockRead(*wrapped_body, 6, ASYNC), + MockRead(ASYNC, 0, 8), + }; + + scoped_ptr<OrderedSocketData> spdy_data( + new OrderedSocketData( + spdy_reads, arraysize(spdy_reads), + spdy_writes, arraysize(spdy_writes))); + session_deps.socket_factory.AddSocketDataProvider(spdy_data.get()); + + SSLSocketDataProvider ssl(ASYNC, OK); + ssl.SetNextProto(SSLClientSocket::kProtoSPDY21); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); + SSLSocketDataProvider ssl2(ASYNC, OK); + ssl2.SetNextProto(SSLClientSocket::kProtoSPDY21); + ssl2.protocol_negotiated = SSLClientSocket::kProtoSPDY21; + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl2); + + TestCompletionCallback callback1; + + int rv = trans->Start(&request, callback1.callback(), log.bound()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + + std::string response_data; + ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data)); + EXPECT_EQ(kUploadData, response_data); +} + +// Test a SPDY CONNECT failure through an HTTPS Proxy. +TEST_F(HttpNetworkTransactionSpdy21Test, HttpsProxySpdyConnectFailure) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("https://www.google.com/"); + request.load_flags = 0; + + // Configure against https proxy server "proxy:70". + SessionDependencies session_deps(ProxyService::CreateFixed( + "https://proxy:70")); + CapturingBoundNetLog log(CapturingNetLog::kUnbounded); + session_deps.net_log = log.bound().net_log(); + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + // CONNECT to www.google.com:443 via SPDY + scoped_ptr<spdy::SpdyFrame> connect(ConstructSpdyConnect(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> get(ConstructSpdyRstStream(1, spdy::CANCEL)); + + MockWrite spdy_writes[] = { + CreateMockWrite(*connect, 1), + CreateMockWrite(*get, 3), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdySynReplyError(1)); + scoped_ptr<spdy::SpdyFrame> data(ConstructSpdyBodyFrame(1, true)); + MockRead spdy_reads[] = { + CreateMockRead(*resp, 2, ASYNC), + MockRead(ASYNC, 0, 4), + }; + + scoped_ptr<OrderedSocketData> spdy_data( + new OrderedSocketData( + spdy_reads, arraysize(spdy_reads), + spdy_writes, arraysize(spdy_writes))); + session_deps.socket_factory.AddSocketDataProvider(spdy_data.get()); + + SSLSocketDataProvider ssl(ASYNC, OK); + ssl.SetNextProto(SSLClientSocket::kProtoSPDY21); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); + SSLSocketDataProvider ssl2(ASYNC, OK); + ssl2.SetNextProto(SSLClientSocket::kProtoSPDY21); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl2); + + TestCompletionCallback callback1; + + int rv = trans->Start(&request, callback1.callback(), log.bound()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_EQ(500, response->headers->response_code()); +} + +// Test the challenge-response-retry sequence through an HTTPS Proxy +TEST_F(HttpNetworkTransactionSpdy21Test, HttpsProxyAuthRetry) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + // when the no authentication data flag is set. + request.load_flags = net::LOAD_DO_NOT_SEND_AUTH_DATA; + + // Configure against https proxy server "myproxy:70". + SessionDependencies session_deps( + ProxyService::CreateFixed("https://myproxy:70")); + CapturingBoundNetLog log(CapturingNetLog::kUnbounded); + session_deps.net_log = log.bound().net_log(); + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + // Since we have proxy, should use full url + MockWrite data_writes1[] = { + MockWrite("GET http://www.google.com/ HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n\r\n"), + + // After calling trans->RestartWithAuth(), this is the request we should + // be issuing -- the final header line contains the credentials. + MockWrite("GET http://www.google.com/ HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n" + "Proxy-Authorization: Basic Zm9vOmJhcg==\r\n\r\n"), + }; + + // The proxy responds to the GET with a 407, using a persistent + // connection. + MockRead data_reads1[] = { + // No credentials. + MockRead("HTTP/1.1 407 Proxy Authentication Required\r\n"), + MockRead("Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n"), + MockRead("Proxy-Connection: keep-alive\r\n"), + MockRead("Content-Length: 0\r\n\r\n"), + + MockRead("HTTP/1.1 200 OK\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), + MockRead("Content-Length: 100\r\n\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + + StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), + data_writes1, arraysize(data_writes1)); + session_deps.socket_factory.AddSocketDataProvider(&data1); + SSLSocketDataProvider ssl(ASYNC, OK); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); + + TestCompletionCallback callback1; + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + int rv = trans->Start(&request, callback1.callback(), log.bound()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_FALSE(response->headers == NULL); + EXPECT_EQ(407, response->headers->response_code()); + EXPECT_TRUE(HttpVersion(1, 1) == response->headers->GetHttpVersion()); + EXPECT_TRUE(CheckBasicProxyAuth(response->auth_challenge.get())); + + TestCompletionCallback callback2; + + rv = trans->RestartWithAuth( + AuthCredentials(kFoo, kBar), callback2.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback2.WaitForResult(); + EXPECT_EQ(OK, rv); + + response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + + EXPECT_TRUE(response->headers->IsKeepAlive()); + EXPECT_EQ(200, response->headers->response_code()); + EXPECT_EQ(100, response->headers->GetContentLength()); + EXPECT_TRUE(HttpVersion(1, 1) == response->headers->GetHttpVersion()); + + // The password prompt info should not be set. + EXPECT_TRUE(response->auth_challenge.get() == NULL); +} + +void HttpNetworkTransactionSpdy21Test::ConnectStatusHelperWithExpectedStatus( + const MockRead& status, int expected_status) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("https://www.google.com/"); + request.load_flags = 0; + + // Configure against proxy server "myproxy:70". + SessionDependencies session_deps(ProxyService::CreateFixed("myproxy:70")); + + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + // Since we have proxy, should try to establish tunnel. + MockWrite data_writes[] = { + MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n\r\n"), + }; + + MockRead data_reads[] = { + status, + MockRead("Content-Length: 10\r\n\r\n"), + // No response body because the test stops reading here. + MockRead(SYNCHRONOUS, ERR_UNEXPECTED), // Should not be reached. + }; + + StaticSocketDataProvider data(data_reads, arraysize(data_reads), + data_writes, arraysize(data_writes)); + session_deps.socket_factory.AddSocketDataProvider(&data); + + TestCompletionCallback callback; + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(expected_status, rv); +} + +void HttpNetworkTransactionSpdy21Test::ConnectStatusHelper( + const MockRead& status) { + ConnectStatusHelperWithExpectedStatus( + status, ERR_TUNNEL_CONNECTION_FAILED); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, ConnectStatus100) { + ConnectStatusHelper(MockRead("HTTP/1.1 100 Continue\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, ConnectStatus101) { + ConnectStatusHelper(MockRead("HTTP/1.1 101 Switching Protocols\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, ConnectStatus201) { + ConnectStatusHelper(MockRead("HTTP/1.1 201 Created\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, ConnectStatus202) { + ConnectStatusHelper(MockRead("HTTP/1.1 202 Accepted\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, ConnectStatus203) { + ConnectStatusHelper( + MockRead("HTTP/1.1 203 Non-Authoritative Information\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, ConnectStatus204) { + ConnectStatusHelper(MockRead("HTTP/1.1 204 No Content\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, ConnectStatus205) { + ConnectStatusHelper(MockRead("HTTP/1.1 205 Reset Content\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, ConnectStatus206) { + ConnectStatusHelper(MockRead("HTTP/1.1 206 Partial Content\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, ConnectStatus300) { + ConnectStatusHelper(MockRead("HTTP/1.1 300 Multiple Choices\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, ConnectStatus301) { + ConnectStatusHelper(MockRead("HTTP/1.1 301 Moved Permanently\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, ConnectStatus302) { + ConnectStatusHelper(MockRead("HTTP/1.1 302 Found\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, ConnectStatus303) { + ConnectStatusHelper(MockRead("HTTP/1.1 303 See Other\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, ConnectStatus304) { + ConnectStatusHelper(MockRead("HTTP/1.1 304 Not Modified\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, ConnectStatus305) { + ConnectStatusHelper(MockRead("HTTP/1.1 305 Use Proxy\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, ConnectStatus306) { + ConnectStatusHelper(MockRead("HTTP/1.1 306\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, ConnectStatus307) { + ConnectStatusHelper(MockRead("HTTP/1.1 307 Temporary Redirect\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, ConnectStatus400) { + ConnectStatusHelper(MockRead("HTTP/1.1 400 Bad Request\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, ConnectStatus401) { + ConnectStatusHelper(MockRead("HTTP/1.1 401 Unauthorized\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, ConnectStatus402) { + ConnectStatusHelper(MockRead("HTTP/1.1 402 Payment Required\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, ConnectStatus403) { + ConnectStatusHelper(MockRead("HTTP/1.1 403 Forbidden\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, ConnectStatus404) { + ConnectStatusHelper(MockRead("HTTP/1.1 404 Not Found\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, ConnectStatus405) { + ConnectStatusHelper(MockRead("HTTP/1.1 405 Method Not Allowed\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, ConnectStatus406) { + ConnectStatusHelper(MockRead("HTTP/1.1 406 Not Acceptable\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, ConnectStatus407) { + ConnectStatusHelperWithExpectedStatus( + MockRead("HTTP/1.1 407 Proxy Authentication Required\r\n"), + ERR_PROXY_AUTH_UNSUPPORTED); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, ConnectStatus408) { + ConnectStatusHelper(MockRead("HTTP/1.1 408 Request Timeout\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, ConnectStatus409) { + ConnectStatusHelper(MockRead("HTTP/1.1 409 Conflict\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, ConnectStatus410) { + ConnectStatusHelper(MockRead("HTTP/1.1 410 Gone\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, ConnectStatus411) { + ConnectStatusHelper(MockRead("HTTP/1.1 411 Length Required\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, ConnectStatus412) { + ConnectStatusHelper(MockRead("HTTP/1.1 412 Precondition Failed\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, ConnectStatus413) { + ConnectStatusHelper(MockRead("HTTP/1.1 413 Request Entity Too Large\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, ConnectStatus414) { + ConnectStatusHelper(MockRead("HTTP/1.1 414 Request-URI Too Long\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, ConnectStatus415) { + ConnectStatusHelper(MockRead("HTTP/1.1 415 Unsupported Media Type\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, ConnectStatus416) { + ConnectStatusHelper( + MockRead("HTTP/1.1 416 Requested Range Not Satisfiable\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, ConnectStatus417) { + ConnectStatusHelper(MockRead("HTTP/1.1 417 Expectation Failed\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, ConnectStatus500) { + ConnectStatusHelper(MockRead("HTTP/1.1 500 Internal Server Error\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, ConnectStatus501) { + ConnectStatusHelper(MockRead("HTTP/1.1 501 Not Implemented\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, ConnectStatus502) { + ConnectStatusHelper(MockRead("HTTP/1.1 502 Bad Gateway\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, ConnectStatus503) { + ConnectStatusHelper(MockRead("HTTP/1.1 503 Service Unavailable\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, ConnectStatus504) { + ConnectStatusHelper(MockRead("HTTP/1.1 504 Gateway Timeout\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, ConnectStatus505) { + ConnectStatusHelper(MockRead("HTTP/1.1 505 HTTP Version Not Supported\r\n")); +} + +// Test the flow when both the proxy server AND origin server require +// authentication. Again, this uses basic auth for both since that is +// the simplest to mock. +TEST_F(HttpNetworkTransactionSpdy21Test, BasicAuthProxyThenServer) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + + SessionDependencies session_deps(ProxyService::CreateFixed("myproxy:70")); + + // Configure against proxy server "myproxy:70". + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction( + CreateSession(&session_deps))); + + MockWrite data_writes1[] = { + MockWrite("GET http://www.google.com/ HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n\r\n"), + }; + + MockRead data_reads1[] = { + MockRead("HTTP/1.0 407 Unauthorized\r\n"), + // Give a couple authenticate options (only the middle one is actually + // supported). + MockRead("Proxy-Authenticate: Basic invalid\r\n"), // Malformed. + MockRead("Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n"), + MockRead("Proxy-Authenticate: UNSUPPORTED realm=\"FOO\"\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), + // Large content-length -- won't matter, as connection will be reset. + MockRead("Content-Length: 10000\r\n\r\n"), + MockRead(SYNCHRONOUS, ERR_FAILED), + }; + + // After calling trans->RestartWithAuth() the first time, this is the + // request we should be issuing -- the final header line contains the + // proxy's credentials. + MockWrite data_writes2[] = { + MockWrite("GET http://www.google.com/ HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n" + "Proxy-Authorization: Basic Zm9vOmJhcg==\r\n\r\n"), + }; + + // Now the proxy server lets the request pass through to origin server. + // The origin server responds with a 401. + MockRead data_reads2[] = { + MockRead("HTTP/1.0 401 Unauthorized\r\n"), + // Note: We are using the same realm-name as the proxy server. This is + // completely valid, as realms are unique across hosts. + MockRead("WWW-Authenticate: Basic realm=\"MyRealm1\"\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), + MockRead("Content-Length: 2000\r\n\r\n"), + MockRead(SYNCHRONOUS, ERR_FAILED), // Won't be reached. + }; + + // After calling trans->RestartWithAuth() the second time, we should send + // the credentials for both the proxy and origin server. + MockWrite data_writes3[] = { + MockWrite("GET http://www.google.com/ HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n" + "Proxy-Authorization: Basic Zm9vOmJhcg==\r\n" + "Authorization: Basic Zm9vMjpiYXIy\r\n\r\n"), + }; + + // Lastly we get the desired content. + MockRead data_reads3[] = { + MockRead("HTTP/1.0 200 OK\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), + MockRead("Content-Length: 100\r\n\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + + StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), + data_writes1, arraysize(data_writes1)); + StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2), + data_writes2, arraysize(data_writes2)); + StaticSocketDataProvider data3(data_reads3, arraysize(data_reads3), + data_writes3, arraysize(data_writes3)); + session_deps.socket_factory.AddSocketDataProvider(&data1); + session_deps.socket_factory.AddSocketDataProvider(&data2); + session_deps.socket_factory.AddSocketDataProvider(&data3); + + TestCompletionCallback callback1; + + int rv = trans->Start(&request, callback1.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(CheckBasicProxyAuth(response->auth_challenge.get())); + + TestCompletionCallback callback2; + + rv = trans->RestartWithAuth( + AuthCredentials(kFoo, kBar), callback2.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback2.WaitForResult(); + EXPECT_EQ(OK, rv); + + response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(CheckBasicServerAuth(response->auth_challenge.get())); + + TestCompletionCallback callback3; + + rv = trans->RestartWithAuth( + AuthCredentials(kFoo2, kBar2), callback3.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback3.WaitForResult(); + EXPECT_EQ(OK, rv); + + response = trans->GetResponseInfo(); + EXPECT_TRUE(response->auth_challenge.get() == NULL); + EXPECT_EQ(100, response->headers->GetContentLength()); +} + +// For the NTLM implementation using SSPI, we skip the NTLM tests since we +// can't hook into its internals to cause it to generate predictable NTLM +// authorization headers. +#if defined(NTLM_PORTABLE) +// The NTLM authentication unit tests were generated by capturing the HTTP +// requests and responses using Fiddler 2 and inspecting the generated random +// bytes in the debugger. + +// Enter the correct password and authenticate successfully. +TEST_F(HttpNetworkTransactionSpdy21Test, NTLMAuth1) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://172.22.68.17/kids/login.aspx"); + request.load_flags = 0; + + HttpAuthHandlerNTLM::ScopedProcSetter proc_setter(MockGenerateRandom1, + MockGetHostName); + SessionDependencies session_deps; + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + MockWrite data_writes1[] = { + MockWrite("GET /kids/login.aspx HTTP/1.1\r\n" + "Host: 172.22.68.17\r\n" + "Connection: keep-alive\r\n\r\n"), + }; + + MockRead data_reads1[] = { + MockRead("HTTP/1.1 401 Access Denied\r\n"), + // Negotiate and NTLM are often requested together. However, we only want + // to test NTLM. Since Negotiate is preferred over NTLM, we have to skip + // the header that requests Negotiate for this test. + MockRead("WWW-Authenticate: NTLM\r\n"), + MockRead("Connection: close\r\n"), + MockRead("Content-Length: 42\r\n"), + MockRead("Content-Type: text/html\r\n\r\n"), + // Missing content -- won't matter, as connection will be reset. + MockRead(SYNCHRONOUS, ERR_UNEXPECTED), + }; + + MockWrite data_writes2[] = { + // After restarting with a null identity, this is the + // request we should be issuing -- the final header line contains a Type + // 1 message. + MockWrite("GET /kids/login.aspx HTTP/1.1\r\n" + "Host: 172.22.68.17\r\n" + "Connection: keep-alive\r\n" + "Authorization: NTLM " + "TlRMTVNTUAABAAAAB4IIAAAAAAAAAAAAAAAAAAAAAAA=\r\n\r\n"), + + // After calling trans->RestartWithAuth(), we should send a Type 3 message + // (the credentials for the origin server). The second request continues + // on the same connection. + MockWrite("GET /kids/login.aspx HTTP/1.1\r\n" + "Host: 172.22.68.17\r\n" + "Connection: keep-alive\r\n" + "Authorization: NTLM TlRMTVNTUAADAAAAGAAYAGgAAAAYABgAgA" + "AAAAAAAABAAAAAGAAYAEAAAAAQABAAWAAAAAAAAAAAAAAABYIIAHQA" + "ZQBzAHQAaQBuAGcALQBuAHQAbABtAFcAVABDAC0AVwBJAE4ANwBVKW" + "Yma5xzVAAAAAAAAAAAAAAAAAAAAACH+gWcm+YsP9Tqb9zCR3WAeZZX" + "ahlhx5I=\r\n\r\n"), + }; + + MockRead data_reads2[] = { + // The origin server responds with a Type 2 message. + MockRead("HTTP/1.1 401 Access Denied\r\n"), + MockRead("WWW-Authenticate: NTLM " + "TlRMTVNTUAACAAAADAAMADgAAAAFgokCjGpMpPGlYKkAAAAAAAAAALo" + "AugBEAAAABQEoCgAAAA9HAE8ATwBHAEwARQACAAwARwBPAE8ARwBMAE" + "UAAQAaAEEASwBFAEUAUwBBAFIAQQAtAEMATwBSAFAABAAeAGMAbwByA" + "HAALgBnAG8AbwBnAGwAZQAuAGMAbwBtAAMAQABhAGsAZQBlAHMAYQBy" + "AGEALQBjAG8AcgBwAC4AYQBkAC4AYwBvAHIAcAAuAGcAbwBvAGcAbAB" + "lAC4AYwBvAG0ABQAeAGMAbwByAHAALgBnAG8AbwBnAGwAZQAuAGMAbw" + "BtAAAAAAA=\r\n"), + MockRead("Content-Length: 42\r\n"), + MockRead("Content-Type: text/html\r\n\r\n"), + MockRead("You are not authorized to view this page\r\n"), + + // Lastly we get the desired content. + MockRead("HTTP/1.1 200 OK\r\n"), + MockRead("Content-Type: text/html; charset=utf-8\r\n"), + MockRead("Content-Length: 13\r\n\r\n"), + MockRead("Please Login\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + + StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), + data_writes1, arraysize(data_writes1)); + StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2), + data_writes2, arraysize(data_writes2)); + session_deps.socket_factory.AddSocketDataProvider(&data1); + session_deps.socket_factory.AddSocketDataProvider(&data2); + + TestCompletionCallback callback1; + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + int rv = trans->Start(&request, callback1.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(OK, rv); + + EXPECT_FALSE(trans->IsReadyToRestartForAuth()); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_FALSE(response == NULL); + EXPECT_TRUE(CheckNTLMServerAuth(response->auth_challenge.get())); + + TestCompletionCallback callback2; + + rv = trans->RestartWithAuth(AuthCredentials(kTestingNTLM, kTestingNTLM), + callback2.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback2.WaitForResult(); + EXPECT_EQ(OK, rv); + + EXPECT_TRUE(trans->IsReadyToRestartForAuth()); + + response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(response->auth_challenge.get() == NULL); + + TestCompletionCallback callback3; + + rv = trans->RestartWithAuth(AuthCredentials(), callback3.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback3.WaitForResult(); + EXPECT_EQ(OK, rv); + + response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(response->auth_challenge.get() == NULL); + EXPECT_EQ(13, response->headers->GetContentLength()); +} + +// Enter a wrong password, and then the correct one. +TEST_F(HttpNetworkTransactionSpdy21Test, NTLMAuth2) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://172.22.68.17/kids/login.aspx"); + request.load_flags = 0; + + HttpAuthHandlerNTLM::ScopedProcSetter proc_setter(MockGenerateRandom2, + MockGetHostName); + SessionDependencies session_deps; + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + MockWrite data_writes1[] = { + MockWrite("GET /kids/login.aspx HTTP/1.1\r\n" + "Host: 172.22.68.17\r\n" + "Connection: keep-alive\r\n\r\n"), + }; + + MockRead data_reads1[] = { + MockRead("HTTP/1.1 401 Access Denied\r\n"), + // Negotiate and NTLM are often requested together. However, we only want + // to test NTLM. Since Negotiate is preferred over NTLM, we have to skip + // the header that requests Negotiate for this test. + MockRead("WWW-Authenticate: NTLM\r\n"), + MockRead("Connection: close\r\n"), + MockRead("Content-Length: 42\r\n"), + MockRead("Content-Type: text/html\r\n\r\n"), + // Missing content -- won't matter, as connection will be reset. + MockRead(SYNCHRONOUS, ERR_UNEXPECTED), + }; + + MockWrite data_writes2[] = { + // After restarting with a null identity, this is the + // request we should be issuing -- the final header line contains a Type + // 1 message. + MockWrite("GET /kids/login.aspx HTTP/1.1\r\n" + "Host: 172.22.68.17\r\n" + "Connection: keep-alive\r\n" + "Authorization: NTLM " + "TlRMTVNTUAABAAAAB4IIAAAAAAAAAAAAAAAAAAAAAAA=\r\n\r\n"), + + // After calling trans->RestartWithAuth(), we should send a Type 3 message + // (the credentials for the origin server). The second request continues + // on the same connection. + MockWrite("GET /kids/login.aspx HTTP/1.1\r\n" + "Host: 172.22.68.17\r\n" + "Connection: keep-alive\r\n" + "Authorization: NTLM TlRMTVNTUAADAAAAGAAYAGgAAAAYABgAgA" + "AAAAAAAABAAAAAGAAYAEAAAAAQABAAWAAAAAAAAAAAAAAABYIIAHQA" + "ZQBzAHQAaQBuAGcALQBuAHQAbABtAFcAVABDAC0AVwBJAE4ANwCWeY" + "XnSZNwoQAAAAAAAAAAAAAAAAAAAADLa34/phTTKzNTWdub+uyFleOj" + "4Ww7b7E=\r\n\r\n"), + }; + + MockRead data_reads2[] = { + // The origin server responds with a Type 2 message. + MockRead("HTTP/1.1 401 Access Denied\r\n"), + MockRead("WWW-Authenticate: NTLM " + "TlRMTVNTUAACAAAADAAMADgAAAAFgokCbVWUZezVGpAAAAAAAAAAALo" + "AugBEAAAABQEoCgAAAA9HAE8ATwBHAEwARQACAAwARwBPAE8ARwBMAE" + "UAAQAaAEEASwBFAEUAUwBBAFIAQQAtAEMATwBSAFAABAAeAGMAbwByA" + "HAALgBnAG8AbwBnAGwAZQAuAGMAbwBtAAMAQABhAGsAZQBlAHMAYQBy" + "AGEALQBjAG8AcgBwAC4AYQBkAC4AYwBvAHIAcAAuAGcAbwBvAGcAbAB" + "lAC4AYwBvAG0ABQAeAGMAbwByAHAALgBnAG8AbwBnAGwAZQAuAGMAbw" + "BtAAAAAAA=\r\n"), + MockRead("Content-Length: 42\r\n"), + MockRead("Content-Type: text/html\r\n\r\n"), + MockRead("You are not authorized to view this page\r\n"), + + // Wrong password. + MockRead("HTTP/1.1 401 Access Denied\r\n"), + MockRead("WWW-Authenticate: NTLM\r\n"), + MockRead("Connection: close\r\n"), + MockRead("Content-Length: 42\r\n"), + MockRead("Content-Type: text/html\r\n\r\n"), + // Missing content -- won't matter, as connection will be reset. + MockRead(SYNCHRONOUS, ERR_UNEXPECTED), + }; + + MockWrite data_writes3[] = { + // After restarting with a null identity, this is the + // request we should be issuing -- the final header line contains a Type + // 1 message. + MockWrite("GET /kids/login.aspx HTTP/1.1\r\n" + "Host: 172.22.68.17\r\n" + "Connection: keep-alive\r\n" + "Authorization: NTLM " + "TlRMTVNTUAABAAAAB4IIAAAAAAAAAAAAAAAAAAAAAAA=\r\n\r\n"), + + // After calling trans->RestartWithAuth(), we should send a Type 3 message + // (the credentials for the origin server). The second request continues + // on the same connection. + MockWrite("GET /kids/login.aspx HTTP/1.1\r\n" + "Host: 172.22.68.17\r\n" + "Connection: keep-alive\r\n" + "Authorization: NTLM TlRMTVNTUAADAAAAGAAYAGgAAAAYABgAgA" + "AAAAAAAABAAAAAGAAYAEAAAAAQABAAWAAAAAAAAAAAAAAABYIIAHQA" + "ZQBzAHQAaQBuAGcALQBuAHQAbABtAFcAVABDAC0AVwBJAE4ANwBO54" + "dFMVvTHwAAAAAAAAAAAAAAAAAAAACS7sT6Uzw7L0L//WUqlIaVWpbI" + "+4MUm7c=\r\n\r\n"), + }; + + MockRead data_reads3[] = { + // The origin server responds with a Type 2 message. + MockRead("HTTP/1.1 401 Access Denied\r\n"), + MockRead("WWW-Authenticate: NTLM " + "TlRMTVNTUAACAAAADAAMADgAAAAFgokCL24VN8dgOR8AAAAAAAAAALo" + "AugBEAAAABQEoCgAAAA9HAE8ATwBHAEwARQACAAwARwBPAE8ARwBMAE" + "UAAQAaAEEASwBFAEUAUwBBAFIAQQAtAEMATwBSAFAABAAeAGMAbwByA" + "HAALgBnAG8AbwBnAGwAZQAuAGMAbwBtAAMAQABhAGsAZQBlAHMAYQBy" + "AGEALQBjAG8AcgBwAC4AYQBkAC4AYwBvAHIAcAAuAGcAbwBvAGcAbAB" + "lAC4AYwBvAG0ABQAeAGMAbwByAHAALgBnAG8AbwBnAGwAZQAuAGMAbw" + "BtAAAAAAA=\r\n"), + MockRead("Content-Length: 42\r\n"), + MockRead("Content-Type: text/html\r\n\r\n"), + MockRead("You are not authorized to view this page\r\n"), + + // Lastly we get the desired content. + MockRead("HTTP/1.1 200 OK\r\n"), + MockRead("Content-Type: text/html; charset=utf-8\r\n"), + MockRead("Content-Length: 13\r\n\r\n"), + MockRead("Please Login\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + + StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), + data_writes1, arraysize(data_writes1)); + StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2), + data_writes2, arraysize(data_writes2)); + StaticSocketDataProvider data3(data_reads3, arraysize(data_reads3), + data_writes3, arraysize(data_writes3)); + session_deps.socket_factory.AddSocketDataProvider(&data1); + session_deps.socket_factory.AddSocketDataProvider(&data2); + session_deps.socket_factory.AddSocketDataProvider(&data3); + + TestCompletionCallback callback1; + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + int rv = trans->Start(&request, callback1.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(OK, rv); + + EXPECT_FALSE(trans->IsReadyToRestartForAuth()); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(CheckNTLMServerAuth(response->auth_challenge.get())); + + TestCompletionCallback callback2; + + // Enter the wrong password. + rv = trans->RestartWithAuth(AuthCredentials(kTestingNTLM, kWrongPassword), + callback2.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback2.WaitForResult(); + EXPECT_EQ(OK, rv); + + EXPECT_TRUE(trans->IsReadyToRestartForAuth()); + TestCompletionCallback callback3; + rv = trans->RestartWithAuth(AuthCredentials(), callback3.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback3.WaitForResult(); + EXPECT_EQ(OK, rv); + EXPECT_FALSE(trans->IsReadyToRestartForAuth()); + + response = trans->GetResponseInfo(); + ASSERT_FALSE(response == NULL); + EXPECT_TRUE(CheckNTLMServerAuth(response->auth_challenge.get())); + + TestCompletionCallback callback4; + + // Now enter the right password. + rv = trans->RestartWithAuth(AuthCredentials(kTestingNTLM, kTestingNTLM), + callback4.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback4.WaitForResult(); + EXPECT_EQ(OK, rv); + + EXPECT_TRUE(trans->IsReadyToRestartForAuth()); + + TestCompletionCallback callback5; + + // One more roundtrip + rv = trans->RestartWithAuth(AuthCredentials(), callback5.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback5.WaitForResult(); + EXPECT_EQ(OK, rv); + + response = trans->GetResponseInfo(); + EXPECT_TRUE(response->auth_challenge.get() == NULL); + EXPECT_EQ(13, response->headers->GetContentLength()); +} +#endif // NTLM_PORTABLE + +// Test reading a server response which has only headers, and no body. +// After some maximum number of bytes is consumed, the transaction should +// fail with ERR_RESPONSE_HEADERS_TOO_BIG. +TEST_F(HttpNetworkTransactionSpdy21Test, LargeHeadersNoBody) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + + SessionDependencies session_deps; + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + // Respond with 300 kb of headers (we should fail after 256 kb). + std::string large_headers_string; + FillLargeHeadersString(&large_headers_string, 300 * 1024); + + MockRead data_reads[] = { + MockRead("HTTP/1.0 200 OK\r\n"), + MockRead(ASYNC, large_headers_string.data(), large_headers_string.size()), + MockRead("\r\nBODY"), + MockRead(SYNCHRONOUS, OK), + }; + StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&data); + + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(ERR_RESPONSE_HEADERS_TOO_BIG, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + EXPECT_TRUE(response == NULL); +} + +// Make sure that we don't try to reuse a TCPClientSocket when failing to +// establish tunnel. +// http://code.google.com/p/chromium/issues/detail?id=3772 +TEST_F(HttpNetworkTransactionSpdy21Test, + DontRecycleTransportSocketForSSLTunnel) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("https://www.google.com/"); + request.load_flags = 0; + + // Configure against proxy server "myproxy:70". + SessionDependencies session_deps(ProxyService::CreateFixed("myproxy:70")); + + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + // Since we have proxy, should try to establish tunnel. + MockWrite data_writes1[] = { + MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n\r\n"), + }; + + // The proxy responds to the connect with a 404, using a persistent + // connection. Usually a proxy would return 501 (not implemented), + // or 200 (tunnel established). + MockRead data_reads1[] = { + MockRead("HTTP/1.1 404 Not Found\r\n"), + MockRead("Content-Length: 10\r\n\r\n"), + MockRead(SYNCHRONOUS, ERR_UNEXPECTED), // Should not be reached. + }; + + StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), + data_writes1, arraysize(data_writes1)); + session_deps.socket_factory.AddSocketDataProvider(&data1); + + TestCompletionCallback callback1; + + int rv = trans->Start(&request, callback1.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(ERR_TUNNEL_CONNECTION_FAILED, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + EXPECT_TRUE(response == NULL); + + // Empty the current queue. This is necessary because idle sockets are + // added to the connection pool asynchronously with a PostTask. + MessageLoop::current()->RunAllPending(); + + // We now check to make sure the TCPClientSocket was not added back to + // the pool. + EXPECT_EQ(0, session->GetTransportSocketPool()->IdleSocketCount()); + trans.reset(); + MessageLoop::current()->RunAllPending(); + // Make sure that the socket didn't get recycled after calling the destructor. + EXPECT_EQ(0, session->GetTransportSocketPool()->IdleSocketCount()); +} + +// Make sure that we recycle a socket after reading all of the response body. +TEST_F(HttpNetworkTransactionSpdy21Test, RecycleSocket) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + + SessionDependencies session_deps; + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + MockRead data_reads[] = { + // A part of the response body is received with the response headers. + MockRead("HTTP/1.1 200 OK\r\nContent-Length: 11\r\n\r\nhel"), + // The rest of the response body is received in two parts. + MockRead("lo"), + MockRead(" world"), + MockRead("junk"), // Should not be read!! + MockRead(SYNCHRONOUS, OK), + }; + + StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&data); + + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + + EXPECT_TRUE(response->headers != NULL); + std::string status_line = response->headers->GetStatusLine(); + EXPECT_EQ("HTTP/1.1 200 OK", status_line); + + EXPECT_EQ(0, session->GetTransportSocketPool()->IdleSocketCount()); + + std::string response_data; + rv = ReadTransaction(trans.get(), &response_data); + EXPECT_EQ(OK, rv); + EXPECT_EQ("hello world", response_data); + + // Empty the current queue. This is necessary because idle sockets are + // added to the connection pool asynchronously with a PostTask. + MessageLoop::current()->RunAllPending(); + + // We now check to make sure the socket was added back to the pool. + EXPECT_EQ(1, session->GetTransportSocketPool()->IdleSocketCount()); +} + +// Make sure that we recycle a SSL socket after reading all of the response +// body. +TEST_F(HttpNetworkTransactionSpdy21Test, RecycleSSLSocket) { + SessionDependencies session_deps; + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("https://www.google.com/"); + request.load_flags = 0; + + MockWrite data_writes[] = { + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n\r\n"), + }; + + MockRead data_reads[] = { + MockRead("HTTP/1.1 200 OK\r\n"), + MockRead("Content-Length: 11\r\n\r\n"), + MockRead("hello world"), + MockRead(SYNCHRONOUS, OK), + }; + + SSLSocketDataProvider ssl(ASYNC, OK); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); + + StaticSocketDataProvider data(data_reads, arraysize(data_reads), + data_writes, arraysize(data_writes)); + session_deps.socket_factory.AddSocketDataProvider(&data); + + TestCompletionCallback callback; + + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_EQ(OK, callback.WaitForResult()); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + + EXPECT_EQ(0, session->GetTransportSocketPool()->IdleSocketCount()); + + std::string response_data; + rv = ReadTransaction(trans.get(), &response_data); + EXPECT_EQ(OK, rv); + EXPECT_EQ("hello world", response_data); + + // Empty the current queue. This is necessary because idle sockets are + // added to the connection pool asynchronously with a PostTask. + MessageLoop::current()->RunAllPending(); + + // We now check to make sure the socket was added back to the pool. + EXPECT_EQ(1, session->GetSSLSocketPool()->IdleSocketCount()); +} + +// Grab a SSL socket, use it, and put it back into the pool. Then, reuse it +// from the pool and make sure that we recover okay. +TEST_F(HttpNetworkTransactionSpdy21Test, RecycleDeadSSLSocket) { + SessionDependencies session_deps; + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("https://www.google.com/"); + request.load_flags = 0; + + MockWrite data_writes[] = { + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n\r\n"), + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n\r\n"), + }; + + MockRead data_reads[] = { + MockRead("HTTP/1.1 200 OK\r\n"), + MockRead("Content-Length: 11\r\n\r\n"), + MockRead(SYNCHRONOUS, ERR_TEST_PEER_CLOSE_AFTER_NEXT_MOCK_READ), + MockRead("hello world"), + MockRead(ASYNC, 0, 0) // EOF + }; + + SSLSocketDataProvider ssl(ASYNC, OK); + SSLSocketDataProvider ssl2(ASYNC, OK); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl2); + + StaticSocketDataProvider data(data_reads, arraysize(data_reads), + data_writes, arraysize(data_writes)); + StaticSocketDataProvider data2(data_reads, arraysize(data_reads), + data_writes, arraysize(data_writes)); + session_deps.socket_factory.AddSocketDataProvider(&data); + session_deps.socket_factory.AddSocketDataProvider(&data2); + + TestCompletionCallback callback; + + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_EQ(OK, callback.WaitForResult()); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + + EXPECT_EQ(0, session->GetTransportSocketPool()->IdleSocketCount()); + + std::string response_data; + rv = ReadTransaction(trans.get(), &response_data); + EXPECT_EQ(OK, rv); + EXPECT_EQ("hello world", response_data); + + // Empty the current queue. This is necessary because idle sockets are + // added to the connection pool asynchronously with a PostTask. + MessageLoop::current()->RunAllPending(); + + // We now check to make sure the socket was added back to the pool. + EXPECT_EQ(1, session->GetSSLSocketPool()->IdleSocketCount()); + + // Now start the second transaction, which should reuse the previous socket. + + trans.reset(new HttpNetworkTransaction(session)); + + rv = trans->Start(&request, callback.callback(), BoundNetLog()); + + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_EQ(OK, callback.WaitForResult()); + + response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + + EXPECT_EQ(0, session->GetTransportSocketPool()->IdleSocketCount()); + + rv = ReadTransaction(trans.get(), &response_data); + EXPECT_EQ(OK, rv); + EXPECT_EQ("hello world", response_data); + + // Empty the current queue. This is necessary because idle sockets are + // added to the connection pool asynchronously with a PostTask. + MessageLoop::current()->RunAllPending(); + + // We now check to make sure the socket was added back to the pool. + EXPECT_EQ(1, session->GetSSLSocketPool()->IdleSocketCount()); +} + +// Make sure that we recycle a socket after a zero-length response. +// http://crbug.com/9880 +TEST_F(HttpNetworkTransactionSpdy21Test, RecycleSocketAfterZeroContentLength) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/csi?v=3&s=web&action=&" + "tran=undefined&ei=mAXcSeegAo-SMurloeUN&" + "e=17259,18167,19592,19773,19981,20133,20173,20233&" + "rt=prt.2642,ol.2649,xjs.2951"); + request.load_flags = 0; + + SessionDependencies session_deps; + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + MockRead data_reads[] = { + MockRead("HTTP/1.1 204 No Content\r\n" + "Content-Length: 0\r\n" + "Content-Type: text/html\r\n\r\n"), + MockRead("junk"), // Should not be read!! + MockRead(SYNCHRONOUS, OK), + }; + + StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&data); + + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + + EXPECT_TRUE(response->headers != NULL); + std::string status_line = response->headers->GetStatusLine(); + EXPECT_EQ("HTTP/1.1 204 No Content", status_line); + + EXPECT_EQ(0, session->GetTransportSocketPool()->IdleSocketCount()); + + std::string response_data; + rv = ReadTransaction(trans.get(), &response_data); + EXPECT_EQ(OK, rv); + EXPECT_EQ("", response_data); + + // Empty the current queue. This is necessary because idle sockets are + // added to the connection pool asynchronously with a PostTask. + MessageLoop::current()->RunAllPending(); + + // We now check to make sure the socket was added back to the pool. + EXPECT_EQ(1, session->GetTransportSocketPool()->IdleSocketCount()); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, ResendRequestOnWriteBodyError) { + HttpRequestInfo request[2]; + // Transaction 1: a GET request that succeeds. The socket is recycled + // after use. + request[0].method = "GET"; + request[0].url = GURL("http://www.google.com/"); + request[0].load_flags = 0; + // Transaction 2: a POST request. Reuses the socket kept alive from + // transaction 1. The first attempts fails when writing the POST data. + // This causes the transaction to retry with a new socket. The second + // attempt succeeds. + request[1].method = "POST"; + request[1].url = GURL("http://www.google.com/login.cgi"); + request[1].upload_data = new UploadData; + request[1].upload_data->AppendBytes("foo", 3); + request[1].load_flags = 0; + + SessionDependencies session_deps; + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + // The first socket is used for transaction 1 and the first attempt of + // transaction 2. + + // The response of transaction 1. + MockRead data_reads1[] = { + MockRead("HTTP/1.1 200 OK\r\nContent-Length: 11\r\n\r\n"), + MockRead("hello world"), + MockRead(SYNCHRONOUS, OK), + }; + // The mock write results of transaction 1 and the first attempt of + // transaction 2. + MockWrite data_writes1[] = { + MockWrite(SYNCHRONOUS, 64), // GET + MockWrite(SYNCHRONOUS, 93), // POST + MockWrite(SYNCHRONOUS, ERR_CONNECTION_ABORTED), // POST data + }; + StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), + data_writes1, arraysize(data_writes1)); + + // The second socket is used for the second attempt of transaction 2. + + // The response of transaction 2. + MockRead data_reads2[] = { + MockRead("HTTP/1.1 200 OK\r\nContent-Length: 7\r\n\r\n"), + MockRead("welcome"), + MockRead(SYNCHRONOUS, OK), + }; + // The mock write results of the second attempt of transaction 2. + MockWrite data_writes2[] = { + MockWrite(SYNCHRONOUS, 93), // POST + MockWrite(SYNCHRONOUS, 3), // POST data + }; + StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2), + data_writes2, arraysize(data_writes2)); + + session_deps.socket_factory.AddSocketDataProvider(&data1); + session_deps.socket_factory.AddSocketDataProvider(&data2); + + const char* kExpectedResponseData[] = { + "hello world", "welcome" + }; + + for (int i = 0; i < 2; ++i) { + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(session)); + + TestCompletionCallback callback; + + int rv = trans->Start(&request[i], callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + + EXPECT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + + std::string response_data; + rv = ReadTransaction(trans.get(), &response_data); + EXPECT_EQ(OK, rv); + EXPECT_EQ(kExpectedResponseData[i], response_data); + } +} + +// Test the request-challenge-retry sequence for basic auth when there is +// an identity in the URL. The request should be sent as normal, but when +// it fails the identity from the URL is no longer used. +TEST_F(HttpNetworkTransactionSpdy21Test, IgnoreAuthIdentityInURL) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://foo:b@r@www.google.com/"); + request.load_flags = LOAD_NORMAL; + + SessionDependencies session_deps; + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + // The password contains an escaped character -- for this test to pass it + // will need to be unescaped by HttpNetworkTransaction. + EXPECT_EQ("b%40r", request.url.password()); + + MockWrite data_writes1[] = { + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n\r\n"), + }; + + MockRead data_reads1[] = { + MockRead("HTTP/1.0 401 Unauthorized\r\n"), + MockRead("WWW-Authenticate: Basic realm=\"MyRealm1\"\r\n"), + MockRead("Content-Length: 10\r\n\r\n"), + MockRead(SYNCHRONOUS, ERR_FAILED), + }; + + StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), + data_writes1, arraysize(data_writes1)); + session_deps.socket_factory.AddSocketDataProvider(&data1); + + TestCompletionCallback callback1; + int rv = trans->Start(&request, callback1.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback1.WaitForResult(); + EXPECT_EQ(OK, rv); + EXPECT_FALSE(trans->IsReadyToRestartForAuth()); + + // Empty the current queue. + MessageLoop::current()->RunAllPending(); +} + +// Test that previously tried username/passwords for a realm get re-used. +TEST_F(HttpNetworkTransactionSpdy21Test, BasicAuthCacheAndPreauth) { + SessionDependencies session_deps; + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + // Transaction 1: authenticate (foo, bar) on MyRealm1 + { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/x/y/z"); + request.load_flags = 0; + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + MockWrite data_writes1[] = { + MockWrite("GET /x/y/z HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n\r\n"), + }; + + MockRead data_reads1[] = { + MockRead("HTTP/1.0 401 Unauthorized\r\n"), + MockRead("WWW-Authenticate: Basic realm=\"MyRealm1\"\r\n"), + MockRead("Content-Length: 10000\r\n\r\n"), + MockRead(SYNCHRONOUS, ERR_FAILED), + }; + + // Resend with authorization (username=foo, password=bar) + MockWrite data_writes2[] = { + MockWrite("GET /x/y/z HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n" + "Authorization: Basic Zm9vOmJhcg==\r\n\r\n"), + }; + + // Sever accepts the authorization. + MockRead data_reads2[] = { + MockRead("HTTP/1.0 200 OK\r\n"), + MockRead("Content-Length: 100\r\n\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + + StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), + data_writes1, arraysize(data_writes1)); + StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2), + data_writes2, arraysize(data_writes2)); + session_deps.socket_factory.AddSocketDataProvider(&data1); + session_deps.socket_factory.AddSocketDataProvider(&data2); + + TestCompletionCallback callback1; + + int rv = trans->Start(&request, callback1.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(CheckBasicServerAuth(response->auth_challenge.get())); + + TestCompletionCallback callback2; + + rv = trans->RestartWithAuth( + AuthCredentials(kFoo, kBar), callback2.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback2.WaitForResult(); + EXPECT_EQ(OK, rv); + + response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(response->auth_challenge.get() == NULL); + EXPECT_EQ(100, response->headers->GetContentLength()); + } + + // ------------------------------------------------------------------------ + + // Transaction 2: authenticate (foo2, bar2) on MyRealm2 + { + HttpRequestInfo request; + request.method = "GET"; + // Note that Transaction 1 was at /x/y/z, so this is in the same + // protection space as MyRealm1. + request.url = GURL("http://www.google.com/x/y/a/b"); + request.load_flags = 0; + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + MockWrite data_writes1[] = { + MockWrite("GET /x/y/a/b HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n" + // Send preemptive authorization for MyRealm1 + "Authorization: Basic Zm9vOmJhcg==\r\n\r\n"), + }; + + // The server didn't like the preemptive authorization, and + // challenges us for a different realm (MyRealm2). + MockRead data_reads1[] = { + MockRead("HTTP/1.0 401 Unauthorized\r\n"), + MockRead("WWW-Authenticate: Basic realm=\"MyRealm2\"\r\n"), + MockRead("Content-Length: 10000\r\n\r\n"), + MockRead(SYNCHRONOUS, ERR_FAILED), + }; + + // Resend with authorization for MyRealm2 (username=foo2, password=bar2) + MockWrite data_writes2[] = { + MockWrite("GET /x/y/a/b HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n" + "Authorization: Basic Zm9vMjpiYXIy\r\n\r\n"), + }; + + // Sever accepts the authorization. + MockRead data_reads2[] = { + MockRead("HTTP/1.0 200 OK\r\n"), + MockRead("Content-Length: 100\r\n\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + + StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), + data_writes1, arraysize(data_writes1)); + StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2), + data_writes2, arraysize(data_writes2)); + session_deps.socket_factory.AddSocketDataProvider(&data1); + session_deps.socket_factory.AddSocketDataProvider(&data2); + + TestCompletionCallback callback1; + + int rv = trans->Start(&request, callback1.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->auth_challenge.get()); + EXPECT_FALSE(response->auth_challenge->is_proxy); + EXPECT_EQ("www.google.com:80", + response->auth_challenge->challenger.ToString()); + EXPECT_EQ("MyRealm2", response->auth_challenge->realm); + EXPECT_EQ("basic", response->auth_challenge->scheme); + + TestCompletionCallback callback2; + + rv = trans->RestartWithAuth( + AuthCredentials(kFoo2, kBar2), callback2.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback2.WaitForResult(); + EXPECT_EQ(OK, rv); + + response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(response->auth_challenge.get() == NULL); + EXPECT_EQ(100, response->headers->GetContentLength()); + } + + // ------------------------------------------------------------------------ + + // Transaction 3: Resend a request in MyRealm's protection space -- + // succeed with preemptive authorization. + { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/x/y/z2"); + request.load_flags = 0; + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + MockWrite data_writes1[] = { + MockWrite("GET /x/y/z2 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n" + // The authorization for MyRealm1 gets sent preemptively + // (since the url is in the same protection space) + "Authorization: Basic Zm9vOmJhcg==\r\n\r\n"), + }; + + // Sever accepts the preemptive authorization + MockRead data_reads1[] = { + MockRead("HTTP/1.0 200 OK\r\n"), + MockRead("Content-Length: 100\r\n\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + + StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), + data_writes1, arraysize(data_writes1)); + session_deps.socket_factory.AddSocketDataProvider(&data1); + + TestCompletionCallback callback1; + + int rv = trans->Start(&request, callback1.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + + EXPECT_TRUE(response->auth_challenge.get() == NULL); + EXPECT_EQ(100, response->headers->GetContentLength()); + } + + // ------------------------------------------------------------------------ + + // Transaction 4: request another URL in MyRealm (however the + // url is not known to belong to the protection space, so no pre-auth). + { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/x/1"); + request.load_flags = 0; + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + MockWrite data_writes1[] = { + MockWrite("GET /x/1 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n\r\n"), + }; + + MockRead data_reads1[] = { + MockRead("HTTP/1.0 401 Unauthorized\r\n"), + MockRead("WWW-Authenticate: Basic realm=\"MyRealm1\"\r\n"), + MockRead("Content-Length: 10000\r\n\r\n"), + MockRead(SYNCHRONOUS, ERR_FAILED), + }; + + // Resend with authorization from MyRealm's cache. + MockWrite data_writes2[] = { + MockWrite("GET /x/1 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n" + "Authorization: Basic Zm9vOmJhcg==\r\n\r\n"), + }; + + // Sever accepts the authorization. + MockRead data_reads2[] = { + MockRead("HTTP/1.0 200 OK\r\n"), + MockRead("Content-Length: 100\r\n\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + + StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), + data_writes1, arraysize(data_writes1)); + StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2), + data_writes2, arraysize(data_writes2)); + session_deps.socket_factory.AddSocketDataProvider(&data1); + session_deps.socket_factory.AddSocketDataProvider(&data2); + + TestCompletionCallback callback1; + + int rv = trans->Start(&request, callback1.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(OK, rv); + + EXPECT_TRUE(trans->IsReadyToRestartForAuth()); + TestCompletionCallback callback2; + rv = trans->RestartWithAuth(AuthCredentials(), callback2.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback2.WaitForResult(); + EXPECT_EQ(OK, rv); + EXPECT_FALSE(trans->IsReadyToRestartForAuth()); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(response->auth_challenge.get() == NULL); + EXPECT_EQ(100, response->headers->GetContentLength()); + } + + // ------------------------------------------------------------------------ + + // Transaction 5: request a URL in MyRealm, but the server rejects the + // cached identity. Should invalidate and re-prompt. + { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/p/q/t"); + request.load_flags = 0; + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + MockWrite data_writes1[] = { + MockWrite("GET /p/q/t HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n\r\n"), + }; + + MockRead data_reads1[] = { + MockRead("HTTP/1.0 401 Unauthorized\r\n"), + MockRead("WWW-Authenticate: Basic realm=\"MyRealm1\"\r\n"), + MockRead("Content-Length: 10000\r\n\r\n"), + MockRead(SYNCHRONOUS, ERR_FAILED), + }; + + // Resend with authorization from cache for MyRealm. + MockWrite data_writes2[] = { + MockWrite("GET /p/q/t HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n" + "Authorization: Basic Zm9vOmJhcg==\r\n\r\n"), + }; + + // Sever rejects the authorization. + MockRead data_reads2[] = { + MockRead("HTTP/1.0 401 Unauthorized\r\n"), + MockRead("WWW-Authenticate: Basic realm=\"MyRealm1\"\r\n"), + MockRead("Content-Length: 10000\r\n\r\n"), + MockRead(SYNCHRONOUS, ERR_FAILED), + }; + + // At this point we should prompt for new credentials for MyRealm. + // Restart with username=foo3, password=foo4. + MockWrite data_writes3[] = { + MockWrite("GET /p/q/t HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n" + "Authorization: Basic Zm9vMzpiYXIz\r\n\r\n"), + }; + + // Sever accepts the authorization. + MockRead data_reads3[] = { + MockRead("HTTP/1.0 200 OK\r\n"), + MockRead("Content-Length: 100\r\n\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + + StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), + data_writes1, arraysize(data_writes1)); + StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2), + data_writes2, arraysize(data_writes2)); + StaticSocketDataProvider data3(data_reads3, arraysize(data_reads3), + data_writes3, arraysize(data_writes3)); + session_deps.socket_factory.AddSocketDataProvider(&data1); + session_deps.socket_factory.AddSocketDataProvider(&data2); + session_deps.socket_factory.AddSocketDataProvider(&data3); + + TestCompletionCallback callback1; + + int rv = trans->Start(&request, callback1.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(OK, rv); + + EXPECT_TRUE(trans->IsReadyToRestartForAuth()); + TestCompletionCallback callback2; + rv = trans->RestartWithAuth(AuthCredentials(), callback2.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback2.WaitForResult(); + EXPECT_EQ(OK, rv); + EXPECT_FALSE(trans->IsReadyToRestartForAuth()); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(CheckBasicServerAuth(response->auth_challenge.get())); + + TestCompletionCallback callback3; + + rv = trans->RestartWithAuth( + AuthCredentials(kFoo3, kBar3), callback3.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback3.WaitForResult(); + EXPECT_EQ(OK, rv); + + response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(response->auth_challenge.get() == NULL); + EXPECT_EQ(100, response->headers->GetContentLength()); + } +} + +// Tests that nonce count increments when multiple auth attempts +// are started with the same nonce. +TEST_F(HttpNetworkTransactionSpdy21Test, DigestPreAuthNonceCount) { + SessionDependencies session_deps; + HttpAuthHandlerDigest::Factory* digest_factory = + new HttpAuthHandlerDigest::Factory(); + HttpAuthHandlerDigest::FixedNonceGenerator* nonce_generator = + new HttpAuthHandlerDigest::FixedNonceGenerator("0123456789abcdef"); + digest_factory->set_nonce_generator(nonce_generator); + session_deps.http_auth_handler_factory.reset(digest_factory); + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + // Transaction 1: authenticate (foo, bar) on MyRealm1 + { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/x/y/z"); + request.load_flags = 0; + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + MockWrite data_writes1[] = { + MockWrite("GET /x/y/z HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n\r\n"), + }; + + MockRead data_reads1[] = { + MockRead("HTTP/1.0 401 Unauthorized\r\n"), + MockRead("WWW-Authenticate: Digest realm=\"digestive\", nonce=\"OU812\", " + "algorithm=MD5, qop=\"auth\"\r\n\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + + // Resend with authorization (username=foo, password=bar) + MockWrite data_writes2[] = { + MockWrite("GET /x/y/z HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n" + "Authorization: Digest username=\"foo\", realm=\"digestive\", " + "nonce=\"OU812\", uri=\"/x/y/z\", algorithm=MD5, " + "response=\"03ffbcd30add722589c1de345d7a927f\", qop=auth, " + "nc=00000001, cnonce=\"0123456789abcdef\"\r\n\r\n"), + }; + + // Sever accepts the authorization. + MockRead data_reads2[] = { + MockRead("HTTP/1.0 200 OK\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + + StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), + data_writes1, arraysize(data_writes1)); + StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2), + data_writes2, arraysize(data_writes2)); + session_deps.socket_factory.AddSocketDataProvider(&data1); + session_deps.socket_factory.AddSocketDataProvider(&data2); + + TestCompletionCallback callback1; + + int rv = trans->Start(&request, callback1.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(CheckDigestServerAuth(response->auth_challenge.get())); + + TestCompletionCallback callback2; + + rv = trans->RestartWithAuth( + AuthCredentials(kFoo, kBar), callback2.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback2.WaitForResult(); + EXPECT_EQ(OK, rv); + + response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(response->auth_challenge.get() == NULL); + } + + // ------------------------------------------------------------------------ + + // Transaction 2: Request another resource in digestive's protection space. + // This will preemptively add an Authorization header which should have an + // "nc" value of 2 (as compared to 1 in the first use. + { + HttpRequestInfo request; + request.method = "GET"; + // Note that Transaction 1 was at /x/y/z, so this is in the same + // protection space as digest. + request.url = GURL("http://www.google.com/x/y/a/b"); + request.load_flags = 0; + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + MockWrite data_writes1[] = { + MockWrite("GET /x/y/a/b HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n" + "Authorization: Digest username=\"foo\", realm=\"digestive\", " + "nonce=\"OU812\", uri=\"/x/y/a/b\", algorithm=MD5, " + "response=\"d6f9a2c07d1c5df7b89379dca1269b35\", qop=auth, " + "nc=00000002, cnonce=\"0123456789abcdef\"\r\n\r\n"), + }; + + // Sever accepts the authorization. + MockRead data_reads1[] = { + MockRead("HTTP/1.0 200 OK\r\n"), + MockRead("Content-Length: 100\r\n\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + + StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), + data_writes1, arraysize(data_writes1)); + session_deps.socket_factory.AddSocketDataProvider(&data1); + + TestCompletionCallback callback1; + + int rv = trans->Start(&request, callback1.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(response->auth_challenge.get() == NULL); + } +} + +// Test the ResetStateForRestart() private method. +TEST_F(HttpNetworkTransactionSpdy21Test, ResetStateForRestart) { + // Create a transaction (the dependencies aren't important). + SessionDependencies session_deps; + scoped_ptr<HttpNetworkTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + // Setup some state (which we expect ResetStateForRestart() will clear). + trans->read_buf_ = new IOBuffer(15); + trans->read_buf_len_ = 15; + trans->request_headers_.SetHeader("Authorization", "NTLM"); + + // Setup state in response_ + HttpResponseInfo* response = &trans->response_; + response->auth_challenge = new AuthChallengeInfo(); + response->ssl_info.cert_status = static_cast<CertStatus>(-1); // Nonsensical. + response->response_time = base::Time::Now(); + response->was_cached = true; // (Wouldn't ever actually be true...) + + { // Setup state for response_.vary_data + HttpRequestInfo request; + std::string temp("HTTP/1.1 200 OK\nVary: foo, bar\n\n"); + std::replace(temp.begin(), temp.end(), '\n', '\0'); + scoped_refptr<HttpResponseHeaders> headers(new HttpResponseHeaders(temp)); + request.extra_headers.SetHeader("Foo", "1"); + request.extra_headers.SetHeader("bar", "23"); + EXPECT_TRUE(response->vary_data.Init(request, *headers)); + } + + // Cause the above state to be reset. + trans->ResetStateForRestart(); + + // Verify that the state that needed to be reset, has been reset. + EXPECT_TRUE(trans->read_buf_.get() == NULL); + EXPECT_EQ(0, trans->read_buf_len_); + EXPECT_TRUE(trans->request_headers_.IsEmpty()); + EXPECT_TRUE(response->auth_challenge.get() == NULL); + EXPECT_TRUE(response->headers.get() == NULL); + EXPECT_FALSE(response->was_cached); + EXPECT_EQ(0U, response->ssl_info.cert_status); + EXPECT_FALSE(response->vary_data.is_valid()); +} + +// Test HTTPS connections to a site with a bad certificate +TEST_F(HttpNetworkTransactionSpdy21Test, HTTPSBadCertificate) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("https://www.google.com/"); + request.load_flags = 0; + + SessionDependencies session_deps; + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + MockWrite data_writes[] = { + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n\r\n"), + }; + + MockRead data_reads[] = { + MockRead("HTTP/1.0 200 OK\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), + MockRead("Content-Length: 100\r\n\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + + StaticSocketDataProvider ssl_bad_certificate; + StaticSocketDataProvider data(data_reads, arraysize(data_reads), + data_writes, arraysize(data_writes)); + SSLSocketDataProvider ssl_bad(ASYNC, ERR_CERT_AUTHORITY_INVALID); + SSLSocketDataProvider ssl(ASYNC, OK); + + session_deps.socket_factory.AddSocketDataProvider(&ssl_bad_certificate); + session_deps.socket_factory.AddSocketDataProvider(&data); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl_bad); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); + + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(ERR_CERT_AUTHORITY_INVALID, rv); + + rv = trans->RestartIgnoringLastError(callback.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + + ASSERT_TRUE(response != NULL); + EXPECT_EQ(100, response->headers->GetContentLength()); +} + +// Test HTTPS connections to a site with a bad certificate, going through a +// proxy +TEST_F(HttpNetworkTransactionSpdy21Test, HTTPSBadCertificateViaProxy) { + SessionDependencies session_deps(ProxyService::CreateFixed("myproxy:70")); + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("https://www.google.com/"); + request.load_flags = 0; + + MockWrite proxy_writes[] = { + MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n\r\n"), + }; + + MockRead proxy_reads[] = { + MockRead("HTTP/1.0 200 Connected\r\n\r\n"), + MockRead(SYNCHRONOUS, OK) + }; + + MockWrite data_writes[] = { + MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n\r\n"), + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n\r\n"), + }; + + MockRead data_reads[] = { + MockRead("HTTP/1.0 200 Connected\r\n\r\n"), + MockRead("HTTP/1.0 200 OK\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), + MockRead("Content-Length: 100\r\n\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + + StaticSocketDataProvider ssl_bad_certificate( + proxy_reads, arraysize(proxy_reads), + proxy_writes, arraysize(proxy_writes)); + StaticSocketDataProvider data(data_reads, arraysize(data_reads), + data_writes, arraysize(data_writes)); + SSLSocketDataProvider ssl_bad(ASYNC, ERR_CERT_AUTHORITY_INVALID); + SSLSocketDataProvider ssl(ASYNC, OK); + + session_deps.socket_factory.AddSocketDataProvider(&ssl_bad_certificate); + session_deps.socket_factory.AddSocketDataProvider(&data); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl_bad); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); + + TestCompletionCallback callback; + + for (int i = 0; i < 2; i++) { + session_deps.socket_factory.ResetNextMockIndexes(); + + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(ERR_CERT_AUTHORITY_INVALID, rv); + + rv = trans->RestartIgnoringLastError(callback.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + + ASSERT_TRUE(response != NULL); + EXPECT_EQ(100, response->headers->GetContentLength()); + } +} + + +// Test HTTPS connections to a site, going through an HTTPS proxy +TEST_F(HttpNetworkTransactionSpdy21Test, HTTPSViaHttpsProxy) { + SessionDependencies session_deps(ProxyService::CreateFixed( + "https://proxy:70")); + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("https://www.google.com/"); + request.load_flags = 0; + + MockWrite data_writes[] = { + MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n\r\n"), + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n\r\n"), + }; + + MockRead data_reads[] = { + MockRead("HTTP/1.0 200 Connected\r\n\r\n"), + MockRead("HTTP/1.1 200 OK\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), + MockRead("Content-Length: 100\r\n\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + + StaticSocketDataProvider data(data_reads, arraysize(data_reads), + data_writes, arraysize(data_writes)); + SSLSocketDataProvider proxy_ssl(ASYNC, OK); // SSL to the proxy + SSLSocketDataProvider tunnel_ssl(ASYNC, OK); // SSL through the tunnel + + session_deps.socket_factory.AddSocketDataProvider(&data); + session_deps.socket_factory.AddSSLSocketDataProvider(&proxy_ssl); + session_deps.socket_factory.AddSSLSocketDataProvider(&tunnel_ssl); + + TestCompletionCallback callback; + + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + const HttpResponseInfo* response = trans->GetResponseInfo(); + + ASSERT_TRUE(response != NULL); + + EXPECT_TRUE(response->headers->IsKeepAlive()); + EXPECT_EQ(200, response->headers->response_code()); + EXPECT_EQ(100, response->headers->GetContentLength()); + EXPECT_TRUE(HttpVersion(1, 1) == response->headers->GetHttpVersion()); +} + +// Test an HTTPS Proxy's ability to redirect a CONNECT request +TEST_F(HttpNetworkTransactionSpdy21Test, RedirectOfHttpsConnectViaHttpsProxy) { + SessionDependencies session_deps( + ProxyService::CreateFixed("https://proxy:70")); + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("https://www.google.com/"); + request.load_flags = 0; + + MockWrite data_writes[] = { + MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n\r\n"), + }; + + MockRead data_reads[] = { + MockRead("HTTP/1.1 302 Redirect\r\n"), + MockRead("Location: http://login.example.com/\r\n"), + MockRead("Content-Length: 0\r\n\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + + StaticSocketDataProvider data(data_reads, arraysize(data_reads), + data_writes, arraysize(data_writes)); + SSLSocketDataProvider proxy_ssl(ASYNC, OK); // SSL to the proxy + + session_deps.socket_factory.AddSocketDataProvider(&data); + session_deps.socket_factory.AddSSLSocketDataProvider(&proxy_ssl); + + TestCompletionCallback callback; + + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + const HttpResponseInfo* response = trans->GetResponseInfo(); + + ASSERT_TRUE(response != NULL); + + EXPECT_EQ(302, response->headers->response_code()); + std::string url; + EXPECT_TRUE(response->headers->IsRedirect(&url)); + EXPECT_EQ("http://login.example.com/", url); +} + +// Test an HTTPS (SPDY) Proxy's ability to redirect a CONNECT request +TEST_F(HttpNetworkTransactionSpdy21Test, RedirectOfHttpsConnectViaSpdyProxy) { + SessionDependencies session_deps( + ProxyService::CreateFixed("https://proxy:70")); + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("https://www.google.com/"); + request.load_flags = 0; + + scoped_ptr<spdy::SpdyFrame> conn(ConstructSpdyConnect(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> goaway(ConstructSpdyRstStream(1, spdy::CANCEL)); + MockWrite data_writes[] = { + CreateMockWrite(*conn.get(), 0, SYNCHRONOUS), + }; + + static const char* const kExtraHeaders[] = { + "location", + "http://login.example.com/", + }; + scoped_ptr<spdy::SpdyFrame> resp( + ConstructSpdySynReplyError("302 Redirect", kExtraHeaders, + arraysize(kExtraHeaders)/2, 1)); + MockRead data_reads[] = { + CreateMockRead(*resp.get(), 1, SYNCHRONOUS), + MockRead(ASYNC, 0, 2), // EOF + }; + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData( + 1, // wait for one write to finish before reading. + data_reads, arraysize(data_reads), + data_writes, arraysize(data_writes))); + SSLSocketDataProvider proxy_ssl(ASYNC, OK); // SSL to the proxy + proxy_ssl.SetNextProto(SSLClientSocket::kProtoSPDY21); + + session_deps.socket_factory.AddSocketDataProvider(data.get()); + session_deps.socket_factory.AddSSLSocketDataProvider(&proxy_ssl); + + TestCompletionCallback callback; + + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + const HttpResponseInfo* response = trans->GetResponseInfo(); + + ASSERT_TRUE(response != NULL); + + EXPECT_EQ(302, response->headers->response_code()); + std::string url; + EXPECT_TRUE(response->headers->IsRedirect(&url)); + EXPECT_EQ("http://login.example.com/", url); +} + +// Test an HTTPS Proxy's ability to provide a response to a CONNECT request +TEST_F(HttpNetworkTransactionSpdy21Test, + ErrorResponseTofHttpsConnectViaHttpsProxy) { + SessionDependencies session_deps( + ProxyService::CreateFixed("https://proxy:70")); + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("https://www.google.com/"); + request.load_flags = 0; + + MockWrite data_writes[] = { + MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n\r\n"), + }; + + MockRead data_reads[] = { + MockRead("HTTP/1.1 404 Not Found\r\n"), + MockRead("Content-Length: 23\r\n\r\n"), + MockRead("The host does not exist"), + MockRead(SYNCHRONOUS, OK), + }; + + StaticSocketDataProvider data(data_reads, arraysize(data_reads), + data_writes, arraysize(data_writes)); + SSLSocketDataProvider proxy_ssl(ASYNC, OK); // SSL to the proxy + + session_deps.socket_factory.AddSocketDataProvider(&data); + session_deps.socket_factory.AddSSLSocketDataProvider(&proxy_ssl); + + TestCompletionCallback callback; + + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + const HttpResponseInfo* response = trans->GetResponseInfo(); + + ASSERT_TRUE(response != NULL); + + EXPECT_EQ(404, response->headers->response_code()); + EXPECT_EQ(23, response->headers->GetContentLength()); + EXPECT_TRUE(HttpVersion(1, 1) == response->headers->GetHttpVersion()); + EXPECT_FALSE(response->ssl_info.is_valid()); + + std::string response_data; + ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data)); + EXPECT_EQ("The host does not exist", response_data); +} + +// Test an HTTPS (SPDY) Proxy's ability to provide a response to a CONNECT +// request +TEST_F(HttpNetworkTransactionSpdy21Test, + ErrorResponseTofHttpsConnectViaSpdyProxy) { + SessionDependencies session_deps( + ProxyService::CreateFixed("https://proxy:70")); + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("https://www.google.com/"); + request.load_flags = 0; + + scoped_ptr<spdy::SpdyFrame> conn(ConstructSpdyConnect(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> goaway(ConstructSpdyRstStream(1, spdy::CANCEL)); + MockWrite data_writes[] = { + CreateMockWrite(*conn.get(), 0, SYNCHRONOUS), + }; + + static const char* const kExtraHeaders[] = { + "location", + "http://login.example.com/", + }; + scoped_ptr<spdy::SpdyFrame> resp( + ConstructSpdySynReplyError("404 Not Found", kExtraHeaders, + arraysize(kExtraHeaders)/2, 1)); + scoped_ptr<spdy::SpdyFrame> body( + ConstructSpdyBodyFrame(1, "The host does not exist", 23, true)); + MockRead data_reads[] = { + CreateMockRead(*resp.get(), 1, SYNCHRONOUS), + CreateMockRead(*body.get(), 2, SYNCHRONOUS), + MockRead(ASYNC, 0, 3), // EOF + }; + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData( + 1, // wait for one write to finish before reading. + data_reads, arraysize(data_reads), + data_writes, arraysize(data_writes))); + SSLSocketDataProvider proxy_ssl(ASYNC, OK); // SSL to the proxy + proxy_ssl.SetNextProto(SSLClientSocket::kProtoSPDY21); + + session_deps.socket_factory.AddSocketDataProvider(data.get()); + session_deps.socket_factory.AddSSLSocketDataProvider(&proxy_ssl); + + TestCompletionCallback callback; + + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + const HttpResponseInfo* response = trans->GetResponseInfo(); + + ASSERT_TRUE(response != NULL); + + EXPECT_EQ(404, response->headers->response_code()); + EXPECT_FALSE(response->ssl_info.is_valid()); + + std::string response_data; + ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data)); + EXPECT_EQ("The host does not exist", response_data); +} + +// Test the request-challenge-retry sequence for basic auth, through +// a SPDY proxy over a single SPDY session. +TEST_F(HttpNetworkTransactionSpdy21Test, BasicAuthSpdyProxy) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("https://www.google.com/"); + // when the no authentication data flag is set. + request.load_flags = net::LOAD_DO_NOT_SEND_AUTH_DATA; + + // Configure against https proxy server "myproxy:70". + SessionDependencies session_deps( + ProxyService::CreateFixed("https://myproxy:70")); + CapturingBoundNetLog log(CapturingNetLog::kUnbounded); + session_deps.net_log = log.bound().net_log(); + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + // Since we have proxy, should try to establish tunnel. + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyConnect(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> rst(ConstructSpdyRstStream(1, spdy::CANCEL)); + + // After calling trans->RestartWithAuth(), this is the request we should + // be issuing -- the final header line contains the credentials. + const char* const kAuthCredentials[] = { + "proxy-authorization", "Basic Zm9vOmJhcg==", + }; + scoped_ptr<spdy::SpdyFrame> connect2( + ConstructSpdyConnect(kAuthCredentials, arraysize(kAuthCredentials)/2, 3)); + // fetch https://www.google.com/ via HTTP + const char get[] = "GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n\r\n"; + scoped_ptr<spdy::SpdyFrame> wrapped_get( + ConstructSpdyBodyFrame(3, get, strlen(get), false)); + + MockWrite spdy_writes[] = { + CreateMockWrite(*req, 0, ASYNC), + CreateMockWrite(*rst, 2, ASYNC), + CreateMockWrite(*connect2, 3), + CreateMockWrite(*wrapped_get, 5) + }; + + // The proxy responds to the connect with a 407, using a persistent + // connection. + const char* const kAuthChallenge[] = { + "status", "407 Proxy Authentication Required", + "version", "HTTP/1.1", + "proxy-authenticate", "Basic realm=\"MyRealm1\"", + }; + + scoped_ptr<spdy::SpdyFrame> conn_auth_resp( + ConstructSpdyControlFrame(NULL, + 0, + false, + 1, + LOWEST, + spdy::SYN_REPLY, + spdy::CONTROL_FLAG_NONE, + kAuthChallenge, + arraysize(kAuthChallenge))); + + scoped_ptr<spdy::SpdyFrame> conn_resp(ConstructSpdyGetSynReply(NULL, 0, 3)); + const char resp[] = "HTTP/1.1 200 OK\r\n" + "Content-Length: 5\r\n\r\n"; + + scoped_ptr<spdy::SpdyFrame> wrapped_get_resp( + ConstructSpdyBodyFrame(3, resp, strlen(resp), false)); + scoped_ptr<spdy::SpdyFrame> wrapped_body( + ConstructSpdyBodyFrame(3, "hello", 5, false)); + MockRead spdy_reads[] = { + CreateMockRead(*conn_auth_resp, 1, ASYNC), + CreateMockRead(*conn_resp, 4, ASYNC), + CreateMockRead(*wrapped_get_resp, 5, ASYNC), + CreateMockRead(*wrapped_body, 6, ASYNC), + MockRead(SYNCHRONOUS, ERR_IO_PENDING), + }; + + scoped_ptr<OrderedSocketData> spdy_data( + new OrderedSocketData( + spdy_reads, arraysize(spdy_reads), + spdy_writes, arraysize(spdy_writes))); + session_deps.socket_factory.AddSocketDataProvider(spdy_data.get()); + // Negotiate SPDY to the proxy + SSLSocketDataProvider proxy(ASYNC, OK); + proxy.SetNextProto(SSLClientSocket::kProtoSPDY21); + session_deps.socket_factory.AddSSLSocketDataProvider(&proxy); + // Vanilla SSL to the server + SSLSocketDataProvider server(ASYNC, OK); + session_deps.socket_factory.AddSSLSocketDataProvider(&server); + + TestCompletionCallback callback1; + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + int rv = trans->Start(&request, callback1.callback(), log.bound()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(OK, rv); + net::CapturingNetLog::EntryList entries; + log.GetEntries(&entries); + size_t pos = ExpectLogContainsSomewhere( + entries, 0, NetLog::TYPE_HTTP_TRANSACTION_SEND_TUNNEL_HEADERS, + NetLog::PHASE_NONE); + ExpectLogContainsSomewhere( + entries, pos, + NetLog::TYPE_HTTP_TRANSACTION_READ_TUNNEL_RESPONSE_HEADERS, + NetLog::PHASE_NONE); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_FALSE(response->headers == NULL); + EXPECT_EQ(407, response->headers->response_code()); + EXPECT_TRUE(HttpVersion(1, 1) == response->headers->GetHttpVersion()); + EXPECT_TRUE(response->auth_challenge.get() != NULL); + EXPECT_TRUE(CheckBasicProxyAuth(response->auth_challenge.get())); + + TestCompletionCallback callback2; + + rv = trans->RestartWithAuth(AuthCredentials(kFoo, kBar), + callback2.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback2.WaitForResult(); + EXPECT_EQ(OK, rv); + + response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + + EXPECT_TRUE(response->headers->IsKeepAlive()); + EXPECT_EQ(200, response->headers->response_code()); + EXPECT_EQ(5, response->headers->GetContentLength()); + EXPECT_TRUE(HttpVersion(1, 1) == response->headers->GetHttpVersion()); + + // The password prompt info should not be set. + EXPECT_TRUE(response->auth_challenge.get() == NULL); + + trans.reset(); + session->CloseAllConnections(); +} + +// Test HTTPS connections to a site with a bad certificate, going through an +// HTTPS proxy +TEST_F(HttpNetworkTransactionSpdy21Test, HTTPSBadCertificateViaHttpsProxy) { + SessionDependencies session_deps(ProxyService::CreateFixed( + "https://proxy:70")); + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("https://www.google.com/"); + request.load_flags = 0; + + // Attempt to fetch the URL from a server with a bad cert + MockWrite bad_cert_writes[] = { + MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n\r\n"), + }; + + MockRead bad_cert_reads[] = { + MockRead("HTTP/1.0 200 Connected\r\n\r\n"), + MockRead(SYNCHRONOUS, OK) + }; + + // Attempt to fetch the URL with a good cert + MockWrite good_data_writes[] = { + MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n\r\n"), + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n\r\n"), + }; + + MockRead good_cert_reads[] = { + MockRead("HTTP/1.0 200 Connected\r\n\r\n"), + MockRead("HTTP/1.0 200 OK\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), + MockRead("Content-Length: 100\r\n\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + + StaticSocketDataProvider ssl_bad_certificate( + bad_cert_reads, arraysize(bad_cert_reads), + bad_cert_writes, arraysize(bad_cert_writes)); + StaticSocketDataProvider data(good_cert_reads, arraysize(good_cert_reads), + good_data_writes, arraysize(good_data_writes)); + SSLSocketDataProvider ssl_bad(ASYNC, ERR_CERT_AUTHORITY_INVALID); + SSLSocketDataProvider ssl(ASYNC, OK); + + // SSL to the proxy, then CONNECT request, then SSL with bad certificate + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); + session_deps.socket_factory.AddSocketDataProvider(&ssl_bad_certificate); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl_bad); + + // SSL to the proxy, then CONNECT request, then valid SSL certificate + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); + session_deps.socket_factory.AddSocketDataProvider(&data); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); + + TestCompletionCallback callback; + + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(ERR_CERT_AUTHORITY_INVALID, rv); + + rv = trans->RestartIgnoringLastError(callback.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + + ASSERT_TRUE(response != NULL); + EXPECT_EQ(100, response->headers->GetContentLength()); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, BuildRequest_UserAgent) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.extra_headers.SetHeader(HttpRequestHeaders::kUserAgent, + "Chromium Ultra Awesome X Edition"); + + SessionDependencies session_deps; + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + MockWrite data_writes[] = { + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n" + "User-Agent: Chromium Ultra Awesome X Edition\r\n\r\n"), + }; + + // Lastly, the server responds with the actual content. + MockRead data_reads[] = { + MockRead("HTTP/1.0 200 OK\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), + MockRead("Content-Length: 100\r\n\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + + StaticSocketDataProvider data(data_reads, arraysize(data_reads), + data_writes, arraysize(data_writes)); + session_deps.socket_factory.AddSocketDataProvider(&data); + + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, BuildRequest_UserAgentOverTunnel) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("https://www.google.com/"); + request.extra_headers.SetHeader(HttpRequestHeaders::kUserAgent, + "Chromium Ultra Awesome X Edition"); + + SessionDependencies session_deps(ProxyService::CreateFixed("myproxy:70")); + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + MockWrite data_writes[] = { + MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n" + "User-Agent: Chromium Ultra Awesome X Edition\r\n\r\n"), + }; + MockRead data_reads[] = { + // Return an error, so the transaction stops here (this test isn't + // interested in the rest). + MockRead("HTTP/1.1 407 Proxy Authentication Required\r\n"), + MockRead("Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n"), + MockRead("Proxy-Connection: close\r\n\r\n"), + }; + + StaticSocketDataProvider data(data_reads, arraysize(data_reads), + data_writes, arraysize(data_writes)); + session_deps.socket_factory.AddSocketDataProvider(&data); + + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, BuildRequest_Referer) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + request.extra_headers.SetHeader(HttpRequestHeaders::kReferer, + "http://the.previous.site.com/"); + + SessionDependencies session_deps; + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + MockWrite data_writes[] = { + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n" + "Referer: http://the.previous.site.com/\r\n\r\n"), + }; + + // Lastly, the server responds with the actual content. + MockRead data_reads[] = { + MockRead("HTTP/1.0 200 OK\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), + MockRead("Content-Length: 100\r\n\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + + StaticSocketDataProvider data(data_reads, arraysize(data_reads), + data_writes, arraysize(data_writes)); + session_deps.socket_factory.AddSocketDataProvider(&data); + + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, BuildRequest_PostContentLengthZero) { + HttpRequestInfo request; + request.method = "POST"; + request.url = GURL("http://www.google.com/"); + + SessionDependencies session_deps; + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + MockWrite data_writes[] = { + MockWrite("POST / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n" + "Content-Length: 0\r\n\r\n"), + }; + + // Lastly, the server responds with the actual content. + MockRead data_reads[] = { + MockRead("HTTP/1.0 200 OK\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), + MockRead("Content-Length: 100\r\n\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + + StaticSocketDataProvider data(data_reads, arraysize(data_reads), + data_writes, arraysize(data_writes)); + session_deps.socket_factory.AddSocketDataProvider(&data); + + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, BuildRequest_PutContentLengthZero) { + HttpRequestInfo request; + request.method = "PUT"; + request.url = GURL("http://www.google.com/"); + + SessionDependencies session_deps; + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + MockWrite data_writes[] = { + MockWrite("PUT / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n" + "Content-Length: 0\r\n\r\n"), + }; + + // Lastly, the server responds with the actual content. + MockRead data_reads[] = { + MockRead("HTTP/1.0 200 OK\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), + MockRead("Content-Length: 100\r\n\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + + StaticSocketDataProvider data(data_reads, arraysize(data_reads), + data_writes, arraysize(data_writes)); + session_deps.socket_factory.AddSocketDataProvider(&data); + + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, BuildRequest_HeadContentLengthZero) { + HttpRequestInfo request; + request.method = "HEAD"; + request.url = GURL("http://www.google.com/"); + + SessionDependencies session_deps; + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + MockWrite data_writes[] = { + MockWrite("HEAD / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n" + "Content-Length: 0\r\n\r\n"), + }; + + // Lastly, the server responds with the actual content. + MockRead data_reads[] = { + MockRead("HTTP/1.0 200 OK\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), + MockRead("Content-Length: 100\r\n\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + + StaticSocketDataProvider data(data_reads, arraysize(data_reads), + data_writes, arraysize(data_writes)); + session_deps.socket_factory.AddSocketDataProvider(&data); + + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, BuildRequest_CacheControlNoCache) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = LOAD_BYPASS_CACHE; + + SessionDependencies session_deps; + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + MockWrite data_writes[] = { + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n" + "Pragma: no-cache\r\n" + "Cache-Control: no-cache\r\n\r\n"), + }; + + // Lastly, the server responds with the actual content. + MockRead data_reads[] = { + MockRead("HTTP/1.0 200 OK\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), + MockRead("Content-Length: 100\r\n\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + + StaticSocketDataProvider data(data_reads, arraysize(data_reads), + data_writes, arraysize(data_writes)); + session_deps.socket_factory.AddSocketDataProvider(&data); + + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, + BuildRequest_CacheControlValidateCache) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = LOAD_VALIDATE_CACHE; + + SessionDependencies session_deps; + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + MockWrite data_writes[] = { + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n" + "Cache-Control: max-age=0\r\n\r\n"), + }; + + // Lastly, the server responds with the actual content. + MockRead data_reads[] = { + MockRead("HTTP/1.0 200 OK\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), + MockRead("Content-Length: 100\r\n\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + + StaticSocketDataProvider data(data_reads, arraysize(data_reads), + data_writes, arraysize(data_writes)); + session_deps.socket_factory.AddSocketDataProvider(&data); + + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, BuildRequest_ExtraHeaders) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.extra_headers.SetHeader("FooHeader", "Bar"); + + SessionDependencies session_deps; + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + MockWrite data_writes[] = { + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n" + "FooHeader: Bar\r\n\r\n"), + }; + + // Lastly, the server responds with the actual content. + MockRead data_reads[] = { + MockRead("HTTP/1.0 200 OK\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), + MockRead("Content-Length: 100\r\n\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + + StaticSocketDataProvider data(data_reads, arraysize(data_reads), + data_writes, arraysize(data_writes)); + session_deps.socket_factory.AddSocketDataProvider(&data); + + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, BuildRequest_ExtraHeadersStripped) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.extra_headers.SetHeader("referer", "www.foo.com"); + request.extra_headers.SetHeader("hEllo", "Kitty"); + request.extra_headers.SetHeader("FoO", "bar"); + + SessionDependencies session_deps; + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + MockWrite data_writes[] = { + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n" + "referer: www.foo.com\r\n" + "hEllo: Kitty\r\n" + "FoO: bar\r\n\r\n"), + }; + + // Lastly, the server responds with the actual content. + MockRead data_reads[] = { + MockRead("HTTP/1.0 200 OK\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), + MockRead("Content-Length: 100\r\n\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + + StaticSocketDataProvider data(data_reads, arraysize(data_reads), + data_writes, arraysize(data_writes)); + session_deps.socket_factory.AddSocketDataProvider(&data); + + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); +} + +// http://crbug.com/112682 +#if defined(OS_MACOSX) +#define MAYBE_SOCKS4_HTTP_GET DISABLED_SOCKS4_HTTP_GET +#else +#define MAYBE_SOCKS4_HTTP_GET SOCKS4_HTTP_GET +#endif + +TEST_F(HttpNetworkTransactionSpdy21Test, MAYBE_SOCKS4_HTTP_GET) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + + SessionDependencies session_deps( + ProxyService::CreateFixed("socks4://myproxy:1080")); + + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + char write_buffer[] = { 0x04, 0x01, 0x00, 0x50, 127, 0, 0, 1, 0 }; + char read_buffer[] = { 0x00, 0x5A, 0x00, 0x00, 0, 0, 0, 0 }; + + MockWrite data_writes[] = { + MockWrite(ASYNC, write_buffer, arraysize(write_buffer)), + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n\r\n") + }; + + MockRead data_reads[] = { + MockRead(ASYNC, read_buffer, arraysize(read_buffer)), + MockRead("HTTP/1.0 200 OK\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n\r\n"), + MockRead("Payload"), + MockRead(SYNCHRONOUS, OK) + }; + + StaticSocketDataProvider data(data_reads, arraysize(data_reads), + data_writes, arraysize(data_writes)); + session_deps.socket_factory.AddSocketDataProvider(&data); + + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + + std::string response_text; + rv = ReadTransaction(trans.get(), &response_text); + EXPECT_EQ(OK, rv); + EXPECT_EQ("Payload", response_text); +} + +// http://crbug.com/112682 +#if defined(OS_MACOSX) +#define MAYBE_SOCKS4_SSL_GET DISABLED_SOCKS4_SSL_GET +#else +#define MAYBE_SOCKS4_SSL_GET SOCKS4_SSL_GET +#endif + +TEST_F(HttpNetworkTransactionSpdy21Test, MAYBE_SOCKS4_SSL_GET) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("https://www.google.com/"); + request.load_flags = 0; + + SessionDependencies session_deps( + ProxyService::CreateFixed("socks4://myproxy:1080")); + + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + unsigned char write_buffer[] = { 0x04, 0x01, 0x01, 0xBB, 127, 0, 0, 1, 0 }; + unsigned char read_buffer[] = { 0x00, 0x5A, 0x00, 0x00, 0, 0, 0, 0 }; + + MockWrite data_writes[] = { + MockWrite(ASYNC, reinterpret_cast<char*>(write_buffer), + arraysize(write_buffer)), + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n\r\n") + }; + + MockRead data_reads[] = { + MockWrite(ASYNC, reinterpret_cast<char*>(read_buffer), + arraysize(read_buffer)), + MockRead("HTTP/1.0 200 OK\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n\r\n"), + MockRead("Payload"), + MockRead(SYNCHRONOUS, OK) + }; + + StaticSocketDataProvider data(data_reads, arraysize(data_reads), + data_writes, arraysize(data_writes)); + session_deps.socket_factory.AddSocketDataProvider(&data); + + SSLSocketDataProvider ssl(ASYNC, OK); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); + + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + + std::string response_text; + rv = ReadTransaction(trans.get(), &response_text); + EXPECT_EQ(OK, rv); + EXPECT_EQ("Payload", response_text); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, SOCKS5_HTTP_GET) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + + SessionDependencies session_deps( + ProxyService::CreateFixed("socks5://myproxy:1080")); + + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + const char kSOCKS5GreetRequest[] = { 0x05, 0x01, 0x00 }; + const char kSOCKS5GreetResponse[] = { 0x05, 0x00 }; + const char kSOCKS5OkRequest[] = { + 0x05, // Version + 0x01, // Command (CONNECT) + 0x00, // Reserved. + 0x03, // Address type (DOMAINNAME). + 0x0E, // Length of domain (14) + // Domain string: + 'w', 'w', 'w', '.', 'g', 'o', 'o', 'g', 'l', 'e', '.', 'c', 'o', 'm', + 0x00, 0x50, // 16-bit port (80) + }; + const char kSOCKS5OkResponse[] = + { 0x05, 0x00, 0x00, 0x01, 127, 0, 0, 1, 0x00, 0x50 }; + + MockWrite data_writes[] = { + MockWrite(ASYNC, kSOCKS5GreetRequest, arraysize(kSOCKS5GreetRequest)), + MockWrite(ASYNC, kSOCKS5OkRequest, arraysize(kSOCKS5OkRequest)), + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n\r\n") + }; + + MockRead data_reads[] = { + MockWrite(ASYNC, kSOCKS5GreetResponse, arraysize(kSOCKS5GreetResponse)), + MockWrite(ASYNC, kSOCKS5OkResponse, arraysize(kSOCKS5OkResponse)), + MockRead("HTTP/1.0 200 OK\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n\r\n"), + MockRead("Payload"), + MockRead(SYNCHRONOUS, OK) + }; + + StaticSocketDataProvider data(data_reads, arraysize(data_reads), + data_writes, arraysize(data_writes)); + session_deps.socket_factory.AddSocketDataProvider(&data); + + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + + std::string response_text; + rv = ReadTransaction(trans.get(), &response_text); + EXPECT_EQ(OK, rv); + EXPECT_EQ("Payload", response_text); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, SOCKS5_SSL_GET) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("https://www.google.com/"); + request.load_flags = 0; + + SessionDependencies session_deps( + ProxyService::CreateFixed("socks5://myproxy:1080")); + + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + const char kSOCKS5GreetRequest[] = { 0x05, 0x01, 0x00 }; + const char kSOCKS5GreetResponse[] = { 0x05, 0x00 }; + const unsigned char kSOCKS5OkRequest[] = { + 0x05, // Version + 0x01, // Command (CONNECT) + 0x00, // Reserved. + 0x03, // Address type (DOMAINNAME). + 0x0E, // Length of domain (14) + // Domain string: + 'w', 'w', 'w', '.', 'g', 'o', 'o', 'g', 'l', 'e', '.', 'c', 'o', 'm', + 0x01, 0xBB, // 16-bit port (443) + }; + + const char kSOCKS5OkResponse[] = + { 0x05, 0x00, 0x00, 0x01, 0, 0, 0, 0, 0x00, 0x00 }; + + MockWrite data_writes[] = { + MockWrite(ASYNC, kSOCKS5GreetRequest, arraysize(kSOCKS5GreetRequest)), + MockWrite(ASYNC, reinterpret_cast<const char*>(kSOCKS5OkRequest), + arraysize(kSOCKS5OkRequest)), + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n\r\n") + }; + + MockRead data_reads[] = { + MockWrite(ASYNC, kSOCKS5GreetResponse, arraysize(kSOCKS5GreetResponse)), + MockWrite(ASYNC, kSOCKS5OkResponse, arraysize(kSOCKS5OkResponse)), + MockRead("HTTP/1.0 200 OK\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n\r\n"), + MockRead("Payload"), + MockRead(SYNCHRONOUS, OK) + }; + + StaticSocketDataProvider data(data_reads, arraysize(data_reads), + data_writes, arraysize(data_writes)); + session_deps.socket_factory.AddSocketDataProvider(&data); + + SSLSocketDataProvider ssl(ASYNC, OK); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); + + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + + std::string response_text; + rv = ReadTransaction(trans.get(), &response_text); + EXPECT_EQ(OK, rv); + EXPECT_EQ("Payload", response_text); +} + +namespace { + +// Tests that for connection endpoints the group names are correctly set. + +struct GroupNameTest { + std::string proxy_server; + std::string url; + std::string expected_group_name; + bool ssl; +}; + +scoped_refptr<HttpNetworkSession> SetupSessionForGroupNameTests( + SessionDependencies* session_deps) { + scoped_refptr<HttpNetworkSession> session(CreateSession(session_deps)); + + HttpServerProperties* http_server_properties = + session->http_server_properties(); + http_server_properties->SetAlternateProtocol( + HostPortPair("host.with.alternate", 80), 443, + NPN_SPDY_21); + + return session; +} + +int GroupNameTransactionHelper( + const std::string& url, + const scoped_refptr<HttpNetworkSession>& session) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL(url); + request.load_flags = 0; + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + TestCompletionCallback callback; + + // We do not complete this request, the dtor will clean the transaction up. + return trans->Start(&request, callback.callback(), BoundNetLog()); +} + +} // namespace + +TEST_F(HttpNetworkTransactionSpdy21Test, GroupNameForDirectConnections) { + const GroupNameTest tests[] = { + { + "", // unused + "http://www.google.com/direct", + "www.google.com:80", + false, + }, + { + "", // unused + "http://[2001:1418:13:1::25]/direct", + "[2001:1418:13:1::25]:80", + false, + }, + + // SSL Tests + { + "", // unused + "https://www.google.com/direct_ssl", + "ssl/www.google.com:443", + true, + }, + { + "", // unused + "https://[2001:1418:13:1::25]/direct", + "ssl/[2001:1418:13:1::25]:443", + true, + }, + { + "", // unused + "http://host.with.alternate/direct", + "ssl/host.with.alternate:443", + true, + }, + }; + + HttpStreamFactory::set_use_alternate_protocols(true); + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) { + SessionDependencies session_deps( + ProxyService::CreateFixed(tests[i].proxy_server)); + scoped_refptr<HttpNetworkSession> session( + SetupSessionForGroupNameTests(&session_deps)); + + HttpNetworkSessionPeer peer(session); + CaptureGroupNameTransportSocketPool* transport_conn_pool = + new CaptureGroupNameTransportSocketPool(NULL, NULL); + CaptureGroupNameSSLSocketPool* ssl_conn_pool = + new CaptureGroupNameSSLSocketPool(NULL, NULL); + MockClientSocketPoolManager* mock_pool_manager = + new MockClientSocketPoolManager; + mock_pool_manager->SetTransportSocketPool(transport_conn_pool); + mock_pool_manager->SetSSLSocketPool(ssl_conn_pool); + peer.SetClientSocketPoolManager(mock_pool_manager); + + EXPECT_EQ(ERR_IO_PENDING, + GroupNameTransactionHelper(tests[i].url, session)); + if (tests[i].ssl) + EXPECT_EQ(tests[i].expected_group_name, + ssl_conn_pool->last_group_name_received()); + else + EXPECT_EQ(tests[i].expected_group_name, + transport_conn_pool->last_group_name_received()); + } + + HttpStreamFactory::set_use_alternate_protocols(false); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, GroupNameForHTTPProxyConnections) { + const GroupNameTest tests[] = { + { + "http_proxy", + "http://www.google.com/http_proxy_normal", + "www.google.com:80", + false, + }, + + // SSL Tests + { + "http_proxy", + "https://www.google.com/http_connect_ssl", + "ssl/www.google.com:443", + true, + }, + + { + "http_proxy", + "http://host.with.alternate/direct", + "ssl/host.with.alternate:443", + true, + }, + }; + + HttpStreamFactory::set_use_alternate_protocols(true); + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) { + SessionDependencies session_deps( + ProxyService::CreateFixed(tests[i].proxy_server)); + scoped_refptr<HttpNetworkSession> session( + SetupSessionForGroupNameTests(&session_deps)); + + HttpNetworkSessionPeer peer(session); + + HostPortPair proxy_host("http_proxy", 80); + CaptureGroupNameHttpProxySocketPool* http_proxy_pool = + new CaptureGroupNameHttpProxySocketPool(NULL, NULL); + CaptureGroupNameSSLSocketPool* ssl_conn_pool = + new CaptureGroupNameSSLSocketPool(NULL, NULL); + + MockClientSocketPoolManager* mock_pool_manager = + new MockClientSocketPoolManager; + mock_pool_manager->SetSocketPoolForHTTPProxy(proxy_host, http_proxy_pool); + mock_pool_manager->SetSocketPoolForSSLWithProxy(proxy_host, ssl_conn_pool); + peer.SetClientSocketPoolManager(mock_pool_manager); + + EXPECT_EQ(ERR_IO_PENDING, + GroupNameTransactionHelper(tests[i].url, session)); + if (tests[i].ssl) + EXPECT_EQ(tests[i].expected_group_name, + ssl_conn_pool->last_group_name_received()); + else + EXPECT_EQ(tests[i].expected_group_name, + http_proxy_pool->last_group_name_received()); + } + + HttpStreamFactory::set_use_alternate_protocols(false); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, GroupNameForSOCKSConnections) { + const GroupNameTest tests[] = { + { + "socks4://socks_proxy:1080", + "http://www.google.com/socks4_direct", + "socks4/www.google.com:80", + false, + }, + { + "socks5://socks_proxy:1080", + "http://www.google.com/socks5_direct", + "socks5/www.google.com:80", + false, + }, + + // SSL Tests + { + "socks4://socks_proxy:1080", + "https://www.google.com/socks4_ssl", + "socks4/ssl/www.google.com:443", + true, + }, + { + "socks5://socks_proxy:1080", + "https://www.google.com/socks5_ssl", + "socks5/ssl/www.google.com:443", + true, + }, + + { + "socks4://socks_proxy:1080", + "http://host.with.alternate/direct", + "socks4/ssl/host.with.alternate:443", + true, + }, + }; + + HttpStreamFactory::set_use_alternate_protocols(true); + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) { + SessionDependencies session_deps( + ProxyService::CreateFixed(tests[i].proxy_server)); + scoped_refptr<HttpNetworkSession> session( + SetupSessionForGroupNameTests(&session_deps)); + + HttpNetworkSessionPeer peer(session); + + HostPortPair proxy_host("socks_proxy", 1080); + CaptureGroupNameSOCKSSocketPool* socks_conn_pool = + new CaptureGroupNameSOCKSSocketPool(NULL, NULL); + CaptureGroupNameSSLSocketPool* ssl_conn_pool = + new CaptureGroupNameSSLSocketPool(NULL, NULL); + + MockClientSocketPoolManager* mock_pool_manager = + new MockClientSocketPoolManager; + mock_pool_manager->SetSocketPoolForSOCKSProxy(proxy_host, socks_conn_pool); + mock_pool_manager->SetSocketPoolForSSLWithProxy(proxy_host, ssl_conn_pool); + peer.SetClientSocketPoolManager(mock_pool_manager); + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + EXPECT_EQ(ERR_IO_PENDING, + GroupNameTransactionHelper(tests[i].url, session)); + if (tests[i].ssl) + EXPECT_EQ(tests[i].expected_group_name, + ssl_conn_pool->last_group_name_received()); + else + EXPECT_EQ(tests[i].expected_group_name, + socks_conn_pool->last_group_name_received()); + } + + HttpStreamFactory::set_use_alternate_protocols(false); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, ReconsiderProxyAfterFailedConnection) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + + SessionDependencies session_deps( + ProxyService::CreateFixed("myproxy:70;foobar:80")); + + // This simulates failure resolving all hostnames; that means we will fail + // connecting to both proxies (myproxy:70 and foobar:80). + session_deps.host_resolver->rules()->AddSimulatedFailure("*"); + + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(ERR_PROXY_CONNECTION_FAILED, rv); +} + +namespace { + +// Base test to make sure that when the load flags for a request specify to +// bypass the cache, the DNS cache is not used. +void BypassHostCacheOnRefreshHelper(int load_flags) { + // Issue a request, asking to bypass the cache(s). + HttpRequestInfo request; + request.method = "GET"; + request.load_flags = load_flags; + request.url = GURL("http://www.google.com/"); + + SessionDependencies session_deps; + + // Select a host resolver that does caching. + session_deps.host_resolver.reset(new MockCachingHostResolver); + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction( + CreateSession(&session_deps))); + + // Warm up the host cache so it has an entry for "www.google.com". + AddressList addrlist; + TestCompletionCallback callback; + int rv = session_deps.host_resolver->Resolve( + HostResolver::RequestInfo(HostPortPair("www.google.com", 80)), &addrlist, + callback.callback(), NULL, BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + // Verify that it was added to host cache, by doing a subsequent async lookup + // and confirming it completes synchronously. + rv = session_deps.host_resolver->Resolve( + HostResolver::RequestInfo(HostPortPair("www.google.com", 80)), &addrlist, + callback.callback(), NULL, BoundNetLog()); + ASSERT_EQ(OK, rv); + + // Inject a failure the next time that "www.google.com" is resolved. This way + // we can tell if the next lookup hit the cache, or the "network". + // (cache --> success, "network" --> failure). + session_deps.host_resolver->rules()->AddSimulatedFailure("www.google.com"); + + // Connect up a mock socket which will fail with ERR_UNEXPECTED during the + // first read -- this won't be reached as the host resolution will fail first. + MockRead data_reads[] = { MockRead(SYNCHRONOUS, ERR_UNEXPECTED) }; + StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&data); + + // Run the request. + rv = trans->Start(&request, callback.callback(), BoundNetLog()); + ASSERT_EQ(ERR_IO_PENDING, rv); + rv = callback.WaitForResult(); + + // If we bypassed the cache, we would have gotten a failure while resolving + // "www.google.com". + EXPECT_EQ(ERR_NAME_NOT_RESOLVED, rv); +} + +} // namespace + +// There are multiple load flags that should trigger the host cache bypass. +// Test each in isolation: +TEST_F(HttpNetworkTransactionSpdy21Test, BypassHostCacheOnRefresh1) { + BypassHostCacheOnRefreshHelper(LOAD_BYPASS_CACHE); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, BypassHostCacheOnRefresh2) { + BypassHostCacheOnRefreshHelper(LOAD_VALIDATE_CACHE); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, BypassHostCacheOnRefresh3) { + BypassHostCacheOnRefreshHelper(LOAD_DISABLE_CACHE); +} + +// Make sure we can handle an error when writing the request. +TEST_F(HttpNetworkTransactionSpdy21Test, RequestWriteError) { + SessionDependencies session_deps; + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.foo.com/"); + request.load_flags = 0; + + MockWrite write_failure[] = { + MockWrite(ASYNC, ERR_CONNECTION_RESET), + }; + StaticSocketDataProvider data(NULL, 0, + write_failure, arraysize(write_failure)); + session_deps.socket_factory.AddSocketDataProvider(&data); + + TestCompletionCallback callback; + + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(ERR_CONNECTION_RESET, rv); +} + +// Check that a connection closed after the start of the headers finishes ok. +TEST_F(HttpNetworkTransactionSpdy21Test, ConnectionClosedAfterStartOfHeaders) { + SessionDependencies session_deps; + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.foo.com/"); + request.load_flags = 0; + + MockRead data_reads[] = { + MockRead("HTTP/1."), + MockRead(SYNCHRONOUS, OK), + }; + + StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&data); + + TestCompletionCallback callback; + + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + + EXPECT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.0 200 OK", response->headers->GetStatusLine()); + + std::string response_data; + rv = ReadTransaction(trans.get(), &response_data); + EXPECT_EQ(OK, rv); + EXPECT_EQ("", response_data); +} + +// Make sure that a dropped connection while draining the body for auth +// restart does the right thing. +TEST_F(HttpNetworkTransactionSpdy21Test, DrainResetOK) { + SessionDependencies session_deps; + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + + MockWrite data_writes1[] = { + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n\r\n"), + }; + + MockRead data_reads1[] = { + MockRead("HTTP/1.1 401 Unauthorized\r\n"), + MockRead("WWW-Authenticate: Basic realm=\"MyRealm1\"\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), + MockRead("Content-Length: 14\r\n\r\n"), + MockRead("Unauth"), + MockRead(ASYNC, ERR_CONNECTION_RESET), + }; + + StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), + data_writes1, arraysize(data_writes1)); + session_deps.socket_factory.AddSocketDataProvider(&data1); + + // After calling trans->RestartWithAuth(), this is the request we should + // be issuing -- the final header line contains the credentials. + MockWrite data_writes2[] = { + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n" + "Authorization: Basic Zm9vOmJhcg==\r\n\r\n"), + }; + + // Lastly, the server responds with the actual content. + MockRead data_reads2[] = { + MockRead("HTTP/1.1 200 OK\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), + MockRead("Content-Length: 100\r\n\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + + StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2), + data_writes2, arraysize(data_writes2)); + session_deps.socket_factory.AddSocketDataProvider(&data2); + + TestCompletionCallback callback1; + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + int rv = trans->Start(&request, callback1.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(CheckBasicServerAuth(response->auth_challenge.get())); + + TestCompletionCallback callback2; + + rv = trans->RestartWithAuth( + AuthCredentials(kFoo, kBar), callback2.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback2.WaitForResult(); + EXPECT_EQ(OK, rv); + + response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(response->auth_challenge.get() == NULL); + EXPECT_EQ(100, response->headers->GetContentLength()); +} + +// Test HTTPS connections going through a proxy that sends extra data. +TEST_F(HttpNetworkTransactionSpdy21Test, HTTPSViaProxyWithExtraData) { + SessionDependencies session_deps(ProxyService::CreateFixed("myproxy:70")); + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("https://www.google.com/"); + request.load_flags = 0; + + MockRead proxy_reads[] = { + MockRead("HTTP/1.0 200 Connected\r\n\r\nExtra data"), + MockRead(SYNCHRONOUS, OK) + }; + + StaticSocketDataProvider data(proxy_reads, arraysize(proxy_reads), NULL, 0); + SSLSocketDataProvider ssl(ASYNC, OK); + + session_deps.socket_factory.AddSocketDataProvider(&data); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); + + TestCompletionCallback callback; + + session_deps.socket_factory.ResetNextMockIndexes(); + + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(ERR_TUNNEL_CONNECTION_FAILED, rv); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, LargeContentLengthThenClose) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + + SessionDependencies session_deps; + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + MockRead data_reads[] = { + MockRead("HTTP/1.0 200 OK\r\nContent-Length:6719476739\r\n\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + + StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&data); + + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + EXPECT_EQ(OK, callback.WaitForResult()); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + + EXPECT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.0 200 OK", response->headers->GetStatusLine()); + + std::string response_data; + rv = ReadTransaction(trans.get(), &response_data); + EXPECT_EQ(ERR_CONNECTION_CLOSED, rv); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, UploadFileSmallerThanLength) { + HttpRequestInfo request; + request.method = "POST"; + request.url = GURL("http://www.google.com/upload"); + request.upload_data = new UploadData; + request.load_flags = 0; + + SessionDependencies session_deps; + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + FilePath temp_file_path; + ASSERT_TRUE(file_util::CreateTemporaryFile(&temp_file_path)); + const uint64 kFakeSize = 100000; // file is actually blank + + std::vector<UploadData::Element> elements; + UploadData::Element element; + element.SetToFilePath(temp_file_path); + element.SetContentLength(kFakeSize); + elements.push_back(element); + request.upload_data->SetElements(elements); + EXPECT_EQ(kFakeSize, request.upload_data->GetContentLengthSync()); + + MockRead data_reads[] = { + MockRead("HTTP/1.0 200 OK\r\n\r\n"), + MockRead("hello world"), + MockRead(SYNCHRONOUS, OK), + }; + StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&data); + + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + + EXPECT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.0 200 OK", response->headers->GetStatusLine()); + + std::string response_data; + rv = ReadTransaction(trans.get(), &response_data); + EXPECT_EQ(OK, rv); + EXPECT_EQ("hello world", response_data); + + file_util::Delete(temp_file_path, false); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, UploadUnreadableFile) { + HttpRequestInfo request; + request.method = "POST"; + request.url = GURL("http://www.google.com/upload"); + request.upload_data = new UploadData; + request.load_flags = 0; + + // If we try to upload an unreadable file, the network stack should report + // the file size as zero and upload zero bytes for that file. + SessionDependencies session_deps; + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + FilePath temp_file; + ASSERT_TRUE(file_util::CreateTemporaryFile(&temp_file)); + std::string temp_file_content("Unreadable file."); + ASSERT_TRUE(file_util::WriteFile(temp_file, temp_file_content.c_str(), + temp_file_content.length())); + ASSERT_TRUE(file_util::MakeFileUnreadable(temp_file)); + + std::vector<UploadData::Element> elements; + UploadData::Element element; + element.SetToFilePath(temp_file); + elements.push_back(element); + request.upload_data->SetElements(elements); + + MockRead data_reads[] = { + MockRead("HTTP/1.0 200 OK\r\n\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + MockWrite data_writes[] = { + MockWrite("POST /upload HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n" + "Content-Length: 0\r\n\r\n"), + MockWrite(SYNCHRONOUS, OK), + }; + StaticSocketDataProvider data(data_reads, arraysize(data_reads), data_writes, + arraysize(data_writes)); + session_deps.socket_factory.AddSocketDataProvider(&data); + + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.0 200 OK", response->headers->GetStatusLine()); + + file_util::Delete(temp_file, false); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, UnreadableUploadFileAfterAuthRestart) { + HttpRequestInfo request; + request.method = "POST"; + request.url = GURL("http://www.google.com/upload"); + request.upload_data = new UploadData; + request.load_flags = 0; + + SessionDependencies session_deps; + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + FilePath temp_file; + ASSERT_TRUE(file_util::CreateTemporaryFile(&temp_file)); + std::string temp_file_contents("Unreadable file."); + std::string unreadable_contents(temp_file_contents.length(), '\0'); + ASSERT_TRUE(file_util::WriteFile(temp_file, temp_file_contents.c_str(), + temp_file_contents.length())); + + std::vector<UploadData::Element> elements; + UploadData::Element element; + element.SetToFilePath(temp_file); + elements.push_back(element); + request.upload_data->SetElements(elements); + + MockRead data_reads[] = { + MockRead("HTTP/1.1 401 Unauthorized\r\n"), + MockRead("WWW-Authenticate: Basic realm=\"MyRealm1\"\r\n"), + MockRead("Content-Length: 0\r\n\r\n"), // No response body. + + MockRead("HTTP/1.1 200 OK\r\n"), + MockRead("Content-Length: 0\r\n\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + MockWrite data_writes[] = { + MockWrite("POST /upload HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n" + "Content-Length: 16\r\n\r\n"), + MockWrite(SYNCHRONOUS, temp_file_contents.c_str()), + + MockWrite("POST /upload HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n" + "Content-Length: 16\r\n" + "Authorization: Basic Zm9vOmJhcg==\r\n\r\n"), + MockWrite(SYNCHRONOUS, unreadable_contents.c_str(), + temp_file_contents.length()), + MockWrite(SYNCHRONOUS, OK), + }; + StaticSocketDataProvider data(data_reads, arraysize(data_reads), data_writes, + arraysize(data_writes)); + session_deps.socket_factory.AddSocketDataProvider(&data); + + TestCompletionCallback callback1; + + int rv = trans->Start(&request, callback1.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 401 Unauthorized", response->headers->GetStatusLine()); + EXPECT_TRUE(CheckBasicServerAuth(response->auth_challenge.get())); + + // Now make the file unreadable and try again. + ASSERT_TRUE(file_util::MakeFileUnreadable(temp_file)); + + TestCompletionCallback callback2; + + rv = trans->RestartWithAuth( + AuthCredentials(kFoo, kBar), callback2.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback2.WaitForResult(); + EXPECT_EQ(OK, rv); + + response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(response->headers != NULL); + EXPECT_TRUE(response->auth_challenge.get() == NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + + file_util::Delete(temp_file, false); +} + +// Tests that changes to Auth realms are treated like auth rejections. +TEST_F(HttpNetworkTransactionSpdy21Test, ChangeAuthRealms) { + SessionDependencies session_deps; + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + + // First transaction will request a resource and receive a Basic challenge + // with realm="first_realm". + MockWrite data_writes1[] = { + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n" + "\r\n"), + }; + MockRead data_reads1[] = { + MockRead("HTTP/1.1 401 Unauthorized\r\n" + "WWW-Authenticate: Basic realm=\"first_realm\"\r\n" + "\r\n"), + }; + + // After calling trans->RestartWithAuth(), provide an Authentication header + // for first_realm. The server will reject and provide a challenge with + // second_realm. + MockWrite data_writes2[] = { + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n" + "Authorization: Basic Zmlyc3Q6YmF6\r\n" + "\r\n"), + }; + MockRead data_reads2[] = { + MockRead("HTTP/1.1 401 Unauthorized\r\n" + "WWW-Authenticate: Basic realm=\"second_realm\"\r\n" + "\r\n"), + }; + + // This again fails, and goes back to first_realm. Make sure that the + // entry is removed from cache. + MockWrite data_writes3[] = { + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n" + "Authorization: Basic c2Vjb25kOmZvdQ==\r\n" + "\r\n"), + }; + MockRead data_reads3[] = { + MockRead("HTTP/1.1 401 Unauthorized\r\n" + "WWW-Authenticate: Basic realm=\"first_realm\"\r\n" + "\r\n"), + }; + + // Try one last time (with the correct password) and get the resource. + MockWrite data_writes4[] = { + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n" + "Authorization: Basic Zmlyc3Q6YmFy\r\n" + "\r\n"), + }; + MockRead data_reads4[] = { + MockRead("HTTP/1.1 200 OK\r\n" + "Content-Type: text/html; charset=iso-8859-1\r\n" + "Content-Length: 5\r\n" + "\r\n" + "hello"), + }; + + StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), + data_writes1, arraysize(data_writes1)); + StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2), + data_writes2, arraysize(data_writes2)); + StaticSocketDataProvider data3(data_reads3, arraysize(data_reads3), + data_writes3, arraysize(data_writes3)); + StaticSocketDataProvider data4(data_reads4, arraysize(data_reads4), + data_writes4, arraysize(data_writes4)); + session_deps.socket_factory.AddSocketDataProvider(&data1); + session_deps.socket_factory.AddSocketDataProvider(&data2); + session_deps.socket_factory.AddSocketDataProvider(&data3); + session_deps.socket_factory.AddSocketDataProvider(&data4); + + TestCompletionCallback callback1; + + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + // Issue the first request with Authorize headers. There should be a + // password prompt for first_realm waiting to be filled in after the + // transaction completes. + int rv = trans->Start(&request, callback1.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback1.WaitForResult(); + EXPECT_EQ(OK, rv); + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + const AuthChallengeInfo* challenge = response->auth_challenge.get(); + ASSERT_FALSE(challenge == NULL); + EXPECT_FALSE(challenge->is_proxy); + EXPECT_EQ("www.google.com:80", challenge->challenger.ToString()); + EXPECT_EQ("first_realm", challenge->realm); + EXPECT_EQ("basic", challenge->scheme); + + // Issue the second request with an incorrect password. There should be a + // password prompt for second_realm waiting to be filled in after the + // transaction completes. + TestCompletionCallback callback2; + rv = trans->RestartWithAuth( + AuthCredentials(kFirst, kBaz), callback2.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback2.WaitForResult(); + EXPECT_EQ(OK, rv); + response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + challenge = response->auth_challenge.get(); + ASSERT_FALSE(challenge == NULL); + EXPECT_FALSE(challenge->is_proxy); + EXPECT_EQ("www.google.com:80", challenge->challenger.ToString()); + EXPECT_EQ("second_realm", challenge->realm); + EXPECT_EQ("basic", challenge->scheme); + + // Issue the third request with another incorrect password. There should be + // a password prompt for first_realm waiting to be filled in. If the password + // prompt is not present, it indicates that the HttpAuthCacheEntry for + // first_realm was not correctly removed. + TestCompletionCallback callback3; + rv = trans->RestartWithAuth( + AuthCredentials(kSecond, kFou), callback3.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback3.WaitForResult(); + EXPECT_EQ(OK, rv); + response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + challenge = response->auth_challenge.get(); + ASSERT_FALSE(challenge == NULL); + EXPECT_FALSE(challenge->is_proxy); + EXPECT_EQ("www.google.com:80", challenge->challenger.ToString()); + EXPECT_EQ("first_realm", challenge->realm); + EXPECT_EQ("basic", challenge->scheme); + + // Issue the fourth request with the correct password and username. + TestCompletionCallback callback4; + rv = trans->RestartWithAuth( + AuthCredentials(kFirst, kBar), callback4.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback4.WaitForResult(); + EXPECT_EQ(OK, rv); + response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(response->auth_challenge.get() == NULL); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, HonorAlternateProtocolHeader) { + HttpStreamFactory::SetNextProtos(SpdyNextProtos()); + HttpStreamFactory::set_use_alternate_protocols(true); + + SessionDependencies session_deps; + + MockRead data_reads[] = { + MockRead("HTTP/1.1 200 OK\r\n"), + MockRead(kAlternateProtocolHttpHeader), + MockRead("hello world"), + MockRead(SYNCHRONOUS, OK), + }; + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + + StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0); + + session_deps.socket_factory.AddSocketDataProvider(&data); + + TestCompletionCallback callback; + + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + HostPortPair http_host_port_pair("www.google.com", 80); + const HttpServerProperties& http_server_properties = + *session->http_server_properties(); + EXPECT_FALSE( + http_server_properties.HasAlternateProtocol(http_host_port_pair)); + + EXPECT_EQ(OK, callback.WaitForResult()); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + EXPECT_FALSE(response->was_fetched_via_spdy); + EXPECT_FALSE(response->was_npn_negotiated); + + std::string response_data; + ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data)); + EXPECT_EQ("hello world", response_data); + + ASSERT_TRUE(http_server_properties.HasAlternateProtocol(http_host_port_pair)); + const PortAlternateProtocolPair alternate = + http_server_properties.GetAlternateProtocol(http_host_port_pair); + PortAlternateProtocolPair expected_alternate; + expected_alternate.port = 443; + expected_alternate.protocol = NPN_SPDY_21; + EXPECT_TRUE(expected_alternate.Equals(alternate)); + + HttpStreamFactory::set_use_alternate_protocols(false); + HttpStreamFactory::SetNextProtos(std::vector<std::string>()); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, + MarkBrokenAlternateProtocolAndFallback) { + HttpStreamFactory::set_use_alternate_protocols(true); + SessionDependencies session_deps; + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + + MockConnect mock_connect(ASYNC, ERR_CONNECTION_REFUSED); + StaticSocketDataProvider first_data; + first_data.set_connect_data(mock_connect); + session_deps.socket_factory.AddSocketDataProvider(&first_data); + + MockRead data_reads[] = { + MockRead("HTTP/1.1 200 OK\r\n\r\n"), + MockRead("hello world"), + MockRead(ASYNC, OK), + }; + StaticSocketDataProvider second_data( + data_reads, arraysize(data_reads), NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&second_data); + + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + HttpServerProperties* http_server_properties = + session->http_server_properties(); + // Port must be < 1024, or the header will be ignored (since initial port was + // port 80 (another restricted port). + http_server_properties->SetAlternateProtocol( + HostPortPair::FromURL(request.url), + 666 /* port is ignored by MockConnect anyway */, + NPN_SPDY_21); + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_EQ(OK, callback.WaitForResult()); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + + std::string response_data; + ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data)); + EXPECT_EQ("hello world", response_data); + + ASSERT_TRUE(http_server_properties->HasAlternateProtocol( + HostPortPair::FromURL(request.url))); + const PortAlternateProtocolPair alternate = + http_server_properties->GetAlternateProtocol( + HostPortPair::FromURL(request.url)); + EXPECT_EQ(ALTERNATE_PROTOCOL_BROKEN, alternate.protocol); + HttpStreamFactory::set_use_alternate_protocols(false); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, + AlternateProtocolPortRestrictedBlocked) { + // Ensure that we're not allowed to redirect traffic via an alternate + // protocol to an unrestricted (port >= 1024) when the original traffic was + // on a restricted port (port < 1024). Ensure that we can redirect in all + // other cases. + HttpStreamFactory::set_use_alternate_protocols(true); + SessionDependencies session_deps; + + HttpRequestInfo restricted_port_request; + restricted_port_request.method = "GET"; + restricted_port_request.url = GURL("http://www.google.com:1023/"); + restricted_port_request.load_flags = 0; + + MockConnect mock_connect(ASYNC, ERR_CONNECTION_REFUSED); + StaticSocketDataProvider first_data; + first_data.set_connect_data(mock_connect); + session_deps.socket_factory.AddSocketDataProvider(&first_data); + + MockRead data_reads[] = { + MockRead("HTTP/1.1 200 OK\r\n\r\n"), + MockRead("hello world"), + MockRead(ASYNC, OK), + }; + StaticSocketDataProvider second_data( + data_reads, arraysize(data_reads), NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&second_data); + + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + HttpServerProperties* http_server_properties = + session->http_server_properties(); + const int kUnrestrictedAlternatePort = 1024; + http_server_properties->SetAlternateProtocol( + HostPortPair::FromURL(restricted_port_request.url), + kUnrestrictedAlternatePort, + NPN_SPDY_21); + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + TestCompletionCallback callback; + + int rv = trans->Start( + &restricted_port_request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + // Invalid change to unrestricted port should fail. + EXPECT_EQ(ERR_CONNECTION_REFUSED, callback.WaitForResult()); + + HttpStreamFactory::set_use_alternate_protocols(false); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, + AlternateProtocolPortRestrictedAllowed) { + // Ensure that we're not allowed to redirect traffic via an alternate + // protocol to an unrestricted (port >= 1024) when the original traffic was + // on a restricted port (port < 1024). Ensure that we can redirect in all + // other cases. + HttpStreamFactory::set_use_alternate_protocols(true); + SessionDependencies session_deps; + + HttpRequestInfo restricted_port_request; + restricted_port_request.method = "GET"; + restricted_port_request.url = GURL("http://www.google.com:1023/"); + restricted_port_request.load_flags = 0; + + MockConnect mock_connect(ASYNC, ERR_CONNECTION_REFUSED); + StaticSocketDataProvider first_data; + first_data.set_connect_data(mock_connect); + session_deps.socket_factory.AddSocketDataProvider(&first_data); + + MockRead data_reads[] = { + MockRead("HTTP/1.1 200 OK\r\n\r\n"), + MockRead("hello world"), + MockRead(ASYNC, OK), + }; + StaticSocketDataProvider second_data( + data_reads, arraysize(data_reads), NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&second_data); + + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + HttpServerProperties* http_server_properties = + session->http_server_properties(); + const int kRestrictedAlternatePort = 80; + http_server_properties->SetAlternateProtocol( + HostPortPair::FromURL(restricted_port_request.url), + kRestrictedAlternatePort, + NPN_SPDY_21); + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + TestCompletionCallback callback; + + int rv = trans->Start( + &restricted_port_request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + // Valid change to restricted port should pass. + EXPECT_EQ(OK, callback.WaitForResult()); + + HttpStreamFactory::set_use_alternate_protocols(false); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, + AlternateProtocolPortUnrestrictedAllowed1) { + // Ensure that we're not allowed to redirect traffic via an alternate + // protocol to an unrestricted (port >= 1024) when the original traffic was + // on a restricted port (port < 1024). Ensure that we can redirect in all + // other cases. + HttpStreamFactory::set_use_alternate_protocols(true); + SessionDependencies session_deps; + + HttpRequestInfo unrestricted_port_request; + unrestricted_port_request.method = "GET"; + unrestricted_port_request.url = GURL("http://www.google.com:1024/"); + unrestricted_port_request.load_flags = 0; + + MockConnect mock_connect(ASYNC, ERR_CONNECTION_REFUSED); + StaticSocketDataProvider first_data; + first_data.set_connect_data(mock_connect); + session_deps.socket_factory.AddSocketDataProvider(&first_data); + + MockRead data_reads[] = { + MockRead("HTTP/1.1 200 OK\r\n\r\n"), + MockRead("hello world"), + MockRead(ASYNC, OK), + }; + StaticSocketDataProvider second_data( + data_reads, arraysize(data_reads), NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&second_data); + + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + HttpServerProperties* http_server_properties = + session->http_server_properties(); + const int kRestrictedAlternatePort = 80; + http_server_properties->SetAlternateProtocol( + HostPortPair::FromURL(unrestricted_port_request.url), + kRestrictedAlternatePort, + NPN_SPDY_21); + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + TestCompletionCallback callback; + + int rv = trans->Start( + &unrestricted_port_request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + // Valid change to restricted port should pass. + EXPECT_EQ(OK, callback.WaitForResult()); + + HttpStreamFactory::set_use_alternate_protocols(false); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, + AlternateProtocolPortUnrestrictedAllowed2) { + // Ensure that we're not allowed to redirect traffic via an alternate + // protocol to an unrestricted (port >= 1024) when the original traffic was + // on a restricted port (port < 1024). Ensure that we can redirect in all + // other cases. + HttpStreamFactory::set_use_alternate_protocols(true); + SessionDependencies session_deps; + + HttpRequestInfo unrestricted_port_request; + unrestricted_port_request.method = "GET"; + unrestricted_port_request.url = GURL("http://www.google.com:1024/"); + unrestricted_port_request.load_flags = 0; + + MockConnect mock_connect(ASYNC, ERR_CONNECTION_REFUSED); + StaticSocketDataProvider first_data; + first_data.set_connect_data(mock_connect); + session_deps.socket_factory.AddSocketDataProvider(&first_data); + + MockRead data_reads[] = { + MockRead("HTTP/1.1 200 OK\r\n\r\n"), + MockRead("hello world"), + MockRead(ASYNC, OK), + }; + StaticSocketDataProvider second_data( + data_reads, arraysize(data_reads), NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&second_data); + + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + HttpServerProperties* http_server_properties = + session->http_server_properties(); + const int kUnrestrictedAlternatePort = 1024; + http_server_properties->SetAlternateProtocol( + HostPortPair::FromURL(unrestricted_port_request.url), + kUnrestrictedAlternatePort, + NPN_SPDY_21); + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + TestCompletionCallback callback; + + int rv = trans->Start( + &unrestricted_port_request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + // Valid change to an unrestricted port should pass. + EXPECT_EQ(OK, callback.WaitForResult()); + + HttpStreamFactory::set_use_alternate_protocols(false); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, + AlternateProtocolUnsafeBlocked) { + // Ensure that we're not allowed to redirect traffic via an alternate + // protocol to an unsafe port, and that we resume the second + // HttpStreamFactoryImpl::Job once the alternate protocol request fails. + HttpStreamFactory::set_use_alternate_protocols(true); + SessionDependencies session_deps; + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + + // The alternate protocol request will error out before we attempt to connect, + // so only the standard HTTP request will try to connect. + MockRead data_reads[] = { + MockRead("HTTP/1.1 200 OK\r\n\r\n"), + MockRead("hello world"), + MockRead(ASYNC, OK), + }; + StaticSocketDataProvider data( + data_reads, arraysize(data_reads), NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&data); + + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + HttpServerProperties* http_server_properties = + session->http_server_properties(); + const int kUnsafePort = 7; + http_server_properties->SetAlternateProtocol( + HostPortPair::FromURL(request.url), + kUnsafePort, + NPN_SPDY_2); + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + // The HTTP request should succeed. + EXPECT_EQ(OK, callback.WaitForResult()); + + // Disable alternate protocol before the asserts. + HttpStreamFactory::set_use_alternate_protocols(false); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + + std::string response_data; + ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data)); + EXPECT_EQ("hello world", response_data); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, + UseAlternateProtocolForNpnSpdy) { + HttpStreamFactory::set_use_alternate_protocols(true); + HttpStreamFactory::SetNextProtos(SpdyNextProtos()); + SessionDependencies session_deps; + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + + MockRead data_reads[] = { + MockRead("HTTP/1.1 200 OK\r\n"), + MockRead(kAlternateProtocolHttpHeader), + MockRead("hello world"), + MockRead(ASYNC, OK), + }; + + StaticSocketDataProvider first_transaction( + data_reads, arraysize(data_reads), NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&first_transaction); + + SSLSocketDataProvider ssl(ASYNC, OK); + ssl.SetNextProto(SSLClientSocket::kProtoSPDY21); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); + + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + MockWrite spdy_writes[] = { CreateMockWrite(*req) }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> data(ConstructSpdyBodyFrame(1, true)); + MockRead spdy_reads[] = { + CreateMockRead(*resp), + CreateMockRead(*data), + MockRead(ASYNC, 0, 0), + }; + + scoped_ptr<DelayedSocketData> spdy_data( + new DelayedSocketData( + 1, // wait for one write to finish before reading. + spdy_reads, arraysize(spdy_reads), + spdy_writes, arraysize(spdy_writes))); + session_deps.socket_factory.AddSocketDataProvider(spdy_data.get()); + + MockConnect never_finishing_connect(SYNCHRONOUS, ERR_IO_PENDING); + StaticSocketDataProvider hanging_non_alternate_protocol_socket( + NULL, 0, NULL, 0); + hanging_non_alternate_protocol_socket.set_connect_data( + never_finishing_connect); + session_deps.socket_factory.AddSocketDataProvider( + &hanging_non_alternate_protocol_socket); + + TestCompletionCallback callback; + + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + scoped_ptr<HttpNetworkTransaction> trans(new HttpNetworkTransaction(session)); + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_EQ(OK, callback.WaitForResult()); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + + std::string response_data; + ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data)); + EXPECT_EQ("hello world", response_data); + + trans.reset(new HttpNetworkTransaction(session)); + + rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_EQ(OK, callback.WaitForResult()); + + response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + EXPECT_TRUE(response->was_fetched_via_spdy); + EXPECT_TRUE(response->was_npn_negotiated); + + ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data)); + EXPECT_EQ("hello!", response_data); + + HttpStreamFactory::SetNextProtos(std::vector<std::string>()); + HttpStreamFactory::set_use_alternate_protocols(false); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, AlternateProtocolWithSpdyLateBinding) { + HttpStreamFactory::set_use_alternate_protocols(true); + HttpStreamFactory::SetNextProtos(SpdyNextProtos()); + SessionDependencies session_deps; + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + + MockRead data_reads[] = { + MockRead("HTTP/1.1 200 OK\r\n"), + MockRead(kAlternateProtocolHttpHeader), + MockRead("hello world"), + MockRead(ASYNC, OK), + }; + + StaticSocketDataProvider first_transaction( + data_reads, arraysize(data_reads), NULL, 0); + // Socket 1 is the HTTP transaction with the Alternate-Protocol header. + session_deps.socket_factory.AddSocketDataProvider(&first_transaction); + + MockConnect never_finishing_connect(SYNCHRONOUS, ERR_IO_PENDING); + StaticSocketDataProvider hanging_socket( + NULL, 0, NULL, 0); + hanging_socket.set_connect_data(never_finishing_connect); + // Socket 2 and 3 are the hanging Alternate-Protocol and + // non-Alternate-Protocol jobs from the 2nd transaction. + session_deps.socket_factory.AddSocketDataProvider(&hanging_socket); + session_deps.socket_factory.AddSocketDataProvider(&hanging_socket); + + SSLSocketDataProvider ssl(ASYNC, OK); + ssl.SetNextProto(SSLClientSocket::kProtoSPDY21); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); + + scoped_ptr<spdy::SpdyFrame> req1(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 3, LOWEST)); + MockWrite spdy_writes[] = { + CreateMockWrite(*req1), + CreateMockWrite(*req2), + }; + scoped_ptr<spdy::SpdyFrame> resp1(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> data1(ConstructSpdyBodyFrame(1, true)); + scoped_ptr<spdy::SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 3)); + scoped_ptr<spdy::SpdyFrame> data2(ConstructSpdyBodyFrame(3, true)); + MockRead spdy_reads[] = { + CreateMockRead(*resp1), + CreateMockRead(*data1), + CreateMockRead(*resp2), + CreateMockRead(*data2), + MockRead(ASYNC, 0, 0), + }; + + scoped_ptr<DelayedSocketData> spdy_data( + new DelayedSocketData( + 2, // wait for writes to finish before reading. + spdy_reads, arraysize(spdy_reads), + spdy_writes, arraysize(spdy_writes))); + // Socket 4 is the successful Alternate-Protocol for transaction 3. + session_deps.socket_factory.AddSocketDataProvider(spdy_data.get()); + + // Socket 5 is the unsuccessful non-Alternate-Protocol for transaction 3. + session_deps.socket_factory.AddSocketDataProvider(&hanging_socket); + + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + TestCompletionCallback callback1; + HttpNetworkTransaction trans1(session); + + int rv = trans1.Start(&request, callback1.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_EQ(OK, callback1.WaitForResult()); + + const HttpResponseInfo* response = trans1.GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + + std::string response_data; + ASSERT_EQ(OK, ReadTransaction(&trans1, &response_data)); + EXPECT_EQ("hello world", response_data); + + TestCompletionCallback callback2; + HttpNetworkTransaction trans2(session); + rv = trans2.Start(&request, callback2.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + TestCompletionCallback callback3; + HttpNetworkTransaction trans3(session); + rv = trans3.Start(&request, callback3.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + EXPECT_EQ(OK, callback2.WaitForResult()); + EXPECT_EQ(OK, callback3.WaitForResult()); + + response = trans2.GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + EXPECT_TRUE(response->was_fetched_via_spdy); + EXPECT_TRUE(response->was_npn_negotiated); + ASSERT_EQ(OK, ReadTransaction(&trans2, &response_data)); + EXPECT_EQ("hello!", response_data); + + response = trans3.GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + EXPECT_TRUE(response->was_fetched_via_spdy); + EXPECT_TRUE(response->was_npn_negotiated); + ASSERT_EQ(OK, ReadTransaction(&trans3, &response_data)); + EXPECT_EQ("hello!", response_data); + + HttpStreamFactory::SetNextProtos(std::vector<std::string>()); + HttpStreamFactory::set_use_alternate_protocols(false); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, StallAlternateProtocolForNpnSpdy) { + HttpStreamFactory::set_use_alternate_protocols(true); + HttpStreamFactory::SetNextProtos(SpdyNextProtos()); + SessionDependencies session_deps; + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + + MockRead data_reads[] = { + MockRead("HTTP/1.1 200 OK\r\n"), + MockRead(kAlternateProtocolHttpHeader), + MockRead("hello world"), + MockRead(ASYNC, OK), + }; + + StaticSocketDataProvider first_transaction( + data_reads, arraysize(data_reads), NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&first_transaction); + + SSLSocketDataProvider ssl(ASYNC, OK); + ssl.SetNextProto(SSLClientSocket::kProtoSPDY21); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); + + MockConnect never_finishing_connect(SYNCHRONOUS, ERR_IO_PENDING); + StaticSocketDataProvider hanging_alternate_protocol_socket( + NULL, 0, NULL, 0); + hanging_alternate_protocol_socket.set_connect_data( + never_finishing_connect); + session_deps.socket_factory.AddSocketDataProvider( + &hanging_alternate_protocol_socket); + + // 2nd request is just a copy of the first one, over HTTP again. + session_deps.socket_factory.AddSocketDataProvider(&first_transaction); + + TestCompletionCallback callback; + + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + scoped_ptr<HttpNetworkTransaction> trans(new HttpNetworkTransaction(session)); + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_EQ(OK, callback.WaitForResult()); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + + std::string response_data; + ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data)); + EXPECT_EQ("hello world", response_data); + + trans.reset(new HttpNetworkTransaction(session)); + + rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_EQ(OK, callback.WaitForResult()); + + response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + EXPECT_FALSE(response->was_fetched_via_spdy); + EXPECT_FALSE(response->was_npn_negotiated); + + ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data)); + EXPECT_EQ("hello world", response_data); + + HttpStreamFactory::SetNextProtos(std::vector<std::string>()); + HttpStreamFactory::set_use_alternate_protocols(false); +} + +class CapturingProxyResolver : public ProxyResolver { + public: + CapturingProxyResolver() : ProxyResolver(false /* expects_pac_bytes */) {} + virtual ~CapturingProxyResolver() {} + + virtual int GetProxyForURL(const GURL& url, + ProxyInfo* results, + const CompletionCallback& callback, + RequestHandle* request, + const BoundNetLog& net_log) { + ProxyServer proxy_server(ProxyServer::SCHEME_HTTP, + HostPortPair("myproxy", 80)); + results->UseProxyServer(proxy_server); + resolved_.push_back(url); + return OK; + } + + virtual void CancelRequest(RequestHandle request) { + NOTREACHED(); + } + + virtual LoadState GetLoadState(RequestHandle request) const OVERRIDE { + NOTREACHED(); + return LOAD_STATE_IDLE; + } + + virtual LoadState GetLoadStateThreadSafe( + RequestHandle request) const OVERRIDE { + NOTREACHED(); + return LOAD_STATE_IDLE; + } + + virtual void CancelSetPacScript() { + NOTREACHED(); + } + + virtual int SetPacScript(const scoped_refptr<ProxyResolverScriptData>&, + const CompletionCallback& /*callback*/) { + return OK; + } + + const std::vector<GURL>& resolved() const { return resolved_; } + + private: + std::vector<GURL> resolved_; + + DISALLOW_COPY_AND_ASSIGN(CapturingProxyResolver); +}; + +TEST_F(HttpNetworkTransactionSpdy21Test, + UseAlternateProtocolForTunneledNpnSpdy) { + HttpStreamFactory::set_use_alternate_protocols(true); + HttpStreamFactory::SetNextProtos(SpdyNextProtos()); + + ProxyConfig proxy_config; + proxy_config.set_auto_detect(true); + proxy_config.set_pac_url(GURL("http://fooproxyurl")); + + CapturingProxyResolver* capturing_proxy_resolver = + new CapturingProxyResolver(); + SessionDependencies session_deps(new ProxyService( + new ProxyConfigServiceFixed(proxy_config), capturing_proxy_resolver, + NULL)); + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + + MockRead data_reads[] = { + MockRead("HTTP/1.1 200 OK\r\n"), + MockRead(kAlternateProtocolHttpHeader), + MockRead("hello world"), + MockRead(ASYNC, OK), + }; + + StaticSocketDataProvider first_transaction( + data_reads, arraysize(data_reads), NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&first_transaction); + + SSLSocketDataProvider ssl(ASYNC, OK); + ssl.SetNextProto(SSLClientSocket::kProtoSPDY21); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); + + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + MockWrite spdy_writes[] = { + MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n\r\n"), // 0 + CreateMockWrite(*req) // 3 + }; + + const char kCONNECTResponse[] = "HTTP/1.1 200 Connected\r\n\r\n"; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> data(ConstructSpdyBodyFrame(1, true)); + MockRead spdy_reads[] = { + MockRead(ASYNC, kCONNECTResponse, arraysize(kCONNECTResponse) - 1, 1), // 1 + CreateMockRead(*resp.get(), 4), // 2, 4 + CreateMockRead(*data.get(), 4), // 5 + MockRead(ASYNC, 0, 0, 4), // 6 + }; + + scoped_ptr<OrderedSocketData> spdy_data( + new OrderedSocketData( + spdy_reads, arraysize(spdy_reads), + spdy_writes, arraysize(spdy_writes))); + session_deps.socket_factory.AddSocketDataProvider(spdy_data.get()); + + MockConnect never_finishing_connect(SYNCHRONOUS, ERR_IO_PENDING); + StaticSocketDataProvider hanging_non_alternate_protocol_socket( + NULL, 0, NULL, 0); + hanging_non_alternate_protocol_socket.set_connect_data( + never_finishing_connect); + session_deps.socket_factory.AddSocketDataProvider( + &hanging_non_alternate_protocol_socket); + + TestCompletionCallback callback; + + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + scoped_ptr<HttpNetworkTransaction> trans(new HttpNetworkTransaction(session)); + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_EQ(OK, callback.WaitForResult()); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + EXPECT_FALSE(response->was_fetched_via_spdy); + EXPECT_FALSE(response->was_npn_negotiated); + + std::string response_data; + ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data)); + EXPECT_EQ("hello world", response_data); + + trans.reset(new HttpNetworkTransaction(session)); + + rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_EQ(OK, callback.WaitForResult()); + + response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + EXPECT_TRUE(response->was_fetched_via_spdy); + EXPECT_TRUE(response->was_npn_negotiated); + + ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data)); + EXPECT_EQ("hello!", response_data); + ASSERT_EQ(3u, capturing_proxy_resolver->resolved().size()); + EXPECT_EQ("http://www.google.com/", + capturing_proxy_resolver->resolved()[0].spec()); + EXPECT_EQ("https://www.google.com/", + capturing_proxy_resolver->resolved()[1].spec()); + + HttpStreamFactory::SetNextProtos(std::vector<std::string>()); + HttpStreamFactory::set_use_alternate_protocols(false); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, + UseAlternateProtocolForNpnSpdyWithExistingSpdySession) { + HttpStreamFactory::set_use_alternate_protocols(true); + HttpStreamFactory::SetNextProtos(SpdyNextProtos()); + SessionDependencies session_deps; + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + + MockRead data_reads[] = { + MockRead("HTTP/1.1 200 OK\r\n"), + MockRead(kAlternateProtocolHttpHeader), + MockRead("hello world"), + MockRead(ASYNC, OK), + }; + + StaticSocketDataProvider first_transaction( + data_reads, arraysize(data_reads), NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&first_transaction); + + SSLSocketDataProvider ssl(ASYNC, OK); + ssl.SetNextProto(SSLClientSocket::kProtoSPDY21); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); + // Make sure we use ssl for spdy here. + SpdySession::SetSSLMode(true); + + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + MockWrite spdy_writes[] = { CreateMockWrite(*req) }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> data(ConstructSpdyBodyFrame(1, true)); + MockRead spdy_reads[] = { + CreateMockRead(*resp), + CreateMockRead(*data), + MockRead(ASYNC, 0, 0), + }; + + scoped_ptr<DelayedSocketData> spdy_data( + new DelayedSocketData( + 1, // wait for one write to finish before reading. + spdy_reads, arraysize(spdy_reads), + spdy_writes, arraysize(spdy_writes))); + session_deps.socket_factory.AddSocketDataProvider(spdy_data.get()); + + TestCompletionCallback callback; + + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + scoped_ptr<HttpNetworkTransaction> trans(new HttpNetworkTransaction(session)); + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_EQ(OK, callback.WaitForResult()); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + + std::string response_data; + ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data)); + EXPECT_EQ("hello world", response_data); + + // Set up an initial SpdySession in the pool to reuse. + HostPortPair host_port_pair("www.google.com", 443); + HostPortProxyPair pair(host_port_pair, ProxyServer::Direct()); + scoped_refptr<SpdySession> spdy_session = + session->spdy_session_pool()->Get(pair, BoundNetLog()); + scoped_refptr<TransportSocketParams> transport_params( + new TransportSocketParams(host_port_pair, MEDIUM, false, false)); + + scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle); + EXPECT_EQ(ERR_IO_PENDING, + connection->Init(host_port_pair.ToString(), + transport_params, + LOWEST, + callback.callback(), + session->GetTransportSocketPool(), + BoundNetLog())); + EXPECT_EQ(OK, callback.WaitForResult()); + + SSLConfig ssl_config; + session->ssl_config_service()->GetSSLConfig(&ssl_config); + scoped_ptr<ClientSocketHandle> ssl_connection(new ClientSocketHandle); + SSLClientSocketContext context; + context.cert_verifier = session_deps.cert_verifier.get(); + ssl_connection->set_socket(session_deps.socket_factory.CreateSSLClientSocket( + connection.release(), HostPortPair("" , 443), ssl_config, + NULL /* ssl_host_info */, context)); + EXPECT_EQ(ERR_IO_PENDING, + ssl_connection->socket()->Connect(callback.callback())); + EXPECT_EQ(OK, callback.WaitForResult()); + + EXPECT_EQ(OK, spdy_session->InitializeWithSocket(ssl_connection.release(), + true, OK)); + + trans.reset(new HttpNetworkTransaction(session)); + + rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_EQ(OK, callback.WaitForResult()); + + response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + EXPECT_TRUE(response->was_fetched_via_spdy); + EXPECT_TRUE(response->was_npn_negotiated); + + ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data)); + EXPECT_EQ("hello!", response_data); + + HttpStreamFactory::SetNextProtos(std::vector<std::string>()); + HttpStreamFactory::set_use_alternate_protocols(false); +} + +// GenerateAuthToken is a mighty big test. +// It tests all permutation of GenerateAuthToken behavior: +// - Synchronous and Asynchronous completion. +// - OK or error on completion. +// - Direct connection, non-authenticating proxy, and authenticating proxy. +// - HTTP or HTTPS backend (to include proxy tunneling). +// - Non-authenticating and authenticating backend. +// +// In all, there are 44 reasonable permuations (for example, if there are +// problems generating an auth token for an authenticating proxy, we don't +// need to test all permutations of the backend server). +// +// The test proceeds by going over each of the configuration cases, and +// potentially running up to three rounds in each of the tests. The TestConfig +// specifies both the configuration for the test as well as the expectations +// for the results. +TEST_F(HttpNetworkTransactionSpdy21Test, GenerateAuthToken) { + static const char kServer[] = "http://www.example.com"; + static const char kSecureServer[] = "https://www.example.com"; + static const char kProxy[] = "myproxy:70"; + const int kAuthErr = ERR_INVALID_AUTH_CREDENTIALS; + + enum AuthTiming { + AUTH_NONE, + AUTH_SYNC, + AUTH_ASYNC, + }; + + const MockWrite kGet( + "GET / HTTP/1.1\r\n" + "Host: www.example.com\r\n" + "Connection: keep-alive\r\n\r\n"); + const MockWrite kGetProxy( + "GET http://www.example.com/ HTTP/1.1\r\n" + "Host: www.example.com\r\n" + "Proxy-Connection: keep-alive\r\n\r\n"); + const MockWrite kGetAuth( + "GET / HTTP/1.1\r\n" + "Host: www.example.com\r\n" + "Connection: keep-alive\r\n" + "Authorization: auth_token\r\n\r\n"); + const MockWrite kGetProxyAuth( + "GET http://www.example.com/ HTTP/1.1\r\n" + "Host: www.example.com\r\n" + "Proxy-Connection: keep-alive\r\n" + "Proxy-Authorization: auth_token\r\n\r\n"); + const MockWrite kGetAuthThroughProxy( + "GET http://www.example.com/ HTTP/1.1\r\n" + "Host: www.example.com\r\n" + "Proxy-Connection: keep-alive\r\n" + "Authorization: auth_token\r\n\r\n"); + const MockWrite kGetAuthWithProxyAuth( + "GET http://www.example.com/ HTTP/1.1\r\n" + "Host: www.example.com\r\n" + "Proxy-Connection: keep-alive\r\n" + "Proxy-Authorization: auth_token\r\n" + "Authorization: auth_token\r\n\r\n"); + const MockWrite kConnect( + "CONNECT www.example.com:443 HTTP/1.1\r\n" + "Host: www.example.com\r\n" + "Proxy-Connection: keep-alive\r\n\r\n"); + const MockWrite kConnectProxyAuth( + "CONNECT www.example.com:443 HTTP/1.1\r\n" + "Host: www.example.com\r\n" + "Proxy-Connection: keep-alive\r\n" + "Proxy-Authorization: auth_token\r\n\r\n"); + + const MockRead kSuccess( + "HTTP/1.1 200 OK\r\n" + "Content-Type: text/html; charset=iso-8859-1\r\n" + "Content-Length: 3\r\n\r\n" + "Yes"); + const MockRead kFailure( + "Should not be called."); + const MockRead kServerChallenge( + "HTTP/1.1 401 Unauthorized\r\n" + "WWW-Authenticate: Mock realm=server\r\n" + "Content-Type: text/html; charset=iso-8859-1\r\n" + "Content-Length: 14\r\n\r\n" + "Unauthorized\r\n"); + const MockRead kProxyChallenge( + "HTTP/1.1 407 Unauthorized\r\n" + "Proxy-Authenticate: Mock realm=proxy\r\n" + "Proxy-Connection: close\r\n" + "Content-Type: text/html; charset=iso-8859-1\r\n" + "Content-Length: 14\r\n\r\n" + "Unauthorized\r\n"); + const MockRead kProxyConnected( + "HTTP/1.1 200 Connection Established\r\n\r\n"); + + // NOTE(cbentzel): I wanted TestReadWriteRound to be a simple struct with + // no constructors, but the C++ compiler on Windows warns about + // unspecified data in compound literals. So, moved to using constructors, + // and TestRound's created with the default constructor should not be used. + struct TestRound { + TestRound() + : expected_rv(ERR_UNEXPECTED), + extra_write(NULL), + extra_read(NULL) { + } + TestRound(const MockWrite& write_arg, const MockRead& read_arg, + int expected_rv_arg) + : write(write_arg), + read(read_arg), + expected_rv(expected_rv_arg), + extra_write(NULL), + extra_read(NULL) { + } + TestRound(const MockWrite& write_arg, const MockRead& read_arg, + int expected_rv_arg, const MockWrite* extra_write_arg, + const MockWrite* extra_read_arg) + : write(write_arg), + read(read_arg), + expected_rv(expected_rv_arg), + extra_write(extra_write_arg), + extra_read(extra_read_arg) { + } + MockWrite write; + MockRead read; + int expected_rv; + const MockWrite* extra_write; + const MockRead* extra_read; + }; + + static const int kNoSSL = 500; + + struct TestConfig { + const char* proxy_url; + AuthTiming proxy_auth_timing; + int proxy_auth_rv; + const char* server_url; + AuthTiming server_auth_timing; + int server_auth_rv; + int num_auth_rounds; + int first_ssl_round; + TestRound rounds[3]; + } test_configs[] = { + // Non-authenticating HTTP server with a direct connection. + { NULL, AUTH_NONE, OK, kServer, AUTH_NONE, OK, 1, kNoSSL, + { TestRound(kGet, kSuccess, OK)}}, + // Authenticating HTTP server with a direct connection. + { NULL, AUTH_NONE, OK, kServer, AUTH_SYNC, OK, 2, kNoSSL, + { TestRound(kGet, kServerChallenge, OK), + TestRound(kGetAuth, kSuccess, OK)}}, + { NULL, AUTH_NONE, OK, kServer, AUTH_SYNC, kAuthErr, 2, kNoSSL, + { TestRound(kGet, kServerChallenge, OK), + TestRound(kGetAuth, kFailure, kAuthErr)}}, + { NULL, AUTH_NONE, OK, kServer, AUTH_ASYNC, OK, 2, kNoSSL, + { TestRound(kGet, kServerChallenge, OK), + TestRound(kGetAuth, kSuccess, OK)}}, + { NULL, AUTH_NONE, OK, kServer, AUTH_ASYNC, kAuthErr, 2, kNoSSL, + { TestRound(kGet, kServerChallenge, OK), + TestRound(kGetAuth, kFailure, kAuthErr)}}, + // Non-authenticating HTTP server through a non-authenticating proxy. + { kProxy, AUTH_NONE, OK, kServer, AUTH_NONE, OK, 1, kNoSSL, + { TestRound(kGetProxy, kSuccess, OK)}}, + // Authenticating HTTP server through a non-authenticating proxy. + { kProxy, AUTH_NONE, OK, kServer, AUTH_SYNC, OK, 2, kNoSSL, + { TestRound(kGetProxy, kServerChallenge, OK), + TestRound(kGetAuthThroughProxy, kSuccess, OK)}}, + { kProxy, AUTH_NONE, OK, kServer, AUTH_SYNC, kAuthErr, 2, kNoSSL, + { TestRound(kGetProxy, kServerChallenge, OK), + TestRound(kGetAuthThroughProxy, kFailure, kAuthErr)}}, + { kProxy, AUTH_NONE, OK, kServer, AUTH_ASYNC, OK, 2, kNoSSL, + { TestRound(kGetProxy, kServerChallenge, OK), + TestRound(kGetAuthThroughProxy, kSuccess, OK)}}, + { kProxy, AUTH_NONE, OK, kServer, AUTH_ASYNC, kAuthErr, 2, kNoSSL, + { TestRound(kGetProxy, kServerChallenge, OK), + TestRound(kGetAuthThroughProxy, kFailure, kAuthErr)}}, + // Non-authenticating HTTP server through an authenticating proxy. + { kProxy, AUTH_SYNC, OK, kServer, AUTH_NONE, OK, 2, kNoSSL, + { TestRound(kGetProxy, kProxyChallenge, OK), + TestRound(kGetProxyAuth, kSuccess, OK)}}, + { kProxy, AUTH_SYNC, kAuthErr, kServer, AUTH_NONE, OK, 2, kNoSSL, + { TestRound(kGetProxy, kProxyChallenge, OK), + TestRound(kGetProxyAuth, kFailure, kAuthErr)}}, + { kProxy, AUTH_ASYNC, OK, kServer, AUTH_NONE, OK, 2, kNoSSL, + { TestRound(kGetProxy, kProxyChallenge, OK), + TestRound(kGetProxyAuth, kSuccess, OK)}}, + { kProxy, AUTH_ASYNC, kAuthErr, kServer, AUTH_NONE, OK, 2, kNoSSL, + { TestRound(kGetProxy, kProxyChallenge, OK), + TestRound(kGetProxyAuth, kFailure, kAuthErr)}}, + // Authenticating HTTP server through an authenticating proxy. + { kProxy, AUTH_SYNC, OK, kServer, AUTH_SYNC, OK, 3, kNoSSL, + { TestRound(kGetProxy, kProxyChallenge, OK), + TestRound(kGetProxyAuth, kServerChallenge, OK), + TestRound(kGetAuthWithProxyAuth, kSuccess, OK)}}, + { kProxy, AUTH_SYNC, OK, kServer, AUTH_SYNC, kAuthErr, 3, kNoSSL, + { TestRound(kGetProxy, kProxyChallenge, OK), + TestRound(kGetProxyAuth, kServerChallenge, OK), + TestRound(kGetAuthWithProxyAuth, kFailure, kAuthErr)}}, + { kProxy, AUTH_ASYNC, OK, kServer, AUTH_SYNC, OK, 3, kNoSSL, + { TestRound(kGetProxy, kProxyChallenge, OK), + TestRound(kGetProxyAuth, kServerChallenge, OK), + TestRound(kGetAuthWithProxyAuth, kSuccess, OK)}}, + { kProxy, AUTH_ASYNC, OK, kServer, AUTH_SYNC, kAuthErr, 3, kNoSSL, + { TestRound(kGetProxy, kProxyChallenge, OK), + TestRound(kGetProxyAuth, kServerChallenge, OK), + TestRound(kGetAuthWithProxyAuth, kFailure, kAuthErr)}}, + { kProxy, AUTH_SYNC, OK, kServer, AUTH_ASYNC, OK, 3, kNoSSL, + { TestRound(kGetProxy, kProxyChallenge, OK), + TestRound(kGetProxyAuth, kServerChallenge, OK), + TestRound(kGetAuthWithProxyAuth, kSuccess, OK)}}, + { kProxy, AUTH_SYNC, OK, kServer, AUTH_ASYNC, kAuthErr, 3, kNoSSL, + { TestRound(kGetProxy, kProxyChallenge, OK), + TestRound(kGetProxyAuth, kServerChallenge, OK), + TestRound(kGetAuthWithProxyAuth, kFailure, kAuthErr)}}, + { kProxy, AUTH_ASYNC, OK, kServer, AUTH_ASYNC, OK, 3, kNoSSL, + { TestRound(kGetProxy, kProxyChallenge, OK), + TestRound(kGetProxyAuth, kServerChallenge, OK), + TestRound(kGetAuthWithProxyAuth, kSuccess, OK)}}, + { kProxy, AUTH_ASYNC, OK, kServer, AUTH_ASYNC, kAuthErr, 3, kNoSSL, + { TestRound(kGetProxy, kProxyChallenge, OK), + TestRound(kGetProxyAuth, kServerChallenge, OK), + TestRound(kGetAuthWithProxyAuth, kFailure, kAuthErr)}}, + // Non-authenticating HTTPS server with a direct connection. + { NULL, AUTH_NONE, OK, kSecureServer, AUTH_NONE, OK, 1, 0, + { TestRound(kGet, kSuccess, OK)}}, + // Authenticating HTTPS server with a direct connection. + { NULL, AUTH_NONE, OK, kSecureServer, AUTH_SYNC, OK, 2, 0, + { TestRound(kGet, kServerChallenge, OK), + TestRound(kGetAuth, kSuccess, OK)}}, + { NULL, AUTH_NONE, OK, kSecureServer, AUTH_SYNC, kAuthErr, 2, 0, + { TestRound(kGet, kServerChallenge, OK), + TestRound(kGetAuth, kFailure, kAuthErr)}}, + { NULL, AUTH_NONE, OK, kSecureServer, AUTH_ASYNC, OK, 2, 0, + { TestRound(kGet, kServerChallenge, OK), + TestRound(kGetAuth, kSuccess, OK)}}, + { NULL, AUTH_NONE, OK, kSecureServer, AUTH_ASYNC, kAuthErr, 2, 0, + { TestRound(kGet, kServerChallenge, OK), + TestRound(kGetAuth, kFailure, kAuthErr)}}, + // Non-authenticating HTTPS server with a non-authenticating proxy. + { kProxy, AUTH_NONE, OK, kSecureServer, AUTH_NONE, OK, 1, 0, + { TestRound(kConnect, kProxyConnected, OK, &kGet, &kSuccess)}}, + // Authenticating HTTPS server through a non-authenticating proxy. + { kProxy, AUTH_NONE, OK, kSecureServer, AUTH_SYNC, OK, 2, 0, + { TestRound(kConnect, kProxyConnected, OK, &kGet, &kServerChallenge), + TestRound(kGetAuth, kSuccess, OK)}}, + { kProxy, AUTH_NONE, OK, kSecureServer, AUTH_SYNC, kAuthErr, 2, 0, + { TestRound(kConnect, kProxyConnected, OK, &kGet, &kServerChallenge), + TestRound(kGetAuth, kFailure, kAuthErr)}}, + { kProxy, AUTH_NONE, OK, kSecureServer, AUTH_ASYNC, OK, 2, 0, + { TestRound(kConnect, kProxyConnected, OK, &kGet, &kServerChallenge), + TestRound(kGetAuth, kSuccess, OK)}}, + { kProxy, AUTH_NONE, OK, kSecureServer, AUTH_ASYNC, kAuthErr, 2, 0, + { TestRound(kConnect, kProxyConnected, OK, &kGet, &kServerChallenge), + TestRound(kGetAuth, kFailure, kAuthErr)}}, + // Non-Authenticating HTTPS server through an authenticating proxy. + { kProxy, AUTH_SYNC, OK, kSecureServer, AUTH_NONE, OK, 2, 1, + { TestRound(kConnect, kProxyChallenge, OK), + TestRound(kConnectProxyAuth, kProxyConnected, OK, &kGet, &kSuccess)}}, + { kProxy, AUTH_SYNC, kAuthErr, kSecureServer, AUTH_NONE, OK, 2, kNoSSL, + { TestRound(kConnect, kProxyChallenge, OK), + TestRound(kConnectProxyAuth, kFailure, kAuthErr)}}, + { kProxy, AUTH_ASYNC, OK, kSecureServer, AUTH_NONE, OK, 2, 1, + { TestRound(kConnect, kProxyChallenge, OK), + TestRound(kConnectProxyAuth, kProxyConnected, OK, &kGet, &kSuccess)}}, + { kProxy, AUTH_ASYNC, kAuthErr, kSecureServer, AUTH_NONE, OK, 2, kNoSSL, + { TestRound(kConnect, kProxyChallenge, OK), + TestRound(kConnectProxyAuth, kFailure, kAuthErr)}}, + // Authenticating HTTPS server through an authenticating proxy. + { kProxy, AUTH_SYNC, OK, kSecureServer, AUTH_SYNC, OK, 3, 1, + { TestRound(kConnect, kProxyChallenge, OK), + TestRound(kConnectProxyAuth, kProxyConnected, OK, + &kGet, &kServerChallenge), + TestRound(kGetAuth, kSuccess, OK)}}, + { kProxy, AUTH_SYNC, OK, kSecureServer, AUTH_SYNC, kAuthErr, 3, 1, + { TestRound(kConnect, kProxyChallenge, OK), + TestRound(kConnectProxyAuth, kProxyConnected, OK, + &kGet, &kServerChallenge), + TestRound(kGetAuth, kFailure, kAuthErr)}}, + { kProxy, AUTH_ASYNC, OK, kSecureServer, AUTH_SYNC, OK, 3, 1, + { TestRound(kConnect, kProxyChallenge, OK), + TestRound(kConnectProxyAuth, kProxyConnected, OK, + &kGet, &kServerChallenge), + TestRound(kGetAuth, kSuccess, OK)}}, + { kProxy, AUTH_ASYNC, OK, kSecureServer, AUTH_SYNC, kAuthErr, 3, 1, + { TestRound(kConnect, kProxyChallenge, OK), + TestRound(kConnectProxyAuth, kProxyConnected, OK, + &kGet, &kServerChallenge), + TestRound(kGetAuth, kFailure, kAuthErr)}}, + { kProxy, AUTH_SYNC, OK, kSecureServer, AUTH_ASYNC, OK, 3, 1, + { TestRound(kConnect, kProxyChallenge, OK), + TestRound(kConnectProxyAuth, kProxyConnected, OK, + &kGet, &kServerChallenge), + TestRound(kGetAuth, kSuccess, OK)}}, + { kProxy, AUTH_SYNC, OK, kSecureServer, AUTH_ASYNC, kAuthErr, 3, 1, + { TestRound(kConnect, kProxyChallenge, OK), + TestRound(kConnectProxyAuth, kProxyConnected, OK, + &kGet, &kServerChallenge), + TestRound(kGetAuth, kFailure, kAuthErr)}}, + { kProxy, AUTH_ASYNC, OK, kSecureServer, AUTH_ASYNC, OK, 3, 1, + { TestRound(kConnect, kProxyChallenge, OK), + TestRound(kConnectProxyAuth, kProxyConnected, OK, + &kGet, &kServerChallenge), + TestRound(kGetAuth, kSuccess, OK)}}, + { kProxy, AUTH_ASYNC, OK, kSecureServer, AUTH_ASYNC, kAuthErr, 3, 1, + { TestRound(kConnect, kProxyChallenge, OK), + TestRound(kConnectProxyAuth, kProxyConnected, OK, + &kGet, &kServerChallenge), + TestRound(kGetAuth, kFailure, kAuthErr)}}, + }; + + SessionDependencies session_deps; + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_configs); ++i) { + HttpAuthHandlerMock::Factory* auth_factory( + new HttpAuthHandlerMock::Factory()); + session_deps.http_auth_handler_factory.reset(auth_factory); + const TestConfig& test_config = test_configs[i]; + + // Set up authentication handlers as necessary. + if (test_config.proxy_auth_timing != AUTH_NONE) { + for (int n = 0; n < 2; n++) { + HttpAuthHandlerMock* auth_handler(new HttpAuthHandlerMock()); + std::string auth_challenge = "Mock realm=proxy"; + GURL origin(test_config.proxy_url); + HttpAuth::ChallengeTokenizer tokenizer(auth_challenge.begin(), + auth_challenge.end()); + auth_handler->InitFromChallenge(&tokenizer, HttpAuth::AUTH_PROXY, + origin, BoundNetLog()); + auth_handler->SetGenerateExpectation( + test_config.proxy_auth_timing == AUTH_ASYNC, + test_config.proxy_auth_rv); + auth_factory->AddMockHandler(auth_handler, HttpAuth::AUTH_PROXY); + } + } + if (test_config.server_auth_timing != AUTH_NONE) { + HttpAuthHandlerMock* auth_handler(new HttpAuthHandlerMock()); + std::string auth_challenge = "Mock realm=server"; + GURL origin(test_config.server_url); + HttpAuth::ChallengeTokenizer tokenizer(auth_challenge.begin(), + auth_challenge.end()); + auth_handler->InitFromChallenge(&tokenizer, HttpAuth::AUTH_SERVER, + origin, BoundNetLog()); + auth_handler->SetGenerateExpectation( + test_config.server_auth_timing == AUTH_ASYNC, + test_config.server_auth_rv); + auth_factory->AddMockHandler(auth_handler, HttpAuth::AUTH_SERVER); + } + if (test_config.proxy_url) { + session_deps.proxy_service.reset( + ProxyService::CreateFixed(test_config.proxy_url)); + } else { + session_deps.proxy_service.reset(ProxyService::CreateDirect()); + } + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL(test_config.server_url); + request.load_flags = 0; + + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + HttpNetworkTransaction trans(CreateSession(&session_deps)); + + for (int round = 0; round < test_config.num_auth_rounds; ++round) { + const TestRound& read_write_round = test_config.rounds[round]; + + // Set up expected reads and writes. + MockRead reads[2]; + reads[0] = read_write_round.read; + size_t length_reads = 1; + if (read_write_round.extra_read) { + reads[1] = *read_write_round.extra_read; + length_reads = 2; + } + + MockWrite writes[2]; + writes[0] = read_write_round.write; + size_t length_writes = 1; + if (read_write_round.extra_write) { + writes[1] = *read_write_round.extra_write; + length_writes = 2; + } + StaticSocketDataProvider data_provider( + reads, length_reads, writes, length_writes); + session_deps.socket_factory.AddSocketDataProvider(&data_provider); + + // Add an SSL sequence if necessary. + SSLSocketDataProvider ssl_socket_data_provider(SYNCHRONOUS, OK); + if (round >= test_config.first_ssl_round) + session_deps.socket_factory.AddSSLSocketDataProvider( + &ssl_socket_data_provider); + + // Start or restart the transaction. + TestCompletionCallback callback; + int rv; + if (round == 0) { + rv = trans.Start(&request, callback.callback(), BoundNetLog()); + } else { + rv = trans.RestartWithAuth( + AuthCredentials(kFoo, kBar), callback.callback()); + } + if (rv == ERR_IO_PENDING) + rv = callback.WaitForResult(); + + // Compare results with expected data. + EXPECT_EQ(read_write_round.expected_rv, rv); + const HttpResponseInfo* response = trans.GetResponseInfo(); + if (read_write_round.expected_rv == OK) { + ASSERT_TRUE(response != NULL); + } else { + EXPECT_TRUE(response == NULL); + EXPECT_EQ(round + 1, test_config.num_auth_rounds); + continue; + } + if (round + 1 < test_config.num_auth_rounds) { + EXPECT_FALSE(response->auth_challenge.get() == NULL); + } else { + EXPECT_TRUE(response->auth_challenge.get() == NULL); + } + } + } +} + +TEST_F(HttpNetworkTransactionSpdy21Test, MultiRoundAuth) { + // Do multi-round authentication and make sure it works correctly. + SessionDependencies session_deps; + HttpAuthHandlerMock::Factory* auth_factory( + new HttpAuthHandlerMock::Factory()); + session_deps.http_auth_handler_factory.reset(auth_factory); + session_deps.proxy_service.reset(ProxyService::CreateDirect()); + session_deps.host_resolver->rules()->AddRule("www.example.com", "10.0.0.1"); + session_deps.host_resolver->set_synchronous_mode(true); + + HttpAuthHandlerMock* auth_handler(new HttpAuthHandlerMock()); + auth_handler->set_connection_based(true); + std::string auth_challenge = "Mock realm=server"; + GURL origin("http://www.example.com"); + HttpAuth::ChallengeTokenizer tokenizer(auth_challenge.begin(), + auth_challenge.end()); + auth_handler->InitFromChallenge(&tokenizer, HttpAuth::AUTH_SERVER, + origin, BoundNetLog()); + auth_factory->AddMockHandler(auth_handler, HttpAuth::AUTH_SERVER); + + int rv = OK; + const HttpResponseInfo* response = NULL; + HttpRequestInfo request; + request.method = "GET"; + request.url = origin; + request.load_flags = 0; + + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + // Use a TCP Socket Pool with only one connection per group. This is used + // to validate that the TCP socket is not released to the pool between + // each round of multi-round authentication. + HttpNetworkSessionPeer session_peer(session); + ClientSocketPoolHistograms transport_pool_histograms("SmallTCP"); + TransportClientSocketPool* transport_pool = new TransportClientSocketPool( + 50, // Max sockets for pool + 1, // Max sockets per group + &transport_pool_histograms, + session_deps.host_resolver.get(), + &session_deps.socket_factory, + session_deps.net_log); + MockClientSocketPoolManager* mock_pool_manager = + new MockClientSocketPoolManager; + mock_pool_manager->SetTransportSocketPool(transport_pool); + session_peer.SetClientSocketPoolManager(mock_pool_manager); + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + TestCompletionCallback callback; + + const MockWrite kGet( + "GET / HTTP/1.1\r\n" + "Host: www.example.com\r\n" + "Connection: keep-alive\r\n\r\n"); + const MockWrite kGetAuth( + "GET / HTTP/1.1\r\n" + "Host: www.example.com\r\n" + "Connection: keep-alive\r\n" + "Authorization: auth_token\r\n\r\n"); + + const MockRead kServerChallenge( + "HTTP/1.1 401 Unauthorized\r\n" + "WWW-Authenticate: Mock realm=server\r\n" + "Content-Type: text/html; charset=iso-8859-1\r\n" + "Content-Length: 14\r\n\r\n" + "Unauthorized\r\n"); + const MockRead kSuccess( + "HTTP/1.1 200 OK\r\n" + "Content-Type: text/html; charset=iso-8859-1\r\n" + "Content-Length: 3\r\n\r\n" + "Yes"); + + MockWrite writes[] = { + // First round + kGet, + // Second round + kGetAuth, + // Third round + kGetAuth, + // Fourth round + kGetAuth, + // Competing request + kGet, + }; + MockRead reads[] = { + // First round + kServerChallenge, + // Second round + kServerChallenge, + // Third round + kServerChallenge, + // Fourth round + kSuccess, + // Competing response + kSuccess, + }; + StaticSocketDataProvider data_provider(reads, arraysize(reads), + writes, arraysize(writes)); + session_deps.socket_factory.AddSocketDataProvider(&data_provider); + + const char* const kSocketGroup = "www.example.com:80"; + + // First round of authentication. + auth_handler->SetGenerateExpectation(false, OK); + rv = trans->Start(&request, callback.callback(), BoundNetLog()); + if (rv == ERR_IO_PENDING) + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_FALSE(response->auth_challenge.get() == NULL); + EXPECT_EQ(0, transport_pool->IdleSocketCountInGroup(kSocketGroup)); + + // In between rounds, another request comes in for the same domain. + // It should not be able to grab the TCP socket that trans has already + // claimed. + scoped_ptr<HttpTransaction> trans_compete( + new HttpNetworkTransaction(session)); + TestCompletionCallback callback_compete; + rv = trans_compete->Start( + &request, callback_compete.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + // callback_compete.WaitForResult at this point would stall forever, + // since the HttpNetworkTransaction does not release the request back to + // the pool until after authentication completes. + + // Second round of authentication. + auth_handler->SetGenerateExpectation(false, OK); + rv = trans->RestartWithAuth(AuthCredentials(kFoo, kBar), callback.callback()); + if (rv == ERR_IO_PENDING) + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(response->auth_challenge.get() == NULL); + EXPECT_EQ(0, transport_pool->IdleSocketCountInGroup(kSocketGroup)); + + // Third round of authentication. + auth_handler->SetGenerateExpectation(false, OK); + rv = trans->RestartWithAuth(AuthCredentials(), callback.callback()); + if (rv == ERR_IO_PENDING) + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(response->auth_challenge.get() == NULL); + EXPECT_EQ(0, transport_pool->IdleSocketCountInGroup(kSocketGroup)); + + // Fourth round of authentication, which completes successfully. + auth_handler->SetGenerateExpectation(false, OK); + rv = trans->RestartWithAuth(AuthCredentials(), callback.callback()); + if (rv == ERR_IO_PENDING) + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(response->auth_challenge.get() == NULL); + EXPECT_EQ(0, transport_pool->IdleSocketCountInGroup(kSocketGroup)); + + // Read the body since the fourth round was successful. This will also + // release the socket back to the pool. + scoped_refptr<IOBufferWithSize> io_buf(new IOBufferWithSize(50)); + rv = trans->Read(io_buf, io_buf->size(), callback.callback()); + if (rv == ERR_IO_PENDING) + rv = callback.WaitForResult(); + EXPECT_EQ(3, rv); + rv = trans->Read(io_buf, io_buf->size(), callback.callback()); + EXPECT_EQ(0, rv); + // There are still 0 idle sockets, since the trans_compete transaction + // will be handed it immediately after trans releases it to the group. + EXPECT_EQ(0, transport_pool->IdleSocketCountInGroup(kSocketGroup)); + + // The competing request can now finish. Wait for the headers and then + // read the body. + rv = callback_compete.WaitForResult(); + EXPECT_EQ(OK, rv); + rv = trans_compete->Read(io_buf, io_buf->size(), callback.callback()); + if (rv == ERR_IO_PENDING) + rv = callback.WaitForResult(); + EXPECT_EQ(3, rv); + rv = trans_compete->Read(io_buf, io_buf->size(), callback.callback()); + EXPECT_EQ(0, rv); + + // Finally, the socket is released to the group. + EXPECT_EQ(1, transport_pool->IdleSocketCountInGroup(kSocketGroup)); +} + +class TLSDecompressionFailureSocketDataProvider : public SocketDataProvider { + public: + explicit TLSDecompressionFailureSocketDataProvider(bool fail_all) + : fail_all_(fail_all) { + } + + virtual MockRead GetNextRead() { + if (fail_all_) + return MockRead(SYNCHRONOUS, ERR_SSL_DECOMPRESSION_FAILURE_ALERT); + + return MockRead(SYNCHRONOUS, + "HTTP/1.1 200 OK\r\nContent-Length: 3\r\n\r\nok.\r\n"); + } + + virtual MockWriteResult OnWrite(const std::string& data) { + return MockWriteResult(SYNCHRONOUS /* async */, data.size()); + } + + void Reset() { + } + + private: + const bool fail_all_; +}; + +// Test that we restart a connection when we see a decompression failure from +// the peer during the handshake. (In the real world we'll restart with SSLv3 +// and we won't offer DEFLATE in that case.) +TEST_F(HttpNetworkTransactionSpdy21Test, RestartAfterTLSDecompressionFailure) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("https://tlsdecompressionfailure.example.com/"); + request.load_flags = 0; + + SessionDependencies session_deps; + TLSDecompressionFailureSocketDataProvider socket_data_provider1( + false /* fail all reads */); + TLSDecompressionFailureSocketDataProvider socket_data_provider2(false); + SSLSocketDataProvider ssl_socket_data_provider1( + SYNCHRONOUS, ERR_SSL_DECOMPRESSION_FAILURE_ALERT); + SSLSocketDataProvider ssl_socket_data_provider2(SYNCHRONOUS, OK); + session_deps.socket_factory.AddSocketDataProvider(&socket_data_provider1); + session_deps.socket_factory.AddSocketDataProvider(&socket_data_provider2); + session_deps.socket_factory.AddSSLSocketDataProvider( + &ssl_socket_data_provider1); + session_deps.socket_factory.AddSSLSocketDataProvider( + &ssl_socket_data_provider2); + + // Work around http://crbug.com/37454 + StaticSocketDataProvider bug37454_connection; + bug37454_connection.set_connect_data(MockConnect(ASYNC, ERR_UNEXPECTED)); + session_deps.socket_factory.AddSocketDataProvider(&bug37454_connection); + + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_EQ(OK, callback.WaitForResult()); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + + std::string response_data; + ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data)); + EXPECT_EQ("ok.", response_data); +} + +// Test that we restart a connection if we get a decompression failure from the +// peer while reading the first bytes from the connection. This occurs when the +// peer cannot handle DEFLATE but we're using False Start, so we don't notice +// in the handshake. +TEST_F(HttpNetworkTransactionSpdy21Test, + RestartAfterTLSDecompressionFailureWithFalseStart) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("https://tlsdecompressionfailure2.example.com/"); + request.load_flags = 0; + + SessionDependencies session_deps; + TLSDecompressionFailureSocketDataProvider socket_data_provider1( + true /* fail all reads */); + TLSDecompressionFailureSocketDataProvider socket_data_provider2(false); + SSLSocketDataProvider ssl_socket_data_provider1(SYNCHRONOUS, OK); + SSLSocketDataProvider ssl_socket_data_provider2(SYNCHRONOUS, OK); + session_deps.socket_factory.AddSocketDataProvider(&socket_data_provider1); + session_deps.socket_factory.AddSocketDataProvider(&socket_data_provider2); + session_deps.socket_factory.AddSSLSocketDataProvider( + &ssl_socket_data_provider1); + session_deps.socket_factory.AddSSLSocketDataProvider( + &ssl_socket_data_provider2); + + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_EQ(OK, callback.WaitForResult()); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + + std::string response_data; + ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data)); + EXPECT_EQ("ok.", response_data); +} + +// This tests the case that a request is issued via http instead of spdy after +// npn is negotiated. +TEST_F(HttpNetworkTransactionSpdy21Test, NpnWithHttpOverSSL) { + HttpStreamFactory::set_use_alternate_protocols(true); + HttpStreamFactory::SetNextProtos( + MakeNextProtos("http/1.1", "http1.1", NULL)); + SessionDependencies session_deps; + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("https://www.google.com/"); + request.load_flags = 0; + + MockWrite data_writes[] = { + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n\r\n"), + }; + + MockRead data_reads[] = { + MockRead("HTTP/1.1 200 OK\r\n"), + MockRead(kAlternateProtocolHttpHeader), + MockRead("hello world"), + MockRead(SYNCHRONOUS, OK), + }; + + SSLSocketDataProvider ssl(ASYNC, OK); + ssl.next_proto_status = SSLClientSocket::kNextProtoNegotiated; + ssl.next_proto = "http/1.1"; + ssl.protocol_negotiated = SSLClientSocket::kProtoHTTP11; + + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); + + StaticSocketDataProvider data(data_reads, arraysize(data_reads), + data_writes, arraysize(data_writes)); + session_deps.socket_factory.AddSocketDataProvider(&data); + + TestCompletionCallback callback; + + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_EQ(OK, callback.WaitForResult()); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + + std::string response_data; + ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data)); + EXPECT_EQ("hello world", response_data); + + EXPECT_FALSE(response->was_fetched_via_spdy); + EXPECT_TRUE(response->was_npn_negotiated); + + HttpStreamFactory::SetNextProtos(std::vector<std::string>()); + HttpStreamFactory::set_use_alternate_protocols(false); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, SpdyPostNPNServerHangup) { + // Simulate the SSL handshake completing with an NPN negotiation + // followed by an immediate server closing of the socket. + // Fix crash: http://crbug.com/46369 + HttpStreamFactory::set_use_alternate_protocols(true); + HttpStreamFactory::SetNextProtos(SpdyNextProtos()); + SessionDependencies session_deps; + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("https://www.google.com/"); + request.load_flags = 0; + + SSLSocketDataProvider ssl(ASYNC, OK); + ssl.SetNextProto(SSLClientSocket::kProtoSPDY21); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); + + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + MockWrite spdy_writes[] = { CreateMockWrite(*req) }; + + MockRead spdy_reads[] = { + MockRead(SYNCHRONOUS, 0, 0) // Not async - return 0 immediately. + }; + + scoped_ptr<DelayedSocketData> spdy_data( + new DelayedSocketData( + 0, // don't wait in this case, immediate hangup. + spdy_reads, arraysize(spdy_reads), + spdy_writes, arraysize(spdy_writes))); + session_deps.socket_factory.AddSocketDataProvider(spdy_data.get()); + + TestCompletionCallback callback; + + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + scoped_ptr<HttpNetworkTransaction> trans(new HttpNetworkTransaction(session)); + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_EQ(ERR_CONNECTION_CLOSED, callback.WaitForResult()); + + HttpStreamFactory::SetNextProtos(std::vector<std::string>()); + HttpStreamFactory::set_use_alternate_protocols(false); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, SpdyAlternateProtocolThroughProxy) { + // This test ensures that the URL passed into the proxy is upgraded + // to https when doing an Alternate Protocol upgrade. + HttpStreamFactory::set_use_alternate_protocols(true); + HttpStreamFactory::SetNextProtos( + MakeNextProtos( + "http/1.1", "http1.1", "spdy/2.1", "spdy/2", "spdy", NULL)); + + SessionDependencies session_deps(ProxyService::CreateFixed("myproxy:70")); + HttpAuthHandlerMock::Factory* auth_factory = + new HttpAuthHandlerMock::Factory(); + HttpAuthHandlerMock* auth_handler = new HttpAuthHandlerMock(); + auth_factory->AddMockHandler(auth_handler, HttpAuth::AUTH_PROXY); + auth_factory->set_do_init_from_challenge(true); + session_deps.http_auth_handler_factory.reset(auth_factory); + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com"); + request.load_flags = 0; + + // First round goes unauthenticated through the proxy. + MockWrite data_writes_1[] = { + MockWrite("GET http://www.google.com/ HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n" + "\r\n"), + }; + MockRead data_reads_1[] = { + MockRead(SYNCHRONOUS, ERR_TEST_PEER_CLOSE_AFTER_NEXT_MOCK_READ), + MockRead("HTTP/1.1 200 OK\r\n" + "Alternate-Protocol: 443:npn-spdy/2.1\r\n" + "Proxy-Connection: close\r\n" + "\r\n"), + }; + StaticSocketDataProvider data_1(data_reads_1, arraysize(data_reads_1), + data_writes_1, arraysize(data_writes_1)); + + // Second round tries to tunnel to www.google.com due to the + // Alternate-Protocol announcement in the first round. It fails due + // to a proxy authentication challenge. + // After the failure, a tunnel is established to www.google.com using + // Proxy-Authorization headers. There is then a SPDY request round. + // + // NOTE: Despite the "Proxy-Connection: Close", these are done on the + // same MockTCPClientSocket since the underlying HttpNetworkClientSocket + // does a Disconnect and Connect on the same socket, rather than trying + // to obtain a new one. + // + // NOTE: Originally, the proxy response to the second CONNECT request + // simply returned another 407 so the unit test could skip the SSL connection + // establishment and SPDY framing issues. Alas, the + // retry-http-when-alternate-protocol fails logic kicks in, which was more + // complicated to set up expectations for than the SPDY session. + + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> data(ConstructSpdyBodyFrame(1, true)); + + MockWrite data_writes_2[] = { + // First connection attempt without Proxy-Authorization. + MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n" + "\r\n"), + + // Second connection attempt with Proxy-Authorization. + MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n" + "Proxy-Authorization: auth_token\r\n" + "\r\n"), + + // SPDY request + CreateMockWrite(*req), + }; + const char kRejectConnectResponse[] = ("HTTP/1.1 407 Unauthorized\r\n" + "Proxy-Authenticate: Mock\r\n" + "Proxy-Connection: close\r\n" + "\r\n"); + const char kAcceptConnectResponse[] = "HTTP/1.1 200 Connected\r\n\r\n"; + MockRead data_reads_2[] = { + // First connection attempt fails + MockRead(SYNCHRONOUS, ERR_TEST_PEER_CLOSE_AFTER_NEXT_MOCK_READ, 1), + MockRead(ASYNC, kRejectConnectResponse, + arraysize(kRejectConnectResponse) - 1, 1), + + // Second connection attempt passes + MockRead(ASYNC, kAcceptConnectResponse, + arraysize(kAcceptConnectResponse) -1, 4), + + // SPDY response + CreateMockRead(*resp.get(), 6), + CreateMockRead(*data.get(), 6), + MockRead(ASYNC, 0, 0, 6), + }; + scoped_ptr<OrderedSocketData> data_2( + new OrderedSocketData(data_reads_2, arraysize(data_reads_2), + data_writes_2, arraysize(data_writes_2))); + + SSLSocketDataProvider ssl(ASYNC, OK); + ssl.SetNextProto(SSLClientSocket::kProtoSPDY21); + + MockConnect never_finishing_connect(SYNCHRONOUS, ERR_IO_PENDING); + StaticSocketDataProvider hanging_non_alternate_protocol_socket( + NULL, 0, NULL, 0); + hanging_non_alternate_protocol_socket.set_connect_data( + never_finishing_connect); + + session_deps.socket_factory.AddSocketDataProvider(&data_1); + session_deps.socket_factory.AddSocketDataProvider(data_2.get()); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); + session_deps.socket_factory.AddSocketDataProvider( + &hanging_non_alternate_protocol_socket); + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + // First round should work and provide the Alternate-Protocol state. + TestCompletionCallback callback_1; + scoped_ptr<HttpTransaction> trans_1(new HttpNetworkTransaction(session)); + int rv = trans_1->Start(&request, callback_1.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_EQ(OK, callback_1.WaitForResult()); + + // Second round should attempt a tunnel connect and get an auth challenge. + TestCompletionCallback callback_2; + scoped_ptr<HttpTransaction> trans_2(new HttpNetworkTransaction(session)); + rv = trans_2->Start(&request, callback_2.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_EQ(OK, callback_2.WaitForResult()); + const HttpResponseInfo* response = trans_2->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_FALSE(response->auth_challenge.get() == NULL); + + // Restart with auth. Tunnel should work and response received. + TestCompletionCallback callback_3; + rv = trans_2->RestartWithAuth( + AuthCredentials(kFoo, kBar), callback_3.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_EQ(OK, callback_3.WaitForResult()); + + // After all that work, these two lines (or actually, just the scheme) are + // what this test is all about. Make sure it happens correctly. + const GURL& request_url = auth_handler->request_url(); + EXPECT_EQ("https", request_url.scheme()); + EXPECT_EQ("www.google.com", request_url.host()); + + HttpStreamFactory::SetNextProtos(std::vector<std::string>()); + HttpStreamFactory::set_use_alternate_protocols(false); +} + +// Test that if we cancel the transaction as the connection is completing, that +// everything tears down correctly. +TEST_F(HttpNetworkTransactionSpdy21Test, SimpleCancel) { + // Setup everything about the connection to complete synchronously, so that + // after calling HttpNetworkTransaction::Start, the only thing we're waiting + // for is the callback from the HttpStreamRequest. + // Then cancel the transaction. + // Verify that we don't crash. + MockConnect mock_connect(SYNCHRONOUS, OK); + MockRead data_reads[] = { + MockRead(SYNCHRONOUS, "HTTP/1.0 200 OK\r\n\r\n"), + MockRead(SYNCHRONOUS, "hello world"), + MockRead(SYNCHRONOUS, OK), + }; + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + + SessionDependencies session_deps; + session_deps.host_resolver->set_synchronous_mode(true); + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0); + data.set_connect_data(mock_connect); + session_deps.socket_factory.AddSocketDataProvider(&data); + + TestCompletionCallback callback; + + CapturingBoundNetLog log(CapturingNetLog::kUnbounded); + int rv = trans->Start(&request, callback.callback(), log.bound()); + EXPECT_EQ(ERR_IO_PENDING, rv); + trans.reset(); // Cancel the transaction here. + + MessageLoop::current()->RunAllPending(); +} + +// Test a basic GET request through a proxy. +TEST_F(HttpNetworkTransactionSpdy21Test, ProxyGet) { + SessionDependencies session_deps(ProxyService::CreateFixed("myproxy:70")); + CapturingBoundNetLog log(CapturingNetLog::kUnbounded); + session_deps.net_log = log.bound().net_log(); + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + + MockWrite data_writes1[] = { + MockWrite("GET http://www.google.com/ HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n\r\n"), + }; + + MockRead data_reads1[] = { + MockRead("HTTP/1.1 200 OK\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), + MockRead("Content-Length: 100\r\n\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + + StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), + data_writes1, arraysize(data_writes1)); + session_deps.socket_factory.AddSocketDataProvider(&data1); + + TestCompletionCallback callback1; + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + int rv = trans->Start(&request, callback1.callback(), log.bound()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + + EXPECT_TRUE(response->headers->IsKeepAlive()); + EXPECT_EQ(200, response->headers->response_code()); + EXPECT_EQ(100, response->headers->GetContentLength()); + EXPECT_TRUE(response->was_fetched_via_proxy); + EXPECT_TRUE(HttpVersion(1, 1) == response->headers->GetHttpVersion()); +} + +// Test a basic HTTPS GET request through a proxy. +TEST_F(HttpNetworkTransactionSpdy21Test, ProxyTunnelGet) { + SessionDependencies session_deps(ProxyService::CreateFixed("myproxy:70")); + CapturingBoundNetLog log(CapturingNetLog::kUnbounded); + session_deps.net_log = log.bound().net_log(); + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("https://www.google.com/"); + + // Since we have proxy, should try to establish tunnel. + MockWrite data_writes1[] = { + MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n\r\n"), + + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n\r\n"), + }; + + MockRead data_reads1[] = { + MockRead("HTTP/1.1 200 Connection Established\r\n\r\n"), + + MockRead("HTTP/1.1 200 OK\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), + MockRead("Content-Length: 100\r\n\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + + StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), + data_writes1, arraysize(data_writes1)); + session_deps.socket_factory.AddSocketDataProvider(&data1); + SSLSocketDataProvider ssl(ASYNC, OK); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); + + TestCompletionCallback callback1; + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + int rv = trans->Start(&request, callback1.callback(), log.bound()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(OK, rv); + net::CapturingNetLog::EntryList entries; + log.GetEntries(&entries); + size_t pos = ExpectLogContainsSomewhere( + entries, 0, NetLog::TYPE_HTTP_TRANSACTION_SEND_TUNNEL_HEADERS, + NetLog::PHASE_NONE); + ExpectLogContainsSomewhere( + entries, pos, + NetLog::TYPE_HTTP_TRANSACTION_READ_TUNNEL_RESPONSE_HEADERS, + NetLog::PHASE_NONE); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + + EXPECT_TRUE(response->headers->IsKeepAlive()); + EXPECT_EQ(200, response->headers->response_code()); + EXPECT_EQ(100, response->headers->GetContentLength()); + EXPECT_TRUE(HttpVersion(1, 1) == response->headers->GetHttpVersion()); + EXPECT_TRUE(response->was_fetched_via_proxy); +} + +// Test a basic HTTPS GET request through a proxy, but the server hangs up +// while establishing the tunnel. +TEST_F(HttpNetworkTransactionSpdy21Test, ProxyTunnelGetHangup) { + SessionDependencies session_deps(ProxyService::CreateFixed("myproxy:70")); + CapturingBoundNetLog log(CapturingNetLog::kUnbounded); + session_deps.net_log = log.bound().net_log(); + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("https://www.google.com/"); + + // Since we have proxy, should try to establish tunnel. + MockWrite data_writes1[] = { + MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n\r\n"), + + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n\r\n"), + }; + + MockRead data_reads1[] = { + MockRead(SYNCHRONOUS, ERR_TEST_PEER_CLOSE_AFTER_NEXT_MOCK_READ), + MockRead("HTTP/1.1 200 Connection Established\r\n\r\n"), + MockRead(ASYNC, 0, 0), // EOF + }; + + StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), + data_writes1, arraysize(data_writes1)); + session_deps.socket_factory.AddSocketDataProvider(&data1); + SSLSocketDataProvider ssl(ASYNC, OK); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); + + TestCompletionCallback callback1; + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + int rv = trans->Start(&request, callback1.callback(), log.bound()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(ERR_EMPTY_RESPONSE, rv); + net::CapturingNetLog::EntryList entries; + log.GetEntries(&entries); + size_t pos = ExpectLogContainsSomewhere( + entries, 0, NetLog::TYPE_HTTP_TRANSACTION_SEND_TUNNEL_HEADERS, + NetLog::PHASE_NONE); + ExpectLogContainsSomewhere( + entries, pos, + NetLog::TYPE_HTTP_TRANSACTION_READ_TUNNEL_RESPONSE_HEADERS, + NetLog::PHASE_NONE); +} + +// Test for crbug.com/55424. +TEST_F(HttpNetworkTransactionSpdy21Test, PreconnectWithExistingSpdySession) { + SessionDependencies session_deps; + + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet( + "https://www.google.com", false, 1, LOWEST)); + MockWrite spdy_writes[] = { CreateMockWrite(*req) }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> data(ConstructSpdyBodyFrame(1, true)); + MockRead spdy_reads[] = { + CreateMockRead(*resp), + CreateMockRead(*data), + MockRead(ASYNC, 0, 0), + }; + + scoped_ptr<DelayedSocketData> spdy_data( + new DelayedSocketData( + 1, // wait for one write to finish before reading. + spdy_reads, arraysize(spdy_reads), + spdy_writes, arraysize(spdy_writes))); + session_deps.socket_factory.AddSocketDataProvider(spdy_data.get()); + + SSLSocketDataProvider ssl(ASYNC, OK); + ssl.SetNextProto(SSLClientSocket::kProtoSPDY21); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); + + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + // Set up an initial SpdySession in the pool to reuse. + HostPortPair host_port_pair("www.google.com", 443); + HostPortProxyPair pair(host_port_pair, ProxyServer::Direct()); + scoped_refptr<SpdySession> spdy_session = + session->spdy_session_pool()->Get(pair, BoundNetLog()); + scoped_refptr<TransportSocketParams> transport_params( + new TransportSocketParams(host_port_pair, MEDIUM, false, false)); + TestCompletionCallback callback; + + scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle); + EXPECT_EQ(ERR_IO_PENDING, + connection->Init(host_port_pair.ToString(), transport_params, + LOWEST, callback.callback(), + session->GetTransportSocketPool(), BoundNetLog())); + EXPECT_EQ(OK, callback.WaitForResult()); + spdy_session->InitializeWithSocket(connection.release(), false, OK); + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("https://www.google.com/"); + request.load_flags = 0; + + // This is the important line that marks this as a preconnect. + request.motivation = HttpRequestInfo::PRECONNECT_MOTIVATED; + + scoped_ptr<HttpNetworkTransaction> trans(new HttpNetworkTransaction(session)); + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_EQ(OK, callback.WaitForResult()); +} + +// Given a net error, cause that error to be returned from the first Write() +// call and verify that the HttpTransaction fails with that error. +static void CheckErrorIsPassedBack(int error, IoMode mode) { + net::HttpRequestInfo request_info; + request_info.url = GURL("https://www.example.com/"); + request_info.method = "GET"; + request_info.load_flags = net::LOAD_NORMAL; + + SessionDependencies session_deps; + + SSLSocketDataProvider ssl_data(mode, OK); + net::MockWrite data_writes[] = { + net::MockWrite(mode, error), + }; + net::StaticSocketDataProvider data(NULL, 0, + data_writes, arraysize(data_writes)); + session_deps.socket_factory.AddSocketDataProvider(&data); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl_data); + + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + scoped_ptr<HttpNetworkTransaction> trans(new HttpNetworkTransaction(session)); + + TestCompletionCallback callback; + int rv = trans->Start(&request_info, callback.callback(), net::BoundNetLog()); + if (rv == net::ERR_IO_PENDING) + rv = callback.WaitForResult(); + ASSERT_EQ(error, rv); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, SSLWriteCertError) { + // Just check a grab bag of cert errors. + static const int kErrors[] = { + ERR_CERT_COMMON_NAME_INVALID, + ERR_CERT_AUTHORITY_INVALID, + ERR_CERT_DATE_INVALID, + }; + for (size_t i = 0; i < arraysize(kErrors); i++) { + CheckErrorIsPassedBack(kErrors[i], ASYNC); + CheckErrorIsPassedBack(kErrors[i], SYNCHRONOUS); + } +} + +// Ensure that a client certificate is removed from the SSL client auth +// cache when: +// 1) No proxy is involved. +// 2) TLS False Start is disabled. +// 3) The initial TLS handshake requests a client certificate. +// 4) The client supplies an invalid/unacceptable certificate. +TEST_F(HttpNetworkTransactionSpdy21Test, + ClientAuthCertCache_Direct_NoFalseStart) { + net::HttpRequestInfo request_info; + request_info.url = GURL("https://www.example.com/"); + request_info.method = "GET"; + request_info.load_flags = net::LOAD_NORMAL; + + SessionDependencies session_deps; + + scoped_refptr<SSLCertRequestInfo> cert_request(new SSLCertRequestInfo()); + cert_request->host_and_port = "www.example.com:443"; + + // [ssl_]data1 contains the data for the first SSL handshake. When a + // CertificateRequest is received for the first time, the handshake will + // be aborted to allow the caller to provide a certificate. + SSLSocketDataProvider ssl_data1(ASYNC, net::ERR_SSL_CLIENT_AUTH_CERT_NEEDED); + ssl_data1.cert_request_info = cert_request.get(); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl_data1); + net::StaticSocketDataProvider data1(NULL, 0, NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&data1); + + // [ssl_]data2 contains the data for the second SSL handshake. When TLS + // False Start is not being used, the result of the SSL handshake will be + // returned as part of the SSLClientSocket::Connect() call. This test + // matches the result of a server sending a handshake_failure alert, + // rather than a Finished message, because it requires a client + // certificate and none was supplied. + SSLSocketDataProvider ssl_data2(ASYNC, net::ERR_SSL_PROTOCOL_ERROR); + ssl_data2.cert_request_info = cert_request.get(); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl_data2); + net::StaticSocketDataProvider data2(NULL, 0, NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&data2); + + // [ssl_]data3 contains the data for the third SSL handshake. When a + // connection to a server fails during an SSL handshake, + // HttpNetworkTransaction will attempt to fallback to SSLv3 if the initial + // connection was attempted with TLSv1. This is transparent to the caller + // of the HttpNetworkTransaction. Because this test failure is due to + // requiring a client certificate, this fallback handshake should also + // fail. + SSLSocketDataProvider ssl_data3(ASYNC, net::ERR_SSL_PROTOCOL_ERROR); + ssl_data3.cert_request_info = cert_request.get(); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl_data3); + net::StaticSocketDataProvider data3(NULL, 0, NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&data3); + + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + scoped_ptr<HttpNetworkTransaction> trans(new HttpNetworkTransaction(session)); + + // Begin the SSL handshake with the peer. This consumes ssl_data1. + TestCompletionCallback callback; + int rv = trans->Start(&request_info, callback.callback(), net::BoundNetLog()); + ASSERT_EQ(net::ERR_IO_PENDING, rv); + + // Complete the SSL handshake, which should abort due to requiring a + // client certificate. + rv = callback.WaitForResult(); + ASSERT_EQ(net::ERR_SSL_CLIENT_AUTH_CERT_NEEDED, rv); + + // Indicate that no certificate should be supplied. From the perspective + // of SSLClientCertCache, NULL is just as meaningful as a real + // certificate, so this is the same as supply a + // legitimate-but-unacceptable certificate. + rv = trans->RestartWithCertificate(NULL, callback.callback()); + ASSERT_EQ(net::ERR_IO_PENDING, rv); + + // Ensure the certificate was added to the client auth cache before + // allowing the connection to continue restarting. + scoped_refptr<X509Certificate> client_cert; + ASSERT_TRUE(session->ssl_client_auth_cache()->Lookup("www.example.com:443", + &client_cert)); + ASSERT_EQ(NULL, client_cert.get()); + + // Restart the handshake. This will consume ssl_data2, which fails, and + // then consume ssl_data3, which should also fail. The result code is + // checked against what ssl_data3 should return. + rv = callback.WaitForResult(); + ASSERT_EQ(net::ERR_SSL_PROTOCOL_ERROR, rv); + + // Ensure that the client certificate is removed from the cache on a + // handshake failure. + ASSERT_FALSE(session->ssl_client_auth_cache()->Lookup("www.example.com:443", + &client_cert)); +} + +// Ensure that a client certificate is removed from the SSL client auth +// cache when: +// 1) No proxy is involved. +// 2) TLS False Start is enabled. +// 3) The initial TLS handshake requests a client certificate. +// 4) The client supplies an invalid/unacceptable certificate. +TEST_F(HttpNetworkTransactionSpdy21Test, + ClientAuthCertCache_Direct_FalseStart) { + net::HttpRequestInfo request_info; + request_info.url = GURL("https://www.example.com/"); + request_info.method = "GET"; + request_info.load_flags = net::LOAD_NORMAL; + + SessionDependencies session_deps; + + scoped_refptr<SSLCertRequestInfo> cert_request(new SSLCertRequestInfo()); + cert_request->host_and_port = "www.example.com:443"; + + // When TLS False Start is used, SSLClientSocket::Connect() calls will + // return successfully after reading up to the peer's Certificate message. + // This is to allow the caller to call SSLClientSocket::Write(), which can + // enqueue application data to be sent in the same packet as the + // ChangeCipherSpec and Finished messages. + // The actual handshake will be finished when SSLClientSocket::Read() is + // called, which expects to process the peer's ChangeCipherSpec and + // Finished messages. If there was an error negotiating with the peer, + // such as due to the peer requiring a client certificate when none was + // supplied, the alert sent by the peer won't be processed until Read() is + // called. + + // Like the non-False Start case, when a client certificate is requested by + // the peer, the handshake is aborted during the Connect() call. + // [ssl_]data1 represents the initial SSL handshake with the peer. + SSLSocketDataProvider ssl_data1(ASYNC, net::ERR_SSL_CLIENT_AUTH_CERT_NEEDED); + ssl_data1.cert_request_info = cert_request.get(); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl_data1); + net::StaticSocketDataProvider data1(NULL, 0, NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&data1); + + // When a client certificate is supplied, Connect() will not be aborted + // when the peer requests the certificate. Instead, the handshake will + // artificially succeed, allowing the caller to write the HTTP request to + // the socket. The handshake messages are not processed until Read() is + // called, which then detects that the handshake was aborted, due to the + // peer sending a handshake_failure because it requires a client + // certificate. + SSLSocketDataProvider ssl_data2(ASYNC, net::OK); + ssl_data2.cert_request_info = cert_request.get(); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl_data2); + net::MockRead data2_reads[] = { + net::MockRead(ASYNC /* async */, net::ERR_SSL_PROTOCOL_ERROR), + }; + net::StaticSocketDataProvider data2( + data2_reads, arraysize(data2_reads), NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&data2); + + // As described in ClientAuthCertCache_Direct_NoFalseStart, [ssl_]data3 is + // the data for the SSL handshake once the TLSv1 connection falls back to + // SSLv3. It has the same behaviour as [ssl_]data2. + SSLSocketDataProvider ssl_data3(ASYNC, net::OK); + ssl_data3.cert_request_info = cert_request.get(); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl_data3); + net::StaticSocketDataProvider data3( + data2_reads, arraysize(data2_reads), NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&data3); + + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + scoped_ptr<HttpNetworkTransaction> trans(new HttpNetworkTransaction(session)); + + // Begin the initial SSL handshake. + TestCompletionCallback callback; + int rv = trans->Start(&request_info, callback.callback(), net::BoundNetLog()); + ASSERT_EQ(net::ERR_IO_PENDING, rv); + + // Complete the SSL handshake, which should abort due to requiring a + // client certificate. + rv = callback.WaitForResult(); + ASSERT_EQ(net::ERR_SSL_CLIENT_AUTH_CERT_NEEDED, rv); + + // Indicate that no certificate should be supplied. From the perspective + // of SSLClientCertCache, NULL is just as meaningful as a real + // certificate, so this is the same as supply a + // legitimate-but-unacceptable certificate. + rv = trans->RestartWithCertificate(NULL, callback.callback()); + ASSERT_EQ(net::ERR_IO_PENDING, rv); + + // Ensure the certificate was added to the client auth cache before + // allowing the connection to continue restarting. + scoped_refptr<X509Certificate> client_cert; + ASSERT_TRUE(session->ssl_client_auth_cache()->Lookup("www.example.com:443", + &client_cert)); + ASSERT_EQ(NULL, client_cert.get()); + + + // Restart the handshake. This will consume ssl_data2, which fails, and + // then consume ssl_data3, which should also fail. The result code is + // checked against what ssl_data3 should return. + rv = callback.WaitForResult(); + ASSERT_EQ(net::ERR_SSL_PROTOCOL_ERROR, rv); + + // Ensure that the client certificate is removed from the cache on a + // handshake failure. + ASSERT_FALSE(session->ssl_client_auth_cache()->Lookup("www.example.com:443", + &client_cert)); +} + +// Ensure that a client certificate is removed from the SSL client auth +// cache when: +// 1) An HTTPS proxy is involved. +// 3) The HTTPS proxy requests a client certificate. +// 4) The client supplies an invalid/unacceptable certificate for the +// proxy. +// The test is repeated twice, first for connecting to an HTTPS endpoint, +// then for connecting to an HTTP endpoint. +TEST_F(HttpNetworkTransactionSpdy21Test, ClientAuthCertCache_Proxy_Fail) { + SessionDependencies session_deps( + ProxyService::CreateFixed("https://proxy:70")); + CapturingBoundNetLog log(CapturingNetLog::kUnbounded); + session_deps.net_log = log.bound().net_log(); + + scoped_refptr<SSLCertRequestInfo> cert_request(new SSLCertRequestInfo()); + cert_request->host_and_port = "proxy:70"; + + // See ClientAuthCertCache_Direct_NoFalseStart for the explanation of + // [ssl_]data[1-3]. Rather than represending the endpoint + // (www.example.com:443), they represent failures with the HTTPS proxy + // (proxy:70). + SSLSocketDataProvider ssl_data1(ASYNC, net::ERR_SSL_CLIENT_AUTH_CERT_NEEDED); + ssl_data1.cert_request_info = cert_request.get(); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl_data1); + net::StaticSocketDataProvider data1(NULL, 0, NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&data1); + + SSLSocketDataProvider ssl_data2(ASYNC, net::ERR_SSL_PROTOCOL_ERROR); + ssl_data2.cert_request_info = cert_request.get(); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl_data2); + net::StaticSocketDataProvider data2(NULL, 0, NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&data2); + + SSLSocketDataProvider ssl_data3(ASYNC, net::ERR_SSL_PROTOCOL_ERROR); + ssl_data3.cert_request_info = cert_request.get(); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl_data3); + net::StaticSocketDataProvider data3(NULL, 0, NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&data3); + + net::HttpRequestInfo requests[2]; + requests[0].url = GURL("https://www.example.com/"); + requests[0].method = "GET"; + requests[0].load_flags = net::LOAD_NORMAL; + + requests[1].url = GURL("http://www.example.com/"); + requests[1].method = "GET"; + requests[1].load_flags = net::LOAD_NORMAL; + + for (size_t i = 0; i < arraysize(requests); ++i) { + session_deps.socket_factory.ResetNextMockIndexes(); + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + scoped_ptr<HttpNetworkTransaction> trans( + new HttpNetworkTransaction(session)); + + // Begin the SSL handshake with the proxy. + TestCompletionCallback callback; + int rv = trans->Start( + &requests[i], callback.callback(), net::BoundNetLog()); + ASSERT_EQ(net::ERR_IO_PENDING, rv); + + // Complete the SSL handshake, which should abort due to requiring a + // client certificate. + rv = callback.WaitForResult(); + ASSERT_EQ(net::ERR_SSL_CLIENT_AUTH_CERT_NEEDED, rv); + + // Indicate that no certificate should be supplied. From the perspective + // of SSLClientCertCache, NULL is just as meaningful as a real + // certificate, so this is the same as supply a + // legitimate-but-unacceptable certificate. + rv = trans->RestartWithCertificate(NULL, callback.callback()); + ASSERT_EQ(net::ERR_IO_PENDING, rv); + + // Ensure the certificate was added to the client auth cache before + // allowing the connection to continue restarting. + scoped_refptr<X509Certificate> client_cert; + ASSERT_TRUE(session->ssl_client_auth_cache()->Lookup("proxy:70", + &client_cert)); + ASSERT_EQ(NULL, client_cert.get()); + // Ensure the certificate was NOT cached for the endpoint. This only + // applies to HTTPS requests, but is fine to check for HTTP requests. + ASSERT_FALSE(session->ssl_client_auth_cache()->Lookup("www.example.com:443", + &client_cert)); + + // Restart the handshake. This will consume ssl_data2, which fails, and + // then consume ssl_data3, which should also fail. The result code is + // checked against what ssl_data3 should return. + rv = callback.WaitForResult(); + ASSERT_EQ(net::ERR_PROXY_CONNECTION_FAILED, rv); + + // Now that the new handshake has failed, ensure that the client + // certificate was removed from the client auth cache. + ASSERT_FALSE(session->ssl_client_auth_cache()->Lookup("proxy:70", + &client_cert)); + ASSERT_FALSE(session->ssl_client_auth_cache()->Lookup("www.example.com:443", + &client_cert)); + } +} + +namespace { + +void IPPoolingAddAlias(MockCachingHostResolver* host_resolver, + SpdySessionPoolPeer* pool_peer, + std::string host, + int port, + std::string iplist) { + // Create a host resolver dependency that returns address |iplist| for + // resolutions of |host|. + host_resolver->rules()->AddIPLiteralRule(host, iplist, ""); + + // Setup a HostPortProxyPair. + HostPortPair host_port_pair(host, port); + HostPortProxyPair pair = HostPortProxyPair(host_port_pair, + ProxyServer::Direct()); + + // Resolve the host and port. + AddressList addresses; + HostResolver::RequestInfo info(host_port_pair); + TestCompletionCallback callback; + int rv = host_resolver->Resolve(info, &addresses, callback.callback(), NULL, + BoundNetLog()); + if (rv == ERR_IO_PENDING) + rv = callback.WaitForResult(); + DCHECK_EQ(OK, rv); + + // Add the first address as an alias. It would have been better to call + // MockClientSocket::GetPeerAddress but that returns 192.0.2.33 whereas + // MockHostResolver returns 127.0.0.1 (MockHostResolverBase::Reset). So we use + // the first address (127.0.0.1) returned by MockHostResolver as an alias for + // the |pair|. + const addrinfo* address = addresses.head(); + pool_peer->AddAlias(address, pair); +} + +} // namespace + +TEST_F(HttpNetworkTransactionSpdy21Test, UseIPConnectionPooling) { + HttpStreamFactory::set_use_alternate_protocols(true); + HttpStreamFactory::SetNextProtos(SpdyNextProtos()); + + // Set up a special HttpNetworkSession with a MockCachingHostResolver. + SessionDependencies session_deps; + MockCachingHostResolver host_resolver; + net::HttpNetworkSession::Params params; + params.client_socket_factory = &session_deps.socket_factory; + params.host_resolver = &host_resolver; + params.cert_verifier = session_deps.cert_verifier.get(); + params.proxy_service = session_deps.proxy_service.get(); + params.ssl_config_service = session_deps.ssl_config_service; + params.http_auth_handler_factory = + session_deps.http_auth_handler_factory.get(); + params.http_server_properties = &session_deps.http_server_properties; + params.net_log = session_deps.net_log; + scoped_refptr<HttpNetworkSession> session(new HttpNetworkSession(params)); + SpdySessionPoolPeer pool_peer(session->spdy_session_pool()); + pool_peer.DisableDomainAuthenticationVerification(); + + SSLSocketDataProvider ssl(ASYNC, OK); + ssl.SetNextProto(SSLClientSocket::kProtoSPDY21); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); + + scoped_ptr<spdy::SpdyFrame> host1_req(ConstructSpdyGet( + "https://www.google.com", false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> host2_req(ConstructSpdyGet( + "https://www.gmail.com", false, 3, LOWEST)); + MockWrite spdy_writes[] = { + CreateMockWrite(*host1_req, 1), + CreateMockWrite(*host2_req, 4), + }; + scoped_ptr<spdy::SpdyFrame> host1_resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> host1_resp_body(ConstructSpdyBodyFrame(1, true)); + scoped_ptr<spdy::SpdyFrame> host2_resp(ConstructSpdyGetSynReply(NULL, 0, 3)); + scoped_ptr<spdy::SpdyFrame> host2_resp_body(ConstructSpdyBodyFrame(3, true)); + MockRead spdy_reads[] = { + CreateMockRead(*host1_resp, 2), + CreateMockRead(*host1_resp_body, 3), + CreateMockRead(*host2_resp, 5), + CreateMockRead(*host2_resp_body, 6), + MockRead(ASYNC, 0, 7), + }; + + scoped_ptr<OrderedSocketData> spdy_data( + new OrderedSocketData( + spdy_reads, arraysize(spdy_reads), + spdy_writes, arraysize(spdy_writes))); + session_deps.socket_factory.AddSocketDataProvider(spdy_data.get()); + + TestCompletionCallback callback; + HttpRequestInfo request1; + request1.method = "GET"; + request1.url = GURL("https://www.google.com/"); + request1.load_flags = 0; + HttpNetworkTransaction trans1(session); + + int rv = trans1.Start(&request1, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_EQ(OK, callback.WaitForResult()); + + const HttpResponseInfo* response = trans1.GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + + std::string response_data; + ASSERT_EQ(OK, ReadTransaction(&trans1, &response_data)); + EXPECT_EQ("hello!", response_data); + + // Preload www.gmail.com into HostCache. + HostPortPair host_port("www.gmail.com", 443); + HostResolver::RequestInfo resolve_info(host_port); + AddressList ignored; + rv = host_resolver.Resolve(resolve_info, &ignored, callback.callback(), NULL, + BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + // MockHostResolver returns 127.0.0.1, port 443 for https://www.google.com/ + // and https://www.gmail.com/. Add 127.0.0.1 as alias for host_port_pair: + // (www.google.com, 443). + IPPoolingAddAlias(&host_resolver, &pool_peer, "www.google.com", 443, + "127.0.0.1"); + + HttpRequestInfo request2; + request2.method = "GET"; + request2.url = GURL("https://www.gmail.com/"); + request2.load_flags = 0; + HttpNetworkTransaction trans2(session); + + rv = trans2.Start(&request2, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_EQ(OK, callback.WaitForResult()); + + response = trans2.GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + EXPECT_TRUE(response->was_fetched_via_spdy); + EXPECT_TRUE(response->was_npn_negotiated); + ASSERT_EQ(OK, ReadTransaction(&trans2, &response_data)); + EXPECT_EQ("hello!", response_data); + + HttpStreamFactory::SetNextProtos(std::vector<std::string>()); + HttpStreamFactory::set_use_alternate_protocols(false); +} + +class OneTimeCachingHostResolver : public net::HostResolver { + public: + explicit OneTimeCachingHostResolver(const HostPortPair& host_port) + : host_port_(host_port) {} + virtual ~OneTimeCachingHostResolver() {} + + RuleBasedHostResolverProc* rules() { return host_resolver_.rules(); } + + // HostResolver methods: + virtual int Resolve(const RequestInfo& info, + AddressList* addresses, + const CompletionCallback& callback, + RequestHandle* out_req, + const BoundNetLog& net_log) OVERRIDE { + return host_resolver_.Resolve( + info, addresses, callback, out_req, net_log); + } + + virtual int ResolveFromCache(const RequestInfo& info, + AddressList* addresses, + const BoundNetLog& net_log) OVERRIDE { + int rv = host_resolver_.ResolveFromCache(info, addresses, net_log); + if (rv == OK && info.host_port_pair().Equals(host_port_)) + host_resolver_.GetHostCache()->clear(); + return rv; + } + + virtual void CancelRequest(RequestHandle req) OVERRIDE { + host_resolver_.CancelRequest(req); + } + + MockCachingHostResolver* GetMockHostResolver() { + return &host_resolver_; + } + + private: + MockCachingHostResolver host_resolver_; + const HostPortPair host_port_; +}; + +TEST_F(HttpNetworkTransactionSpdy21Test, + UseIPConnectionPoolingWithHostCacheExpiration) { + HttpStreamFactory::set_use_alternate_protocols(true); + HttpStreamFactory::SetNextProtos(SpdyNextProtos()); + + // Set up a special HttpNetworkSession with a OneTimeCachingHostResolver. + SessionDependencies session_deps; + OneTimeCachingHostResolver host_resolver(HostPortPair("www.gmail.com", 443)); + net::HttpNetworkSession::Params params; + params.client_socket_factory = &session_deps.socket_factory; + params.host_resolver = &host_resolver; + params.cert_verifier = session_deps.cert_verifier.get(); + params.proxy_service = session_deps.proxy_service.get(); + params.ssl_config_service = session_deps.ssl_config_service; + params.http_auth_handler_factory = + session_deps.http_auth_handler_factory.get(); + params.http_server_properties = &session_deps.http_server_properties; + params.net_log = session_deps.net_log; + scoped_refptr<HttpNetworkSession> session(new HttpNetworkSession(params)); + SpdySessionPoolPeer pool_peer(session->spdy_session_pool()); + pool_peer.DisableDomainAuthenticationVerification(); + + SSLSocketDataProvider ssl(ASYNC, OK); + ssl.SetNextProto(SSLClientSocket::kProtoSPDY21); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); + + scoped_ptr<spdy::SpdyFrame> host1_req(ConstructSpdyGet( + "https://www.google.com", false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> host2_req(ConstructSpdyGet( + "https://www.gmail.com", false, 3, LOWEST)); + MockWrite spdy_writes[] = { + CreateMockWrite(*host1_req, 1), + CreateMockWrite(*host2_req, 4), + }; + scoped_ptr<spdy::SpdyFrame> host1_resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> host1_resp_body(ConstructSpdyBodyFrame(1, true)); + scoped_ptr<spdy::SpdyFrame> host2_resp(ConstructSpdyGetSynReply(NULL, 0, 3)); + scoped_ptr<spdy::SpdyFrame> host2_resp_body(ConstructSpdyBodyFrame(3, true)); + MockRead spdy_reads[] = { + CreateMockRead(*host1_resp, 2), + CreateMockRead(*host1_resp_body, 3), + CreateMockRead(*host2_resp, 5), + CreateMockRead(*host2_resp_body, 6), + MockRead(ASYNC, 0, 7), + }; + + scoped_ptr<OrderedSocketData> spdy_data( + new OrderedSocketData( + spdy_reads, arraysize(spdy_reads), + spdy_writes, arraysize(spdy_writes))); + session_deps.socket_factory.AddSocketDataProvider(spdy_data.get()); + + TestCompletionCallback callback; + HttpRequestInfo request1; + request1.method = "GET"; + request1.url = GURL("https://www.google.com/"); + request1.load_flags = 0; + HttpNetworkTransaction trans1(session); + + int rv = trans1.Start(&request1, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_EQ(OK, callback.WaitForResult()); + + const HttpResponseInfo* response = trans1.GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + + std::string response_data; + ASSERT_EQ(OK, ReadTransaction(&trans1, &response_data)); + EXPECT_EQ("hello!", response_data); + + // Preload cache entries into HostCache. + HostResolver::RequestInfo resolve_info(HostPortPair("www.gmail.com", 443)); + AddressList ignored; + rv = host_resolver.Resolve(resolve_info, &ignored, callback.callback(), NULL, + BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + HttpRequestInfo request2; + request2.method = "GET"; + request2.url = GURL("https://www.gmail.com/"); + request2.load_flags = 0; + HttpNetworkTransaction trans2(session); + + // MockHostResolver returns 127.0.0.1, port 443 for https://www.google.com/ + // and https://www.gmail.com/. Add 127.0.0.1 as alias for host_port_pair: + // (www.google.com, 443). + IPPoolingAddAlias(host_resolver.GetMockHostResolver(), &pool_peer, + "www.google.com", 443, "127.0.0.1"); + + rv = trans2.Start(&request2, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_EQ(OK, callback.WaitForResult()); + + response = trans2.GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + EXPECT_TRUE(response->was_fetched_via_spdy); + EXPECT_TRUE(response->was_npn_negotiated); + ASSERT_EQ(OK, ReadTransaction(&trans2, &response_data)); + EXPECT_EQ("hello!", response_data); + + HttpStreamFactory::SetNextProtos(std::vector<std::string>()); + HttpStreamFactory::set_use_alternate_protocols(false); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, ReadPipelineEvictionFallback) { + MockRead data_reads1[] = { + MockRead(SYNCHRONOUS, ERR_PIPELINE_EVICTION), + }; + MockRead data_reads2[] = { + MockRead("HTTP/1.0 200 OK\r\n\r\n"), + MockRead("hello world"), + MockRead(SYNCHRONOUS, OK), + }; + StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), NULL, 0); + StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2), NULL, 0); + StaticSocketDataProvider* data[] = { &data1, &data2 }; + + SimpleGetHelperResult out = SimpleGetHelperForData(data, arraysize(data)); + + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.0 200 OK", out.status_line); + EXPECT_EQ("hello world", out.response_data); +} + +TEST_F(HttpNetworkTransactionSpdy21Test, SendPipelineEvictionFallback) { + MockWrite data_writes1[] = { + MockWrite(SYNCHRONOUS, ERR_PIPELINE_EVICTION), + }; + MockWrite data_writes2[] = { + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n\r\n"), + }; + MockRead data_reads2[] = { + MockRead("HTTP/1.0 200 OK\r\n\r\n"), + MockRead("hello world"), + MockRead(SYNCHRONOUS, OK), + }; + StaticSocketDataProvider data1(NULL, 0, + data_writes1, arraysize(data_writes1)); + StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2), + data_writes2, arraysize(data_writes2)); + StaticSocketDataProvider* data[] = { &data1, &data2 }; + + SimpleGetHelperResult out = SimpleGetHelperForData(data, arraysize(data)); + + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.0 200 OK", out.status_line); + EXPECT_EQ("hello world", out.response_data); +} + +} // namespace net diff --git a/net/http/http_network_transaction_spdy2_unittest.cc b/net/http/http_network_transaction_spdy2_unittest.cc new file mode 100644 index 0000000..68ae655 --- /dev/null +++ b/net/http/http_network_transaction_spdy2_unittest.cc @@ -0,0 +1,9375 @@ +// Copyright (c) 2012 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/http/http_network_transaction.h" + +#include <math.h> // ceil +#include <stdarg.h> +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/memory/scoped_ptr.h" +#include "base/metrics/histogram.h" +#include "base/test/test_file_util.h" +#include "base/utf_string_conversions.h" +#include "net/base/auth.h" +#include "net/base/capturing_net_log.h" +#include "net/base/completion_callback.h" +#include "net/base/host_cache.h" +#include "net/base/mock_host_resolver.h" +#include "net/base/net_log.h" +#include "net/base/net_log_unittest.h" +#include "net/base/request_priority.h" +#include "net/base/ssl_cert_request_info.h" +#include "net/base/ssl_config_service_defaults.h" +#include "net/base/ssl_info.h" +#include "net/base/test_completion_callback.h" +#include "net/base/upload_data.h" +#include "net/http/http_auth_handler_digest.h" +#include "net/http/http_auth_handler_mock.h" +#include "net/http/http_auth_handler_ntlm.h" +#include "net/http/http_basic_stream.h" +#include "net/http/http_net_log_params.h" +#include "net/http/http_network_session.h" +#include "net/http/http_network_session_peer.h" +#include "net/http/http_server_properties_impl.h" +#include "net/http/http_stream.h" +#include "net/http/http_stream_factory.h" +#include "net/http/http_transaction_unittest.h" +#include "net/proxy/proxy_config_service_fixed.h" +#include "net/proxy/proxy_resolver.h" +#include "net/proxy/proxy_service.h" +#include "net/socket/client_socket_factory.h" +#include "net/socket/mock_client_socket_pool_manager.h" +#include "net/socket/socket_test_util.h" +#include "net/socket/ssl_client_socket.h" +#include "net/spdy/spdy_framer.h" +#include "net/spdy/spdy_session.h" +#include "net/spdy/spdy_session_pool.h" +#include "net/spdy/spdy_test_util_spdy2.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/platform_test.h" + +using namespace net::test_spdy2; + +//----------------------------------------------------------------------------- + +namespace { + +const string16 kBar(ASCIIToUTF16("bar")); +const string16 kBar2(ASCIIToUTF16("bar2")); +const string16 kBar3(ASCIIToUTF16("bar3")); +const string16 kBaz(ASCIIToUTF16("baz")); +const string16 kFirst(ASCIIToUTF16("first")); +const string16 kFoo(ASCIIToUTF16("foo")); +const string16 kFoo2(ASCIIToUTF16("foo2")); +const string16 kFoo3(ASCIIToUTF16("foo3")); +const string16 kFou(ASCIIToUTF16("fou")); +const string16 kSecond(ASCIIToUTF16("second")); +const string16 kTestingNTLM(ASCIIToUTF16("testing-ntlm")); +const string16 kWrongPassword(ASCIIToUTF16("wrongpassword")); + +// MakeNextProtos is a utility function that returns a vector of std::strings +// from its arguments. Don't forget to terminate the argument list with a NULL. +std::vector<std::string> MakeNextProtos(const char* a, ...) { + std::vector<std::string> ret; + ret.push_back(a); + + va_list args; + va_start(args, a); + + for (;;) { + const char* value = va_arg(args, const char*); + if (value == NULL) + break; + ret.push_back(value); + } + va_end(args); + + return ret; +} + +// SpdyNextProtos returns a vector of NPN protocol strings for negotiating +// SPDY. +std::vector<std::string> SpdyNextProtos() { + return MakeNextProtos("http/1.1", "spdy/2", NULL); +} + +} // namespace + +namespace net { + +namespace { + +// Helper to manage the lifetimes of the dependencies for a +// HttpNetworkTransaction. +struct SessionDependencies { + // Default set of dependencies -- "null" proxy service. + SessionDependencies() + : host_resolver(new MockHostResolver), + cert_verifier(new CertVerifier), + proxy_service(ProxyService::CreateDirect()), + ssl_config_service(new SSLConfigServiceDefaults), + http_auth_handler_factory( + HttpAuthHandlerFactory::CreateDefault(host_resolver.get())), + net_log(NULL) {} + + // Custom proxy service dependency. + explicit SessionDependencies(ProxyService* proxy_service) + : host_resolver(new MockHostResolver), + cert_verifier(new CertVerifier), + proxy_service(proxy_service), + ssl_config_service(new SSLConfigServiceDefaults), + http_auth_handler_factory( + HttpAuthHandlerFactory::CreateDefault(host_resolver.get())), + net_log(NULL) {} + + scoped_ptr<MockHostResolverBase> host_resolver; + scoped_ptr<CertVerifier> cert_verifier; + scoped_ptr<ProxyService> proxy_service; + scoped_refptr<SSLConfigService> ssl_config_service; + MockClientSocketFactory socket_factory; + scoped_ptr<HttpAuthHandlerFactory> http_auth_handler_factory; + HttpServerPropertiesImpl http_server_properties; + NetLog* net_log; +}; + +HttpNetworkSession* CreateSession(SessionDependencies* session_deps) { + net::HttpNetworkSession::Params params; + params.client_socket_factory = &session_deps->socket_factory; + params.host_resolver = session_deps->host_resolver.get(); + params.cert_verifier = session_deps->cert_verifier.get(); + params.proxy_service = session_deps->proxy_service.get(); + params.ssl_config_service = session_deps->ssl_config_service; + params.http_auth_handler_factory = + session_deps->http_auth_handler_factory.get(); + params.http_server_properties = &session_deps->http_server_properties; + params.net_log = session_deps->net_log; + return new HttpNetworkSession(params); +} + +} // namespace + +class HttpNetworkTransactionSpdy2Test : public PlatformTest { + protected: + struct SimpleGetHelperResult { + int rv; + std::string status_line; + std::string response_data; + }; + + virtual void SetUp() { + NetworkChangeNotifier::NotifyObserversOfIPAddressChangeForTests(); + MessageLoop::current()->RunAllPending(); + spdy::SpdyFramer::set_enable_compression_default(false); + } + + virtual void TearDown() { + NetworkChangeNotifier::NotifyObserversOfIPAddressChangeForTests(); + MessageLoop::current()->RunAllPending(); + spdy::SpdyFramer::set_enable_compression_default(true); + // Empty the current queue. + MessageLoop::current()->RunAllPending(); + PlatformTest::TearDown(); + NetworkChangeNotifier::NotifyObserversOfIPAddressChangeForTests(); + MessageLoop::current()->RunAllPending(); + } + + // Either |write_failure| specifies a write failure or |read_failure| + // specifies a read failure when using a reused socket. In either case, the + // failure should cause the network transaction to resend the request, and the + // other argument should be NULL. + void KeepAliveConnectionResendRequestTest(const MockWrite* write_failure, + const MockRead* read_failure); + + SimpleGetHelperResult SimpleGetHelperForData(StaticSocketDataProvider* data[], + size_t data_count) { + SimpleGetHelperResult out; + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + + SessionDependencies session_deps; + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + for (size_t i = 0; i < data_count; ++i) { + session_deps.socket_factory.AddSocketDataProvider(data[i]); + } + + TestCompletionCallback callback; + + CapturingBoundNetLog log(CapturingNetLog::kUnbounded); + EXPECT_TRUE(log.bound().IsLoggingAllEvents()); + int rv = trans->Start(&request, callback.callback(), log.bound()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + out.rv = callback.WaitForResult(); + if (out.rv != OK) + return out; + + const HttpResponseInfo* response = trans->GetResponseInfo(); + // Can't use ASSERT_* inside helper functions like this, so + // return an error. + if (response == NULL || response->headers == NULL) { + out.rv = ERR_UNEXPECTED; + return out; + } + out.status_line = response->headers->GetStatusLine(); + + EXPECT_EQ("192.0.2.33", response->socket_address.host()); + EXPECT_EQ(0, response->socket_address.port()); + + rv = ReadTransaction(trans.get(), &out.response_data); + EXPECT_EQ(OK, rv); + + net::CapturingNetLog::EntryList entries; + log.GetEntries(&entries); + size_t pos = ExpectLogContainsSomewhere( + entries, 0, NetLog::TYPE_HTTP_TRANSACTION_SEND_REQUEST_HEADERS, + NetLog::PHASE_NONE); + ExpectLogContainsSomewhere( + entries, pos, + NetLog::TYPE_HTTP_TRANSACTION_READ_RESPONSE_HEADERS, + NetLog::PHASE_NONE); + + CapturingNetLog::Entry entry = entries[pos]; + NetLogHttpRequestParameter* request_params = + static_cast<NetLogHttpRequestParameter*>(entry.extra_parameters.get()); + EXPECT_EQ("GET / HTTP/1.1\r\n", request_params->GetLine()); + EXPECT_EQ("Host: www.google.com\r\n" + "Connection: keep-alive\r\n\r\n", + request_params->GetHeaders().ToString()); + + return out; + } + + SimpleGetHelperResult SimpleGetHelper(MockRead data_reads[], + size_t reads_count) { + StaticSocketDataProvider reads(data_reads, reads_count, NULL, 0); + StaticSocketDataProvider* data[] = { &reads }; + return SimpleGetHelperForData(data, 1); + } + + void ConnectStatusHelperWithExpectedStatus(const MockRead& status, + int expected_status); + + void ConnectStatusHelper(const MockRead& status); +}; + +namespace { + +// Fill |str| with a long header list that consumes >= |size| bytes. +void FillLargeHeadersString(std::string* str, int size) { + const char* row = + "SomeHeaderName: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\r\n"; + const int sizeof_row = strlen(row); + const int num_rows = static_cast<int>( + ceil(static_cast<float>(size) / sizeof_row)); + const int sizeof_data = num_rows * sizeof_row; + DCHECK(sizeof_data >= size); + str->reserve(sizeof_data); + + for (int i = 0; i < num_rows; ++i) + str->append(row, sizeof_row); +} + +// Alternative functions that eliminate randomness and dependency on the local +// host name so that the generated NTLM messages are reproducible. +void MockGenerateRandom1(uint8* output, size_t n) { + static const uint8 bytes[] = { + 0x55, 0x29, 0x66, 0x26, 0x6b, 0x9c, 0x73, 0x54 + }; + static size_t current_byte = 0; + for (size_t i = 0; i < n; ++i) { + output[i] = bytes[current_byte++]; + current_byte %= arraysize(bytes); + } +} + +void MockGenerateRandom2(uint8* output, size_t n) { + static const uint8 bytes[] = { + 0x96, 0x79, 0x85, 0xe7, 0x49, 0x93, 0x70, 0xa1, + 0x4e, 0xe7, 0x87, 0x45, 0x31, 0x5b, 0xd3, 0x1f + }; + static size_t current_byte = 0; + for (size_t i = 0; i < n; ++i) { + output[i] = bytes[current_byte++]; + current_byte %= arraysize(bytes); + } +} + +std::string MockGetHostName() { + return "WTC-WIN7"; +} + +template<typename ParentPool> +class CaptureGroupNameSocketPool : public ParentPool { + public: + CaptureGroupNameSocketPool(HostResolver* host_resolver, + CertVerifier* cert_verifier); + + const std::string last_group_name_received() const { + return last_group_name_; + } + + virtual int RequestSocket(const std::string& group_name, + const void* socket_params, + RequestPriority priority, + ClientSocketHandle* handle, + const CompletionCallback& callback, + const BoundNetLog& net_log) { + last_group_name_ = group_name; + return ERR_IO_PENDING; + } + virtual void CancelRequest(const std::string& group_name, + ClientSocketHandle* handle) {} + virtual void ReleaseSocket(const std::string& group_name, + StreamSocket* socket, + int id) {} + virtual void CloseIdleSockets() {} + virtual int IdleSocketCount() const { + return 0; + } + virtual int IdleSocketCountInGroup(const std::string& group_name) const { + return 0; + } + virtual LoadState GetLoadState(const std::string& group_name, + const ClientSocketHandle* handle) const { + return LOAD_STATE_IDLE; + } + virtual base::TimeDelta ConnectionTimeout() const { + return base::TimeDelta(); + } + + private: + std::string last_group_name_; +}; + +typedef CaptureGroupNameSocketPool<TransportClientSocketPool> +CaptureGroupNameTransportSocketPool; +typedef CaptureGroupNameSocketPool<HttpProxyClientSocketPool> +CaptureGroupNameHttpProxySocketPool; +typedef CaptureGroupNameSocketPool<SOCKSClientSocketPool> +CaptureGroupNameSOCKSSocketPool; +typedef CaptureGroupNameSocketPool<SSLClientSocketPool> +CaptureGroupNameSSLSocketPool; + +template<typename ParentPool> +CaptureGroupNameSocketPool<ParentPool>::CaptureGroupNameSocketPool( + HostResolver* host_resolver, + CertVerifier* /* cert_verifier */) + : ParentPool(0, 0, NULL, host_resolver, NULL, NULL) {} + +template<> +CaptureGroupNameHttpProxySocketPool::CaptureGroupNameSocketPool( + HostResolver* host_resolver, + CertVerifier* /* cert_verifier */) + : HttpProxyClientSocketPool(0, 0, NULL, host_resolver, NULL, NULL, NULL) {} + +template<> +CaptureGroupNameSSLSocketPool::CaptureGroupNameSocketPool( + HostResolver* host_resolver, + CertVerifier* cert_verifier) + : SSLClientSocketPool(0, 0, NULL, host_resolver, cert_verifier, NULL, + NULL, NULL, "", NULL, NULL, NULL, NULL, NULL, NULL) {} + +//----------------------------------------------------------------------------- + +// This is the expected return from a current server advertising SPDY. +static const char kAlternateProtocolHttpHeader[] = + "Alternate-Protocol: 443:npn-spdy/2\r\n\r\n"; + +// Helper functions for validating that AuthChallengeInfo's are correctly +// configured for common cases. +bool CheckBasicServerAuth(const AuthChallengeInfo* auth_challenge) { + if (!auth_challenge) + return false; + EXPECT_FALSE(auth_challenge->is_proxy); + EXPECT_EQ("www.google.com:80", auth_challenge->challenger.ToString()); + EXPECT_EQ("MyRealm1", auth_challenge->realm); + EXPECT_EQ("basic", auth_challenge->scheme); + return true; +} + +bool CheckBasicProxyAuth(const AuthChallengeInfo* auth_challenge) { + if (!auth_challenge) + return false; + EXPECT_TRUE(auth_challenge->is_proxy); + EXPECT_EQ("myproxy:70", auth_challenge->challenger.ToString()); + EXPECT_EQ("MyRealm1", auth_challenge->realm); + EXPECT_EQ("basic", auth_challenge->scheme); + return true; +} + +bool CheckDigestServerAuth(const AuthChallengeInfo* auth_challenge) { + if (!auth_challenge) + return false; + EXPECT_FALSE(auth_challenge->is_proxy); + EXPECT_EQ("www.google.com:80", auth_challenge->challenger.ToString()); + EXPECT_EQ("digestive", auth_challenge->realm); + EXPECT_EQ("digest", auth_challenge->scheme); + return true; +} + +bool CheckNTLMServerAuth(const AuthChallengeInfo* auth_challenge) { + if (!auth_challenge) + return false; + EXPECT_FALSE(auth_challenge->is_proxy); + EXPECT_EQ("172.22.68.17:80", auth_challenge->challenger.ToString()); + EXPECT_EQ(std::string(), auth_challenge->realm); + EXPECT_EQ("ntlm", auth_challenge->scheme); + return true; +} + +} // namespace + +TEST_F(HttpNetworkTransactionSpdy2Test, Basic) { + SessionDependencies session_deps; + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, SimpleGET) { + MockRead data_reads[] = { + MockRead("HTTP/1.0 200 OK\r\n\r\n"), + MockRead("hello world"), + MockRead(SYNCHRONOUS, OK), + }; + SimpleGetHelperResult out = SimpleGetHelper(data_reads, + arraysize(data_reads)); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.0 200 OK", out.status_line); + EXPECT_EQ("hello world", out.response_data); +} + +// Response with no status line. +TEST_F(HttpNetworkTransactionSpdy2Test, SimpleGETNoHeaders) { + MockRead data_reads[] = { + MockRead("hello world"), + MockRead(SYNCHRONOUS, OK), + }; + SimpleGetHelperResult out = SimpleGetHelper(data_reads, + arraysize(data_reads)); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/0.9 200 OK", out.status_line); + EXPECT_EQ("hello world", out.response_data); +} + +// Allow up to 4 bytes of junk to precede status line. +TEST_F(HttpNetworkTransactionSpdy2Test, StatusLineJunk2Bytes) { + MockRead data_reads[] = { + MockRead("xxxHTTP/1.0 404 Not Found\nServer: blah\n\nDATA"), + MockRead(SYNCHRONOUS, OK), + }; + SimpleGetHelperResult out = SimpleGetHelper(data_reads, + arraysize(data_reads)); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.0 404 Not Found", out.status_line); + EXPECT_EQ("DATA", out.response_data); +} + +// Allow up to 4 bytes of junk to precede status line. +TEST_F(HttpNetworkTransactionSpdy2Test, StatusLineJunk4Bytes) { + MockRead data_reads[] = { + MockRead("\n\nQJHTTP/1.0 404 Not Found\nServer: blah\n\nDATA"), + MockRead(SYNCHRONOUS, OK), + }; + SimpleGetHelperResult out = SimpleGetHelper(data_reads, + arraysize(data_reads)); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.0 404 Not Found", out.status_line); + EXPECT_EQ("DATA", out.response_data); +} + +// Beyond 4 bytes of slop and it should fail to find a status line. +TEST_F(HttpNetworkTransactionSpdy2Test, StatusLineJunk5Bytes) { + MockRead data_reads[] = { + MockRead("xxxxxHTTP/1.1 404 Not Found\nServer: blah"), + MockRead(SYNCHRONOUS, OK), + }; + SimpleGetHelperResult out = SimpleGetHelper(data_reads, + arraysize(data_reads)); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/0.9 200 OK", out.status_line); + EXPECT_EQ("xxxxxHTTP/1.1 404 Not Found\nServer: blah", out.response_data); +} + +// Same as StatusLineJunk4Bytes, except the read chunks are smaller. +TEST_F(HttpNetworkTransactionSpdy2Test, StatusLineJunk4Bytes_Slow) { + MockRead data_reads[] = { + MockRead("\n"), + MockRead("\n"), + MockRead("Q"), + MockRead("J"), + MockRead("HTTP/1.0 404 Not Found\nServer: blah\n\nDATA"), + MockRead(SYNCHRONOUS, OK), + }; + SimpleGetHelperResult out = SimpleGetHelper(data_reads, + arraysize(data_reads)); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.0 404 Not Found", out.status_line); + EXPECT_EQ("DATA", out.response_data); +} + +// Close the connection before enough bytes to have a status line. +TEST_F(HttpNetworkTransactionSpdy2Test, StatusLinePartial) { + MockRead data_reads[] = { + MockRead("HTT"), + MockRead(SYNCHRONOUS, OK), + }; + SimpleGetHelperResult out = SimpleGetHelper(data_reads, + arraysize(data_reads)); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/0.9 200 OK", out.status_line); + EXPECT_EQ("HTT", out.response_data); +} + +// Simulate a 204 response, lacking a Content-Length header, sent over a +// persistent connection. The response should still terminate since a 204 +// cannot have a response body. +TEST_F(HttpNetworkTransactionSpdy2Test, StopsReading204) { + MockRead data_reads[] = { + MockRead("HTTP/1.1 204 No Content\r\n\r\n"), + MockRead("junk"), // Should not be read!! + MockRead(SYNCHRONOUS, OK), + }; + SimpleGetHelperResult out = SimpleGetHelper(data_reads, + arraysize(data_reads)); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 204 No Content", out.status_line); + EXPECT_EQ("", out.response_data); +} + +// A simple request using chunked encoding with some extra data after. +// (Like might be seen in a pipelined response.) +TEST_F(HttpNetworkTransactionSpdy2Test, ChunkedEncoding) { + MockRead data_reads[] = { + MockRead("HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n"), + MockRead("5\r\nHello\r\n"), + MockRead("1\r\n"), + MockRead(" \r\n"), + MockRead("5\r\nworld\r\n"), + MockRead("0\r\n\r\nHTTP/1.1 200 OK\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + SimpleGetHelperResult out = SimpleGetHelper(data_reads, + arraysize(data_reads)); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("Hello world", out.response_data); +} + +// Next tests deal with http://crbug.com/56344. + +TEST_F(HttpNetworkTransactionSpdy2Test, + MultipleContentLengthHeadersNoTransferEncoding) { + MockRead data_reads[] = { + MockRead("HTTP/1.1 200 OK\r\n"), + MockRead("Content-Length: 10\r\n"), + MockRead("Content-Length: 5\r\n\r\n"), + }; + SimpleGetHelperResult out = SimpleGetHelper(data_reads, + arraysize(data_reads)); + EXPECT_EQ(ERR_RESPONSE_HEADERS_MULTIPLE_CONTENT_LENGTH, out.rv); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, + DuplicateContentLengthHeadersNoTransferEncoding) { + MockRead data_reads[] = { + MockRead("HTTP/1.1 200 OK\r\n"), + MockRead("Content-Length: 5\r\n"), + MockRead("Content-Length: 5\r\n\r\n"), + MockRead("Hello"), + }; + SimpleGetHelperResult out = SimpleGetHelper(data_reads, + arraysize(data_reads)); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("Hello", out.response_data); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, + ComplexContentLengthHeadersNoTransferEncoding) { + // More than 2 dupes. + { + MockRead data_reads[] = { + MockRead("HTTP/1.1 200 OK\r\n"), + MockRead("Content-Length: 5\r\n"), + MockRead("Content-Length: 5\r\n"), + MockRead("Content-Length: 5\r\n\r\n"), + MockRead("Hello"), + }; + SimpleGetHelperResult out = SimpleGetHelper(data_reads, + arraysize(data_reads)); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("Hello", out.response_data); + } + // HTTP/1.0 + { + MockRead data_reads[] = { + MockRead("HTTP/1.0 200 OK\r\n"), + MockRead("Content-Length: 5\r\n"), + MockRead("Content-Length: 5\r\n"), + MockRead("Content-Length: 5\r\n\r\n"), + MockRead("Hello"), + }; + SimpleGetHelperResult out = SimpleGetHelper(data_reads, + arraysize(data_reads)); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.0 200 OK", out.status_line); + EXPECT_EQ("Hello", out.response_data); + } + // 2 dupes and one mismatched. + { + MockRead data_reads[] = { + MockRead("HTTP/1.1 200 OK\r\n"), + MockRead("Content-Length: 10\r\n"), + MockRead("Content-Length: 10\r\n"), + MockRead("Content-Length: 5\r\n\r\n"), + }; + SimpleGetHelperResult out = SimpleGetHelper(data_reads, + arraysize(data_reads)); + EXPECT_EQ(ERR_RESPONSE_HEADERS_MULTIPLE_CONTENT_LENGTH, out.rv); + } +} + +TEST_F(HttpNetworkTransactionSpdy2Test, + MultipleContentLengthHeadersTransferEncoding) { + MockRead data_reads[] = { + MockRead("HTTP/1.1 200 OK\r\n"), + MockRead("Content-Length: 666\r\n"), + MockRead("Content-Length: 1337\r\n"), + MockRead("Transfer-Encoding: chunked\r\n\r\n"), + MockRead("5\r\nHello\r\n"), + MockRead("1\r\n"), + MockRead(" \r\n"), + MockRead("5\r\nworld\r\n"), + MockRead("0\r\n\r\nHTTP/1.1 200 OK\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + SimpleGetHelperResult out = SimpleGetHelper(data_reads, + arraysize(data_reads)); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("Hello world", out.response_data); +} + +// Next tests deal with http://crbug.com/98895. + +// Checks that a single Content-Disposition header results in no error. +TEST_F(HttpNetworkTransactionSpdy2Test, SingleContentDispositionHeader) { + MockRead data_reads[] = { + MockRead("HTTP/1.1 200 OK\r\n"), + MockRead("Content-Disposition: attachment;filename=\"salutations.txt\"r\n"), + MockRead("Content-Length: 5\r\n\r\n"), + MockRead("Hello"), + }; + SimpleGetHelperResult out = SimpleGetHelper(data_reads, + arraysize(data_reads)); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("Hello", out.response_data); +} + +// Checks that two identical Content-Disposition headers result in an error. +TEST_F(HttpNetworkTransactionSpdy2Test, + DuplicateIdenticalContentDispositionHeaders) { + MockRead data_reads[] = { + MockRead("HTTP/1.1 200 OK\r\n"), + MockRead("Content-Disposition: attachment;filename=\"greetings.txt\"r\n"), + MockRead("Content-Disposition: attachment;filename=\"greetings.txt\"r\n"), + MockRead("Content-Length: 5\r\n\r\n"), + MockRead("Hello"), + }; + SimpleGetHelperResult out = SimpleGetHelper(data_reads, + arraysize(data_reads)); + EXPECT_EQ(ERR_RESPONSE_HEADERS_MULTIPLE_CONTENT_DISPOSITION, out.rv); +} + +// Checks that two distinct Content-Disposition headers result in an error. +TEST_F(HttpNetworkTransactionSpdy2Test, + DuplicateDistinctContentDispositionHeaders) { + MockRead data_reads[] = { + MockRead("HTTP/1.1 200 OK\r\n"), + MockRead("Content-Disposition: attachment;filename=\"greetings.txt\"r\n"), + MockRead("Content-Disposition: attachment;filename=\"hi.txt\"r\n"), + MockRead("Content-Length: 5\r\n\r\n"), + MockRead("Hello"), + }; + SimpleGetHelperResult out = SimpleGetHelper(data_reads, + arraysize(data_reads)); + EXPECT_EQ(ERR_RESPONSE_HEADERS_MULTIPLE_CONTENT_DISPOSITION, out.rv); +} + +// Checks the behavior of a single Location header. +TEST_F(HttpNetworkTransactionSpdy2Test, SingleLocationHeader) { + MockRead data_reads[] = { + MockRead("HTTP/1.1 302 Redirect\r\n"), + MockRead("Location: http://good.com/\r\n"), + MockRead("Content-Length: 0\r\n\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://redirect.com/"); + request.load_flags = 0; + + SessionDependencies session_deps; + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&data); + + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + EXPECT_EQ(OK, callback.WaitForResult()); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL && response->headers != NULL); + EXPECT_EQ("HTTP/1.1 302 Redirect", response->headers->GetStatusLine()); + std::string url; + EXPECT_TRUE(response->headers->IsRedirect(&url)); + EXPECT_EQ("http://good.com/", url); +} + +// Checks that two identical Location headers result in an error. +TEST_F(HttpNetworkTransactionSpdy2Test, DuplicateIdenticalLocationHeaders) { + MockRead data_reads[] = { + MockRead("HTTP/1.1 302 Redirect\r\n"), + MockRead("Location: http://good.com/\r\n"), + MockRead("Location: http://good.com/\r\n"), + MockRead("Content-Length: 0\r\n\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + SimpleGetHelperResult out = SimpleGetHelper(data_reads, + arraysize(data_reads)); + EXPECT_EQ(ERR_RESPONSE_HEADERS_MULTIPLE_LOCATION, out.rv); +} + +// Checks that two distinct Location headers result in an error. +TEST_F(HttpNetworkTransactionSpdy2Test, DuplicateDistinctLocationHeaders) { + MockRead data_reads[] = { + MockRead("HTTP/1.1 302 Redirect\r\n"), + MockRead("Location: http://good.com/\r\n"), + MockRead("Location: http://evil.com/\r\n"), + MockRead("Content-Length: 0\r\n\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + SimpleGetHelperResult out = SimpleGetHelper(data_reads, + arraysize(data_reads)); + EXPECT_EQ(ERR_RESPONSE_HEADERS_MULTIPLE_LOCATION, out.rv); +} + +// Do a request using the HEAD method. Verify that we don't try to read the +// message body (since HEAD has none). +TEST_F(HttpNetworkTransactionSpdy2Test, Head) { + HttpRequestInfo request; + request.method = "HEAD"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + + SessionDependencies session_deps; + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + MockWrite data_writes1[] = { + MockWrite("HEAD / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n" + "Content-Length: 0\r\n\r\n"), + }; + MockRead data_reads1[] = { + MockRead("HTTP/1.1 404 Not Found\r\n"), + MockRead("Server: Blah\r\n"), + MockRead("Content-Length: 1234\r\n\r\n"), + + // No response body because the test stops reading here. + MockRead(SYNCHRONOUS, ERR_UNEXPECTED), // Should not be reached. + }; + + StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), + data_writes1, arraysize(data_writes1)); + session_deps.socket_factory.AddSocketDataProvider(&data1); + + TestCompletionCallback callback1; + + int rv = trans->Start(&request, callback1.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + + // Check that the headers got parsed. + EXPECT_TRUE(response->headers != NULL); + EXPECT_EQ(1234, response->headers->GetContentLength()); + EXPECT_EQ("HTTP/1.1 404 Not Found", response->headers->GetStatusLine()); + + std::string server_header; + void* iter = NULL; + bool has_server_header = response->headers->EnumerateHeader( + &iter, "Server", &server_header); + EXPECT_TRUE(has_server_header); + EXPECT_EQ("Blah", server_header); + + // Reading should give EOF right away, since there is no message body + // (despite non-zero content-length). + std::string response_data; + rv = ReadTransaction(trans.get(), &response_data); + EXPECT_EQ(OK, rv); + EXPECT_EQ("", response_data); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, ReuseConnection) { + SessionDependencies session_deps; + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + MockRead data_reads[] = { + MockRead("HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\n"), + MockRead("hello"), + MockRead("HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\n"), + MockRead("world"), + MockRead(SYNCHRONOUS, OK), + }; + StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&data); + + const char* const kExpectedResponseData[] = { + "hello", "world" + }; + + for (int i = 0; i < 2; ++i) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + + EXPECT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + + std::string response_data; + rv = ReadTransaction(trans.get(), &response_data); + EXPECT_EQ(OK, rv); + EXPECT_EQ(kExpectedResponseData[i], response_data); + } +} + +TEST_F(HttpNetworkTransactionSpdy2Test, Ignores100) { + HttpRequestInfo request; + request.method = "POST"; + request.url = GURL("http://www.foo.com/"); + request.upload_data = new UploadData; + request.upload_data->AppendBytes("foo", 3); + request.load_flags = 0; + + SessionDependencies session_deps; + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + MockRead data_reads[] = { + MockRead("HTTP/1.0 100 Continue\r\n\r\n"), + MockRead("HTTP/1.0 200 OK\r\n\r\n"), + MockRead("hello world"), + MockRead(SYNCHRONOUS, OK), + }; + StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&data); + + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + + EXPECT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.0 200 OK", response->headers->GetStatusLine()); + + std::string response_data; + rv = ReadTransaction(trans.get(), &response_data); + EXPECT_EQ(OK, rv); + EXPECT_EQ("hello world", response_data); +} + +// This test is almost the same as Ignores100 above, but the response contains +// a 102 instead of a 100. Also, instead of HTTP/1.0 the response is +// HTTP/1.1 and the two status headers are read in one read. +TEST_F(HttpNetworkTransactionSpdy2Test, Ignores1xx) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.foo.com/"); + request.load_flags = 0; + + SessionDependencies session_deps; + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + MockRead data_reads[] = { + MockRead("HTTP/1.1 102 Unspecified status code\r\n\r\n" + "HTTP/1.1 200 OK\r\n\r\n"), + MockRead("hello world"), + MockRead(SYNCHRONOUS, OK), + }; + StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&data); + + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + + EXPECT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + + std::string response_data; + rv = ReadTransaction(trans.get(), &response_data); + EXPECT_EQ(OK, rv); + EXPECT_EQ("hello world", response_data); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, Incomplete100ThenEOF) { + HttpRequestInfo request; + request.method = "POST"; + request.url = GURL("http://www.foo.com/"); + request.load_flags = 0; + + SessionDependencies session_deps; + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + MockRead data_reads[] = { + MockRead(SYNCHRONOUS, "HTTP/1.0 100 Continue\r\n"), + MockRead(ASYNC, 0), + }; + StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&data); + + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + std::string response_data; + rv = ReadTransaction(trans.get(), &response_data); + EXPECT_EQ(OK, rv); + EXPECT_EQ("", response_data); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, EmptyResponse) { + HttpRequestInfo request; + request.method = "POST"; + request.url = GURL("http://www.foo.com/"); + request.load_flags = 0; + + SessionDependencies session_deps; + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + MockRead data_reads[] = { + MockRead(ASYNC, 0), + }; + StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&data); + + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(ERR_EMPTY_RESPONSE, rv); +} + +void HttpNetworkTransactionSpdy2Test::KeepAliveConnectionResendRequestTest( + const MockWrite* write_failure, + const MockRead* read_failure) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.foo.com/"); + request.load_flags = 0; + + SessionDependencies session_deps; + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + // Written data for successfully sending both requests. + MockWrite data1_writes[] = { + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.foo.com\r\n" + "Connection: keep-alive\r\n\r\n"), + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.foo.com\r\n" + "Connection: keep-alive\r\n\r\n") + }; + + // Read results for the first request. + MockRead data1_reads[] = { + MockRead("HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\n"), + MockRead("hello"), + MockRead(ASYNC, OK), + }; + + if (write_failure) { + ASSERT_TRUE(!read_failure); + data1_writes[1] = *write_failure; + } else { + ASSERT_TRUE(read_failure); + data1_reads[2] = *read_failure; + } + + StaticSocketDataProvider data1(data1_reads, arraysize(data1_reads), + data1_writes, arraysize(data1_writes)); + session_deps.socket_factory.AddSocketDataProvider(&data1); + + MockRead data2_reads[] = { + MockRead("HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\n"), + MockRead("world"), + MockRead(ASYNC, OK), + }; + StaticSocketDataProvider data2(data2_reads, arraysize(data2_reads), NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&data2); + + const char* kExpectedResponseData[] = { + "hello", "world" + }; + + for (int i = 0; i < 2; ++i) { + TestCompletionCallback callback; + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + + EXPECT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + + std::string response_data; + rv = ReadTransaction(trans.get(), &response_data); + EXPECT_EQ(OK, rv); + EXPECT_EQ(kExpectedResponseData[i], response_data); + } +} + +TEST_F(HttpNetworkTransactionSpdy2Test, + KeepAliveConnectionNotConnectedOnWrite) { + MockWrite write_failure(ASYNC, ERR_SOCKET_NOT_CONNECTED); + KeepAliveConnectionResendRequestTest(&write_failure, NULL); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, KeepAliveConnectionReset) { + MockRead read_failure(ASYNC, ERR_CONNECTION_RESET); + KeepAliveConnectionResendRequestTest(NULL, &read_failure); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, KeepAliveConnectionEOF) { + MockRead read_failure(SYNCHRONOUS, OK); // EOF + KeepAliveConnectionResendRequestTest(NULL, &read_failure); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, NonKeepAliveConnectionReset) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + + SessionDependencies session_deps; + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + MockRead data_reads[] = { + MockRead(ASYNC, ERR_CONNECTION_RESET), + MockRead("HTTP/1.0 200 OK\r\n\r\n"), // Should not be used + MockRead("hello world"), + MockRead(SYNCHRONOUS, OK), + }; + StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&data); + + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(ERR_CONNECTION_RESET, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + EXPECT_TRUE(response == NULL); +} + +// What do various browsers do when the server closes a non-keepalive +// connection without sending any response header or body? +// +// IE7: error page +// Safari 3.1.2 (Windows): error page +// Firefox 3.0.1: blank page +// Opera 9.52: after five attempts, blank page +// Us with WinHTTP: error page (ERR_INVALID_RESPONSE) +// Us: error page (EMPTY_RESPONSE) +TEST_F(HttpNetworkTransactionSpdy2Test, NonKeepAliveConnectionEOF) { + MockRead data_reads[] = { + MockRead(SYNCHRONOUS, OK), // EOF + MockRead("HTTP/1.0 200 OK\r\n\r\n"), // Should not be used + MockRead("hello world"), + MockRead(SYNCHRONOUS, OK), + }; + SimpleGetHelperResult out = SimpleGetHelper(data_reads, + arraysize(data_reads)); + EXPECT_EQ(ERR_EMPTY_RESPONSE, out.rv); +} + +// Test that we correctly reuse a keep-alive connection after not explicitly +// reading the body. +TEST_F(HttpNetworkTransactionSpdy2Test, KeepAliveAfterUnreadBody) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.foo.com/"); + request.load_flags = 0; + + SessionDependencies session_deps; + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + // Note that because all these reads happen in the same + // StaticSocketDataProvider, it shows that the same socket is being reused for + // all transactions. + MockRead data1_reads[] = { + MockRead("HTTP/1.1 204 No Content\r\n\r\n"), + MockRead("HTTP/1.1 205 Reset Content\r\n\r\n"), + MockRead("HTTP/1.1 304 Not Modified\r\n\r\n"), + MockRead("HTTP/1.1 302 Found\r\n" + "Content-Length: 0\r\n\r\n"), + MockRead("HTTP/1.1 302 Found\r\n" + "Content-Length: 5\r\n\r\n" + "hello"), + MockRead("HTTP/1.1 301 Moved Permanently\r\n" + "Content-Length: 0\r\n\r\n"), + MockRead("HTTP/1.1 301 Moved Permanently\r\n" + "Content-Length: 5\r\n\r\n" + "hello"), + MockRead("HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\n"), + MockRead("hello"), + }; + StaticSocketDataProvider data1(data1_reads, arraysize(data1_reads), NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&data1); + + MockRead data2_reads[] = { + MockRead(SYNCHRONOUS, ERR_UNEXPECTED), // Should not be reached. + }; + StaticSocketDataProvider data2(data2_reads, arraysize(data2_reads), NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&data2); + + const int kNumUnreadBodies = arraysize(data1_reads) - 2; + std::string response_lines[kNumUnreadBodies]; + + for (size_t i = 0; i < arraysize(data1_reads) - 2; ++i) { + TestCompletionCallback callback; + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + + ASSERT_TRUE(response->headers != NULL); + response_lines[i] = response->headers->GetStatusLine(); + + // We intentionally don't read the response bodies. + } + + const char* const kStatusLines[] = { + "HTTP/1.1 204 No Content", + "HTTP/1.1 205 Reset Content", + "HTTP/1.1 304 Not Modified", + "HTTP/1.1 302 Found", + "HTTP/1.1 302 Found", + "HTTP/1.1 301 Moved Permanently", + "HTTP/1.1 301 Moved Permanently", + }; + + COMPILE_ASSERT(kNumUnreadBodies == arraysize(kStatusLines), + forgot_to_update_kStatusLines); + + for (int i = 0; i < kNumUnreadBodies; ++i) + EXPECT_EQ(kStatusLines[i], response_lines[i]); + + TestCompletionCallback callback; + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + std::string response_data; + rv = ReadTransaction(trans.get(), &response_data); + EXPECT_EQ(OK, rv); + EXPECT_EQ("hello", response_data); +} + +// Test the request-challenge-retry sequence for basic auth. +// (basic auth is the easiest to mock, because it has no randomness). +TEST_F(HttpNetworkTransactionSpdy2Test, BasicAuth) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + + SessionDependencies session_deps; + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + MockWrite data_writes1[] = { + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n\r\n"), + }; + + MockRead data_reads1[] = { + MockRead("HTTP/1.0 401 Unauthorized\r\n"), + // Give a couple authenticate options (only the middle one is actually + // supported). + MockRead("WWW-Authenticate: Basic invalid\r\n"), // Malformed. + MockRead("WWW-Authenticate: Basic realm=\"MyRealm1\"\r\n"), + MockRead("WWW-Authenticate: UNSUPPORTED realm=\"FOO\"\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), + // Large content-length -- won't matter, as connection will be reset. + MockRead("Content-Length: 10000\r\n\r\n"), + MockRead(SYNCHRONOUS, ERR_FAILED), + }; + + // After calling trans->RestartWithAuth(), this is the request we should + // be issuing -- the final header line contains the credentials. + MockWrite data_writes2[] = { + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n" + "Authorization: Basic Zm9vOmJhcg==\r\n\r\n"), + }; + + // Lastly, the server responds with the actual content. + MockRead data_reads2[] = { + MockRead("HTTP/1.0 200 OK\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), + MockRead("Content-Length: 100\r\n\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + + StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), + data_writes1, arraysize(data_writes1)); + StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2), + data_writes2, arraysize(data_writes2)); + session_deps.socket_factory.AddSocketDataProvider(&data1); + session_deps.socket_factory.AddSocketDataProvider(&data2); + + TestCompletionCallback callback1; + + int rv = trans->Start(&request, callback1.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(CheckBasicServerAuth(response->auth_challenge.get())); + + TestCompletionCallback callback2; + + rv = trans->RestartWithAuth( + AuthCredentials(kFoo, kBar), callback2.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback2.WaitForResult(); + EXPECT_EQ(OK, rv); + + response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(response->auth_challenge.get() == NULL); + EXPECT_EQ(100, response->headers->GetContentLength()); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, DoNotSendAuth) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = net::LOAD_DO_NOT_SEND_AUTH_DATA; + + SessionDependencies session_deps; + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + MockWrite data_writes[] = { + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n\r\n"), + }; + + MockRead data_reads[] = { + MockRead("HTTP/1.0 401 Unauthorized\r\n"), + MockRead("WWW-Authenticate: Basic realm=\"MyRealm1\"\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), + // Large content-length -- won't matter, as connection will be reset. + MockRead("Content-Length: 10000\r\n\r\n"), + MockRead(SYNCHRONOUS, ERR_FAILED), + }; + + StaticSocketDataProvider data(data_reads, arraysize(data_reads), + data_writes, arraysize(data_writes)); + session_deps.socket_factory.AddSocketDataProvider(&data); + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(0, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(response->auth_challenge.get() == NULL); +} + +// Test the request-challenge-retry sequence for basic auth, over a keep-alive +// connection. +TEST_F(HttpNetworkTransactionSpdy2Test, BasicAuthKeepAlive) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + + SessionDependencies session_deps; + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + MockWrite data_writes1[] = { + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n\r\n"), + + // After calling trans->RestartWithAuth(), this is the request we should + // be issuing -- the final header line contains the credentials. + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n" + "Authorization: Basic Zm9vOmJhcg==\r\n\r\n"), + }; + + MockRead data_reads1[] = { + MockRead("HTTP/1.1 401 Unauthorized\r\n"), + MockRead("WWW-Authenticate: Basic realm=\"MyRealm1\"\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), + MockRead("Content-Length: 14\r\n\r\n"), + MockRead("Unauthorized\r\n"), + + // Lastly, the server responds with the actual content. + MockRead("HTTP/1.1 200 OK\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), + MockRead("Content-Length: 5\r\n\r\n"), + MockRead("Hello"), + }; + + // If there is a regression where we disconnect a Keep-Alive + // connection during an auth roundtrip, we'll end up reading this. + MockRead data_reads2[] = { + MockRead(SYNCHRONOUS, ERR_FAILED), + }; + + StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), + data_writes1, arraysize(data_writes1)); + StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2), + NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&data1); + session_deps.socket_factory.AddSocketDataProvider(&data2); + + TestCompletionCallback callback1; + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + int rv = trans->Start(&request, callback1.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(CheckBasicServerAuth(response->auth_challenge.get())); + + TestCompletionCallback callback2; + + rv = trans->RestartWithAuth( + AuthCredentials(kFoo, kBar), callback2.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback2.WaitForResult(); + EXPECT_EQ(OK, rv); + + response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(response->auth_challenge.get() == NULL); + EXPECT_EQ(5, response->headers->GetContentLength()); +} + +// Test the request-challenge-retry sequence for basic auth, over a keep-alive +// connection and with no response body to drain. +TEST_F(HttpNetworkTransactionSpdy2Test, BasicAuthKeepAliveNoBody) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + + SessionDependencies session_deps; + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + MockWrite data_writes1[] = { + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n\r\n"), + + // After calling trans->RestartWithAuth(), this is the request we should + // be issuing -- the final header line contains the credentials. + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n" + "Authorization: Basic Zm9vOmJhcg==\r\n\r\n"), + }; + + MockRead data_reads1[] = { + MockRead("HTTP/1.1 401 Unauthorized\r\n"), + MockRead("WWW-Authenticate: Basic realm=\"MyRealm1\"\r\n"), + MockRead("Content-Length: 0\r\n\r\n"), // No response body. + + // Lastly, the server responds with the actual content. + MockRead("HTTP/1.1 200 OK\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), + MockRead("Content-Length: 5\r\n\r\n"), + MockRead("hello"), + }; + + // An incorrect reconnect would cause this to be read. + MockRead data_reads2[] = { + MockRead(SYNCHRONOUS, ERR_FAILED), + }; + + StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), + data_writes1, arraysize(data_writes1)); + StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2), + NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&data1); + session_deps.socket_factory.AddSocketDataProvider(&data2); + + TestCompletionCallback callback1; + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + int rv = trans->Start(&request, callback1.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(CheckBasicServerAuth(response->auth_challenge.get())); + + TestCompletionCallback callback2; + + rv = trans->RestartWithAuth( + AuthCredentials(kFoo, kBar), callback2.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback2.WaitForResult(); + EXPECT_EQ(OK, rv); + + response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(response->auth_challenge.get() == NULL); + EXPECT_EQ(5, response->headers->GetContentLength()); +} + +// Test the request-challenge-retry sequence for basic auth, over a keep-alive +// connection and with a large response body to drain. +TEST_F(HttpNetworkTransactionSpdy2Test, BasicAuthKeepAliveLargeBody) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + + SessionDependencies session_deps; + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + MockWrite data_writes1[] = { + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n\r\n"), + + // After calling trans->RestartWithAuth(), this is the request we should + // be issuing -- the final header line contains the credentials. + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n" + "Authorization: Basic Zm9vOmJhcg==\r\n\r\n"), + }; + + // Respond with 5 kb of response body. + std::string large_body_string("Unauthorized"); + large_body_string.append(5 * 1024, ' '); + large_body_string.append("\r\n"); + + MockRead data_reads1[] = { + MockRead("HTTP/1.1 401 Unauthorized\r\n"), + MockRead("WWW-Authenticate: Basic realm=\"MyRealm1\"\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), + // 5134 = 12 + 5 * 1024 + 2 + MockRead("Content-Length: 5134\r\n\r\n"), + MockRead(ASYNC, large_body_string.data(), large_body_string.size()), + + // Lastly, the server responds with the actual content. + MockRead("HTTP/1.1 200 OK\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), + MockRead("Content-Length: 5\r\n\r\n"), + MockRead("hello"), + }; + + // An incorrect reconnect would cause this to be read. + MockRead data_reads2[] = { + MockRead(SYNCHRONOUS, ERR_FAILED), + }; + + StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), + data_writes1, arraysize(data_writes1)); + StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2), + NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&data1); + session_deps.socket_factory.AddSocketDataProvider(&data2); + + TestCompletionCallback callback1; + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + int rv = trans->Start(&request, callback1.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(CheckBasicServerAuth(response->auth_challenge.get())); + + TestCompletionCallback callback2; + + rv = trans->RestartWithAuth( + AuthCredentials(kFoo, kBar), callback2.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback2.WaitForResult(); + EXPECT_EQ(OK, rv); + + response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(response->auth_challenge.get() == NULL); + EXPECT_EQ(5, response->headers->GetContentLength()); +} + +// Test the request-challenge-retry sequence for basic auth, over a keep-alive +// connection, but the server gets impatient and closes the connection. +TEST_F(HttpNetworkTransactionSpdy2Test, BasicAuthKeepAliveImpatientServer) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + + SessionDependencies session_deps; + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + MockWrite data_writes1[] = { + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n\r\n"), + // This simulates the seemingly successful write to a closed connection + // if the bug is not fixed. + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n" + "Authorization: Basic Zm9vOmJhcg==\r\n\r\n"), + }; + + MockRead data_reads1[] = { + MockRead("HTTP/1.1 401 Unauthorized\r\n"), + MockRead("WWW-Authenticate: Basic realm=\"MyRealm1\"\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), + MockRead("Content-Length: 14\r\n\r\n"), + // Tell MockTCPClientSocket to simulate the server closing the connection. + MockRead(SYNCHRONOUS, ERR_TEST_PEER_CLOSE_AFTER_NEXT_MOCK_READ), + MockRead("Unauthorized\r\n"), + MockRead(SYNCHRONOUS, OK), // The server closes the connection. + }; + + // After calling trans->RestartWithAuth(), this is the request we should + // be issuing -- the final header line contains the credentials. + MockWrite data_writes2[] = { + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n" + "Authorization: Basic Zm9vOmJhcg==\r\n\r\n"), + }; + + // Lastly, the server responds with the actual content. + MockRead data_reads2[] = { + MockRead("HTTP/1.1 200 OK\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), + MockRead("Content-Length: 5\r\n\r\n"), + MockRead("hello"), + }; + + StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), + data_writes1, arraysize(data_writes1)); + StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2), + data_writes2, arraysize(data_writes2)); + session_deps.socket_factory.AddSocketDataProvider(&data1); + session_deps.socket_factory.AddSocketDataProvider(&data2); + + TestCompletionCallback callback1; + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + int rv = trans->Start(&request, callback1.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(CheckBasicServerAuth(response->auth_challenge.get())); + + TestCompletionCallback callback2; + + rv = trans->RestartWithAuth( + AuthCredentials(kFoo, kBar), callback2.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback2.WaitForResult(); + EXPECT_EQ(OK, rv); + + response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(response->auth_challenge.get() == NULL); + EXPECT_EQ(5, response->headers->GetContentLength()); +} + +// Test the request-challenge-retry sequence for basic auth, over a connection +// that requires a restart when setting up an SSL tunnel. +TEST_F(HttpNetworkTransactionSpdy2Test, BasicAuthProxyNoKeepAlive) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("https://www.google.com/"); + // when the no authentication data flag is set. + request.load_flags = net::LOAD_DO_NOT_SEND_AUTH_DATA; + + // Configure against proxy server "myproxy:70". + SessionDependencies session_deps(ProxyService::CreateFixed("myproxy:70")); + CapturingBoundNetLog log(CapturingNetLog::kUnbounded); + session_deps.net_log = log.bound().net_log(); + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + // Since we have proxy, should try to establish tunnel. + MockWrite data_writes1[] = { + MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n\r\n"), + + // After calling trans->RestartWithAuth(), this is the request we should + // be issuing -- the final header line contains the credentials. + MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n" + "Proxy-Authorization: Basic Zm9vOmJhcg==\r\n\r\n"), + + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n\r\n"), + }; + + // The proxy responds to the connect with a 407, using a persistent + // connection. + MockRead data_reads1[] = { + // No credentials. + MockRead("HTTP/1.1 407 Proxy Authentication Required\r\n"), + MockRead("Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n"), + MockRead("Proxy-Connection: close\r\n\r\n"), + + MockRead("HTTP/1.1 200 Connection Established\r\n\r\n"), + + MockRead("HTTP/1.1 200 OK\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), + MockRead("Content-Length: 5\r\n\r\n"), + MockRead(SYNCHRONOUS, "hello"), + }; + + StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), + data_writes1, arraysize(data_writes1)); + session_deps.socket_factory.AddSocketDataProvider(&data1); + SSLSocketDataProvider ssl(ASYNC, OK); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); + + TestCompletionCallback callback1; + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + int rv = trans->Start(&request, callback1.callback(), log.bound()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(OK, rv); + net::CapturingNetLog::EntryList entries; + log.GetEntries(&entries); + size_t pos = ExpectLogContainsSomewhere( + entries, 0, NetLog::TYPE_HTTP_TRANSACTION_SEND_TUNNEL_HEADERS, + NetLog::PHASE_NONE); + ExpectLogContainsSomewhere( + entries, pos, + NetLog::TYPE_HTTP_TRANSACTION_READ_TUNNEL_RESPONSE_HEADERS, + NetLog::PHASE_NONE); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_FALSE(response->headers == NULL); + EXPECT_EQ(407, response->headers->response_code()); + EXPECT_TRUE(HttpVersion(1, 1) == response->headers->GetHttpVersion()); + EXPECT_TRUE(CheckBasicProxyAuth(response->auth_challenge.get())); + + TestCompletionCallback callback2; + + rv = trans->RestartWithAuth( + AuthCredentials(kFoo, kBar), callback2.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback2.WaitForResult(); + EXPECT_EQ(OK, rv); + + response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + + EXPECT_TRUE(response->headers->IsKeepAlive()); + EXPECT_EQ(200, response->headers->response_code()); + EXPECT_EQ(5, response->headers->GetContentLength()); + EXPECT_TRUE(HttpVersion(1, 1) == response->headers->GetHttpVersion()); + + // The password prompt info should not be set. + EXPECT_TRUE(response->auth_challenge.get() == NULL); + + trans.reset(); + session->CloseAllConnections(); +} + +// Test the request-challenge-retry sequence for basic auth, over a keep-alive +// proxy connection, when setting up an SSL tunnel. +TEST_F(HttpNetworkTransactionSpdy2Test, BasicAuthProxyKeepAlive) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("https://www.google.com/"); + // Ensure that proxy authentication is attempted even + // when the no authentication data flag is set. + request.load_flags = net::LOAD_DO_NOT_SEND_AUTH_DATA; + + // Configure against proxy server "myproxy:70". + SessionDependencies session_deps(ProxyService::CreateFixed("myproxy:70")); + CapturingBoundNetLog log(CapturingNetLog::kUnbounded); + session_deps.net_log = log.bound().net_log(); + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + // Since we have proxy, should try to establish tunnel. + MockWrite data_writes1[] = { + MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n\r\n"), + + // After calling trans->RestartWithAuth(), this is the request we should + // be issuing -- the final header line contains the credentials. + MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n" + "Proxy-Authorization: Basic Zm9vOmJheg==\r\n\r\n"), + }; + + // The proxy responds to the connect with a 407, using a persistent + // connection. + MockRead data_reads1[] = { + // No credentials. + MockRead("HTTP/1.1 407 Proxy Authentication Required\r\n"), + MockRead("Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n"), + MockRead("Content-Length: 10\r\n\r\n"), + MockRead("0123456789"), + + // Wrong credentials (wrong password). + MockRead("HTTP/1.1 407 Proxy Authentication Required\r\n"), + MockRead("Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n"), + MockRead("Content-Length: 10\r\n\r\n"), + // No response body because the test stops reading here. + MockRead(SYNCHRONOUS, ERR_UNEXPECTED), // Should not be reached. + }; + + StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), + data_writes1, arraysize(data_writes1)); + session_deps.socket_factory.AddSocketDataProvider(&data1); + + TestCompletionCallback callback1; + + int rv = trans->Start(&request, callback1.callback(), log.bound()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(OK, rv); + net::CapturingNetLog::EntryList entries; + log.GetEntries(&entries); + size_t pos = ExpectLogContainsSomewhere( + entries, 0, NetLog::TYPE_HTTP_TRANSACTION_SEND_TUNNEL_HEADERS, + NetLog::PHASE_NONE); + ExpectLogContainsSomewhere( + entries, pos, + NetLog::TYPE_HTTP_TRANSACTION_READ_TUNNEL_RESPONSE_HEADERS, + NetLog::PHASE_NONE); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_FALSE(response->headers == NULL); + EXPECT_TRUE(response->headers->IsKeepAlive()); + EXPECT_EQ(407, response->headers->response_code()); + EXPECT_EQ(10, response->headers->GetContentLength()); + EXPECT_TRUE(HttpVersion(1, 1) == response->headers->GetHttpVersion()); + EXPECT_TRUE(CheckBasicProxyAuth(response->auth_challenge.get())); + + TestCompletionCallback callback2; + + // Wrong password (should be "bar"). + rv = trans->RestartWithAuth( + AuthCredentials(kFoo, kBaz), callback2.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback2.WaitForResult(); + EXPECT_EQ(OK, rv); + + response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_FALSE(response->headers == NULL); + EXPECT_TRUE(response->headers->IsKeepAlive()); + EXPECT_EQ(407, response->headers->response_code()); + EXPECT_EQ(10, response->headers->GetContentLength()); + EXPECT_TRUE(HttpVersion(1, 1) == response->headers->GetHttpVersion()); + EXPECT_TRUE(CheckBasicProxyAuth(response->auth_challenge.get())); + + // Flush the idle socket before the NetLog and HttpNetworkTransaction go + // out of scope. + session->CloseAllConnections(); +} + +// Test that we don't read the response body when we fail to establish a tunnel, +// even if the user cancels the proxy's auth attempt. +TEST_F(HttpNetworkTransactionSpdy2Test, BasicAuthProxyCancelTunnel) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("https://www.google.com/"); + request.load_flags = 0; + + // Configure against proxy server "myproxy:70". + SessionDependencies session_deps(ProxyService::CreateFixed("myproxy:70")); + + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + // Since we have proxy, should try to establish tunnel. + MockWrite data_writes[] = { + MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n\r\n"), + }; + + // The proxy responds to the connect with a 407. + MockRead data_reads[] = { + MockRead("HTTP/1.1 407 Proxy Authentication Required\r\n"), + MockRead("Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n"), + MockRead("Content-Length: 10\r\n\r\n"), + MockRead(SYNCHRONOUS, ERR_UNEXPECTED), // Should not be reached. + }; + + StaticSocketDataProvider data(data_reads, arraysize(data_reads), + data_writes, arraysize(data_writes)); + session_deps.socket_factory.AddSocketDataProvider(&data); + + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + + EXPECT_TRUE(response->headers->IsKeepAlive()); + EXPECT_EQ(407, response->headers->response_code()); + EXPECT_EQ(10, response->headers->GetContentLength()); + EXPECT_TRUE(HttpVersion(1, 1) == response->headers->GetHttpVersion()); + + std::string response_data; + rv = ReadTransaction(trans.get(), &response_data); + EXPECT_EQ(ERR_TUNNEL_CONNECTION_FAILED, rv); + + // Flush the idle socket before the HttpNetworkTransaction goes out of scope. + session->CloseAllConnections(); +} + +// Test when a server (non-proxy) returns a 407 (proxy-authenticate). +// The request should fail with ERR_UNEXPECTED_PROXY_AUTH. +TEST_F(HttpNetworkTransactionSpdy2Test, UnexpectedProxyAuth) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + + // We are using a DIRECT connection (i.e. no proxy) for this session. + SessionDependencies session_deps; + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + MockWrite data_writes1[] = { + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n\r\n"), + }; + + MockRead data_reads1[] = { + MockRead("HTTP/1.0 407 Proxy Auth required\r\n"), + MockRead("Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n"), + // Large content-length -- won't matter, as connection will be reset. + MockRead("Content-Length: 10000\r\n\r\n"), + MockRead(SYNCHRONOUS, ERR_FAILED), + }; + + StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), + data_writes1, arraysize(data_writes1)); + session_deps.socket_factory.AddSocketDataProvider(&data1); + + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(ERR_UNEXPECTED_PROXY_AUTH, rv); +} + +// Tests when an HTTPS server (non-proxy) returns a 407 (proxy-authentication) +// through a non-authenticating proxy. The request should fail with +// ERR_UNEXPECTED_PROXY_AUTH. +// Note that it is impossible to detect if an HTTP server returns a 407 through +// a non-authenticating proxy - there is nothing to indicate whether the +// response came from the proxy or the server, so it is treated as if the proxy +// issued the challenge. +TEST_F(HttpNetworkTransactionSpdy2Test, + HttpsServerRequestsProxyAuthThroughProxy) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("https://www.google.com/"); + + SessionDependencies session_deps(ProxyService::CreateFixed("myproxy:70")); + CapturingBoundNetLog log(CapturingNetLog::kUnbounded); + session_deps.net_log = log.bound().net_log(); + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + // Since we have proxy, should try to establish tunnel. + MockWrite data_writes1[] = { + MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n\r\n"), + + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n\r\n"), + }; + + MockRead data_reads1[] = { + MockRead("HTTP/1.1 200 Connection Established\r\n\r\n"), + + MockRead("HTTP/1.1 407 Unauthorized\r\n"), + MockRead("Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n"), + MockRead("\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + + StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), + data_writes1, arraysize(data_writes1)); + session_deps.socket_factory.AddSocketDataProvider(&data1); + SSLSocketDataProvider ssl(ASYNC, OK); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); + + TestCompletionCallback callback1; + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + int rv = trans->Start(&request, callback1.callback(), log.bound()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(ERR_UNEXPECTED_PROXY_AUTH, rv); + net::CapturingNetLog::EntryList entries; + log.GetEntries(&entries); + size_t pos = ExpectLogContainsSomewhere( + entries, 0, NetLog::TYPE_HTTP_TRANSACTION_SEND_TUNNEL_HEADERS, + NetLog::PHASE_NONE); + ExpectLogContainsSomewhere( + entries, pos, + NetLog::TYPE_HTTP_TRANSACTION_READ_TUNNEL_RESPONSE_HEADERS, + NetLog::PHASE_NONE); +} + +// Test a simple get through an HTTPS Proxy. +TEST_F(HttpNetworkTransactionSpdy2Test, HttpsProxyGet) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + + // Configure against https proxy server "proxy:70". + SessionDependencies session_deps(ProxyService::CreateFixed( + "https://proxy:70")); + CapturingBoundNetLog log(CapturingNetLog::kUnbounded); + session_deps.net_log = log.bound().net_log(); + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + // Since we have proxy, should use full url + MockWrite data_writes1[] = { + MockWrite("GET http://www.google.com/ HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n\r\n"), + }; + + MockRead data_reads1[] = { + MockRead("HTTP/1.1 200 OK\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), + MockRead("Content-Length: 100\r\n\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + + StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), + data_writes1, arraysize(data_writes1)); + session_deps.socket_factory.AddSocketDataProvider(&data1); + SSLSocketDataProvider ssl(ASYNC, OK); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); + + TestCompletionCallback callback1; + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + int rv = trans->Start(&request, callback1.callback(), log.bound()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + + EXPECT_TRUE(response->headers->IsKeepAlive()); + EXPECT_EQ(200, response->headers->response_code()); + EXPECT_EQ(100, response->headers->GetContentLength()); + EXPECT_TRUE(HttpVersion(1, 1) == response->headers->GetHttpVersion()); + + // The password prompt info should not be set. + EXPECT_TRUE(response->auth_challenge.get() == NULL); +} + +// Test a SPDY get through an HTTPS Proxy. +TEST_F(HttpNetworkTransactionSpdy2Test, HttpsProxySpdyGet) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + + // Configure against https proxy server "proxy:70". + SessionDependencies session_deps(ProxyService::CreateFixed( + "https://proxy:70")); + CapturingBoundNetLog log(CapturingNetLog::kUnbounded); + session_deps.net_log = log.bound().net_log(); + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + // fetch http://www.google.com/ via SPDY + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST, + false)); + MockWrite spdy_writes[] = { CreateMockWrite(*req) }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> data(ConstructSpdyBodyFrame(1, true)); + MockRead spdy_reads[] = { + CreateMockRead(*resp), + CreateMockRead(*data), + MockRead(ASYNC, 0, 0), + }; + + scoped_ptr<DelayedSocketData> spdy_data( + new DelayedSocketData( + 1, // wait for one write to finish before reading. + spdy_reads, arraysize(spdy_reads), + spdy_writes, arraysize(spdy_writes))); + session_deps.socket_factory.AddSocketDataProvider(spdy_data.get()); + + SSLSocketDataProvider ssl(ASYNC, OK); + ssl.SetNextProto(SSLClientSocket::kProtoSPDY2); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); + + TestCompletionCallback callback1; + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + int rv = trans->Start(&request, callback1.callback(), log.bound()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + + std::string response_data; + ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data)); + EXPECT_EQ(kUploadData, response_data); +} + +// Test a SPDY get through an HTTPS Proxy. +TEST_F(HttpNetworkTransactionSpdy2Test, HttpsProxySpdyGetWithProxyAuth) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + + // Configure against https proxy server "myproxy:70". + SessionDependencies session_deps( + ProxyService::CreateFixed("https://myproxy:70")); + CapturingBoundNetLog log(CapturingNetLog::kUnbounded); + session_deps.net_log = log.bound().net_log(); + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + // The first request will be a bare GET, the second request will be a + // GET with a Proxy-Authorization header. + scoped_ptr<spdy::SpdyFrame> req_get( + ConstructSpdyGet(NULL, 0, false, 1, LOWEST, false)); + const char* const kExtraAuthorizationHeaders[] = { + "proxy-authorization", + "Basic Zm9vOmJhcg==", + }; + scoped_ptr<spdy::SpdyFrame> req_get_authorization( + ConstructSpdyGet( + kExtraAuthorizationHeaders, arraysize(kExtraAuthorizationHeaders)/2, + false, 3, LOWEST, false)); + MockWrite spdy_writes[] = { + CreateMockWrite(*req_get, 1), + CreateMockWrite(*req_get_authorization, 4), + }; + + // The first response is a 407 proxy authentication challenge, and the second + // response will be a 200 response since the second request includes a valid + // Authorization header. + const char* const kExtraAuthenticationHeaders[] = { + "proxy-authenticate", + "Basic realm=\"MyRealm1\"" + }; + scoped_ptr<spdy::SpdyFrame> resp_authentication( + ConstructSpdySynReplyError( + "407 Proxy Authentication Required", + kExtraAuthenticationHeaders, arraysize(kExtraAuthenticationHeaders)/2, + 1)); + scoped_ptr<spdy::SpdyFrame> body_authentication( + ConstructSpdyBodyFrame(1, true)); + scoped_ptr<spdy::SpdyFrame> resp_data(ConstructSpdyGetSynReply(NULL, 0, 3)); + scoped_ptr<spdy::SpdyFrame> body_data(ConstructSpdyBodyFrame(3, true)); + MockRead spdy_reads[] = { + CreateMockRead(*resp_authentication, 2), + CreateMockRead(*body_authentication, 3), + CreateMockRead(*resp_data, 5), + CreateMockRead(*body_data, 6), + MockRead(ASYNC, 0, 7), + }; + + scoped_ptr<OrderedSocketData> data( + new OrderedSocketData(spdy_reads, arraysize(spdy_reads), + spdy_writes, arraysize(spdy_writes))); + session_deps.socket_factory.AddSocketDataProvider(data.get()); + + SSLSocketDataProvider ssl(ASYNC, OK); + ssl.SetNextProto(SSLClientSocket::kProtoSPDY2); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); + + TestCompletionCallback callback1; + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + int rv = trans->Start(&request, callback1.callback(), log.bound()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* const response = trans->GetResponseInfo(); + + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers != NULL); + EXPECT_EQ(407, response->headers->response_code()); + EXPECT_TRUE(response->was_fetched_via_spdy); + EXPECT_TRUE(CheckBasicProxyAuth(response->auth_challenge.get())); + + TestCompletionCallback callback2; + + rv = trans->RestartWithAuth( + AuthCredentials(kFoo, kBar), callback2.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback2.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* const response_restart = trans->GetResponseInfo(); + + ASSERT_TRUE(response_restart != NULL); + ASSERT_TRUE(response_restart->headers != NULL); + EXPECT_EQ(200, response_restart->headers->response_code()); + // The password prompt info should not be set. + EXPECT_TRUE(response_restart->auth_challenge.get() == NULL); +} + +// Test a SPDY CONNECT through an HTTPS Proxy to an HTTPS (non-SPDY) Server. +TEST_F(HttpNetworkTransactionSpdy2Test, HttpsProxySpdyConnectHttps) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("https://www.google.com/"); + request.load_flags = 0; + + // Configure against https proxy server "proxy:70". + SessionDependencies session_deps(ProxyService::CreateFixed( + "https://proxy:70")); + CapturingBoundNetLog log(CapturingNetLog::kUnbounded); + session_deps.net_log = log.bound().net_log(); + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + // CONNECT to www.google.com:443 via SPDY + scoped_ptr<spdy::SpdyFrame> connect(ConstructSpdyConnect(NULL, 0, 1)); + // fetch https://www.google.com/ via HTTP + + const char get[] = "GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n\r\n"; + scoped_ptr<spdy::SpdyFrame> wrapped_get( + ConstructSpdyBodyFrame(1, get, strlen(get), false)); + scoped_ptr<spdy::SpdyFrame> conn_resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + const char resp[] = "HTTP/1.1 200 OK\r\n" + "Content-Length: 10\r\n\r\n"; + scoped_ptr<spdy::SpdyFrame> wrapped_get_resp( + ConstructSpdyBodyFrame(1, resp, strlen(resp), false)); + scoped_ptr<spdy::SpdyFrame> wrapped_body( + ConstructSpdyBodyFrame(1, "1234567890", 10, false)); + scoped_ptr<spdy::SpdyFrame> window_update( + ConstructSpdyWindowUpdate(1, wrapped_get_resp->length())); + + MockWrite spdy_writes[] = { + CreateMockWrite(*connect, 1), + CreateMockWrite(*wrapped_get, 3), + CreateMockWrite(*window_update, 5) + }; + + MockRead spdy_reads[] = { + CreateMockRead(*conn_resp, 2, ASYNC), + CreateMockRead(*wrapped_get_resp, 4, ASYNC), + CreateMockRead(*wrapped_body, 6, ASYNC), + CreateMockRead(*wrapped_body, 7, ASYNC), + MockRead(ASYNC, 0, 8), + }; + + scoped_ptr<OrderedSocketData> spdy_data( + new OrderedSocketData( + spdy_reads, arraysize(spdy_reads), + spdy_writes, arraysize(spdy_writes))); + session_deps.socket_factory.AddSocketDataProvider(spdy_data.get()); + + SSLSocketDataProvider ssl(ASYNC, OK); + ssl.SetNextProto(SSLClientSocket::kProtoSPDY2); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); + SSLSocketDataProvider ssl2(ASYNC, OK); + ssl2.was_npn_negotiated = false; + ssl2.protocol_negotiated = SSLClientSocket::kProtoUnknown; + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl2); + + TestCompletionCallback callback1; + + int rv = trans->Start(&request, callback1.callback(), log.bound()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + + std::string response_data; + ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data)); + EXPECT_EQ("1234567890", response_data); +} + +// Test a SPDY CONNECT through an HTTPS Proxy to a SPDY server. +TEST_F(HttpNetworkTransactionSpdy2Test, HttpsProxySpdyConnectSpdy) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("https://www.google.com/"); + request.load_flags = 0; + + // Configure against https proxy server "proxy:70". + SessionDependencies session_deps(ProxyService::CreateFixed( + "https://proxy:70")); + CapturingBoundNetLog log(CapturingNetLog::kUnbounded); + session_deps.net_log = log.bound().net_log(); + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + // CONNECT to www.google.com:443 via SPDY + scoped_ptr<spdy::SpdyFrame> connect(ConstructSpdyConnect(NULL, 0, 1)); + // fetch https://www.google.com/ via SPDY + const char* const kMyUrl = "https://www.google.com/"; + scoped_ptr<spdy::SpdyFrame> get(ConstructSpdyGet(kMyUrl, false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> wrapped_get(ConstructWrappedSpdyFrame(get, 1)); + scoped_ptr<spdy::SpdyFrame> conn_resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> get_resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> wrapped_get_resp( + ConstructWrappedSpdyFrame(get_resp, 1)); + scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true)); + scoped_ptr<spdy::SpdyFrame> wrapped_body(ConstructWrappedSpdyFrame(body, 1)); + scoped_ptr<spdy::SpdyFrame> window_update_get_resp( + ConstructSpdyWindowUpdate(1, wrapped_get_resp->length())); + scoped_ptr<spdy::SpdyFrame> window_update_body( + ConstructSpdyWindowUpdate(1, wrapped_body->length())); + + MockWrite spdy_writes[] = { + CreateMockWrite(*connect, 1), + CreateMockWrite(*wrapped_get, 3), + CreateMockWrite(*window_update_get_resp, 5), + CreateMockWrite(*window_update_body, 7), + }; + + MockRead spdy_reads[] = { + CreateMockRead(*conn_resp, 2, ASYNC), + CreateMockRead(*wrapped_get_resp, 4, ASYNC), + CreateMockRead(*wrapped_body, 6, ASYNC), + MockRead(ASYNC, 0, 8), + }; + + scoped_ptr<OrderedSocketData> spdy_data( + new OrderedSocketData( + spdy_reads, arraysize(spdy_reads), + spdy_writes, arraysize(spdy_writes))); + session_deps.socket_factory.AddSocketDataProvider(spdy_data.get()); + + SSLSocketDataProvider ssl(ASYNC, OK); + ssl.SetNextProto(SSLClientSocket::kProtoSPDY2); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); + SSLSocketDataProvider ssl2(ASYNC, OK); + ssl2.SetNextProto(SSLClientSocket::kProtoSPDY2); + ssl2.protocol_negotiated = SSLClientSocket::kProtoSPDY2; + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl2); + + TestCompletionCallback callback1; + + int rv = trans->Start(&request, callback1.callback(), log.bound()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + + std::string response_data; + ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data)); + EXPECT_EQ(kUploadData, response_data); +} + +// Test a SPDY CONNECT failure through an HTTPS Proxy. +TEST_F(HttpNetworkTransactionSpdy2Test, HttpsProxySpdyConnectFailure) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("https://www.google.com/"); + request.load_flags = 0; + + // Configure against https proxy server "proxy:70". + SessionDependencies session_deps(ProxyService::CreateFixed( + "https://proxy:70")); + CapturingBoundNetLog log(CapturingNetLog::kUnbounded); + session_deps.net_log = log.bound().net_log(); + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + // CONNECT to www.google.com:443 via SPDY + scoped_ptr<spdy::SpdyFrame> connect(ConstructSpdyConnect(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> get(ConstructSpdyRstStream(1, spdy::CANCEL)); + + MockWrite spdy_writes[] = { + CreateMockWrite(*connect, 1), + CreateMockWrite(*get, 3), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdySynReplyError(1)); + scoped_ptr<spdy::SpdyFrame> data(ConstructSpdyBodyFrame(1, true)); + MockRead spdy_reads[] = { + CreateMockRead(*resp, 2, ASYNC), + MockRead(ASYNC, 0, 4), + }; + + scoped_ptr<OrderedSocketData> spdy_data( + new OrderedSocketData( + spdy_reads, arraysize(spdy_reads), + spdy_writes, arraysize(spdy_writes))); + session_deps.socket_factory.AddSocketDataProvider(spdy_data.get()); + + SSLSocketDataProvider ssl(ASYNC, OK); + ssl.SetNextProto(SSLClientSocket::kProtoSPDY2); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); + SSLSocketDataProvider ssl2(ASYNC, OK); + ssl2.SetNextProto(SSLClientSocket::kProtoSPDY2); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl2); + + TestCompletionCallback callback1; + + int rv = trans->Start(&request, callback1.callback(), log.bound()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_EQ(500, response->headers->response_code()); +} + +// Test the challenge-response-retry sequence through an HTTPS Proxy +TEST_F(HttpNetworkTransactionSpdy2Test, HttpsProxyAuthRetry) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + // when the no authentication data flag is set. + request.load_flags = net::LOAD_DO_NOT_SEND_AUTH_DATA; + + // Configure against https proxy server "myproxy:70". + SessionDependencies session_deps( + ProxyService::CreateFixed("https://myproxy:70")); + CapturingBoundNetLog log(CapturingNetLog::kUnbounded); + session_deps.net_log = log.bound().net_log(); + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + // Since we have proxy, should use full url + MockWrite data_writes1[] = { + MockWrite("GET http://www.google.com/ HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n\r\n"), + + // After calling trans->RestartWithAuth(), this is the request we should + // be issuing -- the final header line contains the credentials. + MockWrite("GET http://www.google.com/ HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n" + "Proxy-Authorization: Basic Zm9vOmJhcg==\r\n\r\n"), + }; + + // The proxy responds to the GET with a 407, using a persistent + // connection. + MockRead data_reads1[] = { + // No credentials. + MockRead("HTTP/1.1 407 Proxy Authentication Required\r\n"), + MockRead("Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n"), + MockRead("Proxy-Connection: keep-alive\r\n"), + MockRead("Content-Length: 0\r\n\r\n"), + + MockRead("HTTP/1.1 200 OK\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), + MockRead("Content-Length: 100\r\n\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + + StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), + data_writes1, arraysize(data_writes1)); + session_deps.socket_factory.AddSocketDataProvider(&data1); + SSLSocketDataProvider ssl(ASYNC, OK); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); + + TestCompletionCallback callback1; + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + int rv = trans->Start(&request, callback1.callback(), log.bound()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_FALSE(response->headers == NULL); + EXPECT_EQ(407, response->headers->response_code()); + EXPECT_TRUE(HttpVersion(1, 1) == response->headers->GetHttpVersion()); + EXPECT_TRUE(CheckBasicProxyAuth(response->auth_challenge.get())); + + TestCompletionCallback callback2; + + rv = trans->RestartWithAuth( + AuthCredentials(kFoo, kBar), callback2.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback2.WaitForResult(); + EXPECT_EQ(OK, rv); + + response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + + EXPECT_TRUE(response->headers->IsKeepAlive()); + EXPECT_EQ(200, response->headers->response_code()); + EXPECT_EQ(100, response->headers->GetContentLength()); + EXPECT_TRUE(HttpVersion(1, 1) == response->headers->GetHttpVersion()); + + // The password prompt info should not be set. + EXPECT_TRUE(response->auth_challenge.get() == NULL); +} + +void HttpNetworkTransactionSpdy2Test::ConnectStatusHelperWithExpectedStatus( + const MockRead& status, int expected_status) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("https://www.google.com/"); + request.load_flags = 0; + + // Configure against proxy server "myproxy:70". + SessionDependencies session_deps(ProxyService::CreateFixed("myproxy:70")); + + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + // Since we have proxy, should try to establish tunnel. + MockWrite data_writes[] = { + MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n\r\n"), + }; + + MockRead data_reads[] = { + status, + MockRead("Content-Length: 10\r\n\r\n"), + // No response body because the test stops reading here. + MockRead(SYNCHRONOUS, ERR_UNEXPECTED), // Should not be reached. + }; + + StaticSocketDataProvider data(data_reads, arraysize(data_reads), + data_writes, arraysize(data_writes)); + session_deps.socket_factory.AddSocketDataProvider(&data); + + TestCompletionCallback callback; + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(expected_status, rv); +} + +void HttpNetworkTransactionSpdy2Test::ConnectStatusHelper( + const MockRead& status) { + ConnectStatusHelperWithExpectedStatus( + status, ERR_TUNNEL_CONNECTION_FAILED); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, ConnectStatus100) { + ConnectStatusHelper(MockRead("HTTP/1.1 100 Continue\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, ConnectStatus101) { + ConnectStatusHelper(MockRead("HTTP/1.1 101 Switching Protocols\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, ConnectStatus201) { + ConnectStatusHelper(MockRead("HTTP/1.1 201 Created\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, ConnectStatus202) { + ConnectStatusHelper(MockRead("HTTP/1.1 202 Accepted\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, ConnectStatus203) { + ConnectStatusHelper( + MockRead("HTTP/1.1 203 Non-Authoritative Information\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, ConnectStatus204) { + ConnectStatusHelper(MockRead("HTTP/1.1 204 No Content\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, ConnectStatus205) { + ConnectStatusHelper(MockRead("HTTP/1.1 205 Reset Content\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, ConnectStatus206) { + ConnectStatusHelper(MockRead("HTTP/1.1 206 Partial Content\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, ConnectStatus300) { + ConnectStatusHelper(MockRead("HTTP/1.1 300 Multiple Choices\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, ConnectStatus301) { + ConnectStatusHelper(MockRead("HTTP/1.1 301 Moved Permanently\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, ConnectStatus302) { + ConnectStatusHelper(MockRead("HTTP/1.1 302 Found\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, ConnectStatus303) { + ConnectStatusHelper(MockRead("HTTP/1.1 303 See Other\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, ConnectStatus304) { + ConnectStatusHelper(MockRead("HTTP/1.1 304 Not Modified\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, ConnectStatus305) { + ConnectStatusHelper(MockRead("HTTP/1.1 305 Use Proxy\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, ConnectStatus306) { + ConnectStatusHelper(MockRead("HTTP/1.1 306\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, ConnectStatus307) { + ConnectStatusHelper(MockRead("HTTP/1.1 307 Temporary Redirect\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, ConnectStatus400) { + ConnectStatusHelper(MockRead("HTTP/1.1 400 Bad Request\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, ConnectStatus401) { + ConnectStatusHelper(MockRead("HTTP/1.1 401 Unauthorized\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, ConnectStatus402) { + ConnectStatusHelper(MockRead("HTTP/1.1 402 Payment Required\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, ConnectStatus403) { + ConnectStatusHelper(MockRead("HTTP/1.1 403 Forbidden\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, ConnectStatus404) { + ConnectStatusHelper(MockRead("HTTP/1.1 404 Not Found\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, ConnectStatus405) { + ConnectStatusHelper(MockRead("HTTP/1.1 405 Method Not Allowed\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, ConnectStatus406) { + ConnectStatusHelper(MockRead("HTTP/1.1 406 Not Acceptable\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, ConnectStatus407) { + ConnectStatusHelperWithExpectedStatus( + MockRead("HTTP/1.1 407 Proxy Authentication Required\r\n"), + ERR_PROXY_AUTH_UNSUPPORTED); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, ConnectStatus408) { + ConnectStatusHelper(MockRead("HTTP/1.1 408 Request Timeout\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, ConnectStatus409) { + ConnectStatusHelper(MockRead("HTTP/1.1 409 Conflict\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, ConnectStatus410) { + ConnectStatusHelper(MockRead("HTTP/1.1 410 Gone\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, ConnectStatus411) { + ConnectStatusHelper(MockRead("HTTP/1.1 411 Length Required\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, ConnectStatus412) { + ConnectStatusHelper(MockRead("HTTP/1.1 412 Precondition Failed\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, ConnectStatus413) { + ConnectStatusHelper(MockRead("HTTP/1.1 413 Request Entity Too Large\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, ConnectStatus414) { + ConnectStatusHelper(MockRead("HTTP/1.1 414 Request-URI Too Long\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, ConnectStatus415) { + ConnectStatusHelper(MockRead("HTTP/1.1 415 Unsupported Media Type\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, ConnectStatus416) { + ConnectStatusHelper( + MockRead("HTTP/1.1 416 Requested Range Not Satisfiable\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, ConnectStatus417) { + ConnectStatusHelper(MockRead("HTTP/1.1 417 Expectation Failed\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, ConnectStatus500) { + ConnectStatusHelper(MockRead("HTTP/1.1 500 Internal Server Error\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, ConnectStatus501) { + ConnectStatusHelper(MockRead("HTTP/1.1 501 Not Implemented\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, ConnectStatus502) { + ConnectStatusHelper(MockRead("HTTP/1.1 502 Bad Gateway\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, ConnectStatus503) { + ConnectStatusHelper(MockRead("HTTP/1.1 503 Service Unavailable\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, ConnectStatus504) { + ConnectStatusHelper(MockRead("HTTP/1.1 504 Gateway Timeout\r\n")); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, ConnectStatus505) { + ConnectStatusHelper(MockRead("HTTP/1.1 505 HTTP Version Not Supported\r\n")); +} + +// Test the flow when both the proxy server AND origin server require +// authentication. Again, this uses basic auth for both since that is +// the simplest to mock. +TEST_F(HttpNetworkTransactionSpdy2Test, BasicAuthProxyThenServer) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + + SessionDependencies session_deps(ProxyService::CreateFixed("myproxy:70")); + + // Configure against proxy server "myproxy:70". + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction( + CreateSession(&session_deps))); + + MockWrite data_writes1[] = { + MockWrite("GET http://www.google.com/ HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n\r\n"), + }; + + MockRead data_reads1[] = { + MockRead("HTTP/1.0 407 Unauthorized\r\n"), + // Give a couple authenticate options (only the middle one is actually + // supported). + MockRead("Proxy-Authenticate: Basic invalid\r\n"), // Malformed. + MockRead("Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n"), + MockRead("Proxy-Authenticate: UNSUPPORTED realm=\"FOO\"\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), + // Large content-length -- won't matter, as connection will be reset. + MockRead("Content-Length: 10000\r\n\r\n"), + MockRead(SYNCHRONOUS, ERR_FAILED), + }; + + // After calling trans->RestartWithAuth() the first time, this is the + // request we should be issuing -- the final header line contains the + // proxy's credentials. + MockWrite data_writes2[] = { + MockWrite("GET http://www.google.com/ HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n" + "Proxy-Authorization: Basic Zm9vOmJhcg==\r\n\r\n"), + }; + + // Now the proxy server lets the request pass through to origin server. + // The origin server responds with a 401. + MockRead data_reads2[] = { + MockRead("HTTP/1.0 401 Unauthorized\r\n"), + // Note: We are using the same realm-name as the proxy server. This is + // completely valid, as realms are unique across hosts. + MockRead("WWW-Authenticate: Basic realm=\"MyRealm1\"\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), + MockRead("Content-Length: 2000\r\n\r\n"), + MockRead(SYNCHRONOUS, ERR_FAILED), // Won't be reached. + }; + + // After calling trans->RestartWithAuth() the second time, we should send + // the credentials for both the proxy and origin server. + MockWrite data_writes3[] = { + MockWrite("GET http://www.google.com/ HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n" + "Proxy-Authorization: Basic Zm9vOmJhcg==\r\n" + "Authorization: Basic Zm9vMjpiYXIy\r\n\r\n"), + }; + + // Lastly we get the desired content. + MockRead data_reads3[] = { + MockRead("HTTP/1.0 200 OK\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), + MockRead("Content-Length: 100\r\n\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + + StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), + data_writes1, arraysize(data_writes1)); + StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2), + data_writes2, arraysize(data_writes2)); + StaticSocketDataProvider data3(data_reads3, arraysize(data_reads3), + data_writes3, arraysize(data_writes3)); + session_deps.socket_factory.AddSocketDataProvider(&data1); + session_deps.socket_factory.AddSocketDataProvider(&data2); + session_deps.socket_factory.AddSocketDataProvider(&data3); + + TestCompletionCallback callback1; + + int rv = trans->Start(&request, callback1.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(CheckBasicProxyAuth(response->auth_challenge.get())); + + TestCompletionCallback callback2; + + rv = trans->RestartWithAuth( + AuthCredentials(kFoo, kBar), callback2.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback2.WaitForResult(); + EXPECT_EQ(OK, rv); + + response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(CheckBasicServerAuth(response->auth_challenge.get())); + + TestCompletionCallback callback3; + + rv = trans->RestartWithAuth( + AuthCredentials(kFoo2, kBar2), callback3.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback3.WaitForResult(); + EXPECT_EQ(OK, rv); + + response = trans->GetResponseInfo(); + EXPECT_TRUE(response->auth_challenge.get() == NULL); + EXPECT_EQ(100, response->headers->GetContentLength()); +} + +// For the NTLM implementation using SSPI, we skip the NTLM tests since we +// can't hook into its internals to cause it to generate predictable NTLM +// authorization headers. +#if defined(NTLM_PORTABLE) +// The NTLM authentication unit tests were generated by capturing the HTTP +// requests and responses using Fiddler 2 and inspecting the generated random +// bytes in the debugger. + +// Enter the correct password and authenticate successfully. +TEST_F(HttpNetworkTransactionSpdy2Test, NTLMAuth1) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://172.22.68.17/kids/login.aspx"); + request.load_flags = 0; + + HttpAuthHandlerNTLM::ScopedProcSetter proc_setter(MockGenerateRandom1, + MockGetHostName); + SessionDependencies session_deps; + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + MockWrite data_writes1[] = { + MockWrite("GET /kids/login.aspx HTTP/1.1\r\n" + "Host: 172.22.68.17\r\n" + "Connection: keep-alive\r\n\r\n"), + }; + + MockRead data_reads1[] = { + MockRead("HTTP/1.1 401 Access Denied\r\n"), + // Negotiate and NTLM are often requested together. However, we only want + // to test NTLM. Since Negotiate is preferred over NTLM, we have to skip + // the header that requests Negotiate for this test. + MockRead("WWW-Authenticate: NTLM\r\n"), + MockRead("Connection: close\r\n"), + MockRead("Content-Length: 42\r\n"), + MockRead("Content-Type: text/html\r\n\r\n"), + // Missing content -- won't matter, as connection will be reset. + MockRead(SYNCHRONOUS, ERR_UNEXPECTED), + }; + + MockWrite data_writes2[] = { + // After restarting with a null identity, this is the + // request we should be issuing -- the final header line contains a Type + // 1 message. + MockWrite("GET /kids/login.aspx HTTP/1.1\r\n" + "Host: 172.22.68.17\r\n" + "Connection: keep-alive\r\n" + "Authorization: NTLM " + "TlRMTVNTUAABAAAAB4IIAAAAAAAAAAAAAAAAAAAAAAA=\r\n\r\n"), + + // After calling trans->RestartWithAuth(), we should send a Type 3 message + // (the credentials for the origin server). The second request continues + // on the same connection. + MockWrite("GET /kids/login.aspx HTTP/1.1\r\n" + "Host: 172.22.68.17\r\n" + "Connection: keep-alive\r\n" + "Authorization: NTLM TlRMTVNTUAADAAAAGAAYAGgAAAAYABgAgA" + "AAAAAAAABAAAAAGAAYAEAAAAAQABAAWAAAAAAAAAAAAAAABYIIAHQA" + "ZQBzAHQAaQBuAGcALQBuAHQAbABtAFcAVABDAC0AVwBJAE4ANwBVKW" + "Yma5xzVAAAAAAAAAAAAAAAAAAAAACH+gWcm+YsP9Tqb9zCR3WAeZZX" + "ahlhx5I=\r\n\r\n"), + }; + + MockRead data_reads2[] = { + // The origin server responds with a Type 2 message. + MockRead("HTTP/1.1 401 Access Denied\r\n"), + MockRead("WWW-Authenticate: NTLM " + "TlRMTVNTUAACAAAADAAMADgAAAAFgokCjGpMpPGlYKkAAAAAAAAAALo" + "AugBEAAAABQEoCgAAAA9HAE8ATwBHAEwARQACAAwARwBPAE8ARwBMAE" + "UAAQAaAEEASwBFAEUAUwBBAFIAQQAtAEMATwBSAFAABAAeAGMAbwByA" + "HAALgBnAG8AbwBnAGwAZQAuAGMAbwBtAAMAQABhAGsAZQBlAHMAYQBy" + "AGEALQBjAG8AcgBwAC4AYQBkAC4AYwBvAHIAcAAuAGcAbwBvAGcAbAB" + "lAC4AYwBvAG0ABQAeAGMAbwByAHAALgBnAG8AbwBnAGwAZQAuAGMAbw" + "BtAAAAAAA=\r\n"), + MockRead("Content-Length: 42\r\n"), + MockRead("Content-Type: text/html\r\n\r\n"), + MockRead("You are not authorized to view this page\r\n"), + + // Lastly we get the desired content. + MockRead("HTTP/1.1 200 OK\r\n"), + MockRead("Content-Type: text/html; charset=utf-8\r\n"), + MockRead("Content-Length: 13\r\n\r\n"), + MockRead("Please Login\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + + StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), + data_writes1, arraysize(data_writes1)); + StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2), + data_writes2, arraysize(data_writes2)); + session_deps.socket_factory.AddSocketDataProvider(&data1); + session_deps.socket_factory.AddSocketDataProvider(&data2); + + TestCompletionCallback callback1; + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + int rv = trans->Start(&request, callback1.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(OK, rv); + + EXPECT_FALSE(trans->IsReadyToRestartForAuth()); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_FALSE(response == NULL); + EXPECT_TRUE(CheckNTLMServerAuth(response->auth_challenge.get())); + + TestCompletionCallback callback2; + + rv = trans->RestartWithAuth(AuthCredentials(kTestingNTLM, kTestingNTLM), + callback2.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback2.WaitForResult(); + EXPECT_EQ(OK, rv); + + EXPECT_TRUE(trans->IsReadyToRestartForAuth()); + + response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(response->auth_challenge.get() == NULL); + + TestCompletionCallback callback3; + + rv = trans->RestartWithAuth(AuthCredentials(), callback3.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback3.WaitForResult(); + EXPECT_EQ(OK, rv); + + response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(response->auth_challenge.get() == NULL); + EXPECT_EQ(13, response->headers->GetContentLength()); +} + +// Enter a wrong password, and then the correct one. +TEST_F(HttpNetworkTransactionSpdy2Test, NTLMAuth2) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://172.22.68.17/kids/login.aspx"); + request.load_flags = 0; + + HttpAuthHandlerNTLM::ScopedProcSetter proc_setter(MockGenerateRandom2, + MockGetHostName); + SessionDependencies session_deps; + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + MockWrite data_writes1[] = { + MockWrite("GET /kids/login.aspx HTTP/1.1\r\n" + "Host: 172.22.68.17\r\n" + "Connection: keep-alive\r\n\r\n"), + }; + + MockRead data_reads1[] = { + MockRead("HTTP/1.1 401 Access Denied\r\n"), + // Negotiate and NTLM are often requested together. However, we only want + // to test NTLM. Since Negotiate is preferred over NTLM, we have to skip + // the header that requests Negotiate for this test. + MockRead("WWW-Authenticate: NTLM\r\n"), + MockRead("Connection: close\r\n"), + MockRead("Content-Length: 42\r\n"), + MockRead("Content-Type: text/html\r\n\r\n"), + // Missing content -- won't matter, as connection will be reset. + MockRead(SYNCHRONOUS, ERR_UNEXPECTED), + }; + + MockWrite data_writes2[] = { + // After restarting with a null identity, this is the + // request we should be issuing -- the final header line contains a Type + // 1 message. + MockWrite("GET /kids/login.aspx HTTP/1.1\r\n" + "Host: 172.22.68.17\r\n" + "Connection: keep-alive\r\n" + "Authorization: NTLM " + "TlRMTVNTUAABAAAAB4IIAAAAAAAAAAAAAAAAAAAAAAA=\r\n\r\n"), + + // After calling trans->RestartWithAuth(), we should send a Type 3 message + // (the credentials for the origin server). The second request continues + // on the same connection. + MockWrite("GET /kids/login.aspx HTTP/1.1\r\n" + "Host: 172.22.68.17\r\n" + "Connection: keep-alive\r\n" + "Authorization: NTLM TlRMTVNTUAADAAAAGAAYAGgAAAAYABgAgA" + "AAAAAAAABAAAAAGAAYAEAAAAAQABAAWAAAAAAAAAAAAAAABYIIAHQA" + "ZQBzAHQAaQBuAGcALQBuAHQAbABtAFcAVABDAC0AVwBJAE4ANwCWeY" + "XnSZNwoQAAAAAAAAAAAAAAAAAAAADLa34/phTTKzNTWdub+uyFleOj" + "4Ww7b7E=\r\n\r\n"), + }; + + MockRead data_reads2[] = { + // The origin server responds with a Type 2 message. + MockRead("HTTP/1.1 401 Access Denied\r\n"), + MockRead("WWW-Authenticate: NTLM " + "TlRMTVNTUAACAAAADAAMADgAAAAFgokCbVWUZezVGpAAAAAAAAAAALo" + "AugBEAAAABQEoCgAAAA9HAE8ATwBHAEwARQACAAwARwBPAE8ARwBMAE" + "UAAQAaAEEASwBFAEUAUwBBAFIAQQAtAEMATwBSAFAABAAeAGMAbwByA" + "HAALgBnAG8AbwBnAGwAZQAuAGMAbwBtAAMAQABhAGsAZQBlAHMAYQBy" + "AGEALQBjAG8AcgBwAC4AYQBkAC4AYwBvAHIAcAAuAGcAbwBvAGcAbAB" + "lAC4AYwBvAG0ABQAeAGMAbwByAHAALgBnAG8AbwBnAGwAZQAuAGMAbw" + "BtAAAAAAA=\r\n"), + MockRead("Content-Length: 42\r\n"), + MockRead("Content-Type: text/html\r\n\r\n"), + MockRead("You are not authorized to view this page\r\n"), + + // Wrong password. + MockRead("HTTP/1.1 401 Access Denied\r\n"), + MockRead("WWW-Authenticate: NTLM\r\n"), + MockRead("Connection: close\r\n"), + MockRead("Content-Length: 42\r\n"), + MockRead("Content-Type: text/html\r\n\r\n"), + // Missing content -- won't matter, as connection will be reset. + MockRead(SYNCHRONOUS, ERR_UNEXPECTED), + }; + + MockWrite data_writes3[] = { + // After restarting with a null identity, this is the + // request we should be issuing -- the final header line contains a Type + // 1 message. + MockWrite("GET /kids/login.aspx HTTP/1.1\r\n" + "Host: 172.22.68.17\r\n" + "Connection: keep-alive\r\n" + "Authorization: NTLM " + "TlRMTVNTUAABAAAAB4IIAAAAAAAAAAAAAAAAAAAAAAA=\r\n\r\n"), + + // After calling trans->RestartWithAuth(), we should send a Type 3 message + // (the credentials for the origin server). The second request continues + // on the same connection. + MockWrite("GET /kids/login.aspx HTTP/1.1\r\n" + "Host: 172.22.68.17\r\n" + "Connection: keep-alive\r\n" + "Authorization: NTLM TlRMTVNTUAADAAAAGAAYAGgAAAAYABgAgA" + "AAAAAAAABAAAAAGAAYAEAAAAAQABAAWAAAAAAAAAAAAAAABYIIAHQA" + "ZQBzAHQAaQBuAGcALQBuAHQAbABtAFcAVABDAC0AVwBJAE4ANwBO54" + "dFMVvTHwAAAAAAAAAAAAAAAAAAAACS7sT6Uzw7L0L//WUqlIaVWpbI" + "+4MUm7c=\r\n\r\n"), + }; + + MockRead data_reads3[] = { + // The origin server responds with a Type 2 message. + MockRead("HTTP/1.1 401 Access Denied\r\n"), + MockRead("WWW-Authenticate: NTLM " + "TlRMTVNTUAACAAAADAAMADgAAAAFgokCL24VN8dgOR8AAAAAAAAAALo" + "AugBEAAAABQEoCgAAAA9HAE8ATwBHAEwARQACAAwARwBPAE8ARwBMAE" + "UAAQAaAEEASwBFAEUAUwBBAFIAQQAtAEMATwBSAFAABAAeAGMAbwByA" + "HAALgBnAG8AbwBnAGwAZQAuAGMAbwBtAAMAQABhAGsAZQBlAHMAYQBy" + "AGEALQBjAG8AcgBwAC4AYQBkAC4AYwBvAHIAcAAuAGcAbwBvAGcAbAB" + "lAC4AYwBvAG0ABQAeAGMAbwByAHAALgBnAG8AbwBnAGwAZQAuAGMAbw" + "BtAAAAAAA=\r\n"), + MockRead("Content-Length: 42\r\n"), + MockRead("Content-Type: text/html\r\n\r\n"), + MockRead("You are not authorized to view this page\r\n"), + + // Lastly we get the desired content. + MockRead("HTTP/1.1 200 OK\r\n"), + MockRead("Content-Type: text/html; charset=utf-8\r\n"), + MockRead("Content-Length: 13\r\n\r\n"), + MockRead("Please Login\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + + StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), + data_writes1, arraysize(data_writes1)); + StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2), + data_writes2, arraysize(data_writes2)); + StaticSocketDataProvider data3(data_reads3, arraysize(data_reads3), + data_writes3, arraysize(data_writes3)); + session_deps.socket_factory.AddSocketDataProvider(&data1); + session_deps.socket_factory.AddSocketDataProvider(&data2); + session_deps.socket_factory.AddSocketDataProvider(&data3); + + TestCompletionCallback callback1; + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + int rv = trans->Start(&request, callback1.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(OK, rv); + + EXPECT_FALSE(trans->IsReadyToRestartForAuth()); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(CheckNTLMServerAuth(response->auth_challenge.get())); + + TestCompletionCallback callback2; + + // Enter the wrong password. + rv = trans->RestartWithAuth(AuthCredentials(kTestingNTLM, kWrongPassword), + callback2.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback2.WaitForResult(); + EXPECT_EQ(OK, rv); + + EXPECT_TRUE(trans->IsReadyToRestartForAuth()); + TestCompletionCallback callback3; + rv = trans->RestartWithAuth(AuthCredentials(), callback3.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback3.WaitForResult(); + EXPECT_EQ(OK, rv); + EXPECT_FALSE(trans->IsReadyToRestartForAuth()); + + response = trans->GetResponseInfo(); + ASSERT_FALSE(response == NULL); + EXPECT_TRUE(CheckNTLMServerAuth(response->auth_challenge.get())); + + TestCompletionCallback callback4; + + // Now enter the right password. + rv = trans->RestartWithAuth(AuthCredentials(kTestingNTLM, kTestingNTLM), + callback4.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback4.WaitForResult(); + EXPECT_EQ(OK, rv); + + EXPECT_TRUE(trans->IsReadyToRestartForAuth()); + + TestCompletionCallback callback5; + + // One more roundtrip + rv = trans->RestartWithAuth(AuthCredentials(), callback5.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback5.WaitForResult(); + EXPECT_EQ(OK, rv); + + response = trans->GetResponseInfo(); + EXPECT_TRUE(response->auth_challenge.get() == NULL); + EXPECT_EQ(13, response->headers->GetContentLength()); +} +#endif // NTLM_PORTABLE + +// Test reading a server response which has only headers, and no body. +// After some maximum number of bytes is consumed, the transaction should +// fail with ERR_RESPONSE_HEADERS_TOO_BIG. +TEST_F(HttpNetworkTransactionSpdy2Test, LargeHeadersNoBody) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + + SessionDependencies session_deps; + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + // Respond with 300 kb of headers (we should fail after 256 kb). + std::string large_headers_string; + FillLargeHeadersString(&large_headers_string, 300 * 1024); + + MockRead data_reads[] = { + MockRead("HTTP/1.0 200 OK\r\n"), + MockRead(ASYNC, large_headers_string.data(), large_headers_string.size()), + MockRead("\r\nBODY"), + MockRead(SYNCHRONOUS, OK), + }; + StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&data); + + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(ERR_RESPONSE_HEADERS_TOO_BIG, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + EXPECT_TRUE(response == NULL); +} + +// Make sure that we don't try to reuse a TCPClientSocket when failing to +// establish tunnel. +// http://code.google.com/p/chromium/issues/detail?id=3772 +TEST_F(HttpNetworkTransactionSpdy2Test, + DontRecycleTransportSocketForSSLTunnel) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("https://www.google.com/"); + request.load_flags = 0; + + // Configure against proxy server "myproxy:70". + SessionDependencies session_deps(ProxyService::CreateFixed("myproxy:70")); + + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + // Since we have proxy, should try to establish tunnel. + MockWrite data_writes1[] = { + MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n\r\n"), + }; + + // The proxy responds to the connect with a 404, using a persistent + // connection. Usually a proxy would return 501 (not implemented), + // or 200 (tunnel established). + MockRead data_reads1[] = { + MockRead("HTTP/1.1 404 Not Found\r\n"), + MockRead("Content-Length: 10\r\n\r\n"), + MockRead(SYNCHRONOUS, ERR_UNEXPECTED), // Should not be reached. + }; + + StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), + data_writes1, arraysize(data_writes1)); + session_deps.socket_factory.AddSocketDataProvider(&data1); + + TestCompletionCallback callback1; + + int rv = trans->Start(&request, callback1.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(ERR_TUNNEL_CONNECTION_FAILED, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + EXPECT_TRUE(response == NULL); + + // Empty the current queue. This is necessary because idle sockets are + // added to the connection pool asynchronously with a PostTask. + MessageLoop::current()->RunAllPending(); + + // We now check to make sure the TCPClientSocket was not added back to + // the pool. + EXPECT_EQ(0, session->GetTransportSocketPool()->IdleSocketCount()); + trans.reset(); + MessageLoop::current()->RunAllPending(); + // Make sure that the socket didn't get recycled after calling the destructor. + EXPECT_EQ(0, session->GetTransportSocketPool()->IdleSocketCount()); +} + +// Make sure that we recycle a socket after reading all of the response body. +TEST_F(HttpNetworkTransactionSpdy2Test, RecycleSocket) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + + SessionDependencies session_deps; + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + MockRead data_reads[] = { + // A part of the response body is received with the response headers. + MockRead("HTTP/1.1 200 OK\r\nContent-Length: 11\r\n\r\nhel"), + // The rest of the response body is received in two parts. + MockRead("lo"), + MockRead(" world"), + MockRead("junk"), // Should not be read!! + MockRead(SYNCHRONOUS, OK), + }; + + StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&data); + + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + + EXPECT_TRUE(response->headers != NULL); + std::string status_line = response->headers->GetStatusLine(); + EXPECT_EQ("HTTP/1.1 200 OK", status_line); + + EXPECT_EQ(0, session->GetTransportSocketPool()->IdleSocketCount()); + + std::string response_data; + rv = ReadTransaction(trans.get(), &response_data); + EXPECT_EQ(OK, rv); + EXPECT_EQ("hello world", response_data); + + // Empty the current queue. This is necessary because idle sockets are + // added to the connection pool asynchronously with a PostTask. + MessageLoop::current()->RunAllPending(); + + // We now check to make sure the socket was added back to the pool. + EXPECT_EQ(1, session->GetTransportSocketPool()->IdleSocketCount()); +} + +// Make sure that we recycle a SSL socket after reading all of the response +// body. +TEST_F(HttpNetworkTransactionSpdy2Test, RecycleSSLSocket) { + SessionDependencies session_deps; + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("https://www.google.com/"); + request.load_flags = 0; + + MockWrite data_writes[] = { + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n\r\n"), + }; + + MockRead data_reads[] = { + MockRead("HTTP/1.1 200 OK\r\n"), + MockRead("Content-Length: 11\r\n\r\n"), + MockRead("hello world"), + MockRead(SYNCHRONOUS, OK), + }; + + SSLSocketDataProvider ssl(ASYNC, OK); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); + + StaticSocketDataProvider data(data_reads, arraysize(data_reads), + data_writes, arraysize(data_writes)); + session_deps.socket_factory.AddSocketDataProvider(&data); + + TestCompletionCallback callback; + + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_EQ(OK, callback.WaitForResult()); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + + EXPECT_EQ(0, session->GetTransportSocketPool()->IdleSocketCount()); + + std::string response_data; + rv = ReadTransaction(trans.get(), &response_data); + EXPECT_EQ(OK, rv); + EXPECT_EQ("hello world", response_data); + + // Empty the current queue. This is necessary because idle sockets are + // added to the connection pool asynchronously with a PostTask. + MessageLoop::current()->RunAllPending(); + + // We now check to make sure the socket was added back to the pool. + EXPECT_EQ(1, session->GetSSLSocketPool()->IdleSocketCount()); +} + +// Grab a SSL socket, use it, and put it back into the pool. Then, reuse it +// from the pool and make sure that we recover okay. +TEST_F(HttpNetworkTransactionSpdy2Test, RecycleDeadSSLSocket) { + SessionDependencies session_deps; + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("https://www.google.com/"); + request.load_flags = 0; + + MockWrite data_writes[] = { + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n\r\n"), + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n\r\n"), + }; + + MockRead data_reads[] = { + MockRead("HTTP/1.1 200 OK\r\n"), + MockRead("Content-Length: 11\r\n\r\n"), + MockRead(SYNCHRONOUS, ERR_TEST_PEER_CLOSE_AFTER_NEXT_MOCK_READ), + MockRead("hello world"), + MockRead(ASYNC, 0, 0) // EOF + }; + + SSLSocketDataProvider ssl(ASYNC, OK); + SSLSocketDataProvider ssl2(ASYNC, OK); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl2); + + StaticSocketDataProvider data(data_reads, arraysize(data_reads), + data_writes, arraysize(data_writes)); + StaticSocketDataProvider data2(data_reads, arraysize(data_reads), + data_writes, arraysize(data_writes)); + session_deps.socket_factory.AddSocketDataProvider(&data); + session_deps.socket_factory.AddSocketDataProvider(&data2); + + TestCompletionCallback callback; + + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_EQ(OK, callback.WaitForResult()); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + + EXPECT_EQ(0, session->GetTransportSocketPool()->IdleSocketCount()); + + std::string response_data; + rv = ReadTransaction(trans.get(), &response_data); + EXPECT_EQ(OK, rv); + EXPECT_EQ("hello world", response_data); + + // Empty the current queue. This is necessary because idle sockets are + // added to the connection pool asynchronously with a PostTask. + MessageLoop::current()->RunAllPending(); + + // We now check to make sure the socket was added back to the pool. + EXPECT_EQ(1, session->GetSSLSocketPool()->IdleSocketCount()); + + // Now start the second transaction, which should reuse the previous socket. + + trans.reset(new HttpNetworkTransaction(session)); + + rv = trans->Start(&request, callback.callback(), BoundNetLog()); + + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_EQ(OK, callback.WaitForResult()); + + response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + + EXPECT_EQ(0, session->GetTransportSocketPool()->IdleSocketCount()); + + rv = ReadTransaction(trans.get(), &response_data); + EXPECT_EQ(OK, rv); + EXPECT_EQ("hello world", response_data); + + // Empty the current queue. This is necessary because idle sockets are + // added to the connection pool asynchronously with a PostTask. + MessageLoop::current()->RunAllPending(); + + // We now check to make sure the socket was added back to the pool. + EXPECT_EQ(1, session->GetSSLSocketPool()->IdleSocketCount()); +} + +// Make sure that we recycle a socket after a zero-length response. +// http://crbug.com/9880 +TEST_F(HttpNetworkTransactionSpdy2Test, RecycleSocketAfterZeroContentLength) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/csi?v=3&s=web&action=&" + "tran=undefined&ei=mAXcSeegAo-SMurloeUN&" + "e=17259,18167,19592,19773,19981,20133,20173,20233&" + "rt=prt.2642,ol.2649,xjs.2951"); + request.load_flags = 0; + + SessionDependencies session_deps; + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + MockRead data_reads[] = { + MockRead("HTTP/1.1 204 No Content\r\n" + "Content-Length: 0\r\n" + "Content-Type: text/html\r\n\r\n"), + MockRead("junk"), // Should not be read!! + MockRead(SYNCHRONOUS, OK), + }; + + StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&data); + + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + + EXPECT_TRUE(response->headers != NULL); + std::string status_line = response->headers->GetStatusLine(); + EXPECT_EQ("HTTP/1.1 204 No Content", status_line); + + EXPECT_EQ(0, session->GetTransportSocketPool()->IdleSocketCount()); + + std::string response_data; + rv = ReadTransaction(trans.get(), &response_data); + EXPECT_EQ(OK, rv); + EXPECT_EQ("", response_data); + + // Empty the current queue. This is necessary because idle sockets are + // added to the connection pool asynchronously with a PostTask. + MessageLoop::current()->RunAllPending(); + + // We now check to make sure the socket was added back to the pool. + EXPECT_EQ(1, session->GetTransportSocketPool()->IdleSocketCount()); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, ResendRequestOnWriteBodyError) { + HttpRequestInfo request[2]; + // Transaction 1: a GET request that succeeds. The socket is recycled + // after use. + request[0].method = "GET"; + request[0].url = GURL("http://www.google.com/"); + request[0].load_flags = 0; + // Transaction 2: a POST request. Reuses the socket kept alive from + // transaction 1. The first attempts fails when writing the POST data. + // This causes the transaction to retry with a new socket. The second + // attempt succeeds. + request[1].method = "POST"; + request[1].url = GURL("http://www.google.com/login.cgi"); + request[1].upload_data = new UploadData; + request[1].upload_data->AppendBytes("foo", 3); + request[1].load_flags = 0; + + SessionDependencies session_deps; + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + // The first socket is used for transaction 1 and the first attempt of + // transaction 2. + + // The response of transaction 1. + MockRead data_reads1[] = { + MockRead("HTTP/1.1 200 OK\r\nContent-Length: 11\r\n\r\n"), + MockRead("hello world"), + MockRead(SYNCHRONOUS, OK), + }; + // The mock write results of transaction 1 and the first attempt of + // transaction 2. + MockWrite data_writes1[] = { + MockWrite(SYNCHRONOUS, 64), // GET + MockWrite(SYNCHRONOUS, 93), // POST + MockWrite(SYNCHRONOUS, ERR_CONNECTION_ABORTED), // POST data + }; + StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), + data_writes1, arraysize(data_writes1)); + + // The second socket is used for the second attempt of transaction 2. + + // The response of transaction 2. + MockRead data_reads2[] = { + MockRead("HTTP/1.1 200 OK\r\nContent-Length: 7\r\n\r\n"), + MockRead("welcome"), + MockRead(SYNCHRONOUS, OK), + }; + // The mock write results of the second attempt of transaction 2. + MockWrite data_writes2[] = { + MockWrite(SYNCHRONOUS, 93), // POST + MockWrite(SYNCHRONOUS, 3), // POST data + }; + StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2), + data_writes2, arraysize(data_writes2)); + + session_deps.socket_factory.AddSocketDataProvider(&data1); + session_deps.socket_factory.AddSocketDataProvider(&data2); + + const char* kExpectedResponseData[] = { + "hello world", "welcome" + }; + + for (int i = 0; i < 2; ++i) { + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(session)); + + TestCompletionCallback callback; + + int rv = trans->Start(&request[i], callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + + EXPECT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + + std::string response_data; + rv = ReadTransaction(trans.get(), &response_data); + EXPECT_EQ(OK, rv); + EXPECT_EQ(kExpectedResponseData[i], response_data); + } +} + +// Test the request-challenge-retry sequence for basic auth when there is +// an identity in the URL. The request should be sent as normal, but when +// it fails the identity from the URL is no longer used. +TEST_F(HttpNetworkTransactionSpdy2Test, IgnoreAuthIdentityInURL) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://foo:b@r@www.google.com/"); + request.load_flags = LOAD_NORMAL; + + SessionDependencies session_deps; + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + // The password contains an escaped character -- for this test to pass it + // will need to be unescaped by HttpNetworkTransaction. + EXPECT_EQ("b%40r", request.url.password()); + + MockWrite data_writes1[] = { + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n\r\n"), + }; + + MockRead data_reads1[] = { + MockRead("HTTP/1.0 401 Unauthorized\r\n"), + MockRead("WWW-Authenticate: Basic realm=\"MyRealm1\"\r\n"), + MockRead("Content-Length: 10\r\n\r\n"), + MockRead(SYNCHRONOUS, ERR_FAILED), + }; + + StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), + data_writes1, arraysize(data_writes1)); + session_deps.socket_factory.AddSocketDataProvider(&data1); + + TestCompletionCallback callback1; + int rv = trans->Start(&request, callback1.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback1.WaitForResult(); + EXPECT_EQ(OK, rv); + EXPECT_FALSE(trans->IsReadyToRestartForAuth()); + + // Empty the current queue. + MessageLoop::current()->RunAllPending(); +} + +// Test that previously tried username/passwords for a realm get re-used. +TEST_F(HttpNetworkTransactionSpdy2Test, BasicAuthCacheAndPreauth) { + SessionDependencies session_deps; + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + // Transaction 1: authenticate (foo, bar) on MyRealm1 + { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/x/y/z"); + request.load_flags = 0; + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + MockWrite data_writes1[] = { + MockWrite("GET /x/y/z HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n\r\n"), + }; + + MockRead data_reads1[] = { + MockRead("HTTP/1.0 401 Unauthorized\r\n"), + MockRead("WWW-Authenticate: Basic realm=\"MyRealm1\"\r\n"), + MockRead("Content-Length: 10000\r\n\r\n"), + MockRead(SYNCHRONOUS, ERR_FAILED), + }; + + // Resend with authorization (username=foo, password=bar) + MockWrite data_writes2[] = { + MockWrite("GET /x/y/z HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n" + "Authorization: Basic Zm9vOmJhcg==\r\n\r\n"), + }; + + // Sever accepts the authorization. + MockRead data_reads2[] = { + MockRead("HTTP/1.0 200 OK\r\n"), + MockRead("Content-Length: 100\r\n\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + + StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), + data_writes1, arraysize(data_writes1)); + StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2), + data_writes2, arraysize(data_writes2)); + session_deps.socket_factory.AddSocketDataProvider(&data1); + session_deps.socket_factory.AddSocketDataProvider(&data2); + + TestCompletionCallback callback1; + + int rv = trans->Start(&request, callback1.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(CheckBasicServerAuth(response->auth_challenge.get())); + + TestCompletionCallback callback2; + + rv = trans->RestartWithAuth( + AuthCredentials(kFoo, kBar), callback2.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback2.WaitForResult(); + EXPECT_EQ(OK, rv); + + response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(response->auth_challenge.get() == NULL); + EXPECT_EQ(100, response->headers->GetContentLength()); + } + + // ------------------------------------------------------------------------ + + // Transaction 2: authenticate (foo2, bar2) on MyRealm2 + { + HttpRequestInfo request; + request.method = "GET"; + // Note that Transaction 1 was at /x/y/z, so this is in the same + // protection space as MyRealm1. + request.url = GURL("http://www.google.com/x/y/a/b"); + request.load_flags = 0; + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + MockWrite data_writes1[] = { + MockWrite("GET /x/y/a/b HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n" + // Send preemptive authorization for MyRealm1 + "Authorization: Basic Zm9vOmJhcg==\r\n\r\n"), + }; + + // The server didn't like the preemptive authorization, and + // challenges us for a different realm (MyRealm2). + MockRead data_reads1[] = { + MockRead("HTTP/1.0 401 Unauthorized\r\n"), + MockRead("WWW-Authenticate: Basic realm=\"MyRealm2\"\r\n"), + MockRead("Content-Length: 10000\r\n\r\n"), + MockRead(SYNCHRONOUS, ERR_FAILED), + }; + + // Resend with authorization for MyRealm2 (username=foo2, password=bar2) + MockWrite data_writes2[] = { + MockWrite("GET /x/y/a/b HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n" + "Authorization: Basic Zm9vMjpiYXIy\r\n\r\n"), + }; + + // Sever accepts the authorization. + MockRead data_reads2[] = { + MockRead("HTTP/1.0 200 OK\r\n"), + MockRead("Content-Length: 100\r\n\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + + StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), + data_writes1, arraysize(data_writes1)); + StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2), + data_writes2, arraysize(data_writes2)); + session_deps.socket_factory.AddSocketDataProvider(&data1); + session_deps.socket_factory.AddSocketDataProvider(&data2); + + TestCompletionCallback callback1; + + int rv = trans->Start(&request, callback1.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->auth_challenge.get()); + EXPECT_FALSE(response->auth_challenge->is_proxy); + EXPECT_EQ("www.google.com:80", + response->auth_challenge->challenger.ToString()); + EXPECT_EQ("MyRealm2", response->auth_challenge->realm); + EXPECT_EQ("basic", response->auth_challenge->scheme); + + TestCompletionCallback callback2; + + rv = trans->RestartWithAuth( + AuthCredentials(kFoo2, kBar2), callback2.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback2.WaitForResult(); + EXPECT_EQ(OK, rv); + + response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(response->auth_challenge.get() == NULL); + EXPECT_EQ(100, response->headers->GetContentLength()); + } + + // ------------------------------------------------------------------------ + + // Transaction 3: Resend a request in MyRealm's protection space -- + // succeed with preemptive authorization. + { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/x/y/z2"); + request.load_flags = 0; + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + MockWrite data_writes1[] = { + MockWrite("GET /x/y/z2 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n" + // The authorization for MyRealm1 gets sent preemptively + // (since the url is in the same protection space) + "Authorization: Basic Zm9vOmJhcg==\r\n\r\n"), + }; + + // Sever accepts the preemptive authorization + MockRead data_reads1[] = { + MockRead("HTTP/1.0 200 OK\r\n"), + MockRead("Content-Length: 100\r\n\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + + StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), + data_writes1, arraysize(data_writes1)); + session_deps.socket_factory.AddSocketDataProvider(&data1); + + TestCompletionCallback callback1; + + int rv = trans->Start(&request, callback1.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + + EXPECT_TRUE(response->auth_challenge.get() == NULL); + EXPECT_EQ(100, response->headers->GetContentLength()); + } + + // ------------------------------------------------------------------------ + + // Transaction 4: request another URL in MyRealm (however the + // url is not known to belong to the protection space, so no pre-auth). + { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/x/1"); + request.load_flags = 0; + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + MockWrite data_writes1[] = { + MockWrite("GET /x/1 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n\r\n"), + }; + + MockRead data_reads1[] = { + MockRead("HTTP/1.0 401 Unauthorized\r\n"), + MockRead("WWW-Authenticate: Basic realm=\"MyRealm1\"\r\n"), + MockRead("Content-Length: 10000\r\n\r\n"), + MockRead(SYNCHRONOUS, ERR_FAILED), + }; + + // Resend with authorization from MyRealm's cache. + MockWrite data_writes2[] = { + MockWrite("GET /x/1 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n" + "Authorization: Basic Zm9vOmJhcg==\r\n\r\n"), + }; + + // Sever accepts the authorization. + MockRead data_reads2[] = { + MockRead("HTTP/1.0 200 OK\r\n"), + MockRead("Content-Length: 100\r\n\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + + StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), + data_writes1, arraysize(data_writes1)); + StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2), + data_writes2, arraysize(data_writes2)); + session_deps.socket_factory.AddSocketDataProvider(&data1); + session_deps.socket_factory.AddSocketDataProvider(&data2); + + TestCompletionCallback callback1; + + int rv = trans->Start(&request, callback1.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(OK, rv); + + EXPECT_TRUE(trans->IsReadyToRestartForAuth()); + TestCompletionCallback callback2; + rv = trans->RestartWithAuth(AuthCredentials(), callback2.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback2.WaitForResult(); + EXPECT_EQ(OK, rv); + EXPECT_FALSE(trans->IsReadyToRestartForAuth()); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(response->auth_challenge.get() == NULL); + EXPECT_EQ(100, response->headers->GetContentLength()); + } + + // ------------------------------------------------------------------------ + + // Transaction 5: request a URL in MyRealm, but the server rejects the + // cached identity. Should invalidate and re-prompt. + { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/p/q/t"); + request.load_flags = 0; + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + MockWrite data_writes1[] = { + MockWrite("GET /p/q/t HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n\r\n"), + }; + + MockRead data_reads1[] = { + MockRead("HTTP/1.0 401 Unauthorized\r\n"), + MockRead("WWW-Authenticate: Basic realm=\"MyRealm1\"\r\n"), + MockRead("Content-Length: 10000\r\n\r\n"), + MockRead(SYNCHRONOUS, ERR_FAILED), + }; + + // Resend with authorization from cache for MyRealm. + MockWrite data_writes2[] = { + MockWrite("GET /p/q/t HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n" + "Authorization: Basic Zm9vOmJhcg==\r\n\r\n"), + }; + + // Sever rejects the authorization. + MockRead data_reads2[] = { + MockRead("HTTP/1.0 401 Unauthorized\r\n"), + MockRead("WWW-Authenticate: Basic realm=\"MyRealm1\"\r\n"), + MockRead("Content-Length: 10000\r\n\r\n"), + MockRead(SYNCHRONOUS, ERR_FAILED), + }; + + // At this point we should prompt for new credentials for MyRealm. + // Restart with username=foo3, password=foo4. + MockWrite data_writes3[] = { + MockWrite("GET /p/q/t HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n" + "Authorization: Basic Zm9vMzpiYXIz\r\n\r\n"), + }; + + // Sever accepts the authorization. + MockRead data_reads3[] = { + MockRead("HTTP/1.0 200 OK\r\n"), + MockRead("Content-Length: 100\r\n\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + + StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), + data_writes1, arraysize(data_writes1)); + StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2), + data_writes2, arraysize(data_writes2)); + StaticSocketDataProvider data3(data_reads3, arraysize(data_reads3), + data_writes3, arraysize(data_writes3)); + session_deps.socket_factory.AddSocketDataProvider(&data1); + session_deps.socket_factory.AddSocketDataProvider(&data2); + session_deps.socket_factory.AddSocketDataProvider(&data3); + + TestCompletionCallback callback1; + + int rv = trans->Start(&request, callback1.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(OK, rv); + + EXPECT_TRUE(trans->IsReadyToRestartForAuth()); + TestCompletionCallback callback2; + rv = trans->RestartWithAuth(AuthCredentials(), callback2.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback2.WaitForResult(); + EXPECT_EQ(OK, rv); + EXPECT_FALSE(trans->IsReadyToRestartForAuth()); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(CheckBasicServerAuth(response->auth_challenge.get())); + + TestCompletionCallback callback3; + + rv = trans->RestartWithAuth( + AuthCredentials(kFoo3, kBar3), callback3.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback3.WaitForResult(); + EXPECT_EQ(OK, rv); + + response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(response->auth_challenge.get() == NULL); + EXPECT_EQ(100, response->headers->GetContentLength()); + } +} + +// Tests that nonce count increments when multiple auth attempts +// are started with the same nonce. +TEST_F(HttpNetworkTransactionSpdy2Test, DigestPreAuthNonceCount) { + SessionDependencies session_deps; + HttpAuthHandlerDigest::Factory* digest_factory = + new HttpAuthHandlerDigest::Factory(); + HttpAuthHandlerDigest::FixedNonceGenerator* nonce_generator = + new HttpAuthHandlerDigest::FixedNonceGenerator("0123456789abcdef"); + digest_factory->set_nonce_generator(nonce_generator); + session_deps.http_auth_handler_factory.reset(digest_factory); + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + // Transaction 1: authenticate (foo, bar) on MyRealm1 + { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/x/y/z"); + request.load_flags = 0; + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + MockWrite data_writes1[] = { + MockWrite("GET /x/y/z HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n\r\n"), + }; + + MockRead data_reads1[] = { + MockRead("HTTP/1.0 401 Unauthorized\r\n"), + MockRead("WWW-Authenticate: Digest realm=\"digestive\", nonce=\"OU812\", " + "algorithm=MD5, qop=\"auth\"\r\n\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + + // Resend with authorization (username=foo, password=bar) + MockWrite data_writes2[] = { + MockWrite("GET /x/y/z HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n" + "Authorization: Digest username=\"foo\", realm=\"digestive\", " + "nonce=\"OU812\", uri=\"/x/y/z\", algorithm=MD5, " + "response=\"03ffbcd30add722589c1de345d7a927f\", qop=auth, " + "nc=00000001, cnonce=\"0123456789abcdef\"\r\n\r\n"), + }; + + // Sever accepts the authorization. + MockRead data_reads2[] = { + MockRead("HTTP/1.0 200 OK\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + + StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), + data_writes1, arraysize(data_writes1)); + StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2), + data_writes2, arraysize(data_writes2)); + session_deps.socket_factory.AddSocketDataProvider(&data1); + session_deps.socket_factory.AddSocketDataProvider(&data2); + + TestCompletionCallback callback1; + + int rv = trans->Start(&request, callback1.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(CheckDigestServerAuth(response->auth_challenge.get())); + + TestCompletionCallback callback2; + + rv = trans->RestartWithAuth( + AuthCredentials(kFoo, kBar), callback2.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback2.WaitForResult(); + EXPECT_EQ(OK, rv); + + response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(response->auth_challenge.get() == NULL); + } + + // ------------------------------------------------------------------------ + + // Transaction 2: Request another resource in digestive's protection space. + // This will preemptively add an Authorization header which should have an + // "nc" value of 2 (as compared to 1 in the first use. + { + HttpRequestInfo request; + request.method = "GET"; + // Note that Transaction 1 was at /x/y/z, so this is in the same + // protection space as digest. + request.url = GURL("http://www.google.com/x/y/a/b"); + request.load_flags = 0; + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + MockWrite data_writes1[] = { + MockWrite("GET /x/y/a/b HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n" + "Authorization: Digest username=\"foo\", realm=\"digestive\", " + "nonce=\"OU812\", uri=\"/x/y/a/b\", algorithm=MD5, " + "response=\"d6f9a2c07d1c5df7b89379dca1269b35\", qop=auth, " + "nc=00000002, cnonce=\"0123456789abcdef\"\r\n\r\n"), + }; + + // Sever accepts the authorization. + MockRead data_reads1[] = { + MockRead("HTTP/1.0 200 OK\r\n"), + MockRead("Content-Length: 100\r\n\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + + StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), + data_writes1, arraysize(data_writes1)); + session_deps.socket_factory.AddSocketDataProvider(&data1); + + TestCompletionCallback callback1; + + int rv = trans->Start(&request, callback1.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(response->auth_challenge.get() == NULL); + } +} + +// Test the ResetStateForRestart() private method. +TEST_F(HttpNetworkTransactionSpdy2Test, ResetStateForRestart) { + // Create a transaction (the dependencies aren't important). + SessionDependencies session_deps; + scoped_ptr<HttpNetworkTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + // Setup some state (which we expect ResetStateForRestart() will clear). + trans->read_buf_ = new IOBuffer(15); + trans->read_buf_len_ = 15; + trans->request_headers_.SetHeader("Authorization", "NTLM"); + + // Setup state in response_ + HttpResponseInfo* response = &trans->response_; + response->auth_challenge = new AuthChallengeInfo(); + response->ssl_info.cert_status = static_cast<CertStatus>(-1); // Nonsensical. + response->response_time = base::Time::Now(); + response->was_cached = true; // (Wouldn't ever actually be true...) + + { // Setup state for response_.vary_data + HttpRequestInfo request; + std::string temp("HTTP/1.1 200 OK\nVary: foo, bar\n\n"); + std::replace(temp.begin(), temp.end(), '\n', '\0'); + scoped_refptr<HttpResponseHeaders> headers(new HttpResponseHeaders(temp)); + request.extra_headers.SetHeader("Foo", "1"); + request.extra_headers.SetHeader("bar", "23"); + EXPECT_TRUE(response->vary_data.Init(request, *headers)); + } + + // Cause the above state to be reset. + trans->ResetStateForRestart(); + + // Verify that the state that needed to be reset, has been reset. + EXPECT_TRUE(trans->read_buf_.get() == NULL); + EXPECT_EQ(0, trans->read_buf_len_); + EXPECT_TRUE(trans->request_headers_.IsEmpty()); + EXPECT_TRUE(response->auth_challenge.get() == NULL); + EXPECT_TRUE(response->headers.get() == NULL); + EXPECT_FALSE(response->was_cached); + EXPECT_EQ(0U, response->ssl_info.cert_status); + EXPECT_FALSE(response->vary_data.is_valid()); +} + +// Test HTTPS connections to a site with a bad certificate +TEST_F(HttpNetworkTransactionSpdy2Test, HTTPSBadCertificate) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("https://www.google.com/"); + request.load_flags = 0; + + SessionDependencies session_deps; + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + MockWrite data_writes[] = { + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n\r\n"), + }; + + MockRead data_reads[] = { + MockRead("HTTP/1.0 200 OK\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), + MockRead("Content-Length: 100\r\n\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + + StaticSocketDataProvider ssl_bad_certificate; + StaticSocketDataProvider data(data_reads, arraysize(data_reads), + data_writes, arraysize(data_writes)); + SSLSocketDataProvider ssl_bad(ASYNC, ERR_CERT_AUTHORITY_INVALID); + SSLSocketDataProvider ssl(ASYNC, OK); + + session_deps.socket_factory.AddSocketDataProvider(&ssl_bad_certificate); + session_deps.socket_factory.AddSocketDataProvider(&data); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl_bad); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); + + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(ERR_CERT_AUTHORITY_INVALID, rv); + + rv = trans->RestartIgnoringLastError(callback.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + + ASSERT_TRUE(response != NULL); + EXPECT_EQ(100, response->headers->GetContentLength()); +} + +// Test HTTPS connections to a site with a bad certificate, going through a +// proxy +TEST_F(HttpNetworkTransactionSpdy2Test, HTTPSBadCertificateViaProxy) { + SessionDependencies session_deps(ProxyService::CreateFixed("myproxy:70")); + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("https://www.google.com/"); + request.load_flags = 0; + + MockWrite proxy_writes[] = { + MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n\r\n"), + }; + + MockRead proxy_reads[] = { + MockRead("HTTP/1.0 200 Connected\r\n\r\n"), + MockRead(SYNCHRONOUS, OK) + }; + + MockWrite data_writes[] = { + MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n\r\n"), + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n\r\n"), + }; + + MockRead data_reads[] = { + MockRead("HTTP/1.0 200 Connected\r\n\r\n"), + MockRead("HTTP/1.0 200 OK\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), + MockRead("Content-Length: 100\r\n\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + + StaticSocketDataProvider ssl_bad_certificate( + proxy_reads, arraysize(proxy_reads), + proxy_writes, arraysize(proxy_writes)); + StaticSocketDataProvider data(data_reads, arraysize(data_reads), + data_writes, arraysize(data_writes)); + SSLSocketDataProvider ssl_bad(ASYNC, ERR_CERT_AUTHORITY_INVALID); + SSLSocketDataProvider ssl(ASYNC, OK); + + session_deps.socket_factory.AddSocketDataProvider(&ssl_bad_certificate); + session_deps.socket_factory.AddSocketDataProvider(&data); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl_bad); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); + + TestCompletionCallback callback; + + for (int i = 0; i < 2; i++) { + session_deps.socket_factory.ResetNextMockIndexes(); + + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(ERR_CERT_AUTHORITY_INVALID, rv); + + rv = trans->RestartIgnoringLastError(callback.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + + ASSERT_TRUE(response != NULL); + EXPECT_EQ(100, response->headers->GetContentLength()); + } +} + + +// Test HTTPS connections to a site, going through an HTTPS proxy +TEST_F(HttpNetworkTransactionSpdy2Test, HTTPSViaHttpsProxy) { + SessionDependencies session_deps(ProxyService::CreateFixed( + "https://proxy:70")); + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("https://www.google.com/"); + request.load_flags = 0; + + MockWrite data_writes[] = { + MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n\r\n"), + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n\r\n"), + }; + + MockRead data_reads[] = { + MockRead("HTTP/1.0 200 Connected\r\n\r\n"), + MockRead("HTTP/1.1 200 OK\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), + MockRead("Content-Length: 100\r\n\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + + StaticSocketDataProvider data(data_reads, arraysize(data_reads), + data_writes, arraysize(data_writes)); + SSLSocketDataProvider proxy_ssl(ASYNC, OK); // SSL to the proxy + SSLSocketDataProvider tunnel_ssl(ASYNC, OK); // SSL through the tunnel + + session_deps.socket_factory.AddSocketDataProvider(&data); + session_deps.socket_factory.AddSSLSocketDataProvider(&proxy_ssl); + session_deps.socket_factory.AddSSLSocketDataProvider(&tunnel_ssl); + + TestCompletionCallback callback; + + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + const HttpResponseInfo* response = trans->GetResponseInfo(); + + ASSERT_TRUE(response != NULL); + + EXPECT_TRUE(response->headers->IsKeepAlive()); + EXPECT_EQ(200, response->headers->response_code()); + EXPECT_EQ(100, response->headers->GetContentLength()); + EXPECT_TRUE(HttpVersion(1, 1) == response->headers->GetHttpVersion()); +} + +// Test an HTTPS Proxy's ability to redirect a CONNECT request +TEST_F(HttpNetworkTransactionSpdy2Test, RedirectOfHttpsConnectViaHttpsProxy) { + SessionDependencies session_deps( + ProxyService::CreateFixed("https://proxy:70")); + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("https://www.google.com/"); + request.load_flags = 0; + + MockWrite data_writes[] = { + MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n\r\n"), + }; + + MockRead data_reads[] = { + MockRead("HTTP/1.1 302 Redirect\r\n"), + MockRead("Location: http://login.example.com/\r\n"), + MockRead("Content-Length: 0\r\n\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + + StaticSocketDataProvider data(data_reads, arraysize(data_reads), + data_writes, arraysize(data_writes)); + SSLSocketDataProvider proxy_ssl(ASYNC, OK); // SSL to the proxy + + session_deps.socket_factory.AddSocketDataProvider(&data); + session_deps.socket_factory.AddSSLSocketDataProvider(&proxy_ssl); + + TestCompletionCallback callback; + + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + const HttpResponseInfo* response = trans->GetResponseInfo(); + + ASSERT_TRUE(response != NULL); + + EXPECT_EQ(302, response->headers->response_code()); + std::string url; + EXPECT_TRUE(response->headers->IsRedirect(&url)); + EXPECT_EQ("http://login.example.com/", url); +} + +// Test an HTTPS (SPDY) Proxy's ability to redirect a CONNECT request +TEST_F(HttpNetworkTransactionSpdy2Test, RedirectOfHttpsConnectViaSpdyProxy) { + SessionDependencies session_deps( + ProxyService::CreateFixed("https://proxy:70")); + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("https://www.google.com/"); + request.load_flags = 0; + + scoped_ptr<spdy::SpdyFrame> conn(ConstructSpdyConnect(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> goaway(ConstructSpdyRstStream(1, spdy::CANCEL)); + MockWrite data_writes[] = { + CreateMockWrite(*conn.get(), 0, SYNCHRONOUS), + }; + + static const char* const kExtraHeaders[] = { + "location", + "http://login.example.com/", + }; + scoped_ptr<spdy::SpdyFrame> resp( + ConstructSpdySynReplyError("302 Redirect", kExtraHeaders, + arraysize(kExtraHeaders)/2, 1)); + MockRead data_reads[] = { + CreateMockRead(*resp.get(), 1, SYNCHRONOUS), + MockRead(ASYNC, 0, 2), // EOF + }; + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData( + 1, // wait for one write to finish before reading. + data_reads, arraysize(data_reads), + data_writes, arraysize(data_writes))); + SSLSocketDataProvider proxy_ssl(ASYNC, OK); // SSL to the proxy + proxy_ssl.SetNextProto(SSLClientSocket::kProtoSPDY2); + + session_deps.socket_factory.AddSocketDataProvider(data.get()); + session_deps.socket_factory.AddSSLSocketDataProvider(&proxy_ssl); + + TestCompletionCallback callback; + + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + const HttpResponseInfo* response = trans->GetResponseInfo(); + + ASSERT_TRUE(response != NULL); + + EXPECT_EQ(302, response->headers->response_code()); + std::string url; + EXPECT_TRUE(response->headers->IsRedirect(&url)); + EXPECT_EQ("http://login.example.com/", url); +} + +// Test an HTTPS Proxy's ability to provide a response to a CONNECT request +TEST_F(HttpNetworkTransactionSpdy2Test, + ErrorResponseTofHttpsConnectViaHttpsProxy) { + SessionDependencies session_deps( + ProxyService::CreateFixed("https://proxy:70")); + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("https://www.google.com/"); + request.load_flags = 0; + + MockWrite data_writes[] = { + MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n\r\n"), + }; + + MockRead data_reads[] = { + MockRead("HTTP/1.1 404 Not Found\r\n"), + MockRead("Content-Length: 23\r\n\r\n"), + MockRead("The host does not exist"), + MockRead(SYNCHRONOUS, OK), + }; + + StaticSocketDataProvider data(data_reads, arraysize(data_reads), + data_writes, arraysize(data_writes)); + SSLSocketDataProvider proxy_ssl(ASYNC, OK); // SSL to the proxy + + session_deps.socket_factory.AddSocketDataProvider(&data); + session_deps.socket_factory.AddSSLSocketDataProvider(&proxy_ssl); + + TestCompletionCallback callback; + + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + const HttpResponseInfo* response = trans->GetResponseInfo(); + + ASSERT_TRUE(response != NULL); + + EXPECT_EQ(404, response->headers->response_code()); + EXPECT_EQ(23, response->headers->GetContentLength()); + EXPECT_TRUE(HttpVersion(1, 1) == response->headers->GetHttpVersion()); + EXPECT_FALSE(response->ssl_info.is_valid()); + + std::string response_data; + ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data)); + EXPECT_EQ("The host does not exist", response_data); +} + +// Test an HTTPS (SPDY) Proxy's ability to provide a response to a CONNECT +// request +TEST_F(HttpNetworkTransactionSpdy2Test, + ErrorResponseTofHttpsConnectViaSpdyProxy) { + SessionDependencies session_deps( + ProxyService::CreateFixed("https://proxy:70")); + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("https://www.google.com/"); + request.load_flags = 0; + + scoped_ptr<spdy::SpdyFrame> conn(ConstructSpdyConnect(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> goaway(ConstructSpdyRstStream(1, spdy::CANCEL)); + MockWrite data_writes[] = { + CreateMockWrite(*conn.get(), 0, SYNCHRONOUS), + }; + + static const char* const kExtraHeaders[] = { + "location", + "http://login.example.com/", + }; + scoped_ptr<spdy::SpdyFrame> resp( + ConstructSpdySynReplyError("404 Not Found", kExtraHeaders, + arraysize(kExtraHeaders)/2, 1)); + scoped_ptr<spdy::SpdyFrame> body( + ConstructSpdyBodyFrame(1, "The host does not exist", 23, true)); + MockRead data_reads[] = { + CreateMockRead(*resp.get(), 1, SYNCHRONOUS), + CreateMockRead(*body.get(), 2, SYNCHRONOUS), + MockRead(ASYNC, 0, 3), // EOF + }; + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData( + 1, // wait for one write to finish before reading. + data_reads, arraysize(data_reads), + data_writes, arraysize(data_writes))); + SSLSocketDataProvider proxy_ssl(ASYNC, OK); // SSL to the proxy + proxy_ssl.SetNextProto(SSLClientSocket::kProtoSPDY2); + + session_deps.socket_factory.AddSocketDataProvider(data.get()); + session_deps.socket_factory.AddSSLSocketDataProvider(&proxy_ssl); + + TestCompletionCallback callback; + + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + const HttpResponseInfo* response = trans->GetResponseInfo(); + + ASSERT_TRUE(response != NULL); + + EXPECT_EQ(404, response->headers->response_code()); + EXPECT_FALSE(response->ssl_info.is_valid()); + + std::string response_data; + ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data)); + EXPECT_EQ("The host does not exist", response_data); +} + +// Test the request-challenge-retry sequence for basic auth, through +// a SPDY proxy over a single SPDY session. +TEST_F(HttpNetworkTransactionSpdy2Test, BasicAuthSpdyProxy) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("https://www.google.com/"); + // when the no authentication data flag is set. + request.load_flags = net::LOAD_DO_NOT_SEND_AUTH_DATA; + + // Configure against https proxy server "myproxy:70". + SessionDependencies session_deps( + ProxyService::CreateFixed("https://myproxy:70")); + CapturingBoundNetLog log(CapturingNetLog::kUnbounded); + session_deps.net_log = log.bound().net_log(); + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + // Since we have proxy, should try to establish tunnel. + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyConnect(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> rst(ConstructSpdyRstStream(1, spdy::CANCEL)); + + // After calling trans->RestartWithAuth(), this is the request we should + // be issuing -- the final header line contains the credentials. + const char* const kAuthCredentials[] = { + "proxy-authorization", "Basic Zm9vOmJhcg==", + }; + scoped_ptr<spdy::SpdyFrame> connect2( + ConstructSpdyConnect(kAuthCredentials, arraysize(kAuthCredentials)/2, 3)); + // fetch https://www.google.com/ via HTTP + const char get[] = "GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n\r\n"; + scoped_ptr<spdy::SpdyFrame> wrapped_get( + ConstructSpdyBodyFrame(3, get, strlen(get), false)); + + MockWrite spdy_writes[] = { + CreateMockWrite(*req, 0, ASYNC), + CreateMockWrite(*rst, 2, ASYNC), + CreateMockWrite(*connect2, 3), + CreateMockWrite(*wrapped_get, 5) + }; + + // The proxy responds to the connect with a 407, using a persistent + // connection. + const char* const kAuthChallenge[] = { + "status", "407 Proxy Authentication Required", + "version", "HTTP/1.1", + "proxy-authenticate", "Basic realm=\"MyRealm1\"", + }; + + scoped_ptr<spdy::SpdyFrame> conn_auth_resp( + ConstructSpdyControlFrame(NULL, + 0, + false, + 1, + LOWEST, + spdy::SYN_REPLY, + spdy::CONTROL_FLAG_NONE, + kAuthChallenge, + arraysize(kAuthChallenge))); + + scoped_ptr<spdy::SpdyFrame> conn_resp(ConstructSpdyGetSynReply(NULL, 0, 3)); + const char resp[] = "HTTP/1.1 200 OK\r\n" + "Content-Length: 5\r\n\r\n"; + + scoped_ptr<spdy::SpdyFrame> wrapped_get_resp( + ConstructSpdyBodyFrame(3, resp, strlen(resp), false)); + scoped_ptr<spdy::SpdyFrame> wrapped_body( + ConstructSpdyBodyFrame(3, "hello", 5, false)); + MockRead spdy_reads[] = { + CreateMockRead(*conn_auth_resp, 1, ASYNC), + CreateMockRead(*conn_resp, 4, ASYNC), + CreateMockRead(*wrapped_get_resp, 5, ASYNC), + CreateMockRead(*wrapped_body, 6, ASYNC), + MockRead(SYNCHRONOUS, ERR_IO_PENDING), + }; + + scoped_ptr<OrderedSocketData> spdy_data( + new OrderedSocketData( + spdy_reads, arraysize(spdy_reads), + spdy_writes, arraysize(spdy_writes))); + session_deps.socket_factory.AddSocketDataProvider(spdy_data.get()); + // Negotiate SPDY to the proxy + SSLSocketDataProvider proxy(ASYNC, OK); + proxy.SetNextProto(SSLClientSocket::kProtoSPDY2); + session_deps.socket_factory.AddSSLSocketDataProvider(&proxy); + // Vanilla SSL to the server + SSLSocketDataProvider server(ASYNC, OK); + session_deps.socket_factory.AddSSLSocketDataProvider(&server); + + TestCompletionCallback callback1; + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + int rv = trans->Start(&request, callback1.callback(), log.bound()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(OK, rv); + net::CapturingNetLog::EntryList entries; + log.GetEntries(&entries); + size_t pos = ExpectLogContainsSomewhere( + entries, 0, NetLog::TYPE_HTTP_TRANSACTION_SEND_TUNNEL_HEADERS, + NetLog::PHASE_NONE); + ExpectLogContainsSomewhere( + entries, pos, + NetLog::TYPE_HTTP_TRANSACTION_READ_TUNNEL_RESPONSE_HEADERS, + NetLog::PHASE_NONE); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_FALSE(response->headers == NULL); + EXPECT_EQ(407, response->headers->response_code()); + EXPECT_TRUE(HttpVersion(1, 1) == response->headers->GetHttpVersion()); + EXPECT_TRUE(response->auth_challenge.get() != NULL); + EXPECT_TRUE(CheckBasicProxyAuth(response->auth_challenge.get())); + + TestCompletionCallback callback2; + + rv = trans->RestartWithAuth(AuthCredentials(kFoo, kBar), + callback2.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback2.WaitForResult(); + EXPECT_EQ(OK, rv); + + response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + + EXPECT_TRUE(response->headers->IsKeepAlive()); + EXPECT_EQ(200, response->headers->response_code()); + EXPECT_EQ(5, response->headers->GetContentLength()); + EXPECT_TRUE(HttpVersion(1, 1) == response->headers->GetHttpVersion()); + + // The password prompt info should not be set. + EXPECT_TRUE(response->auth_challenge.get() == NULL); + + trans.reset(); + session->CloseAllConnections(); +} + +// Test HTTPS connections to a site with a bad certificate, going through an +// HTTPS proxy +TEST_F(HttpNetworkTransactionSpdy2Test, HTTPSBadCertificateViaHttpsProxy) { + SessionDependencies session_deps(ProxyService::CreateFixed( + "https://proxy:70")); + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("https://www.google.com/"); + request.load_flags = 0; + + // Attempt to fetch the URL from a server with a bad cert + MockWrite bad_cert_writes[] = { + MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n\r\n"), + }; + + MockRead bad_cert_reads[] = { + MockRead("HTTP/1.0 200 Connected\r\n\r\n"), + MockRead(SYNCHRONOUS, OK) + }; + + // Attempt to fetch the URL with a good cert + MockWrite good_data_writes[] = { + MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n\r\n"), + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n\r\n"), + }; + + MockRead good_cert_reads[] = { + MockRead("HTTP/1.0 200 Connected\r\n\r\n"), + MockRead("HTTP/1.0 200 OK\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), + MockRead("Content-Length: 100\r\n\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + + StaticSocketDataProvider ssl_bad_certificate( + bad_cert_reads, arraysize(bad_cert_reads), + bad_cert_writes, arraysize(bad_cert_writes)); + StaticSocketDataProvider data(good_cert_reads, arraysize(good_cert_reads), + good_data_writes, arraysize(good_data_writes)); + SSLSocketDataProvider ssl_bad(ASYNC, ERR_CERT_AUTHORITY_INVALID); + SSLSocketDataProvider ssl(ASYNC, OK); + + // SSL to the proxy, then CONNECT request, then SSL with bad certificate + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); + session_deps.socket_factory.AddSocketDataProvider(&ssl_bad_certificate); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl_bad); + + // SSL to the proxy, then CONNECT request, then valid SSL certificate + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); + session_deps.socket_factory.AddSocketDataProvider(&data); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); + + TestCompletionCallback callback; + + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(ERR_CERT_AUTHORITY_INVALID, rv); + + rv = trans->RestartIgnoringLastError(callback.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + + ASSERT_TRUE(response != NULL); + EXPECT_EQ(100, response->headers->GetContentLength()); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, BuildRequest_UserAgent) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.extra_headers.SetHeader(HttpRequestHeaders::kUserAgent, + "Chromium Ultra Awesome X Edition"); + + SessionDependencies session_deps; + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + MockWrite data_writes[] = { + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n" + "User-Agent: Chromium Ultra Awesome X Edition\r\n\r\n"), + }; + + // Lastly, the server responds with the actual content. + MockRead data_reads[] = { + MockRead("HTTP/1.0 200 OK\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), + MockRead("Content-Length: 100\r\n\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + + StaticSocketDataProvider data(data_reads, arraysize(data_reads), + data_writes, arraysize(data_writes)); + session_deps.socket_factory.AddSocketDataProvider(&data); + + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, BuildRequest_UserAgentOverTunnel) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("https://www.google.com/"); + request.extra_headers.SetHeader(HttpRequestHeaders::kUserAgent, + "Chromium Ultra Awesome X Edition"); + + SessionDependencies session_deps(ProxyService::CreateFixed("myproxy:70")); + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + MockWrite data_writes[] = { + MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n" + "User-Agent: Chromium Ultra Awesome X Edition\r\n\r\n"), + }; + MockRead data_reads[] = { + // Return an error, so the transaction stops here (this test isn't + // interested in the rest). + MockRead("HTTP/1.1 407 Proxy Authentication Required\r\n"), + MockRead("Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n"), + MockRead("Proxy-Connection: close\r\n\r\n"), + }; + + StaticSocketDataProvider data(data_reads, arraysize(data_reads), + data_writes, arraysize(data_writes)); + session_deps.socket_factory.AddSocketDataProvider(&data); + + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, BuildRequest_Referer) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + request.extra_headers.SetHeader(HttpRequestHeaders::kReferer, + "http://the.previous.site.com/"); + + SessionDependencies session_deps; + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + MockWrite data_writes[] = { + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n" + "Referer: http://the.previous.site.com/\r\n\r\n"), + }; + + // Lastly, the server responds with the actual content. + MockRead data_reads[] = { + MockRead("HTTP/1.0 200 OK\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), + MockRead("Content-Length: 100\r\n\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + + StaticSocketDataProvider data(data_reads, arraysize(data_reads), + data_writes, arraysize(data_writes)); + session_deps.socket_factory.AddSocketDataProvider(&data); + + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, BuildRequest_PostContentLengthZero) { + HttpRequestInfo request; + request.method = "POST"; + request.url = GURL("http://www.google.com/"); + + SessionDependencies session_deps; + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + MockWrite data_writes[] = { + MockWrite("POST / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n" + "Content-Length: 0\r\n\r\n"), + }; + + // Lastly, the server responds with the actual content. + MockRead data_reads[] = { + MockRead("HTTP/1.0 200 OK\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), + MockRead("Content-Length: 100\r\n\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + + StaticSocketDataProvider data(data_reads, arraysize(data_reads), + data_writes, arraysize(data_writes)); + session_deps.socket_factory.AddSocketDataProvider(&data); + + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, BuildRequest_PutContentLengthZero) { + HttpRequestInfo request; + request.method = "PUT"; + request.url = GURL("http://www.google.com/"); + + SessionDependencies session_deps; + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + MockWrite data_writes[] = { + MockWrite("PUT / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n" + "Content-Length: 0\r\n\r\n"), + }; + + // Lastly, the server responds with the actual content. + MockRead data_reads[] = { + MockRead("HTTP/1.0 200 OK\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), + MockRead("Content-Length: 100\r\n\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + + StaticSocketDataProvider data(data_reads, arraysize(data_reads), + data_writes, arraysize(data_writes)); + session_deps.socket_factory.AddSocketDataProvider(&data); + + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, BuildRequest_HeadContentLengthZero) { + HttpRequestInfo request; + request.method = "HEAD"; + request.url = GURL("http://www.google.com/"); + + SessionDependencies session_deps; + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + MockWrite data_writes[] = { + MockWrite("HEAD / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n" + "Content-Length: 0\r\n\r\n"), + }; + + // Lastly, the server responds with the actual content. + MockRead data_reads[] = { + MockRead("HTTP/1.0 200 OK\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), + MockRead("Content-Length: 100\r\n\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + + StaticSocketDataProvider data(data_reads, arraysize(data_reads), + data_writes, arraysize(data_writes)); + session_deps.socket_factory.AddSocketDataProvider(&data); + + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, BuildRequest_CacheControlNoCache) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = LOAD_BYPASS_CACHE; + + SessionDependencies session_deps; + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + MockWrite data_writes[] = { + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n" + "Pragma: no-cache\r\n" + "Cache-Control: no-cache\r\n\r\n"), + }; + + // Lastly, the server responds with the actual content. + MockRead data_reads[] = { + MockRead("HTTP/1.0 200 OK\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), + MockRead("Content-Length: 100\r\n\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + + StaticSocketDataProvider data(data_reads, arraysize(data_reads), + data_writes, arraysize(data_writes)); + session_deps.socket_factory.AddSocketDataProvider(&data); + + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, + BuildRequest_CacheControlValidateCache) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = LOAD_VALIDATE_CACHE; + + SessionDependencies session_deps; + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + MockWrite data_writes[] = { + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n" + "Cache-Control: max-age=0\r\n\r\n"), + }; + + // Lastly, the server responds with the actual content. + MockRead data_reads[] = { + MockRead("HTTP/1.0 200 OK\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), + MockRead("Content-Length: 100\r\n\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + + StaticSocketDataProvider data(data_reads, arraysize(data_reads), + data_writes, arraysize(data_writes)); + session_deps.socket_factory.AddSocketDataProvider(&data); + + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, BuildRequest_ExtraHeaders) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.extra_headers.SetHeader("FooHeader", "Bar"); + + SessionDependencies session_deps; + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + MockWrite data_writes[] = { + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n" + "FooHeader: Bar\r\n\r\n"), + }; + + // Lastly, the server responds with the actual content. + MockRead data_reads[] = { + MockRead("HTTP/1.0 200 OK\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), + MockRead("Content-Length: 100\r\n\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + + StaticSocketDataProvider data(data_reads, arraysize(data_reads), + data_writes, arraysize(data_writes)); + session_deps.socket_factory.AddSocketDataProvider(&data); + + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, BuildRequest_ExtraHeadersStripped) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.extra_headers.SetHeader("referer", "www.foo.com"); + request.extra_headers.SetHeader("hEllo", "Kitty"); + request.extra_headers.SetHeader("FoO", "bar"); + + SessionDependencies session_deps; + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + MockWrite data_writes[] = { + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n" + "referer: www.foo.com\r\n" + "hEllo: Kitty\r\n" + "FoO: bar\r\n\r\n"), + }; + + // Lastly, the server responds with the actual content. + MockRead data_reads[] = { + MockRead("HTTP/1.0 200 OK\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), + MockRead("Content-Length: 100\r\n\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + + StaticSocketDataProvider data(data_reads, arraysize(data_reads), + data_writes, arraysize(data_writes)); + session_deps.socket_factory.AddSocketDataProvider(&data); + + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); +} + +// http://crbug.com/112682 +#if defined(OS_MACOSX) +#define MAYBE_SOCKS4_HTTP_GET DISABLED_SOCKS4_HTTP_GET +#else +#define MAYBE_SOCKS4_HTTP_GET SOCKS4_HTTP_GET +#endif + +TEST_F(HttpNetworkTransactionSpdy2Test, MAYBE_SOCKS4_HTTP_GET) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + + SessionDependencies session_deps( + ProxyService::CreateFixed("socks4://myproxy:1080")); + + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + char write_buffer[] = { 0x04, 0x01, 0x00, 0x50, 127, 0, 0, 1, 0 }; + char read_buffer[] = { 0x00, 0x5A, 0x00, 0x00, 0, 0, 0, 0 }; + + MockWrite data_writes[] = { + MockWrite(ASYNC, write_buffer, arraysize(write_buffer)), + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n\r\n") + }; + + MockRead data_reads[] = { + MockRead(ASYNC, read_buffer, arraysize(read_buffer)), + MockRead("HTTP/1.0 200 OK\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n\r\n"), + MockRead("Payload"), + MockRead(SYNCHRONOUS, OK) + }; + + StaticSocketDataProvider data(data_reads, arraysize(data_reads), + data_writes, arraysize(data_writes)); + session_deps.socket_factory.AddSocketDataProvider(&data); + + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + + std::string response_text; + rv = ReadTransaction(trans.get(), &response_text); + EXPECT_EQ(OK, rv); + EXPECT_EQ("Payload", response_text); +} + +// http://crbug.com/112682 +#if defined(OS_MACOSX) +#define MAYBE_SOCKS4_SSL_GET DISABLED_SOCKS4_SSL_GET +#else +#define MAYBE_SOCKS4_SSL_GET SOCKS4_SSL_GET +#endif + +TEST_F(HttpNetworkTransactionSpdy2Test, MAYBE_SOCKS4_SSL_GET) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("https://www.google.com/"); + request.load_flags = 0; + + SessionDependencies session_deps( + ProxyService::CreateFixed("socks4://myproxy:1080")); + + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + unsigned char write_buffer[] = { 0x04, 0x01, 0x01, 0xBB, 127, 0, 0, 1, 0 }; + unsigned char read_buffer[] = { 0x00, 0x5A, 0x00, 0x00, 0, 0, 0, 0 }; + + MockWrite data_writes[] = { + MockWrite(ASYNC, reinterpret_cast<char*>(write_buffer), + arraysize(write_buffer)), + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n\r\n") + }; + + MockRead data_reads[] = { + MockWrite(ASYNC, reinterpret_cast<char*>(read_buffer), + arraysize(read_buffer)), + MockRead("HTTP/1.0 200 OK\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n\r\n"), + MockRead("Payload"), + MockRead(SYNCHRONOUS, OK) + }; + + StaticSocketDataProvider data(data_reads, arraysize(data_reads), + data_writes, arraysize(data_writes)); + session_deps.socket_factory.AddSocketDataProvider(&data); + + SSLSocketDataProvider ssl(ASYNC, OK); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); + + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + + std::string response_text; + rv = ReadTransaction(trans.get(), &response_text); + EXPECT_EQ(OK, rv); + EXPECT_EQ("Payload", response_text); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, SOCKS5_HTTP_GET) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + + SessionDependencies session_deps( + ProxyService::CreateFixed("socks5://myproxy:1080")); + + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + const char kSOCKS5GreetRequest[] = { 0x05, 0x01, 0x00 }; + const char kSOCKS5GreetResponse[] = { 0x05, 0x00 }; + const char kSOCKS5OkRequest[] = { + 0x05, // Version + 0x01, // Command (CONNECT) + 0x00, // Reserved. + 0x03, // Address type (DOMAINNAME). + 0x0E, // Length of domain (14) + // Domain string: + 'w', 'w', 'w', '.', 'g', 'o', 'o', 'g', 'l', 'e', '.', 'c', 'o', 'm', + 0x00, 0x50, // 16-bit port (80) + }; + const char kSOCKS5OkResponse[] = + { 0x05, 0x00, 0x00, 0x01, 127, 0, 0, 1, 0x00, 0x50 }; + + MockWrite data_writes[] = { + MockWrite(ASYNC, kSOCKS5GreetRequest, arraysize(kSOCKS5GreetRequest)), + MockWrite(ASYNC, kSOCKS5OkRequest, arraysize(kSOCKS5OkRequest)), + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n\r\n") + }; + + MockRead data_reads[] = { + MockWrite(ASYNC, kSOCKS5GreetResponse, arraysize(kSOCKS5GreetResponse)), + MockWrite(ASYNC, kSOCKS5OkResponse, arraysize(kSOCKS5OkResponse)), + MockRead("HTTP/1.0 200 OK\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n\r\n"), + MockRead("Payload"), + MockRead(SYNCHRONOUS, OK) + }; + + StaticSocketDataProvider data(data_reads, arraysize(data_reads), + data_writes, arraysize(data_writes)); + session_deps.socket_factory.AddSocketDataProvider(&data); + + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + + std::string response_text; + rv = ReadTransaction(trans.get(), &response_text); + EXPECT_EQ(OK, rv); + EXPECT_EQ("Payload", response_text); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, SOCKS5_SSL_GET) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("https://www.google.com/"); + request.load_flags = 0; + + SessionDependencies session_deps( + ProxyService::CreateFixed("socks5://myproxy:1080")); + + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + const char kSOCKS5GreetRequest[] = { 0x05, 0x01, 0x00 }; + const char kSOCKS5GreetResponse[] = { 0x05, 0x00 }; + const unsigned char kSOCKS5OkRequest[] = { + 0x05, // Version + 0x01, // Command (CONNECT) + 0x00, // Reserved. + 0x03, // Address type (DOMAINNAME). + 0x0E, // Length of domain (14) + // Domain string: + 'w', 'w', 'w', '.', 'g', 'o', 'o', 'g', 'l', 'e', '.', 'c', 'o', 'm', + 0x01, 0xBB, // 16-bit port (443) + }; + + const char kSOCKS5OkResponse[] = + { 0x05, 0x00, 0x00, 0x01, 0, 0, 0, 0, 0x00, 0x00 }; + + MockWrite data_writes[] = { + MockWrite(ASYNC, kSOCKS5GreetRequest, arraysize(kSOCKS5GreetRequest)), + MockWrite(ASYNC, reinterpret_cast<const char*>(kSOCKS5OkRequest), + arraysize(kSOCKS5OkRequest)), + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n\r\n") + }; + + MockRead data_reads[] = { + MockWrite(ASYNC, kSOCKS5GreetResponse, arraysize(kSOCKS5GreetResponse)), + MockWrite(ASYNC, kSOCKS5OkResponse, arraysize(kSOCKS5OkResponse)), + MockRead("HTTP/1.0 200 OK\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n\r\n"), + MockRead("Payload"), + MockRead(SYNCHRONOUS, OK) + }; + + StaticSocketDataProvider data(data_reads, arraysize(data_reads), + data_writes, arraysize(data_writes)); + session_deps.socket_factory.AddSocketDataProvider(&data); + + SSLSocketDataProvider ssl(ASYNC, OK); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); + + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + + std::string response_text; + rv = ReadTransaction(trans.get(), &response_text); + EXPECT_EQ(OK, rv); + EXPECT_EQ("Payload", response_text); +} + +namespace { + +// Tests that for connection endpoints the group names are correctly set. + +struct GroupNameTest { + std::string proxy_server; + std::string url; + std::string expected_group_name; + bool ssl; +}; + +scoped_refptr<HttpNetworkSession> SetupSessionForGroupNameTests( + SessionDependencies* session_deps) { + scoped_refptr<HttpNetworkSession> session(CreateSession(session_deps)); + + HttpServerProperties* http_server_properties = + session->http_server_properties(); + http_server_properties->SetAlternateProtocol( + HostPortPair("host.with.alternate", 80), 443, + NPN_SPDY_2); + + return session; +} + +int GroupNameTransactionHelper( + const std::string& url, + const scoped_refptr<HttpNetworkSession>& session) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL(url); + request.load_flags = 0; + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + TestCompletionCallback callback; + + // We do not complete this request, the dtor will clean the transaction up. + return trans->Start(&request, callback.callback(), BoundNetLog()); +} + +} // namespace + +TEST_F(HttpNetworkTransactionSpdy2Test, GroupNameForDirectConnections) { + const GroupNameTest tests[] = { + { + "", // unused + "http://www.google.com/direct", + "www.google.com:80", + false, + }, + { + "", // unused + "http://[2001:1418:13:1::25]/direct", + "[2001:1418:13:1::25]:80", + false, + }, + + // SSL Tests + { + "", // unused + "https://www.google.com/direct_ssl", + "ssl/www.google.com:443", + true, + }, + { + "", // unused + "https://[2001:1418:13:1::25]/direct", + "ssl/[2001:1418:13:1::25]:443", + true, + }, + { + "", // unused + "http://host.with.alternate/direct", + "ssl/host.with.alternate:443", + true, + }, + }; + + HttpStreamFactory::set_use_alternate_protocols(true); + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) { + SessionDependencies session_deps( + ProxyService::CreateFixed(tests[i].proxy_server)); + scoped_refptr<HttpNetworkSession> session( + SetupSessionForGroupNameTests(&session_deps)); + + HttpNetworkSessionPeer peer(session); + CaptureGroupNameTransportSocketPool* transport_conn_pool = + new CaptureGroupNameTransportSocketPool(NULL, NULL); + CaptureGroupNameSSLSocketPool* ssl_conn_pool = + new CaptureGroupNameSSLSocketPool(NULL, NULL); + MockClientSocketPoolManager* mock_pool_manager = + new MockClientSocketPoolManager; + mock_pool_manager->SetTransportSocketPool(transport_conn_pool); + mock_pool_manager->SetSSLSocketPool(ssl_conn_pool); + peer.SetClientSocketPoolManager(mock_pool_manager); + + EXPECT_EQ(ERR_IO_PENDING, + GroupNameTransactionHelper(tests[i].url, session)); + if (tests[i].ssl) + EXPECT_EQ(tests[i].expected_group_name, + ssl_conn_pool->last_group_name_received()); + else + EXPECT_EQ(tests[i].expected_group_name, + transport_conn_pool->last_group_name_received()); + } + + HttpStreamFactory::set_use_alternate_protocols(false); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, GroupNameForHTTPProxyConnections) { + const GroupNameTest tests[] = { + { + "http_proxy", + "http://www.google.com/http_proxy_normal", + "www.google.com:80", + false, + }, + + // SSL Tests + { + "http_proxy", + "https://www.google.com/http_connect_ssl", + "ssl/www.google.com:443", + true, + }, + + { + "http_proxy", + "http://host.with.alternate/direct", + "ssl/host.with.alternate:443", + true, + }, + }; + + HttpStreamFactory::set_use_alternate_protocols(true); + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) { + SessionDependencies session_deps( + ProxyService::CreateFixed(tests[i].proxy_server)); + scoped_refptr<HttpNetworkSession> session( + SetupSessionForGroupNameTests(&session_deps)); + + HttpNetworkSessionPeer peer(session); + + HostPortPair proxy_host("http_proxy", 80); + CaptureGroupNameHttpProxySocketPool* http_proxy_pool = + new CaptureGroupNameHttpProxySocketPool(NULL, NULL); + CaptureGroupNameSSLSocketPool* ssl_conn_pool = + new CaptureGroupNameSSLSocketPool(NULL, NULL); + + MockClientSocketPoolManager* mock_pool_manager = + new MockClientSocketPoolManager; + mock_pool_manager->SetSocketPoolForHTTPProxy(proxy_host, http_proxy_pool); + mock_pool_manager->SetSocketPoolForSSLWithProxy(proxy_host, ssl_conn_pool); + peer.SetClientSocketPoolManager(mock_pool_manager); + + EXPECT_EQ(ERR_IO_PENDING, + GroupNameTransactionHelper(tests[i].url, session)); + if (tests[i].ssl) + EXPECT_EQ(tests[i].expected_group_name, + ssl_conn_pool->last_group_name_received()); + else + EXPECT_EQ(tests[i].expected_group_name, + http_proxy_pool->last_group_name_received()); + } + + HttpStreamFactory::set_use_alternate_protocols(false); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, GroupNameForSOCKSConnections) { + const GroupNameTest tests[] = { + { + "socks4://socks_proxy:1080", + "http://www.google.com/socks4_direct", + "socks4/www.google.com:80", + false, + }, + { + "socks5://socks_proxy:1080", + "http://www.google.com/socks5_direct", + "socks5/www.google.com:80", + false, + }, + + // SSL Tests + { + "socks4://socks_proxy:1080", + "https://www.google.com/socks4_ssl", + "socks4/ssl/www.google.com:443", + true, + }, + { + "socks5://socks_proxy:1080", + "https://www.google.com/socks5_ssl", + "socks5/ssl/www.google.com:443", + true, + }, + + { + "socks4://socks_proxy:1080", + "http://host.with.alternate/direct", + "socks4/ssl/host.with.alternate:443", + true, + }, + }; + + HttpStreamFactory::set_use_alternate_protocols(true); + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) { + SessionDependencies session_deps( + ProxyService::CreateFixed(tests[i].proxy_server)); + scoped_refptr<HttpNetworkSession> session( + SetupSessionForGroupNameTests(&session_deps)); + + HttpNetworkSessionPeer peer(session); + + HostPortPair proxy_host("socks_proxy", 1080); + CaptureGroupNameSOCKSSocketPool* socks_conn_pool = + new CaptureGroupNameSOCKSSocketPool(NULL, NULL); + CaptureGroupNameSSLSocketPool* ssl_conn_pool = + new CaptureGroupNameSSLSocketPool(NULL, NULL); + + MockClientSocketPoolManager* mock_pool_manager = + new MockClientSocketPoolManager; + mock_pool_manager->SetSocketPoolForSOCKSProxy(proxy_host, socks_conn_pool); + mock_pool_manager->SetSocketPoolForSSLWithProxy(proxy_host, ssl_conn_pool); + peer.SetClientSocketPoolManager(mock_pool_manager); + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + EXPECT_EQ(ERR_IO_PENDING, + GroupNameTransactionHelper(tests[i].url, session)); + if (tests[i].ssl) + EXPECT_EQ(tests[i].expected_group_name, + ssl_conn_pool->last_group_name_received()); + else + EXPECT_EQ(tests[i].expected_group_name, + socks_conn_pool->last_group_name_received()); + } + + HttpStreamFactory::set_use_alternate_protocols(false); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, ReconsiderProxyAfterFailedConnection) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + + SessionDependencies session_deps( + ProxyService::CreateFixed("myproxy:70;foobar:80")); + + // This simulates failure resolving all hostnames; that means we will fail + // connecting to both proxies (myproxy:70 and foobar:80). + session_deps.host_resolver->rules()->AddSimulatedFailure("*"); + + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(ERR_PROXY_CONNECTION_FAILED, rv); +} + +namespace { + +// Base test to make sure that when the load flags for a request specify to +// bypass the cache, the DNS cache is not used. +void BypassHostCacheOnRefreshHelper(int load_flags) { + // Issue a request, asking to bypass the cache(s). + HttpRequestInfo request; + request.method = "GET"; + request.load_flags = load_flags; + request.url = GURL("http://www.google.com/"); + + SessionDependencies session_deps; + + // Select a host resolver that does caching. + session_deps.host_resolver.reset(new MockCachingHostResolver); + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction( + CreateSession(&session_deps))); + + // Warm up the host cache so it has an entry for "www.google.com". + AddressList addrlist; + TestCompletionCallback callback; + int rv = session_deps.host_resolver->Resolve( + HostResolver::RequestInfo(HostPortPair("www.google.com", 80)), &addrlist, + callback.callback(), NULL, BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + // Verify that it was added to host cache, by doing a subsequent async lookup + // and confirming it completes synchronously. + rv = session_deps.host_resolver->Resolve( + HostResolver::RequestInfo(HostPortPair("www.google.com", 80)), &addrlist, + callback.callback(), NULL, BoundNetLog()); + ASSERT_EQ(OK, rv); + + // Inject a failure the next time that "www.google.com" is resolved. This way + // we can tell if the next lookup hit the cache, or the "network". + // (cache --> success, "network" --> failure). + session_deps.host_resolver->rules()->AddSimulatedFailure("www.google.com"); + + // Connect up a mock socket which will fail with ERR_UNEXPECTED during the + // first read -- this won't be reached as the host resolution will fail first. + MockRead data_reads[] = { MockRead(SYNCHRONOUS, ERR_UNEXPECTED) }; + StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&data); + + // Run the request. + rv = trans->Start(&request, callback.callback(), BoundNetLog()); + ASSERT_EQ(ERR_IO_PENDING, rv); + rv = callback.WaitForResult(); + + // If we bypassed the cache, we would have gotten a failure while resolving + // "www.google.com". + EXPECT_EQ(ERR_NAME_NOT_RESOLVED, rv); +} + +} // namespace + +// There are multiple load flags that should trigger the host cache bypass. +// Test each in isolation: +TEST_F(HttpNetworkTransactionSpdy2Test, BypassHostCacheOnRefresh1) { + BypassHostCacheOnRefreshHelper(LOAD_BYPASS_CACHE); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, BypassHostCacheOnRefresh2) { + BypassHostCacheOnRefreshHelper(LOAD_VALIDATE_CACHE); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, BypassHostCacheOnRefresh3) { + BypassHostCacheOnRefreshHelper(LOAD_DISABLE_CACHE); +} + +// Make sure we can handle an error when writing the request. +TEST_F(HttpNetworkTransactionSpdy2Test, RequestWriteError) { + SessionDependencies session_deps; + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.foo.com/"); + request.load_flags = 0; + + MockWrite write_failure[] = { + MockWrite(ASYNC, ERR_CONNECTION_RESET), + }; + StaticSocketDataProvider data(NULL, 0, + write_failure, arraysize(write_failure)); + session_deps.socket_factory.AddSocketDataProvider(&data); + + TestCompletionCallback callback; + + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(ERR_CONNECTION_RESET, rv); +} + +// Check that a connection closed after the start of the headers finishes ok. +TEST_F(HttpNetworkTransactionSpdy2Test, ConnectionClosedAfterStartOfHeaders) { + SessionDependencies session_deps; + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.foo.com/"); + request.load_flags = 0; + + MockRead data_reads[] = { + MockRead("HTTP/1."), + MockRead(SYNCHRONOUS, OK), + }; + + StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&data); + + TestCompletionCallback callback; + + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + + EXPECT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.0 200 OK", response->headers->GetStatusLine()); + + std::string response_data; + rv = ReadTransaction(trans.get(), &response_data); + EXPECT_EQ(OK, rv); + EXPECT_EQ("", response_data); +} + +// Make sure that a dropped connection while draining the body for auth +// restart does the right thing. +TEST_F(HttpNetworkTransactionSpdy2Test, DrainResetOK) { + SessionDependencies session_deps; + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + + MockWrite data_writes1[] = { + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n\r\n"), + }; + + MockRead data_reads1[] = { + MockRead("HTTP/1.1 401 Unauthorized\r\n"), + MockRead("WWW-Authenticate: Basic realm=\"MyRealm1\"\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), + MockRead("Content-Length: 14\r\n\r\n"), + MockRead("Unauth"), + MockRead(ASYNC, ERR_CONNECTION_RESET), + }; + + StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), + data_writes1, arraysize(data_writes1)); + session_deps.socket_factory.AddSocketDataProvider(&data1); + + // After calling trans->RestartWithAuth(), this is the request we should + // be issuing -- the final header line contains the credentials. + MockWrite data_writes2[] = { + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n" + "Authorization: Basic Zm9vOmJhcg==\r\n\r\n"), + }; + + // Lastly, the server responds with the actual content. + MockRead data_reads2[] = { + MockRead("HTTP/1.1 200 OK\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), + MockRead("Content-Length: 100\r\n\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + + StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2), + data_writes2, arraysize(data_writes2)); + session_deps.socket_factory.AddSocketDataProvider(&data2); + + TestCompletionCallback callback1; + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + int rv = trans->Start(&request, callback1.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(CheckBasicServerAuth(response->auth_challenge.get())); + + TestCompletionCallback callback2; + + rv = trans->RestartWithAuth( + AuthCredentials(kFoo, kBar), callback2.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback2.WaitForResult(); + EXPECT_EQ(OK, rv); + + response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(response->auth_challenge.get() == NULL); + EXPECT_EQ(100, response->headers->GetContentLength()); +} + +// Test HTTPS connections going through a proxy that sends extra data. +TEST_F(HttpNetworkTransactionSpdy2Test, HTTPSViaProxyWithExtraData) { + SessionDependencies session_deps(ProxyService::CreateFixed("myproxy:70")); + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("https://www.google.com/"); + request.load_flags = 0; + + MockRead proxy_reads[] = { + MockRead("HTTP/1.0 200 Connected\r\n\r\nExtra data"), + MockRead(SYNCHRONOUS, OK) + }; + + StaticSocketDataProvider data(proxy_reads, arraysize(proxy_reads), NULL, 0); + SSLSocketDataProvider ssl(ASYNC, OK); + + session_deps.socket_factory.AddSocketDataProvider(&data); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); + + TestCompletionCallback callback; + + session_deps.socket_factory.ResetNextMockIndexes(); + + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(ERR_TUNNEL_CONNECTION_FAILED, rv); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, LargeContentLengthThenClose) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + + SessionDependencies session_deps; + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + MockRead data_reads[] = { + MockRead("HTTP/1.0 200 OK\r\nContent-Length:6719476739\r\n\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + + StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&data); + + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + EXPECT_EQ(OK, callback.WaitForResult()); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + + EXPECT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.0 200 OK", response->headers->GetStatusLine()); + + std::string response_data; + rv = ReadTransaction(trans.get(), &response_data); + EXPECT_EQ(ERR_CONNECTION_CLOSED, rv); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, UploadFileSmallerThanLength) { + HttpRequestInfo request; + request.method = "POST"; + request.url = GURL("http://www.google.com/upload"); + request.upload_data = new UploadData; + request.load_flags = 0; + + SessionDependencies session_deps; + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + FilePath temp_file_path; + ASSERT_TRUE(file_util::CreateTemporaryFile(&temp_file_path)); + const uint64 kFakeSize = 100000; // file is actually blank + + std::vector<UploadData::Element> elements; + UploadData::Element element; + element.SetToFilePath(temp_file_path); + element.SetContentLength(kFakeSize); + elements.push_back(element); + request.upload_data->SetElements(elements); + EXPECT_EQ(kFakeSize, request.upload_data->GetContentLengthSync()); + + MockRead data_reads[] = { + MockRead("HTTP/1.0 200 OK\r\n\r\n"), + MockRead("hello world"), + MockRead(SYNCHRONOUS, OK), + }; + StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&data); + + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + + EXPECT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.0 200 OK", response->headers->GetStatusLine()); + + std::string response_data; + rv = ReadTransaction(trans.get(), &response_data); + EXPECT_EQ(OK, rv); + EXPECT_EQ("hello world", response_data); + + file_util::Delete(temp_file_path, false); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, UploadUnreadableFile) { + HttpRequestInfo request; + request.method = "POST"; + request.url = GURL("http://www.google.com/upload"); + request.upload_data = new UploadData; + request.load_flags = 0; + + // If we try to upload an unreadable file, the network stack should report + // the file size as zero and upload zero bytes for that file. + SessionDependencies session_deps; + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + FilePath temp_file; + ASSERT_TRUE(file_util::CreateTemporaryFile(&temp_file)); + std::string temp_file_content("Unreadable file."); + ASSERT_TRUE(file_util::WriteFile(temp_file, temp_file_content.c_str(), + temp_file_content.length())); + ASSERT_TRUE(file_util::MakeFileUnreadable(temp_file)); + + std::vector<UploadData::Element> elements; + UploadData::Element element; + element.SetToFilePath(temp_file); + elements.push_back(element); + request.upload_data->SetElements(elements); + + MockRead data_reads[] = { + MockRead("HTTP/1.0 200 OK\r\n\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + MockWrite data_writes[] = { + MockWrite("POST /upload HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n" + "Content-Length: 0\r\n\r\n"), + MockWrite(SYNCHRONOUS, OK), + }; + StaticSocketDataProvider data(data_reads, arraysize(data_reads), data_writes, + arraysize(data_writes)); + session_deps.socket_factory.AddSocketDataProvider(&data); + + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.0 200 OK", response->headers->GetStatusLine()); + + file_util::Delete(temp_file, false); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, UnreadableUploadFileAfterAuthRestart) { + HttpRequestInfo request; + request.method = "POST"; + request.url = GURL("http://www.google.com/upload"); + request.upload_data = new UploadData; + request.load_flags = 0; + + SessionDependencies session_deps; + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + FilePath temp_file; + ASSERT_TRUE(file_util::CreateTemporaryFile(&temp_file)); + std::string temp_file_contents("Unreadable file."); + std::string unreadable_contents(temp_file_contents.length(), '\0'); + ASSERT_TRUE(file_util::WriteFile(temp_file, temp_file_contents.c_str(), + temp_file_contents.length())); + + std::vector<UploadData::Element> elements; + UploadData::Element element; + element.SetToFilePath(temp_file); + elements.push_back(element); + request.upload_data->SetElements(elements); + + MockRead data_reads[] = { + MockRead("HTTP/1.1 401 Unauthorized\r\n"), + MockRead("WWW-Authenticate: Basic realm=\"MyRealm1\"\r\n"), + MockRead("Content-Length: 0\r\n\r\n"), // No response body. + + MockRead("HTTP/1.1 200 OK\r\n"), + MockRead("Content-Length: 0\r\n\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + MockWrite data_writes[] = { + MockWrite("POST /upload HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n" + "Content-Length: 16\r\n\r\n"), + MockWrite(SYNCHRONOUS, temp_file_contents.c_str()), + + MockWrite("POST /upload HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n" + "Content-Length: 16\r\n" + "Authorization: Basic Zm9vOmJhcg==\r\n\r\n"), + MockWrite(SYNCHRONOUS, unreadable_contents.c_str(), + temp_file_contents.length()), + MockWrite(SYNCHRONOUS, OK), + }; + StaticSocketDataProvider data(data_reads, arraysize(data_reads), data_writes, + arraysize(data_writes)); + session_deps.socket_factory.AddSocketDataProvider(&data); + + TestCompletionCallback callback1; + + int rv = trans->Start(&request, callback1.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 401 Unauthorized", response->headers->GetStatusLine()); + EXPECT_TRUE(CheckBasicServerAuth(response->auth_challenge.get())); + + // Now make the file unreadable and try again. + ASSERT_TRUE(file_util::MakeFileUnreadable(temp_file)); + + TestCompletionCallback callback2; + + rv = trans->RestartWithAuth( + AuthCredentials(kFoo, kBar), callback2.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback2.WaitForResult(); + EXPECT_EQ(OK, rv); + + response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(response->headers != NULL); + EXPECT_TRUE(response->auth_challenge.get() == NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + + file_util::Delete(temp_file, false); +} + +// Tests that changes to Auth realms are treated like auth rejections. +TEST_F(HttpNetworkTransactionSpdy2Test, ChangeAuthRealms) { + SessionDependencies session_deps; + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + + // First transaction will request a resource and receive a Basic challenge + // with realm="first_realm". + MockWrite data_writes1[] = { + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n" + "\r\n"), + }; + MockRead data_reads1[] = { + MockRead("HTTP/1.1 401 Unauthorized\r\n" + "WWW-Authenticate: Basic realm=\"first_realm\"\r\n" + "\r\n"), + }; + + // After calling trans->RestartWithAuth(), provide an Authentication header + // for first_realm. The server will reject and provide a challenge with + // second_realm. + MockWrite data_writes2[] = { + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n" + "Authorization: Basic Zmlyc3Q6YmF6\r\n" + "\r\n"), + }; + MockRead data_reads2[] = { + MockRead("HTTP/1.1 401 Unauthorized\r\n" + "WWW-Authenticate: Basic realm=\"second_realm\"\r\n" + "\r\n"), + }; + + // This again fails, and goes back to first_realm. Make sure that the + // entry is removed from cache. + MockWrite data_writes3[] = { + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n" + "Authorization: Basic c2Vjb25kOmZvdQ==\r\n" + "\r\n"), + }; + MockRead data_reads3[] = { + MockRead("HTTP/1.1 401 Unauthorized\r\n" + "WWW-Authenticate: Basic realm=\"first_realm\"\r\n" + "\r\n"), + }; + + // Try one last time (with the correct password) and get the resource. + MockWrite data_writes4[] = { + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n" + "Authorization: Basic Zmlyc3Q6YmFy\r\n" + "\r\n"), + }; + MockRead data_reads4[] = { + MockRead("HTTP/1.1 200 OK\r\n" + "Content-Type: text/html; charset=iso-8859-1\r\n" + "Content-Length: 5\r\n" + "\r\n" + "hello"), + }; + + StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), + data_writes1, arraysize(data_writes1)); + StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2), + data_writes2, arraysize(data_writes2)); + StaticSocketDataProvider data3(data_reads3, arraysize(data_reads3), + data_writes3, arraysize(data_writes3)); + StaticSocketDataProvider data4(data_reads4, arraysize(data_reads4), + data_writes4, arraysize(data_writes4)); + session_deps.socket_factory.AddSocketDataProvider(&data1); + session_deps.socket_factory.AddSocketDataProvider(&data2); + session_deps.socket_factory.AddSocketDataProvider(&data3); + session_deps.socket_factory.AddSocketDataProvider(&data4); + + TestCompletionCallback callback1; + + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + // Issue the first request with Authorize headers. There should be a + // password prompt for first_realm waiting to be filled in after the + // transaction completes. + int rv = trans->Start(&request, callback1.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback1.WaitForResult(); + EXPECT_EQ(OK, rv); + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + const AuthChallengeInfo* challenge = response->auth_challenge.get(); + ASSERT_FALSE(challenge == NULL); + EXPECT_FALSE(challenge->is_proxy); + EXPECT_EQ("www.google.com:80", challenge->challenger.ToString()); + EXPECT_EQ("first_realm", challenge->realm); + EXPECT_EQ("basic", challenge->scheme); + + // Issue the second request with an incorrect password. There should be a + // password prompt for second_realm waiting to be filled in after the + // transaction completes. + TestCompletionCallback callback2; + rv = trans->RestartWithAuth( + AuthCredentials(kFirst, kBaz), callback2.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback2.WaitForResult(); + EXPECT_EQ(OK, rv); + response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + challenge = response->auth_challenge.get(); + ASSERT_FALSE(challenge == NULL); + EXPECT_FALSE(challenge->is_proxy); + EXPECT_EQ("www.google.com:80", challenge->challenger.ToString()); + EXPECT_EQ("second_realm", challenge->realm); + EXPECT_EQ("basic", challenge->scheme); + + // Issue the third request with another incorrect password. There should be + // a password prompt for first_realm waiting to be filled in. If the password + // prompt is not present, it indicates that the HttpAuthCacheEntry for + // first_realm was not correctly removed. + TestCompletionCallback callback3; + rv = trans->RestartWithAuth( + AuthCredentials(kSecond, kFou), callback3.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback3.WaitForResult(); + EXPECT_EQ(OK, rv); + response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + challenge = response->auth_challenge.get(); + ASSERT_FALSE(challenge == NULL); + EXPECT_FALSE(challenge->is_proxy); + EXPECT_EQ("www.google.com:80", challenge->challenger.ToString()); + EXPECT_EQ("first_realm", challenge->realm); + EXPECT_EQ("basic", challenge->scheme); + + // Issue the fourth request with the correct password and username. + TestCompletionCallback callback4; + rv = trans->RestartWithAuth( + AuthCredentials(kFirst, kBar), callback4.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback4.WaitForResult(); + EXPECT_EQ(OK, rv); + response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(response->auth_challenge.get() == NULL); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, HonorAlternateProtocolHeader) { + HttpStreamFactory::SetNextProtos(SpdyNextProtos()); + HttpStreamFactory::set_use_alternate_protocols(true); + + SessionDependencies session_deps; + + MockRead data_reads[] = { + MockRead("HTTP/1.1 200 OK\r\n"), + MockRead(kAlternateProtocolHttpHeader), + MockRead("hello world"), + MockRead(SYNCHRONOUS, OK), + }; + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + + StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0); + + session_deps.socket_factory.AddSocketDataProvider(&data); + + TestCompletionCallback callback; + + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + HostPortPair http_host_port_pair("www.google.com", 80); + const HttpServerProperties& http_server_properties = + *session->http_server_properties(); + EXPECT_FALSE( + http_server_properties.HasAlternateProtocol(http_host_port_pair)); + + EXPECT_EQ(OK, callback.WaitForResult()); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + EXPECT_FALSE(response->was_fetched_via_spdy); + EXPECT_FALSE(response->was_npn_negotiated); + + std::string response_data; + ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data)); + EXPECT_EQ("hello world", response_data); + + ASSERT_TRUE(http_server_properties.HasAlternateProtocol(http_host_port_pair)); + const PortAlternateProtocolPair alternate = + http_server_properties.GetAlternateProtocol(http_host_port_pair); + PortAlternateProtocolPair expected_alternate; + expected_alternate.port = 443; + expected_alternate.protocol = NPN_SPDY_2; + EXPECT_TRUE(expected_alternate.Equals(alternate)); + + HttpStreamFactory::set_use_alternate_protocols(false); + HttpStreamFactory::SetNextProtos(std::vector<std::string>()); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, + MarkBrokenAlternateProtocolAndFallback) { + HttpStreamFactory::set_use_alternate_protocols(true); + SessionDependencies session_deps; + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + + MockConnect mock_connect(ASYNC, ERR_CONNECTION_REFUSED); + StaticSocketDataProvider first_data; + first_data.set_connect_data(mock_connect); + session_deps.socket_factory.AddSocketDataProvider(&first_data); + + MockRead data_reads[] = { + MockRead("HTTP/1.1 200 OK\r\n\r\n"), + MockRead("hello world"), + MockRead(ASYNC, OK), + }; + StaticSocketDataProvider second_data( + data_reads, arraysize(data_reads), NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&second_data); + + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + HttpServerProperties* http_server_properties = + session->http_server_properties(); + // Port must be < 1024, or the header will be ignored (since initial port was + // port 80 (another restricted port). + http_server_properties->SetAlternateProtocol( + HostPortPair::FromURL(request.url), + 666 /* port is ignored by MockConnect anyway */, + NPN_SPDY_2); + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_EQ(OK, callback.WaitForResult()); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + + std::string response_data; + ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data)); + EXPECT_EQ("hello world", response_data); + + ASSERT_TRUE(http_server_properties->HasAlternateProtocol( + HostPortPair::FromURL(request.url))); + const PortAlternateProtocolPair alternate = + http_server_properties->GetAlternateProtocol( + HostPortPair::FromURL(request.url)); + EXPECT_EQ(ALTERNATE_PROTOCOL_BROKEN, alternate.protocol); + HttpStreamFactory::set_use_alternate_protocols(false); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, + AlternateProtocolPortRestrictedBlocked) { + // Ensure that we're not allowed to redirect traffic via an alternate + // protocol to an unrestricted (port >= 1024) when the original traffic was + // on a restricted port (port < 1024). Ensure that we can redirect in all + // other cases. + HttpStreamFactory::set_use_alternate_protocols(true); + SessionDependencies session_deps; + + HttpRequestInfo restricted_port_request; + restricted_port_request.method = "GET"; + restricted_port_request.url = GURL("http://www.google.com:1023/"); + restricted_port_request.load_flags = 0; + + MockConnect mock_connect(ASYNC, ERR_CONNECTION_REFUSED); + StaticSocketDataProvider first_data; + first_data.set_connect_data(mock_connect); + session_deps.socket_factory.AddSocketDataProvider(&first_data); + + MockRead data_reads[] = { + MockRead("HTTP/1.1 200 OK\r\n\r\n"), + MockRead("hello world"), + MockRead(ASYNC, OK), + }; + StaticSocketDataProvider second_data( + data_reads, arraysize(data_reads), NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&second_data); + + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + HttpServerProperties* http_server_properties = + session->http_server_properties(); + const int kUnrestrictedAlternatePort = 1024; + http_server_properties->SetAlternateProtocol( + HostPortPair::FromURL(restricted_port_request.url), + kUnrestrictedAlternatePort, + NPN_SPDY_2); + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + TestCompletionCallback callback; + + int rv = trans->Start( + &restricted_port_request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + // Invalid change to unrestricted port should fail. + EXPECT_EQ(ERR_CONNECTION_REFUSED, callback.WaitForResult()); + + HttpStreamFactory::set_use_alternate_protocols(false); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, + AlternateProtocolPortRestrictedAllowed) { + // Ensure that we're not allowed to redirect traffic via an alternate + // protocol to an unrestricted (port >= 1024) when the original traffic was + // on a restricted port (port < 1024). Ensure that we can redirect in all + // other cases. + HttpStreamFactory::set_use_alternate_protocols(true); + SessionDependencies session_deps; + + HttpRequestInfo restricted_port_request; + restricted_port_request.method = "GET"; + restricted_port_request.url = GURL("http://www.google.com:1023/"); + restricted_port_request.load_flags = 0; + + MockConnect mock_connect(ASYNC, ERR_CONNECTION_REFUSED); + StaticSocketDataProvider first_data; + first_data.set_connect_data(mock_connect); + session_deps.socket_factory.AddSocketDataProvider(&first_data); + + MockRead data_reads[] = { + MockRead("HTTP/1.1 200 OK\r\n\r\n"), + MockRead("hello world"), + MockRead(ASYNC, OK), + }; + StaticSocketDataProvider second_data( + data_reads, arraysize(data_reads), NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&second_data); + + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + HttpServerProperties* http_server_properties = + session->http_server_properties(); + const int kRestrictedAlternatePort = 80; + http_server_properties->SetAlternateProtocol( + HostPortPair::FromURL(restricted_port_request.url), + kRestrictedAlternatePort, + NPN_SPDY_2); + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + TestCompletionCallback callback; + + int rv = trans->Start( + &restricted_port_request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + // Valid change to restricted port should pass. + EXPECT_EQ(OK, callback.WaitForResult()); + + HttpStreamFactory::set_use_alternate_protocols(false); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, + AlternateProtocolPortUnrestrictedAllowed1) { + // Ensure that we're not allowed to redirect traffic via an alternate + // protocol to an unrestricted (port >= 1024) when the original traffic was + // on a restricted port (port < 1024). Ensure that we can redirect in all + // other cases. + HttpStreamFactory::set_use_alternate_protocols(true); + SessionDependencies session_deps; + + HttpRequestInfo unrestricted_port_request; + unrestricted_port_request.method = "GET"; + unrestricted_port_request.url = GURL("http://www.google.com:1024/"); + unrestricted_port_request.load_flags = 0; + + MockConnect mock_connect(ASYNC, ERR_CONNECTION_REFUSED); + StaticSocketDataProvider first_data; + first_data.set_connect_data(mock_connect); + session_deps.socket_factory.AddSocketDataProvider(&first_data); + + MockRead data_reads[] = { + MockRead("HTTP/1.1 200 OK\r\n\r\n"), + MockRead("hello world"), + MockRead(ASYNC, OK), + }; + StaticSocketDataProvider second_data( + data_reads, arraysize(data_reads), NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&second_data); + + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + HttpServerProperties* http_server_properties = + session->http_server_properties(); + const int kRestrictedAlternatePort = 80; + http_server_properties->SetAlternateProtocol( + HostPortPair::FromURL(unrestricted_port_request.url), + kRestrictedAlternatePort, + NPN_SPDY_2); + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + TestCompletionCallback callback; + + int rv = trans->Start( + &unrestricted_port_request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + // Valid change to restricted port should pass. + EXPECT_EQ(OK, callback.WaitForResult()); + + HttpStreamFactory::set_use_alternate_protocols(false); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, + AlternateProtocolPortUnrestrictedAllowed2) { + // Ensure that we're not allowed to redirect traffic via an alternate + // protocol to an unrestricted (port >= 1024) when the original traffic was + // on a restricted port (port < 1024). Ensure that we can redirect in all + // other cases. + HttpStreamFactory::set_use_alternate_protocols(true); + SessionDependencies session_deps; + + HttpRequestInfo unrestricted_port_request; + unrestricted_port_request.method = "GET"; + unrestricted_port_request.url = GURL("http://www.google.com:1024/"); + unrestricted_port_request.load_flags = 0; + + MockConnect mock_connect(ASYNC, ERR_CONNECTION_REFUSED); + StaticSocketDataProvider first_data; + first_data.set_connect_data(mock_connect); + session_deps.socket_factory.AddSocketDataProvider(&first_data); + + MockRead data_reads[] = { + MockRead("HTTP/1.1 200 OK\r\n\r\n"), + MockRead("hello world"), + MockRead(ASYNC, OK), + }; + StaticSocketDataProvider second_data( + data_reads, arraysize(data_reads), NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&second_data); + + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + HttpServerProperties* http_server_properties = + session->http_server_properties(); + const int kUnrestrictedAlternatePort = 1024; + http_server_properties->SetAlternateProtocol( + HostPortPair::FromURL(unrestricted_port_request.url), + kUnrestrictedAlternatePort, + NPN_SPDY_2); + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + TestCompletionCallback callback; + + int rv = trans->Start( + &unrestricted_port_request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + // Valid change to an unrestricted port should pass. + EXPECT_EQ(OK, callback.WaitForResult()); + + HttpStreamFactory::set_use_alternate_protocols(false); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, + AlternateProtocolUnsafeBlocked) { + // Ensure that we're not allowed to redirect traffic via an alternate + // protocol to an unsafe port, and that we resume the second + // HttpStreamFactoryImpl::Job once the alternate protocol request fails. + HttpStreamFactory::set_use_alternate_protocols(true); + SessionDependencies session_deps; + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + + // The alternate protocol request will error out before we attempt to connect, + // so only the standard HTTP request will try to connect. + MockRead data_reads[] = { + MockRead("HTTP/1.1 200 OK\r\n\r\n"), + MockRead("hello world"), + MockRead(ASYNC, OK), + }; + StaticSocketDataProvider data( + data_reads, arraysize(data_reads), NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&data); + + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + HttpServerProperties* http_server_properties = + session->http_server_properties(); + const int kUnsafePort = 7; + http_server_properties->SetAlternateProtocol( + HostPortPair::FromURL(request.url), + kUnsafePort, + NPN_SPDY_2); + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + // The HTTP request should succeed. + EXPECT_EQ(OK, callback.WaitForResult()); + + // Disable alternate protocol before the asserts. + HttpStreamFactory::set_use_alternate_protocols(false); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + + std::string response_data; + ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data)); + EXPECT_EQ("hello world", response_data); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, UseAlternateProtocolForNpnSpdy) { + HttpStreamFactory::set_use_alternate_protocols(true); + HttpStreamFactory::SetNextProtos(SpdyNextProtos()); + SessionDependencies session_deps; + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + + MockRead data_reads[] = { + MockRead("HTTP/1.1 200 OK\r\n"), + MockRead(kAlternateProtocolHttpHeader), + MockRead("hello world"), + MockRead(ASYNC, OK), + }; + + StaticSocketDataProvider first_transaction( + data_reads, arraysize(data_reads), NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&first_transaction); + + SSLSocketDataProvider ssl(ASYNC, OK); + ssl.SetNextProto(SSLClientSocket::kProtoSPDY2); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); + + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + MockWrite spdy_writes[] = { CreateMockWrite(*req) }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> data(ConstructSpdyBodyFrame(1, true)); + MockRead spdy_reads[] = { + CreateMockRead(*resp), + CreateMockRead(*data), + MockRead(ASYNC, 0, 0), + }; + + scoped_ptr<DelayedSocketData> spdy_data( + new DelayedSocketData( + 1, // wait for one write to finish before reading. + spdy_reads, arraysize(spdy_reads), + spdy_writes, arraysize(spdy_writes))); + session_deps.socket_factory.AddSocketDataProvider(spdy_data.get()); + + MockConnect never_finishing_connect(SYNCHRONOUS, ERR_IO_PENDING); + StaticSocketDataProvider hanging_non_alternate_protocol_socket( + NULL, 0, NULL, 0); + hanging_non_alternate_protocol_socket.set_connect_data( + never_finishing_connect); + session_deps.socket_factory.AddSocketDataProvider( + &hanging_non_alternate_protocol_socket); + + TestCompletionCallback callback; + + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + scoped_ptr<HttpNetworkTransaction> trans(new HttpNetworkTransaction(session)); + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_EQ(OK, callback.WaitForResult()); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + + std::string response_data; + ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data)); + EXPECT_EQ("hello world", response_data); + + trans.reset(new HttpNetworkTransaction(session)); + + rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_EQ(OK, callback.WaitForResult()); + + response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + EXPECT_TRUE(response->was_fetched_via_spdy); + EXPECT_TRUE(response->was_npn_negotiated); + + ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data)); + EXPECT_EQ("hello!", response_data); + + HttpStreamFactory::SetNextProtos(std::vector<std::string>()); + HttpStreamFactory::set_use_alternate_protocols(false); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, AlternateProtocolWithSpdyLateBinding) { + HttpStreamFactory::set_use_alternate_protocols(true); + HttpStreamFactory::SetNextProtos(SpdyNextProtos()); + SessionDependencies session_deps; + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + + MockRead data_reads[] = { + MockRead("HTTP/1.1 200 OK\r\n"), + MockRead(kAlternateProtocolHttpHeader), + MockRead("hello world"), + MockRead(ASYNC, OK), + }; + + StaticSocketDataProvider first_transaction( + data_reads, arraysize(data_reads), NULL, 0); + // Socket 1 is the HTTP transaction with the Alternate-Protocol header. + session_deps.socket_factory.AddSocketDataProvider(&first_transaction); + + MockConnect never_finishing_connect(SYNCHRONOUS, ERR_IO_PENDING); + StaticSocketDataProvider hanging_socket( + NULL, 0, NULL, 0); + hanging_socket.set_connect_data(never_finishing_connect); + // Socket 2 and 3 are the hanging Alternate-Protocol and + // non-Alternate-Protocol jobs from the 2nd transaction. + session_deps.socket_factory.AddSocketDataProvider(&hanging_socket); + session_deps.socket_factory.AddSocketDataProvider(&hanging_socket); + + SSLSocketDataProvider ssl(ASYNC, OK); + ssl.SetNextProto(SSLClientSocket::kProtoSPDY2); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); + + scoped_ptr<spdy::SpdyFrame> req1(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 3, LOWEST)); + MockWrite spdy_writes[] = { + CreateMockWrite(*req1), + CreateMockWrite(*req2), + }; + scoped_ptr<spdy::SpdyFrame> resp1(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> data1(ConstructSpdyBodyFrame(1, true)); + scoped_ptr<spdy::SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 3)); + scoped_ptr<spdy::SpdyFrame> data2(ConstructSpdyBodyFrame(3, true)); + MockRead spdy_reads[] = { + CreateMockRead(*resp1), + CreateMockRead(*data1), + CreateMockRead(*resp2), + CreateMockRead(*data2), + MockRead(ASYNC, 0, 0), + }; + + scoped_ptr<DelayedSocketData> spdy_data( + new DelayedSocketData( + 2, // wait for writes to finish before reading. + spdy_reads, arraysize(spdy_reads), + spdy_writes, arraysize(spdy_writes))); + // Socket 4 is the successful Alternate-Protocol for transaction 3. + session_deps.socket_factory.AddSocketDataProvider(spdy_data.get()); + + // Socket 5 is the unsuccessful non-Alternate-Protocol for transaction 3. + session_deps.socket_factory.AddSocketDataProvider(&hanging_socket); + + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + TestCompletionCallback callback1; + HttpNetworkTransaction trans1(session); + + int rv = trans1.Start(&request, callback1.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_EQ(OK, callback1.WaitForResult()); + + const HttpResponseInfo* response = trans1.GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + + std::string response_data; + ASSERT_EQ(OK, ReadTransaction(&trans1, &response_data)); + EXPECT_EQ("hello world", response_data); + + TestCompletionCallback callback2; + HttpNetworkTransaction trans2(session); + rv = trans2.Start(&request, callback2.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + TestCompletionCallback callback3; + HttpNetworkTransaction trans3(session); + rv = trans3.Start(&request, callback3.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + EXPECT_EQ(OK, callback2.WaitForResult()); + EXPECT_EQ(OK, callback3.WaitForResult()); + + response = trans2.GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + EXPECT_TRUE(response->was_fetched_via_spdy); + EXPECT_TRUE(response->was_npn_negotiated); + ASSERT_EQ(OK, ReadTransaction(&trans2, &response_data)); + EXPECT_EQ("hello!", response_data); + + response = trans3.GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + EXPECT_TRUE(response->was_fetched_via_spdy); + EXPECT_TRUE(response->was_npn_negotiated); + ASSERT_EQ(OK, ReadTransaction(&trans3, &response_data)); + EXPECT_EQ("hello!", response_data); + + HttpStreamFactory::SetNextProtos(std::vector<std::string>()); + HttpStreamFactory::set_use_alternate_protocols(false); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, StallAlternateProtocolForNpnSpdy) { + HttpStreamFactory::set_use_alternate_protocols(true); + HttpStreamFactory::SetNextProtos(SpdyNextProtos()); + SessionDependencies session_deps; + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + + MockRead data_reads[] = { + MockRead("HTTP/1.1 200 OK\r\n"), + MockRead(kAlternateProtocolHttpHeader), + MockRead("hello world"), + MockRead(ASYNC, OK), + }; + + StaticSocketDataProvider first_transaction( + data_reads, arraysize(data_reads), NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&first_transaction); + + SSLSocketDataProvider ssl(ASYNC, OK); + ssl.SetNextProto(SSLClientSocket::kProtoSPDY2); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); + + MockConnect never_finishing_connect(SYNCHRONOUS, ERR_IO_PENDING); + StaticSocketDataProvider hanging_alternate_protocol_socket( + NULL, 0, NULL, 0); + hanging_alternate_protocol_socket.set_connect_data( + never_finishing_connect); + session_deps.socket_factory.AddSocketDataProvider( + &hanging_alternate_protocol_socket); + + // 2nd request is just a copy of the first one, over HTTP again. + session_deps.socket_factory.AddSocketDataProvider(&first_transaction); + + TestCompletionCallback callback; + + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + scoped_ptr<HttpNetworkTransaction> trans(new HttpNetworkTransaction(session)); + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_EQ(OK, callback.WaitForResult()); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + + std::string response_data; + ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data)); + EXPECT_EQ("hello world", response_data); + + trans.reset(new HttpNetworkTransaction(session)); + + rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_EQ(OK, callback.WaitForResult()); + + response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + EXPECT_FALSE(response->was_fetched_via_spdy); + EXPECT_FALSE(response->was_npn_negotiated); + + ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data)); + EXPECT_EQ("hello world", response_data); + + HttpStreamFactory::SetNextProtos(std::vector<std::string>()); + HttpStreamFactory::set_use_alternate_protocols(false); +} + +class CapturingProxyResolver : public ProxyResolver { + public: + CapturingProxyResolver() : ProxyResolver(false /* expects_pac_bytes */) {} + virtual ~CapturingProxyResolver() {} + + virtual int GetProxyForURL(const GURL& url, + ProxyInfo* results, + const CompletionCallback& callback, + RequestHandle* request, + const BoundNetLog& net_log) { + ProxyServer proxy_server(ProxyServer::SCHEME_HTTP, + HostPortPair("myproxy", 80)); + results->UseProxyServer(proxy_server); + resolved_.push_back(url); + return OK; + } + + virtual void CancelRequest(RequestHandle request) { + NOTREACHED(); + } + + virtual LoadState GetLoadState(RequestHandle request) const OVERRIDE { + NOTREACHED(); + return LOAD_STATE_IDLE; + } + + virtual LoadState GetLoadStateThreadSafe( + RequestHandle request) const OVERRIDE { + NOTREACHED(); + return LOAD_STATE_IDLE; + } + + virtual void CancelSetPacScript() { + NOTREACHED(); + } + + virtual int SetPacScript(const scoped_refptr<ProxyResolverScriptData>&, + const CompletionCallback& /*callback*/) { + return OK; + } + + const std::vector<GURL>& resolved() const { return resolved_; } + + private: + std::vector<GURL> resolved_; + + DISALLOW_COPY_AND_ASSIGN(CapturingProxyResolver); +}; + +TEST_F(HttpNetworkTransactionSpdy2Test, + UseAlternateProtocolForTunneledNpnSpdy) { + HttpStreamFactory::set_use_alternate_protocols(true); + HttpStreamFactory::SetNextProtos(SpdyNextProtos()); + + ProxyConfig proxy_config; + proxy_config.set_auto_detect(true); + proxy_config.set_pac_url(GURL("http://fooproxyurl")); + + CapturingProxyResolver* capturing_proxy_resolver = + new CapturingProxyResolver(); + SessionDependencies session_deps(new ProxyService( + new ProxyConfigServiceFixed(proxy_config), capturing_proxy_resolver, + NULL)); + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + + MockRead data_reads[] = { + MockRead("HTTP/1.1 200 OK\r\n"), + MockRead(kAlternateProtocolHttpHeader), + MockRead("hello world"), + MockRead(ASYNC, OK), + }; + + StaticSocketDataProvider first_transaction( + data_reads, arraysize(data_reads), NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&first_transaction); + + SSLSocketDataProvider ssl(ASYNC, OK); + ssl.SetNextProto(SSLClientSocket::kProtoSPDY2); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); + + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + MockWrite spdy_writes[] = { + MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n\r\n"), // 0 + CreateMockWrite(*req) // 3 + }; + + const char kCONNECTResponse[] = "HTTP/1.1 200 Connected\r\n\r\n"; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> data(ConstructSpdyBodyFrame(1, true)); + MockRead spdy_reads[] = { + MockRead(ASYNC, kCONNECTResponse, arraysize(kCONNECTResponse) - 1, 1), // 1 + CreateMockRead(*resp.get(), 4), // 2, 4 + CreateMockRead(*data.get(), 4), // 5 + MockRead(ASYNC, 0, 0, 4), // 6 + }; + + scoped_ptr<OrderedSocketData> spdy_data( + new OrderedSocketData( + spdy_reads, arraysize(spdy_reads), + spdy_writes, arraysize(spdy_writes))); + session_deps.socket_factory.AddSocketDataProvider(spdy_data.get()); + + MockConnect never_finishing_connect(SYNCHRONOUS, ERR_IO_PENDING); + StaticSocketDataProvider hanging_non_alternate_protocol_socket( + NULL, 0, NULL, 0); + hanging_non_alternate_protocol_socket.set_connect_data( + never_finishing_connect); + session_deps.socket_factory.AddSocketDataProvider( + &hanging_non_alternate_protocol_socket); + + TestCompletionCallback callback; + + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + scoped_ptr<HttpNetworkTransaction> trans(new HttpNetworkTransaction(session)); + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_EQ(OK, callback.WaitForResult()); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + EXPECT_FALSE(response->was_fetched_via_spdy); + EXPECT_FALSE(response->was_npn_negotiated); + + std::string response_data; + ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data)); + EXPECT_EQ("hello world", response_data); + + trans.reset(new HttpNetworkTransaction(session)); + + rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_EQ(OK, callback.WaitForResult()); + + response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + EXPECT_TRUE(response->was_fetched_via_spdy); + EXPECT_TRUE(response->was_npn_negotiated); + + ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data)); + EXPECT_EQ("hello!", response_data); + ASSERT_EQ(3u, capturing_proxy_resolver->resolved().size()); + EXPECT_EQ("http://www.google.com/", + capturing_proxy_resolver->resolved()[0].spec()); + EXPECT_EQ("https://www.google.com/", + capturing_proxy_resolver->resolved()[1].spec()); + + HttpStreamFactory::SetNextProtos(std::vector<std::string>()); + HttpStreamFactory::set_use_alternate_protocols(false); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, + UseAlternateProtocolForNpnSpdyWithExistingSpdySession) { + HttpStreamFactory::set_use_alternate_protocols(true); + HttpStreamFactory::SetNextProtos(SpdyNextProtos()); + SessionDependencies session_deps; + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + + MockRead data_reads[] = { + MockRead("HTTP/1.1 200 OK\r\n"), + MockRead(kAlternateProtocolHttpHeader), + MockRead("hello world"), + MockRead(ASYNC, OK), + }; + + StaticSocketDataProvider first_transaction( + data_reads, arraysize(data_reads), NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&first_transaction); + + SSLSocketDataProvider ssl(ASYNC, OK); + ssl.SetNextProto(SSLClientSocket::kProtoSPDY2); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); + // Make sure we use ssl for spdy here. + SpdySession::SetSSLMode(true); + + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + MockWrite spdy_writes[] = { CreateMockWrite(*req) }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> data(ConstructSpdyBodyFrame(1, true)); + MockRead spdy_reads[] = { + CreateMockRead(*resp), + CreateMockRead(*data), + MockRead(ASYNC, 0, 0), + }; + + scoped_ptr<DelayedSocketData> spdy_data( + new DelayedSocketData( + 1, // wait for one write to finish before reading. + spdy_reads, arraysize(spdy_reads), + spdy_writes, arraysize(spdy_writes))); + session_deps.socket_factory.AddSocketDataProvider(spdy_data.get()); + + TestCompletionCallback callback; + + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + scoped_ptr<HttpNetworkTransaction> trans(new HttpNetworkTransaction(session)); + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_EQ(OK, callback.WaitForResult()); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + + std::string response_data; + ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data)); + EXPECT_EQ("hello world", response_data); + + // Set up an initial SpdySession in the pool to reuse. + HostPortPair host_port_pair("www.google.com", 443); + HostPortProxyPair pair(host_port_pair, ProxyServer::Direct()); + scoped_refptr<SpdySession> spdy_session = + session->spdy_session_pool()->Get(pair, BoundNetLog()); + scoped_refptr<TransportSocketParams> transport_params( + new TransportSocketParams(host_port_pair, MEDIUM, false, false)); + + scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle); + EXPECT_EQ(ERR_IO_PENDING, + connection->Init(host_port_pair.ToString(), + transport_params, + LOWEST, + callback.callback(), + session->GetTransportSocketPool(), + BoundNetLog())); + EXPECT_EQ(OK, callback.WaitForResult()); + + SSLConfig ssl_config; + session->ssl_config_service()->GetSSLConfig(&ssl_config); + scoped_ptr<ClientSocketHandle> ssl_connection(new ClientSocketHandle); + SSLClientSocketContext context; + context.cert_verifier = session_deps.cert_verifier.get(); + ssl_connection->set_socket(session_deps.socket_factory.CreateSSLClientSocket( + connection.release(), HostPortPair("" , 443), ssl_config, + NULL /* ssl_host_info */, context)); + EXPECT_EQ(ERR_IO_PENDING, + ssl_connection->socket()->Connect(callback.callback())); + EXPECT_EQ(OK, callback.WaitForResult()); + + EXPECT_EQ(OK, spdy_session->InitializeWithSocket(ssl_connection.release(), + true, OK)); + + trans.reset(new HttpNetworkTransaction(session)); + + rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_EQ(OK, callback.WaitForResult()); + + response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + EXPECT_TRUE(response->was_fetched_via_spdy); + EXPECT_TRUE(response->was_npn_negotiated); + + ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data)); + EXPECT_EQ("hello!", response_data); + + HttpStreamFactory::SetNextProtos(std::vector<std::string>()); + HttpStreamFactory::set_use_alternate_protocols(false); +} + +// GenerateAuthToken is a mighty big test. +// It tests all permutation of GenerateAuthToken behavior: +// - Synchronous and Asynchronous completion. +// - OK or error on completion. +// - Direct connection, non-authenticating proxy, and authenticating proxy. +// - HTTP or HTTPS backend (to include proxy tunneling). +// - Non-authenticating and authenticating backend. +// +// In all, there are 44 reasonable permuations (for example, if there are +// problems generating an auth token for an authenticating proxy, we don't +// need to test all permutations of the backend server). +// +// The test proceeds by going over each of the configuration cases, and +// potentially running up to three rounds in each of the tests. The TestConfig +// specifies both the configuration for the test as well as the expectations +// for the results. +TEST_F(HttpNetworkTransactionSpdy2Test, GenerateAuthToken) { + static const char kServer[] = "http://www.example.com"; + static const char kSecureServer[] = "https://www.example.com"; + static const char kProxy[] = "myproxy:70"; + const int kAuthErr = ERR_INVALID_AUTH_CREDENTIALS; + + enum AuthTiming { + AUTH_NONE, + AUTH_SYNC, + AUTH_ASYNC, + }; + + const MockWrite kGet( + "GET / HTTP/1.1\r\n" + "Host: www.example.com\r\n" + "Connection: keep-alive\r\n\r\n"); + const MockWrite kGetProxy( + "GET http://www.example.com/ HTTP/1.1\r\n" + "Host: www.example.com\r\n" + "Proxy-Connection: keep-alive\r\n\r\n"); + const MockWrite kGetAuth( + "GET / HTTP/1.1\r\n" + "Host: www.example.com\r\n" + "Connection: keep-alive\r\n" + "Authorization: auth_token\r\n\r\n"); + const MockWrite kGetProxyAuth( + "GET http://www.example.com/ HTTP/1.1\r\n" + "Host: www.example.com\r\n" + "Proxy-Connection: keep-alive\r\n" + "Proxy-Authorization: auth_token\r\n\r\n"); + const MockWrite kGetAuthThroughProxy( + "GET http://www.example.com/ HTTP/1.1\r\n" + "Host: www.example.com\r\n" + "Proxy-Connection: keep-alive\r\n" + "Authorization: auth_token\r\n\r\n"); + const MockWrite kGetAuthWithProxyAuth( + "GET http://www.example.com/ HTTP/1.1\r\n" + "Host: www.example.com\r\n" + "Proxy-Connection: keep-alive\r\n" + "Proxy-Authorization: auth_token\r\n" + "Authorization: auth_token\r\n\r\n"); + const MockWrite kConnect( + "CONNECT www.example.com:443 HTTP/1.1\r\n" + "Host: www.example.com\r\n" + "Proxy-Connection: keep-alive\r\n\r\n"); + const MockWrite kConnectProxyAuth( + "CONNECT www.example.com:443 HTTP/1.1\r\n" + "Host: www.example.com\r\n" + "Proxy-Connection: keep-alive\r\n" + "Proxy-Authorization: auth_token\r\n\r\n"); + + const MockRead kSuccess( + "HTTP/1.1 200 OK\r\n" + "Content-Type: text/html; charset=iso-8859-1\r\n" + "Content-Length: 3\r\n\r\n" + "Yes"); + const MockRead kFailure( + "Should not be called."); + const MockRead kServerChallenge( + "HTTP/1.1 401 Unauthorized\r\n" + "WWW-Authenticate: Mock realm=server\r\n" + "Content-Type: text/html; charset=iso-8859-1\r\n" + "Content-Length: 14\r\n\r\n" + "Unauthorized\r\n"); + const MockRead kProxyChallenge( + "HTTP/1.1 407 Unauthorized\r\n" + "Proxy-Authenticate: Mock realm=proxy\r\n" + "Proxy-Connection: close\r\n" + "Content-Type: text/html; charset=iso-8859-1\r\n" + "Content-Length: 14\r\n\r\n" + "Unauthorized\r\n"); + const MockRead kProxyConnected( + "HTTP/1.1 200 Connection Established\r\n\r\n"); + + // NOTE(cbentzel): I wanted TestReadWriteRound to be a simple struct with + // no constructors, but the C++ compiler on Windows warns about + // unspecified data in compound literals. So, moved to using constructors, + // and TestRound's created with the default constructor should not be used. + struct TestRound { + TestRound() + : expected_rv(ERR_UNEXPECTED), + extra_write(NULL), + extra_read(NULL) { + } + TestRound(const MockWrite& write_arg, const MockRead& read_arg, + int expected_rv_arg) + : write(write_arg), + read(read_arg), + expected_rv(expected_rv_arg), + extra_write(NULL), + extra_read(NULL) { + } + TestRound(const MockWrite& write_arg, const MockRead& read_arg, + int expected_rv_arg, const MockWrite* extra_write_arg, + const MockWrite* extra_read_arg) + : write(write_arg), + read(read_arg), + expected_rv(expected_rv_arg), + extra_write(extra_write_arg), + extra_read(extra_read_arg) { + } + MockWrite write; + MockRead read; + int expected_rv; + const MockWrite* extra_write; + const MockRead* extra_read; + }; + + static const int kNoSSL = 500; + + struct TestConfig { + const char* proxy_url; + AuthTiming proxy_auth_timing; + int proxy_auth_rv; + const char* server_url; + AuthTiming server_auth_timing; + int server_auth_rv; + int num_auth_rounds; + int first_ssl_round; + TestRound rounds[3]; + } test_configs[] = { + // Non-authenticating HTTP server with a direct connection. + { NULL, AUTH_NONE, OK, kServer, AUTH_NONE, OK, 1, kNoSSL, + { TestRound(kGet, kSuccess, OK)}}, + // Authenticating HTTP server with a direct connection. + { NULL, AUTH_NONE, OK, kServer, AUTH_SYNC, OK, 2, kNoSSL, + { TestRound(kGet, kServerChallenge, OK), + TestRound(kGetAuth, kSuccess, OK)}}, + { NULL, AUTH_NONE, OK, kServer, AUTH_SYNC, kAuthErr, 2, kNoSSL, + { TestRound(kGet, kServerChallenge, OK), + TestRound(kGetAuth, kFailure, kAuthErr)}}, + { NULL, AUTH_NONE, OK, kServer, AUTH_ASYNC, OK, 2, kNoSSL, + { TestRound(kGet, kServerChallenge, OK), + TestRound(kGetAuth, kSuccess, OK)}}, + { NULL, AUTH_NONE, OK, kServer, AUTH_ASYNC, kAuthErr, 2, kNoSSL, + { TestRound(kGet, kServerChallenge, OK), + TestRound(kGetAuth, kFailure, kAuthErr)}}, + // Non-authenticating HTTP server through a non-authenticating proxy. + { kProxy, AUTH_NONE, OK, kServer, AUTH_NONE, OK, 1, kNoSSL, + { TestRound(kGetProxy, kSuccess, OK)}}, + // Authenticating HTTP server through a non-authenticating proxy. + { kProxy, AUTH_NONE, OK, kServer, AUTH_SYNC, OK, 2, kNoSSL, + { TestRound(kGetProxy, kServerChallenge, OK), + TestRound(kGetAuthThroughProxy, kSuccess, OK)}}, + { kProxy, AUTH_NONE, OK, kServer, AUTH_SYNC, kAuthErr, 2, kNoSSL, + { TestRound(kGetProxy, kServerChallenge, OK), + TestRound(kGetAuthThroughProxy, kFailure, kAuthErr)}}, + { kProxy, AUTH_NONE, OK, kServer, AUTH_ASYNC, OK, 2, kNoSSL, + { TestRound(kGetProxy, kServerChallenge, OK), + TestRound(kGetAuthThroughProxy, kSuccess, OK)}}, + { kProxy, AUTH_NONE, OK, kServer, AUTH_ASYNC, kAuthErr, 2, kNoSSL, + { TestRound(kGetProxy, kServerChallenge, OK), + TestRound(kGetAuthThroughProxy, kFailure, kAuthErr)}}, + // Non-authenticating HTTP server through an authenticating proxy. + { kProxy, AUTH_SYNC, OK, kServer, AUTH_NONE, OK, 2, kNoSSL, + { TestRound(kGetProxy, kProxyChallenge, OK), + TestRound(kGetProxyAuth, kSuccess, OK)}}, + { kProxy, AUTH_SYNC, kAuthErr, kServer, AUTH_NONE, OK, 2, kNoSSL, + { TestRound(kGetProxy, kProxyChallenge, OK), + TestRound(kGetProxyAuth, kFailure, kAuthErr)}}, + { kProxy, AUTH_ASYNC, OK, kServer, AUTH_NONE, OK, 2, kNoSSL, + { TestRound(kGetProxy, kProxyChallenge, OK), + TestRound(kGetProxyAuth, kSuccess, OK)}}, + { kProxy, AUTH_ASYNC, kAuthErr, kServer, AUTH_NONE, OK, 2, kNoSSL, + { TestRound(kGetProxy, kProxyChallenge, OK), + TestRound(kGetProxyAuth, kFailure, kAuthErr)}}, + // Authenticating HTTP server through an authenticating proxy. + { kProxy, AUTH_SYNC, OK, kServer, AUTH_SYNC, OK, 3, kNoSSL, + { TestRound(kGetProxy, kProxyChallenge, OK), + TestRound(kGetProxyAuth, kServerChallenge, OK), + TestRound(kGetAuthWithProxyAuth, kSuccess, OK)}}, + { kProxy, AUTH_SYNC, OK, kServer, AUTH_SYNC, kAuthErr, 3, kNoSSL, + { TestRound(kGetProxy, kProxyChallenge, OK), + TestRound(kGetProxyAuth, kServerChallenge, OK), + TestRound(kGetAuthWithProxyAuth, kFailure, kAuthErr)}}, + { kProxy, AUTH_ASYNC, OK, kServer, AUTH_SYNC, OK, 3, kNoSSL, + { TestRound(kGetProxy, kProxyChallenge, OK), + TestRound(kGetProxyAuth, kServerChallenge, OK), + TestRound(kGetAuthWithProxyAuth, kSuccess, OK)}}, + { kProxy, AUTH_ASYNC, OK, kServer, AUTH_SYNC, kAuthErr, 3, kNoSSL, + { TestRound(kGetProxy, kProxyChallenge, OK), + TestRound(kGetProxyAuth, kServerChallenge, OK), + TestRound(kGetAuthWithProxyAuth, kFailure, kAuthErr)}}, + { kProxy, AUTH_SYNC, OK, kServer, AUTH_ASYNC, OK, 3, kNoSSL, + { TestRound(kGetProxy, kProxyChallenge, OK), + TestRound(kGetProxyAuth, kServerChallenge, OK), + TestRound(kGetAuthWithProxyAuth, kSuccess, OK)}}, + { kProxy, AUTH_SYNC, OK, kServer, AUTH_ASYNC, kAuthErr, 3, kNoSSL, + { TestRound(kGetProxy, kProxyChallenge, OK), + TestRound(kGetProxyAuth, kServerChallenge, OK), + TestRound(kGetAuthWithProxyAuth, kFailure, kAuthErr)}}, + { kProxy, AUTH_ASYNC, OK, kServer, AUTH_ASYNC, OK, 3, kNoSSL, + { TestRound(kGetProxy, kProxyChallenge, OK), + TestRound(kGetProxyAuth, kServerChallenge, OK), + TestRound(kGetAuthWithProxyAuth, kSuccess, OK)}}, + { kProxy, AUTH_ASYNC, OK, kServer, AUTH_ASYNC, kAuthErr, 3, kNoSSL, + { TestRound(kGetProxy, kProxyChallenge, OK), + TestRound(kGetProxyAuth, kServerChallenge, OK), + TestRound(kGetAuthWithProxyAuth, kFailure, kAuthErr)}}, + // Non-authenticating HTTPS server with a direct connection. + { NULL, AUTH_NONE, OK, kSecureServer, AUTH_NONE, OK, 1, 0, + { TestRound(kGet, kSuccess, OK)}}, + // Authenticating HTTPS server with a direct connection. + { NULL, AUTH_NONE, OK, kSecureServer, AUTH_SYNC, OK, 2, 0, + { TestRound(kGet, kServerChallenge, OK), + TestRound(kGetAuth, kSuccess, OK)}}, + { NULL, AUTH_NONE, OK, kSecureServer, AUTH_SYNC, kAuthErr, 2, 0, + { TestRound(kGet, kServerChallenge, OK), + TestRound(kGetAuth, kFailure, kAuthErr)}}, + { NULL, AUTH_NONE, OK, kSecureServer, AUTH_ASYNC, OK, 2, 0, + { TestRound(kGet, kServerChallenge, OK), + TestRound(kGetAuth, kSuccess, OK)}}, + { NULL, AUTH_NONE, OK, kSecureServer, AUTH_ASYNC, kAuthErr, 2, 0, + { TestRound(kGet, kServerChallenge, OK), + TestRound(kGetAuth, kFailure, kAuthErr)}}, + // Non-authenticating HTTPS server with a non-authenticating proxy. + { kProxy, AUTH_NONE, OK, kSecureServer, AUTH_NONE, OK, 1, 0, + { TestRound(kConnect, kProxyConnected, OK, &kGet, &kSuccess)}}, + // Authenticating HTTPS server through a non-authenticating proxy. + { kProxy, AUTH_NONE, OK, kSecureServer, AUTH_SYNC, OK, 2, 0, + { TestRound(kConnect, kProxyConnected, OK, &kGet, &kServerChallenge), + TestRound(kGetAuth, kSuccess, OK)}}, + { kProxy, AUTH_NONE, OK, kSecureServer, AUTH_SYNC, kAuthErr, 2, 0, + { TestRound(kConnect, kProxyConnected, OK, &kGet, &kServerChallenge), + TestRound(kGetAuth, kFailure, kAuthErr)}}, + { kProxy, AUTH_NONE, OK, kSecureServer, AUTH_ASYNC, OK, 2, 0, + { TestRound(kConnect, kProxyConnected, OK, &kGet, &kServerChallenge), + TestRound(kGetAuth, kSuccess, OK)}}, + { kProxy, AUTH_NONE, OK, kSecureServer, AUTH_ASYNC, kAuthErr, 2, 0, + { TestRound(kConnect, kProxyConnected, OK, &kGet, &kServerChallenge), + TestRound(kGetAuth, kFailure, kAuthErr)}}, + // Non-Authenticating HTTPS server through an authenticating proxy. + { kProxy, AUTH_SYNC, OK, kSecureServer, AUTH_NONE, OK, 2, 1, + { TestRound(kConnect, kProxyChallenge, OK), + TestRound(kConnectProxyAuth, kProxyConnected, OK, &kGet, &kSuccess)}}, + { kProxy, AUTH_SYNC, kAuthErr, kSecureServer, AUTH_NONE, OK, 2, kNoSSL, + { TestRound(kConnect, kProxyChallenge, OK), + TestRound(kConnectProxyAuth, kFailure, kAuthErr)}}, + { kProxy, AUTH_ASYNC, OK, kSecureServer, AUTH_NONE, OK, 2, 1, + { TestRound(kConnect, kProxyChallenge, OK), + TestRound(kConnectProxyAuth, kProxyConnected, OK, &kGet, &kSuccess)}}, + { kProxy, AUTH_ASYNC, kAuthErr, kSecureServer, AUTH_NONE, OK, 2, kNoSSL, + { TestRound(kConnect, kProxyChallenge, OK), + TestRound(kConnectProxyAuth, kFailure, kAuthErr)}}, + // Authenticating HTTPS server through an authenticating proxy. + { kProxy, AUTH_SYNC, OK, kSecureServer, AUTH_SYNC, OK, 3, 1, + { TestRound(kConnect, kProxyChallenge, OK), + TestRound(kConnectProxyAuth, kProxyConnected, OK, + &kGet, &kServerChallenge), + TestRound(kGetAuth, kSuccess, OK)}}, + { kProxy, AUTH_SYNC, OK, kSecureServer, AUTH_SYNC, kAuthErr, 3, 1, + { TestRound(kConnect, kProxyChallenge, OK), + TestRound(kConnectProxyAuth, kProxyConnected, OK, + &kGet, &kServerChallenge), + TestRound(kGetAuth, kFailure, kAuthErr)}}, + { kProxy, AUTH_ASYNC, OK, kSecureServer, AUTH_SYNC, OK, 3, 1, + { TestRound(kConnect, kProxyChallenge, OK), + TestRound(kConnectProxyAuth, kProxyConnected, OK, + &kGet, &kServerChallenge), + TestRound(kGetAuth, kSuccess, OK)}}, + { kProxy, AUTH_ASYNC, OK, kSecureServer, AUTH_SYNC, kAuthErr, 3, 1, + { TestRound(kConnect, kProxyChallenge, OK), + TestRound(kConnectProxyAuth, kProxyConnected, OK, + &kGet, &kServerChallenge), + TestRound(kGetAuth, kFailure, kAuthErr)}}, + { kProxy, AUTH_SYNC, OK, kSecureServer, AUTH_ASYNC, OK, 3, 1, + { TestRound(kConnect, kProxyChallenge, OK), + TestRound(kConnectProxyAuth, kProxyConnected, OK, + &kGet, &kServerChallenge), + TestRound(kGetAuth, kSuccess, OK)}}, + { kProxy, AUTH_SYNC, OK, kSecureServer, AUTH_ASYNC, kAuthErr, 3, 1, + { TestRound(kConnect, kProxyChallenge, OK), + TestRound(kConnectProxyAuth, kProxyConnected, OK, + &kGet, &kServerChallenge), + TestRound(kGetAuth, kFailure, kAuthErr)}}, + { kProxy, AUTH_ASYNC, OK, kSecureServer, AUTH_ASYNC, OK, 3, 1, + { TestRound(kConnect, kProxyChallenge, OK), + TestRound(kConnectProxyAuth, kProxyConnected, OK, + &kGet, &kServerChallenge), + TestRound(kGetAuth, kSuccess, OK)}}, + { kProxy, AUTH_ASYNC, OK, kSecureServer, AUTH_ASYNC, kAuthErr, 3, 1, + { TestRound(kConnect, kProxyChallenge, OK), + TestRound(kConnectProxyAuth, kProxyConnected, OK, + &kGet, &kServerChallenge), + TestRound(kGetAuth, kFailure, kAuthErr)}}, + }; + + SessionDependencies session_deps; + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_configs); ++i) { + HttpAuthHandlerMock::Factory* auth_factory( + new HttpAuthHandlerMock::Factory()); + session_deps.http_auth_handler_factory.reset(auth_factory); + const TestConfig& test_config = test_configs[i]; + + // Set up authentication handlers as necessary. + if (test_config.proxy_auth_timing != AUTH_NONE) { + for (int n = 0; n < 2; n++) { + HttpAuthHandlerMock* auth_handler(new HttpAuthHandlerMock()); + std::string auth_challenge = "Mock realm=proxy"; + GURL origin(test_config.proxy_url); + HttpAuth::ChallengeTokenizer tokenizer(auth_challenge.begin(), + auth_challenge.end()); + auth_handler->InitFromChallenge(&tokenizer, HttpAuth::AUTH_PROXY, + origin, BoundNetLog()); + auth_handler->SetGenerateExpectation( + test_config.proxy_auth_timing == AUTH_ASYNC, + test_config.proxy_auth_rv); + auth_factory->AddMockHandler(auth_handler, HttpAuth::AUTH_PROXY); + } + } + if (test_config.server_auth_timing != AUTH_NONE) { + HttpAuthHandlerMock* auth_handler(new HttpAuthHandlerMock()); + std::string auth_challenge = "Mock realm=server"; + GURL origin(test_config.server_url); + HttpAuth::ChallengeTokenizer tokenizer(auth_challenge.begin(), + auth_challenge.end()); + auth_handler->InitFromChallenge(&tokenizer, HttpAuth::AUTH_SERVER, + origin, BoundNetLog()); + auth_handler->SetGenerateExpectation( + test_config.server_auth_timing == AUTH_ASYNC, + test_config.server_auth_rv); + auth_factory->AddMockHandler(auth_handler, HttpAuth::AUTH_SERVER); + } + if (test_config.proxy_url) { + session_deps.proxy_service.reset( + ProxyService::CreateFixed(test_config.proxy_url)); + } else { + session_deps.proxy_service.reset(ProxyService::CreateDirect()); + } + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL(test_config.server_url); + request.load_flags = 0; + + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + HttpNetworkTransaction trans(CreateSession(&session_deps)); + + for (int round = 0; round < test_config.num_auth_rounds; ++round) { + const TestRound& read_write_round = test_config.rounds[round]; + + // Set up expected reads and writes. + MockRead reads[2]; + reads[0] = read_write_round.read; + size_t length_reads = 1; + if (read_write_round.extra_read) { + reads[1] = *read_write_round.extra_read; + length_reads = 2; + } + + MockWrite writes[2]; + writes[0] = read_write_round.write; + size_t length_writes = 1; + if (read_write_round.extra_write) { + writes[1] = *read_write_round.extra_write; + length_writes = 2; + } + StaticSocketDataProvider data_provider( + reads, length_reads, writes, length_writes); + session_deps.socket_factory.AddSocketDataProvider(&data_provider); + + // Add an SSL sequence if necessary. + SSLSocketDataProvider ssl_socket_data_provider(SYNCHRONOUS, OK); + if (round >= test_config.first_ssl_round) + session_deps.socket_factory.AddSSLSocketDataProvider( + &ssl_socket_data_provider); + + // Start or restart the transaction. + TestCompletionCallback callback; + int rv; + if (round == 0) { + rv = trans.Start(&request, callback.callback(), BoundNetLog()); + } else { + rv = trans.RestartWithAuth( + AuthCredentials(kFoo, kBar), callback.callback()); + } + if (rv == ERR_IO_PENDING) + rv = callback.WaitForResult(); + + // Compare results with expected data. + EXPECT_EQ(read_write_round.expected_rv, rv); + const HttpResponseInfo* response = trans.GetResponseInfo(); + if (read_write_round.expected_rv == OK) { + ASSERT_TRUE(response != NULL); + } else { + EXPECT_TRUE(response == NULL); + EXPECT_EQ(round + 1, test_config.num_auth_rounds); + continue; + } + if (round + 1 < test_config.num_auth_rounds) { + EXPECT_FALSE(response->auth_challenge.get() == NULL); + } else { + EXPECT_TRUE(response->auth_challenge.get() == NULL); + } + } + } +} + +TEST_F(HttpNetworkTransactionSpdy2Test, MultiRoundAuth) { + // Do multi-round authentication and make sure it works correctly. + SessionDependencies session_deps; + HttpAuthHandlerMock::Factory* auth_factory( + new HttpAuthHandlerMock::Factory()); + session_deps.http_auth_handler_factory.reset(auth_factory); + session_deps.proxy_service.reset(ProxyService::CreateDirect()); + session_deps.host_resolver->rules()->AddRule("www.example.com", "10.0.0.1"); + session_deps.host_resolver->set_synchronous_mode(true); + + HttpAuthHandlerMock* auth_handler(new HttpAuthHandlerMock()); + auth_handler->set_connection_based(true); + std::string auth_challenge = "Mock realm=server"; + GURL origin("http://www.example.com"); + HttpAuth::ChallengeTokenizer tokenizer(auth_challenge.begin(), + auth_challenge.end()); + auth_handler->InitFromChallenge(&tokenizer, HttpAuth::AUTH_SERVER, + origin, BoundNetLog()); + auth_factory->AddMockHandler(auth_handler, HttpAuth::AUTH_SERVER); + + int rv = OK; + const HttpResponseInfo* response = NULL; + HttpRequestInfo request; + request.method = "GET"; + request.url = origin; + request.load_flags = 0; + + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + // Use a TCP Socket Pool with only one connection per group. This is used + // to validate that the TCP socket is not released to the pool between + // each round of multi-round authentication. + HttpNetworkSessionPeer session_peer(session); + ClientSocketPoolHistograms transport_pool_histograms("SmallTCP"); + TransportClientSocketPool* transport_pool = new TransportClientSocketPool( + 50, // Max sockets for pool + 1, // Max sockets per group + &transport_pool_histograms, + session_deps.host_resolver.get(), + &session_deps.socket_factory, + session_deps.net_log); + MockClientSocketPoolManager* mock_pool_manager = + new MockClientSocketPoolManager; + mock_pool_manager->SetTransportSocketPool(transport_pool); + session_peer.SetClientSocketPoolManager(mock_pool_manager); + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + TestCompletionCallback callback; + + const MockWrite kGet( + "GET / HTTP/1.1\r\n" + "Host: www.example.com\r\n" + "Connection: keep-alive\r\n\r\n"); + const MockWrite kGetAuth( + "GET / HTTP/1.1\r\n" + "Host: www.example.com\r\n" + "Connection: keep-alive\r\n" + "Authorization: auth_token\r\n\r\n"); + + const MockRead kServerChallenge( + "HTTP/1.1 401 Unauthorized\r\n" + "WWW-Authenticate: Mock realm=server\r\n" + "Content-Type: text/html; charset=iso-8859-1\r\n" + "Content-Length: 14\r\n\r\n" + "Unauthorized\r\n"); + const MockRead kSuccess( + "HTTP/1.1 200 OK\r\n" + "Content-Type: text/html; charset=iso-8859-1\r\n" + "Content-Length: 3\r\n\r\n" + "Yes"); + + MockWrite writes[] = { + // First round + kGet, + // Second round + kGetAuth, + // Third round + kGetAuth, + // Fourth round + kGetAuth, + // Competing request + kGet, + }; + MockRead reads[] = { + // First round + kServerChallenge, + // Second round + kServerChallenge, + // Third round + kServerChallenge, + // Fourth round + kSuccess, + // Competing response + kSuccess, + }; + StaticSocketDataProvider data_provider(reads, arraysize(reads), + writes, arraysize(writes)); + session_deps.socket_factory.AddSocketDataProvider(&data_provider); + + const char* const kSocketGroup = "www.example.com:80"; + + // First round of authentication. + auth_handler->SetGenerateExpectation(false, OK); + rv = trans->Start(&request, callback.callback(), BoundNetLog()); + if (rv == ERR_IO_PENDING) + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_FALSE(response->auth_challenge.get() == NULL); + EXPECT_EQ(0, transport_pool->IdleSocketCountInGroup(kSocketGroup)); + + // In between rounds, another request comes in for the same domain. + // It should not be able to grab the TCP socket that trans has already + // claimed. + scoped_ptr<HttpTransaction> trans_compete( + new HttpNetworkTransaction(session)); + TestCompletionCallback callback_compete; + rv = trans_compete->Start( + &request, callback_compete.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + // callback_compete.WaitForResult at this point would stall forever, + // since the HttpNetworkTransaction does not release the request back to + // the pool until after authentication completes. + + // Second round of authentication. + auth_handler->SetGenerateExpectation(false, OK); + rv = trans->RestartWithAuth(AuthCredentials(kFoo, kBar), callback.callback()); + if (rv == ERR_IO_PENDING) + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(response->auth_challenge.get() == NULL); + EXPECT_EQ(0, transport_pool->IdleSocketCountInGroup(kSocketGroup)); + + // Third round of authentication. + auth_handler->SetGenerateExpectation(false, OK); + rv = trans->RestartWithAuth(AuthCredentials(), callback.callback()); + if (rv == ERR_IO_PENDING) + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(response->auth_challenge.get() == NULL); + EXPECT_EQ(0, transport_pool->IdleSocketCountInGroup(kSocketGroup)); + + // Fourth round of authentication, which completes successfully. + auth_handler->SetGenerateExpectation(false, OK); + rv = trans->RestartWithAuth(AuthCredentials(), callback.callback()); + if (rv == ERR_IO_PENDING) + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(response->auth_challenge.get() == NULL); + EXPECT_EQ(0, transport_pool->IdleSocketCountInGroup(kSocketGroup)); + + // Read the body since the fourth round was successful. This will also + // release the socket back to the pool. + scoped_refptr<IOBufferWithSize> io_buf(new IOBufferWithSize(50)); + rv = trans->Read(io_buf, io_buf->size(), callback.callback()); + if (rv == ERR_IO_PENDING) + rv = callback.WaitForResult(); + EXPECT_EQ(3, rv); + rv = trans->Read(io_buf, io_buf->size(), callback.callback()); + EXPECT_EQ(0, rv); + // There are still 0 idle sockets, since the trans_compete transaction + // will be handed it immediately after trans releases it to the group. + EXPECT_EQ(0, transport_pool->IdleSocketCountInGroup(kSocketGroup)); + + // The competing request can now finish. Wait for the headers and then + // read the body. + rv = callback_compete.WaitForResult(); + EXPECT_EQ(OK, rv); + rv = trans_compete->Read(io_buf, io_buf->size(), callback.callback()); + if (rv == ERR_IO_PENDING) + rv = callback.WaitForResult(); + EXPECT_EQ(3, rv); + rv = trans_compete->Read(io_buf, io_buf->size(), callback.callback()); + EXPECT_EQ(0, rv); + + // Finally, the socket is released to the group. + EXPECT_EQ(1, transport_pool->IdleSocketCountInGroup(kSocketGroup)); +} + +class TLSDecompressionFailureSocketDataProvider : public SocketDataProvider { + public: + explicit TLSDecompressionFailureSocketDataProvider(bool fail_all) + : fail_all_(fail_all) { + } + + virtual MockRead GetNextRead() { + if (fail_all_) + return MockRead(SYNCHRONOUS, ERR_SSL_DECOMPRESSION_FAILURE_ALERT); + + return MockRead(SYNCHRONOUS, + "HTTP/1.1 200 OK\r\nContent-Length: 3\r\n\r\nok.\r\n"); + } + + virtual MockWriteResult OnWrite(const std::string& data) { + return MockWriteResult(SYNCHRONOUS /* async */, data.size()); + } + + void Reset() { + } + + private: + const bool fail_all_; +}; + +// Test that we restart a connection when we see a decompression failure from +// the peer during the handshake. (In the real world we'll restart with SSLv3 +// and we won't offer DEFLATE in that case.) +TEST_F(HttpNetworkTransactionSpdy2Test, RestartAfterTLSDecompressionFailure) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("https://tlsdecompressionfailure.example.com/"); + request.load_flags = 0; + + SessionDependencies session_deps; + TLSDecompressionFailureSocketDataProvider socket_data_provider1( + false /* fail all reads */); + TLSDecompressionFailureSocketDataProvider socket_data_provider2(false); + SSLSocketDataProvider ssl_socket_data_provider1( + SYNCHRONOUS, ERR_SSL_DECOMPRESSION_FAILURE_ALERT); + SSLSocketDataProvider ssl_socket_data_provider2(SYNCHRONOUS, OK); + session_deps.socket_factory.AddSocketDataProvider(&socket_data_provider1); + session_deps.socket_factory.AddSocketDataProvider(&socket_data_provider2); + session_deps.socket_factory.AddSSLSocketDataProvider( + &ssl_socket_data_provider1); + session_deps.socket_factory.AddSSLSocketDataProvider( + &ssl_socket_data_provider2); + + // Work around http://crbug.com/37454 + StaticSocketDataProvider bug37454_connection; + bug37454_connection.set_connect_data(MockConnect(ASYNC, ERR_UNEXPECTED)); + session_deps.socket_factory.AddSocketDataProvider(&bug37454_connection); + + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_EQ(OK, callback.WaitForResult()); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + + std::string response_data; + ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data)); + EXPECT_EQ("ok.", response_data); +} + +// Test that we restart a connection if we get a decompression failure from the +// peer while reading the first bytes from the connection. This occurs when the +// peer cannot handle DEFLATE but we're using False Start, so we don't notice +// in the handshake. +TEST_F(HttpNetworkTransactionSpdy2Test, + RestartAfterTLSDecompressionFailureWithFalseStart) { + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("https://tlsdecompressionfailure2.example.com/"); + request.load_flags = 0; + + SessionDependencies session_deps; + TLSDecompressionFailureSocketDataProvider socket_data_provider1( + true /* fail all reads */); + TLSDecompressionFailureSocketDataProvider socket_data_provider2(false); + SSLSocketDataProvider ssl_socket_data_provider1(SYNCHRONOUS, OK); + SSLSocketDataProvider ssl_socket_data_provider2(SYNCHRONOUS, OK); + session_deps.socket_factory.AddSocketDataProvider(&socket_data_provider1); + session_deps.socket_factory.AddSocketDataProvider(&socket_data_provider2); + session_deps.socket_factory.AddSSLSocketDataProvider( + &ssl_socket_data_provider1); + session_deps.socket_factory.AddSSLSocketDataProvider( + &ssl_socket_data_provider2); + + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_EQ(OK, callback.WaitForResult()); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + + std::string response_data; + ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data)); + EXPECT_EQ("ok.", response_data); +} + +// This tests the case that a request is issued via http instead of spdy after +// npn is negotiated. +TEST_F(HttpNetworkTransactionSpdy2Test, NpnWithHttpOverSSL) { + HttpStreamFactory::set_use_alternate_protocols(true); + HttpStreamFactory::SetNextProtos( + MakeNextProtos("http/1.1", "http1.1", NULL)); + SessionDependencies session_deps; + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("https://www.google.com/"); + request.load_flags = 0; + + MockWrite data_writes[] = { + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n\r\n"), + }; + + MockRead data_reads[] = { + MockRead("HTTP/1.1 200 OK\r\n"), + MockRead(kAlternateProtocolHttpHeader), + MockRead("hello world"), + MockRead(SYNCHRONOUS, OK), + }; + + SSLSocketDataProvider ssl(ASYNC, OK); + ssl.next_proto_status = SSLClientSocket::kNextProtoNegotiated; + ssl.next_proto = "http/1.1"; + ssl.protocol_negotiated = SSLClientSocket::kProtoHTTP11; + + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); + + StaticSocketDataProvider data(data_reads, arraysize(data_reads), + data_writes, arraysize(data_writes)); + session_deps.socket_factory.AddSocketDataProvider(&data); + + TestCompletionCallback callback; + + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_EQ(OK, callback.WaitForResult()); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + + std::string response_data; + ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data)); + EXPECT_EQ("hello world", response_data); + + EXPECT_FALSE(response->was_fetched_via_spdy); + EXPECT_TRUE(response->was_npn_negotiated); + + HttpStreamFactory::SetNextProtos(std::vector<std::string>()); + HttpStreamFactory::set_use_alternate_protocols(false); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, SpdyPostNPNServerHangup) { + // Simulate the SSL handshake completing with an NPN negotiation + // followed by an immediate server closing of the socket. + // Fix crash: http://crbug.com/46369 + HttpStreamFactory::set_use_alternate_protocols(true); + HttpStreamFactory::SetNextProtos(SpdyNextProtos()); + SessionDependencies session_deps; + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("https://www.google.com/"); + request.load_flags = 0; + + SSLSocketDataProvider ssl(ASYNC, OK); + ssl.SetNextProto(SSLClientSocket::kProtoSPDY2); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); + + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + MockWrite spdy_writes[] = { CreateMockWrite(*req) }; + + MockRead spdy_reads[] = { + MockRead(SYNCHRONOUS, 0, 0) // Not async - return 0 immediately. + }; + + scoped_ptr<DelayedSocketData> spdy_data( + new DelayedSocketData( + 0, // don't wait in this case, immediate hangup. + spdy_reads, arraysize(spdy_reads), + spdy_writes, arraysize(spdy_writes))); + session_deps.socket_factory.AddSocketDataProvider(spdy_data.get()); + + TestCompletionCallback callback; + + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + scoped_ptr<HttpNetworkTransaction> trans(new HttpNetworkTransaction(session)); + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_EQ(ERR_CONNECTION_CLOSED, callback.WaitForResult()); + + HttpStreamFactory::SetNextProtos(std::vector<std::string>()); + HttpStreamFactory::set_use_alternate_protocols(false); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, SpdyAlternateProtocolThroughProxy) { + // This test ensures that the URL passed into the proxy is upgraded + // to https when doing an Alternate Protocol upgrade. + HttpStreamFactory::set_use_alternate_protocols(true); + HttpStreamFactory::SetNextProtos( + MakeNextProtos( + "http/1.1", "http1.1", "spdy/2", "spdy", NULL)); + + SessionDependencies session_deps(ProxyService::CreateFixed("myproxy:70")); + HttpAuthHandlerMock::Factory* auth_factory = + new HttpAuthHandlerMock::Factory(); + HttpAuthHandlerMock* auth_handler = new HttpAuthHandlerMock(); + auth_factory->AddMockHandler(auth_handler, HttpAuth::AUTH_PROXY); + auth_factory->set_do_init_from_challenge(true); + session_deps.http_auth_handler_factory.reset(auth_factory); + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com"); + request.load_flags = 0; + + // First round goes unauthenticated through the proxy. + MockWrite data_writes_1[] = { + MockWrite("GET http://www.google.com/ HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n" + "\r\n"), + }; + MockRead data_reads_1[] = { + MockRead(SYNCHRONOUS, ERR_TEST_PEER_CLOSE_AFTER_NEXT_MOCK_READ), + MockRead("HTTP/1.1 200 OK\r\n" + "Alternate-Protocol: 443:npn-spdy/2\r\n" + "Proxy-Connection: close\r\n" + "\r\n"), + }; + StaticSocketDataProvider data_1(data_reads_1, arraysize(data_reads_1), + data_writes_1, arraysize(data_writes_1)); + + // Second round tries to tunnel to www.google.com due to the + // Alternate-Protocol announcement in the first round. It fails due + // to a proxy authentication challenge. + // After the failure, a tunnel is established to www.google.com using + // Proxy-Authorization headers. There is then a SPDY request round. + // + // NOTE: Despite the "Proxy-Connection: Close", these are done on the + // same MockTCPClientSocket since the underlying HttpNetworkClientSocket + // does a Disconnect and Connect on the same socket, rather than trying + // to obtain a new one. + // + // NOTE: Originally, the proxy response to the second CONNECT request + // simply returned another 407 so the unit test could skip the SSL connection + // establishment and SPDY framing issues. Alas, the + // retry-http-when-alternate-protocol fails logic kicks in, which was more + // complicated to set up expectations for than the SPDY session. + + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> data(ConstructSpdyBodyFrame(1, true)); + + MockWrite data_writes_2[] = { + // First connection attempt without Proxy-Authorization. + MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n" + "\r\n"), + + // Second connection attempt with Proxy-Authorization. + MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n" + "Proxy-Authorization: auth_token\r\n" + "\r\n"), + + // SPDY request + CreateMockWrite(*req), + }; + const char kRejectConnectResponse[] = ("HTTP/1.1 407 Unauthorized\r\n" + "Proxy-Authenticate: Mock\r\n" + "Proxy-Connection: close\r\n" + "\r\n"); + const char kAcceptConnectResponse[] = "HTTP/1.1 200 Connected\r\n\r\n"; + MockRead data_reads_2[] = { + // First connection attempt fails + MockRead(SYNCHRONOUS, ERR_TEST_PEER_CLOSE_AFTER_NEXT_MOCK_READ, 1), + MockRead(ASYNC, kRejectConnectResponse, + arraysize(kRejectConnectResponse) - 1, 1), + + // Second connection attempt passes + MockRead(ASYNC, kAcceptConnectResponse, + arraysize(kAcceptConnectResponse) -1, 4), + + // SPDY response + CreateMockRead(*resp.get(), 6), + CreateMockRead(*data.get(), 6), + MockRead(ASYNC, 0, 0, 6), + }; + scoped_ptr<OrderedSocketData> data_2( + new OrderedSocketData(data_reads_2, arraysize(data_reads_2), + data_writes_2, arraysize(data_writes_2))); + + SSLSocketDataProvider ssl(ASYNC, OK); + ssl.SetNextProto(SSLClientSocket::kProtoSPDY2); + + MockConnect never_finishing_connect(SYNCHRONOUS, ERR_IO_PENDING); + StaticSocketDataProvider hanging_non_alternate_protocol_socket( + NULL, 0, NULL, 0); + hanging_non_alternate_protocol_socket.set_connect_data( + never_finishing_connect); + + session_deps.socket_factory.AddSocketDataProvider(&data_1); + session_deps.socket_factory.AddSocketDataProvider(data_2.get()); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); + session_deps.socket_factory.AddSocketDataProvider( + &hanging_non_alternate_protocol_socket); + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + // First round should work and provide the Alternate-Protocol state. + TestCompletionCallback callback_1; + scoped_ptr<HttpTransaction> trans_1(new HttpNetworkTransaction(session)); + int rv = trans_1->Start(&request, callback_1.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_EQ(OK, callback_1.WaitForResult()); + + // Second round should attempt a tunnel connect and get an auth challenge. + TestCompletionCallback callback_2; + scoped_ptr<HttpTransaction> trans_2(new HttpNetworkTransaction(session)); + rv = trans_2->Start(&request, callback_2.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_EQ(OK, callback_2.WaitForResult()); + const HttpResponseInfo* response = trans_2->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_FALSE(response->auth_challenge.get() == NULL); + + // Restart with auth. Tunnel should work and response received. + TestCompletionCallback callback_3; + rv = trans_2->RestartWithAuth( + AuthCredentials(kFoo, kBar), callback_3.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_EQ(OK, callback_3.WaitForResult()); + + // After all that work, these two lines (or actually, just the scheme) are + // what this test is all about. Make sure it happens correctly. + const GURL& request_url = auth_handler->request_url(); + EXPECT_EQ("https", request_url.scheme()); + EXPECT_EQ("www.google.com", request_url.host()); + + HttpStreamFactory::SetNextProtos(std::vector<std::string>()); + HttpStreamFactory::set_use_alternate_protocols(false); +} + +// Test that if we cancel the transaction as the connection is completing, that +// everything tears down correctly. +TEST_F(HttpNetworkTransactionSpdy2Test, SimpleCancel) { + // Setup everything about the connection to complete synchronously, so that + // after calling HttpNetworkTransaction::Start, the only thing we're waiting + // for is the callback from the HttpStreamRequest. + // Then cancel the transaction. + // Verify that we don't crash. + MockConnect mock_connect(SYNCHRONOUS, OK); + MockRead data_reads[] = { + MockRead(SYNCHRONOUS, "HTTP/1.0 200 OK\r\n\r\n"), + MockRead(SYNCHRONOUS, "hello world"), + MockRead(SYNCHRONOUS, OK), + }; + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + + SessionDependencies session_deps; + session_deps.host_resolver->set_synchronous_mode(true); + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0); + data.set_connect_data(mock_connect); + session_deps.socket_factory.AddSocketDataProvider(&data); + + TestCompletionCallback callback; + + CapturingBoundNetLog log(CapturingNetLog::kUnbounded); + int rv = trans->Start(&request, callback.callback(), log.bound()); + EXPECT_EQ(ERR_IO_PENDING, rv); + trans.reset(); // Cancel the transaction here. + + MessageLoop::current()->RunAllPending(); +} + +// Test a basic GET request through a proxy. +TEST_F(HttpNetworkTransactionSpdy2Test, ProxyGet) { + SessionDependencies session_deps(ProxyService::CreateFixed("myproxy:70")); + CapturingBoundNetLog log(CapturingNetLog::kUnbounded); + session_deps.net_log = log.bound().net_log(); + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + + MockWrite data_writes1[] = { + MockWrite("GET http://www.google.com/ HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n\r\n"), + }; + + MockRead data_reads1[] = { + MockRead("HTTP/1.1 200 OK\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), + MockRead("Content-Length: 100\r\n\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + + StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), + data_writes1, arraysize(data_writes1)); + session_deps.socket_factory.AddSocketDataProvider(&data1); + + TestCompletionCallback callback1; + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + int rv = trans->Start(&request, callback1.callback(), log.bound()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + + EXPECT_TRUE(response->headers->IsKeepAlive()); + EXPECT_EQ(200, response->headers->response_code()); + EXPECT_EQ(100, response->headers->GetContentLength()); + EXPECT_TRUE(response->was_fetched_via_proxy); + EXPECT_TRUE(HttpVersion(1, 1) == response->headers->GetHttpVersion()); +} + +// Test a basic HTTPS GET request through a proxy. +TEST_F(HttpNetworkTransactionSpdy2Test, ProxyTunnelGet) { + SessionDependencies session_deps(ProxyService::CreateFixed("myproxy:70")); + CapturingBoundNetLog log(CapturingNetLog::kUnbounded); + session_deps.net_log = log.bound().net_log(); + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("https://www.google.com/"); + + // Since we have proxy, should try to establish tunnel. + MockWrite data_writes1[] = { + MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n\r\n"), + + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n\r\n"), + }; + + MockRead data_reads1[] = { + MockRead("HTTP/1.1 200 Connection Established\r\n\r\n"), + + MockRead("HTTP/1.1 200 OK\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), + MockRead("Content-Length: 100\r\n\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + + StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), + data_writes1, arraysize(data_writes1)); + session_deps.socket_factory.AddSocketDataProvider(&data1); + SSLSocketDataProvider ssl(ASYNC, OK); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); + + TestCompletionCallback callback1; + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + int rv = trans->Start(&request, callback1.callback(), log.bound()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(OK, rv); + net::CapturingNetLog::EntryList entries; + log.GetEntries(&entries); + size_t pos = ExpectLogContainsSomewhere( + entries, 0, NetLog::TYPE_HTTP_TRANSACTION_SEND_TUNNEL_HEADERS, + NetLog::PHASE_NONE); + ExpectLogContainsSomewhere( + entries, pos, + NetLog::TYPE_HTTP_TRANSACTION_READ_TUNNEL_RESPONSE_HEADERS, + NetLog::PHASE_NONE); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + + EXPECT_TRUE(response->headers->IsKeepAlive()); + EXPECT_EQ(200, response->headers->response_code()); + EXPECT_EQ(100, response->headers->GetContentLength()); + EXPECT_TRUE(HttpVersion(1, 1) == response->headers->GetHttpVersion()); + EXPECT_TRUE(response->was_fetched_via_proxy); +} + +// Test a basic HTTPS GET request through a proxy, but the server hangs up +// while establishing the tunnel. +TEST_F(HttpNetworkTransactionSpdy2Test, ProxyTunnelGetHangup) { + SessionDependencies session_deps(ProxyService::CreateFixed("myproxy:70")); + CapturingBoundNetLog log(CapturingNetLog::kUnbounded); + session_deps.net_log = log.bound().net_log(); + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("https://www.google.com/"); + + // Since we have proxy, should try to establish tunnel. + MockWrite data_writes1[] = { + MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n\r\n"), + + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n\r\n"), + }; + + MockRead data_reads1[] = { + MockRead(SYNCHRONOUS, ERR_TEST_PEER_CLOSE_AFTER_NEXT_MOCK_READ), + MockRead("HTTP/1.1 200 Connection Established\r\n\r\n"), + MockRead(ASYNC, 0, 0), // EOF + }; + + StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), + data_writes1, arraysize(data_writes1)); + session_deps.socket_factory.AddSocketDataProvider(&data1); + SSLSocketDataProvider ssl(ASYNC, OK); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); + + TestCompletionCallback callback1; + + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + + int rv = trans->Start(&request, callback1.callback(), log.bound()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(ERR_EMPTY_RESPONSE, rv); + net::CapturingNetLog::EntryList entries; + log.GetEntries(&entries); + size_t pos = ExpectLogContainsSomewhere( + entries, 0, NetLog::TYPE_HTTP_TRANSACTION_SEND_TUNNEL_HEADERS, + NetLog::PHASE_NONE); + ExpectLogContainsSomewhere( + entries, pos, + NetLog::TYPE_HTTP_TRANSACTION_READ_TUNNEL_RESPONSE_HEADERS, + NetLog::PHASE_NONE); +} + +// Test for crbug.com/55424. +TEST_F(HttpNetworkTransactionSpdy2Test, PreconnectWithExistingSpdySession) { + SessionDependencies session_deps; + + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet( + "https://www.google.com", false, 1, LOWEST)); + MockWrite spdy_writes[] = { CreateMockWrite(*req) }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> data(ConstructSpdyBodyFrame(1, true)); + MockRead spdy_reads[] = { + CreateMockRead(*resp), + CreateMockRead(*data), + MockRead(ASYNC, 0, 0), + }; + + scoped_ptr<DelayedSocketData> spdy_data( + new DelayedSocketData( + 1, // wait for one write to finish before reading. + spdy_reads, arraysize(spdy_reads), + spdy_writes, arraysize(spdy_writes))); + session_deps.socket_factory.AddSocketDataProvider(spdy_data.get()); + + SSLSocketDataProvider ssl(ASYNC, OK); + ssl.SetNextProto(SSLClientSocket::kProtoSPDY2); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); + + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + + // Set up an initial SpdySession in the pool to reuse. + HostPortPair host_port_pair("www.google.com", 443); + HostPortProxyPair pair(host_port_pair, ProxyServer::Direct()); + scoped_refptr<SpdySession> spdy_session = + session->spdy_session_pool()->Get(pair, BoundNetLog()); + scoped_refptr<TransportSocketParams> transport_params( + new TransportSocketParams(host_port_pair, MEDIUM, false, false)); + TestCompletionCallback callback; + + scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle); + EXPECT_EQ(ERR_IO_PENDING, + connection->Init(host_port_pair.ToString(), transport_params, + LOWEST, callback.callback(), + session->GetTransportSocketPool(), BoundNetLog())); + EXPECT_EQ(OK, callback.WaitForResult()); + spdy_session->InitializeWithSocket(connection.release(), false, OK); + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("https://www.google.com/"); + request.load_flags = 0; + + // This is the important line that marks this as a preconnect. + request.motivation = HttpRequestInfo::PRECONNECT_MOTIVATED; + + scoped_ptr<HttpNetworkTransaction> trans(new HttpNetworkTransaction(session)); + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_EQ(OK, callback.WaitForResult()); +} + +// Given a net error, cause that error to be returned from the first Write() +// call and verify that the HttpTransaction fails with that error. +static void CheckErrorIsPassedBack(int error, IoMode mode) { + net::HttpRequestInfo request_info; + request_info.url = GURL("https://www.example.com/"); + request_info.method = "GET"; + request_info.load_flags = net::LOAD_NORMAL; + + SessionDependencies session_deps; + + SSLSocketDataProvider ssl_data(mode, OK); + net::MockWrite data_writes[] = { + net::MockWrite(mode, error), + }; + net::StaticSocketDataProvider data(NULL, 0, + data_writes, arraysize(data_writes)); + session_deps.socket_factory.AddSocketDataProvider(&data); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl_data); + + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + scoped_ptr<HttpNetworkTransaction> trans(new HttpNetworkTransaction(session)); + + TestCompletionCallback callback; + int rv = trans->Start(&request_info, callback.callback(), net::BoundNetLog()); + if (rv == net::ERR_IO_PENDING) + rv = callback.WaitForResult(); + ASSERT_EQ(error, rv); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, SSLWriteCertError) { + // Just check a grab bag of cert errors. + static const int kErrors[] = { + ERR_CERT_COMMON_NAME_INVALID, + ERR_CERT_AUTHORITY_INVALID, + ERR_CERT_DATE_INVALID, + }; + for (size_t i = 0; i < arraysize(kErrors); i++) { + CheckErrorIsPassedBack(kErrors[i], ASYNC); + CheckErrorIsPassedBack(kErrors[i], SYNCHRONOUS); + } +} + +// Ensure that a client certificate is removed from the SSL client auth +// cache when: +// 1) No proxy is involved. +// 2) TLS False Start is disabled. +// 3) The initial TLS handshake requests a client certificate. +// 4) The client supplies an invalid/unacceptable certificate. +TEST_F(HttpNetworkTransactionSpdy2Test, + ClientAuthCertCache_Direct_NoFalseStart) { + net::HttpRequestInfo request_info; + request_info.url = GURL("https://www.example.com/"); + request_info.method = "GET"; + request_info.load_flags = net::LOAD_NORMAL; + + SessionDependencies session_deps; + + scoped_refptr<SSLCertRequestInfo> cert_request(new SSLCertRequestInfo()); + cert_request->host_and_port = "www.example.com:443"; + + // [ssl_]data1 contains the data for the first SSL handshake. When a + // CertificateRequest is received for the first time, the handshake will + // be aborted to allow the caller to provide a certificate. + SSLSocketDataProvider ssl_data1(ASYNC, net::ERR_SSL_CLIENT_AUTH_CERT_NEEDED); + ssl_data1.cert_request_info = cert_request.get(); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl_data1); + net::StaticSocketDataProvider data1(NULL, 0, NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&data1); + + // [ssl_]data2 contains the data for the second SSL handshake. When TLS + // False Start is not being used, the result of the SSL handshake will be + // returned as part of the SSLClientSocket::Connect() call. This test + // matches the result of a server sending a handshake_failure alert, + // rather than a Finished message, because it requires a client + // certificate and none was supplied. + SSLSocketDataProvider ssl_data2(ASYNC, net::ERR_SSL_PROTOCOL_ERROR); + ssl_data2.cert_request_info = cert_request.get(); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl_data2); + net::StaticSocketDataProvider data2(NULL, 0, NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&data2); + + // [ssl_]data3 contains the data for the third SSL handshake. When a + // connection to a server fails during an SSL handshake, + // HttpNetworkTransaction will attempt to fallback to SSLv3 if the initial + // connection was attempted with TLSv1. This is transparent to the caller + // of the HttpNetworkTransaction. Because this test failure is due to + // requiring a client certificate, this fallback handshake should also + // fail. + SSLSocketDataProvider ssl_data3(ASYNC, net::ERR_SSL_PROTOCOL_ERROR); + ssl_data3.cert_request_info = cert_request.get(); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl_data3); + net::StaticSocketDataProvider data3(NULL, 0, NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&data3); + + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + scoped_ptr<HttpNetworkTransaction> trans(new HttpNetworkTransaction(session)); + + // Begin the SSL handshake with the peer. This consumes ssl_data1. + TestCompletionCallback callback; + int rv = trans->Start(&request_info, callback.callback(), net::BoundNetLog()); + ASSERT_EQ(net::ERR_IO_PENDING, rv); + + // Complete the SSL handshake, which should abort due to requiring a + // client certificate. + rv = callback.WaitForResult(); + ASSERT_EQ(net::ERR_SSL_CLIENT_AUTH_CERT_NEEDED, rv); + + // Indicate that no certificate should be supplied. From the perspective + // of SSLClientCertCache, NULL is just as meaningful as a real + // certificate, so this is the same as supply a + // legitimate-but-unacceptable certificate. + rv = trans->RestartWithCertificate(NULL, callback.callback()); + ASSERT_EQ(net::ERR_IO_PENDING, rv); + + // Ensure the certificate was added to the client auth cache before + // allowing the connection to continue restarting. + scoped_refptr<X509Certificate> client_cert; + ASSERT_TRUE(session->ssl_client_auth_cache()->Lookup("www.example.com:443", + &client_cert)); + ASSERT_EQ(NULL, client_cert.get()); + + // Restart the handshake. This will consume ssl_data2, which fails, and + // then consume ssl_data3, which should also fail. The result code is + // checked against what ssl_data3 should return. + rv = callback.WaitForResult(); + ASSERT_EQ(net::ERR_SSL_PROTOCOL_ERROR, rv); + + // Ensure that the client certificate is removed from the cache on a + // handshake failure. + ASSERT_FALSE(session->ssl_client_auth_cache()->Lookup("www.example.com:443", + &client_cert)); +} + +// Ensure that a client certificate is removed from the SSL client auth +// cache when: +// 1) No proxy is involved. +// 2) TLS False Start is enabled. +// 3) The initial TLS handshake requests a client certificate. +// 4) The client supplies an invalid/unacceptable certificate. +TEST_F(HttpNetworkTransactionSpdy2Test, + ClientAuthCertCache_Direct_FalseStart) { + net::HttpRequestInfo request_info; + request_info.url = GURL("https://www.example.com/"); + request_info.method = "GET"; + request_info.load_flags = net::LOAD_NORMAL; + + SessionDependencies session_deps; + + scoped_refptr<SSLCertRequestInfo> cert_request(new SSLCertRequestInfo()); + cert_request->host_and_port = "www.example.com:443"; + + // When TLS False Start is used, SSLClientSocket::Connect() calls will + // return successfully after reading up to the peer's Certificate message. + // This is to allow the caller to call SSLClientSocket::Write(), which can + // enqueue application data to be sent in the same packet as the + // ChangeCipherSpec and Finished messages. + // The actual handshake will be finished when SSLClientSocket::Read() is + // called, which expects to process the peer's ChangeCipherSpec and + // Finished messages. If there was an error negotiating with the peer, + // such as due to the peer requiring a client certificate when none was + // supplied, the alert sent by the peer won't be processed until Read() is + // called. + + // Like the non-False Start case, when a client certificate is requested by + // the peer, the handshake is aborted during the Connect() call. + // [ssl_]data1 represents the initial SSL handshake with the peer. + SSLSocketDataProvider ssl_data1(ASYNC, net::ERR_SSL_CLIENT_AUTH_CERT_NEEDED); + ssl_data1.cert_request_info = cert_request.get(); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl_data1); + net::StaticSocketDataProvider data1(NULL, 0, NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&data1); + + // When a client certificate is supplied, Connect() will not be aborted + // when the peer requests the certificate. Instead, the handshake will + // artificially succeed, allowing the caller to write the HTTP request to + // the socket. The handshake messages are not processed until Read() is + // called, which then detects that the handshake was aborted, due to the + // peer sending a handshake_failure because it requires a client + // certificate. + SSLSocketDataProvider ssl_data2(ASYNC, net::OK); + ssl_data2.cert_request_info = cert_request.get(); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl_data2); + net::MockRead data2_reads[] = { + net::MockRead(ASYNC /* async */, net::ERR_SSL_PROTOCOL_ERROR), + }; + net::StaticSocketDataProvider data2( + data2_reads, arraysize(data2_reads), NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&data2); + + // As described in ClientAuthCertCache_Direct_NoFalseStart, [ssl_]data3 is + // the data for the SSL handshake once the TLSv1 connection falls back to + // SSLv3. It has the same behaviour as [ssl_]data2. + SSLSocketDataProvider ssl_data3(ASYNC, net::OK); + ssl_data3.cert_request_info = cert_request.get(); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl_data3); + net::StaticSocketDataProvider data3( + data2_reads, arraysize(data2_reads), NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&data3); + + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + scoped_ptr<HttpNetworkTransaction> trans(new HttpNetworkTransaction(session)); + + // Begin the initial SSL handshake. + TestCompletionCallback callback; + int rv = trans->Start(&request_info, callback.callback(), net::BoundNetLog()); + ASSERT_EQ(net::ERR_IO_PENDING, rv); + + // Complete the SSL handshake, which should abort due to requiring a + // client certificate. + rv = callback.WaitForResult(); + ASSERT_EQ(net::ERR_SSL_CLIENT_AUTH_CERT_NEEDED, rv); + + // Indicate that no certificate should be supplied. From the perspective + // of SSLClientCertCache, NULL is just as meaningful as a real + // certificate, so this is the same as supply a + // legitimate-but-unacceptable certificate. + rv = trans->RestartWithCertificate(NULL, callback.callback()); + ASSERT_EQ(net::ERR_IO_PENDING, rv); + + // Ensure the certificate was added to the client auth cache before + // allowing the connection to continue restarting. + scoped_refptr<X509Certificate> client_cert; + ASSERT_TRUE(session->ssl_client_auth_cache()->Lookup("www.example.com:443", + &client_cert)); + ASSERT_EQ(NULL, client_cert.get()); + + + // Restart the handshake. This will consume ssl_data2, which fails, and + // then consume ssl_data3, which should also fail. The result code is + // checked against what ssl_data3 should return. + rv = callback.WaitForResult(); + ASSERT_EQ(net::ERR_SSL_PROTOCOL_ERROR, rv); + + // Ensure that the client certificate is removed from the cache on a + // handshake failure. + ASSERT_FALSE(session->ssl_client_auth_cache()->Lookup("www.example.com:443", + &client_cert)); +} + +// Ensure that a client certificate is removed from the SSL client auth +// cache when: +// 1) An HTTPS proxy is involved. +// 3) The HTTPS proxy requests a client certificate. +// 4) The client supplies an invalid/unacceptable certificate for the +// proxy. +// The test is repeated twice, first for connecting to an HTTPS endpoint, +// then for connecting to an HTTP endpoint. +TEST_F(HttpNetworkTransactionSpdy2Test, ClientAuthCertCache_Proxy_Fail) { + SessionDependencies session_deps( + ProxyService::CreateFixed("https://proxy:70")); + CapturingBoundNetLog log(CapturingNetLog::kUnbounded); + session_deps.net_log = log.bound().net_log(); + + scoped_refptr<SSLCertRequestInfo> cert_request(new SSLCertRequestInfo()); + cert_request->host_and_port = "proxy:70"; + + // See ClientAuthCertCache_Direct_NoFalseStart for the explanation of + // [ssl_]data[1-3]. Rather than represending the endpoint + // (www.example.com:443), they represent failures with the HTTPS proxy + // (proxy:70). + SSLSocketDataProvider ssl_data1(ASYNC, net::ERR_SSL_CLIENT_AUTH_CERT_NEEDED); + ssl_data1.cert_request_info = cert_request.get(); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl_data1); + net::StaticSocketDataProvider data1(NULL, 0, NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&data1); + + SSLSocketDataProvider ssl_data2(ASYNC, net::ERR_SSL_PROTOCOL_ERROR); + ssl_data2.cert_request_info = cert_request.get(); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl_data2); + net::StaticSocketDataProvider data2(NULL, 0, NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&data2); + + SSLSocketDataProvider ssl_data3(ASYNC, net::ERR_SSL_PROTOCOL_ERROR); + ssl_data3.cert_request_info = cert_request.get(); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl_data3); + net::StaticSocketDataProvider data3(NULL, 0, NULL, 0); + session_deps.socket_factory.AddSocketDataProvider(&data3); + + net::HttpRequestInfo requests[2]; + requests[0].url = GURL("https://www.example.com/"); + requests[0].method = "GET"; + requests[0].load_flags = net::LOAD_NORMAL; + + requests[1].url = GURL("http://www.example.com/"); + requests[1].method = "GET"; + requests[1].load_flags = net::LOAD_NORMAL; + + for (size_t i = 0; i < arraysize(requests); ++i) { + session_deps.socket_factory.ResetNextMockIndexes(); + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + scoped_ptr<HttpNetworkTransaction> trans( + new HttpNetworkTransaction(session)); + + // Begin the SSL handshake with the proxy. + TestCompletionCallback callback; + int rv = trans->Start( + &requests[i], callback.callback(), net::BoundNetLog()); + ASSERT_EQ(net::ERR_IO_PENDING, rv); + + // Complete the SSL handshake, which should abort due to requiring a + // client certificate. + rv = callback.WaitForResult(); + ASSERT_EQ(net::ERR_SSL_CLIENT_AUTH_CERT_NEEDED, rv); + + // Indicate that no certificate should be supplied. From the perspective + // of SSLClientCertCache, NULL is just as meaningful as a real + // certificate, so this is the same as supply a + // legitimate-but-unacceptable certificate. + rv = trans->RestartWithCertificate(NULL, callback.callback()); + ASSERT_EQ(net::ERR_IO_PENDING, rv); + + // Ensure the certificate was added to the client auth cache before + // allowing the connection to continue restarting. + scoped_refptr<X509Certificate> client_cert; + ASSERT_TRUE(session->ssl_client_auth_cache()->Lookup("proxy:70", + &client_cert)); + ASSERT_EQ(NULL, client_cert.get()); + // Ensure the certificate was NOT cached for the endpoint. This only + // applies to HTTPS requests, but is fine to check for HTTP requests. + ASSERT_FALSE(session->ssl_client_auth_cache()->Lookup("www.example.com:443", + &client_cert)); + + // Restart the handshake. This will consume ssl_data2, which fails, and + // then consume ssl_data3, which should also fail. The result code is + // checked against what ssl_data3 should return. + rv = callback.WaitForResult(); + ASSERT_EQ(net::ERR_PROXY_CONNECTION_FAILED, rv); + + // Now that the new handshake has failed, ensure that the client + // certificate was removed from the client auth cache. + ASSERT_FALSE(session->ssl_client_auth_cache()->Lookup("proxy:70", + &client_cert)); + ASSERT_FALSE(session->ssl_client_auth_cache()->Lookup("www.example.com:443", + &client_cert)); + } +} + +namespace { + +void IPPoolingAddAlias(MockCachingHostResolver* host_resolver, + SpdySessionPoolPeer* pool_peer, + std::string host, + int port, + std::string iplist) { + // Create a host resolver dependency that returns address |iplist| for + // resolutions of |host|. + host_resolver->rules()->AddIPLiteralRule(host, iplist, ""); + + // Setup a HostPortProxyPair. + HostPortPair host_port_pair(host, port); + HostPortProxyPair pair = HostPortProxyPair(host_port_pair, + ProxyServer::Direct()); + + // Resolve the host and port. + AddressList addresses; + HostResolver::RequestInfo info(host_port_pair); + TestCompletionCallback callback; + int rv = host_resolver->Resolve(info, &addresses, callback.callback(), NULL, + BoundNetLog()); + if (rv == ERR_IO_PENDING) + rv = callback.WaitForResult(); + DCHECK_EQ(OK, rv); + + // Add the first address as an alias. It would have been better to call + // MockClientSocket::GetPeerAddress but that returns 192.0.2.33 whereas + // MockHostResolver returns 127.0.0.1 (MockHostResolverBase::Reset). So we use + // the first address (127.0.0.1) returned by MockHostResolver as an alias for + // the |pair|. + const addrinfo* address = addresses.head(); + pool_peer->AddAlias(address, pair); +} + +} // namespace + +TEST_F(HttpNetworkTransactionSpdy2Test, UseIPConnectionPooling) { + HttpStreamFactory::set_use_alternate_protocols(true); + HttpStreamFactory::SetNextProtos(SpdyNextProtos()); + + // Set up a special HttpNetworkSession with a MockCachingHostResolver. + SessionDependencies session_deps; + MockCachingHostResolver host_resolver; + net::HttpNetworkSession::Params params; + params.client_socket_factory = &session_deps.socket_factory; + params.host_resolver = &host_resolver; + params.cert_verifier = session_deps.cert_verifier.get(); + params.proxy_service = session_deps.proxy_service.get(); + params.ssl_config_service = session_deps.ssl_config_service; + params.http_auth_handler_factory = + session_deps.http_auth_handler_factory.get(); + params.http_server_properties = &session_deps.http_server_properties; + params.net_log = session_deps.net_log; + scoped_refptr<HttpNetworkSession> session(new HttpNetworkSession(params)); + SpdySessionPoolPeer pool_peer(session->spdy_session_pool()); + pool_peer.DisableDomainAuthenticationVerification(); + + SSLSocketDataProvider ssl(ASYNC, OK); + ssl.SetNextProto(SSLClientSocket::kProtoSPDY2); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); + + scoped_ptr<spdy::SpdyFrame> host1_req(ConstructSpdyGet( + "https://www.google.com", false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> host2_req(ConstructSpdyGet( + "https://www.gmail.com", false, 3, LOWEST)); + MockWrite spdy_writes[] = { + CreateMockWrite(*host1_req, 1), + CreateMockWrite(*host2_req, 4), + }; + scoped_ptr<spdy::SpdyFrame> host1_resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> host1_resp_body(ConstructSpdyBodyFrame(1, true)); + scoped_ptr<spdy::SpdyFrame> host2_resp(ConstructSpdyGetSynReply(NULL, 0, 3)); + scoped_ptr<spdy::SpdyFrame> host2_resp_body(ConstructSpdyBodyFrame(3, true)); + MockRead spdy_reads[] = { + CreateMockRead(*host1_resp, 2), + CreateMockRead(*host1_resp_body, 3), + CreateMockRead(*host2_resp, 5), + CreateMockRead(*host2_resp_body, 6), + MockRead(ASYNC, 0, 7), + }; + + scoped_ptr<OrderedSocketData> spdy_data( + new OrderedSocketData( + spdy_reads, arraysize(spdy_reads), + spdy_writes, arraysize(spdy_writes))); + session_deps.socket_factory.AddSocketDataProvider(spdy_data.get()); + + TestCompletionCallback callback; + HttpRequestInfo request1; + request1.method = "GET"; + request1.url = GURL("https://www.google.com/"); + request1.load_flags = 0; + HttpNetworkTransaction trans1(session); + + int rv = trans1.Start(&request1, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_EQ(OK, callback.WaitForResult()); + + const HttpResponseInfo* response = trans1.GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + + std::string response_data; + ASSERT_EQ(OK, ReadTransaction(&trans1, &response_data)); + EXPECT_EQ("hello!", response_data); + + // Preload www.gmail.com into HostCache. + HostPortPair host_port("www.gmail.com", 443); + HostResolver::RequestInfo resolve_info(host_port); + AddressList ignored; + rv = host_resolver.Resolve(resolve_info, &ignored, callback.callback(), NULL, + BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + // MockHostResolver returns 127.0.0.1, port 443 for https://www.google.com/ + // and https://www.gmail.com/. Add 127.0.0.1 as alias for host_port_pair: + // (www.google.com, 443). + IPPoolingAddAlias(&host_resolver, &pool_peer, "www.google.com", 443, + "127.0.0.1"); + + HttpRequestInfo request2; + request2.method = "GET"; + request2.url = GURL("https://www.gmail.com/"); + request2.load_flags = 0; + HttpNetworkTransaction trans2(session); + + rv = trans2.Start(&request2, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_EQ(OK, callback.WaitForResult()); + + response = trans2.GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + EXPECT_TRUE(response->was_fetched_via_spdy); + EXPECT_TRUE(response->was_npn_negotiated); + ASSERT_EQ(OK, ReadTransaction(&trans2, &response_data)); + EXPECT_EQ("hello!", response_data); + + HttpStreamFactory::SetNextProtos(std::vector<std::string>()); + HttpStreamFactory::set_use_alternate_protocols(false); +} + +class OneTimeCachingHostResolver : public net::HostResolver { + public: + explicit OneTimeCachingHostResolver(const HostPortPair& host_port) + : host_port_(host_port) {} + virtual ~OneTimeCachingHostResolver() {} + + RuleBasedHostResolverProc* rules() { return host_resolver_.rules(); } + + // HostResolver methods: + virtual int Resolve(const RequestInfo& info, + AddressList* addresses, + const CompletionCallback& callback, + RequestHandle* out_req, + const BoundNetLog& net_log) OVERRIDE { + return host_resolver_.Resolve( + info, addresses, callback, out_req, net_log); + } + + virtual int ResolveFromCache(const RequestInfo& info, + AddressList* addresses, + const BoundNetLog& net_log) OVERRIDE { + int rv = host_resolver_.ResolveFromCache(info, addresses, net_log); + if (rv == OK && info.host_port_pair().Equals(host_port_)) + host_resolver_.GetHostCache()->clear(); + return rv; + } + + virtual void CancelRequest(RequestHandle req) OVERRIDE { + host_resolver_.CancelRequest(req); + } + + MockCachingHostResolver* GetMockHostResolver() { + return &host_resolver_; + } + + private: + MockCachingHostResolver host_resolver_; + const HostPortPair host_port_; +}; + +TEST_F(HttpNetworkTransactionSpdy2Test, + UseIPConnectionPoolingWithHostCacheExpiration) { + HttpStreamFactory::set_use_alternate_protocols(true); + HttpStreamFactory::SetNextProtos(SpdyNextProtos()); + + // Set up a special HttpNetworkSession with a OneTimeCachingHostResolver. + SessionDependencies session_deps; + OneTimeCachingHostResolver host_resolver(HostPortPair("www.gmail.com", 443)); + net::HttpNetworkSession::Params params; + params.client_socket_factory = &session_deps.socket_factory; + params.host_resolver = &host_resolver; + params.cert_verifier = session_deps.cert_verifier.get(); + params.proxy_service = session_deps.proxy_service.get(); + params.ssl_config_service = session_deps.ssl_config_service; + params.http_auth_handler_factory = + session_deps.http_auth_handler_factory.get(); + params.http_server_properties = &session_deps.http_server_properties; + params.net_log = session_deps.net_log; + scoped_refptr<HttpNetworkSession> session(new HttpNetworkSession(params)); + SpdySessionPoolPeer pool_peer(session->spdy_session_pool()); + pool_peer.DisableDomainAuthenticationVerification(); + + SSLSocketDataProvider ssl(ASYNC, OK); + ssl.SetNextProto(SSLClientSocket::kProtoSPDY2); + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); + + scoped_ptr<spdy::SpdyFrame> host1_req(ConstructSpdyGet( + "https://www.google.com", false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> host2_req(ConstructSpdyGet( + "https://www.gmail.com", false, 3, LOWEST)); + MockWrite spdy_writes[] = { + CreateMockWrite(*host1_req, 1), + CreateMockWrite(*host2_req, 4), + }; + scoped_ptr<spdy::SpdyFrame> host1_resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> host1_resp_body(ConstructSpdyBodyFrame(1, true)); + scoped_ptr<spdy::SpdyFrame> host2_resp(ConstructSpdyGetSynReply(NULL, 0, 3)); + scoped_ptr<spdy::SpdyFrame> host2_resp_body(ConstructSpdyBodyFrame(3, true)); + MockRead spdy_reads[] = { + CreateMockRead(*host1_resp, 2), + CreateMockRead(*host1_resp_body, 3), + CreateMockRead(*host2_resp, 5), + CreateMockRead(*host2_resp_body, 6), + MockRead(ASYNC, 0, 7), + }; + + scoped_ptr<OrderedSocketData> spdy_data( + new OrderedSocketData( + spdy_reads, arraysize(spdy_reads), + spdy_writes, arraysize(spdy_writes))); + session_deps.socket_factory.AddSocketDataProvider(spdy_data.get()); + + TestCompletionCallback callback; + HttpRequestInfo request1; + request1.method = "GET"; + request1.url = GURL("https://www.google.com/"); + request1.load_flags = 0; + HttpNetworkTransaction trans1(session); + + int rv = trans1.Start(&request1, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_EQ(OK, callback.WaitForResult()); + + const HttpResponseInfo* response = trans1.GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + + std::string response_data; + ASSERT_EQ(OK, ReadTransaction(&trans1, &response_data)); + EXPECT_EQ("hello!", response_data); + + // Preload cache entries into HostCache. + HostResolver::RequestInfo resolve_info(HostPortPair("www.gmail.com", 443)); + AddressList ignored; + rv = host_resolver.Resolve(resolve_info, &ignored, callback.callback(), NULL, + BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + HttpRequestInfo request2; + request2.method = "GET"; + request2.url = GURL("https://www.gmail.com/"); + request2.load_flags = 0; + HttpNetworkTransaction trans2(session); + + // MockHostResolver returns 127.0.0.1, port 443 for https://www.google.com/ + // and https://www.gmail.com/. Add 127.0.0.1 as alias for host_port_pair: + // (www.google.com, 443). + IPPoolingAddAlias(host_resolver.GetMockHostResolver(), &pool_peer, + "www.google.com", 443, "127.0.0.1"); + + rv = trans2.Start(&request2, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_EQ(OK, callback.WaitForResult()); + + response = trans2.GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + EXPECT_TRUE(response->was_fetched_via_spdy); + EXPECT_TRUE(response->was_npn_negotiated); + ASSERT_EQ(OK, ReadTransaction(&trans2, &response_data)); + EXPECT_EQ("hello!", response_data); + + HttpStreamFactory::SetNextProtos(std::vector<std::string>()); + HttpStreamFactory::set_use_alternate_protocols(false); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, ReadPipelineEvictionFallback) { + MockRead data_reads1[] = { + MockRead(SYNCHRONOUS, ERR_PIPELINE_EVICTION), + }; + MockRead data_reads2[] = { + MockRead("HTTP/1.0 200 OK\r\n\r\n"), + MockRead("hello world"), + MockRead(SYNCHRONOUS, OK), + }; + StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), NULL, 0); + StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2), NULL, 0); + StaticSocketDataProvider* data[] = { &data1, &data2 }; + + SimpleGetHelperResult out = SimpleGetHelperForData(data, arraysize(data)); + + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.0 200 OK", out.status_line); + EXPECT_EQ("hello world", out.response_data); +} + +TEST_F(HttpNetworkTransactionSpdy2Test, SendPipelineEvictionFallback) { + MockWrite data_writes1[] = { + MockWrite(SYNCHRONOUS, ERR_PIPELINE_EVICTION), + }; + MockWrite data_writes2[] = { + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Connection: keep-alive\r\n\r\n"), + }; + MockRead data_reads2[] = { + MockRead("HTTP/1.0 200 OK\r\n\r\n"), + MockRead("hello world"), + MockRead(SYNCHRONOUS, OK), + }; + StaticSocketDataProvider data1(NULL, 0, + data_writes1, arraysize(data_writes1)); + StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2), + data_writes2, arraysize(data_writes2)); + StaticSocketDataProvider* data[] = { &data1, &data2 }; + + SimpleGetHelperResult out = SimpleGetHelperForData(data, arraysize(data)); + + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.0 200 OK", out.status_line); + EXPECT_EQ("hello world", out.response_data); +} + +} // namespace net diff --git a/net/http/http_network_transaction_unittest.cc b/net/http/http_network_transaction_spdy3_unittest.cc index 45efec7..77954c2 100644 --- a/net/http/http_network_transaction_unittest.cc +++ b/net/http/http_network_transaction_spdy3_unittest.cc @@ -51,10 +51,12 @@ #include "net/spdy/spdy_framer.h" #include "net/spdy/spdy_session.h" #include "net/spdy/spdy_session_pool.h" -#include "net/spdy/spdy_test_util.h" +#include "net/spdy/spdy_test_util_spdy3.h" #include "testing/gtest/include/gtest/gtest.h" #include "testing/platform_test.h" +using namespace net::test_spdy3; + //----------------------------------------------------------------------------- namespace { @@ -102,6 +104,8 @@ std::vector<std::string> SpdyNextProtos() { namespace net { +namespace { + // Helper to manage the lifetimes of the dependencies for a // HttpNetworkTransaction. struct SessionDependencies { @@ -149,7 +153,9 @@ HttpNetworkSession* CreateSession(SessionDependencies* session_deps) { return new HttpNetworkSession(params); } -class HttpNetworkTransactionTest : public PlatformTest { +} // namespace + +class HttpNetworkTransactionSpdy3Test : public PlatformTest { protected: struct SimpleGetHelperResult { int rv; @@ -258,6 +264,8 @@ class HttpNetworkTransactionTest : public PlatformTest { void ConnectStatusHelper(const MockRead& status); }; +namespace { + // Fill |str| with a long header list that consumes >= |size| bytes. void FillLargeHeadersString(std::string* str, int size) { const char* row = @@ -421,13 +429,15 @@ bool CheckNTLMServerAuth(const AuthChallengeInfo* auth_challenge) { return true; } -TEST_F(HttpNetworkTransactionTest, Basic) { +} // namespace + +TEST_F(HttpNetworkTransactionSpdy3Test, Basic) { SessionDependencies session_deps; scoped_ptr<HttpTransaction> trans( new HttpNetworkTransaction(CreateSession(&session_deps))); } -TEST_F(HttpNetworkTransactionTest, SimpleGET) { +TEST_F(HttpNetworkTransactionSpdy3Test, SimpleGET) { MockRead data_reads[] = { MockRead("HTTP/1.0 200 OK\r\n\r\n"), MockRead("hello world"), @@ -441,7 +451,7 @@ TEST_F(HttpNetworkTransactionTest, SimpleGET) { } // Response with no status line. -TEST_F(HttpNetworkTransactionTest, SimpleGETNoHeaders) { +TEST_F(HttpNetworkTransactionSpdy3Test, SimpleGETNoHeaders) { MockRead data_reads[] = { MockRead("hello world"), MockRead(SYNCHRONOUS, OK), @@ -454,7 +464,7 @@ TEST_F(HttpNetworkTransactionTest, SimpleGETNoHeaders) { } // Allow up to 4 bytes of junk to precede status line. -TEST_F(HttpNetworkTransactionTest, StatusLineJunk2Bytes) { +TEST_F(HttpNetworkTransactionSpdy3Test, StatusLineJunk2Bytes) { MockRead data_reads[] = { MockRead("xxxHTTP/1.0 404 Not Found\nServer: blah\n\nDATA"), MockRead(SYNCHRONOUS, OK), @@ -467,7 +477,7 @@ TEST_F(HttpNetworkTransactionTest, StatusLineJunk2Bytes) { } // Allow up to 4 bytes of junk to precede status line. -TEST_F(HttpNetworkTransactionTest, StatusLineJunk4Bytes) { +TEST_F(HttpNetworkTransactionSpdy3Test, StatusLineJunk4Bytes) { MockRead data_reads[] = { MockRead("\n\nQJHTTP/1.0 404 Not Found\nServer: blah\n\nDATA"), MockRead(SYNCHRONOUS, OK), @@ -480,7 +490,7 @@ TEST_F(HttpNetworkTransactionTest, StatusLineJunk4Bytes) { } // Beyond 4 bytes of slop and it should fail to find a status line. -TEST_F(HttpNetworkTransactionTest, StatusLineJunk5Bytes) { +TEST_F(HttpNetworkTransactionSpdy3Test, StatusLineJunk5Bytes) { MockRead data_reads[] = { MockRead("xxxxxHTTP/1.1 404 Not Found\nServer: blah"), MockRead(SYNCHRONOUS, OK), @@ -493,7 +503,7 @@ TEST_F(HttpNetworkTransactionTest, StatusLineJunk5Bytes) { } // Same as StatusLineJunk4Bytes, except the read chunks are smaller. -TEST_F(HttpNetworkTransactionTest, StatusLineJunk4Bytes_Slow) { +TEST_F(HttpNetworkTransactionSpdy3Test, StatusLineJunk4Bytes_Slow) { MockRead data_reads[] = { MockRead("\n"), MockRead("\n"), @@ -510,7 +520,7 @@ TEST_F(HttpNetworkTransactionTest, StatusLineJunk4Bytes_Slow) { } // Close the connection before enough bytes to have a status line. -TEST_F(HttpNetworkTransactionTest, StatusLinePartial) { +TEST_F(HttpNetworkTransactionSpdy3Test, StatusLinePartial) { MockRead data_reads[] = { MockRead("HTT"), MockRead(SYNCHRONOUS, OK), @@ -525,7 +535,7 @@ TEST_F(HttpNetworkTransactionTest, StatusLinePartial) { // Simulate a 204 response, lacking a Content-Length header, sent over a // persistent connection. The response should still terminate since a 204 // cannot have a response body. -TEST_F(HttpNetworkTransactionTest, StopsReading204) { +TEST_F(HttpNetworkTransactionSpdy3Test, StopsReading204) { MockRead data_reads[] = { MockRead("HTTP/1.1 204 No Content\r\n\r\n"), MockRead("junk"), // Should not be read!! @@ -540,7 +550,7 @@ TEST_F(HttpNetworkTransactionTest, StopsReading204) { // A simple request using chunked encoding with some extra data after. // (Like might be seen in a pipelined response.) -TEST_F(HttpNetworkTransactionTest, ChunkedEncoding) { +TEST_F(HttpNetworkTransactionSpdy3Test, ChunkedEncoding) { MockRead data_reads[] = { MockRead("HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n"), MockRead("5\r\nHello\r\n"), @@ -559,7 +569,7 @@ TEST_F(HttpNetworkTransactionTest, ChunkedEncoding) { // Next tests deal with http://crbug.com/56344. -TEST_F(HttpNetworkTransactionTest, +TEST_F(HttpNetworkTransactionSpdy3Test, MultipleContentLengthHeadersNoTransferEncoding) { MockRead data_reads[] = { MockRead("HTTP/1.1 200 OK\r\n"), @@ -571,7 +581,7 @@ TEST_F(HttpNetworkTransactionTest, EXPECT_EQ(ERR_RESPONSE_HEADERS_MULTIPLE_CONTENT_LENGTH, out.rv); } -TEST_F(HttpNetworkTransactionTest, +TEST_F(HttpNetworkTransactionSpdy3Test, DuplicateContentLengthHeadersNoTransferEncoding) { MockRead data_reads[] = { MockRead("HTTP/1.1 200 OK\r\n"), @@ -586,7 +596,7 @@ TEST_F(HttpNetworkTransactionTest, EXPECT_EQ("Hello", out.response_data); } -TEST_F(HttpNetworkTransactionTest, +TEST_F(HttpNetworkTransactionSpdy3Test, ComplexContentLengthHeadersNoTransferEncoding) { // More than 2 dupes. { @@ -632,7 +642,7 @@ TEST_F(HttpNetworkTransactionTest, } } -TEST_F(HttpNetworkTransactionTest, +TEST_F(HttpNetworkTransactionSpdy3Test, MultipleContentLengthHeadersTransferEncoding) { MockRead data_reads[] = { MockRead("HTTP/1.1 200 OK\r\n"), @@ -656,7 +666,7 @@ TEST_F(HttpNetworkTransactionTest, // Next tests deal with http://crbug.com/98895. // Checks that a single Content-Disposition header results in no error. -TEST_F(HttpNetworkTransactionTest, SingleContentDispositionHeader) { +TEST_F(HttpNetworkTransactionSpdy3Test, SingleContentDispositionHeader) { MockRead data_reads[] = { MockRead("HTTP/1.1 200 OK\r\n"), MockRead("Content-Disposition: attachment;filename=\"salutations.txt\"r\n"), @@ -671,7 +681,7 @@ TEST_F(HttpNetworkTransactionTest, SingleContentDispositionHeader) { } // Checks that two identical Content-Disposition headers result in an error. -TEST_F(HttpNetworkTransactionTest, +TEST_F(HttpNetworkTransactionSpdy3Test, DuplicateIdenticalContentDispositionHeaders) { MockRead data_reads[] = { MockRead("HTTP/1.1 200 OK\r\n"), @@ -686,7 +696,8 @@ TEST_F(HttpNetworkTransactionTest, } // Checks that two distinct Content-Disposition headers result in an error. -TEST_F(HttpNetworkTransactionTest, DuplicateDistinctContentDispositionHeaders) { +TEST_F(HttpNetworkTransactionSpdy3Test, + DuplicateDistinctContentDispositionHeaders) { MockRead data_reads[] = { MockRead("HTTP/1.1 200 OK\r\n"), MockRead("Content-Disposition: attachment;filename=\"greetings.txt\"r\n"), @@ -700,7 +711,7 @@ TEST_F(HttpNetworkTransactionTest, DuplicateDistinctContentDispositionHeaders) { } // Checks the behavior of a single Location header. -TEST_F(HttpNetworkTransactionTest, SingleLocationHeader) { +TEST_F(HttpNetworkTransactionSpdy3Test, SingleLocationHeader) { MockRead data_reads[] = { MockRead("HTTP/1.1 302 Redirect\r\n"), MockRead("Location: http://good.com/\r\n"), @@ -736,7 +747,7 @@ TEST_F(HttpNetworkTransactionTest, SingleLocationHeader) { } // Checks that two identical Location headers result in an error. -TEST_F(HttpNetworkTransactionTest, DuplicateIdenticalLocationHeaders) { +TEST_F(HttpNetworkTransactionSpdy3Test, DuplicateIdenticalLocationHeaders) { MockRead data_reads[] = { MockRead("HTTP/1.1 302 Redirect\r\n"), MockRead("Location: http://good.com/\r\n"), @@ -750,7 +761,7 @@ TEST_F(HttpNetworkTransactionTest, DuplicateIdenticalLocationHeaders) { } // Checks that two distinct Location headers result in an error. -TEST_F(HttpNetworkTransactionTest, DuplicateDistinctLocationHeaders) { +TEST_F(HttpNetworkTransactionSpdy3Test, DuplicateDistinctLocationHeaders) { MockRead data_reads[] = { MockRead("HTTP/1.1 302 Redirect\r\n"), MockRead("Location: http://good.com/\r\n"), @@ -765,7 +776,7 @@ TEST_F(HttpNetworkTransactionTest, DuplicateDistinctLocationHeaders) { // Do a request using the HEAD method. Verify that we don't try to read the // message body (since HEAD has none). -TEST_F(HttpNetworkTransactionTest, Head) { +TEST_F(HttpNetworkTransactionSpdy3Test, Head) { HttpRequestInfo request; request.method = "HEAD"; request.url = GURL("http://www.google.com/"); @@ -825,7 +836,7 @@ TEST_F(HttpNetworkTransactionTest, Head) { EXPECT_EQ("", response_data); } -TEST_F(HttpNetworkTransactionTest, ReuseConnection) { +TEST_F(HttpNetworkTransactionSpdy3Test, ReuseConnection) { SessionDependencies session_deps; scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); @@ -872,7 +883,7 @@ TEST_F(HttpNetworkTransactionTest, ReuseConnection) { } } -TEST_F(HttpNetworkTransactionTest, Ignores100) { +TEST_F(HttpNetworkTransactionSpdy3Test, Ignores100) { HttpRequestInfo request; request.method = "POST"; request.url = GURL("http://www.foo.com/"); @@ -916,7 +927,7 @@ TEST_F(HttpNetworkTransactionTest, Ignores100) { // This test is almost the same as Ignores100 above, but the response contains // a 102 instead of a 100. Also, instead of HTTP/1.0 the response is // HTTP/1.1 and the two status headers are read in one read. -TEST_F(HttpNetworkTransactionTest, Ignores1xx) { +TEST_F(HttpNetworkTransactionSpdy3Test, Ignores1xx) { HttpRequestInfo request; request.method = "GET"; request.url = GURL("http://www.foo.com/"); @@ -955,7 +966,7 @@ TEST_F(HttpNetworkTransactionTest, Ignores1xx) { EXPECT_EQ("hello world", response_data); } -TEST_F(HttpNetworkTransactionTest, Incomplete100ThenEOF) { +TEST_F(HttpNetworkTransactionSpdy3Test, Incomplete100ThenEOF) { HttpRequestInfo request; request.method = "POST"; request.url = GURL("http://www.foo.com/"); @@ -986,7 +997,7 @@ TEST_F(HttpNetworkTransactionTest, Incomplete100ThenEOF) { EXPECT_EQ("", response_data); } -TEST_F(HttpNetworkTransactionTest, EmptyResponse) { +TEST_F(HttpNetworkTransactionSpdy3Test, EmptyResponse) { HttpRequestInfo request; request.method = "POST"; request.url = GURL("http://www.foo.com/"); @@ -1011,7 +1022,7 @@ TEST_F(HttpNetworkTransactionTest, EmptyResponse) { EXPECT_EQ(ERR_EMPTY_RESPONSE, rv); } -void HttpNetworkTransactionTest::KeepAliveConnectionResendRequestTest( +void HttpNetworkTransactionSpdy3Test::KeepAliveConnectionResendRequestTest( const MockWrite* write_failure, const MockRead* read_failure) { HttpRequestInfo request; @@ -1087,22 +1098,23 @@ void HttpNetworkTransactionTest::KeepAliveConnectionResendRequestTest( } } -TEST_F(HttpNetworkTransactionTest, KeepAliveConnectionNotConnectedOnWrite) { +TEST_F(HttpNetworkTransactionSpdy3Test, + KeepAliveConnectionNotConnectedOnWrite) { MockWrite write_failure(ASYNC, ERR_SOCKET_NOT_CONNECTED); KeepAliveConnectionResendRequestTest(&write_failure, NULL); } -TEST_F(HttpNetworkTransactionTest, KeepAliveConnectionReset) { +TEST_F(HttpNetworkTransactionSpdy3Test, KeepAliveConnectionReset) { MockRead read_failure(ASYNC, ERR_CONNECTION_RESET); KeepAliveConnectionResendRequestTest(NULL, &read_failure); } -TEST_F(HttpNetworkTransactionTest, KeepAliveConnectionEOF) { +TEST_F(HttpNetworkTransactionSpdy3Test, KeepAliveConnectionEOF) { MockRead read_failure(SYNCHRONOUS, OK); // EOF KeepAliveConnectionResendRequestTest(NULL, &read_failure); } -TEST_F(HttpNetworkTransactionTest, NonKeepAliveConnectionReset) { +TEST_F(HttpNetworkTransactionSpdy3Test, NonKeepAliveConnectionReset) { HttpRequestInfo request; request.method = "GET"; request.url = GURL("http://www.google.com/"); @@ -1142,7 +1154,7 @@ TEST_F(HttpNetworkTransactionTest, NonKeepAliveConnectionReset) { // Opera 9.52: after five attempts, blank page // Us with WinHTTP: error page (ERR_INVALID_RESPONSE) // Us: error page (EMPTY_RESPONSE) -TEST_F(HttpNetworkTransactionTest, NonKeepAliveConnectionEOF) { +TEST_F(HttpNetworkTransactionSpdy3Test, NonKeepAliveConnectionEOF) { MockRead data_reads[] = { MockRead(SYNCHRONOUS, OK), // EOF MockRead("HTTP/1.0 200 OK\r\n\r\n"), // Should not be used @@ -1156,7 +1168,7 @@ TEST_F(HttpNetworkTransactionTest, NonKeepAliveConnectionEOF) { // Test that we correctly reuse a keep-alive connection after not explicitly // reading the body. -TEST_F(HttpNetworkTransactionTest, KeepAliveAfterUnreadBody) { +TEST_F(HttpNetworkTransactionSpdy3Test, KeepAliveAfterUnreadBody) { HttpRequestInfo request; request.method = "GET"; request.url = GURL("http://www.foo.com/"); @@ -1251,7 +1263,7 @@ TEST_F(HttpNetworkTransactionTest, KeepAliveAfterUnreadBody) { // Test the request-challenge-retry sequence for basic auth. // (basic auth is the easiest to mock, because it has no randomness). -TEST_F(HttpNetworkTransactionTest, BasicAuth) { +TEST_F(HttpNetworkTransactionSpdy3Test, BasicAuth) { HttpRequestInfo request; request.method = "GET"; request.url = GURL("http://www.google.com/"); @@ -1331,7 +1343,7 @@ TEST_F(HttpNetworkTransactionTest, BasicAuth) { EXPECT_EQ(100, response->headers->GetContentLength()); } -TEST_F(HttpNetworkTransactionTest, DoNotSendAuth) { +TEST_F(HttpNetworkTransactionSpdy3Test, DoNotSendAuth) { HttpRequestInfo request; request.method = "GET"; request.url = GURL("http://www.google.com/"); @@ -1374,7 +1386,7 @@ TEST_F(HttpNetworkTransactionTest, DoNotSendAuth) { // Test the request-challenge-retry sequence for basic auth, over a keep-alive // connection. -TEST_F(HttpNetworkTransactionTest, BasicAuthKeepAlive) { +TEST_F(HttpNetworkTransactionSpdy3Test, BasicAuthKeepAlive) { HttpRequestInfo request; request.method = "GET"; request.url = GURL("http://www.google.com/"); @@ -1453,7 +1465,7 @@ TEST_F(HttpNetworkTransactionTest, BasicAuthKeepAlive) { // Test the request-challenge-retry sequence for basic auth, over a keep-alive // connection and with no response body to drain. -TEST_F(HttpNetworkTransactionTest, BasicAuthKeepAliveNoBody) { +TEST_F(HttpNetworkTransactionSpdy3Test, BasicAuthKeepAliveNoBody) { HttpRequestInfo request; request.method = "GET"; request.url = GURL("http://www.google.com/"); @@ -1529,7 +1541,7 @@ TEST_F(HttpNetworkTransactionTest, BasicAuthKeepAliveNoBody) { // Test the request-challenge-retry sequence for basic auth, over a keep-alive // connection and with a large response body to drain. -TEST_F(HttpNetworkTransactionTest, BasicAuthKeepAliveLargeBody) { +TEST_F(HttpNetworkTransactionSpdy3Test, BasicAuthKeepAliveLargeBody) { HttpRequestInfo request; request.method = "GET"; request.url = GURL("http://www.google.com/"); @@ -1613,7 +1625,7 @@ TEST_F(HttpNetworkTransactionTest, BasicAuthKeepAliveLargeBody) { // Test the request-challenge-retry sequence for basic auth, over a keep-alive // connection, but the server gets impatient and closes the connection. -TEST_F(HttpNetworkTransactionTest, BasicAuthKeepAliveImpatientServer) { +TEST_F(HttpNetworkTransactionSpdy3Test, BasicAuthKeepAliveImpatientServer) { HttpRequestInfo request; request.method = "GET"; request.url = GURL("http://www.google.com/"); @@ -1699,7 +1711,7 @@ TEST_F(HttpNetworkTransactionTest, BasicAuthKeepAliveImpatientServer) { // Test the request-challenge-retry sequence for basic auth, over a connection // that requires a restart when setting up an SSL tunnel. -TEST_F(HttpNetworkTransactionTest, BasicAuthProxyNoKeepAlive) { +TEST_F(HttpNetworkTransactionSpdy3Test, BasicAuthProxyNoKeepAlive) { HttpRequestInfo request; request.method = "GET"; request.url = GURL("https://www.google.com/"); @@ -1804,7 +1816,7 @@ TEST_F(HttpNetworkTransactionTest, BasicAuthProxyNoKeepAlive) { // Test the request-challenge-retry sequence for basic auth, over a keep-alive // proxy connection, when setting up an SSL tunnel. -TEST_F(HttpNetworkTransactionTest, BasicAuthProxyKeepAlive) { +TEST_F(HttpNetworkTransactionSpdy3Test, BasicAuthProxyKeepAlive) { HttpRequestInfo request; request.method = "GET"; request.url = GURL("https://www.google.com/"); @@ -1907,7 +1919,7 @@ TEST_F(HttpNetworkTransactionTest, BasicAuthProxyKeepAlive) { // Test that we don't read the response body when we fail to establish a tunnel, // even if the user cancels the proxy's auth attempt. -TEST_F(HttpNetworkTransactionTest, BasicAuthProxyCancelTunnel) { +TEST_F(HttpNetworkTransactionSpdy3Test, BasicAuthProxyCancelTunnel) { HttpRequestInfo request; request.method = "GET"; request.url = GURL("https://www.google.com/"); @@ -1965,7 +1977,7 @@ TEST_F(HttpNetworkTransactionTest, BasicAuthProxyCancelTunnel) { // Test when a server (non-proxy) returns a 407 (proxy-authenticate). // The request should fail with ERR_UNEXPECTED_PROXY_AUTH. -TEST_F(HttpNetworkTransactionTest, UnexpectedProxyAuth) { +TEST_F(HttpNetworkTransactionSpdy3Test, UnexpectedProxyAuth) { HttpRequestInfo request; request.method = "GET"; request.url = GURL("http://www.google.com/"); @@ -2010,7 +2022,8 @@ TEST_F(HttpNetworkTransactionTest, UnexpectedProxyAuth) { // a non-authenticating proxy - there is nothing to indicate whether the // response came from the proxy or the server, so it is treated as if the proxy // issued the challenge. -TEST_F(HttpNetworkTransactionTest, HttpsServerRequestsProxyAuthThroughProxy) { +TEST_F(HttpNetworkTransactionSpdy3Test, + HttpsServerRequestsProxyAuthThroughProxy) { HttpRequestInfo request; request.method = "GET"; request.url = GURL("https://www.google.com/"); @@ -2067,7 +2080,7 @@ TEST_F(HttpNetworkTransactionTest, HttpsServerRequestsProxyAuthThroughProxy) { } // Test a simple get through an HTTPS Proxy. -TEST_F(HttpNetworkTransactionTest, HttpsProxyGet) { +TEST_F(HttpNetworkTransactionSpdy3Test, HttpsProxyGet) { HttpRequestInfo request; request.method = "GET"; request.url = GURL("http://www.google.com/"); @@ -2122,7 +2135,7 @@ TEST_F(HttpNetworkTransactionTest, HttpsProxyGet) { } // Test a SPDY get through an HTTPS Proxy. -TEST_F(HttpNetworkTransactionTest, HttpsProxySpdyGet) { +TEST_F(HttpNetworkTransactionSpdy3Test, HttpsProxySpdyGet) { HttpRequestInfo request; request.method = "GET"; request.url = GURL("http://www.google.com/"); @@ -2176,11 +2189,11 @@ TEST_F(HttpNetworkTransactionTest, HttpsProxySpdyGet) { std::string response_data; ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data)); - EXPECT_EQ(net::kUploadData, response_data); + EXPECT_EQ(kUploadData, response_data); } // Test a SPDY get through an HTTPS Proxy. -TEST_F(HttpNetworkTransactionTest, HttpsProxySpdyGetWithProxyAuth) { +TEST_F(HttpNetworkTransactionSpdy3Test, HttpsProxySpdyGetWithProxyAuth) { HttpRequestInfo request; request.method = "GET"; request.url = GURL("http://www.google.com/"); @@ -2280,7 +2293,7 @@ TEST_F(HttpNetworkTransactionTest, HttpsProxySpdyGetWithProxyAuth) { } // Test a SPDY CONNECT through an HTTPS Proxy to an HTTPS (non-SPDY) Server. -TEST_F(HttpNetworkTransactionTest, HttpsProxySpdyConnectHttps) { +TEST_F(HttpNetworkTransactionSpdy3Test, HttpsProxySpdyConnectHttps) { HttpRequestInfo request; request.method = "GET"; request.url = GURL("https://www.google.com/"); @@ -2361,7 +2374,7 @@ TEST_F(HttpNetworkTransactionTest, HttpsProxySpdyConnectHttps) { } // Test a SPDY CONNECT through an HTTPS Proxy to a SPDY server. -TEST_F(HttpNetworkTransactionTest, HttpsProxySpdyConnectSpdy) { +TEST_F(HttpNetworkTransactionSpdy3Test, HttpsProxySpdyConnectSpdy) { HttpRequestInfo request; request.method = "GET"; request.url = GURL("https://www.google.com/"); @@ -2436,11 +2449,11 @@ TEST_F(HttpNetworkTransactionTest, HttpsProxySpdyConnectSpdy) { std::string response_data; ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data)); - EXPECT_EQ(net::kUploadData, response_data); + EXPECT_EQ(kUploadData, response_data); } // Test a SPDY CONNECT failure through an HTTPS Proxy. -TEST_F(HttpNetworkTransactionTest, HttpsProxySpdyConnectFailure) { +TEST_F(HttpNetworkTransactionSpdy3Test, HttpsProxySpdyConnectFailure) { HttpRequestInfo request; request.method = "GET"; request.url = GURL("https://www.google.com/"); @@ -2498,7 +2511,7 @@ TEST_F(HttpNetworkTransactionTest, HttpsProxySpdyConnectFailure) { } // Test the challenge-response-retry sequence through an HTTPS Proxy -TEST_F(HttpNetworkTransactionTest, HttpsProxyAuthRetry) { +TEST_F(HttpNetworkTransactionSpdy3Test, HttpsProxyAuthRetry) { HttpRequestInfo request; request.method = "GET"; request.url = GURL("http://www.google.com/"); @@ -2585,7 +2598,7 @@ TEST_F(HttpNetworkTransactionTest, HttpsProxyAuthRetry) { EXPECT_TRUE(response->auth_challenge.get() == NULL); } -void HttpNetworkTransactionTest::ConnectStatusHelperWithExpectedStatus( +void HttpNetworkTransactionSpdy3Test::ConnectStatusHelperWithExpectedStatus( const MockRead& status, int expected_status) { HttpRequestInfo request; request.method = "GET"; @@ -2626,179 +2639,180 @@ void HttpNetworkTransactionTest::ConnectStatusHelperWithExpectedStatus( EXPECT_EQ(expected_status, rv); } -void HttpNetworkTransactionTest::ConnectStatusHelper(const MockRead& status) { +void HttpNetworkTransactionSpdy3Test::ConnectStatusHelper( + const MockRead& status) { ConnectStatusHelperWithExpectedStatus( status, ERR_TUNNEL_CONNECTION_FAILED); } -TEST_F(HttpNetworkTransactionTest, ConnectStatus100) { +TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus100) { ConnectStatusHelper(MockRead("HTTP/1.1 100 Continue\r\n")); } -TEST_F(HttpNetworkTransactionTest, ConnectStatus101) { +TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus101) { ConnectStatusHelper(MockRead("HTTP/1.1 101 Switching Protocols\r\n")); } -TEST_F(HttpNetworkTransactionTest, ConnectStatus201) { +TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus201) { ConnectStatusHelper(MockRead("HTTP/1.1 201 Created\r\n")); } -TEST_F(HttpNetworkTransactionTest, ConnectStatus202) { +TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus202) { ConnectStatusHelper(MockRead("HTTP/1.1 202 Accepted\r\n")); } -TEST_F(HttpNetworkTransactionTest, ConnectStatus203) { +TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus203) { ConnectStatusHelper( MockRead("HTTP/1.1 203 Non-Authoritative Information\r\n")); } -TEST_F(HttpNetworkTransactionTest, ConnectStatus204) { +TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus204) { ConnectStatusHelper(MockRead("HTTP/1.1 204 No Content\r\n")); } -TEST_F(HttpNetworkTransactionTest, ConnectStatus205) { +TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus205) { ConnectStatusHelper(MockRead("HTTP/1.1 205 Reset Content\r\n")); } -TEST_F(HttpNetworkTransactionTest, ConnectStatus206) { +TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus206) { ConnectStatusHelper(MockRead("HTTP/1.1 206 Partial Content\r\n")); } -TEST_F(HttpNetworkTransactionTest, ConnectStatus300) { +TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus300) { ConnectStatusHelper(MockRead("HTTP/1.1 300 Multiple Choices\r\n")); } -TEST_F(HttpNetworkTransactionTest, ConnectStatus301) { +TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus301) { ConnectStatusHelper(MockRead("HTTP/1.1 301 Moved Permanently\r\n")); } -TEST_F(HttpNetworkTransactionTest, ConnectStatus302) { +TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus302) { ConnectStatusHelper(MockRead("HTTP/1.1 302 Found\r\n")); } -TEST_F(HttpNetworkTransactionTest, ConnectStatus303) { +TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus303) { ConnectStatusHelper(MockRead("HTTP/1.1 303 See Other\r\n")); } -TEST_F(HttpNetworkTransactionTest, ConnectStatus304) { +TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus304) { ConnectStatusHelper(MockRead("HTTP/1.1 304 Not Modified\r\n")); } -TEST_F(HttpNetworkTransactionTest, ConnectStatus305) { +TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus305) { ConnectStatusHelper(MockRead("HTTP/1.1 305 Use Proxy\r\n")); } -TEST_F(HttpNetworkTransactionTest, ConnectStatus306) { +TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus306) { ConnectStatusHelper(MockRead("HTTP/1.1 306\r\n")); } -TEST_F(HttpNetworkTransactionTest, ConnectStatus307) { +TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus307) { ConnectStatusHelper(MockRead("HTTP/1.1 307 Temporary Redirect\r\n")); } -TEST_F(HttpNetworkTransactionTest, ConnectStatus400) { +TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus400) { ConnectStatusHelper(MockRead("HTTP/1.1 400 Bad Request\r\n")); } -TEST_F(HttpNetworkTransactionTest, ConnectStatus401) { +TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus401) { ConnectStatusHelper(MockRead("HTTP/1.1 401 Unauthorized\r\n")); } -TEST_F(HttpNetworkTransactionTest, ConnectStatus402) { +TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus402) { ConnectStatusHelper(MockRead("HTTP/1.1 402 Payment Required\r\n")); } -TEST_F(HttpNetworkTransactionTest, ConnectStatus403) { +TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus403) { ConnectStatusHelper(MockRead("HTTP/1.1 403 Forbidden\r\n")); } -TEST_F(HttpNetworkTransactionTest, ConnectStatus404) { +TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus404) { ConnectStatusHelper(MockRead("HTTP/1.1 404 Not Found\r\n")); } -TEST_F(HttpNetworkTransactionTest, ConnectStatus405) { +TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus405) { ConnectStatusHelper(MockRead("HTTP/1.1 405 Method Not Allowed\r\n")); } -TEST_F(HttpNetworkTransactionTest, ConnectStatus406) { +TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus406) { ConnectStatusHelper(MockRead("HTTP/1.1 406 Not Acceptable\r\n")); } -TEST_F(HttpNetworkTransactionTest, ConnectStatus407) { +TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus407) { ConnectStatusHelperWithExpectedStatus( MockRead("HTTP/1.1 407 Proxy Authentication Required\r\n"), ERR_PROXY_AUTH_UNSUPPORTED); } -TEST_F(HttpNetworkTransactionTest, ConnectStatus408) { +TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus408) { ConnectStatusHelper(MockRead("HTTP/1.1 408 Request Timeout\r\n")); } -TEST_F(HttpNetworkTransactionTest, ConnectStatus409) { +TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus409) { ConnectStatusHelper(MockRead("HTTP/1.1 409 Conflict\r\n")); } -TEST_F(HttpNetworkTransactionTest, ConnectStatus410) { +TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus410) { ConnectStatusHelper(MockRead("HTTP/1.1 410 Gone\r\n")); } -TEST_F(HttpNetworkTransactionTest, ConnectStatus411) { +TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus411) { ConnectStatusHelper(MockRead("HTTP/1.1 411 Length Required\r\n")); } -TEST_F(HttpNetworkTransactionTest, ConnectStatus412) { +TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus412) { ConnectStatusHelper(MockRead("HTTP/1.1 412 Precondition Failed\r\n")); } -TEST_F(HttpNetworkTransactionTest, ConnectStatus413) { +TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus413) { ConnectStatusHelper(MockRead("HTTP/1.1 413 Request Entity Too Large\r\n")); } -TEST_F(HttpNetworkTransactionTest, ConnectStatus414) { +TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus414) { ConnectStatusHelper(MockRead("HTTP/1.1 414 Request-URI Too Long\r\n")); } -TEST_F(HttpNetworkTransactionTest, ConnectStatus415) { +TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus415) { ConnectStatusHelper(MockRead("HTTP/1.1 415 Unsupported Media Type\r\n")); } -TEST_F(HttpNetworkTransactionTest, ConnectStatus416) { +TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus416) { ConnectStatusHelper( MockRead("HTTP/1.1 416 Requested Range Not Satisfiable\r\n")); } -TEST_F(HttpNetworkTransactionTest, ConnectStatus417) { +TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus417) { ConnectStatusHelper(MockRead("HTTP/1.1 417 Expectation Failed\r\n")); } -TEST_F(HttpNetworkTransactionTest, ConnectStatus500) { +TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus500) { ConnectStatusHelper(MockRead("HTTP/1.1 500 Internal Server Error\r\n")); } -TEST_F(HttpNetworkTransactionTest, ConnectStatus501) { +TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus501) { ConnectStatusHelper(MockRead("HTTP/1.1 501 Not Implemented\r\n")); } -TEST_F(HttpNetworkTransactionTest, ConnectStatus502) { +TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus502) { ConnectStatusHelper(MockRead("HTTP/1.1 502 Bad Gateway\r\n")); } -TEST_F(HttpNetworkTransactionTest, ConnectStatus503) { +TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus503) { ConnectStatusHelper(MockRead("HTTP/1.1 503 Service Unavailable\r\n")); } -TEST_F(HttpNetworkTransactionTest, ConnectStatus504) { +TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus504) { ConnectStatusHelper(MockRead("HTTP/1.1 504 Gateway Timeout\r\n")); } -TEST_F(HttpNetworkTransactionTest, ConnectStatus505) { +TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus505) { ConnectStatusHelper(MockRead("HTTP/1.1 505 HTTP Version Not Supported\r\n")); } // Test the flow when both the proxy server AND origin server require // authentication. Again, this uses basic auth for both since that is // the simplest to mock. -TEST_F(HttpNetworkTransactionTest, BasicAuthProxyThenServer) { +TEST_F(HttpNetworkTransactionSpdy3Test, BasicAuthProxyThenServer) { HttpRequestInfo request; request.method = "GET"; request.url = GURL("http://www.google.com/"); @@ -2927,7 +2941,7 @@ TEST_F(HttpNetworkTransactionTest, BasicAuthProxyThenServer) { // bytes in the debugger. // Enter the correct password and authenticate successfully. -TEST_F(HttpNetworkTransactionTest, NTLMAuth1) { +TEST_F(HttpNetworkTransactionSpdy3Test, NTLMAuth1) { HttpRequestInfo request; request.method = "GET"; request.url = GURL("http://172.22.68.17/kids/login.aspx"); @@ -3056,7 +3070,7 @@ TEST_F(HttpNetworkTransactionTest, NTLMAuth1) { } // Enter a wrong password, and then the correct one. -TEST_F(HttpNetworkTransactionTest, NTLMAuth2) { +TEST_F(HttpNetworkTransactionSpdy3Test, NTLMAuth2) { HttpRequestInfo request; request.method = "GET"; request.url = GURL("http://172.22.68.17/kids/login.aspx"); @@ -3258,7 +3272,7 @@ TEST_F(HttpNetworkTransactionTest, NTLMAuth2) { // Test reading a server response which has only headers, and no body. // After some maximum number of bytes is consumed, the transaction should // fail with ERR_RESPONSE_HEADERS_TOO_BIG. -TEST_F(HttpNetworkTransactionTest, LargeHeadersNoBody) { +TEST_F(HttpNetworkTransactionSpdy3Test, LargeHeadersNoBody) { HttpRequestInfo request; request.method = "GET"; request.url = GURL("http://www.google.com/"); @@ -3296,7 +3310,8 @@ TEST_F(HttpNetworkTransactionTest, LargeHeadersNoBody) { // Make sure that we don't try to reuse a TCPClientSocket when failing to // establish tunnel. // http://code.google.com/p/chromium/issues/detail?id=3772 -TEST_F(HttpNetworkTransactionTest, DontRecycleTransportSocketForSSLTunnel) { +TEST_F(HttpNetworkTransactionSpdy3Test, + DontRecycleTransportSocketForSSLTunnel) { HttpRequestInfo request; request.method = "GET"; request.url = GURL("https://www.google.com/"); @@ -3354,7 +3369,7 @@ TEST_F(HttpNetworkTransactionTest, DontRecycleTransportSocketForSSLTunnel) { } // Make sure that we recycle a socket after reading all of the response body. -TEST_F(HttpNetworkTransactionTest, RecycleSocket) { +TEST_F(HttpNetworkTransactionSpdy3Test, RecycleSocket) { HttpRequestInfo request; request.method = "GET"; request.url = GURL("http://www.google.com/"); @@ -3410,7 +3425,7 @@ TEST_F(HttpNetworkTransactionTest, RecycleSocket) { // Make sure that we recycle a SSL socket after reading all of the response // body. -TEST_F(HttpNetworkTransactionTest, RecycleSSLSocket) { +TEST_F(HttpNetworkTransactionSpdy3Test, RecycleSSLSocket) { SessionDependencies session_deps; HttpRequestInfo request; request.method = "GET"; @@ -3469,7 +3484,7 @@ TEST_F(HttpNetworkTransactionTest, RecycleSSLSocket) { // Grab a SSL socket, use it, and put it back into the pool. Then, reuse it // from the pool and make sure that we recover okay. -TEST_F(HttpNetworkTransactionTest, RecycleDeadSSLSocket) { +TEST_F(HttpNetworkTransactionSpdy3Test, RecycleDeadSSLSocket) { SessionDependencies session_deps; HttpRequestInfo request; request.method = "GET"; @@ -3564,7 +3579,7 @@ TEST_F(HttpNetworkTransactionTest, RecycleDeadSSLSocket) { // Make sure that we recycle a socket after a zero-length response. // http://crbug.com/9880 -TEST_F(HttpNetworkTransactionTest, RecycleSocketAfterZeroContentLength) { +TEST_F(HttpNetworkTransactionSpdy3Test, RecycleSocketAfterZeroContentLength) { HttpRequestInfo request; request.method = "GET"; request.url = GURL("http://www.google.com/csi?v=3&s=web&action=&" @@ -3619,7 +3634,7 @@ TEST_F(HttpNetworkTransactionTest, RecycleSocketAfterZeroContentLength) { EXPECT_EQ(1, session->GetTransportSocketPool()->IdleSocketCount()); } -TEST_F(HttpNetworkTransactionTest, ResendRequestOnWriteBodyError) { +TEST_F(HttpNetworkTransactionSpdy3Test, ResendRequestOnWriteBodyError) { HttpRequestInfo request[2]; // Transaction 1: a GET request that succeeds. The socket is recycled // after use. @@ -3709,7 +3724,7 @@ TEST_F(HttpNetworkTransactionTest, ResendRequestOnWriteBodyError) { // Test the request-challenge-retry sequence for basic auth when there is // an identity in the URL. The request should be sent as normal, but when // it fails the identity from the URL is no longer used. -TEST_F(HttpNetworkTransactionTest, IgnoreAuthIdentityInURL) { +TEST_F(HttpNetworkTransactionSpdy3Test, IgnoreAuthIdentityInURL) { HttpRequestInfo request; request.method = "GET"; request.url = GURL("http://foo:b@r@www.google.com/"); @@ -3752,7 +3767,7 @@ TEST_F(HttpNetworkTransactionTest, IgnoreAuthIdentityInURL) { } // Test that previously tried username/passwords for a realm get re-used. -TEST_F(HttpNetworkTransactionTest, BasicAuthCacheAndPreauth) { +TEST_F(HttpNetworkTransactionSpdy3Test, BasicAuthCacheAndPreauth) { SessionDependencies session_deps; scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); @@ -4132,7 +4147,7 @@ TEST_F(HttpNetworkTransactionTest, BasicAuthCacheAndPreauth) { // Tests that nonce count increments when multiple auth attempts // are started with the same nonce. -TEST_F(HttpNetworkTransactionTest, DigestPreAuthNonceCount) { +TEST_F(HttpNetworkTransactionSpdy3Test, DigestPreAuthNonceCount) { SessionDependencies session_deps; HttpAuthHandlerDigest::Factory* digest_factory = new HttpAuthHandlerDigest::Factory(); @@ -4265,7 +4280,7 @@ TEST_F(HttpNetworkTransactionTest, DigestPreAuthNonceCount) { } // Test the ResetStateForRestart() private method. -TEST_F(HttpNetworkTransactionTest, ResetStateForRestart) { +TEST_F(HttpNetworkTransactionSpdy3Test, ResetStateForRestart) { // Create a transaction (the dependencies aren't important). SessionDependencies session_deps; scoped_ptr<HttpNetworkTransaction> trans( @@ -4308,7 +4323,7 @@ TEST_F(HttpNetworkTransactionTest, ResetStateForRestart) { } // Test HTTPS connections to a site with a bad certificate -TEST_F(HttpNetworkTransactionTest, HTTPSBadCertificate) { +TEST_F(HttpNetworkTransactionSpdy3Test, HTTPSBadCertificate) { HttpRequestInfo request; request.method = "GET"; request.url = GURL("https://www.google.com/"); @@ -4364,7 +4379,7 @@ TEST_F(HttpNetworkTransactionTest, HTTPSBadCertificate) { // Test HTTPS connections to a site with a bad certificate, going through a // proxy -TEST_F(HttpNetworkTransactionTest, HTTPSBadCertificateViaProxy) { +TEST_F(HttpNetworkTransactionSpdy3Test, HTTPSBadCertificateViaProxy) { SessionDependencies session_deps(ProxyService::CreateFixed("myproxy:70")); HttpRequestInfo request; @@ -4442,7 +4457,7 @@ TEST_F(HttpNetworkTransactionTest, HTTPSBadCertificateViaProxy) { // Test HTTPS connections to a site, going through an HTTPS proxy -TEST_F(HttpNetworkTransactionTest, HTTPSViaHttpsProxy) { +TEST_F(HttpNetworkTransactionSpdy3Test, HTTPSViaHttpsProxy) { SessionDependencies session_deps(ProxyService::CreateFixed( "https://proxy:70")); @@ -4498,7 +4513,7 @@ TEST_F(HttpNetworkTransactionTest, HTTPSViaHttpsProxy) { } // Test an HTTPS Proxy's ability to redirect a CONNECT request -TEST_F(HttpNetworkTransactionTest, RedirectOfHttpsConnectViaHttpsProxy) { +TEST_F(HttpNetworkTransactionSpdy3Test, RedirectOfHttpsConnectViaHttpsProxy) { SessionDependencies session_deps( ProxyService::CreateFixed("https://proxy:70")); @@ -4548,7 +4563,7 @@ TEST_F(HttpNetworkTransactionTest, RedirectOfHttpsConnectViaHttpsProxy) { } // Test an HTTPS (SPDY) Proxy's ability to redirect a CONNECT request -TEST_F(HttpNetworkTransactionTest, RedirectOfHttpsConnectViaSpdyProxy) { +TEST_F(HttpNetworkTransactionSpdy3Test, RedirectOfHttpsConnectViaSpdyProxy) { SessionDependencies session_deps( ProxyService::CreateFixed("https://proxy:70")); @@ -4607,7 +4622,8 @@ TEST_F(HttpNetworkTransactionTest, RedirectOfHttpsConnectViaSpdyProxy) { } // Test an HTTPS Proxy's ability to provide a response to a CONNECT request -TEST_F(HttpNetworkTransactionTest, ErrorResponseTofHttpsConnectViaHttpsProxy) { +TEST_F(HttpNetworkTransactionSpdy3Test, + ErrorResponseTofHttpsConnectViaHttpsProxy) { SessionDependencies session_deps( ProxyService::CreateFixed("https://proxy:70")); @@ -4662,7 +4678,8 @@ TEST_F(HttpNetworkTransactionTest, ErrorResponseTofHttpsConnectViaHttpsProxy) { // Test an HTTPS (SPDY) Proxy's ability to provide a response to a CONNECT // request -TEST_F(HttpNetworkTransactionTest, ErrorResponseTofHttpsConnectViaSpdyProxy) { +TEST_F(HttpNetworkTransactionSpdy3Test, + ErrorResponseTofHttpsConnectViaSpdyProxy) { SessionDependencies session_deps( ProxyService::CreateFixed("https://proxy:70")); @@ -4727,7 +4744,7 @@ TEST_F(HttpNetworkTransactionTest, ErrorResponseTofHttpsConnectViaSpdyProxy) { // Test the request-challenge-retry sequence for basic auth, through // a SPDY proxy over a single SPDY session. -TEST_F(HttpNetworkTransactionTest, BasicAuthSpdyProxy) { +TEST_F(HttpNetworkTransactionSpdy3Test, BasicAuthSpdyProxy) { HttpRequestInfo request; request.method = "GET"; request.url = GURL("https://www.google.com/"); @@ -4808,7 +4825,7 @@ TEST_F(HttpNetworkTransactionTest, BasicAuthSpdyProxy) { session_deps.socket_factory.AddSocketDataProvider(spdy_data.get()); // Negotiate SPDY to the proxy SSLSocketDataProvider proxy(ASYNC, OK); - proxy.SetNextProto(SSLClientSocket::kProtoSPDY2); + proxy.SetNextProto(SSLClientSocket::kProtoSPDY21); session_deps.socket_factory.AddSSLSocketDataProvider(&proxy); // Vanilla SSL to the server SSLSocketDataProvider server(ASYNC, OK); @@ -4867,7 +4884,7 @@ TEST_F(HttpNetworkTransactionTest, BasicAuthSpdyProxy) { // Test HTTPS connections to a site with a bad certificate, going through an // HTTPS proxy -TEST_F(HttpNetworkTransactionTest, HTTPSBadCertificateViaHttpsProxy) { +TEST_F(HttpNetworkTransactionSpdy3Test, HTTPSBadCertificateViaHttpsProxy) { SessionDependencies session_deps(ProxyService::CreateFixed( "https://proxy:70")); @@ -4947,7 +4964,7 @@ TEST_F(HttpNetworkTransactionTest, HTTPSBadCertificateViaHttpsProxy) { EXPECT_EQ(100, response->headers->GetContentLength()); } -TEST_F(HttpNetworkTransactionTest, BuildRequest_UserAgent) { +TEST_F(HttpNetworkTransactionSpdy3Test, BuildRequest_UserAgent) { HttpRequestInfo request; request.method = "GET"; request.url = GURL("http://www.google.com/"); @@ -4986,7 +5003,7 @@ TEST_F(HttpNetworkTransactionTest, BuildRequest_UserAgent) { EXPECT_EQ(OK, rv); } -TEST_F(HttpNetworkTransactionTest, BuildRequest_UserAgentOverTunnel) { +TEST_F(HttpNetworkTransactionSpdy3Test, BuildRequest_UserAgentOverTunnel) { HttpRequestInfo request; request.method = "GET"; request.url = GURL("https://www.google.com/"); @@ -5024,7 +5041,7 @@ TEST_F(HttpNetworkTransactionTest, BuildRequest_UserAgentOverTunnel) { EXPECT_EQ(OK, rv); } -TEST_F(HttpNetworkTransactionTest, BuildRequest_Referer) { +TEST_F(HttpNetworkTransactionSpdy3Test, BuildRequest_Referer) { HttpRequestInfo request; request.method = "GET"; request.url = GURL("http://www.google.com/"); @@ -5064,7 +5081,7 @@ TEST_F(HttpNetworkTransactionTest, BuildRequest_Referer) { EXPECT_EQ(OK, rv); } -TEST_F(HttpNetworkTransactionTest, BuildRequest_PostContentLengthZero) { +TEST_F(HttpNetworkTransactionSpdy3Test, BuildRequest_PostContentLengthZero) { HttpRequestInfo request; request.method = "POST"; request.url = GURL("http://www.google.com/"); @@ -5101,7 +5118,7 @@ TEST_F(HttpNetworkTransactionTest, BuildRequest_PostContentLengthZero) { EXPECT_EQ(OK, rv); } -TEST_F(HttpNetworkTransactionTest, BuildRequest_PutContentLengthZero) { +TEST_F(HttpNetworkTransactionSpdy3Test, BuildRequest_PutContentLengthZero) { HttpRequestInfo request; request.method = "PUT"; request.url = GURL("http://www.google.com/"); @@ -5138,7 +5155,7 @@ TEST_F(HttpNetworkTransactionTest, BuildRequest_PutContentLengthZero) { EXPECT_EQ(OK, rv); } -TEST_F(HttpNetworkTransactionTest, BuildRequest_HeadContentLengthZero) { +TEST_F(HttpNetworkTransactionSpdy3Test, BuildRequest_HeadContentLengthZero) { HttpRequestInfo request; request.method = "HEAD"; request.url = GURL("http://www.google.com/"); @@ -5175,7 +5192,7 @@ TEST_F(HttpNetworkTransactionTest, BuildRequest_HeadContentLengthZero) { EXPECT_EQ(OK, rv); } -TEST_F(HttpNetworkTransactionTest, BuildRequest_CacheControlNoCache) { +TEST_F(HttpNetworkTransactionSpdy3Test, BuildRequest_CacheControlNoCache) { HttpRequestInfo request; request.method = "GET"; request.url = GURL("http://www.google.com/"); @@ -5214,7 +5231,7 @@ TEST_F(HttpNetworkTransactionTest, BuildRequest_CacheControlNoCache) { EXPECT_EQ(OK, rv); } -TEST_F(HttpNetworkTransactionTest, +TEST_F(HttpNetworkTransactionSpdy3Test, BuildRequest_CacheControlValidateCache) { HttpRequestInfo request; request.method = "GET"; @@ -5253,7 +5270,7 @@ TEST_F(HttpNetworkTransactionTest, EXPECT_EQ(OK, rv); } -TEST_F(HttpNetworkTransactionTest, BuildRequest_ExtraHeaders) { +TEST_F(HttpNetworkTransactionSpdy3Test, BuildRequest_ExtraHeaders) { HttpRequestInfo request; request.method = "GET"; request.url = GURL("http://www.google.com/"); @@ -5291,7 +5308,7 @@ TEST_F(HttpNetworkTransactionTest, BuildRequest_ExtraHeaders) { EXPECT_EQ(OK, rv); } -TEST_F(HttpNetworkTransactionTest, BuildRequest_ExtraHeadersStripped) { +TEST_F(HttpNetworkTransactionSpdy3Test, BuildRequest_ExtraHeadersStripped) { HttpRequestInfo request; request.method = "GET"; request.url = GURL("http://www.google.com/"); @@ -5340,7 +5357,7 @@ TEST_F(HttpNetworkTransactionTest, BuildRequest_ExtraHeadersStripped) { #define MAYBE_SOCKS4_HTTP_GET SOCKS4_HTTP_GET #endif -TEST_F(HttpNetworkTransactionTest, MAYBE_SOCKS4_HTTP_GET) { +TEST_F(HttpNetworkTransactionSpdy3Test, MAYBE_SOCKS4_HTTP_GET) { HttpRequestInfo request; request.method = "GET"; request.url = GURL("http://www.google.com/"); @@ -5398,7 +5415,7 @@ TEST_F(HttpNetworkTransactionTest, MAYBE_SOCKS4_HTTP_GET) { #define MAYBE_SOCKS4_SSL_GET SOCKS4_SSL_GET #endif -TEST_F(HttpNetworkTransactionTest, MAYBE_SOCKS4_SSL_GET) { +TEST_F(HttpNetworkTransactionSpdy3Test, MAYBE_SOCKS4_SSL_GET) { HttpRequestInfo request; request.method = "GET"; request.url = GURL("https://www.google.com/"); @@ -5454,7 +5471,7 @@ TEST_F(HttpNetworkTransactionTest, MAYBE_SOCKS4_SSL_GET) { EXPECT_EQ("Payload", response_text); } -TEST_F(HttpNetworkTransactionTest, SOCKS5_HTTP_GET) { +TEST_F(HttpNetworkTransactionSpdy3Test, SOCKS5_HTTP_GET) { HttpRequestInfo request; request.method = "GET"; request.url = GURL("http://www.google.com/"); @@ -5519,7 +5536,7 @@ TEST_F(HttpNetworkTransactionTest, SOCKS5_HTTP_GET) { EXPECT_EQ("Payload", response_text); } -TEST_F(HttpNetworkTransactionTest, SOCKS5_SSL_GET) { +TEST_F(HttpNetworkTransactionSpdy3Test, SOCKS5_SSL_GET) { HttpRequestInfo request; request.method = "GET"; request.url = GURL("https://www.google.com/"); @@ -5589,6 +5606,8 @@ TEST_F(HttpNetworkTransactionTest, SOCKS5_SSL_GET) { EXPECT_EQ("Payload", response_text); } +namespace { + // Tests that for connection endpoints the group names are correctly set. struct GroupNameTest { @@ -5627,7 +5646,9 @@ int GroupNameTransactionHelper( return trans->Start(&request, callback.callback(), BoundNetLog()); } -TEST_F(HttpNetworkTransactionTest, GroupNameForDirectConnections) { +} // namespace + +TEST_F(HttpNetworkTransactionSpdy3Test, GroupNameForDirectConnections) { const GroupNameTest tests[] = { { "", // unused @@ -5695,7 +5716,7 @@ TEST_F(HttpNetworkTransactionTest, GroupNameForDirectConnections) { HttpStreamFactory::set_use_alternate_protocols(false); } -TEST_F(HttpNetworkTransactionTest, GroupNameForHTTPProxyConnections) { +TEST_F(HttpNetworkTransactionSpdy3Test, GroupNameForHTTPProxyConnections) { const GroupNameTest tests[] = { { "http_proxy", @@ -5755,7 +5776,7 @@ TEST_F(HttpNetworkTransactionTest, GroupNameForHTTPProxyConnections) { HttpStreamFactory::set_use_alternate_protocols(false); } -TEST_F(HttpNetworkTransactionTest, GroupNameForSOCKSConnections) { +TEST_F(HttpNetworkTransactionSpdy3Test, GroupNameForSOCKSConnections) { const GroupNameTest tests[] = { { "socks4://socks_proxy:1080", @@ -5829,7 +5850,7 @@ TEST_F(HttpNetworkTransactionTest, GroupNameForSOCKSConnections) { HttpStreamFactory::set_use_alternate_protocols(false); } -TEST_F(HttpNetworkTransactionTest, ReconsiderProxyAfterFailedConnection) { +TEST_F(HttpNetworkTransactionSpdy3Test, ReconsiderProxyAfterFailedConnection) { HttpRequestInfo request; request.method = "GET"; request.url = GURL("http://www.google.com/"); @@ -5853,6 +5874,8 @@ TEST_F(HttpNetworkTransactionTest, ReconsiderProxyAfterFailedConnection) { EXPECT_EQ(ERR_PROXY_CONNECTION_FAILED, rv); } +namespace { + // Base test to make sure that when the load flags for a request specify to // bypass the cache, the DNS cache is not used. void BypassHostCacheOnRefreshHelper(int load_flags) { @@ -5908,22 +5931,24 @@ void BypassHostCacheOnRefreshHelper(int load_flags) { EXPECT_EQ(ERR_NAME_NOT_RESOLVED, rv); } +} // namespace + // There are multiple load flags that should trigger the host cache bypass. // Test each in isolation: -TEST_F(HttpNetworkTransactionTest, BypassHostCacheOnRefresh1) { +TEST_F(HttpNetworkTransactionSpdy3Test, BypassHostCacheOnRefresh1) { BypassHostCacheOnRefreshHelper(LOAD_BYPASS_CACHE); } -TEST_F(HttpNetworkTransactionTest, BypassHostCacheOnRefresh2) { +TEST_F(HttpNetworkTransactionSpdy3Test, BypassHostCacheOnRefresh2) { BypassHostCacheOnRefreshHelper(LOAD_VALIDATE_CACHE); } -TEST_F(HttpNetworkTransactionTest, BypassHostCacheOnRefresh3) { +TEST_F(HttpNetworkTransactionSpdy3Test, BypassHostCacheOnRefresh3) { BypassHostCacheOnRefreshHelper(LOAD_DISABLE_CACHE); } // Make sure we can handle an error when writing the request. -TEST_F(HttpNetworkTransactionTest, RequestWriteError) { +TEST_F(HttpNetworkTransactionSpdy3Test, RequestWriteError) { SessionDependencies session_deps; scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); @@ -5952,7 +5977,7 @@ TEST_F(HttpNetworkTransactionTest, RequestWriteError) { } // Check that a connection closed after the start of the headers finishes ok. -TEST_F(HttpNetworkTransactionTest, ConnectionClosedAfterStartOfHeaders) { +TEST_F(HttpNetworkTransactionSpdy3Test, ConnectionClosedAfterStartOfHeaders) { SessionDependencies session_deps; scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); @@ -5994,7 +6019,7 @@ TEST_F(HttpNetworkTransactionTest, ConnectionClosedAfterStartOfHeaders) { // Make sure that a dropped connection while draining the body for auth // restart does the right thing. -TEST_F(HttpNetworkTransactionTest, DrainResetOK) { +TEST_F(HttpNetworkTransactionSpdy3Test, DrainResetOK) { SessionDependencies session_deps; scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); @@ -6073,7 +6098,7 @@ TEST_F(HttpNetworkTransactionTest, DrainResetOK) { } // Test HTTPS connections going through a proxy that sends extra data. -TEST_F(HttpNetworkTransactionTest, HTTPSViaProxyWithExtraData) { +TEST_F(HttpNetworkTransactionSpdy3Test, HTTPSViaProxyWithExtraData) { SessionDependencies session_deps(ProxyService::CreateFixed("myproxy:70")); HttpRequestInfo request; @@ -6106,7 +6131,7 @@ TEST_F(HttpNetworkTransactionTest, HTTPSViaProxyWithExtraData) { EXPECT_EQ(ERR_TUNNEL_CONNECTION_FAILED, rv); } -TEST_F(HttpNetworkTransactionTest, LargeContentLengthThenClose) { +TEST_F(HttpNetworkTransactionSpdy3Test, LargeContentLengthThenClose) { HttpRequestInfo request; request.method = "GET"; request.url = GURL("http://www.google.com/"); @@ -6142,7 +6167,7 @@ TEST_F(HttpNetworkTransactionTest, LargeContentLengthThenClose) { EXPECT_EQ(ERR_CONNECTION_CLOSED, rv); } -TEST_F(HttpNetworkTransactionTest, UploadFileSmallerThanLength) { +TEST_F(HttpNetworkTransactionSpdy3Test, UploadFileSmallerThanLength) { HttpRequestInfo request; request.method = "POST"; request.url = GURL("http://www.google.com/upload"); @@ -6195,7 +6220,7 @@ TEST_F(HttpNetworkTransactionTest, UploadFileSmallerThanLength) { file_util::Delete(temp_file_path, false); } -TEST_F(HttpNetworkTransactionTest, UploadUnreadableFile) { +TEST_F(HttpNetworkTransactionSpdy3Test, UploadUnreadableFile) { HttpRequestInfo request; request.method = "POST"; request.url = GURL("http://www.google.com/upload"); @@ -6252,7 +6277,7 @@ TEST_F(HttpNetworkTransactionTest, UploadUnreadableFile) { file_util::Delete(temp_file, false); } -TEST_F(HttpNetworkTransactionTest, UnreadableUploadFileAfterAuthRestart) { +TEST_F(HttpNetworkTransactionSpdy3Test, UnreadableUploadFileAfterAuthRestart) { HttpRequestInfo request; request.method = "POST"; request.url = GURL("http://www.google.com/upload"); @@ -6341,7 +6366,7 @@ TEST_F(HttpNetworkTransactionTest, UnreadableUploadFileAfterAuthRestart) { } // Tests that changes to Auth realms are treated like auth rejections. -TEST_F(HttpNetworkTransactionTest, ChangeAuthRealms) { +TEST_F(HttpNetworkTransactionSpdy3Test, ChangeAuthRealms) { SessionDependencies session_deps; HttpRequestInfo request; @@ -6493,7 +6518,7 @@ TEST_F(HttpNetworkTransactionTest, ChangeAuthRealms) { EXPECT_TRUE(response->auth_challenge.get() == NULL); } -TEST_F(HttpNetworkTransactionTest, HonorAlternateProtocolHeader) { +TEST_F(HttpNetworkTransactionSpdy3Test, HonorAlternateProtocolHeader) { HttpStreamFactory::set_use_alternate_protocols(true); HttpStreamFactory::SetNextProtos(SpdyNextProtos()); @@ -6554,7 +6579,8 @@ TEST_F(HttpNetworkTransactionTest, HonorAlternateProtocolHeader) { HttpStreamFactory::SetNextProtos(std::vector<std::string>()); } -TEST_F(HttpNetworkTransactionTest, MarkBrokenAlternateProtocolAndFallback) { +TEST_F(HttpNetworkTransactionSpdy3Test, + MarkBrokenAlternateProtocolAndFallback) { HttpStreamFactory::set_use_alternate_protocols(true); SessionDependencies session_deps; @@ -6613,7 +6639,8 @@ TEST_F(HttpNetworkTransactionTest, MarkBrokenAlternateProtocolAndFallback) { HttpStreamFactory::set_use_alternate_protocols(false); } -TEST_F(HttpNetworkTransactionTest, AlternateProtocolPortRestrictedBlocked) { +TEST_F(HttpNetworkTransactionSpdy3Test, + AlternateProtocolPortRestrictedBlocked) { // Ensure that we're not allowed to redirect traffic via an alternate // protocol to an unrestricted (port >= 1024) when the original traffic was // on a restricted port (port < 1024). Ensure that we can redirect in all @@ -6662,7 +6689,8 @@ TEST_F(HttpNetworkTransactionTest, AlternateProtocolPortRestrictedBlocked) { HttpStreamFactory::set_use_alternate_protocols(false); } -TEST_F(HttpNetworkTransactionTest, AlternateProtocolPortRestrictedAllowed) { +TEST_F(HttpNetworkTransactionSpdy3Test, + AlternateProtocolPortRestrictedAllowed) { // Ensure that we're not allowed to redirect traffic via an alternate // protocol to an unrestricted (port >= 1024) when the original traffic was // on a restricted port (port < 1024). Ensure that we can redirect in all @@ -6711,7 +6739,8 @@ TEST_F(HttpNetworkTransactionTest, AlternateProtocolPortRestrictedAllowed) { HttpStreamFactory::set_use_alternate_protocols(false); } -TEST_F(HttpNetworkTransactionTest, AlternateProtocolPortUnrestrictedAllowed1) { +TEST_F(HttpNetworkTransactionSpdy3Test, + AlternateProtocolPortUnrestrictedAllowed1) { // Ensure that we're not allowed to redirect traffic via an alternate // protocol to an unrestricted (port >= 1024) when the original traffic was // on a restricted port (port < 1024). Ensure that we can redirect in all @@ -6760,7 +6789,8 @@ TEST_F(HttpNetworkTransactionTest, AlternateProtocolPortUnrestrictedAllowed1) { HttpStreamFactory::set_use_alternate_protocols(false); } -TEST_F(HttpNetworkTransactionTest, AlternateProtocolPortUnrestrictedAllowed2) { +TEST_F(HttpNetworkTransactionSpdy3Test, + AlternateProtocolPortUnrestrictedAllowed2) { // Ensure that we're not allowed to redirect traffic via an alternate // protocol to an unrestricted (port >= 1024) when the original traffic was // on a restricted port (port < 1024). Ensure that we can redirect in all @@ -6809,7 +6839,7 @@ TEST_F(HttpNetworkTransactionTest, AlternateProtocolPortUnrestrictedAllowed2) { HttpStreamFactory::set_use_alternate_protocols(false); } -TEST_F(HttpNetworkTransactionTest, AlternateProtocolUnsafeBlocked) { +TEST_F(HttpNetworkTransactionSpdy3Test, AlternateProtocolUnsafeBlocked) { // Ensure that we're not allowed to redirect traffic via an alternate // protocol to an unsafe port, and that we resume the second // HttpStreamFactoryImpl::Job once the alternate protocol request fails. @@ -6863,7 +6893,7 @@ TEST_F(HttpNetworkTransactionTest, AlternateProtocolUnsafeBlocked) { EXPECT_EQ("hello world", response_data); } -TEST_F(HttpNetworkTransactionTest, UseAlternateProtocolForNpnSpdy) { +TEST_F(HttpNetworkTransactionSpdy3Test, UseAlternateProtocolForNpnSpdy) { HttpStreamFactory::set_use_alternate_protocols(true); HttpStreamFactory::SetNextProtos(SpdyNextProtos()); SessionDependencies session_deps; @@ -6952,7 +6982,7 @@ TEST_F(HttpNetworkTransactionTest, UseAlternateProtocolForNpnSpdy) { HttpStreamFactory::set_use_alternate_protocols(false); } -TEST_F(HttpNetworkTransactionTest, AlternateProtocolWithSpdyLateBinding) { +TEST_F(HttpNetworkTransactionSpdy3Test, AlternateProtocolWithSpdyLateBinding) { HttpStreamFactory::set_use_alternate_protocols(true); HttpStreamFactory::SetNextProtos(SpdyNextProtos()); SessionDependencies session_deps; @@ -7068,7 +7098,7 @@ TEST_F(HttpNetworkTransactionTest, AlternateProtocolWithSpdyLateBinding) { HttpStreamFactory::set_use_alternate_protocols(false); } -TEST_F(HttpNetworkTransactionTest, StallAlternateProtocolForNpnSpdy) { +TEST_F(HttpNetworkTransactionSpdy3Test, StallAlternateProtocolForNpnSpdy) { HttpStreamFactory::set_use_alternate_protocols(true); HttpStreamFactory::SetNextProtos(SpdyNextProtos()); SessionDependencies session_deps; @@ -7191,7 +7221,8 @@ class CapturingProxyResolver : public ProxyResolver { DISALLOW_COPY_AND_ASSIGN(CapturingProxyResolver); }; -TEST_F(HttpNetworkTransactionTest, UseAlternateProtocolForTunneledNpnSpdy) { +TEST_F(HttpNetworkTransactionSpdy3Test, + UseAlternateProtocolForTunneledNpnSpdy) { HttpStreamFactory::set_use_alternate_protocols(true); HttpStreamFactory::SetNextProtos(SpdyNextProtos()); @@ -7303,7 +7334,7 @@ TEST_F(HttpNetworkTransactionTest, UseAlternateProtocolForTunneledNpnSpdy) { HttpStreamFactory::set_use_alternate_protocols(false); } -TEST_F(HttpNetworkTransactionTest, +TEST_F(HttpNetworkTransactionSpdy3Test, UseAlternateProtocolForNpnSpdyWithExistingSpdySession) { HttpStreamFactory::set_use_alternate_protocols(true); HttpStreamFactory::SetNextProtos(SpdyNextProtos()); @@ -7437,7 +7468,7 @@ TEST_F(HttpNetworkTransactionTest, // potentially running up to three rounds in each of the tests. The TestConfig // specifies both the configuration for the test as well as the expectations // for the results. -TEST_F(HttpNetworkTransactionTest, GenerateAuthToken) { +TEST_F(HttpNetworkTransactionSpdy3Test, GenerateAuthToken) { static const char kServer[] = "http://www.example.com"; static const char kSecureServer[] = "https://www.example.com"; static const char kProxy[] = "myproxy:70"; @@ -7835,7 +7866,7 @@ TEST_F(HttpNetworkTransactionTest, GenerateAuthToken) { } } -TEST_F(HttpNetworkTransactionTest, MultiRoundAuth) { +TEST_F(HttpNetworkTransactionSpdy3Test, MultiRoundAuth) { // Do multi-round authentication and make sure it works correctly. SessionDependencies session_deps; HttpAuthHandlerMock::Factory* auth_factory( @@ -8049,7 +8080,7 @@ class TLSDecompressionFailureSocketDataProvider : public SocketDataProvider { // Test that we restart a connection when we see a decompression failure from // the peer during the handshake. (In the real world we'll restart with SSLv3 // and we won't offer DEFLATE in that case.) -TEST_F(HttpNetworkTransactionTest, RestartAfterTLSDecompressionFailure) { +TEST_F(HttpNetworkTransactionSpdy3Test, RestartAfterTLSDecompressionFailure) { HttpRequestInfo request; request.method = "GET"; request.url = GURL("https://tlsdecompressionfailure.example.com/"); @@ -8096,7 +8127,7 @@ TEST_F(HttpNetworkTransactionTest, RestartAfterTLSDecompressionFailure) { // peer while reading the first bytes from the connection. This occurs when the // peer cannot handle DEFLATE but we're using False Start, so we don't notice // in the handshake. -TEST_F(HttpNetworkTransactionTest, +TEST_F(HttpNetworkTransactionSpdy3Test, RestartAfterTLSDecompressionFailureWithFalseStart) { HttpRequestInfo request; request.method = "GET"; @@ -8136,7 +8167,7 @@ TEST_F(HttpNetworkTransactionTest, // This tests the case that a request is issued via http instead of spdy after // npn is negotiated. -TEST_F(HttpNetworkTransactionTest, NpnWithHttpOverSSL) { +TEST_F(HttpNetworkTransactionSpdy3Test, NpnWithHttpOverSSL) { HttpStreamFactory::set_use_alternate_protocols(true); HttpStreamFactory::SetNextProtos( MakeNextProtos("http/1.1", "http1.1", NULL)); @@ -8196,7 +8227,7 @@ TEST_F(HttpNetworkTransactionTest, NpnWithHttpOverSSL) { HttpStreamFactory::set_use_alternate_protocols(false); } -TEST_F(HttpNetworkTransactionTest, SpdyPostNPNServerHangup) { +TEST_F(HttpNetworkTransactionSpdy3Test, SpdyPostNPNServerHangup) { // Simulate the SSL handshake completing with an NPN negotiation // followed by an immediate server closing of the socket. // Fix crash: http://crbug.com/46369 @@ -8240,7 +8271,7 @@ TEST_F(HttpNetworkTransactionTest, SpdyPostNPNServerHangup) { HttpStreamFactory::set_use_alternate_protocols(false); } -TEST_F(HttpNetworkTransactionTest, SpdyAlternateProtocolThroughProxy) { +TEST_F(HttpNetworkTransactionSpdy3Test, SpdyAlternateProtocolThroughProxy) { // This test ensures that the URL passed into the proxy is upgraded // to https when doing an Alternate Protocol upgrade. HttpStreamFactory::set_use_alternate_protocols(true); @@ -8392,7 +8423,7 @@ TEST_F(HttpNetworkTransactionTest, SpdyAlternateProtocolThroughProxy) { // Test that if we cancel the transaction as the connection is completing, that // everything tears down correctly. -TEST_F(HttpNetworkTransactionTest, SimpleCancel) { +TEST_F(HttpNetworkTransactionSpdy3Test, SimpleCancel) { // Setup everything about the connection to complete synchronously, so that // after calling HttpNetworkTransaction::Start, the only thing we're waiting // for is the callback from the HttpStreamRequest. @@ -8430,7 +8461,7 @@ TEST_F(HttpNetworkTransactionTest, SimpleCancel) { } // Test a basic GET request through a proxy. -TEST_F(HttpNetworkTransactionTest, ProxyGet) { +TEST_F(HttpNetworkTransactionSpdy3Test, ProxyGet) { SessionDependencies session_deps(ProxyService::CreateFixed("myproxy:70")); CapturingBoundNetLog log(CapturingNetLog::kUnbounded); session_deps.net_log = log.bound().net_log(); @@ -8478,7 +8509,7 @@ TEST_F(HttpNetworkTransactionTest, ProxyGet) { } // Test a basic HTTPS GET request through a proxy. -TEST_F(HttpNetworkTransactionTest, ProxyTunnelGet) { +TEST_F(HttpNetworkTransactionSpdy3Test, ProxyTunnelGet) { SessionDependencies session_deps(ProxyService::CreateFixed("myproxy:70")); CapturingBoundNetLog log(CapturingNetLog::kUnbounded); session_deps.net_log = log.bound().net_log(); @@ -8545,7 +8576,7 @@ TEST_F(HttpNetworkTransactionTest, ProxyTunnelGet) { // Test a basic HTTPS GET request through a proxy, but the server hangs up // while establishing the tunnel. -TEST_F(HttpNetworkTransactionTest, ProxyTunnelGetHangup) { +TEST_F(HttpNetworkTransactionSpdy3Test, ProxyTunnelGetHangup) { SessionDependencies session_deps(ProxyService::CreateFixed("myproxy:70")); CapturingBoundNetLog log(CapturingNetLog::kUnbounded); session_deps.net_log = log.bound().net_log(); @@ -8599,7 +8630,7 @@ TEST_F(HttpNetworkTransactionTest, ProxyTunnelGetHangup) { } // Test for crbug.com/55424. -TEST_F(HttpNetworkTransactionTest, PreconnectWithExistingSpdySession) { +TEST_F(HttpNetworkTransactionSpdy3Test, PreconnectWithExistingSpdySession) { SessionDependencies session_deps; scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet( @@ -8688,7 +8719,7 @@ static void CheckErrorIsPassedBack(int error, IoMode mode) { ASSERT_EQ(error, rv); } -TEST_F(HttpNetworkTransactionTest, SSLWriteCertError) { +TEST_F(HttpNetworkTransactionSpdy3Test, SSLWriteCertError) { // Just check a grab bag of cert errors. static const int kErrors[] = { ERR_CERT_COMMON_NAME_INVALID, @@ -8707,7 +8738,8 @@ TEST_F(HttpNetworkTransactionTest, SSLWriteCertError) { // 2) TLS False Start is disabled. // 3) The initial TLS handshake requests a client certificate. // 4) The client supplies an invalid/unacceptable certificate. -TEST_F(HttpNetworkTransactionTest, ClientAuthCertCache_Direct_NoFalseStart) { +TEST_F(HttpNetworkTransactionSpdy3Test, + ClientAuthCertCache_Direct_NoFalseStart) { net::HttpRequestInfo request_info; request_info.url = GURL("https://www.example.com/"); request_info.method = "GET"; @@ -8797,7 +8829,7 @@ TEST_F(HttpNetworkTransactionTest, ClientAuthCertCache_Direct_NoFalseStart) { // 2) TLS False Start is enabled. // 3) The initial TLS handshake requests a client certificate. // 4) The client supplies an invalid/unacceptable certificate. -TEST_F(HttpNetworkTransactionTest, ClientAuthCertCache_Direct_FalseStart) { +TEST_F(HttpNetworkTransactionSpdy3Test, ClientAuthCertCache_Direct_FalseStart) { net::HttpRequestInfo request_info; request_info.url = GURL("https://www.example.com/"); request_info.method = "GET"; @@ -8904,7 +8936,7 @@ TEST_F(HttpNetworkTransactionTest, ClientAuthCertCache_Direct_FalseStart) { // proxy. // The test is repeated twice, first for connecting to an HTTPS endpoint, // then for connecting to an HTTP endpoint. -TEST_F(HttpNetworkTransactionTest, ClientAuthCertCache_Proxy_Fail) { +TEST_F(HttpNetworkTransactionSpdy3Test, ClientAuthCertCache_Proxy_Fail) { SessionDependencies session_deps( ProxyService::CreateFixed("https://proxy:70")); CapturingBoundNetLog log(CapturingNetLog::kUnbounded); @@ -8994,6 +9026,8 @@ TEST_F(HttpNetworkTransactionTest, ClientAuthCertCache_Proxy_Fail) { } } +namespace { + void IPPoolingAddAlias(MockCachingHostResolver* host_resolver, SpdySessionPoolPeer* pool_peer, std::string host, @@ -9027,7 +9061,9 @@ void IPPoolingAddAlias(MockCachingHostResolver* host_resolver, pool_peer->AddAlias(address, pair); } -TEST_F(HttpNetworkTransactionTest, UseIPConnectionPooling) { +} // namespace + +TEST_F(HttpNetworkTransactionSpdy3Test, UseIPConnectionPooling) { HttpStreamFactory::set_use_alternate_protocols(true); HttpStreamFactory::SetNextProtos(SpdyNextProtos()); @@ -9177,7 +9213,7 @@ class OneTimeCachingHostResolver : public net::HostResolver { const HostPortPair host_port_; }; -TEST_F(HttpNetworkTransactionTest, +TEST_F(HttpNetworkTransactionSpdy3Test, UseIPConnectionPoolingWithHostCacheExpiration) { HttpStreamFactory::set_use_alternate_protocols(true); HttpStreamFactory::SetNextProtos(SpdyNextProtos()); @@ -9287,7 +9323,7 @@ TEST_F(HttpNetworkTransactionTest, HttpStreamFactory::set_use_alternate_protocols(false); } -TEST_F(HttpNetworkTransactionTest, ReadPipelineEvictionFallback) { +TEST_F(HttpNetworkTransactionSpdy3Test, ReadPipelineEvictionFallback) { MockRead data_reads1[] = { MockRead(SYNCHRONOUS, ERR_PIPELINE_EVICTION), }; @@ -9307,7 +9343,7 @@ TEST_F(HttpNetworkTransactionTest, ReadPipelineEvictionFallback) { EXPECT_EQ("hello world", out.response_data); } -TEST_F(HttpNetworkTransactionTest, SendPipelineEvictionFallback) { +TEST_F(HttpNetworkTransactionSpdy3Test, SendPipelineEvictionFallback) { MockWrite data_writes1[] = { MockWrite(SYNCHRONOUS, ERR_PIPELINE_EVICTION), }; diff --git a/net/http/http_proxy_client_socket_pool_spdy21_unittest.cc b/net/http/http_proxy_client_socket_pool_spdy21_unittest.cc new file mode 100644 index 0000000..1569627 --- /dev/null +++ b/net/http/http_proxy_client_socket_pool_spdy21_unittest.cc @@ -0,0 +1,541 @@ +// Copyright (c) 2012 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/http/http_proxy_client_socket_pool.h" + +#include "base/callback.h" +#include "base/compiler_specific.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "net/base/mock_host_resolver.h" +#include "net/base/net_errors.h" +#include "net/base/ssl_config_service_defaults.h" +#include "net/base/test_completion_callback.h" +#include "net/http/http_auth_handler_factory.h" +#include "net/http/http_network_session.h" +#include "net/http/http_proxy_client_socket.h" +#include "net/http/http_server_properties_impl.h" +#include "net/proxy/proxy_service.h" +#include "net/socket/client_socket_handle.h" +#include "net/socket/client_socket_pool_histograms.h" +#include "net/socket/socket_test_util.h" +#include "net/spdy/spdy_protocol.h" +#include "net/spdy/spdy_test_util_spdy2.h" +#include "testing/gtest/include/gtest/gtest.h" + +using namespace net::test_spdy2; + +namespace net { + +namespace { + +const int kMaxSockets = 32; +const int kMaxSocketsPerGroup = 6; +const char * const kAuthHeaders[] = { + "proxy-authorization", "Basic Zm9vOmJhcg==" +}; +const int kAuthHeadersSize = arraysize(kAuthHeaders) / 2; + +enum HttpProxyType { + HTTP, + HTTPS, + SPDY +}; + +typedef ::testing::TestWithParam<HttpProxyType> TestWithHttpParam; + +} // namespace + +class HttpProxyClientSocketPoolSpdy21Test : public TestWithHttpParam { + protected: + HttpProxyClientSocketPoolSpdy21Test() + : ssl_config_(), + ignored_transport_socket_params_(new TransportSocketParams( + HostPortPair("proxy", 80), LOWEST, false, false)), + ignored_ssl_socket_params_(new SSLSocketParams( + ignored_transport_socket_params_, NULL, NULL, + ProxyServer::SCHEME_DIRECT, HostPortPair("www.google.com", 443), + ssl_config_, 0, false, false)), + tcp_histograms_("MockTCP"), + transport_socket_pool_( + kMaxSockets, kMaxSocketsPerGroup, + &tcp_histograms_, + &socket_factory_), + ssl_histograms_("MockSSL"), + proxy_service_(ProxyService::CreateDirect()), + ssl_config_service_(new SSLConfigServiceDefaults), + ssl_socket_pool_(kMaxSockets, kMaxSocketsPerGroup, + &ssl_histograms_, + &host_resolver_, + &cert_verifier_, + NULL /* origin_bound_cert_store */, + NULL /* transport_security_state */, + NULL /* ssl_host_info_factory */, + "" /* ssl_session_cache_shard */, + &socket_factory_, + &transport_socket_pool_, + NULL, + NULL, + ssl_config_service_.get(), + BoundNetLog().net_log()), + http_auth_handler_factory_( + HttpAuthHandlerFactory::CreateDefault(&host_resolver_)), + session_(CreateNetworkSession()), + http_proxy_histograms_("HttpProxyUnitTest"), + ssl_data_(NULL), + data_(NULL), + pool_(kMaxSockets, kMaxSocketsPerGroup, + &http_proxy_histograms_, + NULL, + &transport_socket_pool_, + &ssl_socket_pool_, + NULL) { + } + + virtual ~HttpProxyClientSocketPoolSpdy21Test() { + } + + void AddAuthToCache() { + const string16 kFoo(ASCIIToUTF16("foo")); + const string16 kBar(ASCIIToUTF16("bar")); + GURL proxy_url(GetParam() == HTTP ? "http://proxy" : "https://proxy:80"); + session_->http_auth_cache()->Add(proxy_url, + "MyRealm1", + HttpAuth::AUTH_SCHEME_BASIC, + "Basic realm=MyRealm1", + AuthCredentials(kFoo, kBar), + "/"); + } + + scoped_refptr<TransportSocketParams> GetTcpParams() { + if (GetParam() != HTTP) + return scoped_refptr<TransportSocketParams>(); + return ignored_transport_socket_params_; + } + + scoped_refptr<SSLSocketParams> GetSslParams() { + if (GetParam() == HTTP) + return scoped_refptr<SSLSocketParams>(); + return ignored_ssl_socket_params_; + } + + // Returns the a correctly constructed HttpProxyParms + // for the HTTP or HTTPS proxy. + scoped_refptr<HttpProxySocketParams> GetParams(bool tunnel) { + return scoped_refptr<HttpProxySocketParams>( + new HttpProxySocketParams( + GetTcpParams(), + GetSslParams(), + GURL(tunnel ? "https://www.google.com/" : "http://www.google.com"), + "", + HostPortPair("www.google.com", tunnel ? 443 : 80), + session_->http_auth_cache(), + session_->http_auth_handler_factory(), + session_->spdy_session_pool(), + tunnel)); + } + + scoped_refptr<HttpProxySocketParams> GetTunnelParams() { + return GetParams(true); + } + + scoped_refptr<HttpProxySocketParams> GetNoTunnelParams() { + return GetParams(false); + } + + DeterministicMockClientSocketFactory& socket_factory() { + return socket_factory_; + } + + void Initialize(MockRead* reads, size_t reads_count, + MockWrite* writes, size_t writes_count, + MockRead* spdy_reads, size_t spdy_reads_count, + MockWrite* spdy_writes, size_t spdy_writes_count) { + if (GetParam() == SPDY) + data_ = new DeterministicSocketData(spdy_reads, spdy_reads_count, + spdy_writes, spdy_writes_count); + else + data_ = new DeterministicSocketData(reads, reads_count, writes, + writes_count); + + data_->set_connect_data(MockConnect(SYNCHRONOUS, OK)); + data_->StopAfter(2); // Request / Response + + socket_factory_.AddSocketDataProvider(data_.get()); + + if (GetParam() != HTTP) { + ssl_data_.reset(new SSLSocketDataProvider(SYNCHRONOUS, OK)); + if (GetParam() == SPDY) { + InitializeSpdySsl(); + } + socket_factory_.AddSSLSocketDataProvider(ssl_data_.get()); + } + } + + void InitializeSpdySsl() { + spdy::SpdyFramer::set_enable_compression_default(false); + ssl_data_->SetNextProto(SSLClientSocket::kProtoSPDY21); + } + + HttpNetworkSession* CreateNetworkSession() { + HttpNetworkSession::Params params; + params.host_resolver = &host_resolver_; + params.cert_verifier = &cert_verifier_; + params.proxy_service = proxy_service_.get(); + params.client_socket_factory = &socket_factory_; + params.ssl_config_service = ssl_config_service_; + params.http_auth_handler_factory = http_auth_handler_factory_.get(); + params.http_server_properties = &http_server_properties_; + return new HttpNetworkSession(params); + } + + private: + SSLConfig ssl_config_; + + scoped_refptr<TransportSocketParams> ignored_transport_socket_params_; + scoped_refptr<SSLSocketParams> ignored_ssl_socket_params_; + ClientSocketPoolHistograms tcp_histograms_; + DeterministicMockClientSocketFactory socket_factory_; + MockTransportClientSocketPool transport_socket_pool_; + ClientSocketPoolHistograms ssl_histograms_; + MockHostResolver host_resolver_; + CertVerifier cert_verifier_; + const scoped_ptr<ProxyService> proxy_service_; + const scoped_refptr<SSLConfigService> ssl_config_service_; + SSLClientSocketPool ssl_socket_pool_; + + const scoped_ptr<HttpAuthHandlerFactory> http_auth_handler_factory_; + HttpServerPropertiesImpl http_server_properties_; + const scoped_refptr<HttpNetworkSession> session_; + ClientSocketPoolHistograms http_proxy_histograms_; + + protected: + scoped_ptr<SSLSocketDataProvider> ssl_data_; + scoped_refptr<DeterministicSocketData> data_; + HttpProxyClientSocketPool pool_; + ClientSocketHandle handle_; + TestCompletionCallback callback_; +}; + +//----------------------------------------------------------------------------- +// All tests are run with three different proxy types: HTTP, HTTPS (non-SPDY) +// and SPDY. +INSTANTIATE_TEST_CASE_P(HttpProxyClientSocketPoolSpdy21Tests, + HttpProxyClientSocketPoolSpdy21Test, + ::testing::Values(HTTP, HTTPS, SPDY)); + +TEST_P(HttpProxyClientSocketPoolSpdy21Test, NoTunnel) { + Initialize(NULL, 0, NULL, 0, NULL, 0, NULL, 0); + + int rv = handle_.Init("a", GetNoTunnelParams(), LOW, CompletionCallback(), + &pool_, BoundNetLog()); + EXPECT_EQ(OK, rv); + EXPECT_TRUE(handle_.is_initialized()); + ASSERT_TRUE(handle_.socket()); + HttpProxyClientSocket* tunnel_socket = + static_cast<HttpProxyClientSocket*>(handle_.socket()); + EXPECT_TRUE(tunnel_socket->IsConnected()); +} + +TEST_P(HttpProxyClientSocketPoolSpdy21Test, NeedAuth) { + MockWrite writes[] = { + MockWrite(ASYNC, 0, "CONNECT www.google.com:443 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n\r\n"), + }; + MockRead reads[] = { + // No credentials. + MockRead(ASYNC, 1, "HTTP/1.1 407 Proxy Authentication Required\r\n"), + MockRead(ASYNC, 2, "Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n"), + MockRead(ASYNC, 3, "Content-Length: 10\r\n\r\n"), + MockRead(ASYNC, 4, "0123456789"), + }; + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyConnect(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> rst(ConstructSpdyRstStream(1, spdy::CANCEL)); + MockWrite spdy_writes[] = { + CreateMockWrite(*req, 0, ASYNC), + CreateMockWrite(*rst, 2, ASYNC), + }; + static const char* const kAuthChallenge[] = { + "status", "407 Proxy Authentication Required", + "version", "HTTP/1.1", + "proxy-authenticate", "Basic realm=\"MyRealm1\"", + }; + scoped_ptr<spdy::SpdyFrame> resp( + + ConstructSpdyControlFrame(NULL, + 0, + false, + 1, + LOWEST, + spdy::SYN_REPLY, + spdy::CONTROL_FLAG_NONE, + kAuthChallenge, + arraysize(kAuthChallenge))); + MockRead spdy_reads[] = { + CreateMockWrite(*resp, 1, ASYNC), + MockRead(ASYNC, 0, 3) + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes), + spdy_reads, arraysize(spdy_reads), spdy_writes, + arraysize(spdy_writes)); + + data_->StopAfter(4); + int rv = handle_.Init("a", GetTunnelParams(), LOW, callback_.callback(), + &pool_, BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_FALSE(handle_.is_initialized()); + EXPECT_FALSE(handle_.socket()); + + data_->RunFor(GetParam() == SPDY ? 2 : 4); + rv = callback_.WaitForResult(); + EXPECT_EQ(ERR_PROXY_AUTH_REQUESTED, rv); + EXPECT_TRUE(handle_.is_initialized()); + ASSERT_TRUE(handle_.socket()); + ProxyClientSocket* tunnel_socket = + static_cast<ProxyClientSocket*>(handle_.socket()); + if (GetParam() == SPDY) { + EXPECT_TRUE(tunnel_socket->IsConnected()); + EXPECT_TRUE(tunnel_socket->IsUsingSpdy()); + } else { + EXPECT_FALSE(tunnel_socket->IsConnected()); + EXPECT_FALSE(tunnel_socket->IsUsingSpdy()); + EXPECT_FALSE(tunnel_socket->IsUsingSpdy()); + } +} + +TEST_P(HttpProxyClientSocketPoolSpdy21Test, HaveAuth) { + // It's pretty much impossible to make the SPDY case behave synchronously + // so we skip this test for SPDY + if (GetParam() == SPDY) + return; + MockWrite writes[] = { + MockWrite(SYNCHRONOUS, 0, + "CONNECT www.google.com:443 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n" + "Proxy-Authorization: Basic Zm9vOmJhcg==\r\n\r\n"), + }; + MockRead reads[] = { + MockRead(SYNCHRONOUS, 1, "HTTP/1.1 200 Connection Established\r\n\r\n"), + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes), NULL, 0, + NULL, 0); + AddAuthToCache(); + + int rv = handle_.Init("a", GetTunnelParams(), LOW, callback_.callback(), + &pool_, BoundNetLog()); + EXPECT_EQ(OK, rv); + EXPECT_TRUE(handle_.is_initialized()); + ASSERT_TRUE(handle_.socket()); + HttpProxyClientSocket* tunnel_socket = + static_cast<HttpProxyClientSocket*>(handle_.socket()); + EXPECT_TRUE(tunnel_socket->IsConnected()); +} + +TEST_P(HttpProxyClientSocketPoolSpdy21Test, AsyncHaveAuth) { + MockWrite writes[] = { + MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n" + "Proxy-Authorization: Basic Zm9vOmJhcg==\r\n\r\n"), + }; + MockRead reads[] = { + MockRead(SYNCHRONOUS, "HTTP/1.1 200 Connection Established\r\n\r\n"), + }; + + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyConnect(kAuthHeaders, + kAuthHeadersSize, 1)); + MockWrite spdy_writes[] = { + CreateMockWrite(*req, 0, ASYNC) + }; + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + MockRead spdy_reads[] = { + CreateMockRead(*resp, 1, ASYNC), + MockRead(ASYNC, 0, 2) + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes), + spdy_reads, arraysize(spdy_reads), spdy_writes, + arraysize(spdy_writes)); + AddAuthToCache(); + + int rv = handle_.Init("a", GetTunnelParams(), LOW, callback_.callback(), + &pool_, BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_FALSE(handle_.is_initialized()); + EXPECT_FALSE(handle_.socket()); + + data_->RunFor(2); + EXPECT_EQ(OK, callback_.WaitForResult()); + EXPECT_TRUE(handle_.is_initialized()); + ASSERT_TRUE(handle_.socket()); + HttpProxyClientSocket* tunnel_socket = + static_cast<HttpProxyClientSocket*>(handle_.socket()); + EXPECT_TRUE(tunnel_socket->IsConnected()); +} + +TEST_P(HttpProxyClientSocketPoolSpdy21Test, TCPError) { + if (GetParam() == SPDY) return; + data_ = new DeterministicSocketData(NULL, 0, NULL, 0); + data_->set_connect_data(MockConnect(ASYNC, ERR_CONNECTION_CLOSED)); + + socket_factory().AddSocketDataProvider(data_.get()); + + int rv = handle_.Init("a", GetTunnelParams(), LOW, callback_.callback(), + &pool_, BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_FALSE(handle_.is_initialized()); + EXPECT_FALSE(handle_.socket()); + + EXPECT_EQ(ERR_PROXY_CONNECTION_FAILED, callback_.WaitForResult()); + + EXPECT_FALSE(handle_.is_initialized()); + EXPECT_FALSE(handle_.socket()); +} + +TEST_P(HttpProxyClientSocketPoolSpdy21Test, SSLError) { + if (GetParam() == HTTP) return; + data_ = new DeterministicSocketData(NULL, 0, NULL, 0); + data_->set_connect_data(MockConnect(ASYNC, OK)); + socket_factory().AddSocketDataProvider(data_.get()); + + ssl_data_.reset(new SSLSocketDataProvider(ASYNC, + ERR_CERT_AUTHORITY_INVALID)); + if (GetParam() == SPDY) { + InitializeSpdySsl(); + } + socket_factory().AddSSLSocketDataProvider(ssl_data_.get()); + + int rv = handle_.Init("a", GetTunnelParams(), LOW, callback_.callback(), + &pool_, BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_FALSE(handle_.is_initialized()); + EXPECT_FALSE(handle_.socket()); + + EXPECT_EQ(ERR_PROXY_CERTIFICATE_INVALID, callback_.WaitForResult()); + + EXPECT_FALSE(handle_.is_initialized()); + EXPECT_FALSE(handle_.socket()); +} + +TEST_P(HttpProxyClientSocketPoolSpdy21Test, SslClientAuth) { + if (GetParam() == HTTP) return; + data_ = new DeterministicSocketData(NULL, 0, NULL, 0); + data_->set_connect_data(MockConnect(ASYNC, OK)); + socket_factory().AddSocketDataProvider(data_.get()); + + ssl_data_.reset(new SSLSocketDataProvider(ASYNC, + ERR_SSL_CLIENT_AUTH_CERT_NEEDED)); + if (GetParam() == SPDY) { + InitializeSpdySsl(); + } + socket_factory().AddSSLSocketDataProvider(ssl_data_.get()); + + int rv = handle_.Init("a", GetTunnelParams(), LOW, callback_.callback(), + &pool_, BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_FALSE(handle_.is_initialized()); + EXPECT_FALSE(handle_.socket()); + + EXPECT_EQ(ERR_SSL_CLIENT_AUTH_CERT_NEEDED, callback_.WaitForResult()); + + EXPECT_FALSE(handle_.is_initialized()); + EXPECT_FALSE(handle_.socket()); +} + +TEST_P(HttpProxyClientSocketPoolSpdy21Test, TunnelUnexpectedClose) { + MockWrite writes[] = { + MockWrite(ASYNC, 0, + "CONNECT www.google.com:443 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n" + "Proxy-Authorization: Basic Zm9vOmJhcg==\r\n\r\n"), + }; + MockRead reads[] = { + MockRead(ASYNC, 1, "HTTP/1.1 200 Conn"), + MockRead(ASYNC, ERR_CONNECTION_CLOSED, 2), + }; + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyConnect(kAuthHeaders, + kAuthHeadersSize, 1)); + MockWrite spdy_writes[] = { + CreateMockWrite(*req, 0, ASYNC) + }; + MockRead spdy_reads[] = { + MockRead(ASYNC, ERR_CONNECTION_CLOSED, 1), + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes), + spdy_reads, arraysize(spdy_reads), spdy_writes, + arraysize(spdy_writes)); + AddAuthToCache(); + + int rv = handle_.Init("a", GetTunnelParams(), LOW, callback_.callback(), + &pool_, BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_FALSE(handle_.is_initialized()); + EXPECT_FALSE(handle_.socket()); + + data_->RunFor(3); + EXPECT_EQ(ERR_CONNECTION_CLOSED, callback_.WaitForResult()); + EXPECT_FALSE(handle_.is_initialized()); + EXPECT_FALSE(handle_.socket()); +} + +TEST_P(HttpProxyClientSocketPoolSpdy21Test, TunnelSetupError) { + MockWrite writes[] = { + MockWrite(ASYNC, 0, + "CONNECT www.google.com:443 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n" + "Proxy-Authorization: Basic Zm9vOmJhcg==\r\n\r\n"), + }; + MockRead reads[] = { + MockRead(ASYNC, 1, "HTTP/1.1 304 Not Modified\r\n\r\n"), + }; + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyConnect(kAuthHeaders, + kAuthHeadersSize, 1)); + scoped_ptr<spdy::SpdyFrame> rst(ConstructSpdyRstStream(1, spdy::CANCEL)); + MockWrite spdy_writes[] = { + CreateMockWrite(*req, 0, ASYNC), + CreateMockWrite(*rst, 2, ASYNC), + }; + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdySynReplyError(1)); + MockRead spdy_reads[] = { + CreateMockRead(*resp, 1, ASYNC), + MockRead(ASYNC, 0, 3), + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes), + spdy_reads, arraysize(spdy_reads), spdy_writes, + arraysize(spdy_writes)); + AddAuthToCache(); + + int rv = handle_.Init("a", GetTunnelParams(), LOW, callback_.callback(), + &pool_, BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_FALSE(handle_.is_initialized()); + EXPECT_FALSE(handle_.socket()); + + data_->RunFor(2); + + rv = callback_.WaitForResult(); + if (GetParam() == HTTP) { + // HTTP Proxy CONNECT responses are not trustworthy + EXPECT_EQ(ERR_TUNNEL_CONNECTION_FAILED, rv); + EXPECT_FALSE(handle_.is_initialized()); + EXPECT_FALSE(handle_.socket()); + } else { + // HTTPS or SPDY Proxy CONNECT responses are trustworthy + EXPECT_EQ(ERR_HTTPS_PROXY_TUNNEL_RESPONSE, rv); + EXPECT_TRUE(handle_.is_initialized()); + EXPECT_TRUE(handle_.socket()); + } +} + +// It would be nice to also test the timeouts in HttpProxyClientSocketPool. + +} // namespace net diff --git a/net/http/http_proxy_client_socket_pool_spdy2_unittest.cc b/net/http/http_proxy_client_socket_pool_spdy2_unittest.cc new file mode 100644 index 0000000..b1bbe6d --- /dev/null +++ b/net/http/http_proxy_client_socket_pool_spdy2_unittest.cc @@ -0,0 +1,541 @@ +// Copyright (c) 2012 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/http/http_proxy_client_socket_pool.h" + +#include "base/callback.h" +#include "base/compiler_specific.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "net/base/mock_host_resolver.h" +#include "net/base/net_errors.h" +#include "net/base/ssl_config_service_defaults.h" +#include "net/base/test_completion_callback.h" +#include "net/http/http_auth_handler_factory.h" +#include "net/http/http_network_session.h" +#include "net/http/http_proxy_client_socket.h" +#include "net/http/http_server_properties_impl.h" +#include "net/proxy/proxy_service.h" +#include "net/socket/client_socket_handle.h" +#include "net/socket/client_socket_pool_histograms.h" +#include "net/socket/socket_test_util.h" +#include "net/spdy/spdy_protocol.h" +#include "net/spdy/spdy_test_util_spdy2.h" +#include "testing/gtest/include/gtest/gtest.h" + +using namespace net::test_spdy2; + +namespace net { + +namespace { + +const int kMaxSockets = 32; +const int kMaxSocketsPerGroup = 6; +const char * const kAuthHeaders[] = { + "proxy-authorization", "Basic Zm9vOmJhcg==" +}; +const int kAuthHeadersSize = arraysize(kAuthHeaders) / 2; + +enum HttpProxyType { + HTTP, + HTTPS, + SPDY +}; + +typedef ::testing::TestWithParam<HttpProxyType> TestWithHttpParam; + +} // namespace + +class HttpProxyClientSocketPoolSpdy2Test : public TestWithHttpParam { + protected: + HttpProxyClientSocketPoolSpdy2Test() + : ssl_config_(), + ignored_transport_socket_params_(new TransportSocketParams( + HostPortPair("proxy", 80), LOWEST, false, false)), + ignored_ssl_socket_params_(new SSLSocketParams( + ignored_transport_socket_params_, NULL, NULL, + ProxyServer::SCHEME_DIRECT, HostPortPair("www.google.com", 443), + ssl_config_, 0, false, false)), + tcp_histograms_("MockTCP"), + transport_socket_pool_( + kMaxSockets, kMaxSocketsPerGroup, + &tcp_histograms_, + &socket_factory_), + ssl_histograms_("MockSSL"), + proxy_service_(ProxyService::CreateDirect()), + ssl_config_service_(new SSLConfigServiceDefaults), + ssl_socket_pool_(kMaxSockets, kMaxSocketsPerGroup, + &ssl_histograms_, + &host_resolver_, + &cert_verifier_, + NULL /* origin_bound_cert_store */, + NULL /* transport_security_state */, + NULL /* ssl_host_info_factory */, + "" /* ssl_session_cache_shard */, + &socket_factory_, + &transport_socket_pool_, + NULL, + NULL, + ssl_config_service_.get(), + BoundNetLog().net_log()), + http_auth_handler_factory_( + HttpAuthHandlerFactory::CreateDefault(&host_resolver_)), + session_(CreateNetworkSession()), + http_proxy_histograms_("HttpProxyUnitTest"), + ssl_data_(NULL), + data_(NULL), + pool_(kMaxSockets, kMaxSocketsPerGroup, + &http_proxy_histograms_, + NULL, + &transport_socket_pool_, + &ssl_socket_pool_, + NULL) { + } + + virtual ~HttpProxyClientSocketPoolSpdy2Test() { + } + + void AddAuthToCache() { + const string16 kFoo(ASCIIToUTF16("foo")); + const string16 kBar(ASCIIToUTF16("bar")); + GURL proxy_url(GetParam() == HTTP ? "http://proxy" : "https://proxy:80"); + session_->http_auth_cache()->Add(proxy_url, + "MyRealm1", + HttpAuth::AUTH_SCHEME_BASIC, + "Basic realm=MyRealm1", + AuthCredentials(kFoo, kBar), + "/"); + } + + scoped_refptr<TransportSocketParams> GetTcpParams() { + if (GetParam() != HTTP) + return scoped_refptr<TransportSocketParams>(); + return ignored_transport_socket_params_; + } + + scoped_refptr<SSLSocketParams> GetSslParams() { + if (GetParam() == HTTP) + return scoped_refptr<SSLSocketParams>(); + return ignored_ssl_socket_params_; + } + + // Returns the a correctly constructed HttpProxyParms + // for the HTTP or HTTPS proxy. + scoped_refptr<HttpProxySocketParams> GetParams(bool tunnel) { + return scoped_refptr<HttpProxySocketParams>( + new HttpProxySocketParams( + GetTcpParams(), + GetSslParams(), + GURL(tunnel ? "https://www.google.com/" : "http://www.google.com"), + "", + HostPortPair("www.google.com", tunnel ? 443 : 80), + session_->http_auth_cache(), + session_->http_auth_handler_factory(), + session_->spdy_session_pool(), + tunnel)); + } + + scoped_refptr<HttpProxySocketParams> GetTunnelParams() { + return GetParams(true); + } + + scoped_refptr<HttpProxySocketParams> GetNoTunnelParams() { + return GetParams(false); + } + + DeterministicMockClientSocketFactory& socket_factory() { + return socket_factory_; + } + + void Initialize(MockRead* reads, size_t reads_count, + MockWrite* writes, size_t writes_count, + MockRead* spdy_reads, size_t spdy_reads_count, + MockWrite* spdy_writes, size_t spdy_writes_count) { + if (GetParam() == SPDY) + data_ = new DeterministicSocketData(spdy_reads, spdy_reads_count, + spdy_writes, spdy_writes_count); + else + data_ = new DeterministicSocketData(reads, reads_count, writes, + writes_count); + + data_->set_connect_data(MockConnect(SYNCHRONOUS, OK)); + data_->StopAfter(2); // Request / Response + + socket_factory_.AddSocketDataProvider(data_.get()); + + if (GetParam() != HTTP) { + ssl_data_.reset(new SSLSocketDataProvider(SYNCHRONOUS, OK)); + if (GetParam() == SPDY) { + InitializeSpdySsl(); + } + socket_factory_.AddSSLSocketDataProvider(ssl_data_.get()); + } + } + + void InitializeSpdySsl() { + spdy::SpdyFramer::set_enable_compression_default(false); + ssl_data_->SetNextProto(SSLClientSocket::kProtoSPDY2); + } + + HttpNetworkSession* CreateNetworkSession() { + HttpNetworkSession::Params params; + params.host_resolver = &host_resolver_; + params.cert_verifier = &cert_verifier_; + params.proxy_service = proxy_service_.get(); + params.client_socket_factory = &socket_factory_; + params.ssl_config_service = ssl_config_service_; + params.http_auth_handler_factory = http_auth_handler_factory_.get(); + params.http_server_properties = &http_server_properties_; + return new HttpNetworkSession(params); + } + + private: + SSLConfig ssl_config_; + + scoped_refptr<TransportSocketParams> ignored_transport_socket_params_; + scoped_refptr<SSLSocketParams> ignored_ssl_socket_params_; + ClientSocketPoolHistograms tcp_histograms_; + DeterministicMockClientSocketFactory socket_factory_; + MockTransportClientSocketPool transport_socket_pool_; + ClientSocketPoolHistograms ssl_histograms_; + MockHostResolver host_resolver_; + CertVerifier cert_verifier_; + const scoped_ptr<ProxyService> proxy_service_; + const scoped_refptr<SSLConfigService> ssl_config_service_; + SSLClientSocketPool ssl_socket_pool_; + + const scoped_ptr<HttpAuthHandlerFactory> http_auth_handler_factory_; + HttpServerPropertiesImpl http_server_properties_; + const scoped_refptr<HttpNetworkSession> session_; + ClientSocketPoolHistograms http_proxy_histograms_; + + protected: + scoped_ptr<SSLSocketDataProvider> ssl_data_; + scoped_refptr<DeterministicSocketData> data_; + HttpProxyClientSocketPool pool_; + ClientSocketHandle handle_; + TestCompletionCallback callback_; +}; + +//----------------------------------------------------------------------------- +// All tests are run with three different proxy types: HTTP, HTTPS (non-SPDY) +// and SPDY. +INSTANTIATE_TEST_CASE_P(HttpProxyClientSocketPoolSpdy2Tests, + HttpProxyClientSocketPoolSpdy2Test, + ::testing::Values(HTTP, HTTPS, SPDY)); + +TEST_P(HttpProxyClientSocketPoolSpdy2Test, NoTunnel) { + Initialize(NULL, 0, NULL, 0, NULL, 0, NULL, 0); + + int rv = handle_.Init("a", GetNoTunnelParams(), LOW, CompletionCallback(), + &pool_, BoundNetLog()); + EXPECT_EQ(OK, rv); + EXPECT_TRUE(handle_.is_initialized()); + ASSERT_TRUE(handle_.socket()); + HttpProxyClientSocket* tunnel_socket = + static_cast<HttpProxyClientSocket*>(handle_.socket()); + EXPECT_TRUE(tunnel_socket->IsConnected()); +} + +TEST_P(HttpProxyClientSocketPoolSpdy2Test, NeedAuth) { + MockWrite writes[] = { + MockWrite(ASYNC, 0, "CONNECT www.google.com:443 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n\r\n"), + }; + MockRead reads[] = { + // No credentials. + MockRead(ASYNC, 1, "HTTP/1.1 407 Proxy Authentication Required\r\n"), + MockRead(ASYNC, 2, "Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n"), + MockRead(ASYNC, 3, "Content-Length: 10\r\n\r\n"), + MockRead(ASYNC, 4, "0123456789"), + }; + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyConnect(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> rst(ConstructSpdyRstStream(1, spdy::CANCEL)); + MockWrite spdy_writes[] = { + CreateMockWrite(*req, 0, ASYNC), + CreateMockWrite(*rst, 2, ASYNC), + }; + static const char* const kAuthChallenge[] = { + "status", "407 Proxy Authentication Required", + "version", "HTTP/1.1", + "proxy-authenticate", "Basic realm=\"MyRealm1\"", + }; + scoped_ptr<spdy::SpdyFrame> resp( + + ConstructSpdyControlFrame(NULL, + 0, + false, + 1, + LOWEST, + spdy::SYN_REPLY, + spdy::CONTROL_FLAG_NONE, + kAuthChallenge, + arraysize(kAuthChallenge))); + MockRead spdy_reads[] = { + CreateMockWrite(*resp, 1, ASYNC), + MockRead(ASYNC, 0, 3) + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes), + spdy_reads, arraysize(spdy_reads), spdy_writes, + arraysize(spdy_writes)); + + data_->StopAfter(4); + int rv = handle_.Init("a", GetTunnelParams(), LOW, callback_.callback(), + &pool_, BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_FALSE(handle_.is_initialized()); + EXPECT_FALSE(handle_.socket()); + + data_->RunFor(GetParam() == SPDY ? 2 : 4); + rv = callback_.WaitForResult(); + EXPECT_EQ(ERR_PROXY_AUTH_REQUESTED, rv); + EXPECT_TRUE(handle_.is_initialized()); + ASSERT_TRUE(handle_.socket()); + ProxyClientSocket* tunnel_socket = + static_cast<ProxyClientSocket*>(handle_.socket()); + if (GetParam() == SPDY) { + EXPECT_TRUE(tunnel_socket->IsConnected()); + EXPECT_TRUE(tunnel_socket->IsUsingSpdy()); + } else { + EXPECT_FALSE(tunnel_socket->IsConnected()); + EXPECT_FALSE(tunnel_socket->IsUsingSpdy()); + EXPECT_FALSE(tunnel_socket->IsUsingSpdy()); + } +} + +TEST_P(HttpProxyClientSocketPoolSpdy2Test, HaveAuth) { + // It's pretty much impossible to make the SPDY case behave synchronously + // so we skip this test for SPDY + if (GetParam() == SPDY) + return; + MockWrite writes[] = { + MockWrite(SYNCHRONOUS, 0, + "CONNECT www.google.com:443 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n" + "Proxy-Authorization: Basic Zm9vOmJhcg==\r\n\r\n"), + }; + MockRead reads[] = { + MockRead(SYNCHRONOUS, 1, "HTTP/1.1 200 Connection Established\r\n\r\n"), + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes), NULL, 0, + NULL, 0); + AddAuthToCache(); + + int rv = handle_.Init("a", GetTunnelParams(), LOW, callback_.callback(), + &pool_, BoundNetLog()); + EXPECT_EQ(OK, rv); + EXPECT_TRUE(handle_.is_initialized()); + ASSERT_TRUE(handle_.socket()); + HttpProxyClientSocket* tunnel_socket = + static_cast<HttpProxyClientSocket*>(handle_.socket()); + EXPECT_TRUE(tunnel_socket->IsConnected()); +} + +TEST_P(HttpProxyClientSocketPoolSpdy2Test, AsyncHaveAuth) { + MockWrite writes[] = { + MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n" + "Proxy-Authorization: Basic Zm9vOmJhcg==\r\n\r\n"), + }; + MockRead reads[] = { + MockRead(SYNCHRONOUS, "HTTP/1.1 200 Connection Established\r\n\r\n"), + }; + + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyConnect(kAuthHeaders, + kAuthHeadersSize, 1)); + MockWrite spdy_writes[] = { + CreateMockWrite(*req, 0, ASYNC) + }; + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + MockRead spdy_reads[] = { + CreateMockRead(*resp, 1, ASYNC), + MockRead(ASYNC, 0, 2) + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes), + spdy_reads, arraysize(spdy_reads), spdy_writes, + arraysize(spdy_writes)); + AddAuthToCache(); + + int rv = handle_.Init("a", GetTunnelParams(), LOW, callback_.callback(), + &pool_, BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_FALSE(handle_.is_initialized()); + EXPECT_FALSE(handle_.socket()); + + data_->RunFor(2); + EXPECT_EQ(OK, callback_.WaitForResult()); + EXPECT_TRUE(handle_.is_initialized()); + ASSERT_TRUE(handle_.socket()); + HttpProxyClientSocket* tunnel_socket = + static_cast<HttpProxyClientSocket*>(handle_.socket()); + EXPECT_TRUE(tunnel_socket->IsConnected()); +} + +TEST_P(HttpProxyClientSocketPoolSpdy2Test, TCPError) { + if (GetParam() == SPDY) return; + data_ = new DeterministicSocketData(NULL, 0, NULL, 0); + data_->set_connect_data(MockConnect(ASYNC, ERR_CONNECTION_CLOSED)); + + socket_factory().AddSocketDataProvider(data_.get()); + + int rv = handle_.Init("a", GetTunnelParams(), LOW, callback_.callback(), + &pool_, BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_FALSE(handle_.is_initialized()); + EXPECT_FALSE(handle_.socket()); + + EXPECT_EQ(ERR_PROXY_CONNECTION_FAILED, callback_.WaitForResult()); + + EXPECT_FALSE(handle_.is_initialized()); + EXPECT_FALSE(handle_.socket()); +} + +TEST_P(HttpProxyClientSocketPoolSpdy2Test, SSLError) { + if (GetParam() == HTTP) return; + data_ = new DeterministicSocketData(NULL, 0, NULL, 0); + data_->set_connect_data(MockConnect(ASYNC, OK)); + socket_factory().AddSocketDataProvider(data_.get()); + + ssl_data_.reset(new SSLSocketDataProvider(ASYNC, + ERR_CERT_AUTHORITY_INVALID)); + if (GetParam() == SPDY) { + InitializeSpdySsl(); + } + socket_factory().AddSSLSocketDataProvider(ssl_data_.get()); + + int rv = handle_.Init("a", GetTunnelParams(), LOW, callback_.callback(), + &pool_, BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_FALSE(handle_.is_initialized()); + EXPECT_FALSE(handle_.socket()); + + EXPECT_EQ(ERR_PROXY_CERTIFICATE_INVALID, callback_.WaitForResult()); + + EXPECT_FALSE(handle_.is_initialized()); + EXPECT_FALSE(handle_.socket()); +} + +TEST_P(HttpProxyClientSocketPoolSpdy2Test, SslClientAuth) { + if (GetParam() == HTTP) return; + data_ = new DeterministicSocketData(NULL, 0, NULL, 0); + data_->set_connect_data(MockConnect(ASYNC, OK)); + socket_factory().AddSocketDataProvider(data_.get()); + + ssl_data_.reset(new SSLSocketDataProvider(ASYNC, + ERR_SSL_CLIENT_AUTH_CERT_NEEDED)); + if (GetParam() == SPDY) { + InitializeSpdySsl(); + } + socket_factory().AddSSLSocketDataProvider(ssl_data_.get()); + + int rv = handle_.Init("a", GetTunnelParams(), LOW, callback_.callback(), + &pool_, BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_FALSE(handle_.is_initialized()); + EXPECT_FALSE(handle_.socket()); + + EXPECT_EQ(ERR_SSL_CLIENT_AUTH_CERT_NEEDED, callback_.WaitForResult()); + + EXPECT_FALSE(handle_.is_initialized()); + EXPECT_FALSE(handle_.socket()); +} + +TEST_P(HttpProxyClientSocketPoolSpdy2Test, TunnelUnexpectedClose) { + MockWrite writes[] = { + MockWrite(ASYNC, 0, + "CONNECT www.google.com:443 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n" + "Proxy-Authorization: Basic Zm9vOmJhcg==\r\n\r\n"), + }; + MockRead reads[] = { + MockRead(ASYNC, 1, "HTTP/1.1 200 Conn"), + MockRead(ASYNC, ERR_CONNECTION_CLOSED, 2), + }; + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyConnect(kAuthHeaders, + kAuthHeadersSize, 1)); + MockWrite spdy_writes[] = { + CreateMockWrite(*req, 0, ASYNC) + }; + MockRead spdy_reads[] = { + MockRead(ASYNC, ERR_CONNECTION_CLOSED, 1), + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes), + spdy_reads, arraysize(spdy_reads), spdy_writes, + arraysize(spdy_writes)); + AddAuthToCache(); + + int rv = handle_.Init("a", GetTunnelParams(), LOW, callback_.callback(), + &pool_, BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_FALSE(handle_.is_initialized()); + EXPECT_FALSE(handle_.socket()); + + data_->RunFor(3); + EXPECT_EQ(ERR_CONNECTION_CLOSED, callback_.WaitForResult()); + EXPECT_FALSE(handle_.is_initialized()); + EXPECT_FALSE(handle_.socket()); +} + +TEST_P(HttpProxyClientSocketPoolSpdy2Test, TunnelSetupError) { + MockWrite writes[] = { + MockWrite(ASYNC, 0, + "CONNECT www.google.com:443 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n" + "Proxy-Authorization: Basic Zm9vOmJhcg==\r\n\r\n"), + }; + MockRead reads[] = { + MockRead(ASYNC, 1, "HTTP/1.1 304 Not Modified\r\n\r\n"), + }; + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyConnect(kAuthHeaders, + kAuthHeadersSize, 1)); + scoped_ptr<spdy::SpdyFrame> rst(ConstructSpdyRstStream(1, spdy::CANCEL)); + MockWrite spdy_writes[] = { + CreateMockWrite(*req, 0, ASYNC), + CreateMockWrite(*rst, 2, ASYNC), + }; + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdySynReplyError(1)); + MockRead spdy_reads[] = { + CreateMockRead(*resp, 1, ASYNC), + MockRead(ASYNC, 0, 3), + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes), + spdy_reads, arraysize(spdy_reads), spdy_writes, + arraysize(spdy_writes)); + AddAuthToCache(); + + int rv = handle_.Init("a", GetTunnelParams(), LOW, callback_.callback(), + &pool_, BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_FALSE(handle_.is_initialized()); + EXPECT_FALSE(handle_.socket()); + + data_->RunFor(2); + + rv = callback_.WaitForResult(); + if (GetParam() == HTTP) { + // HTTP Proxy CONNECT responses are not trustworthy + EXPECT_EQ(ERR_TUNNEL_CONNECTION_FAILED, rv); + EXPECT_FALSE(handle_.is_initialized()); + EXPECT_FALSE(handle_.socket()); + } else { + // HTTPS or SPDY Proxy CONNECT responses are trustworthy + EXPECT_EQ(ERR_HTTPS_PROXY_TUNNEL_RESPONSE, rv); + EXPECT_TRUE(handle_.is_initialized()); + EXPECT_TRUE(handle_.socket()); + } +} + +// It would be nice to also test the timeouts in HttpProxyClientSocketPool. + +} // namespace net diff --git a/net/http/http_proxy_client_socket_pool_unittest.cc b/net/http/http_proxy_client_socket_pool_spdy3_unittest.cc index 4797391..6aa2779 100644 --- a/net/http/http_proxy_client_socket_pool_unittest.cc +++ b/net/http/http_proxy_client_socket_pool_spdy3_unittest.cc @@ -21,9 +21,11 @@ #include "net/socket/client_socket_pool_histograms.h" #include "net/socket/socket_test_util.h" #include "net/spdy/spdy_protocol.h" -#include "net/spdy/spdy_test_util.h" +#include "net/spdy/spdy_test_util_spdy3.h" #include "testing/gtest/include/gtest/gtest.h" +using namespace net::test_spdy3; + namespace net { namespace { @@ -45,9 +47,9 @@ typedef ::testing::TestWithParam<HttpProxyType> TestWithHttpParam; } // namespace -class HttpProxyClientSocketPoolTest : public TestWithHttpParam { +class HttpProxyClientSocketPoolSpdy3Test : public TestWithHttpParam { protected: - HttpProxyClientSocketPoolTest() + HttpProxyClientSocketPoolSpdy3Test() : ssl_config_(), ignored_transport_socket_params_(new TransportSocketParams( HostPortPair("proxy", 80), LOWEST, false, false)), @@ -91,7 +93,7 @@ class HttpProxyClientSocketPoolTest : public TestWithHttpParam { NULL) { } - virtual ~HttpProxyClientSocketPoolTest() { + virtual ~HttpProxyClientSocketPoolSpdy3Test() { } void AddAuthToCache() { @@ -219,11 +221,11 @@ class HttpProxyClientSocketPoolTest : public TestWithHttpParam { //----------------------------------------------------------------------------- // All tests are run with three different proxy types: HTTP, HTTPS (non-SPDY) // and SPDY. -INSTANTIATE_TEST_CASE_P(HttpProxyClientSocketPoolTests, - HttpProxyClientSocketPoolTest, +INSTANTIATE_TEST_CASE_P(HttpProxyClientSocketPoolSpdy3Tests, + HttpProxyClientSocketPoolSpdy3Test, ::testing::Values(HTTP, HTTPS, SPDY)); -TEST_P(HttpProxyClientSocketPoolTest, NoTunnel) { +TEST_P(HttpProxyClientSocketPoolSpdy3Test, NoTunnel) { Initialize(NULL, 0, NULL, 0, NULL, 0, NULL, 0); int rv = handle_.Init("a", GetNoTunnelParams(), LOW, CompletionCallback(), @@ -236,7 +238,7 @@ TEST_P(HttpProxyClientSocketPoolTest, NoTunnel) { EXPECT_TRUE(tunnel_socket->IsConnected()); } -TEST_P(HttpProxyClientSocketPoolTest, NeedAuth) { +TEST_P(HttpProxyClientSocketPoolSpdy3Test, NeedAuth) { MockWrite writes[] = { MockWrite(ASYNC, 0, "CONNECT www.google.com:443 HTTP/1.1\r\n" "Host: www.google.com\r\n" @@ -304,7 +306,7 @@ TEST_P(HttpProxyClientSocketPoolTest, NeedAuth) { } } -TEST_P(HttpProxyClientSocketPoolTest, HaveAuth) { +TEST_P(HttpProxyClientSocketPoolSpdy3Test, HaveAuth) { // It's pretty much impossible to make the SPDY case behave synchronously // so we skip this test for SPDY if (GetParam() == SPDY) @@ -334,7 +336,7 @@ TEST_P(HttpProxyClientSocketPoolTest, HaveAuth) { EXPECT_TRUE(tunnel_socket->IsConnected()); } -TEST_P(HttpProxyClientSocketPoolTest, AsyncHaveAuth) { +TEST_P(HttpProxyClientSocketPoolSpdy3Test, AsyncHaveAuth) { MockWrite writes[] = { MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n" "Host: www.google.com\r\n" @@ -376,7 +378,7 @@ TEST_P(HttpProxyClientSocketPoolTest, AsyncHaveAuth) { EXPECT_TRUE(tunnel_socket->IsConnected()); } -TEST_P(HttpProxyClientSocketPoolTest, TCPError) { +TEST_P(HttpProxyClientSocketPoolSpdy3Test, TCPError) { if (GetParam() == SPDY) return; data_ = new DeterministicSocketData(NULL, 0, NULL, 0); data_->set_connect_data(MockConnect(ASYNC, ERR_CONNECTION_CLOSED)); @@ -395,7 +397,7 @@ TEST_P(HttpProxyClientSocketPoolTest, TCPError) { EXPECT_FALSE(handle_.socket()); } -TEST_P(HttpProxyClientSocketPoolTest, SSLError) { +TEST_P(HttpProxyClientSocketPoolSpdy3Test, SSLError) { if (GetParam() == HTTP) return; data_ = new DeterministicSocketData(NULL, 0, NULL, 0); data_->set_connect_data(MockConnect(ASYNC, OK)); @@ -420,7 +422,7 @@ TEST_P(HttpProxyClientSocketPoolTest, SSLError) { EXPECT_FALSE(handle_.socket()); } -TEST_P(HttpProxyClientSocketPoolTest, SslClientAuth) { +TEST_P(HttpProxyClientSocketPoolSpdy3Test, SslClientAuth) { if (GetParam() == HTTP) return; data_ = new DeterministicSocketData(NULL, 0, NULL, 0); data_->set_connect_data(MockConnect(ASYNC, OK)); @@ -445,7 +447,7 @@ TEST_P(HttpProxyClientSocketPoolTest, SslClientAuth) { EXPECT_FALSE(handle_.socket()); } -TEST_P(HttpProxyClientSocketPoolTest, TunnelUnexpectedClose) { +TEST_P(HttpProxyClientSocketPoolSpdy3Test, TunnelUnexpectedClose) { MockWrite writes[] = { MockWrite(ASYNC, 0, "CONNECT www.google.com:443 HTTP/1.1\r\n" @@ -483,7 +485,7 @@ TEST_P(HttpProxyClientSocketPoolTest, TunnelUnexpectedClose) { EXPECT_FALSE(handle_.socket()); } -TEST_P(HttpProxyClientSocketPoolTest, TunnelSetupError) { +TEST_P(HttpProxyClientSocketPoolSpdy3Test, TunnelSetupError) { MockWrite writes[] = { MockWrite(ASYNC, 0, "CONNECT www.google.com:443 HTTP/1.1\r\n" diff --git a/net/net.gyp b/net/net.gyp index 6d911b4..c719d12 100644 --- a/net/net.gyp +++ b/net/net.gyp @@ -1123,7 +1123,9 @@ 'http/http_content_disposition_unittest.cc', 'http/http_mac_signature_unittest.cc', 'http/http_network_layer_unittest.cc', - 'http/http_network_transaction_unittest.cc', + 'http/http_network_transaction_spdy3_unittest.cc', + 'http/http_network_transaction_spdy21_unittest.cc', + 'http/http_network_transaction_spdy2_unittest.cc', 'http/http_pipelined_connection_impl_unittest.cc', 'http/http_pipelined_host_forced_unittest.cc', 'http/http_pipelined_host_impl_unittest.cc', @@ -1131,7 +1133,9 @@ 'http/http_pipelined_host_test_util.cc', 'http/http_pipelined_host_test_util.h', 'http/http_pipelined_network_transaction_unittest.cc', - 'http/http_proxy_client_socket_pool_unittest.cc', + 'http/http_proxy_client_socket_pool_spdy2_unittest.cc', + 'http/http_proxy_client_socket_pool_spdy21_unittest.cc', + 'http/http_proxy_client_socket_pool_spdy3_unittest.cc', 'http/http_request_headers_unittest.cc', 'http/http_response_body_drainer_unittest.cc', 'http/http_response_headers_unittest.cc', @@ -1186,20 +1190,32 @@ 'socket/web_socket_server_socket_unittest.cc', 'socket_stream/socket_stream_metrics_unittest.cc', 'socket_stream/socket_stream_unittest.cc', - 'spdy/buffered_spdy_framer_unittest.cc', + 'spdy/buffered_spdy_framer_spdy3_unittest.cc', + 'spdy/buffered_spdy_framer_spdy2_unittest.cc', 'spdy/spdy_credential_state_unittest.cc', 'spdy/spdy_framer_test.cc', - 'spdy/spdy_http_stream_unittest.cc', - 'spdy/spdy_network_transaction_unittest.cc', + 'spdy/spdy_http_stream_spdy3_unittest.cc', + 'spdy/spdy_http_stream_spdy2_unittest.cc', + 'spdy/spdy_network_transaction_spdy3_unittest.cc', + 'spdy/spdy_network_transaction_spdy21_unittest.cc', + 'spdy/spdy_network_transaction_spdy2_unittest.cc', 'spdy/spdy_protocol_test.cc', - 'spdy/spdy_proxy_client_socket_unittest.cc', - 'spdy/spdy_session_unittest.cc', - 'spdy/spdy_stream_unittest.cc', - 'spdy/spdy_test_util.cc', - 'spdy/spdy_test_util.h', - 'spdy/spdy_websocket_stream_unittest.cc', - 'spdy/spdy_websocket_test_util.cc', - 'spdy/spdy_websocket_test_util.h', + 'spdy/spdy_proxy_client_socket_spdy3_unittest.cc', + 'spdy/spdy_proxy_client_socket_spdy2_unittest.cc', + 'spdy/spdy_session_spdy3_unittest.cc', + 'spdy/spdy_session_spdy2_unittest.cc', + 'spdy/spdy_stream_spdy3_unittest.cc', + 'spdy/spdy_stream_spdy2_unittest.cc', + 'spdy/spdy_test_util_spdy3.cc', + 'spdy/spdy_test_util_spdy3.h', + 'spdy/spdy_test_util_spdy2.cc', + 'spdy/spdy_test_util_spdy2.h', + 'spdy/spdy_websocket_stream_spdy2_unittest.cc', + 'spdy/spdy_websocket_stream_spdy3_unittest.cc', + 'spdy/spdy_websocket_test_util_spdy2.cc', + 'spdy/spdy_websocket_test_util_spdy2.h', + 'spdy/spdy_websocket_test_util_spdy3.cc', + 'spdy/spdy_websocket_test_util_spdy3.h', 'test/python_utils_unittest.cc', 'tools/dump_cache/url_to_filename_encoder.cc', 'tools/dump_cache/url_to_filename_encoder.h', @@ -1217,7 +1233,8 @@ 'url_request/url_request_unittest.cc', 'url_request/view_cache_helper_unittest.cc', 'websockets/websocket_handshake_handler_unittest.cc', - 'websockets/websocket_job_unittest.cc', + 'websockets/websocket_job_spdy2_unittest.cc', + 'websockets/websocket_job_spdy3_unittest.cc', 'websockets/websocket_net_log_params_unittest.cc', 'websockets/websocket_throttle_unittest.cc', ], diff --git a/net/socket/ssl_client_socket_pool_unittest.cc b/net/socket/ssl_client_socket_pool_unittest.cc index df893f9..d77e157 100644 --- a/net/socket/ssl_client_socket_pool_unittest.cc +++ b/net/socket/ssl_client_socket_pool_unittest.cc @@ -28,9 +28,11 @@ #include "net/socket/socket_test_util.h" #include "net/spdy/spdy_session.h" #include "net/spdy/spdy_session_pool.h" -#include "net/spdy/spdy_test_util.h" +#include "net/spdy/spdy_test_util_spdy2.h" #include "testing/gtest/include/gtest/gtest.h" +using namespace net::test_spdy2; + namespace net { namespace { diff --git a/net/socket_stream/socket_stream_job.h b/net/socket_stream/socket_stream_job.h index 039b933..6665919 100644 --- a/net/socket_stream/socket_stream_job.h +++ b/net/socket_stream/socket_stream_job.h @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 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. @@ -67,7 +67,8 @@ class NET_EXPORT SocketStreamJob virtual void DetachDelegate(); protected: - friend class WebSocketJobTest; + friend class WebSocketJobSpdy2Test; + friend class WebSocketJobSpdy3Test; friend class base::RefCountedThreadSafe<SocketStreamJob>; virtual ~SocketStreamJob(); diff --git a/net/spdy/buffered_spdy_framer_unittest.cc b/net/spdy/buffered_spdy_framer_spdy2_unittest.cc index 3c2921e..135a635 100644 --- a/net/spdy/buffered_spdy_framer_unittest.cc +++ b/net/spdy/buffered_spdy_framer_spdy2_unittest.cc @@ -4,12 +4,14 @@ #include "net/spdy/buffered_spdy_framer.h" -#include "net/spdy/spdy_test_util.h" +#include "net/spdy/spdy_test_util_spdy2.h" #include "testing/platform_test.h" +using namespace net::test_spdy2; + namespace spdy { -namespace test { +namespace { class TestBufferedSpdyVisitor : public BufferedSpdyFramerVisitorInterface { public: @@ -131,15 +133,9 @@ class TestBufferedSpdyVisitor : public BufferedSpdyFramerVisitorInterface { SpdyHeaderBlock headers_; }; -} // namespace test - -} // namespace spdy - -using spdy::test::TestBufferedSpdyVisitor; - -namespace spdy { +} // namespace -class BufferedSpdyFramerTest : public PlatformTest { +class BufferedSpdyFramerSpdy2Test : public PlatformTest { protected: void EnableCompression(bool enabled) { SpdyFramer::set_enable_compression_default(enabled); @@ -173,7 +169,7 @@ class BufferedSpdyFramerTest : public PlatformTest { } }; -TEST_F(BufferedSpdyFramerTest, ReadSynStreamHeaderBlock) { +TEST_F(BufferedSpdyFramerSpdy2Test, ReadSynStreamHeaderBlock) { EnableCompression(false); SpdyHeaderBlock headers; @@ -200,7 +196,7 @@ TEST_F(BufferedSpdyFramerTest, ReadSynStreamHeaderBlock) { EXPECT_TRUE(CompareHeaderBlocks(&headers, &visitor.headers_)); } -TEST_F(BufferedSpdyFramerTest, ReadSynReplyHeaderBlock) { +TEST_F(BufferedSpdyFramerSpdy2Test, ReadSynReplyHeaderBlock) { EnableCompression(false); SpdyHeaderBlock headers; @@ -225,7 +221,7 @@ TEST_F(BufferedSpdyFramerTest, ReadSynReplyHeaderBlock) { EXPECT_TRUE(CompareHeaderBlocks(&headers, &visitor.headers_)); } -TEST_F(BufferedSpdyFramerTest, ReadHeadersHeaderBlock) { +TEST_F(BufferedSpdyFramerSpdy2Test, ReadHeadersHeaderBlock) { EnableCompression(false); SpdyHeaderBlock headers; diff --git a/net/spdy/buffered_spdy_framer_spdy3_unittest.cc b/net/spdy/buffered_spdy_framer_spdy3_unittest.cc new file mode 100644 index 0000000..85a6c45 --- /dev/null +++ b/net/spdy/buffered_spdy_framer_spdy3_unittest.cc @@ -0,0 +1,248 @@ +// Copyright (c) 2012 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/buffered_spdy_framer.h" + +#include "net/spdy/spdy_test_util_spdy3.h" +#include "testing/platform_test.h" + +using namespace net::test_spdy3; + +namespace spdy { + +namespace { + +class TestBufferedSpdyVisitor : public BufferedSpdyFramerVisitorInterface { + public: + TestBufferedSpdyVisitor() + : error_count_(0), + syn_frame_count_(0), + syn_reply_frame_count_(0), + headers_frame_count_(0), + header_stream_id_(-1) { + } + + void OnError(int error_code) { + LOG(INFO) << "SpdyFramer Error: " << error_code; + error_count_++; + } + + void OnStreamError(spdy::SpdyStreamId stream_id, + const std::string& description) { + LOG(INFO) << "SpdyFramer Error on stream: " << stream_id << " " + << description; + error_count_++; + } + + void OnSynStream(const SpdySynStreamControlFrame& frame, + const linked_ptr<SpdyHeaderBlock>& headers) { + header_stream_id_ = frame.stream_id(); + EXPECT_NE(header_stream_id_, SpdyFramer::kInvalidStream); + syn_frame_count_++; + headers_ = *headers; + } + + void OnSynReply(const SpdySynReplyControlFrame& frame, + const linked_ptr<SpdyHeaderBlock>& headers) { + header_stream_id_ = frame.stream_id(); + EXPECT_NE(header_stream_id_, SpdyFramer::kInvalidStream); + syn_reply_frame_count_++; + headers_ = *headers; + } + + void OnHeaders(const SpdyHeadersControlFrame& frame, + const linked_ptr<SpdyHeaderBlock>& headers) { + header_stream_id_ = frame.stream_id(); + EXPECT_NE(header_stream_id_, SpdyFramer::kInvalidStream); + headers_frame_count_++; + headers_ = *headers; + } + + void OnStreamFrameData(SpdyStreamId stream_id, + const char* data, + size_t len) { + LOG(FATAL) << "Unexpected OnStreamFrameData call."; + } + + bool OnCredentialFrameData(const char*, size_t) { + LOG(FATAL) << "Unexpected OnCredentialFrameData call."; + return false; + } + + void OnDataFrameHeader(const SpdyDataFrame* frame) { + LOG(FATAL) << "Unexpected OnDataFrameHeader call."; + } + + void OnControl(const SpdyControlFrame* frame) { + uint32 type = frame->type(); + switch (type) { + case SYN_STREAM: + case SYN_REPLY: + case HEADERS: + header_stream_id_ = SpdyFramer::GetControlFrameStreamId(frame); + EXPECT_NE(header_stream_id_, SpdyFramer::kInvalidStream); + buffered_spdy_framer_.OnControl(frame); + break; + default: + LOG(FATAL) << "Unexpected frame type." << type; + } + } + + void OnRstStream(const spdy::SpdyRstStreamControlFrame& frame) {} + void OnGoAway(const spdy::SpdyGoAwayControlFrame& frame) {} + void OnPing(const spdy::SpdyPingControlFrame& frame) {} + void OnSettings(const spdy::SpdySettingsControlFrame& frame) {} + void OnWindowUpdate(const spdy::SpdyWindowUpdateControlFrame& frame) {} + void OnCredential(const spdy::SpdyCredentialControlFrame& frame) {} + + // Convenience function which runs a framer simulation with particular input. + void SimulateInFramer(const unsigned char* input, size_t size) { + buffered_spdy_framer_.set_visitor(this); + size_t input_remaining = size; + const char* input_ptr = reinterpret_cast<const char*>(input); + while (input_remaining > 0 && + buffered_spdy_framer_.error_code() == SpdyFramer::SPDY_NO_ERROR) { + // To make the tests more interesting, we feed random (amd small) chunks + // into the framer. This simulates getting strange-sized reads from + // the socket. + const size_t kMaxReadSize = 32; + size_t bytes_read = + (rand() % std::min(input_remaining, kMaxReadSize)) + 1; + size_t bytes_processed = + buffered_spdy_framer_.ProcessInput(input_ptr, bytes_read); + input_remaining -= bytes_processed; + input_ptr += bytes_processed; + if (buffered_spdy_framer_.state() == SpdyFramer::SPDY_DONE) + buffered_spdy_framer_.Reset(); + } + } + + BufferedSpdyFramer buffered_spdy_framer_; + + // Counters from the visitor callbacks. + int error_count_; + int syn_frame_count_; + int syn_reply_frame_count_; + int headers_frame_count_; + + // Header block streaming state: + SpdyStreamId header_stream_id_; + + // Headers from OnSyn, OnSynReply and OnHeaders for verification. + SpdyHeaderBlock headers_; +}; + +} // namespace + +class BufferedSpdyFramerSpdy3Test : public PlatformTest { + protected: + void EnableCompression(bool enabled) { + SpdyFramer::set_enable_compression_default(enabled); + } + + // Returns true if the two header blocks have equivalent content. + bool CompareHeaderBlocks(const SpdyHeaderBlock* expected, + const SpdyHeaderBlock* actual) { + if (expected->size() != actual->size()) { + LOG(ERROR) << "Expected " << expected->size() << " headers; actually got " + << actual->size() << "."; + return false; + } + for (SpdyHeaderBlock::const_iterator it = expected->begin(); + it != expected->end(); + ++it) { + SpdyHeaderBlock::const_iterator it2 = actual->find(it->first); + if (it2 == actual->end()) { + LOG(ERROR) << "Expected header name '" << it->first << "'."; + return false; + } + if (it->second.compare(it2->second) != 0) { + LOG(ERROR) << "Expected header named '" << it->first + << "' to have a value of '" << it->second + << "'. The actual value received was '" << it2->second + << "'."; + return false; + } + } + return true; + } +}; + +TEST_F(BufferedSpdyFramerSpdy3Test, ReadSynStreamHeaderBlock) { + EnableCompression(false); + + SpdyHeaderBlock headers; + headers["aa"] = "vv"; + headers["bb"] = "ww"; + BufferedSpdyFramer framer; + scoped_ptr<SpdySynStreamControlFrame> control_frame( + framer.CreateSynStream(1, // stream_id + 0, // associated_stream_id + 1, // priority + CONTROL_FLAG_NONE, + true, // compress + &headers)); + EXPECT_TRUE(control_frame.get() != NULL); + + TestBufferedSpdyVisitor visitor; + visitor.SimulateInFramer( + reinterpret_cast<unsigned char*>(control_frame.get()->data()), + control_frame.get()->length() + SpdyControlFrame::kHeaderSize); + EXPECT_EQ(0, visitor.error_count_); + EXPECT_EQ(1, visitor.syn_frame_count_); + EXPECT_EQ(0, visitor.syn_reply_frame_count_); + EXPECT_EQ(0, visitor.headers_frame_count_); + EXPECT_TRUE(CompareHeaderBlocks(&headers, &visitor.headers_)); +} + +TEST_F(BufferedSpdyFramerSpdy3Test, ReadSynReplyHeaderBlock) { + EnableCompression(false); + + SpdyHeaderBlock headers; + headers["alpha"] = "beta"; + headers["gamma"] = "delta"; + BufferedSpdyFramer framer; + scoped_ptr<SpdySynReplyControlFrame> control_frame( + framer.CreateSynReply(1, // stream_id + CONTROL_FLAG_NONE, + true, // compress + &headers)); + EXPECT_TRUE(control_frame.get() != NULL); + + TestBufferedSpdyVisitor visitor; + visitor.SimulateInFramer( + reinterpret_cast<unsigned char*>(control_frame.get()->data()), + control_frame.get()->length() + SpdyControlFrame::kHeaderSize); + EXPECT_EQ(0, visitor.error_count_); + EXPECT_EQ(0, visitor.syn_frame_count_); + EXPECT_EQ(1, visitor.syn_reply_frame_count_); + EXPECT_EQ(0, visitor.headers_frame_count_); + EXPECT_TRUE(CompareHeaderBlocks(&headers, &visitor.headers_)); +} + +TEST_F(BufferedSpdyFramerSpdy3Test, ReadHeadersHeaderBlock) { + EnableCompression(false); + + SpdyHeaderBlock headers; + headers["alpha"] = "beta"; + headers["gamma"] = "delta"; + BufferedSpdyFramer framer; + scoped_ptr<SpdyHeadersControlFrame> control_frame( + framer.CreateHeaders(1, // stream_id + CONTROL_FLAG_NONE, + true, // compress + &headers)); + EXPECT_TRUE(control_frame.get() != NULL); + + TestBufferedSpdyVisitor visitor; + visitor.SimulateInFramer( + reinterpret_cast<unsigned char*>(control_frame.get()->data()), + control_frame.get()->length() + SpdyControlFrame::kHeaderSize); + EXPECT_EQ(0, visitor.error_count_); + EXPECT_EQ(0, visitor.syn_frame_count_); + EXPECT_EQ(0, visitor.syn_reply_frame_count_); + EXPECT_EQ(1, visitor.headers_frame_count_); + EXPECT_TRUE(CompareHeaderBlocks(&headers, &visitor.headers_)); +} +} // namespace spdy diff --git a/net/spdy/spdy_http_stream.h b/net/spdy/spdy_http_stream.h index 429eee8..b4b644d 100644 --- a/net/spdy/spdy_http_stream.h +++ b/net/spdy/spdy_http_stream.h @@ -86,7 +86,12 @@ class NET_EXPORT_PRIVATE SpdyHttpStream : public SpdyStream::Delegate, virtual void set_chunk_callback(ChunkCallback* callback) OVERRIDE; private: - FRIEND_TEST_ALL_PREFIXES(SpdyNetworkTransactionTest, FlowControlStallResume); + FRIEND_TEST_ALL_PREFIXES(SpdyNetworkTransactionSpdy2Test, + FlowControlStallResume); + FRIEND_TEST_ALL_PREFIXES(SpdyNetworkTransactionSpdy21Test, + FlowControlStallResume); + FRIEND_TEST_ALL_PREFIXES(SpdyNetworkTransactionSpdy3Test, + FlowControlStallResume); // Call the user callback. void DoCallback(int rv); diff --git a/net/spdy/spdy_http_stream_unittest.cc b/net/spdy/spdy_http_stream_spdy2_unittest.cc index c7dddc4..244f84e 100644 --- a/net/spdy/spdy_http_stream_unittest.cc +++ b/net/spdy/spdy_http_stream_spdy2_unittest.cc @@ -12,16 +12,18 @@ #include "net/http/http_response_headers.h" #include "net/http/http_response_info.h" #include "net/spdy/spdy_session.h" -#include "net/spdy/spdy_test_util.h" +#include "net/spdy/spdy_test_util_spdy2.h" #include "testing/gtest/include/gtest/gtest.h" +using namespace net::test_spdy2; + namespace net { -class SpdyHttpStreamTest : public testing::Test { +class SpdyHttpStreamSpdy2Test : public testing::Test { public: OrderedSocketData* data() { return data_.get(); } protected: - SpdyHttpStreamTest() {} + SpdyHttpStreamSpdy2Test() {} void EnableCompression(bool enabled) { spdy::SpdyFramer::set_enable_compression_default(enabled); @@ -68,7 +70,7 @@ class SpdyHttpStreamTest : public testing::Test { scoped_refptr<TransportSocketParams> transport_params_; }; -TEST_F(SpdyHttpStreamTest, SendRequest) { +TEST_F(SpdyHttpStreamSpdy2Test, SendRequest) { EnableCompression(false); SpdySession::SetSSLMode(false); @@ -117,7 +119,7 @@ TEST_F(SpdyHttpStreamTest, SendRequest) { EXPECT_TRUE(data()->at_write_eof()); } -TEST_F(SpdyHttpStreamTest, SendChunkedPost) { +TEST_F(SpdyHttpStreamSpdy2Test, SendChunkedPost) { EnableCompression(false); SpdySession::SetSSLMode(false); UploadDataStream::set_merge_chunks(false); @@ -181,7 +183,7 @@ TEST_F(SpdyHttpStreamTest, SendChunkedPost) { } // Test case for bug: http://code.google.com/p/chromium/issues/detail?id=50058 -TEST_F(SpdyHttpStreamTest, SpdyURLTest) { +TEST_F(SpdyHttpStreamSpdy2Test, SpdyURLTest) { EnableCompression(false); SpdySession::SetSSLMode(false); @@ -238,6 +240,8 @@ TEST_F(SpdyHttpStreamTest, SpdyURLTest) { EXPECT_TRUE(data()->at_write_eof()); } +namespace { + void GetECOriginBoundCertAndProof(const std::string& origin, OriginBoundCertService* obc_service, std::string* cert, @@ -277,13 +281,15 @@ void GetECOriginBoundCertAndProof(const std::string& origin, proof->assign(proof_data.begin(), proof_data.end()); } +} // namespace + // TODO(rch): When openssl supports origin bound certifictes, this // guard can be removed #if !defined(USE_OPENSSL) // Test that if we request a resource for a new origin on a session that // used origin bound certificates, that we send a CREDENTIAL frame for // the new origin before we send the new request. -void SpdyHttpStreamTest::TestSendCredentials( +void SpdyHttpStreamSpdy2Test::TestSendCredentials( OriginBoundCertService* obc_service, const std::string& cert, const std::string& proof, @@ -441,7 +447,7 @@ class MockECSignatureCreatorFactory : public crypto::ECSignatureCreatorFactory { DISALLOW_COPY_AND_ASSIGN(MockECSignatureCreatorFactory); }; -TEST_F(SpdyHttpStreamTest, SendCredentialsEC) { +TEST_F(SpdyHttpStreamSpdy2Test, SendCredentialsEC) { scoped_ptr<crypto::ECSignatureCreatorFactory> ec_signature_creator_factory( new MockECSignatureCreatorFactory()); crypto::ECSignatureCreator::SetFactoryForTesting( diff --git a/net/spdy/spdy_http_stream_spdy3_unittest.cc b/net/spdy/spdy_http_stream_spdy3_unittest.cc new file mode 100644 index 0000000..79390df --- /dev/null +++ b/net/spdy/spdy_http_stream_spdy3_unittest.cc @@ -0,0 +1,471 @@ +// Copyright (c) 2012 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_http_stream.h" + +#include "crypto/ec_private_key.h" +#include "crypto/ec_signature_creator.h" +#include "crypto/signature_creator.h" +#include "net/base/asn1_util.h" +#include "net/base/default_origin_bound_cert_store.h" +#include "net/http/http_response_headers.h" +#include "net/http/http_response_info.h" +#include "net/spdy/spdy_session.h" +#include "net/spdy/spdy_test_util_spdy3.h" +#include "testing/gtest/include/gtest/gtest.h" + +using namespace net::test_spdy3; + +namespace net { + +class SpdyHttpStreamSpdy3Test : public testing::Test { + public: + OrderedSocketData* data() { return data_.get(); } + protected: + SpdyHttpStreamSpdy3Test() {} + + void EnableCompression(bool enabled) { + spdy::SpdyFramer::set_enable_compression_default(enabled); + } + + virtual void TearDown() { + crypto::ECSignatureCreator::SetFactoryForTesting(NULL); + MessageLoop::current()->RunAllPending(); + } + int InitSession(MockRead* reads, size_t reads_count, + MockWrite* writes, size_t writes_count, + HostPortPair& host_port_pair) { + HostPortProxyPair pair(host_port_pair, ProxyServer::Direct()); + data_.reset(new OrderedSocketData(reads, reads_count, + writes, writes_count)); + session_deps_.socket_factory->AddSocketDataProvider(data_.get()); + http_session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps_); + session_ = http_session_->spdy_session_pool()->Get(pair, BoundNetLog()); + transport_params_ = new TransportSocketParams(host_port_pair, + MEDIUM, false, false); + TestCompletionCallback callback; + scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle); + EXPECT_EQ(ERR_IO_PENDING, + connection->Init(host_port_pair.ToString(), + transport_params_, + MEDIUM, + callback.callback(), + http_session_->GetTransportSocketPool(), + BoundNetLog())); + EXPECT_EQ(OK, callback.WaitForResult()); + return session_->InitializeWithSocket(connection.release(), false, OK); + } + + void TestSendCredentials( + OriginBoundCertService* obc_service, + const std::string& cert, + const std::string& proof, + SSLClientCertType type); + + SpdySessionDependencies session_deps_; + scoped_ptr<OrderedSocketData> data_; + scoped_refptr<HttpNetworkSession> http_session_; + scoped_refptr<SpdySession> session_; + scoped_refptr<TransportSocketParams> transport_params_; +}; + +TEST_F(SpdyHttpStreamSpdy3Test, SendRequest) { + EnableCompression(false); + SpdySession::SetSSLMode(false); + + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + MockWrite writes[] = { + CreateMockWrite(*req.get(), 1), + }; + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + MockRead reads[] = { + CreateMockRead(*resp, 2), + MockRead(SYNCHRONOUS, 0, 3) // EOF + }; + + HostPortPair host_port_pair("www.google.com", 80); + HostPortProxyPair pair(host_port_pair, ProxyServer::Direct()); + EXPECT_EQ(OK, InitSession(reads, arraysize(reads), writes, arraysize(writes), + host_port_pair)); + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + TestCompletionCallback callback; + HttpResponseInfo response; + HttpRequestHeaders headers; + BoundNetLog net_log; + scoped_ptr<SpdyHttpStream> http_stream( + new SpdyHttpStream(session_.get(), true)); + ASSERT_EQ( + OK, + http_stream->InitializeStream(&request, net_log, CompletionCallback())); + + EXPECT_EQ(ERR_IO_PENDING, http_stream->SendRequest(headers, NULL, &response, + callback.callback())); + EXPECT_TRUE(http_session_->spdy_session_pool()->HasSession(pair)); + + // This triggers the MockWrite and read 2 + callback.WaitForResult(); + + // This triggers read 3. The empty read causes the session to shut down. + data()->CompleteRead(); + + // Because we abandoned the stream, we don't expect to find a session in the + // pool anymore. + EXPECT_FALSE(http_session_->spdy_session_pool()->HasSession(pair)); + EXPECT_TRUE(data()->at_read_eof()); + EXPECT_TRUE(data()->at_write_eof()); +} + +TEST_F(SpdyHttpStreamSpdy3Test, SendChunkedPost) { + EnableCompression(false); + SpdySession::SetSSLMode(false); + UploadDataStream::set_merge_chunks(false); + + scoped_ptr<spdy::SpdyFrame> req(ConstructChunkedSpdyPost(NULL, 0)); + scoped_ptr<spdy::SpdyFrame> chunk1(ConstructSpdyBodyFrame(1, false)); + scoped_ptr<spdy::SpdyFrame> chunk2(ConstructSpdyBodyFrame(1, true)); + MockWrite writes[] = { + CreateMockWrite(*req.get(), 1), + CreateMockWrite(*chunk1, 2), // POST upload frames + CreateMockWrite(*chunk2, 3), + }; + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyPostSynReply(NULL, 0)); + MockRead reads[] = { + CreateMockRead(*resp, 4), + CreateMockRead(*chunk1, 5), + CreateMockRead(*chunk2, 5), + MockRead(SYNCHRONOUS, 0, 6) // EOF + }; + + HostPortPair host_port_pair("www.google.com", 80); + HostPortProxyPair pair(host_port_pair, ProxyServer::Direct()); + EXPECT_EQ(OK, InitSession(reads, arraysize(reads), writes, arraysize(writes), + host_port_pair)); + + HttpRequestInfo request; + request.method = "POST"; + request.url = GURL("http://www.google.com/"); + request.upload_data = new UploadData(); + request.upload_data->set_is_chunked(true); + request.upload_data->AppendChunk(kUploadData, kUploadDataSize, false); + request.upload_data->AppendChunk(kUploadData, kUploadDataSize, true); + TestCompletionCallback callback; + HttpResponseInfo response; + HttpRequestHeaders headers; + BoundNetLog net_log; + SpdyHttpStream http_stream(session_.get(), true); + ASSERT_EQ( + OK, + http_stream.InitializeStream(&request, net_log, CompletionCallback())); + + // http_stream.SendRequest() will take ownership of upload_stream. + UploadDataStream* upload_stream = new UploadDataStream(request.upload_data); + ASSERT_EQ(OK, upload_stream->Init()); + EXPECT_EQ(ERR_IO_PENDING, http_stream.SendRequest( + headers, upload_stream, &response, callback.callback())); + EXPECT_TRUE(http_session_->spdy_session_pool()->HasSession(pair)); + + // This triggers the MockWrite and read 2 + callback.WaitForResult(); + + // This triggers read 3. The empty read causes the session to shut down. + data()->CompleteRead(); + MessageLoop::current()->RunAllPending(); + + // Because we abandoned the stream, we don't expect to find a session in the + // pool anymore. + EXPECT_FALSE(http_session_->spdy_session_pool()->HasSession(pair)); + EXPECT_TRUE(data()->at_read_eof()); + EXPECT_TRUE(data()->at_write_eof()); +} + +// Test case for bug: http://code.google.com/p/chromium/issues/detail?id=50058 +TEST_F(SpdyHttpStreamSpdy3Test, SpdyURLTest) { + EnableCompression(false); + SpdySession::SetSSLMode(false); + + const char * const full_url = "http://www.google.com/foo?query=what#anchor"; + const char * const base_url = "http://www.google.com/foo?query=what"; + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(base_url, false, 1, LOWEST)); + MockWrite writes[] = { + CreateMockWrite(*req.get(), 1), + }; + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + MockRead reads[] = { + CreateMockRead(*resp, 2), + MockRead(SYNCHRONOUS, 0, 3) // EOF + }; + + HostPortPair host_port_pair("www.google.com", 80); + HostPortProxyPair pair(host_port_pair, ProxyServer::Direct()); + EXPECT_EQ(OK, InitSession(reads, arraysize(reads), writes, arraysize(writes), + host_port_pair)); + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL(full_url); + TestCompletionCallback callback; + HttpResponseInfo response; + HttpRequestHeaders headers; + BoundNetLog net_log; + scoped_ptr<SpdyHttpStream> http_stream(new SpdyHttpStream(session_, true)); + ASSERT_EQ( + OK, + http_stream->InitializeStream(&request, net_log, CompletionCallback())); + + EXPECT_EQ(ERR_IO_PENDING, http_stream->SendRequest(headers, NULL, &response, + callback.callback())); + + spdy::SpdyHeaderBlock* spdy_header = + http_stream->stream()->spdy_headers().get(); + EXPECT_TRUE(spdy_header != NULL); + if (spdy_header->find("url") != spdy_header->end()) + EXPECT_EQ("/foo?query=what", spdy_header->find("url")->second); + else + FAIL() << "No url is set in spdy_header!"; + + // This triggers the MockWrite and read 2 + callback.WaitForResult(); + + // This triggers read 3. The empty read causes the session to shut down. + data()->CompleteRead(); + + // Because we abandoned the stream, we don't expect to find a session in the + // pool anymore. + EXPECT_FALSE(http_session_->spdy_session_pool()->HasSession(pair)); + EXPECT_TRUE(data()->at_read_eof()); + EXPECT_TRUE(data()->at_write_eof()); +} + +namespace { + +void GetECOriginBoundCertAndProof(const std::string& origin, + OriginBoundCertService* obc_service, + std::string* cert, + std::string* proof) { + TestCompletionCallback callback; + std::vector<uint8> requested_cert_types; + requested_cert_types.push_back(CLIENT_CERT_ECDSA_SIGN); + SSLClientCertType cert_type; + std::string key; + OriginBoundCertService::RequestHandle request_handle; + int rv = obc_service->GetOriginBoundCert(origin, requested_cert_types, + &cert_type, &key, cert, + callback.callback(), + &request_handle); + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_EQ(OK, callback.WaitForResult()); + EXPECT_EQ(CLIENT_CERT_ECDSA_SIGN, cert_type); + + unsigned char secret[32]; + memset(secret, 'A', arraysize(secret)); + + // Convert the key string into a vector<unit8> + std::vector<uint8> key_data(key.begin(), key.end()); + + base::StringPiece spki_piece; + ASSERT_TRUE(asn1::ExtractSPKIFromDERCert(*cert, &spki_piece)); + std::vector<uint8> spki(spki_piece.data(), + spki_piece.data() + spki_piece.size()); + + std::vector<uint8> proof_data; + scoped_ptr<crypto::ECPrivateKey> private_key( + crypto::ECPrivateKey::CreateFromEncryptedPrivateKeyInfo( + OriginBoundCertService::kEPKIPassword, key_data, spki)); + scoped_ptr<crypto::ECSignatureCreator> creator( + crypto::ECSignatureCreator::Create(private_key.get())); + creator->Sign(secret, arraysize(secret), &proof_data); + proof->assign(proof_data.begin(), proof_data.end()); +} + +} // namespace + +// TODO(rch): When openssl supports origin bound certifictes, this +// guard can be removed +#if !defined(USE_OPENSSL) +// Test that if we request a resource for a new origin on a session that +// used origin bound certificates, that we send a CREDENTIAL frame for +// the new origin before we send the new request. +void SpdyHttpStreamSpdy3Test::TestSendCredentials( + OriginBoundCertService* obc_service, + const std::string& cert, + const std::string& proof, + SSLClientCertType type) { + EnableCompression(false); + + spdy::SpdyCredential cred; + cred.slot = 1; + cred.proof = proof; + cred.certs.push_back(cert); + + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> credential(ConstructSpdyCredential(cred)); + scoped_ptr<spdy::SpdyFrame> req2(ConstructSpdyGet("http://www.gmail.com", + false, 3, LOWEST)); + MockWrite writes[] = { + CreateMockWrite(*req.get(), 0), + CreateMockWrite(*credential.get(), 2), + CreateMockWrite(*req2.get(), 3), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 3)); + MockRead reads[] = { + CreateMockRead(*resp, 1), + CreateMockRead(*resp2, 4), + MockRead(SYNCHRONOUS, 0, 5) // EOF + }; + + HostPortPair host_port_pair("www.google.com", 80); + HostPortProxyPair pair(host_port_pair, ProxyServer::Direct()); + + DeterministicMockClientSocketFactory* socket_factory = + session_deps_.deterministic_socket_factory.get(); + scoped_refptr<DeterministicSocketData> data( + new DeterministicSocketData(reads, arraysize(reads), + writes, arraysize(writes))); + socket_factory->AddSocketDataProvider(data.get()); + SSLSocketDataProvider ssl(SYNCHRONOUS, OK); + ssl.origin_bound_cert_type = type; + ssl.origin_bound_cert_service = obc_service; + ssl.protocol_negotiated = SSLClientSocket::kProtoSPDY3; + socket_factory->AddSSLSocketDataProvider(&ssl); + http_session_ = SpdySessionDependencies::SpdyCreateSessionDeterministic( + &session_deps_); + session_ = http_session_->spdy_session_pool()->Get(pair, BoundNetLog()); + transport_params_ = new TransportSocketParams(host_port_pair, + MEDIUM, false, false); + TestCompletionCallback callback; + scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle); + SSLConfig ssl_config; + scoped_refptr<SOCKSSocketParams> socks_params; + scoped_refptr<HttpProxySocketParams> http_proxy_params; + scoped_refptr<SSLSocketParams> ssl_params( + new SSLSocketParams(transport_params_, + socks_params, + http_proxy_params, + ProxyServer::SCHEME_DIRECT, + host_port_pair, + ssl_config, + 0, + false, + false)); + EXPECT_EQ(ERR_IO_PENDING, + connection->Init(host_port_pair.ToString(), + ssl_params, + MEDIUM, + callback.callback(), + http_session_->GetSSLSocketPool(), + BoundNetLog())); + callback.WaitForResult(); + EXPECT_EQ(OK, + session_->InitializeWithSocket(connection.release(), true, OK)); + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + HttpResponseInfo response; + HttpRequestHeaders headers; + BoundNetLog net_log; + scoped_ptr<SpdyHttpStream> http_stream( + new SpdyHttpStream(session_.get(), true)); + ASSERT_EQ( + OK, + http_stream->InitializeStream(&request, net_log, CompletionCallback())); + + EXPECT_FALSE(session_->NeedsCredentials(host_port_pair)); + HostPortPair new_host_port_pair("www.gmail.com", 80); + EXPECT_TRUE(session_->NeedsCredentials(new_host_port_pair)); + + EXPECT_EQ(ERR_IO_PENDING, http_stream->SendRequest(headers, NULL, &response, + callback.callback())); + EXPECT_TRUE(http_session_->spdy_session_pool()->HasSession(pair)); + + data->RunFor(2); + callback.WaitForResult(); + + // Start up second request for resource on a new origin. + scoped_ptr<SpdyHttpStream> http_stream2( + new SpdyHttpStream(session_.get(), true)); + request.url = GURL("http://www.gmail.com/"); + ASSERT_EQ( + OK, + http_stream2->InitializeStream(&request, net_log, CompletionCallback())); + EXPECT_EQ(ERR_IO_PENDING, http_stream2->SendRequest(headers, NULL, &response, + callback.callback())); + data->RunFor(2); + callback.WaitForResult(); + + EXPECT_EQ(ERR_IO_PENDING, http_stream2->ReadResponseHeaders( + callback.callback())); + data->RunFor(1); + EXPECT_EQ(OK, callback.WaitForResult()); + ASSERT_TRUE(response.headers.get() != NULL); + ASSERT_EQ(200, response.headers->response_code()); +} + +class MockECSignatureCreator : public crypto::ECSignatureCreator { + public: + explicit MockECSignatureCreator(crypto::ECPrivateKey* key) : key_(key) {} + + virtual bool Sign(const uint8* data, + int data_len, + std::vector<uint8>* signature) OVERRIDE { + std::vector<uint8> private_key_value; + key_->ExportValue(&private_key_value); + std::string head = "fakesignature"; + std::string tail = "/fakesignature"; + + signature->clear(); + signature->insert(signature->end(), head.begin(), head.end()); + signature->insert(signature->end(), private_key_value.begin(), + private_key_value.end()); + signature->insert(signature->end(), '-'); + signature->insert(signature->end(), data, data + data_len); + signature->insert(signature->end(), tail.begin(), tail.end()); + return true; + } + + private: + crypto::ECPrivateKey* key_; + DISALLOW_COPY_AND_ASSIGN(MockECSignatureCreator); +}; + +class MockECSignatureCreatorFactory : public crypto::ECSignatureCreatorFactory { + public: + MockECSignatureCreatorFactory() {} + virtual ~MockECSignatureCreatorFactory() {} + + virtual crypto::ECSignatureCreator* Create( + crypto::ECPrivateKey* key) OVERRIDE { + return new MockECSignatureCreator(key); + } + private: + DISALLOW_COPY_AND_ASSIGN(MockECSignatureCreatorFactory); +}; + +TEST_F(SpdyHttpStreamSpdy3Test, SendCredentialsEC) { + scoped_ptr<crypto::ECSignatureCreatorFactory> ec_signature_creator_factory( + new MockECSignatureCreatorFactory()); + crypto::ECSignatureCreator::SetFactoryForTesting( + ec_signature_creator_factory.get()); + + scoped_ptr<OriginBoundCertService> obc_service( + new OriginBoundCertService(new DefaultOriginBoundCertStore(NULL))); + std::string cert; + std::string proof; + GetECOriginBoundCertAndProof("http://www.gmail.com/", obc_service.get(), + &cert, &proof); + + TestSendCredentials(obc_service.get(), cert, proof, CLIENT_CERT_ECDSA_SIGN); +} + +#endif // !defined(USE_OPENSSL) + +// TODO(willchan): Write a longer test for SpdyStream that exercises all +// methods. + +} // namespace net diff --git a/net/spdy/spdy_network_transaction_spdy21_unittest.cc b/net/spdy/spdy_network_transaction_spdy21_unittest.cc new file mode 100644 index 0000000..aba130e --- /dev/null +++ b/net/spdy/spdy_network_transaction_spdy21_unittest.cc @@ -0,0 +1,5851 @@ +// Copyright (c) 2012 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/http/http_network_transaction.h" + +#include <string> +#include <vector> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "net/base/auth.h" +#include "net/base/net_log_unittest.h" +#include "net/http/http_network_session_peer.h" +#include "net/http/http_transaction_unittest.h" +#include "net/socket/client_socket_pool_base.h" +#include "net/spdy/spdy_http_stream.h" +#include "net/spdy/spdy_http_utils.h" +#include "net/spdy/spdy_session.h" +#include "net/spdy/spdy_session_pool.h" +#include "net/spdy/spdy_test_util_spdy2.h" +#include "net/url_request/url_request_test_util.h" +#include "testing/platform_test.h" + +using namespace net::test_spdy2; + +//----------------------------------------------------------------------------- + +namespace net { + +enum SpdyNetworkTransactionSpdy21TestTypes { + SPDYNPN, + SPDYNOSSL, + SPDYSSL, +}; +class SpdyNetworkTransactionSpdy21Test + : public ::testing::TestWithParam<SpdyNetworkTransactionSpdy21TestTypes> { + protected: + + virtual void SetUp() { + // By default, all tests turn off compression. + EnableCompression(false); + google_get_request_initialized_ = false; + google_post_request_initialized_ = false; + google_chunked_post_request_initialized_ = false; + } + + virtual void TearDown() { + // Empty the current queue. + MessageLoop::current()->RunAllPending(); + } + + struct TransactionHelperResult { + int rv; + std::string status_line; + std::string response_data; + HttpResponseInfo response_info; + }; + + void EnableCompression(bool enabled) { + spdy::SpdyFramer::set_enable_compression_default(enabled); + } + + // A helper class that handles all the initial npn/ssl setup. + class NormalSpdyTransactionHelper { + public: + NormalSpdyTransactionHelper(const HttpRequestInfo& request, + const BoundNetLog& log, + SpdyNetworkTransactionSpdy21TestTypes test_type) + : request_(request), + session_deps_(new SpdySessionDependencies()), + session_(SpdySessionDependencies::SpdyCreateSession( + session_deps_.get())), + log_(log), + test_type_(test_type), + deterministic_(false), + spdy_enabled_(true) { + switch (test_type_) { + case SPDYNOSSL: + case SPDYSSL: + port_ = 80; + break; + case SPDYNPN: + port_ = 443; + break; + default: + NOTREACHED(); + } + } + + ~NormalSpdyTransactionHelper() { + // Any test which doesn't close the socket by sending it an EOF will + // have a valid session left open, which leaks the entire session pool. + // This is just fine - in fact, some of our tests intentionally do this + // so that we can check consistency of the SpdySessionPool as the test + // finishes. If we had put an EOF on the socket, the SpdySession would + // have closed and we wouldn't be able to check the consistency. + + // Forcefully close existing sessions here. + session()->spdy_session_pool()->CloseAllSessions(); + } + + void SetDeterministic() { + session_ = SpdySessionDependencies::SpdyCreateSessionDeterministic( + session_deps_.get()); + deterministic_ = true; + } + + void SetSpdyDisabled() { + spdy_enabled_ = false; + } + + void RunPreTestSetup() { + if (!session_deps_.get()) + session_deps_.reset(new SpdySessionDependencies()); + if (!session_.get()) + session_ = SpdySessionDependencies::SpdyCreateSession( + session_deps_.get()); + HttpStreamFactory::set_use_alternate_protocols(false); + HttpStreamFactory::set_force_spdy_over_ssl(false); + HttpStreamFactory::set_force_spdy_always(false); + + std::vector<std::string> next_protos; + next_protos.push_back("http/1.1"); + next_protos.push_back("spdy/2"); + next_protos.push_back("spdy/2.1"); + + switch (test_type_) { + case SPDYNPN: + session_->http_server_properties()->SetAlternateProtocol( + HostPortPair("www.google.com", 80), 443, + NPN_SPDY_21); + HttpStreamFactory::set_use_alternate_protocols(true); + HttpStreamFactory::SetNextProtos(next_protos); + break; + case SPDYNOSSL: + HttpStreamFactory::set_force_spdy_over_ssl(false); + HttpStreamFactory::set_force_spdy_always(true); + break; + case SPDYSSL: + HttpStreamFactory::set_force_spdy_over_ssl(true); + HttpStreamFactory::set_force_spdy_always(true); + break; + default: + NOTREACHED(); + } + + // We're now ready to use SSL-npn SPDY. + trans_.reset(new HttpNetworkTransaction(session_)); + } + + // Start the transaction, read some data, finish. + void RunDefaultTest() { + output_.rv = trans_->Start(&request_, callback.callback(), log_); + + // We expect an IO Pending or some sort of error. + EXPECT_LT(output_.rv, 0); + if (output_.rv != ERR_IO_PENDING) + return; + + output_.rv = callback.WaitForResult(); + if (output_.rv != OK) { + session_->spdy_session_pool()->CloseCurrentSessions(); + return; + } + + // Verify responses. + const HttpResponseInfo* response = trans_->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + EXPECT_EQ(spdy_enabled_, response->was_fetched_via_spdy); + if (test_type_ == SPDYNPN && spdy_enabled_) { + EXPECT_TRUE(response->was_npn_negotiated); + } else { + EXPECT_TRUE(!response->was_npn_negotiated); + } + // If SPDY is not enabled, a HTTP request should not be diverted + // over a SSL session. + if (!spdy_enabled_) { + EXPECT_EQ(request_.url.SchemeIs("https"), + response->was_npn_negotiated); + } + EXPECT_EQ("192.0.2.33", response->socket_address.host()); + EXPECT_EQ(0, response->socket_address.port()); + output_.status_line = response->headers->GetStatusLine(); + output_.response_info = *response; // Make a copy so we can verify. + output_.rv = ReadTransaction(trans_.get(), &output_.response_data); + } + + // Most tests will want to call this function. In particular, the MockReads + // should end with an empty read, and that read needs to be processed to + // ensure proper deletion of the spdy_session_pool. + void VerifyDataConsumed() { + for (DataVector::iterator it = data_vector_.begin(); + it != data_vector_.end(); ++it) { + EXPECT_TRUE((*it)->at_read_eof()) << "Read count: " + << (*it)->read_count() + << " Read index: " + << (*it)->read_index(); + EXPECT_TRUE((*it)->at_write_eof()) << "Write count: " + << (*it)->write_count() + << " Write index: " + << (*it)->write_index(); + } + } + + // Occasionally a test will expect to error out before certain reads are + // processed. In that case we want to explicitly ensure that the reads were + // not processed. + void VerifyDataNotConsumed() { + for (DataVector::iterator it = data_vector_.begin(); + it != data_vector_.end(); ++it) { + EXPECT_TRUE(!(*it)->at_read_eof()) << "Read count: " + << (*it)->read_count() + << " Read index: " + << (*it)->read_index(); + EXPECT_TRUE(!(*it)->at_write_eof()) << "Write count: " + << (*it)->write_count() + << " Write index: " + << (*it)->write_index(); + } + } + + void RunToCompletion(StaticSocketDataProvider* data) { + RunPreTestSetup(); + AddData(data); + RunDefaultTest(); + VerifyDataConsumed(); + } + + void AddData(StaticSocketDataProvider* data) { + DCHECK(!deterministic_); + data_vector_.push_back(data); + linked_ptr<SSLSocketDataProvider> ssl_( + new SSLSocketDataProvider(ASYNC, OK)); + if (test_type_ == SPDYNPN) { + ssl_->SetNextProto(SSLClientSocket::kProtoSPDY21); + } + ssl_vector_.push_back(ssl_); + if (test_type_ == SPDYNPN || test_type_ == SPDYSSL) + session_deps_->socket_factory->AddSSLSocketDataProvider(ssl_.get()); + session_deps_->socket_factory->AddSocketDataProvider(data); + if (test_type_ == SPDYNPN) { + MockConnect never_finishing_connect(SYNCHRONOUS, ERR_IO_PENDING); + linked_ptr<StaticSocketDataProvider> + hanging_non_alternate_protocol_socket( + new StaticSocketDataProvider(NULL, 0, NULL, 0)); + hanging_non_alternate_protocol_socket->set_connect_data( + never_finishing_connect); + session_deps_->socket_factory->AddSocketDataProvider( + hanging_non_alternate_protocol_socket.get()); + alternate_vector_.push_back(hanging_non_alternate_protocol_socket); + } + } + + void AddDeterministicData(DeterministicSocketData* data) { + DCHECK(deterministic_); + data_vector_.push_back(data); + linked_ptr<SSLSocketDataProvider> ssl_( + new SSLSocketDataProvider(ASYNC, OK)); + if (test_type_ == SPDYNPN) { + ssl_->SetNextProto(SSLClientSocket::kProtoSPDY21); + } + ssl_vector_.push_back(ssl_); + if (test_type_ == SPDYNPN || test_type_ == SPDYSSL) { + session_deps_->deterministic_socket_factory-> + AddSSLSocketDataProvider(ssl_.get()); + } + session_deps_->deterministic_socket_factory->AddSocketDataProvider(data); + if (test_type_ == SPDYNPN) { + MockConnect never_finishing_connect(SYNCHRONOUS, ERR_IO_PENDING); + scoped_refptr<DeterministicSocketData> + hanging_non_alternate_protocol_socket( + new DeterministicSocketData(NULL, 0, NULL, 0)); + hanging_non_alternate_protocol_socket->set_connect_data( + never_finishing_connect); + session_deps_->deterministic_socket_factory->AddSocketDataProvider( + hanging_non_alternate_protocol_socket); + alternate_deterministic_vector_.push_back( + hanging_non_alternate_protocol_socket); + } + } + + // This can only be called after RunPreTestSetup. It adds a Data Provider, + // but not a corresponding SSL data provider + void AddDataNoSSL(StaticSocketDataProvider* data) { + DCHECK(!deterministic_); + session_deps_->socket_factory->AddSocketDataProvider(data); + } + void AddDataNoSSL(DeterministicSocketData* data) { + DCHECK(deterministic_); + session_deps_->deterministic_socket_factory->AddSocketDataProvider(data); + } + + void SetSession(const scoped_refptr<HttpNetworkSession>& session) { + session_ = session; + } + HttpNetworkTransaction* trans() { return trans_.get(); } + void ResetTrans() { trans_.reset(); } + TransactionHelperResult& output() { return output_; } + const HttpRequestInfo& request() const { return request_; } + const scoped_refptr<HttpNetworkSession>& session() const { + return session_; + } + scoped_ptr<SpdySessionDependencies>& session_deps() { + return session_deps_; + } + int port() const { return port_; } + SpdyNetworkTransactionSpdy21TestTypes test_type() const { + return test_type_; + } + + private: + typedef std::vector<StaticSocketDataProvider*> DataVector; + typedef std::vector<linked_ptr<SSLSocketDataProvider> > SSLVector; + typedef std::vector<linked_ptr<StaticSocketDataProvider> > AlternateVector; + typedef std::vector<scoped_refptr<DeterministicSocketData> > + AlternateDeterministicVector; + HttpRequestInfo request_; + scoped_ptr<SpdySessionDependencies> session_deps_; + scoped_refptr<HttpNetworkSession> session_; + TransactionHelperResult output_; + scoped_ptr<StaticSocketDataProvider> first_transaction_; + SSLVector ssl_vector_; + TestCompletionCallback callback; + scoped_ptr<HttpNetworkTransaction> trans_; + scoped_ptr<HttpNetworkTransaction> trans_http_; + DataVector data_vector_; + AlternateVector alternate_vector_; + AlternateDeterministicVector alternate_deterministic_vector_; + const BoundNetLog& log_; + SpdyNetworkTransactionSpdy21TestTypes test_type_; + int port_; + bool deterministic_; + bool spdy_enabled_; + }; + + void ConnectStatusHelperWithExpectedStatus(const MockRead& status, + int expected_status); + + void ConnectStatusHelper(const MockRead& status); + + const HttpRequestInfo& CreateGetPushRequest() { + google_get_push_request_.method = "GET"; + google_get_push_request_.url = GURL("http://www.google.com/foo.dat"); + google_get_push_request_.load_flags = 0; + return google_get_push_request_; + } + + const HttpRequestInfo& CreateGetRequest() { + if (!google_get_request_initialized_) { + google_get_request_.method = "GET"; + google_get_request_.url = GURL(kDefaultURL); + google_get_request_.load_flags = 0; + google_get_request_initialized_ = true; + } + return google_get_request_; + } + + const HttpRequestInfo& CreateGetRequestWithUserAgent() { + if (!google_get_request_initialized_) { + google_get_request_.method = "GET"; + google_get_request_.url = GURL(kDefaultURL); + google_get_request_.load_flags = 0; + google_get_request_.extra_headers.SetHeader("User-Agent", "Chrome"); + google_get_request_initialized_ = true; + } + return google_get_request_; + } + + const HttpRequestInfo& CreatePostRequest() { + if (!google_post_request_initialized_) { + google_post_request_.method = "POST"; + google_post_request_.url = GURL(kDefaultURL); + google_post_request_.upload_data = new UploadData(); + google_post_request_.upload_data->AppendBytes(kUploadData, + kUploadDataSize); + google_post_request_initialized_ = true; + } + return google_post_request_; + } + + const HttpRequestInfo& CreateChunkedPostRequest() { + if (!google_chunked_post_request_initialized_) { + google_chunked_post_request_.method = "POST"; + google_chunked_post_request_.url = GURL(kDefaultURL); + google_chunked_post_request_.upload_data = new UploadData(); + google_chunked_post_request_.upload_data->set_is_chunked(true); + google_chunked_post_request_.upload_data->AppendChunk( + kUploadData, kUploadDataSize, false); + google_chunked_post_request_.upload_data->AppendChunk( + kUploadData, kUploadDataSize, true); + google_chunked_post_request_initialized_ = true; + } + return google_chunked_post_request_; + } + + // Read the result of a particular transaction, knowing that we've got + // multiple transactions in the read pipeline; so as we read, we may have + // to skip over data destined for other transactions while we consume + // the data for |trans|. + int ReadResult(HttpNetworkTransaction* trans, + StaticSocketDataProvider* data, + std::string* result) { + const int kSize = 3000; + + int bytes_read = 0; + scoped_refptr<net::IOBufferWithSize> buf(new net::IOBufferWithSize(kSize)); + TestCompletionCallback callback; + while (true) { + int rv = trans->Read(buf, kSize, callback.callback()); + if (rv == ERR_IO_PENDING) { + // Multiple transactions may be in the data set. Keep pulling off + // reads until we complete our callback. + while (!callback.have_result()) { + data->CompleteRead(); + MessageLoop::current()->RunAllPending(); + } + rv = callback.WaitForResult(); + } else if (rv <= 0) { + break; + } + result->append(buf->data(), rv); + bytes_read += rv; + } + return bytes_read; + } + + void VerifyStreamsClosed(const NormalSpdyTransactionHelper& helper) { + // This lengthy block is reaching into the pool to dig out the active + // session. Once we have the session, we verify that the streams are + // all closed and not leaked at this point. + const GURL& url = helper.request().url; + int port = helper.test_type() == SPDYNPN ? 443 : 80; + HostPortPair host_port_pair(url.host(), port); + HostPortProxyPair pair(host_port_pair, ProxyServer::Direct()); + BoundNetLog log; + const scoped_refptr<HttpNetworkSession>& session = helper.session(); + SpdySessionPool* pool(session->spdy_session_pool()); + EXPECT_TRUE(pool->HasSession(pair)); + scoped_refptr<SpdySession> spdy_session(pool->Get(pair, log)); + ASSERT_TRUE(spdy_session.get() != NULL); + EXPECT_EQ(0u, spdy_session->num_active_streams()); + EXPECT_EQ(0u, spdy_session->num_unclaimed_pushed_streams()); + } + + void RunServerPushTest(OrderedSocketData* data, + HttpResponseInfo* response, + HttpResponseInfo* push_response, + std::string& expected) { + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.RunPreTestSetup(); + helper.AddData(data); + + HttpNetworkTransaction* trans = helper.trans(); + + // Start the transaction with basic parameters. + TestCompletionCallback callback; + int rv = trans->Start( + &CreateGetRequest(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback.WaitForResult(); + + // Request the pushed path. + scoped_ptr<HttpNetworkTransaction> trans2( + new HttpNetworkTransaction(helper.session())); + rv = trans2->Start( + &CreateGetPushRequest(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + MessageLoop::current()->RunAllPending(); + + // The data for the pushed path may be coming in more than 1 packet. Compile + // the results into a single string. + + // Read the server push body. + std::string result2; + ReadResult(trans2.get(), data, &result2); + // Read the response body. + std::string result; + ReadResult(trans, data, &result); + + // Verify that we consumed all test data. + EXPECT_TRUE(data->at_read_eof()); + EXPECT_TRUE(data->at_write_eof()); + + // Verify that the received push data is same as the expected push data. + EXPECT_EQ(result2.compare(expected), 0) << "Received data: " + << result2 + << "||||| Expected data: " + << expected; + + // Verify the SYN_REPLY. + // Copy the response info, because trans goes away. + *response = *trans->GetResponseInfo(); + *push_response = *trans2->GetResponseInfo(); + + VerifyStreamsClosed(helper); + } + + static void DeleteSessionCallback(NormalSpdyTransactionHelper* helper, + int result) { + helper->ResetTrans(); + } + + static void StartTransactionCallback( + const scoped_refptr<HttpNetworkSession>& session, + int result) { + scoped_ptr<HttpNetworkTransaction> trans( + new HttpNetworkTransaction(session)); + TestCompletionCallback callback; + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + callback.WaitForResult(); + } + + private: + bool google_get_request_initialized_; + bool google_post_request_initialized_; + bool google_chunked_post_request_initialized_; + HttpRequestInfo google_get_request_; + HttpRequestInfo google_post_request_; + HttpRequestInfo google_chunked_post_request_; + HttpRequestInfo google_get_push_request_; +}; + +//----------------------------------------------------------------------------- +// All tests are run with three different connection types: SPDY after NPN +// negotiation, SPDY without SSL, and SPDY with SSL. +INSTANTIATE_TEST_CASE_P(Spdy, + SpdyNetworkTransactionSpdy21Test, + ::testing::Values(SPDYNOSSL, SPDYSSL, SPDYNPN)); + + +// Verify HttpNetworkTransaction constructor. +TEST_P(SpdyNetworkTransactionSpdy21Test, Constructor) { + SpdySessionDependencies session_deps; + scoped_refptr<HttpNetworkSession> session( + SpdySessionDependencies::SpdyCreateSession(&session_deps)); + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); +} + +TEST_P(SpdyNetworkTransactionSpdy21Test, Get) { + // Construct the request. + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + MockWrite writes[] = { CreateMockWrite(*req) }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*resp), + CreateMockRead(*body), + MockRead(ASYNC, 0, 0) // EOF + }; + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData(1, reads, arraysize(reads), + writes, arraysize(writes))); + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.RunToCompletion(data.get()); + TransactionHelperResult out = helper.output(); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!", out.response_data); +} + +TEST_P(SpdyNetworkTransactionSpdy21Test, GetAtEachPriority) { + for (RequestPriority p = HIGHEST; p < NUM_PRIORITIES; + p = RequestPriority(p+1)) { + // Construct the request. + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, p)); + MockWrite writes[] = { CreateMockWrite(*req) }; + + const int spdy_prio = reinterpret_cast<spdy::SpdySynStreamControlFrame*>( + req.get())->priority(); + // this repeats the RequestPriority-->SpdyPriority mapping from + // SpdyFramer::ConvertRequestPriorityToSpdyPriority to make + // sure it's being done right. + switch(p) { + case HIGHEST: + EXPECT_EQ(0, spdy_prio); + break; + case MEDIUM: + EXPECT_EQ(1, spdy_prio); + break; + case LOW: + case LOWEST: + EXPECT_EQ(2, spdy_prio); + break; + case IDLE: + EXPECT_EQ(3, spdy_prio); + break; + default: + FAIL(); + } + + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*resp), + CreateMockRead(*body), + MockRead(ASYNC, 0, 0) // EOF + }; + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData(1, reads, arraysize(reads), + writes, arraysize(writes))); + HttpRequestInfo http_req = CreateGetRequest(); + http_req.priority = p; + + NormalSpdyTransactionHelper helper(http_req, BoundNetLog(), GetParam()); + helper.RunToCompletion(data.get()); + TransactionHelperResult out = helper.output(); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!", out.response_data); + } +} + +// Start three gets simultaniously; making sure that multiplexed +// streams work properly. + +// This can't use the TransactionHelper method, since it only +// handles a single transaction, and finishes them as soon +// as it launches them. + +// TODO(gavinp): create a working generalized TransactionHelper that +// can allow multiple streams in flight. + +TEST_P(SpdyNetworkTransactionSpdy21Test, ThreeGets) { + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, false)); + scoped_ptr<spdy::SpdyFrame> fbody(ConstructSpdyBodyFrame(1, true)); + + scoped_ptr<spdy::SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 3, LOWEST)); + scoped_ptr<spdy::SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 3)); + scoped_ptr<spdy::SpdyFrame> body2(ConstructSpdyBodyFrame(3, false)); + scoped_ptr<spdy::SpdyFrame> fbody2(ConstructSpdyBodyFrame(3, true)); + + scoped_ptr<spdy::SpdyFrame> req3(ConstructSpdyGet(NULL, 0, false, 5, LOWEST)); + scoped_ptr<spdy::SpdyFrame> resp3(ConstructSpdyGetSynReply(NULL, 0, 5)); + scoped_ptr<spdy::SpdyFrame> body3(ConstructSpdyBodyFrame(5, false)); + scoped_ptr<spdy::SpdyFrame> fbody3(ConstructSpdyBodyFrame(5, true)); + + MockWrite writes[] = { + CreateMockWrite(*req), + CreateMockWrite(*req2), + CreateMockWrite(*req3), + }; + MockRead reads[] = { + CreateMockRead(*resp, 1), + CreateMockRead(*body), + CreateMockRead(*resp2, 4), + CreateMockRead(*body2), + CreateMockRead(*resp3, 7), + CreateMockRead(*body3), + + CreateMockRead(*fbody), + CreateMockRead(*fbody2), + CreateMockRead(*fbody3), + + MockRead(ASYNC, 0, 0), // EOF + }; + scoped_ptr<OrderedSocketData> data( + new OrderedSocketData(reads, arraysize(reads), + writes, arraysize(writes))); + scoped_ptr<OrderedSocketData> data_placeholder( + new OrderedSocketData(NULL, 0, NULL, 0)); + + BoundNetLog log; + TransactionHelperResult out; + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.RunPreTestSetup(); + helper.AddData(data.get()); + // We require placeholder data because three get requests are sent out, so + // there needs to be three sets of SSL connection data. + helper.AddData(data_placeholder.get()); + helper.AddData(data_placeholder.get()); + scoped_ptr<HttpNetworkTransaction> trans1( + new HttpNetworkTransaction(helper.session())); + scoped_ptr<HttpNetworkTransaction> trans2( + new HttpNetworkTransaction(helper.session())); + scoped_ptr<HttpNetworkTransaction> trans3( + new HttpNetworkTransaction(helper.session())); + + TestCompletionCallback callback1; + TestCompletionCallback callback2; + TestCompletionCallback callback3; + + HttpRequestInfo httpreq1 = CreateGetRequest(); + HttpRequestInfo httpreq2 = CreateGetRequest(); + HttpRequestInfo httpreq3 = CreateGetRequest(); + + out.rv = trans1->Start(&httpreq1, callback1.callback(), log); + ASSERT_EQ(ERR_IO_PENDING, out.rv); + out.rv = trans2->Start(&httpreq2, callback2.callback(), log); + ASSERT_EQ(ERR_IO_PENDING, out.rv); + out.rv = trans3->Start(&httpreq3, callback3.callback(), log); + ASSERT_EQ(ERR_IO_PENDING, out.rv); + + out.rv = callback1.WaitForResult(); + ASSERT_EQ(OK, out.rv); + out.rv = callback3.WaitForResult(); + ASSERT_EQ(OK, out.rv); + + const HttpResponseInfo* response1 = trans1->GetResponseInfo(); + EXPECT_TRUE(response1->headers != NULL); + EXPECT_TRUE(response1->was_fetched_via_spdy); + out.status_line = response1->headers->GetStatusLine(); + out.response_info = *response1; + + trans2->GetResponseInfo(); + + out.rv = ReadTransaction(trans1.get(), &out.response_data); + helper.VerifyDataConsumed(); + EXPECT_EQ(OK, out.rv); + + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!hello!", out.response_data); +} + +TEST_P(SpdyNetworkTransactionSpdy21Test, TwoGetsLateBinding) { + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, false)); + scoped_ptr<spdy::SpdyFrame> fbody(ConstructSpdyBodyFrame(1, true)); + + scoped_ptr<spdy::SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 3, LOWEST)); + scoped_ptr<spdy::SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 3)); + scoped_ptr<spdy::SpdyFrame> body2(ConstructSpdyBodyFrame(3, false)); + scoped_ptr<spdy::SpdyFrame> fbody2(ConstructSpdyBodyFrame(3, true)); + + MockWrite writes[] = { + CreateMockWrite(*req), + CreateMockWrite(*req2), + }; + MockRead reads[] = { + CreateMockRead(*resp, 1), + CreateMockRead(*body), + CreateMockRead(*resp2, 4), + CreateMockRead(*body2), + CreateMockRead(*fbody), + CreateMockRead(*fbody2), + MockRead(ASYNC, 0, 0), // EOF + }; + scoped_ptr<OrderedSocketData> data( + new OrderedSocketData(reads, arraysize(reads), + writes, arraysize(writes))); + + MockConnect never_finishing_connect(SYNCHRONOUS, ERR_IO_PENDING); + + scoped_ptr<OrderedSocketData> data_placeholder( + new OrderedSocketData(NULL, 0, NULL, 0)); + data_placeholder->set_connect_data(never_finishing_connect); + + BoundNetLog log; + TransactionHelperResult out; + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.RunPreTestSetup(); + helper.AddData(data.get()); + // We require placeholder data because two get requests are sent out, so + // there needs to be two sets of SSL connection data. + helper.AddData(data_placeholder.get()); + scoped_ptr<HttpNetworkTransaction> trans1( + new HttpNetworkTransaction(helper.session())); + scoped_ptr<HttpNetworkTransaction> trans2( + new HttpNetworkTransaction(helper.session())); + + TestCompletionCallback callback1; + TestCompletionCallback callback2; + + HttpRequestInfo httpreq1 = CreateGetRequest(); + HttpRequestInfo httpreq2 = CreateGetRequest(); + + out.rv = trans1->Start(&httpreq1, callback1.callback(), log); + ASSERT_EQ(ERR_IO_PENDING, out.rv); + out.rv = trans2->Start(&httpreq2, callback2.callback(), log); + ASSERT_EQ(ERR_IO_PENDING, out.rv); + + out.rv = callback1.WaitForResult(); + ASSERT_EQ(OK, out.rv); + out.rv = callback2.WaitForResult(); + ASSERT_EQ(OK, out.rv); + + const HttpResponseInfo* response1 = trans1->GetResponseInfo(); + EXPECT_TRUE(response1->headers != NULL); + EXPECT_TRUE(response1->was_fetched_via_spdy); + out.status_line = response1->headers->GetStatusLine(); + out.response_info = *response1; + out.rv = ReadTransaction(trans1.get(), &out.response_data); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!hello!", out.response_data); + + const HttpResponseInfo* response2 = trans2->GetResponseInfo(); + EXPECT_TRUE(response2->headers != NULL); + EXPECT_TRUE(response2->was_fetched_via_spdy); + out.status_line = response2->headers->GetStatusLine(); + out.response_info = *response2; + out.rv = ReadTransaction(trans2.get(), &out.response_data); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!hello!", out.response_data); + + helper.VerifyDataConsumed(); +} + +TEST_P(SpdyNetworkTransactionSpdy21Test, TwoGetsLateBindingFromPreconnect) { + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, false)); + scoped_ptr<spdy::SpdyFrame> fbody(ConstructSpdyBodyFrame(1, true)); + + scoped_ptr<spdy::SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 3, LOWEST)); + scoped_ptr<spdy::SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 3)); + scoped_ptr<spdy::SpdyFrame> body2(ConstructSpdyBodyFrame(3, false)); + scoped_ptr<spdy::SpdyFrame> fbody2(ConstructSpdyBodyFrame(3, true)); + + MockWrite writes[] = { + CreateMockWrite(*req), + CreateMockWrite(*req2), + }; + MockRead reads[] = { + CreateMockRead(*resp, 1), + CreateMockRead(*body), + CreateMockRead(*resp2, 4), + CreateMockRead(*body2), + CreateMockRead(*fbody), + CreateMockRead(*fbody2), + MockRead(ASYNC, 0, 0), // EOF + }; + scoped_ptr<OrderedSocketData> preconnect_data( + new OrderedSocketData(reads, arraysize(reads), + writes, arraysize(writes))); + + MockConnect never_finishing_connect(ASYNC, ERR_IO_PENDING); + + scoped_ptr<OrderedSocketData> data_placeholder( + new OrderedSocketData(NULL, 0, NULL, 0)); + data_placeholder->set_connect_data(never_finishing_connect); + + BoundNetLog log; + TransactionHelperResult out; + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.RunPreTestSetup(); + helper.AddData(preconnect_data.get()); + // We require placeholder data because 3 connections are attempted (first is + // the preconnect, 2nd and 3rd are the never finished connections. + helper.AddData(data_placeholder.get()); + helper.AddData(data_placeholder.get()); + + scoped_ptr<HttpNetworkTransaction> trans1( + new HttpNetworkTransaction(helper.session())); + scoped_ptr<HttpNetworkTransaction> trans2( + new HttpNetworkTransaction(helper.session())); + + TestCompletionCallback callback1; + TestCompletionCallback callback2; + + HttpRequestInfo httpreq = CreateGetRequest(); + + // Preconnect the first. + SSLConfig preconnect_ssl_config; + helper.session()->ssl_config_service()->GetSSLConfig(&preconnect_ssl_config); + HttpStreamFactory* http_stream_factory = + helper.session()->http_stream_factory(); + if (http_stream_factory->has_next_protos()) { + preconnect_ssl_config.next_protos = http_stream_factory->next_protos(); + } + + http_stream_factory->PreconnectStreams( + 1, httpreq, preconnect_ssl_config, preconnect_ssl_config); + + out.rv = trans1->Start(&httpreq, callback1.callback(), log); + ASSERT_EQ(ERR_IO_PENDING, out.rv); + out.rv = trans2->Start(&httpreq, callback2.callback(), log); + ASSERT_EQ(ERR_IO_PENDING, out.rv); + + out.rv = callback1.WaitForResult(); + ASSERT_EQ(OK, out.rv); + out.rv = callback2.WaitForResult(); + ASSERT_EQ(OK, out.rv); + + const HttpResponseInfo* response1 = trans1->GetResponseInfo(); + EXPECT_TRUE(response1->headers != NULL); + EXPECT_TRUE(response1->was_fetched_via_spdy); + out.status_line = response1->headers->GetStatusLine(); + out.response_info = *response1; + out.rv = ReadTransaction(trans1.get(), &out.response_data); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!hello!", out.response_data); + + const HttpResponseInfo* response2 = trans2->GetResponseInfo(); + EXPECT_TRUE(response2->headers != NULL); + EXPECT_TRUE(response2->was_fetched_via_spdy); + out.status_line = response2->headers->GetStatusLine(); + out.response_info = *response2; + out.rv = ReadTransaction(trans2.get(), &out.response_data); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!hello!", out.response_data); + + helper.VerifyDataConsumed(); +} + +// Similar to ThreeGets above, however this test adds a SETTINGS +// frame. The SETTINGS frame is read during the IO loop waiting on +// the first transaction completion, and sets a maximum concurrent +// stream limit of 1. This means that our IO loop exists after the +// second transaction completes, so we can assert on read_index(). +TEST_P(SpdyNetworkTransactionSpdy21Test, ThreeGetsWithMaxConcurrent) { + // Construct the request. + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, false)); + scoped_ptr<spdy::SpdyFrame> fbody(ConstructSpdyBodyFrame(1, true)); + + scoped_ptr<spdy::SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 3, LOWEST)); + scoped_ptr<spdy::SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 3)); + scoped_ptr<spdy::SpdyFrame> body2(ConstructSpdyBodyFrame(3, false)); + scoped_ptr<spdy::SpdyFrame> fbody2(ConstructSpdyBodyFrame(3, true)); + + scoped_ptr<spdy::SpdyFrame> req3(ConstructSpdyGet(NULL, 0, false, 5, LOWEST)); + scoped_ptr<spdy::SpdyFrame> resp3(ConstructSpdyGetSynReply(NULL, 0, 5)); + scoped_ptr<spdy::SpdyFrame> body3(ConstructSpdyBodyFrame(5, false)); + scoped_ptr<spdy::SpdyFrame> fbody3(ConstructSpdyBodyFrame(5, true)); + + spdy::SpdySettings settings; + spdy::SettingsFlagsAndId id(0); + id.set_id(spdy::SETTINGS_MAX_CONCURRENT_STREAMS); + const size_t max_concurrent_streams = 1; + + settings.push_back(spdy::SpdySetting(id, max_concurrent_streams)); + scoped_ptr<spdy::SpdyFrame> settings_frame(ConstructSpdySettings(settings)); + + MockWrite writes[] = { + CreateMockWrite(*req), + CreateMockWrite(*req2), + CreateMockWrite(*req3), + }; + + MockRead reads[] = { + CreateMockRead(*settings_frame, 1), + CreateMockRead(*resp), + CreateMockRead(*body), + CreateMockRead(*fbody), + CreateMockRead(*resp2, 7), + CreateMockRead(*body2), + CreateMockRead(*fbody2), + CreateMockRead(*resp3, 12), + CreateMockRead(*body3), + CreateMockRead(*fbody3), + + MockRead(ASYNC, 0, 0), // EOF + }; + + scoped_ptr<OrderedSocketData> data( + new OrderedSocketData(reads, arraysize(reads), + writes, arraysize(writes))); + scoped_ptr<OrderedSocketData> data_placeholder( + new OrderedSocketData(NULL, 0, NULL, 0)); + + BoundNetLog log; + TransactionHelperResult out; + { + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.RunPreTestSetup(); + helper.AddData(data.get()); + // We require placeholder data because three get requests are sent out, so + // there needs to be three sets of SSL connection data. + helper.AddData(data_placeholder.get()); + helper.AddData(data_placeholder.get()); + scoped_ptr<HttpNetworkTransaction> trans1( + new HttpNetworkTransaction(helper.session())); + scoped_ptr<HttpNetworkTransaction> trans2( + new HttpNetworkTransaction(helper.session())); + scoped_ptr<HttpNetworkTransaction> trans3( + new HttpNetworkTransaction(helper.session())); + + TestCompletionCallback callback1; + TestCompletionCallback callback2; + TestCompletionCallback callback3; + + HttpRequestInfo httpreq1 = CreateGetRequest(); + HttpRequestInfo httpreq2 = CreateGetRequest(); + HttpRequestInfo httpreq3 = CreateGetRequest(); + + out.rv = trans1->Start(&httpreq1, callback1.callback(), log); + ASSERT_EQ(out.rv, ERR_IO_PENDING); + // run transaction 1 through quickly to force a read of our SETTINGS + // frame + out.rv = callback1.WaitForResult(); + ASSERT_EQ(OK, out.rv); + + out.rv = trans2->Start(&httpreq2, callback2.callback(), log); + ASSERT_EQ(out.rv, ERR_IO_PENDING); + out.rv = trans3->Start(&httpreq3, callback3.callback(), log); + ASSERT_EQ(out.rv, ERR_IO_PENDING); + out.rv = callback2.WaitForResult(); + ASSERT_EQ(OK, out.rv); + EXPECT_EQ(7U, data->read_index()); // i.e. the third trans was queued + + out.rv = callback3.WaitForResult(); + ASSERT_EQ(OK, out.rv); + + const HttpResponseInfo* response1 = trans1->GetResponseInfo(); + ASSERT_TRUE(response1 != NULL); + EXPECT_TRUE(response1->headers != NULL); + EXPECT_TRUE(response1->was_fetched_via_spdy); + out.status_line = response1->headers->GetStatusLine(); + out.response_info = *response1; + out.rv = ReadTransaction(trans1.get(), &out.response_data); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!hello!", out.response_data); + + const HttpResponseInfo* response2 = trans2->GetResponseInfo(); + out.status_line = response2->headers->GetStatusLine(); + out.response_info = *response2; + out.rv = ReadTransaction(trans2.get(), &out.response_data); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!hello!", out.response_data); + + const HttpResponseInfo* response3 = trans3->GetResponseInfo(); + out.status_line = response3->headers->GetStatusLine(); + out.response_info = *response3; + out.rv = ReadTransaction(trans3.get(), &out.response_data); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!hello!", out.response_data); + + helper.VerifyDataConsumed(); + } + EXPECT_EQ(OK, out.rv); +} + +// Similar to ThreeGetsWithMaxConcurrent above, however this test adds +// a fourth transaction. The third and fourth transactions have +// different data ("hello!" vs "hello!hello!") and because of the +// user specified priority, we expect to see them inverted in +// the response from the server. +TEST_P(SpdyNetworkTransactionSpdy21Test, FourGetsWithMaxConcurrentPriority) { + // Construct the request. + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, false)); + scoped_ptr<spdy::SpdyFrame> fbody(ConstructSpdyBodyFrame(1, true)); + + scoped_ptr<spdy::SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 3, LOWEST)); + scoped_ptr<spdy::SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 3)); + scoped_ptr<spdy::SpdyFrame> body2(ConstructSpdyBodyFrame(3, false)); + scoped_ptr<spdy::SpdyFrame> fbody2(ConstructSpdyBodyFrame(3, true)); + + scoped_ptr<spdy::SpdyFrame> req4( + ConstructSpdyGet(NULL, 0, false, 5, HIGHEST)); + scoped_ptr<spdy::SpdyFrame> resp4(ConstructSpdyGetSynReply(NULL, 0, 5)); + scoped_ptr<spdy::SpdyFrame> fbody4(ConstructSpdyBodyFrame(5, true)); + + scoped_ptr<spdy::SpdyFrame> req3(ConstructSpdyGet(NULL, 0, false, 7, LOWEST)); + scoped_ptr<spdy::SpdyFrame> resp3(ConstructSpdyGetSynReply(NULL, 0, 7)); + scoped_ptr<spdy::SpdyFrame> body3(ConstructSpdyBodyFrame(7, false)); + scoped_ptr<spdy::SpdyFrame> fbody3(ConstructSpdyBodyFrame(7, true)); + + + spdy::SpdySettings settings; + spdy::SettingsFlagsAndId id(0); + id.set_id(spdy::SETTINGS_MAX_CONCURRENT_STREAMS); + const size_t max_concurrent_streams = 1; + + settings.push_back(spdy::SpdySetting(id, max_concurrent_streams)); + scoped_ptr<spdy::SpdyFrame> settings_frame(ConstructSpdySettings(settings)); + + MockWrite writes[] = { CreateMockWrite(*req), + CreateMockWrite(*req2), + CreateMockWrite(*req4), + CreateMockWrite(*req3), + }; + MockRead reads[] = { + CreateMockRead(*settings_frame, 1), + CreateMockRead(*resp), + CreateMockRead(*body), + CreateMockRead(*fbody), + CreateMockRead(*resp2, 7), + CreateMockRead(*body2), + CreateMockRead(*fbody2), + CreateMockRead(*resp4, 13), + CreateMockRead(*fbody4), + CreateMockRead(*resp3, 16), + CreateMockRead(*body3), + CreateMockRead(*fbody3), + + MockRead(ASYNC, 0, 0), // EOF + }; + + scoped_ptr<OrderedSocketData> data( + new OrderedSocketData(reads, arraysize(reads), + writes, arraysize(writes))); + scoped_ptr<OrderedSocketData> data_placeholder( + new OrderedSocketData(NULL, 0, NULL, 0)); + + BoundNetLog log; + TransactionHelperResult out; + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.RunPreTestSetup(); + helper.AddData(data.get()); + // We require placeholder data because four get requests are sent out, so + // there needs to be four sets of SSL connection data. + helper.AddData(data_placeholder.get()); + helper.AddData(data_placeholder.get()); + helper.AddData(data_placeholder.get()); + scoped_ptr<HttpNetworkTransaction> trans1( + new HttpNetworkTransaction(helper.session())); + scoped_ptr<HttpNetworkTransaction> trans2( + new HttpNetworkTransaction(helper.session())); + scoped_ptr<HttpNetworkTransaction> trans3( + new HttpNetworkTransaction(helper.session())); + scoped_ptr<HttpNetworkTransaction> trans4( + new HttpNetworkTransaction(helper.session())); + + TestCompletionCallback callback1; + TestCompletionCallback callback2; + TestCompletionCallback callback3; + TestCompletionCallback callback4; + + HttpRequestInfo httpreq1 = CreateGetRequest(); + HttpRequestInfo httpreq2 = CreateGetRequest(); + HttpRequestInfo httpreq3 = CreateGetRequest(); + HttpRequestInfo httpreq4 = CreateGetRequest(); + httpreq4.priority = HIGHEST; + + out.rv = trans1->Start(&httpreq1, callback1.callback(), log); + ASSERT_EQ(ERR_IO_PENDING, out.rv); + // run transaction 1 through quickly to force a read of our SETTINGS + // frame + out.rv = callback1.WaitForResult(); + ASSERT_EQ(OK, out.rv); + + out.rv = trans2->Start(&httpreq2, callback2.callback(), log); + ASSERT_EQ(ERR_IO_PENDING, out.rv); + out.rv = trans3->Start(&httpreq3, callback3.callback(), log); + ASSERT_EQ(ERR_IO_PENDING, out.rv); + out.rv = trans4->Start(&httpreq4, callback4.callback(), log); + ASSERT_EQ(ERR_IO_PENDING, out.rv); + + out.rv = callback2.WaitForResult(); + ASSERT_EQ(OK, out.rv); + EXPECT_EQ(data->read_index(), 7U); // i.e. the third & fourth trans queued + + out.rv = callback3.WaitForResult(); + ASSERT_EQ(OK, out.rv); + + const HttpResponseInfo* response1 = trans1->GetResponseInfo(); + EXPECT_TRUE(response1->headers != NULL); + EXPECT_TRUE(response1->was_fetched_via_spdy); + out.status_line = response1->headers->GetStatusLine(); + out.response_info = *response1; + out.rv = ReadTransaction(trans1.get(), &out.response_data); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!hello!", out.response_data); + + const HttpResponseInfo* response2 = trans2->GetResponseInfo(); + out.status_line = response2->headers->GetStatusLine(); + out.response_info = *response2; + out.rv = ReadTransaction(trans2.get(), &out.response_data); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!hello!", out.response_data); + + // notice: response3 gets two hellos, response4 gets one + // hello, so we know dequeuing priority was respected. + const HttpResponseInfo* response3 = trans3->GetResponseInfo(); + out.status_line = response3->headers->GetStatusLine(); + out.response_info = *response3; + out.rv = ReadTransaction(trans3.get(), &out.response_data); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!hello!", out.response_data); + + out.rv = callback4.WaitForResult(); + EXPECT_EQ(OK, out.rv); + const HttpResponseInfo* response4 = trans4->GetResponseInfo(); + out.status_line = response4->headers->GetStatusLine(); + out.response_info = *response4; + out.rv = ReadTransaction(trans4.get(), &out.response_data); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!", out.response_data); + helper.VerifyDataConsumed(); + EXPECT_EQ(OK, out.rv); +} + +// Similar to ThreeGetsMaxConcurrrent above, however, this test +// deletes a session in the middle of the transaction to insure +// that we properly remove pendingcreatestream objects from +// the spdy_session +TEST_P(SpdyNetworkTransactionSpdy21Test, ThreeGetsWithMaxConcurrentDelete) { + // Construct the request. + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, false)); + scoped_ptr<spdy::SpdyFrame> fbody(ConstructSpdyBodyFrame(1, true)); + + scoped_ptr<spdy::SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 3, LOWEST)); + scoped_ptr<spdy::SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 3)); + scoped_ptr<spdy::SpdyFrame> body2(ConstructSpdyBodyFrame(3, false)); + scoped_ptr<spdy::SpdyFrame> fbody2(ConstructSpdyBodyFrame(3, true)); + + spdy::SpdySettings settings; + spdy::SettingsFlagsAndId id(0); + id.set_id(spdy::SETTINGS_MAX_CONCURRENT_STREAMS); + const size_t max_concurrent_streams = 1; + + settings.push_back(spdy::SpdySetting(id, max_concurrent_streams)); + scoped_ptr<spdy::SpdyFrame> settings_frame(ConstructSpdySettings(settings)); + + MockWrite writes[] = { CreateMockWrite(*req), + CreateMockWrite(*req2), + }; + MockRead reads[] = { + CreateMockRead(*settings_frame, 1), + CreateMockRead(*resp), + CreateMockRead(*body), + CreateMockRead(*fbody), + CreateMockRead(*resp2, 7), + CreateMockRead(*body2), + CreateMockRead(*fbody2), + MockRead(ASYNC, 0, 0), // EOF + }; + + scoped_ptr<OrderedSocketData> data( + new OrderedSocketData(reads, arraysize(reads), + writes, arraysize(writes))); + scoped_ptr<OrderedSocketData> data_placeholder( + new OrderedSocketData(NULL, 0, NULL, 0)); + + BoundNetLog log; + TransactionHelperResult out; + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.RunPreTestSetup(); + helper.AddData(data.get()); + // We require placeholder data because three get requests are sent out, so + // there needs to be three sets of SSL connection data. + helper.AddData(data_placeholder.get()); + helper.AddData(data_placeholder.get()); + scoped_ptr<HttpNetworkTransaction> trans1( + new HttpNetworkTransaction(helper.session())); + scoped_ptr<HttpNetworkTransaction> trans2( + new HttpNetworkTransaction(helper.session())); + scoped_ptr<HttpNetworkTransaction> trans3( + new HttpNetworkTransaction(helper.session())); + + TestCompletionCallback callback1; + TestCompletionCallback callback2; + TestCompletionCallback callback3; + + HttpRequestInfo httpreq1 = CreateGetRequest(); + HttpRequestInfo httpreq2 = CreateGetRequest(); + HttpRequestInfo httpreq3 = CreateGetRequest(); + + out.rv = trans1->Start(&httpreq1, callback1.callback(), log); + ASSERT_EQ(out.rv, ERR_IO_PENDING); + // run transaction 1 through quickly to force a read of our SETTINGS + // frame + out.rv = callback1.WaitForResult(); + ASSERT_EQ(OK, out.rv); + + out.rv = trans2->Start(&httpreq2, callback2.callback(), log); + ASSERT_EQ(out.rv, ERR_IO_PENDING); + out.rv = trans3->Start(&httpreq3, callback3.callback(), log); + delete trans3.release(); + ASSERT_EQ(out.rv, ERR_IO_PENDING); + out.rv = callback2.WaitForResult(); + ASSERT_EQ(OK, out.rv); + + EXPECT_EQ(8U, data->read_index()); + + const HttpResponseInfo* response1 = trans1->GetResponseInfo(); + ASSERT_TRUE(response1 != NULL); + EXPECT_TRUE(response1->headers != NULL); + EXPECT_TRUE(response1->was_fetched_via_spdy); + out.status_line = response1->headers->GetStatusLine(); + out.response_info = *response1; + out.rv = ReadTransaction(trans1.get(), &out.response_data); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!hello!", out.response_data); + + const HttpResponseInfo* response2 = trans2->GetResponseInfo(); + ASSERT_TRUE(response2 != NULL); + out.status_line = response2->headers->GetStatusLine(); + out.response_info = *response2; + out.rv = ReadTransaction(trans2.get(), &out.response_data); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!hello!", out.response_data); + helper.VerifyDataConsumed(); + EXPECT_EQ(OK, out.rv); +} + +namespace { + +// The KillerCallback will delete the transaction on error as part of the +// callback. +class KillerCallback : public TestCompletionCallbackBase { + public: + explicit KillerCallback(HttpNetworkTransaction* transaction) + : transaction_(transaction), + ALLOW_THIS_IN_INITIALIZER_LIST(callback_( + base::Bind(&KillerCallback::OnComplete, base::Unretained(this)))) { + } + + virtual ~KillerCallback() {} + + const CompletionCallback& callback() const { return callback_; } + + private: + void OnComplete(int result) { + if (result < 0) + delete transaction_; + + SetResult(result); + } + + HttpNetworkTransaction* transaction_; + CompletionCallback callback_; +}; + +} // namespace + +// Similar to ThreeGetsMaxConcurrrentDelete above, however, this test +// closes the socket while we have a pending transaction waiting for +// a pending stream creation. http://crbug.com/52901 +TEST_P(SpdyNetworkTransactionSpdy21Test, + ThreeGetsWithMaxConcurrentSocketClose) { + // Construct the request. + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, false)); + scoped_ptr<spdy::SpdyFrame> fin_body(ConstructSpdyBodyFrame(1, true)); + + scoped_ptr<spdy::SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 3, LOWEST)); + scoped_ptr<spdy::SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 3)); + + spdy::SpdySettings settings; + spdy::SettingsFlagsAndId id(0); + id.set_id(spdy::SETTINGS_MAX_CONCURRENT_STREAMS); + const size_t max_concurrent_streams = 1; + + settings.push_back(spdy::SpdySetting(id, max_concurrent_streams)); + scoped_ptr<spdy::SpdyFrame> settings_frame(ConstructSpdySettings(settings)); + + MockWrite writes[] = { CreateMockWrite(*req), + CreateMockWrite(*req2), + }; + MockRead reads[] = { + CreateMockRead(*settings_frame, 1), + CreateMockRead(*resp), + CreateMockRead(*body), + CreateMockRead(*fin_body), + CreateMockRead(*resp2, 7), + MockRead(ASYNC, ERR_CONNECTION_RESET, 0), // Abort! + }; + + scoped_ptr<OrderedSocketData> data( + new OrderedSocketData(reads, arraysize(reads), + writes, arraysize(writes))); + scoped_ptr<OrderedSocketData> data_placeholder( + new OrderedSocketData(NULL, 0, NULL, 0)); + + BoundNetLog log; + TransactionHelperResult out; + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.RunPreTestSetup(); + helper.AddData(data.get()); + // We require placeholder data because three get requests are sent out, so + // there needs to be three sets of SSL connection data. + helper.AddData(data_placeholder.get()); + helper.AddData(data_placeholder.get()); + HttpNetworkTransaction trans1(helper.session()); + HttpNetworkTransaction trans2(helper.session()); + HttpNetworkTransaction* trans3(new HttpNetworkTransaction(helper.session())); + + TestCompletionCallback callback1; + TestCompletionCallback callback2; + KillerCallback callback3(trans3); + + HttpRequestInfo httpreq1 = CreateGetRequest(); + HttpRequestInfo httpreq2 = CreateGetRequest(); + HttpRequestInfo httpreq3 = CreateGetRequest(); + + out.rv = trans1.Start(&httpreq1, callback1.callback(), log); + ASSERT_EQ(out.rv, ERR_IO_PENDING); + // run transaction 1 through quickly to force a read of our SETTINGS + // frame + out.rv = callback1.WaitForResult(); + ASSERT_EQ(OK, out.rv); + + out.rv = trans2.Start(&httpreq2, callback2.callback(), log); + ASSERT_EQ(out.rv, ERR_IO_PENDING); + out.rv = trans3->Start(&httpreq3, callback3.callback(), log); + ASSERT_EQ(out.rv, ERR_IO_PENDING); + out.rv = callback3.WaitForResult(); + ASSERT_EQ(ERR_ABORTED, out.rv); + + EXPECT_EQ(6U, data->read_index()); + + const HttpResponseInfo* response1 = trans1.GetResponseInfo(); + ASSERT_TRUE(response1 != NULL); + EXPECT_TRUE(response1->headers != NULL); + EXPECT_TRUE(response1->was_fetched_via_spdy); + out.status_line = response1->headers->GetStatusLine(); + out.response_info = *response1; + out.rv = ReadTransaction(&trans1, &out.response_data); + EXPECT_EQ(OK, out.rv); + + const HttpResponseInfo* response2 = trans2.GetResponseInfo(); + ASSERT_TRUE(response2 != NULL); + out.status_line = response2->headers->GetStatusLine(); + out.response_info = *response2; + out.rv = ReadTransaction(&trans2, &out.response_data); + EXPECT_EQ(ERR_CONNECTION_RESET, out.rv); + + helper.VerifyDataConsumed(); +} + +// Test that a simple PUT request works. +TEST_P(SpdyNetworkTransactionSpdy21Test, Put) { + // Setup the request + HttpRequestInfo request; + request.method = "PUT"; + request.url = GURL("http://www.google.com/"); + + const SpdyHeaderInfo kSynStartHeader = { + spdy::SYN_STREAM, // Kind = Syn + 1, // Stream ID + 0, // Associated stream ID + net::ConvertRequestPriorityToSpdyPriority(LOWEST), // Priority + spdy::CONTROL_FLAG_FIN, // Control Flags + false, // Compressed + spdy::INVALID, // Status + NULL, // Data + 0, // Length + spdy::DATA_FLAG_NONE // Data Flags + }; + const char* const kPutHeaders[] = { + "method", "PUT", + "url", "/", + "host", "www.google.com", + "scheme", "http", + "version", "HTTP/1.1", + "content-length", "0" + }; + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyPacket(kSynStartHeader, NULL, 0, + kPutHeaders, arraysize(kPutHeaders) / 2)); + MockWrite writes[] = { + CreateMockWrite(*req) + }; + + scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true)); + const SpdyHeaderInfo kSynReplyHeader = { + spdy::SYN_REPLY, // Kind = SynReply + 1, // Stream ID + 0, // Associated stream ID + net::ConvertRequestPriorityToSpdyPriority(LOWEST), // Priority + spdy::CONTROL_FLAG_NONE, // Control Flags + false, // Compressed + spdy::INVALID, // Status + NULL, // Data + 0, // Length + spdy::DATA_FLAG_NONE // Data Flags + }; + static const char* const kStandardGetHeaders[] = { + "status", "200", + "version", "HTTP/1.1" + "content-length", "1234" + }; + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyPacket(kSynReplyHeader, + NULL, 0, kStandardGetHeaders, arraysize(kStandardGetHeaders) / 2)); + MockRead reads[] = { + CreateMockRead(*resp), + CreateMockRead(*body), + MockRead(ASYNC, 0, 0) // EOF + }; + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData(1, reads, arraysize(reads), + writes, arraysize(writes))); + NormalSpdyTransactionHelper helper(request, + BoundNetLog(), GetParam()); + helper.RunToCompletion(data.get()); + TransactionHelperResult out = helper.output(); + + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); +} + +// Test that a simple HEAD request works. +TEST_P(SpdyNetworkTransactionSpdy21Test, Head) { + // Setup the request + HttpRequestInfo request; + request.method = "HEAD"; + request.url = GURL("http://www.google.com/"); + + const SpdyHeaderInfo kSynStartHeader = { + spdy::SYN_STREAM, // Kind = Syn + 1, // Stream ID + 0, // Associated stream ID + net::ConvertRequestPriorityToSpdyPriority(LOWEST), // Priority + spdy::CONTROL_FLAG_FIN, // Control Flags + false, // Compressed + spdy::INVALID, // Status + NULL, // Data + 0, // Length + spdy::DATA_FLAG_NONE // Data Flags + }; + const char* const kHeadHeaders[] = { + "method", "HEAD", + "url", "/", + "host", "www.google.com", + "scheme", "http", + "version", "HTTP/1.1", + "content-length", "0" + }; + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyPacket(kSynStartHeader, NULL, 0, + kHeadHeaders, arraysize(kHeadHeaders) / 2)); + MockWrite writes[] = { + CreateMockWrite(*req) + }; + + scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true)); + const SpdyHeaderInfo kSynReplyHeader = { + spdy::SYN_REPLY, // Kind = SynReply + 1, // Stream ID + 0, // Associated stream ID + net::ConvertRequestPriorityToSpdyPriority(LOWEST), // Priority + spdy::CONTROL_FLAG_NONE, // Control Flags + false, // Compressed + spdy::INVALID, // Status + NULL, // Data + 0, // Length + spdy::DATA_FLAG_NONE // Data Flags + }; + static const char* const kStandardGetHeaders[] = { + "status", "200", + "version", "HTTP/1.1" + "content-length", "1234" + }; + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyPacket(kSynReplyHeader, + NULL, 0, kStandardGetHeaders, arraysize(kStandardGetHeaders) / 2)); + MockRead reads[] = { + CreateMockRead(*resp), + CreateMockRead(*body), + MockRead(ASYNC, 0, 0) // EOF + }; + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData(1, reads, arraysize(reads), + writes, arraysize(writes))); + NormalSpdyTransactionHelper helper(request, + BoundNetLog(), GetParam()); + helper.RunToCompletion(data.get()); + TransactionHelperResult out = helper.output(); + + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); +} + +// Test that a simple POST works. +TEST_P(SpdyNetworkTransactionSpdy21Test, Post) { + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyPost(kUploadDataSize, NULL, 0)); + scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true)); + MockWrite writes[] = { + CreateMockWrite(*req), + CreateMockWrite(*body), // POST upload frame + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyPostSynReply(NULL, 0)); + MockRead reads[] = { + CreateMockRead(*resp), + CreateMockRead(*body), + MockRead(ASYNC, 0, 0) // EOF + }; + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData(2, reads, arraysize(reads), + writes, arraysize(writes))); + NormalSpdyTransactionHelper helper(CreatePostRequest(), + BoundNetLog(), GetParam()); + helper.RunToCompletion(data.get()); + TransactionHelperResult out = helper.output(); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!", out.response_data); +} + +// Test that a chunked POST works. +TEST_P(SpdyNetworkTransactionSpdy21Test, ChunkedPost) { + UploadDataStream::set_merge_chunks(false); + scoped_ptr<spdy::SpdyFrame> req(ConstructChunkedSpdyPost(NULL, 0)); + scoped_ptr<spdy::SpdyFrame> chunk1(ConstructSpdyBodyFrame(1, false)); + scoped_ptr<spdy::SpdyFrame> chunk2(ConstructSpdyBodyFrame(1, true)); + MockWrite writes[] = { + CreateMockWrite(*req), + CreateMockWrite(*chunk1), + CreateMockWrite(*chunk2), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyPostSynReply(NULL, 0)); + MockRead reads[] = { + CreateMockRead(*resp), + CreateMockRead(*chunk1), + CreateMockRead(*chunk2), + MockRead(ASYNC, 0, 0) // EOF + }; + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData(2, reads, arraysize(reads), + writes, arraysize(writes))); + NormalSpdyTransactionHelper helper(CreateChunkedPostRequest(), + BoundNetLog(), GetParam()); + helper.RunToCompletion(data.get()); + TransactionHelperResult out = helper.output(); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!hello!", out.response_data); +} + +// Test that a POST without any post data works. +TEST_P(SpdyNetworkTransactionSpdy21Test, NullPost) { + // Setup the request + HttpRequestInfo request; + request.method = "POST"; + request.url = GURL("http://www.google.com/"); + // Create an empty UploadData. + request.upload_data = NULL; + + // When request.upload_data is NULL for post, content-length is + // expected to be 0. + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyPost(0, NULL, 0)); + // Set the FIN bit since there will be no body. + req->set_flags(spdy::CONTROL_FLAG_FIN); + MockWrite writes[] = { + CreateMockWrite(*req), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyPostSynReply(NULL, 0)); + scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*resp), + CreateMockRead(*body), + MockRead(ASYNC, 0, 0) // EOF + }; + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData(1, reads, arraysize(reads), + writes, arraysize(writes))); + + NormalSpdyTransactionHelper helper(request, + BoundNetLog(), GetParam()); + helper.RunToCompletion(data.get()); + TransactionHelperResult out = helper.output(); + 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_P(SpdyNetworkTransactionSpdy21Test, EmptyPost) { + // Setup the request + HttpRequestInfo request; + request.method = "POST"; + request.url = GURL("http://www.google.com/"); + // Create an empty UploadData. + request.upload_data = new UploadData(); + + // Http POST Content-Length is using UploadDataStream::size(). + // It is the same as request.upload_data->GetContentLengthSync(). + scoped_ptr<UploadDataStream> stream( + new UploadDataStream(request.upload_data)); + ASSERT_EQ(OK, stream->Init()); + ASSERT_EQ(request.upload_data->GetContentLengthSync(), + stream->size()); + + scoped_ptr<spdy::SpdyFrame> + req(ConstructSpdyPost( + request.upload_data->GetContentLengthSync(), NULL, 0)); + // Set the FIN bit since there will be no body. + req->set_flags(spdy::CONTROL_FLAG_FIN); + MockWrite writes[] = { + CreateMockWrite(*req), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyPostSynReply(NULL, 0)); + scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*resp), + CreateMockRead(*body), + MockRead(ASYNC, 0, 0) // EOF + }; + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData(1, reads, arraysize(reads), + writes, arraysize(writes))); + + NormalSpdyTransactionHelper helper(request, + BoundNetLog(), GetParam()); + helper.RunToCompletion(data.get()); + TransactionHelperResult out = helper.output(); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!", out.response_data); +} + +// While we're doing a post, the server sends back a SYN_REPLY. +TEST_P(SpdyNetworkTransactionSpdy21Test, PostWithEarlySynReply) { + static const char upload[] = { "hello!" }; + + // 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)); + + // Http POST Content-Length is using UploadDataStream::size(). + // It is the same as request.upload_data->GetContentLengthSync(). + scoped_ptr<UploadDataStream> stream( + new UploadDataStream(request.upload_data)); + ASSERT_EQ(OK, stream->Init()); + ASSERT_EQ(request.upload_data->GetContentLengthSync(), + stream->size()); + scoped_ptr<spdy::SpdyFrame> stream_reply(ConstructSpdyPostSynReply(NULL, 0)); + scoped_ptr<spdy::SpdyFrame> stream_body(ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*stream_reply, 2), + CreateMockRead(*stream_body, 3), + MockRead(SYNCHRONOUS, 0, 0) // EOF + }; + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData(0, reads, arraysize(reads), NULL, 0)); + NormalSpdyTransactionHelper helper(request, + BoundNetLog(), GetParam()); + helper.RunPreTestSetup(); + helper.AddData(data.get()); + helper.RunDefaultTest(); + helper.VerifyDataConsumed(); + + TransactionHelperResult out = helper.output(); + EXPECT_EQ(ERR_SYN_REPLY_NOT_RECEIVED, out.rv); +} + +// The client upon cancellation tries to send a RST_STREAM frame. The mock +// socket causes the TCP write to return zero. This test checks that the client +// tries to queue up the RST_STREAM frame again. +TEST_P(SpdyNetworkTransactionSpdy21Test, SocketWriteReturnsZero) { + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> rst( + ConstructSpdyRstStream(1, spdy::CANCEL)); + MockWrite writes[] = { + CreateMockWrite(*req.get(), 0, SYNCHRONOUS), + MockWrite(SYNCHRONOUS, 0, 0, 2), + CreateMockWrite(*rst.get(), 3, SYNCHRONOUS), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + MockRead reads[] = { + CreateMockRead(*resp.get(), 1, ASYNC), + MockRead(ASYNC, 0, 0, 4) // EOF + }; + + scoped_refptr<DeterministicSocketData> data( + new DeterministicSocketData(reads, arraysize(reads), + writes, arraysize(writes))); + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.SetDeterministic(); + helper.RunPreTestSetup(); + helper.AddDeterministicData(data.get()); + HttpNetworkTransaction* trans = helper.trans(); + + TestCompletionCallback callback; + int rv = trans->Start( + &CreateGetRequest(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + data->SetStop(2); + data->Run(); + helper.ResetTrans(); + data->SetStop(20); + data->Run(); + + helper.VerifyDataConsumed(); +} + +// Test that the transaction doesn't crash when we don't have a reply. +TEST_P(SpdyNetworkTransactionSpdy21Test, ResponseWithoutSynReply) { + scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*body), + MockRead(ASYNC, 0, 0) // EOF + }; + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData(1, reads, arraysize(reads), NULL, 0)); + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.RunToCompletion(data.get()); + TransactionHelperResult out = helper.output(); + EXPECT_EQ(ERR_SYN_REPLY_NOT_RECEIVED, out.rv); +} + +// Test that the transaction doesn't crash when we get two replies on the same +// stream ID. See http://crbug.com/45639. +TEST_P(SpdyNetworkTransactionSpdy21Test, ResponseWithTwoSynReplies) { + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + MockWrite writes[] = { CreateMockWrite(*req) }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*resp), + CreateMockRead(*resp), + CreateMockRead(*body), + MockRead(ASYNC, 0, 0) // EOF + }; + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData(1, reads, arraysize(reads), + writes, arraysize(writes))); + + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.RunPreTestSetup(); + helper.AddData(data.get()); + + HttpNetworkTransaction* trans = helper.trans(); + + TestCompletionCallback callback; + int rv = trans->Start(&helper.request(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(response->headers != NULL); + EXPECT_TRUE(response->was_fetched_via_spdy); + std::string response_data; + rv = ReadTransaction(trans, &response_data); + EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, rv); + + helper.VerifyDataConsumed(); +} + +// Test that sent data frames and received WINDOW_UPDATE frames change +// the send_window_size_ correctly. + +// WINDOW_UPDATE is different than most other frames in that it can arrive +// while the client is still sending the request body. In order to enforce +// this scenario, we feed a couple of dummy frames and give a delay of 0 to +// socket data provider, so that initial read that is done as soon as the +// stream is created, succeeds and schedules another read. This way reads +// and writes are interleaved; after doing a full frame write, SpdyStream +// will break out of DoLoop and will read and process a WINDOW_UPDATE. +// Once our WINDOW_UPDATE is read, we cannot send SYN_REPLY right away +// since request has not been completely written, therefore we feed +// enough number of WINDOW_UPDATEs to finish the first read and cause a +// write, leading to a complete write of request body; after that we send +// a reply with a body, to cause a graceful shutdown. + +// TODO(agayev): develop a socket data provider where both, reads and +// writes are ordered so that writing tests like these are easy and rewrite +// all these tests using it. Right now we are working around the +// limitations as described above and it's not deterministic, tests may +// fail under specific circumstances. +TEST_P(SpdyNetworkTransactionSpdy21Test, WindowUpdateReceived) { + SpdySession::set_use_flow_control(SpdySession::kEnableFlowControl); + + static int kFrameCount = 2; + scoped_ptr<std::string> content( + new std::string(kMaxSpdyFrameChunkSize, 'a')); + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyPost( + kMaxSpdyFrameChunkSize * kFrameCount, NULL, 0)); + scoped_ptr<spdy::SpdyFrame> body( + ConstructSpdyBodyFrame(1, content->c_str(), content->size(), false)); + scoped_ptr<spdy::SpdyFrame> body_end( + ConstructSpdyBodyFrame(1, content->c_str(), content->size(), true)); + + MockWrite writes[] = { + CreateMockWrite(*req), + CreateMockWrite(*body), + CreateMockWrite(*body_end), + }; + + static const int32 kDeltaWindowSize = 0xff; + static const int kDeltaCount = 4; + scoped_ptr<spdy::SpdyFrame> window_update( + ConstructSpdyWindowUpdate(1, kDeltaWindowSize)); + scoped_ptr<spdy::SpdyFrame> window_update_dummy( + ConstructSpdyWindowUpdate(2, kDeltaWindowSize)); + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyPostSynReply(NULL, 0)); + MockRead reads[] = { + CreateMockRead(*window_update_dummy), + CreateMockRead(*window_update_dummy), + CreateMockRead(*window_update_dummy), + CreateMockRead(*window_update), // Four updates, therefore window + CreateMockRead(*window_update), // size should increase by + CreateMockRead(*window_update), // kDeltaWindowSize * 4 + CreateMockRead(*window_update), + CreateMockRead(*resp), + CreateMockRead(*body_end), + MockRead(ASYNC, 0, 0) // EOF + }; + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData(0, reads, arraysize(reads), + writes, arraysize(writes))); + + // Setup the request + HttpRequestInfo request; + request.method = "POST"; + request.url = GURL(kDefaultURL); + request.upload_data = new UploadData(); + for (int i = 0; i < kFrameCount; ++i) + request.upload_data->AppendBytes(content->c_str(), content->size()); + + NormalSpdyTransactionHelper helper(request, BoundNetLog(), GetParam()); + helper.AddData(data.get()); + helper.RunPreTestSetup(); + + HttpNetworkTransaction* trans = helper.trans(); + + TestCompletionCallback callback; + int rv = trans->Start(&helper.request(), callback.callback(), BoundNetLog()); + + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + SpdyHttpStream* stream = static_cast<SpdyHttpStream*>(trans->stream_.get()); + ASSERT_TRUE(stream != NULL); + ASSERT_TRUE(stream->stream() != NULL); + EXPECT_EQ(static_cast<int>(spdy::kSpdyStreamInitialWindowSize) + + kDeltaWindowSize * kDeltaCount - + kMaxSpdyFrameChunkSize * kFrameCount, + stream->stream()->send_window_size()); + helper.VerifyDataConsumed(); + + SpdySession::set_use_flow_control(SpdySession::kFlowControlBasedOnNPN); +} + +// Test that received data frames and sent WINDOW_UPDATE frames change +// the recv_window_size_ correctly. +TEST_P(SpdyNetworkTransactionSpdy21Test, WindowUpdateSent) { + SpdySession::set_use_flow_control(SpdySession::kEnableFlowControl); + + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> window_update( + ConstructSpdyWindowUpdate(1, kUploadDataSize)); + + MockWrite writes[] = { + CreateMockWrite(*req), + CreateMockWrite(*window_update), + }; + + scoped_ptr<spdy::SpdyFrame> resp( + ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> body_no_fin( + ConstructSpdyBodyFrame(1, false)); + scoped_ptr<spdy::SpdyFrame> body_fin( + ConstructSpdyBodyFrame(1, NULL, 0, true)); + MockRead reads[] = { + CreateMockRead(*resp), + CreateMockRead(*body_no_fin), + MockRead(ASYNC, ERR_IO_PENDING, 0), // Force a pause + CreateMockRead(*body_fin), + MockRead(ASYNC, ERR_IO_PENDING, 0), // Force a pause + MockRead(ASYNC, 0, 0) // EOF + }; + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData(1, reads, arraysize(reads), + writes, arraysize(writes))); + + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.AddData(data.get()); + helper.RunPreTestSetup(); + HttpNetworkTransaction* trans = helper.trans(); + + TestCompletionCallback callback; + int rv = trans->Start(&helper.request(), callback.callback(), BoundNetLog()); + + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + SpdyHttpStream* stream = + static_cast<SpdyHttpStream*>(trans->stream_.get()); + ASSERT_TRUE(stream != NULL); + ASSERT_TRUE(stream->stream() != NULL); + + EXPECT_EQ( + static_cast<int>(spdy::kSpdyStreamInitialWindowSize) - kUploadDataSize, + stream->stream()->recv_window_size()); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + EXPECT_TRUE(response->was_fetched_via_spdy); + + // Force sending of WINDOW_UPDATE by setting initial_recv_window_size to a + // small value. + stream->stream()->set_initial_recv_window_size(kUploadDataSize / 2); + + // Issue a read which will cause a WINDOW_UPDATE to be sent and window + // size increased to default. + scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(kUploadDataSize)); + rv = trans->Read(buf, kUploadDataSize, CompletionCallback()); + EXPECT_EQ(kUploadDataSize, rv); + std::string content(buf->data(), buf->data()+kUploadDataSize); + EXPECT_STREQ(kUploadData, content.c_str()); + + // Schedule the reading of empty data frame with FIN + data->CompleteRead(); + + // Force write of WINDOW_UPDATE which was scheduled during the above + // read. + MessageLoop::current()->RunAllPending(); + + // Read EOF. + data->CompleteRead(); + + helper.VerifyDataConsumed(); + + SpdySession::set_use_flow_control(SpdySession::kFlowControlBasedOnNPN); +} + +// Test that WINDOW_UPDATE frame causing overflow is handled correctly. We +// use the same trick as in the above test to enforce our scenario. +TEST_P(SpdyNetworkTransactionSpdy21Test, WindowUpdateOverflow) { + SpdySession::set_use_flow_control(SpdySession::kEnableFlowControl); + + // number of full frames we hope to write (but will not, used to + // set content-length header correctly) + static int kFrameCount = 3; + + scoped_ptr<std::string> content( + new std::string(kMaxSpdyFrameChunkSize, 'a')); + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyPost( + kMaxSpdyFrameChunkSize * kFrameCount, NULL, 0)); + scoped_ptr<spdy::SpdyFrame> body( + ConstructSpdyBodyFrame(1, content->c_str(), content->size(), false)); + scoped_ptr<spdy::SpdyFrame> rst( + ConstructSpdyRstStream(1, spdy::FLOW_CONTROL_ERROR)); + + // We're not going to write a data frame with FIN, we'll receive a bad + // WINDOW_UPDATE while sending a request and will send a RST_STREAM frame. + MockWrite writes[] = { + CreateMockWrite(*req), + CreateMockWrite(*body), + CreateMockWrite(*rst), + }; + + static const int32 kDeltaWindowSize = 0x7fffffff; // cause an overflow + scoped_ptr<spdy::SpdyFrame> window_update( + ConstructSpdyWindowUpdate(1, kDeltaWindowSize)); + scoped_ptr<spdy::SpdyFrame> window_update2( + ConstructSpdyWindowUpdate(2, kDeltaWindowSize)); + scoped_ptr<spdy::SpdyFrame> reply(ConstructSpdyPostSynReply(NULL, 0)); + + MockRead reads[] = { + CreateMockRead(*window_update2), + CreateMockRead(*window_update2), + CreateMockRead(*window_update), + CreateMockRead(*window_update), + CreateMockRead(*window_update), + MockRead(ASYNC, ERR_IO_PENDING, 0), // Wait for the RST to be written. + MockRead(ASYNC, 0, 0) // EOF + }; + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData(0, reads, arraysize(reads), + writes, arraysize(writes))); + + // Setup the request + HttpRequestInfo request; + request.method = "POST"; + request.url = GURL("http://www.google.com/"); + request.upload_data = new UploadData(); + for (int i = 0; i < kFrameCount; ++i) + request.upload_data->AppendBytes(content->c_str(), content->size()); + + NormalSpdyTransactionHelper helper(request, + BoundNetLog(), GetParam()); + helper.AddData(data.get()); + helper.RunPreTestSetup(); + + HttpNetworkTransaction* trans = helper.trans(); + + TestCompletionCallback callback; + int rv = trans->Start(&helper.request(), callback.callback(), BoundNetLog()); + + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback.WaitForResult(); + EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, rv); + + data->CompleteRead(); + + ASSERT_TRUE(helper.session() != NULL); + ASSERT_TRUE(helper.session()->spdy_session_pool() != NULL); + helper.session()->spdy_session_pool()->CloseAllSessions(); + helper.VerifyDataConsumed(); + + SpdySession::set_use_flow_control(SpdySession::kFlowControlBasedOnNPN); +} + +// Test that after hitting a send window size of 0, the write process +// stalls and upon receiving WINDOW_UPDATE frame write resumes. + +// This test constructs a POST request followed by enough data frames +// containing 'a' that would make the window size 0, followed by another +// data frame containing default content (which is "hello!") and this frame +// also contains a FIN flag. DelayedSocketData is used to enforce all +// writes go through before a read could happen. However, the last frame +// ("hello!") is not supposed to go through since by the time its turn +// arrives, window size is 0. At this point MessageLoop::Run() called via +// callback would block. Therefore we call MessageLoop::RunAllPending() +// which returns after performing all possible writes. We use DCHECKS to +// ensure that last data frame is still there and stream has stalled. +// After that, next read is artifically enforced, which causes a +// WINDOW_UPDATE to be read and I/O process resumes. +TEST_P(SpdyNetworkTransactionSpdy21Test, FlowControlStallResume) { + SpdySession::set_use_flow_control(SpdySession::kEnableFlowControl); + + // Number of frames we need to send to zero out the window size: data + // frames plus SYN_STREAM plus the last data frame; also we need another + // data frame that we will send once the WINDOW_UPDATE is received, + // therefore +3. + size_t nwrites = + spdy::kSpdyStreamInitialWindowSize / kMaxSpdyFrameChunkSize + 3; + + // Calculate last frame's size; 0 size data frame is legal. + size_t last_frame_size = + spdy::kSpdyStreamInitialWindowSize % kMaxSpdyFrameChunkSize; + + // Construct content for a data frame of maximum size. + scoped_ptr<std::string> content( + new std::string(kMaxSpdyFrameChunkSize, 'a')); + + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyPost( + spdy::kSpdyStreamInitialWindowSize + kUploadDataSize, NULL, 0)); + + // Full frames. + scoped_ptr<spdy::SpdyFrame> body1( + ConstructSpdyBodyFrame(1, content->c_str(), content->size(), false)); + + // Last frame to zero out the window size. + scoped_ptr<spdy::SpdyFrame> body2( + ConstructSpdyBodyFrame(1, content->c_str(), last_frame_size, false)); + + // Data frame to be sent once WINDOW_UPDATE frame is received. + scoped_ptr<spdy::SpdyFrame> body3(ConstructSpdyBodyFrame(1, true)); + + // Fill in mock writes. + scoped_array<MockWrite> writes(new MockWrite[nwrites]); + size_t i = 0; + writes[i] = CreateMockWrite(*req); + for (i = 1; i < nwrites-2; i++) + writes[i] = CreateMockWrite(*body1); + writes[i++] = CreateMockWrite(*body2); + writes[i] = CreateMockWrite(*body3); + + // Construct read frame, give enough space to upload the rest of the + // data. + scoped_ptr<spdy::SpdyFrame> window_update( + ConstructSpdyWindowUpdate(1, kUploadDataSize)); + scoped_ptr<spdy::SpdyFrame> reply(ConstructSpdyPostSynReply(NULL, 0)); + MockRead reads[] = { + CreateMockRead(*window_update), + CreateMockRead(*window_update), + CreateMockRead(*reply), + CreateMockRead(*body2), + CreateMockRead(*body3), + MockRead(ASYNC, 0, 0) // EOF + }; + + // Force all writes to happen before any read, last write will not + // actually queue a frame, due to window size being 0. + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData(nwrites, reads, arraysize(reads), + writes.get(), nwrites)); + + HttpRequestInfo request; + request.method = "POST"; + request.url = GURL("http://www.google.com/"); + request.upload_data = new UploadData(); + scoped_ptr<std::string> upload_data( + new std::string(spdy::kSpdyStreamInitialWindowSize, 'a')); + upload_data->append(kUploadData, kUploadDataSize); + request.upload_data->AppendBytes(upload_data->c_str(), upload_data->size()); + NormalSpdyTransactionHelper helper(request, + BoundNetLog(), GetParam()); + helper.AddData(data.get()); + helper.RunPreTestSetup(); + + HttpNetworkTransaction* trans = helper.trans(); + + TestCompletionCallback callback; + int rv = trans->Start(&helper.request(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + MessageLoop::current()->RunAllPending(); // Write as much as we can. + + SpdyHttpStream* stream = static_cast<SpdyHttpStream*>(trans->stream_.get()); + ASSERT_TRUE(stream != NULL); + ASSERT_TRUE(stream->stream() != NULL); + EXPECT_EQ(0, stream->stream()->send_window_size()); + // All the body data should have been read. + // TODO(satorux): This is because of the weirdness in reading the request + // body in OnSendBodyComplete(). See crbug.com/113107. + EXPECT_TRUE(stream->request_body_stream_->IsEOF()); + // But the body is not yet fully sent ("hello!" is not yet sent). + EXPECT_FALSE(stream->stream()->body_sent()); + + data->ForceNextRead(); // Read in WINDOW_UPDATE frame. + rv = callback.WaitForResult(); + helper.VerifyDataConsumed(); + + SpdySession::set_use_flow_control(SpdySession::kFlowControlBasedOnNPN); +} + +TEST_P(SpdyNetworkTransactionSpdy21Test, CancelledTransaction) { + // Construct the request. + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + MockWrite writes[] = { + CreateMockWrite(*req), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + MockRead reads[] = { + CreateMockRead(*resp), + // This following read isn't used by the test, except during the + // RunAllPending() call at the end since the SpdySession survives the + // HttpNetworkTransaction and still tries to continue Read()'ing. Any + // MockRead will do here. + MockRead(ASYNC, 0, 0) // EOF + }; + + StaticSocketDataProvider data(reads, arraysize(reads), + writes, arraysize(writes)); + + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.RunPreTestSetup(); + helper.AddData(&data); + HttpNetworkTransaction* trans = helper.trans(); + + TestCompletionCallback callback; + int rv = trans->Start( + &CreateGetRequest(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + helper.ResetTrans(); // Cancel the transaction. + + // Flush the MessageLoop while the SpdySessionDependencies (in particular, the + // MockClientSocketFactory) are still alive. + MessageLoop::current()->RunAllPending(); + helper.VerifyDataNotConsumed(); +} + +// Verify that the client sends a Rst Frame upon cancelling the stream. +TEST_P(SpdyNetworkTransactionSpdy21Test, CancelledTransactionSendRst) { + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> rst( + ConstructSpdyRstStream(1, spdy::CANCEL)); + MockWrite writes[] = { + CreateMockWrite(*req, 0, SYNCHRONOUS), + CreateMockWrite(*rst, 2, SYNCHRONOUS), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + MockRead reads[] = { + CreateMockRead(*resp, 1, ASYNC), + MockRead(ASYNC, 0, 0, 3) // EOF + }; + + scoped_refptr<DeterministicSocketData> data( + new DeterministicSocketData(reads, arraysize(reads), + writes, arraysize(writes))); + + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), + GetParam()); + helper.SetDeterministic(); + helper.RunPreTestSetup(); + helper.AddDeterministicData(data.get()); + HttpNetworkTransaction* trans = helper.trans(); + + TestCompletionCallback callback; + + int rv = trans->Start( + &CreateGetRequest(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + data->SetStop(2); + data->Run(); + helper.ResetTrans(); + data->SetStop(20); + data->Run(); + + helper.VerifyDataConsumed(); +} + +// Verify that the client can correctly deal with the user callback attempting +// to start another transaction on a session that is closing down. See +// http://crbug.com/47455 +TEST_P(SpdyNetworkTransactionSpdy21Test, StartTransactionOnReadCallback) { + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + MockWrite writes[] = { CreateMockWrite(*req) }; + MockWrite writes2[] = { CreateMockWrite(*req) }; + + // The indicated length of this packet is longer than its actual length. When + // the session receives an empty packet after this one, it shuts down the + // session, and calls the read callback with the incomplete data. + const uint8 kGetBodyFrame2[] = { + 0x00, 0x00, 0x00, 0x01, + 0x01, 0x00, 0x00, 0x07, + 'h', 'e', 'l', 'l', 'o', '!', + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + MockRead reads[] = { + CreateMockRead(*resp, 2), + MockRead(ASYNC, ERR_IO_PENDING, 3), // Force a pause + MockRead(ASYNC, reinterpret_cast<const char*>(kGetBodyFrame2), + arraysize(kGetBodyFrame2), 4), + MockRead(ASYNC, ERR_IO_PENDING, 5), // Force a pause + MockRead(ASYNC, 0, 0, 6), // EOF + }; + MockRead reads2[] = { + CreateMockRead(*resp, 2), + MockRead(ASYNC, 0, 0, 3), // EOF + }; + + scoped_ptr<OrderedSocketData> data( + new OrderedSocketData(reads, arraysize(reads), + writes, arraysize(writes))); + scoped_ptr<DelayedSocketData> data2( + new DelayedSocketData(1, reads2, arraysize(reads2), + writes2, arraysize(writes2))); + + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.RunPreTestSetup(); + helper.AddData(data.get()); + helper.AddData(data2.get()); + HttpNetworkTransaction* trans = helper.trans(); + + // Start the transaction with basic parameters. + TestCompletionCallback callback; + int rv = trans->Start(&helper.request(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback.WaitForResult(); + + const int kSize = 3000; + scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(kSize)); + rv = trans->Read( + buf, kSize, + base::Bind(&SpdyNetworkTransactionSpdy21Test::StartTransactionCallback, + helper.session())); + // This forces an err_IO_pending, which sets the callback. + data->CompleteRead(); + // This finishes the read. + data->CompleteRead(); + helper.VerifyDataConsumed(); +} + +// Verify that the client can correctly deal with the user callback deleting the +// transaction. Failures will usually be valgrind errors. See +// http://crbug.com/46925 +TEST_P(SpdyNetworkTransactionSpdy21Test, DeleteSessionOnReadCallback) { + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + MockWrite writes[] = { CreateMockWrite(*req) }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*resp.get(), 2), + MockRead(ASYNC, ERR_IO_PENDING, 3), // Force a pause + CreateMockRead(*body.get(), 4), + MockRead(ASYNC, 0, 0, 5), // EOF + }; + + scoped_ptr<OrderedSocketData> data( + new OrderedSocketData(reads, arraysize(reads), + writes, arraysize(writes))); + + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.RunPreTestSetup(); + helper.AddData(data.get()); + HttpNetworkTransaction* trans = helper.trans(); + + // Start the transaction with basic parameters. + TestCompletionCallback callback; + int rv = trans->Start(&helper.request(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback.WaitForResult(); + + // Setup a user callback which will delete the session, and clear out the + // memory holding the stream object. Note that the callback deletes trans. + const int kSize = 3000; + scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(kSize)); + rv = trans->Read( + buf, kSize, + base::Bind(&SpdyNetworkTransactionSpdy21Test::DeleteSessionCallback, + base::Unretained(&helper))); + ASSERT_EQ(ERR_IO_PENDING, rv); + data->CompleteRead(); + + // Finish running rest of tasks. + MessageLoop::current()->RunAllPending(); + helper.VerifyDataConsumed(); +} + +// Send a spdy request to www.google.com that gets redirected to www.foo.com. +TEST_P(SpdyNetworkTransactionSpdy21Test, RedirectGetRequest) { + // These are headers which the net::URLRequest tacks on. + const char* const kExtraHeaders[] = { + "accept-encoding", + "gzip,deflate", + }; + const SpdyHeaderInfo kSynStartHeader = MakeSpdyHeader(spdy::SYN_STREAM); + const char* const kStandardGetHeaders[] = { + "host", + "www.google.com", + "method", + "GET", + "scheme", + "http", + "url", + "/", + "user-agent", + "", + "version", + "HTTP/1.1" + }; + const char* const kStandardGetHeaders2[] = { + "host", + "www.foo.com", + "method", + "GET", + "scheme", + "http", + "url", + "/index.php", + "user-agent", + "", + "version", + "HTTP/1.1" + }; + + // Setup writes/reads to www.google.com + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyPacket( + kSynStartHeader, kExtraHeaders, arraysize(kExtraHeaders) / 2, + kStandardGetHeaders, arraysize(kStandardGetHeaders) / 2)); + scoped_ptr<spdy::SpdyFrame> req2(ConstructSpdyPacket( + kSynStartHeader, kExtraHeaders, arraysize(kExtraHeaders) / 2, + kStandardGetHeaders2, arraysize(kStandardGetHeaders2) / 2)); + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReplyRedirect(1)); + MockWrite writes[] = { + CreateMockWrite(*req, 1), + }; + MockRead reads[] = { + CreateMockRead(*resp, 2), + MockRead(ASYNC, 0, 0, 3) // EOF + }; + + // Setup writes/reads to www.foo.com + scoped_ptr<spdy::SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> body2(ConstructSpdyBodyFrame(1, true)); + MockWrite writes2[] = { + CreateMockWrite(*req2, 1), + }; + MockRead reads2[] = { + CreateMockRead(*resp2, 2), + CreateMockRead(*body2, 3), + MockRead(ASYNC, 0, 0, 4) // EOF + }; + scoped_ptr<OrderedSocketData> data( + new OrderedSocketData(reads, arraysize(reads), + writes, arraysize(writes))); + scoped_ptr<OrderedSocketData> data2( + new OrderedSocketData(reads2, arraysize(reads2), + writes2, arraysize(writes2))); + + // TODO(erikchen): Make test support SPDYSSL, SPDYNPN + HttpStreamFactory::set_force_spdy_over_ssl(false); + HttpStreamFactory::set_force_spdy_always(true); + TestDelegate d; + { + net::URLRequest r(GURL("http://www.google.com/"), &d); + SpdyURLRequestContext* spdy_url_request_context = + new SpdyURLRequestContext(); + r.set_context(spdy_url_request_context); + spdy_url_request_context->socket_factory(). + AddSocketDataProvider(data.get()); + spdy_url_request_context->socket_factory(). + AddSocketDataProvider(data2.get()); + + d.set_quit_on_redirect(true); + r.Start(); + MessageLoop::current()->Run(); + + EXPECT_EQ(1, d.received_redirect_count()); + + r.FollowDeferredRedirect(); + MessageLoop::current()->Run(); + EXPECT_EQ(1, d.response_started_count()); + EXPECT_FALSE(d.received_data_before_response()); + EXPECT_EQ(net::URLRequestStatus::SUCCESS, r.status().status()); + std::string contents("hello!"); + EXPECT_EQ(contents, d.data_received()); + } + EXPECT_TRUE(data->at_read_eof()); + EXPECT_TRUE(data->at_write_eof()); + EXPECT_TRUE(data2->at_read_eof()); + EXPECT_TRUE(data2->at_write_eof()); +} + +// Detect response with upper case headers and reset the stream. +TEST_P(SpdyNetworkTransactionSpdy21Test, UpperCaseHeaders) { + scoped_ptr<spdy::SpdyFrame> + syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> + rst(ConstructSpdyRstStream(1, spdy::PROTOCOL_ERROR)); + MockWrite writes[] = { + CreateMockWrite(*syn, 0), + CreateMockWrite(*rst, 2), + }; + + const char* const kExtraHeaders[] = {"X-UpperCase", "yes"}; + scoped_ptr<spdy::SpdyFrame> + reply(ConstructSpdyGetSynReply(kExtraHeaders, 1, 1)); + MockRead reads[] = { + CreateMockRead(*reply, 1), + MockRead(ASYNC, ERR_IO_PENDING, 3), // Force a pause + }; + + HttpResponseInfo response; + HttpResponseInfo response2; + scoped_ptr<OrderedSocketData> data(new OrderedSocketData( + reads, + arraysize(reads), + writes, + arraysize(writes))); + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.RunToCompletion(data.get()); + TransactionHelperResult out = helper.output(); + EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, out.rv); +} + +// Detect response with upper case headers in a HEADERS frame and reset the +// stream. +TEST_P(SpdyNetworkTransactionSpdy21Test, UpperCaseHeadersInHeadersFrame) { + scoped_ptr<spdy::SpdyFrame> + syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> + rst(ConstructSpdyRstStream(1, spdy::PROTOCOL_ERROR)); + MockWrite writes[] = { + CreateMockWrite(*syn, 0), + CreateMockWrite(*rst, 2), + }; + + static const char* const kInitialHeaders[] = { + "status", "200 OK", + "version", "HTTP/1.1" + }; + static const char* const kLateHeaders[] = { + "X-UpperCase", "yes", + }; + scoped_ptr<spdy::SpdyFrame> + stream1_reply(ConstructSpdyControlFrame(kInitialHeaders, + arraysize(kInitialHeaders) / 2, + false, + 1, + LOWEST, + spdy::SYN_REPLY, + spdy::CONTROL_FLAG_NONE, + NULL, + 0, + 0)); + scoped_ptr<spdy::SpdyFrame> + stream1_headers(ConstructSpdyControlFrame(kLateHeaders, + arraysize(kLateHeaders) / 2, + false, + 1, + LOWEST, + spdy::HEADERS, + spdy::CONTROL_FLAG_NONE, + NULL, + 0, + 0)); + scoped_ptr<spdy::SpdyFrame> stream1_body(ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*stream1_reply), + CreateMockRead(*stream1_headers), + CreateMockRead(*stream1_body), + MockRead(ASYNC, 0, 0) // EOF + }; + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData(1, reads, arraysize(reads), + writes, arraysize(writes))); + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.RunToCompletion(data.get()); + TransactionHelperResult out = helper.output(); + EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, out.rv); +} + +// Detect push stream with upper case headers and reset the stream. +TEST_P(SpdyNetworkTransactionSpdy21Test, UpperCaseHeadersOnPush) { + scoped_ptr<spdy::SpdyFrame> + syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> + rst(ConstructSpdyRstStream(2, spdy::PROTOCOL_ERROR)); + MockWrite writes[] = { + CreateMockWrite(*syn, 0), + CreateMockWrite(*rst, 2), + }; + + scoped_ptr<spdy::SpdyFrame> + reply(ConstructSpdyGetSynReply(NULL, 0, 1)); + const char* const extra_headers[] = {"X-UpperCase", "yes"}; + scoped_ptr<spdy::SpdyFrame> + push(ConstructSpdyPush(extra_headers, 1, 2, 1)); + scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*reply, 1), + CreateMockRead(*push, 1), + CreateMockRead(*body, 1), + MockRead(ASYNC, ERR_IO_PENDING, 3), // Force a pause + }; + + HttpResponseInfo response; + HttpResponseInfo response2; + scoped_ptr<OrderedSocketData> data(new OrderedSocketData( + reads, + arraysize(reads), + writes, + arraysize(writes))); + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.RunToCompletion(data.get()); + TransactionHelperResult out = helper.output(); + EXPECT_EQ(OK, out.rv); +} + +// Send a spdy request to www.google.com. Get a pushed stream that redirects to +// www.foo.com. +TEST_P(SpdyNetworkTransactionSpdy21Test, RedirectServerPush) { + // These are headers which the net::URLRequest tacks on. + const char* const kExtraHeaders[] = { + "accept-encoding", + "gzip,deflate", + }; + const SpdyHeaderInfo kSynStartHeader = MakeSpdyHeader(spdy::SYN_STREAM); + const char* const kStandardGetHeaders[] = { + "host", + "www.google.com", + "method", + "GET", + "scheme", + "http", + "url", + "/", + "user-agent", + "", + "version", + "HTTP/1.1" + }; + + // Setup writes/reads to www.google.com + scoped_ptr<spdy::SpdyFrame> req( + ConstructSpdyPacket(kSynStartHeader, + kExtraHeaders, + arraysize(kExtraHeaders) / 2, + kStandardGetHeaders, + arraysize(kStandardGetHeaders) / 2)); + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> rep( + ConstructSpdyPush(NULL, + 0, + 2, + 1, + "http://www.google.com/foo.dat", + "301 Moved Permanently", + "http://www.foo.com/index.php")); + scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true)); + scoped_ptr<spdy::SpdyFrame> rst(ConstructSpdyRstStream(2, spdy::CANCEL)); + MockWrite writes[] = { + CreateMockWrite(*req, 1), + CreateMockWrite(*rst, 6), + }; + MockRead reads[] = { + CreateMockRead(*resp, 2), + CreateMockRead(*rep, 3), + CreateMockRead(*body, 4), + MockRead(ASYNC, ERR_IO_PENDING, 5), // Force a pause + MockRead(ASYNC, 0, 0, 7) // EOF + }; + + // Setup writes/reads to www.foo.com + const char* const kStandardGetHeaders2[] = { + "host", + "www.foo.com", + "method", + "GET", + "scheme", + "http", + "url", + "/index.php", + "user-agent", + "", + "version", + "HTTP/1.1" + }; + scoped_ptr<spdy::SpdyFrame> req2( + ConstructSpdyPacket(kSynStartHeader, + kExtraHeaders, + arraysize(kExtraHeaders) / 2, + kStandardGetHeaders2, + arraysize(kStandardGetHeaders2) / 2)); + scoped_ptr<spdy::SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> body2(ConstructSpdyBodyFrame(1, true)); + MockWrite writes2[] = { + CreateMockWrite(*req2, 1), + }; + MockRead reads2[] = { + CreateMockRead(*resp2, 2), + CreateMockRead(*body2, 3), + MockRead(ASYNC, 0, 0, 5) // EOF + }; + scoped_ptr<OrderedSocketData> data( + new OrderedSocketData(reads, arraysize(reads), + writes, arraysize(writes))); + scoped_ptr<OrderedSocketData> data2( + new OrderedSocketData(reads2, arraysize(reads2), + writes2, arraysize(writes2))); + + // TODO(erikchen): Make test support SPDYSSL, SPDYNPN + HttpStreamFactory::set_force_spdy_over_ssl(false); + HttpStreamFactory::set_force_spdy_always(true); + TestDelegate d; + TestDelegate d2; + scoped_refptr<SpdyURLRequestContext> spdy_url_request_context( + new SpdyURLRequestContext()); + { + net::URLRequest r(GURL("http://www.google.com/"), &d); + r.set_context(spdy_url_request_context); + spdy_url_request_context->socket_factory(). + AddSocketDataProvider(data.get()); + + r.Start(); + MessageLoop::current()->Run(); + + EXPECT_EQ(0, d.received_redirect_count()); + std::string contents("hello!"); + EXPECT_EQ(contents, d.data_received()); + + net::URLRequest r2(GURL("http://www.google.com/foo.dat"), &d2); + r2.set_context(spdy_url_request_context); + spdy_url_request_context->socket_factory(). + AddSocketDataProvider(data2.get()); + + d2.set_quit_on_redirect(true); + r2.Start(); + MessageLoop::current()->Run(); + EXPECT_EQ(1, d2.received_redirect_count()); + + r2.FollowDeferredRedirect(); + MessageLoop::current()->Run(); + EXPECT_EQ(1, d2.response_started_count()); + EXPECT_FALSE(d2.received_data_before_response()); + EXPECT_EQ(net::URLRequestStatus::SUCCESS, r2.status().status()); + std::string contents2("hello!"); + EXPECT_EQ(contents2, d2.data_received()); + } + data->CompleteRead(); + data2->CompleteRead(); + EXPECT_TRUE(data->at_read_eof()); + EXPECT_TRUE(data->at_write_eof()); + EXPECT_TRUE(data2->at_read_eof()); + EXPECT_TRUE(data2->at_write_eof()); +} + +TEST_P(SpdyNetworkTransactionSpdy21Test, ServerPushSingleDataFrame) { + static const unsigned char kPushBodyFrame[] = { + 0x00, 0x00, 0x00, 0x02, // header, ID + 0x01, 0x00, 0x00, 0x06, // FIN, length + 'p', 'u', 's', 'h', 'e', 'd' // "pushed" + }; + scoped_ptr<spdy::SpdyFrame> + stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> + stream1_body(ConstructSpdyBodyFrame(1, true)); + MockWrite writes[] = { + CreateMockWrite(*stream1_syn, 1), + }; + + scoped_ptr<spdy::SpdyFrame> + stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> + stream2_syn(ConstructSpdyPush(NULL, + 0, + 2, + 1, + "http://www.google.com/foo.dat")); + MockRead reads[] = { + CreateMockRead(*stream1_reply, 2), + CreateMockRead(*stream2_syn, 3), + CreateMockRead(*stream1_body, 4, SYNCHRONOUS), + MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame), + arraysize(kPushBodyFrame), 5), + MockRead(ASYNC, ERR_IO_PENDING, 6), // Force a pause + }; + + HttpResponseInfo response; + HttpResponseInfo response2; + std::string expected_push_result("pushed"); + scoped_ptr<OrderedSocketData> data(new OrderedSocketData( + reads, + arraysize(reads), + writes, + arraysize(writes))); + RunServerPushTest(data.get(), + &response, + &response2, + expected_push_result); + + // Verify the SYN_REPLY. + EXPECT_TRUE(response.headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); + + // Verify the pushed stream. + EXPECT_TRUE(response2.headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine()); +} + +TEST_P(SpdyNetworkTransactionSpdy21Test, ServerPushSingleDataFrame2) { + static const unsigned char kPushBodyFrame[] = { + 0x00, 0x00, 0x00, 0x02, // header, ID + 0x01, 0x00, 0x00, 0x06, // FIN, length + 'p', 'u', 's', 'h', 'e', 'd' // "pushed" + }; + scoped_ptr<spdy::SpdyFrame> + stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + MockWrite writes[] = { + CreateMockWrite(*stream1_syn, 1), + }; + + scoped_ptr<spdy::SpdyFrame> + stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> + stream2_syn(ConstructSpdyPush(NULL, + 0, + 2, + 1, + "http://www.google.com/foo.dat")); + scoped_ptr<spdy::SpdyFrame> + stream1_body(ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*stream1_reply, 2), + CreateMockRead(*stream2_syn, 3), + MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame), + arraysize(kPushBodyFrame), 5), + CreateMockRead(*stream1_body, 4, SYNCHRONOUS), + MockRead(ASYNC, ERR_IO_PENDING, 6), // Force a pause + }; + + HttpResponseInfo response; + HttpResponseInfo response2; + std::string expected_push_result("pushed"); + scoped_ptr<OrderedSocketData> data(new OrderedSocketData( + reads, + arraysize(reads), + writes, + arraysize(writes))); + RunServerPushTest(data.get(), + &response, + &response2, + expected_push_result); + + // Verify the SYN_REPLY. + EXPECT_TRUE(response.headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); + + // Verify the pushed stream. + EXPECT_TRUE(response2.headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine()); +} + +TEST_P(SpdyNetworkTransactionSpdy21Test, ServerPushServerAborted) { + scoped_ptr<spdy::SpdyFrame> + stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> + stream1_body(ConstructSpdyBodyFrame(1, true)); + MockWrite writes[] = { + CreateMockWrite(*stream1_syn, 1), + }; + + scoped_ptr<spdy::SpdyFrame> + stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> + stream2_syn(ConstructSpdyPush(NULL, + 0, + 2, + 1, + "http://www.google.com/foo.dat")); + scoped_ptr<spdy::SpdyFrame> + stream2_rst(ConstructSpdyRstStream(2, spdy::PROTOCOL_ERROR)); + MockRead reads[] = { + CreateMockRead(*stream1_reply, 2), + CreateMockRead(*stream2_syn, 3), + CreateMockRead(*stream2_rst, 4), + CreateMockRead(*stream1_body, 5, SYNCHRONOUS), + MockRead(ASYNC, ERR_IO_PENDING, 6), // Force a pause + }; + + scoped_ptr<OrderedSocketData> data( + new OrderedSocketData(reads, arraysize(reads), + writes, arraysize(writes))); + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + + helper.RunPreTestSetup(); + helper.AddData(data.get()); + + HttpNetworkTransaction* trans = helper.trans(); + + // Start the transaction with basic parameters. + TestCompletionCallback callback; + int rv = trans->Start( + &CreateGetRequest(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + // Verify that we consumed all test data. + EXPECT_TRUE(data->at_read_eof()) << "Read count: " + << data->read_count() + << " Read index: " + << data->read_index(); + EXPECT_TRUE(data->at_write_eof()) << "Write count: " + << data->write_count() + << " Write index: " + << data->write_index(); + + // Verify the SYN_REPLY. + HttpResponseInfo response = *trans->GetResponseInfo(); + EXPECT_TRUE(response.headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); +} + +TEST_P(SpdyNetworkTransactionSpdy21Test, ServerPushDuplicate) { + // Verify that we don't leak streams and that we properly send a reset + // if the server pushes the same stream twice. + static const unsigned char kPushBodyFrame[] = { + 0x00, 0x00, 0x00, 0x02, // header, ID + 0x01, 0x00, 0x00, 0x06, // FIN, length + 'p', 'u', 's', 'h', 'e', 'd' // "pushed" + }; + + scoped_ptr<spdy::SpdyFrame> + stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> + stream1_body(ConstructSpdyBodyFrame(1, true)); + scoped_ptr<spdy::SpdyFrame> + stream3_rst(ConstructSpdyRstStream(4, spdy::PROTOCOL_ERROR)); + MockWrite writes[] = { + CreateMockWrite(*stream1_syn, 1), + CreateMockWrite(*stream3_rst, 5), + }; + + scoped_ptr<spdy::SpdyFrame> + stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> + stream2_syn(ConstructSpdyPush(NULL, + 0, + 2, + 1, + "http://www.google.com/foo.dat")); + scoped_ptr<spdy::SpdyFrame> + stream3_syn(ConstructSpdyPush(NULL, + 0, + 4, + 1, + "http://www.google.com/foo.dat")); + MockRead reads[] = { + CreateMockRead(*stream1_reply, 2), + CreateMockRead(*stream2_syn, 3), + CreateMockRead(*stream3_syn, 4), + CreateMockRead(*stream1_body, 6, SYNCHRONOUS), + MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame), + arraysize(kPushBodyFrame), 7), + MockRead(ASYNC, ERR_IO_PENDING, 8), // Force a pause + }; + + HttpResponseInfo response; + HttpResponseInfo response2; + std::string expected_push_result("pushed"); + scoped_ptr<OrderedSocketData> data(new OrderedSocketData( + reads, + arraysize(reads), + writes, + arraysize(writes))); + RunServerPushTest(data.get(), + &response, + &response2, + expected_push_result); + + // Verify the SYN_REPLY. + EXPECT_TRUE(response.headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); + + // Verify the pushed stream. + EXPECT_TRUE(response2.headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine()); +} + +TEST_P(SpdyNetworkTransactionSpdy21Test, ServerPushMultipleDataFrame) { + static const unsigned char kPushBodyFrame1[] = { + 0x00, 0x00, 0x00, 0x02, // header, ID + 0x01, 0x00, 0x00, 0x1F, // FIN, length + 'p', 'u', 's', 'h', 'e', 'd' // "pushed" + }; + static const char kPushBodyFrame2[] = " my darling"; + static const char kPushBodyFrame3[] = " hello"; + static const char kPushBodyFrame4[] = " my baby"; + + scoped_ptr<spdy::SpdyFrame> + stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> + stream1_body(ConstructSpdyBodyFrame(1, true)); + MockWrite writes[] = { + CreateMockWrite(*stream1_syn, 1), + }; + + scoped_ptr<spdy::SpdyFrame> + stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> + stream2_syn(ConstructSpdyPush(NULL, + 0, + 2, + 1, + "http://www.google.com/foo.dat")); + MockRead reads[] = { + CreateMockRead(*stream1_reply, 2), + CreateMockRead(*stream2_syn, 3), + MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame1), + arraysize(kPushBodyFrame1), 4), + MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame2), + arraysize(kPushBodyFrame2) - 1, 5), + MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame3), + arraysize(kPushBodyFrame3) - 1, 6), + MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame4), + arraysize(kPushBodyFrame4) - 1, 7), + CreateMockRead(*stream1_body, 8, SYNCHRONOUS), + MockRead(ASYNC, ERR_IO_PENDING, 9), // Force a pause + }; + + HttpResponseInfo response; + HttpResponseInfo response2; + std::string expected_push_result("pushed my darling hello my baby"); + scoped_ptr<OrderedSocketData> data(new OrderedSocketData( + reads, + arraysize(reads), + writes, + arraysize(writes))); + RunServerPushTest(data.get(), + &response, + &response2, + expected_push_result); + + // Verify the SYN_REPLY. + EXPECT_TRUE(response.headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); + + // Verify the pushed stream. + EXPECT_TRUE(response2.headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine()); +} + +TEST_P(SpdyNetworkTransactionSpdy21Test, + ServerPushMultipleDataFrameInterrupted) { + SpdySession::set_use_flow_control(SpdySession::kDisableFlowControl); + + static const unsigned char kPushBodyFrame1[] = { + 0x00, 0x00, 0x00, 0x02, // header, ID + 0x01, 0x00, 0x00, 0x1F, // FIN, length + 'p', 'u', 's', 'h', 'e', 'd' // "pushed" + }; + static const char kPushBodyFrame2[] = " my darling"; + static const char kPushBodyFrame3[] = " hello"; + static const char kPushBodyFrame4[] = " my baby"; + + scoped_ptr<spdy::SpdyFrame> + stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> + stream1_body(ConstructSpdyBodyFrame(1, true)); + MockWrite writes[] = { + CreateMockWrite(*stream1_syn, 1), + }; + + scoped_ptr<spdy::SpdyFrame> + stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> + stream2_syn(ConstructSpdyPush(NULL, + 0, + 2, + 1, + "http://www.google.com/foo.dat")); + MockRead reads[] = { + CreateMockRead(*stream1_reply, 2), + CreateMockRead(*stream2_syn, 3), + MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame1), + arraysize(kPushBodyFrame1), 4), + MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame2), + arraysize(kPushBodyFrame2) - 1, 5), + MockRead(ASYNC, ERR_IO_PENDING, 6), // Force a pause + MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame3), + arraysize(kPushBodyFrame3) - 1, 7), + MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame4), + arraysize(kPushBodyFrame4) - 1, 8), + CreateMockRead(*stream1_body.get(), 9, SYNCHRONOUS), + MockRead(ASYNC, ERR_IO_PENDING, 10) // Force a pause. + }; + + HttpResponseInfo response; + HttpResponseInfo response2; + std::string expected_push_result("pushed my darling hello my baby"); + scoped_ptr<OrderedSocketData> data(new OrderedSocketData( + reads, + arraysize(reads), + writes, + arraysize(writes))); + RunServerPushTest(data.get(), + &response, + &response2, + expected_push_result); + + // Verify the SYN_REPLY. + EXPECT_TRUE(response.headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); + + // Verify the pushed stream. + EXPECT_TRUE(response2.headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine()); + + SpdySession::set_use_flow_control(SpdySession::kFlowControlBasedOnNPN); +} + +TEST_P(SpdyNetworkTransactionSpdy21Test, ServerPushInvalidAssociatedStreamID0) { + scoped_ptr<spdy::SpdyFrame> + stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> + stream1_body(ConstructSpdyBodyFrame(1, true)); + scoped_ptr<spdy::SpdyFrame> + stream2_rst(ConstructSpdyRstStream(2, spdy::INVALID_STREAM)); + MockWrite writes[] = { + CreateMockWrite(*stream1_syn, 1), + CreateMockWrite(*stream2_rst, 4), + }; + + scoped_ptr<spdy::SpdyFrame> + stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> + stream2_syn(ConstructSpdyPush(NULL, + 0, + 2, + 0, + "http://www.google.com/foo.dat")); + MockRead reads[] = { + CreateMockRead(*stream1_reply, 2), + CreateMockRead(*stream2_syn, 3), + CreateMockRead(*stream1_body, 4), + MockRead(ASYNC, ERR_IO_PENDING, 5) // Force a pause + }; + + scoped_ptr<OrderedSocketData> data( + new OrderedSocketData(reads, arraysize(reads), + writes, arraysize(writes))); + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + + helper.RunPreTestSetup(); + helper.AddData(data.get()); + + HttpNetworkTransaction* trans = helper.trans(); + + // Start the transaction with basic parameters. + TestCompletionCallback callback; + int rv = trans->Start( + &CreateGetRequest(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + // Verify that we consumed all test data. + EXPECT_TRUE(data->at_read_eof()) << "Read count: " + << data->read_count() + << " Read index: " + << data->read_index(); + EXPECT_TRUE(data->at_write_eof()) << "Write count: " + << data->write_count() + << " Write index: " + << data->write_index(); + + // Verify the SYN_REPLY. + HttpResponseInfo response = *trans->GetResponseInfo(); + EXPECT_TRUE(response.headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); +} + +TEST_P(SpdyNetworkTransactionSpdy21Test, ServerPushInvalidAssociatedStreamID9) { + scoped_ptr<spdy::SpdyFrame> + stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> + stream1_body(ConstructSpdyBodyFrame(1, true)); + scoped_ptr<spdy::SpdyFrame> + stream2_rst(ConstructSpdyRstStream(2, spdy::INVALID_ASSOCIATED_STREAM)); + MockWrite writes[] = { + CreateMockWrite(*stream1_syn, 1), + CreateMockWrite(*stream2_rst, 4), + }; + + scoped_ptr<spdy::SpdyFrame> + stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> + stream2_syn(ConstructSpdyPush(NULL, + 0, + 2, + 9, + "http://www.google.com/foo.dat")); + MockRead reads[] = { + CreateMockRead(*stream1_reply, 2), + CreateMockRead(*stream2_syn, 3), + CreateMockRead(*stream1_body, 4), + MockRead(ASYNC, ERR_IO_PENDING, 5), // Force a pause + }; + + scoped_ptr<OrderedSocketData> data( + new OrderedSocketData(reads, arraysize(reads), + writes, arraysize(writes))); + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + + helper.RunPreTestSetup(); + helper.AddData(data.get()); + + HttpNetworkTransaction* trans = helper.trans(); + + // Start the transaction with basic parameters. + TestCompletionCallback callback; + int rv = trans->Start( + &CreateGetRequest(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + // Verify that we consumed all test data. + EXPECT_TRUE(data->at_read_eof()) << "Read count: " + << data->read_count() + << " Read index: " + << data->read_index(); + EXPECT_TRUE(data->at_write_eof()) << "Write count: " + << data->write_count() + << " Write index: " + << data->write_index(); + + // Verify the SYN_REPLY. + HttpResponseInfo response = *trans->GetResponseInfo(); + EXPECT_TRUE(response.headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); +} + +TEST_P(SpdyNetworkTransactionSpdy21Test, ServerPushNoURL) { + scoped_ptr<spdy::SpdyFrame> + stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> + stream1_body(ConstructSpdyBodyFrame(1, true)); + scoped_ptr<spdy::SpdyFrame> + stream2_rst(ConstructSpdyRstStream(2, spdy::PROTOCOL_ERROR)); + MockWrite writes[] = { + CreateMockWrite(*stream1_syn, 1), + CreateMockWrite(*stream2_rst, 4), + }; + + scoped_ptr<spdy::SpdyFrame> + stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> + stream2_syn(ConstructSpdyPush(NULL, 0, 2, 1)); + MockRead reads[] = { + CreateMockRead(*stream1_reply, 2), + CreateMockRead(*stream2_syn, 3), + CreateMockRead(*stream1_body, 4), + MockRead(ASYNC, ERR_IO_PENDING, 5) // Force a pause + }; + + scoped_ptr<OrderedSocketData> data( + new OrderedSocketData(reads, arraysize(reads), + writes, arraysize(writes))); + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + + helper.RunPreTestSetup(); + helper.AddData(data.get()); + + HttpNetworkTransaction* trans = helper.trans(); + + // Start the transaction with basic parameters. + TestCompletionCallback callback; + int rv = trans->Start( + &CreateGetRequest(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + // Verify that we consumed all test data. + EXPECT_TRUE(data->at_read_eof()) << "Read count: " + << data->read_count() + << " Read index: " + << data->read_index(); + EXPECT_TRUE(data->at_write_eof()) << "Write count: " + << data->write_count() + << " Write index: " + << data->write_index(); + + // Verify the SYN_REPLY. + HttpResponseInfo response = *trans->GetResponseInfo(); + EXPECT_TRUE(response.headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); +} + +// Verify that various SynReply headers parse correctly through the +// HTTP layer. +TEST_P(SpdyNetworkTransactionSpdy21Test, SynReplyHeaders) { + struct SynReplyHeadersTests { + int num_headers; + const char* extra_headers[5]; + const char* expected_headers; + } test_cases[] = { + // This uses a multi-valued cookie header. + { 2, + { "cookie", "val1", + "cookie", "val2", // will get appended separated by NULL + NULL + }, + "cookie: val1\n" + "cookie: val2\n" + "hello: bye\n" + "status: 200\n" + "version: HTTP/1.1\n" + }, + // This is the minimalist set of headers. + { 0, + { NULL }, + "hello: bye\n" + "status: 200\n" + "version: HTTP/1.1\n" + }, + // Headers with a comma separated list. + { 1, + { "cookie", "val1,val2", + NULL + }, + "cookie: val1,val2\n" + "hello: bye\n" + "status: 200\n" + "version: HTTP/1.1\n" + } + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) { + scoped_ptr<spdy::SpdyFrame> req( + ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + MockWrite writes[] = { CreateMockWrite(*req) }; + + scoped_ptr<spdy::SpdyFrame> resp( + ConstructSpdyGetSynReply(test_cases[i].extra_headers, + test_cases[i].num_headers, + 1)); + scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*resp), + CreateMockRead(*body), + MockRead(ASYNC, 0, 0) // EOF + }; + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData(1, reads, arraysize(reads), + writes, arraysize(writes))); + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.RunToCompletion(data.get()); + TransactionHelperResult out = helper.output(); + + 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 various SynReply headers parse vary fields correctly +// through the HTTP layer, and the response matches the request. +TEST_P(SpdyNetworkTransactionSpdy21Test, SynReplyHeadersVary) { + static const SpdyHeaderInfo syn_reply_info = { + spdy::SYN_REPLY, // Syn Reply + 1, // Stream ID + 0, // Associated Stream ID + net::ConvertRequestPriorityToSpdyPriority(LOWEST), + // Priority + spdy::CONTROL_FLAG_NONE, // Control Flags + false, // Compressed + spdy::INVALID, // Status + NULL, // Data + 0, // Data Length + spdy::DATA_FLAG_NONE // Data Flags + }; + // Modify the following data to change/add test cases: + struct SynReplyTests { + const SpdyHeaderInfo* syn_reply; + bool vary_matches; + int num_headers[2]; + const char* extra_headers[2][16]; + } 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_reply_info, + true, + { 1, 4 }, + { { "cookie", "val1,val2", + NULL + }, + { "vary", "cookie", + "status", "200", + "url", "/index.php", + "version", "HTTP/1.1", + NULL + } + } + }, { // Multiple vary fields. + &syn_reply_info, + true, + { 2, 5 }, + { { "friend", "barney", + "enemy", "snaggletooth", + NULL + }, + { "vary", "friend", + "vary", "enemy", + "status", "200", + "url", "/index.php", + "version", "HTTP/1.1", + NULL + } + } + }, { // Test a '*' vary field. + &syn_reply_info, + false, + { 1, 4 }, + { { "cookie", "val1,val2", + NULL + }, + { "vary", "*", + "status", "200", + "url", "/index.php", + "version", "HTTP/1.1", + NULL + } + } + }, { // Multiple comma-separated vary fields. + &syn_reply_info, + true, + { 2, 4 }, + { { "friend", "barney", + "enemy", "snaggletooth", + NULL + }, + { "vary", "friend,enemy", + "status", "200", + "url", "/index.php", + "version", "HTTP/1.1", + NULL + } + } + } + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) { + // Construct the request. + scoped_ptr<spdy::SpdyFrame> frame_req( + ConstructSpdyGet(test_cases[i].extra_headers[0], + test_cases[i].num_headers[0], + false, 1, LOWEST)); + + MockWrite writes[] = { + CreateMockWrite(*frame_req), + }; + + // Construct the reply. + scoped_ptr<spdy::SpdyFrame> frame_reply( + ConstructSpdyPacket(*test_cases[i].syn_reply, + test_cases[i].extra_headers[1], + test_cases[i].num_headers[1], + NULL, + 0)); + + scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*frame_reply), + CreateMockRead(*body), + MockRead(ASYNC, 0, 0) // EOF + }; + + // Attach the headers to the request. + int header_count = test_cases[i].num_headers[0]; + + HttpRequestInfo request = CreateGetRequest(); + for (int ct = 0; ct < header_count; ct++) { + const char* header_key = test_cases[i].extra_headers[0][ct * 2]; + const char* header_value = test_cases[i].extra_headers[0][ct * 2 + 1]; + request.extra_headers.SetHeader(header_key, header_value); + } + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData(1, reads, arraysize(reads), + writes, arraysize(writes))); + NormalSpdyTransactionHelper helper(request, + BoundNetLog(), GetParam()); + helper.RunToCompletion(data.get()); + TransactionHelperResult out = helper.output(); + + EXPECT_EQ(OK, out.rv) << i; + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line) << i; + EXPECT_EQ("hello!", out.response_data) << i; + + // Test the response information. + EXPECT_TRUE(out.response_info.response_time > + out.response_info.request_time) << i; + base::TimeDelta test_delay = out.response_info.response_time - + out.response_info.request_time; + base::TimeDelta min_expected_delay; + min_expected_delay.FromMilliseconds(10); + EXPECT_GT(test_delay.InMillisecondsF(), + min_expected_delay.InMillisecondsF()) << i; + EXPECT_EQ(out.response_info.vary_data.is_valid(), + test_cases[i].vary_matches) << i; + + // Check the headers. + scoped_refptr<HttpResponseHeaders> headers = out.response_info.headers; + ASSERT_TRUE(headers.get() != NULL) << i; + 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"); + } + + // Construct the expected header reply string. + char reply_buffer[256] = ""; + ConstructSpdyReplyString(test_cases[i].extra_headers[1], + test_cases[i].num_headers[1], + reply_buffer, + 256); + + EXPECT_EQ(std::string(reply_buffer), lines) << i; + } +} + +// Verify that we don't crash on invalid SynReply responses. +TEST_P(SpdyNetworkTransactionSpdy21Test, InvalidSynReply) { + const SpdyHeaderInfo kSynStartHeader = { + spdy::SYN_REPLY, // Kind = SynReply + 1, // Stream ID + 0, // Associated stream ID + net::ConvertRequestPriorityToSpdyPriority(LOWEST), + // Priority + spdy::CONTROL_FLAG_NONE, // Control Flags + false, // Compressed + spdy::INVALID, // Status + NULL, // Data + 0, // Length + spdy::DATA_FLAG_NONE // Data Flags + }; + + struct InvalidSynReplyTests { + int num_headers; + const char* headers[10]; + } test_cases[] = { + // SYN_REPLY missing status header + { 4, + { "cookie", "val1", + "cookie", "val2", + "url", "/index.php", + "version", "HTTP/1.1", + NULL + }, + }, + // SYN_REPLY missing version header + { 2, + { "status", "200", + "url", "/index.php", + NULL + }, + }, + // SYN_REPLY with no headers + { 0, { NULL }, }, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) { + scoped_ptr<spdy::SpdyFrame> req( + ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + MockWrite writes[] = { + CreateMockWrite(*req), + }; + + scoped_ptr<spdy::SpdyFrame> resp( + ConstructSpdyPacket(kSynStartHeader, + NULL, 0, + test_cases[i].headers, + test_cases[i].num_headers)); + scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*resp), + CreateMockRead(*body), + MockRead(ASYNC, 0, 0) // EOF + }; + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData(1, reads, arraysize(reads), + writes, arraysize(writes))); + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.RunToCompletion(data.get()); + TransactionHelperResult out = helper.output(); + EXPECT_EQ(ERR_INCOMPLETE_SPDY_HEADERS, out.rv); + } +} + +// Verify that we don't crash on some corrupt frames. +TEST_P(SpdyNetworkTransactionSpdy21Test, CorruptFrameSessionError) { + // This is the length field that's too short. + scoped_ptr<spdy::SpdyFrame> syn_reply_wrong_length( + ConstructSpdyGetSynReply(NULL, 0, 1)); + syn_reply_wrong_length->set_length(syn_reply_wrong_length->length() - 4); + + struct SynReplyTests { + const spdy::SpdyFrame* syn_reply; + } test_cases[] = { + { syn_reply_wrong_length.get(), }, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) { + scoped_ptr<spdy::SpdyFrame> req( + ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + MockWrite writes[] = { + CreateMockWrite(*req), + MockWrite(ASYNC, 0, 0) // EOF + }; + + scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*test_cases[i].syn_reply), + CreateMockRead(*body), + MockRead(ASYNC, 0, 0) // EOF + }; + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData(1, reads, arraysize(reads), + writes, arraysize(writes))); + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.RunToCompletion(data.get()); + TransactionHelperResult out = helper.output(); + EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, out.rv); + } +} + +// Test that we shutdown correctly on write errors. +TEST_P(SpdyNetworkTransactionSpdy21Test, WriteError) { + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + MockWrite writes[] = { + // We'll write 10 bytes successfully + MockWrite(ASYNC, req->data(), 10), + // Followed by ERROR! + MockWrite(ASYNC, ERR_FAILED), + }; + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData(2, NULL, 0, + writes, arraysize(writes))); + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.RunToCompletion(data.get()); + TransactionHelperResult out = helper.output(); + EXPECT_EQ(ERR_FAILED, out.rv); + data->Reset(); +} + +// Test that partial writes work. +TEST_P(SpdyNetworkTransactionSpdy21Test, PartialWrite) { + // Chop the SYN_STREAM frame into 5 chunks. + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + const int kChunks = 5; + scoped_array<MockWrite> writes(ChopWriteFrame(*req.get(), kChunks)); + + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*resp), + CreateMockRead(*body), + MockRead(ASYNC, 0, 0) // EOF + }; + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData(kChunks, reads, arraysize(reads), + writes.get(), kChunks)); + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.RunToCompletion(data.get()); + TransactionHelperResult out = helper.output(); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!", out.response_data); +} + +// In this test, we enable compression, but get a uncompressed SynReply from +// the server. Verify that teardown is all clean. +TEST_P(SpdyNetworkTransactionSpdy21Test, DecompressFailureOnSynReply) { + // For this test, we turn on the normal compression. + EnableCompression(true); + + scoped_ptr<spdy::SpdyFrame> compressed( + ConstructSpdyGet(NULL, 0, true, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> rst( + ConstructSpdyRstStream(1, spdy::PROTOCOL_ERROR)); + MockWrite writes[] = { + CreateMockWrite(*compressed), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*resp), + }; + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData(1, reads, arraysize(reads), + writes, arraysize(writes))); + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.RunToCompletion(data.get()); + TransactionHelperResult out = helper.output(); + EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, out.rv); + data->Reset(); + + EnableCompression(false); +} + +// Test that the NetLog contains good data for a simple GET request. +TEST_P(SpdyNetworkTransactionSpdy21Test, NetLog) { + static const char* const kExtraHeaders[] = { + "user-agent", "Chrome", + }; + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(kExtraHeaders, 1, false, 1, + LOWEST)); + MockWrite writes[] = { CreateMockWrite(*req) }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*resp), + CreateMockRead(*body), + MockRead(ASYNC, 0, 0) // EOF + }; + + net::CapturingBoundNetLog log(net::CapturingNetLog::kUnbounded); + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData(1, reads, arraysize(reads), + writes, arraysize(writes))); + NormalSpdyTransactionHelper helper(CreateGetRequestWithUserAgent(), + log.bound(), GetParam()); + helper.RunToCompletion(data.get()); + TransactionHelperResult out = helper.output(); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!", out.response_data); + + // Check that the NetLog 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, and that + // they are in the right order. + net::CapturingNetLog::EntryList entries; + log.GetEntries(&entries); + + EXPECT_LT(0u, entries.size()); + int pos = 0; + pos = net::ExpectLogContainsSomewhere(entries, 0, + net::NetLog::TYPE_HTTP_TRANSACTION_SEND_REQUEST, + net::NetLog::PHASE_BEGIN); + pos = net::ExpectLogContainsSomewhere(entries, pos + 1, + net::NetLog::TYPE_HTTP_TRANSACTION_SEND_REQUEST, + net::NetLog::PHASE_END); + pos = net::ExpectLogContainsSomewhere(entries, pos + 1, + net::NetLog::TYPE_HTTP_TRANSACTION_READ_HEADERS, + net::NetLog::PHASE_BEGIN); + pos = net::ExpectLogContainsSomewhere(entries, pos + 1, + net::NetLog::TYPE_HTTP_TRANSACTION_READ_HEADERS, + net::NetLog::PHASE_END); + pos = net::ExpectLogContainsSomewhere(entries, pos + 1, + net::NetLog::TYPE_HTTP_TRANSACTION_READ_BODY, + net::NetLog::PHASE_BEGIN); + pos = net::ExpectLogContainsSomewhere(entries, pos + 1, + net::NetLog::TYPE_HTTP_TRANSACTION_READ_BODY, + net::NetLog::PHASE_END); + + // Check that we logged all the headers correctly + pos = net::ExpectLogContainsSomewhere( + entries, 0, + net::NetLog::TYPE_SPDY_SESSION_SYN_STREAM, + net::NetLog::PHASE_NONE); + CapturingNetLog::Entry entry = entries[pos]; + NetLogSpdySynParameter* request_params = + static_cast<NetLogSpdySynParameter*>(entry.extra_parameters.get()); + spdy::SpdyHeaderBlock* headers = + request_params->GetHeaders().get(); + + spdy::SpdyHeaderBlock expected; + expected["host"] = "www.google.com"; + expected["url"] = "/"; + expected["scheme"] = "http"; + expected["version"] = "HTTP/1.1"; + expected["method"] = "GET"; + expected["user-agent"] = "Chrome"; + EXPECT_EQ(expected.size(), headers->size()); + spdy::SpdyHeaderBlock::const_iterator end = expected.end(); + for (spdy::SpdyHeaderBlock::const_iterator it = expected.begin(); + it != end; + ++it) { + EXPECT_EQ(it->second, (*headers)[it->first]); + } +} + +// Since we buffer the IO from the stream to the renderer, this test verifies +// that when we read out the maximum amount of data (e.g. we received 50 bytes +// on the network, but issued a Read for only 5 of those bytes) that the data +// flow still works correctly. +TEST_P(SpdyNetworkTransactionSpdy21Test, BufferFull) { + SpdySession::set_use_flow_control(SpdySession::kDisableFlowControl); + + spdy::SpdyFramer framer; + + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + MockWrite writes[] = { CreateMockWrite(*req) }; + + // 2 data frames in a single read. + scoped_ptr<spdy::SpdyFrame> data_frame_1( + framer.CreateDataFrame(1, "goodby", 6, spdy::DATA_FLAG_NONE)); + scoped_ptr<spdy::SpdyFrame> data_frame_2( + framer.CreateDataFrame(1, "e worl", 6, spdy::DATA_FLAG_NONE)); + const spdy::SpdyFrame* data_frames[2] = { + data_frame_1.get(), + data_frame_2.get(), + }; + char combined_data_frames[100]; + int combined_data_frames_len = + CombineFrames(data_frames, arraysize(data_frames), + combined_data_frames, arraysize(combined_data_frames)); + scoped_ptr<spdy::SpdyFrame> last_frame( + framer.CreateDataFrame(1, "d", 1, spdy::DATA_FLAG_FIN)); + + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + MockRead reads[] = { + CreateMockRead(*resp), + MockRead(ASYNC, ERR_IO_PENDING), // Force a pause + MockRead(ASYNC, combined_data_frames, combined_data_frames_len), + MockRead(ASYNC, ERR_IO_PENDING), // Force a pause + CreateMockRead(*last_frame), + MockRead(ASYNC, 0, 0) // EOF + }; + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData(1, reads, arraysize(reads), + writes, arraysize(writes))); + + + TestCompletionCallback callback; + + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.RunPreTestSetup(); + helper.AddData(data.get()); + HttpNetworkTransaction* trans = helper.trans(); + int rv = trans->Start( + &CreateGetRequest(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + TransactionHelperResult out = helper.output(); + out.rv = callback.WaitForResult(); + EXPECT_EQ(out.rv, OK); + + 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. + + // Read Data + TestCompletionCallback read_callback; + + std::string content; + do { + // Read small chunks at a time. + const int kSmallReadSize = 3; + scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(kSmallReadSize)); + rv = trans->Read(buf, kSmallReadSize, read_callback.callback()); + if (rv == net::ERR_IO_PENDING) { + data->CompleteRead(); + rv = read_callback.WaitForResult(); + } + if (rv > 0) { + content.append(buf->data(), rv); + } else if (rv < 0) { + NOTREACHED(); + } + } while (rv > 0); + + out.response_data.swap(content); + + // Flush the MessageLoop while the SpdySessionDependencies (in particular, the + // MockClientSocketFactory) are still alive. + MessageLoop::current()->RunAllPending(); + + // Verify that we consumed all test data. + helper.VerifyDataConsumed(); + + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("goodbye world", out.response_data); + + SpdySession::set_use_flow_control(SpdySession::kFlowControlBasedOnNPN); +} + +// Verify that basic buffering works; when multiple data frames arrive +// at the same time, ensure that we don't notify a read completion for +// each data frame individually. +TEST_P(SpdyNetworkTransactionSpdy21Test, Buffering) { + SpdySession::set_use_flow_control(SpdySession::kDisableFlowControl); + + spdy::SpdyFramer framer; + + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + MockWrite writes[] = { CreateMockWrite(*req) }; + + // 4 data frames in a single read. + scoped_ptr<spdy::SpdyFrame> data_frame( + framer.CreateDataFrame(1, "message", 7, spdy::DATA_FLAG_NONE)); + scoped_ptr<spdy::SpdyFrame> data_frame_fin( + framer.CreateDataFrame(1, "message", 7, spdy::DATA_FLAG_FIN)); + const spdy::SpdyFrame* data_frames[4] = { + data_frame.get(), + data_frame.get(), + data_frame.get(), + data_frame_fin.get() + }; + char combined_data_frames[100]; + int combined_data_frames_len = + CombineFrames(data_frames, arraysize(data_frames), + combined_data_frames, arraysize(combined_data_frames)); + + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + MockRead reads[] = { + CreateMockRead(*resp), + MockRead(ASYNC, ERR_IO_PENDING), // Force a pause + MockRead(ASYNC, combined_data_frames, combined_data_frames_len), + MockRead(ASYNC, 0, 0) // EOF + }; + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData(1, reads, arraysize(reads), + writes, arraysize(writes))); + + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.RunPreTestSetup(); + helper.AddData(data.get()); + HttpNetworkTransaction* trans = helper.trans(); + + TestCompletionCallback callback; + int rv = trans->Start( + &CreateGetRequest(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + TransactionHelperResult out = helper.output(); + out.rv = callback.WaitForResult(); + EXPECT_EQ(out.rv, OK); + + 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. + + // Read Data + TestCompletionCallback read_callback; + + std::string content; + int reads_completed = 0; + do { + // Read small chunks at a time. + const int kSmallReadSize = 14; + scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(kSmallReadSize)); + rv = trans->Read(buf, kSmallReadSize, read_callback.callback()); + if (rv == net::ERR_IO_PENDING) { + data->CompleteRead(); + rv = read_callback.WaitForResult(); + } + if (rv > 0) { + EXPECT_EQ(kSmallReadSize, rv); + content.append(buf->data(), rv); + } else if (rv < 0) { + FAIL() << "Unexpected read error: " << rv; + } + reads_completed++; + } while (rv > 0); + + EXPECT_EQ(3, reads_completed); // Reads are: 14 bytes, 14 bytes, 0 bytes. + + out.response_data.swap(content); + + // Flush the MessageLoop while the SpdySessionDependencies (in particular, the + // MockClientSocketFactory) are still alive. + MessageLoop::current()->RunAllPending(); + + // Verify that we consumed all test data. + helper.VerifyDataConsumed(); + + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("messagemessagemessagemessage", out.response_data); + + SpdySession::set_use_flow_control(SpdySession::kFlowControlBasedOnNPN); +} + +// Verify the case where we buffer data but read it after it has been buffered. +TEST_P(SpdyNetworkTransactionSpdy21Test, BufferedAll) { + spdy::SpdyFramer framer; + + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + MockWrite writes[] = { CreateMockWrite(*req) }; + + // 5 data frames in a single read. + scoped_ptr<spdy::SpdyFrame> syn_reply( + ConstructSpdyGetSynReply(NULL, 0, 1)); + syn_reply->set_flags(spdy::CONTROL_FLAG_NONE); // turn off FIN bit + scoped_ptr<spdy::SpdyFrame> data_frame( + framer.CreateDataFrame(1, "message", 7, spdy::DATA_FLAG_NONE)); + scoped_ptr<spdy::SpdyFrame> data_frame_fin( + framer.CreateDataFrame(1, "message", 7, spdy::DATA_FLAG_FIN)); + const spdy::SpdyFrame* frames[5] = { + syn_reply.get(), + data_frame.get(), + data_frame.get(), + data_frame.get(), + data_frame_fin.get() + }; + char combined_frames[200]; + int combined_frames_len = + CombineFrames(frames, arraysize(frames), + combined_frames, arraysize(combined_frames)); + + MockRead reads[] = { + MockRead(ASYNC, combined_frames, combined_frames_len), + MockRead(ASYNC, 0, 0) // EOF + }; + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData(1, reads, arraysize(reads), + writes, arraysize(writes))); + + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.RunPreTestSetup(); + helper.AddData(data.get()); + HttpNetworkTransaction* trans = helper.trans(); + + TestCompletionCallback callback; + int rv = trans->Start( + &CreateGetRequest(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + TransactionHelperResult out = helper.output(); + out.rv = callback.WaitForResult(); + EXPECT_EQ(out.rv, OK); + + 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. + + // Read Data + TestCompletionCallback read_callback; + + std::string content; + int reads_completed = 0; + do { + // Read small chunks at a time. + const int kSmallReadSize = 14; + scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(kSmallReadSize)); + rv = trans->Read(buf, kSmallReadSize, read_callback.callback()); + if (rv > 0) { + EXPECT_EQ(kSmallReadSize, rv); + content.append(buf->data(), rv); + } else if (rv < 0) { + FAIL() << "Unexpected read error: " << rv; + } + reads_completed++; + } while (rv > 0); + + EXPECT_EQ(3, reads_completed); + + out.response_data.swap(content); + + // Flush the MessageLoop while the SpdySessionDependencies (in particular, the + // MockClientSocketFactory) are still alive. + MessageLoop::current()->RunAllPending(); + + // Verify that we consumed all test data. + helper.VerifyDataConsumed(); + + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("messagemessagemessagemessage", out.response_data); +} + +// Verify the case where we buffer data and close the connection. +TEST_P(SpdyNetworkTransactionSpdy21Test, BufferedClosed) { + spdy::SpdyFramer framer; + + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + MockWrite writes[] = { CreateMockWrite(*req) }; + + // All data frames in a single read. + // NOTE: We don't FIN the stream. + scoped_ptr<spdy::SpdyFrame> data_frame( + framer.CreateDataFrame(1, "message", 7, spdy::DATA_FLAG_NONE)); + const spdy::SpdyFrame* data_frames[4] = { + data_frame.get(), + data_frame.get(), + data_frame.get(), + data_frame.get() + }; + char combined_data_frames[100]; + int combined_data_frames_len = + CombineFrames(data_frames, arraysize(data_frames), + combined_data_frames, arraysize(combined_data_frames)); + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + MockRead reads[] = { + CreateMockRead(*resp), + MockRead(ASYNC, ERR_IO_PENDING), // Force a wait + MockRead(ASYNC, combined_data_frames, combined_data_frames_len), + MockRead(ASYNC, 0, 0) // EOF + }; + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData(1, reads, arraysize(reads), + writes, arraysize(writes))); + + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.RunPreTestSetup(); + helper.AddData(data.get()); + HttpNetworkTransaction* trans = helper.trans(); + + TestCompletionCallback callback; + + int rv = trans->Start( + &CreateGetRequest(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + TransactionHelperResult out = helper.output(); + out.rv = callback.WaitForResult(); + EXPECT_EQ(out.rv, OK); + + 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. + + // Read Data + TestCompletionCallback read_callback; + + std::string content; + int reads_completed = 0; + do { + // Read small chunks at a time. + const int kSmallReadSize = 14; + scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(kSmallReadSize)); + rv = trans->Read(buf, kSmallReadSize, read_callback.callback()); + if (rv == net::ERR_IO_PENDING) { + data->CompleteRead(); + rv = read_callback.WaitForResult(); + } + if (rv > 0) { + content.append(buf->data(), rv); + } else if (rv < 0) { + // This test intentionally closes the connection, and will get an error. + EXPECT_EQ(ERR_CONNECTION_CLOSED, rv); + break; + } + reads_completed++; + } while (rv > 0); + + EXPECT_EQ(0, reads_completed); + + out.response_data.swap(content); + + // Flush the MessageLoop while the SpdySessionDependencies (in particular, the + // MockClientSocketFactory) are still alive. + MessageLoop::current()->RunAllPending(); + + // Verify that we consumed all test data. + helper.VerifyDataConsumed(); +} + +// Verify the case where we buffer data and cancel the transaction. +TEST_P(SpdyNetworkTransactionSpdy21Test, BufferedCancelled) { + spdy::SpdyFramer framer; + + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + MockWrite writes[] = { CreateMockWrite(*req) }; + + // NOTE: We don't FIN the stream. + scoped_ptr<spdy::SpdyFrame> data_frame( + framer.CreateDataFrame(1, "message", 7, spdy::DATA_FLAG_NONE)); + + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + MockRead reads[] = { + CreateMockRead(*resp), + MockRead(ASYNC, ERR_IO_PENDING), // Force a wait + CreateMockRead(*data_frame), + MockRead(ASYNC, 0, 0) // EOF + }; + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData(1, reads, arraysize(reads), + writes, arraysize(writes))); + + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.RunPreTestSetup(); + helper.AddData(data.get()); + HttpNetworkTransaction* trans = helper.trans(); + TestCompletionCallback callback; + + int rv = trans->Start( + &CreateGetRequest(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + TransactionHelperResult out = helper.output(); + out.rv = callback.WaitForResult(); + EXPECT_EQ(out.rv, OK); + + 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. + + // Read Data + TestCompletionCallback read_callback; + + do { + const int kReadSize = 256; + scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(kReadSize)); + rv = trans->Read(buf, kReadSize, read_callback.callback()); + if (rv == net::ERR_IO_PENDING) { + // Complete the read now, which causes buffering to start. + data->CompleteRead(); + // Destroy the transaction, causing the stream to get cancelled + // and orphaning the buffered IO task. + helper.ResetTrans(); + break; + } + // We shouldn't get here in this test. + FAIL() << "Unexpected read: " << rv; + } while (rv > 0); + + // Flush the MessageLoop; this will cause the buffered IO task + // to run for the final time. + MessageLoop::current()->RunAllPending(); + + // Verify that we consumed all test data. + helper.VerifyDataConsumed(); +} + +// Test that if the server requests persistence of settings, that we save +// the settings in the SpdySettingsStorage. +TEST_P(SpdyNetworkTransactionSpdy21Test, SettingsSaved) { + static const SpdyHeaderInfo kSynReplyInfo = { + spdy::SYN_REPLY, // Syn Reply + 1, // Stream ID + 0, // Associated Stream ID + net::ConvertRequestPriorityToSpdyPriority(LOWEST), + // Priority + spdy::CONTROL_FLAG_NONE, // Control Flags + false, // Compressed + spdy::INVALID, // Status + NULL, // Data + 0, // Data Length + spdy::DATA_FLAG_NONE // Data Flags + }; + static const char* const kExtraHeaders[] = { + "status", "200", + "version", "HTTP/1.1" + }; + + BoundNetLog net_log; + NormalSpdyTransactionHelper helper(CreateGetRequest(), net_log, GetParam()); + helper.RunPreTestSetup(); + + // Verify that no settings exist initially. + HostPortPair host_port_pair("www.google.com", helper.port()); + SpdySessionPool* spdy_session_pool = helper.session()->spdy_session_pool(); + EXPECT_TRUE(spdy_session_pool->http_server_properties()->GetSpdySettings( + host_port_pair).empty()); + + // Construct the request. + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + MockWrite writes[] = { CreateMockWrite(*req) }; + + // Construct the reply. + scoped_ptr<spdy::SpdyFrame> reply( + ConstructSpdyPacket(kSynReplyInfo, + kExtraHeaders, + arraysize(kExtraHeaders) / 2, + NULL, + 0)); + + unsigned int kSampleId1 = 0x1; + unsigned int kSampleValue1 = 0x0a0a0a0a; + unsigned int kSampleId2 = 0x2; + unsigned int kSampleValue2 = 0x0b0b0b0b; + unsigned int kSampleId3 = 0xababab; + unsigned int kSampleValue3 = 0x0c0c0c0c; + scoped_ptr<spdy::SpdyFrame> settings_frame; + { + // Construct the SETTINGS frame. + spdy::SpdySettings settings; + spdy::SettingsFlagsAndId setting(0); + // First add a persisted setting + setting.set_flags(spdy::SETTINGS_FLAG_PLEASE_PERSIST); + setting.set_id(kSampleId1); + settings.push_back(std::make_pair(setting, kSampleValue1)); + // Next add a non-persisted setting + setting.set_flags(0); + setting.set_id(kSampleId2); + settings.push_back(std::make_pair(setting, kSampleValue2)); + // Next add another persisted setting + setting.set_flags(spdy::SETTINGS_FLAG_PLEASE_PERSIST); + setting.set_id(kSampleId3); + settings.push_back(std::make_pair(setting, kSampleValue3)); + settings_frame.reset(ConstructSpdySettings(settings)); + } + + scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*reply), + CreateMockRead(*body), + CreateMockRead(*settings_frame), + MockRead(ASYNC, 0, 0) // EOF + }; + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData(1, reads, arraysize(reads), + writes, arraysize(writes))); + helper.AddData(data.get()); + helper.RunDefaultTest(); + helper.VerifyDataConsumed(); + TransactionHelperResult out = helper.output(); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!", out.response_data); + + { + // Verify we had two persisted settings. + spdy::SpdySettings saved_settings = + spdy_session_pool->http_server_properties()->GetSpdySettings( + host_port_pair); + ASSERT_EQ(2u, saved_settings.size()); + + // Verify the first persisted setting. + spdy::SpdySetting setting = saved_settings.front(); + saved_settings.pop_front(); + EXPECT_EQ(spdy::SETTINGS_FLAG_PERSISTED, setting.first.flags()); + EXPECT_EQ(kSampleId1, setting.first.id()); + EXPECT_EQ(kSampleValue1, setting.second); + + // Verify the second persisted setting. + setting = saved_settings.front(); + saved_settings.pop_front(); + EXPECT_EQ(spdy::SETTINGS_FLAG_PERSISTED, setting.first.flags()); + EXPECT_EQ(kSampleId3, setting.first.id()); + EXPECT_EQ(kSampleValue3, setting.second); + } +} + +// Test that when there are settings saved that they are sent back to the +// server upon session establishment. +TEST_P(SpdyNetworkTransactionSpdy21Test, SettingsPlayback) { + static const SpdyHeaderInfo kSynReplyInfo = { + spdy::SYN_REPLY, // Syn Reply + 1, // Stream ID + 0, // Associated Stream ID + net::ConvertRequestPriorityToSpdyPriority(LOWEST), + // Priority + spdy::CONTROL_FLAG_NONE, // Control Flags + false, // Compressed + spdy::INVALID, // Status + NULL, // Data + 0, // Data Length + spdy::DATA_FLAG_NONE // Data Flags + }; + static const char* kExtraHeaders[] = { + "status", "200", + "version", "HTTP/1.1" + }; + + BoundNetLog net_log; + NormalSpdyTransactionHelper helper(CreateGetRequest(), net_log, GetParam()); + helper.RunPreTestSetup(); + + // Verify that no settings exist initially. + HostPortPair host_port_pair("www.google.com", helper.port()); + SpdySessionPool* spdy_session_pool = helper.session()->spdy_session_pool(); + EXPECT_TRUE(spdy_session_pool->http_server_properties()->GetSpdySettings( + host_port_pair).empty()); + + unsigned int kSampleId1 = 0x1; + unsigned int kSampleValue1 = 0x0a0a0a0a; + unsigned int kSampleId2 = 0xababab; + unsigned int kSampleValue2 = 0x0c0c0c0c; + // Manually insert settings into the SpdySettingsStorage here. + { + spdy::SpdySettings settings; + spdy::SettingsFlagsAndId setting(0); + // First add a persisted setting + setting.set_flags(spdy::SETTINGS_FLAG_PLEASE_PERSIST); + setting.set_id(kSampleId1); + settings.push_back(std::make_pair(setting, kSampleValue1)); + // Next add another persisted setting + setting.set_flags(spdy::SETTINGS_FLAG_PLEASE_PERSIST); + setting.set_id(kSampleId2); + settings.push_back(std::make_pair(setting, kSampleValue2)); + + spdy_session_pool->http_server_properties()->SetSpdySettings( + host_port_pair, settings); + } + + EXPECT_EQ(2u, spdy_session_pool->http_server_properties()->GetSpdySettings( + host_port_pair).size()); + + // Construct the SETTINGS frame. + const spdy::SpdySettings& settings = + spdy_session_pool->http_server_properties()->GetSpdySettings( + host_port_pair); + scoped_ptr<spdy::SpdyFrame> settings_frame(ConstructSpdySettings(settings)); + + // Construct the request. + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + + MockWrite writes[] = { + CreateMockWrite(*settings_frame), + CreateMockWrite(*req), + }; + + // Construct the reply. + scoped_ptr<spdy::SpdyFrame> reply( + ConstructSpdyPacket(kSynReplyInfo, + kExtraHeaders, + arraysize(kExtraHeaders) / 2, + NULL, + 0)); + + scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*reply), + CreateMockRead(*body), + MockRead(ASYNC, 0, 0) // EOF + }; + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData(2, reads, arraysize(reads), + writes, arraysize(writes))); + helper.AddData(data.get()); + helper.RunDefaultTest(); + helper.VerifyDataConsumed(); + TransactionHelperResult out = helper.output(); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!", out.response_data); + + { + // Verify we had two persisted settings. + spdy::SpdySettings saved_settings = + spdy_session_pool->http_server_properties()->GetSpdySettings( + host_port_pair); + ASSERT_EQ(2u, saved_settings.size()); + + // Verify the first persisted setting. + spdy::SpdySetting setting = saved_settings.front(); + saved_settings.pop_front(); + EXPECT_EQ(spdy::SETTINGS_FLAG_PERSISTED, setting.first.flags()); + EXPECT_EQ(kSampleId1, setting.first.id()); + EXPECT_EQ(kSampleValue1, setting.second); + + // Verify the second persisted setting. + setting = saved_settings.front(); + saved_settings.pop_front(); + EXPECT_EQ(spdy::SETTINGS_FLAG_PERSISTED, setting.first.flags()); + EXPECT_EQ(kSampleId2, setting.first.id()); + EXPECT_EQ(kSampleValue2, setting.second); + } +} + +TEST_P(SpdyNetworkTransactionSpdy21Test, GoAwayWithActiveStream) { + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + MockWrite writes[] = { CreateMockWrite(*req) }; + + scoped_ptr<spdy::SpdyFrame> go_away(ConstructSpdyGoAway()); + MockRead reads[] = { + CreateMockRead(*go_away), + MockRead(ASYNC, 0, 0), // EOF + }; + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData(1, reads, arraysize(reads), + writes, arraysize(writes))); + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.AddData(data.get()); + helper.RunToCompletion(data.get()); + TransactionHelperResult out = helper.output(); + EXPECT_EQ(ERR_ABORTED, out.rv); +} + +TEST_P(SpdyNetworkTransactionSpdy21Test, CloseWithActiveStream) { + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + MockWrite writes[] = { CreateMockWrite(*req) }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + MockRead reads[] = { + CreateMockRead(*resp), + MockRead(SYNCHRONOUS, 0, 0) // EOF + }; + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData(1, reads, arraysize(reads), + writes, arraysize(writes))); + BoundNetLog log; + NormalSpdyTransactionHelper helper(CreateGetRequest(), + log, GetParam()); + helper.RunPreTestSetup(); + helper.AddData(data.get()); + HttpNetworkTransaction* trans = helper.trans(); + + TestCompletionCallback callback; + TransactionHelperResult out; + out.rv = trans->Start(&CreateGetRequest(), callback.callback(), log); + + EXPECT_EQ(out.rv, ERR_IO_PENDING); + out.rv = callback.WaitForResult(); + EXPECT_EQ(out.rv, OK); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + EXPECT_TRUE(response->headers != NULL); + EXPECT_TRUE(response->was_fetched_via_spdy); + out.rv = ReadTransaction(trans, &out.response_data); + EXPECT_EQ(ERR_CONNECTION_CLOSED, out.rv); + + // Verify that we consumed all test data. + helper.VerifyDataConsumed(); +} + +// Test to make sure we can correctly connect through a proxy. +TEST_P(SpdyNetworkTransactionSpdy21Test, ProxyConnect) { + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.session_deps().reset(new SpdySessionDependencies( + ProxyService::CreateFixedFromPacResult("PROXY myproxy:70"))); + helper.SetSession(make_scoped_refptr( + SpdySessionDependencies::SpdyCreateSession(helper.session_deps().get()))); + helper.RunPreTestSetup(); + HttpNetworkTransaction* trans = helper.trans(); + + const char kConnect443[] = {"CONNECT www.google.com:443 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n\r\n"}; + const char kConnect80[] = {"CONNECT www.google.com:80 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n\r\n"}; + const char kHTTP200[] = {"HTTP/1.1 200 OK\r\n\r\n"}; + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true)); + + MockWrite writes_SPDYNPN[] = { + MockWrite(SYNCHRONOUS, kConnect443, arraysize(kConnect443) - 1, 0), + CreateMockWrite(*req, 2), + }; + MockRead reads_SPDYNPN[] = { + MockRead(SYNCHRONOUS, kHTTP200, arraysize(kHTTP200) - 1, 1), + CreateMockRead(*resp, 3), + CreateMockRead(*body.get(), 4), + MockRead(ASYNC, 0, 0, 5), + }; + + MockWrite writes_SPDYSSL[] = { + MockWrite(SYNCHRONOUS, kConnect80, arraysize(kConnect80) - 1, 0), + CreateMockWrite(*req, 2), + }; + MockRead reads_SPDYSSL[] = { + MockRead(SYNCHRONOUS, kHTTP200, arraysize(kHTTP200) - 1, 1), + CreateMockRead(*resp, 3), + CreateMockRead(*body.get(), 4), + MockRead(ASYNC, 0, 0, 5), + }; + + MockWrite writes_SPDYNOSSL[] = { + CreateMockWrite(*req, 0), + }; + + MockRead reads_SPDYNOSSL[] = { + CreateMockRead(*resp, 1), + CreateMockRead(*body.get(), 2), + MockRead(ASYNC, 0, 0, 3), + }; + + scoped_ptr<OrderedSocketData> data; + switch(GetParam()) { + case SPDYNOSSL: + data.reset(new OrderedSocketData(reads_SPDYNOSSL, + arraysize(reads_SPDYNOSSL), + writes_SPDYNOSSL, + arraysize(writes_SPDYNOSSL))); + break; + case SPDYSSL: + data.reset(new OrderedSocketData(reads_SPDYSSL, + arraysize(reads_SPDYSSL), + writes_SPDYSSL, + arraysize(writes_SPDYSSL))); + break; + case SPDYNPN: + data.reset(new OrderedSocketData(reads_SPDYNPN, + arraysize(reads_SPDYNPN), + writes_SPDYNPN, + arraysize(writes_SPDYNPN))); + break; + default: + NOTREACHED(); + } + + helper.AddData(data.get()); + TestCompletionCallback callback; + + int rv = trans->Start( + &CreateGetRequest(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(0, rv); + + // Verify the SYN_REPLY. + HttpResponseInfo response = *trans->GetResponseInfo(); + EXPECT_TRUE(response.headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); + + std::string response_data; + ASSERT_EQ(OK, ReadTransaction(trans, &response_data)); + EXPECT_EQ("hello!", response_data); + helper.VerifyDataConsumed(); +} + +// Test to make sure we can correctly connect through a proxy to www.google.com, +// if there already exists a direct spdy connection to www.google.com. See +// http://crbug.com/49874 +TEST_P(SpdyNetworkTransactionSpdy21Test, DirectConnectProxyReconnect) { + // When setting up the first transaction, we store the SpdySessionPool so that + // we can use the same pool in the second transaction. + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + + // Use a proxy service which returns a proxy fallback list from DIRECT to + // myproxy:70. For this test there will be no fallback, so it is equivalent + // to simply DIRECT. The reason for appending the second proxy is to verify + // that the session pool key used does is just "DIRECT". + helper.session_deps().reset(new SpdySessionDependencies( + ProxyService::CreateFixedFromPacResult("DIRECT; PROXY myproxy:70"))); + helper.SetSession(make_scoped_refptr( + SpdySessionDependencies::SpdyCreateSession(helper.session_deps().get()))); + + SpdySessionPool* spdy_session_pool = helper.session()->spdy_session_pool(); + helper.RunPreTestSetup(); + + // Construct and send a simple GET request. + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + MockWrite writes[] = { + CreateMockWrite(*req, 1), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*resp, 2), + CreateMockRead(*body, 3), + MockRead(ASYNC, ERR_IO_PENDING, 4), // Force a pause + MockRead(ASYNC, 0, 5) // EOF + }; + scoped_ptr<OrderedSocketData> data( + new OrderedSocketData(reads, arraysize(reads), + writes, arraysize(writes))); + helper.AddData(data.get()); + HttpNetworkTransaction* trans = helper.trans(); + + TestCompletionCallback callback; + TransactionHelperResult out; + out.rv = trans->Start( + &CreateGetRequest(), callback.callback(), BoundNetLog()); + + EXPECT_EQ(out.rv, ERR_IO_PENDING); + out.rv = callback.WaitForResult(); + EXPECT_EQ(out.rv, OK); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + EXPECT_TRUE(response->headers != NULL); + EXPECT_TRUE(response->was_fetched_via_spdy); + out.rv = ReadTransaction(trans, &out.response_data); + EXPECT_EQ(OK, out.rv); + out.status_line = response->headers->GetStatusLine(); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!", out.response_data); + + // Check that the SpdySession is still in the SpdySessionPool. + HostPortPair host_port_pair("www.google.com", helper.port()); + HostPortProxyPair session_pool_key_direct( + host_port_pair, ProxyServer::Direct()); + EXPECT_TRUE(spdy_session_pool->HasSession(session_pool_key_direct)); + HostPortProxyPair session_pool_key_proxy( + host_port_pair, + ProxyServer::FromURI("www.foo.com", ProxyServer::SCHEME_HTTP)); + EXPECT_FALSE(spdy_session_pool->HasSession(session_pool_key_proxy)); + + // Set up data for the proxy connection. + const char kConnect443[] = {"CONNECT www.google.com:443 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n\r\n"}; + const char kConnect80[] = {"CONNECT www.google.com:80 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n\r\n"}; + const char kHTTP200[] = {"HTTP/1.1 200 OK\r\n\r\n"}; + scoped_ptr<spdy::SpdyFrame> req2(ConstructSpdyGet( + "http://www.google.com/foo.dat", false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> body2(ConstructSpdyBodyFrame(1, true)); + + MockWrite writes_SPDYNPN[] = { + MockWrite(SYNCHRONOUS, kConnect443, arraysize(kConnect443) - 1, 0), + CreateMockWrite(*req2, 2), + }; + MockRead reads_SPDYNPN[] = { + MockRead(SYNCHRONOUS, kHTTP200, arraysize(kHTTP200) - 1, 1), + CreateMockRead(*resp2, 3), + CreateMockRead(*body2, 4), + MockRead(ASYNC, 0, 5) // EOF + }; + + MockWrite writes_SPDYNOSSL[] = { + CreateMockWrite(*req2, 0), + }; + MockRead reads_SPDYNOSSL[] = { + CreateMockRead(*resp2, 1), + CreateMockRead(*body2, 2), + MockRead(ASYNC, 0, 3) // EOF + }; + + MockWrite writes_SPDYSSL[] = { + MockWrite(SYNCHRONOUS, kConnect80, arraysize(kConnect80) - 1, 0), + CreateMockWrite(*req2, 2), + }; + MockRead reads_SPDYSSL[] = { + MockRead(SYNCHRONOUS, kHTTP200, arraysize(kHTTP200) - 1, 1), + CreateMockRead(*resp2, 3), + CreateMockRead(*body2, 4), + MockRead(ASYNC, 0, 0, 5), + }; + + scoped_ptr<OrderedSocketData> data_proxy; + switch(GetParam()) { + case SPDYNPN: + data_proxy.reset(new OrderedSocketData(reads_SPDYNPN, + arraysize(reads_SPDYNPN), + writes_SPDYNPN, + arraysize(writes_SPDYNPN))); + break; + case SPDYNOSSL: + data_proxy.reset(new OrderedSocketData(reads_SPDYNOSSL, + arraysize(reads_SPDYNOSSL), + writes_SPDYNOSSL, + arraysize(writes_SPDYNOSSL))); + break; + case SPDYSSL: + data_proxy.reset(new OrderedSocketData(reads_SPDYSSL, + arraysize(reads_SPDYSSL), + writes_SPDYSSL, + arraysize(writes_SPDYSSL))); + break; + default: + NOTREACHED(); + } + + // Create another request to www.google.com, but this time through a proxy. + HttpRequestInfo request_proxy; + request_proxy.method = "GET"; + request_proxy.url = GURL("http://www.google.com/foo.dat"); + request_proxy.load_flags = 0; + scoped_ptr<SpdySessionDependencies> ssd_proxy(new SpdySessionDependencies()); + // Ensure that this transaction uses the same SpdySessionPool. + scoped_refptr<HttpNetworkSession> session_proxy( + SpdySessionDependencies::SpdyCreateSession(ssd_proxy.get())); + NormalSpdyTransactionHelper helper_proxy(request_proxy, + BoundNetLog(), GetParam()); + HttpNetworkSessionPeer session_peer(session_proxy); + scoped_ptr<net::ProxyService> proxy_service( + ProxyService::CreateFixedFromPacResult("PROXY myproxy:70")); + session_peer.SetProxyService(proxy_service.get()); + helper_proxy.session_deps().swap(ssd_proxy); + helper_proxy.SetSession(session_proxy); + helper_proxy.RunPreTestSetup(); + helper_proxy.AddData(data_proxy.get()); + + HttpNetworkTransaction* trans_proxy = helper_proxy.trans(); + TestCompletionCallback callback_proxy; + int rv = trans_proxy->Start( + &request_proxy, callback_proxy.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback_proxy.WaitForResult(); + EXPECT_EQ(0, rv); + + HttpResponseInfo response_proxy = *trans_proxy->GetResponseInfo(); + EXPECT_TRUE(response_proxy.headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response_proxy.headers->GetStatusLine()); + + std::string response_data; + ASSERT_EQ(OK, ReadTransaction(trans_proxy, &response_data)); + EXPECT_EQ("hello!", response_data); + + data->CompleteRead(); + helper_proxy.VerifyDataConsumed(); +} + +// When we get a TCP-level RST, we need to retry a HttpNetworkTransaction +// on a new connection, if the connection was previously known to be good. +// This can happen when a server reboots without saying goodbye, or when +// we're behind a NAT that masked the RST. +TEST_P(SpdyNetworkTransactionSpdy21Test, VerifyRetryOnConnectionReset) { + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*resp), + CreateMockRead(*body), + MockRead(ASYNC, ERR_IO_PENDING), + MockRead(ASYNC, ERR_CONNECTION_RESET), + }; + + MockRead reads2[] = { + CreateMockRead(*resp), + CreateMockRead(*body), + MockRead(ASYNC, 0, 0) // EOF + }; + + // This test has a couple of variants. + enum { + // Induce the RST while waiting for our transaction to send. + VARIANT_RST_DURING_SEND_COMPLETION, + // Induce the RST while waiting for our transaction to read. + // In this case, the send completed - everything copied into the SNDBUF. + VARIANT_RST_DURING_READ_COMPLETION + }; + + for (int variant = VARIANT_RST_DURING_SEND_COMPLETION; + variant <= VARIANT_RST_DURING_READ_COMPLETION; + ++variant) { + scoped_ptr<DelayedSocketData> data1( + new DelayedSocketData(1, reads, arraysize(reads), + NULL, 0)); + + scoped_ptr<DelayedSocketData> data2( + new DelayedSocketData(1, reads2, arraysize(reads2), + NULL, 0)); + + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.AddData(data1.get()); + helper.AddData(data2.get()); + helper.RunPreTestSetup(); + + for (int i = 0; i < 2; ++i) { + scoped_ptr<HttpNetworkTransaction> trans( + new HttpNetworkTransaction(helper.session())); + + TestCompletionCallback callback; + int rv = trans->Start( + &helper.request(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + // On the second transaction, we trigger the RST. + if (i == 1) { + if (variant == VARIANT_RST_DURING_READ_COMPLETION) { + // Writes to the socket complete asynchronously on SPDY by running + // through the message loop. Complete the write here. + MessageLoop::current()->RunAllPending(); + } + + // Now schedule the ERR_CONNECTION_RESET. + EXPECT_EQ(3u, data1->read_index()); + data1->CompleteRead(); + EXPECT_EQ(4u, data1->read_index()); + } + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(response->headers != NULL); + EXPECT_TRUE(response->was_fetched_via_spdy); + std::string response_data; + rv = ReadTransaction(trans.get(), &response_data); + EXPECT_EQ(OK, rv); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + EXPECT_EQ("hello!", response_data); + } + + helper.VerifyDataConsumed(); + } +} + +// Test that turning SPDY on and off works properly. +TEST_P(SpdyNetworkTransactionSpdy21Test, SpdyOnOffToggle) { + net::HttpStreamFactory::set_spdy_enabled(true); + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + MockWrite spdy_writes[] = { CreateMockWrite(*req) }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true)); + MockRead spdy_reads[] = { + CreateMockRead(*resp), + CreateMockRead(*body), + MockRead(ASYNC, 0, 0) // EOF + }; + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData(1, + spdy_reads, arraysize(spdy_reads), + spdy_writes, arraysize(spdy_writes))); + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.RunToCompletion(data.get()); + TransactionHelperResult out = helper.output(); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!", out.response_data); + + net::HttpStreamFactory::set_spdy_enabled(false); + MockRead http_reads[] = { + MockRead("HTTP/1.1 200 OK\r\n\r\n"), + MockRead("hello from http"), + MockRead(SYNCHRONOUS, OK), + }; + scoped_ptr<DelayedSocketData> data2( + new DelayedSocketData(1, http_reads, arraysize(http_reads), + NULL, 0)); + NormalSpdyTransactionHelper helper2(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper2.SetSpdyDisabled(); + helper2.RunToCompletion(data2.get()); + TransactionHelperResult out2 = helper2.output(); + EXPECT_EQ(OK, out2.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out2.status_line); + EXPECT_EQ("hello from http", out2.response_data); + + net::HttpStreamFactory::set_spdy_enabled(true); +} + +// Tests that Basic authentication works over SPDY +TEST_P(SpdyNetworkTransactionSpdy21Test, SpdyBasicAuth) { + net::HttpStreamFactory::set_spdy_enabled(true); + + // The first request will be a bare GET, the second request will be a + // GET with an Authorization header. + scoped_ptr<spdy::SpdyFrame> req_get( + ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + const char* const kExtraAuthorizationHeaders[] = { + "authorization", + "Basic Zm9vOmJhcg==", + }; + scoped_ptr<spdy::SpdyFrame> req_get_authorization( + ConstructSpdyGet( + kExtraAuthorizationHeaders, + arraysize(kExtraAuthorizationHeaders) / 2, + false, 3, LOWEST)); + MockWrite spdy_writes[] = { + CreateMockWrite(*req_get, 1), + CreateMockWrite(*req_get_authorization, 4), + }; + + // The first response is a 401 authentication challenge, and the second + // response will be a 200 response since the second request includes a valid + // Authorization header. + const char* const kExtraAuthenticationHeaders[] = { + "www-authenticate", + "Basic realm=\"MyRealm\"" + }; + scoped_ptr<spdy::SpdyFrame> resp_authentication( + ConstructSpdySynReplyError( + "401 Authentication Required", + kExtraAuthenticationHeaders, + arraysize(kExtraAuthenticationHeaders) / 2, + 1)); + scoped_ptr<spdy::SpdyFrame> body_authentication( + ConstructSpdyBodyFrame(1, true)); + scoped_ptr<spdy::SpdyFrame> resp_data(ConstructSpdyGetSynReply(NULL, 0, 3)); + scoped_ptr<spdy::SpdyFrame> body_data(ConstructSpdyBodyFrame(3, true)); + MockRead spdy_reads[] = { + CreateMockRead(*resp_authentication, 2), + CreateMockRead(*body_authentication, 3), + CreateMockRead(*resp_data, 5), + CreateMockRead(*body_data, 6), + MockRead(ASYNC, 0, 7), + }; + + scoped_ptr<OrderedSocketData> data( + new OrderedSocketData(spdy_reads, arraysize(spdy_reads), + spdy_writes, arraysize(spdy_writes))); + HttpRequestInfo request(CreateGetRequest()); + BoundNetLog net_log; + NormalSpdyTransactionHelper helper(request, net_log, GetParam()); + + helper.RunPreTestSetup(); + helper.AddData(data.get()); + HttpNetworkTransaction* trans = helper.trans(); + TestCompletionCallback callback; + const int rv_start = trans->Start(&request, callback.callback(), net_log); + EXPECT_EQ(ERR_IO_PENDING, rv_start); + const int rv_start_complete = callback.WaitForResult(); + EXPECT_EQ(OK, rv_start_complete); + + // Make sure the response has an auth challenge. + const HttpResponseInfo* const response_start = trans->GetResponseInfo(); + ASSERT_TRUE(response_start != NULL); + ASSERT_TRUE(response_start->headers != NULL); + EXPECT_EQ(401, response_start->headers->response_code()); + EXPECT_TRUE(response_start->was_fetched_via_spdy); + AuthChallengeInfo* auth_challenge = response_start->auth_challenge.get(); + ASSERT_TRUE(auth_challenge != NULL); + EXPECT_FALSE(auth_challenge->is_proxy); + EXPECT_EQ("basic", auth_challenge->scheme); + EXPECT_EQ("MyRealm", auth_challenge->realm); + + // Restart with a username/password. + AuthCredentials credentials(ASCIIToUTF16("foo"), ASCIIToUTF16("bar")); + TestCompletionCallback callback_restart; + const int rv_restart = trans->RestartWithAuth( + credentials, callback_restart.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv_restart); + const int rv_restart_complete = callback_restart.WaitForResult(); + EXPECT_EQ(OK, rv_restart_complete); + // TODO(cbentzel): This is actually the same response object as before, but + // data has changed. + const HttpResponseInfo* const response_restart = trans->GetResponseInfo(); + ASSERT_TRUE(response_restart != NULL); + ASSERT_TRUE(response_restart->headers != NULL); + EXPECT_EQ(200, response_restart->headers->response_code()); + EXPECT_TRUE(response_restart->auth_challenge.get() == NULL); +} + +TEST_P(SpdyNetworkTransactionSpdy21Test, ServerPushWithHeaders) { + static const unsigned char kPushBodyFrame[] = { + 0x00, 0x00, 0x00, 0x02, // header, ID + 0x01, 0x00, 0x00, 0x06, // FIN, length + 'p', 'u', 's', 'h', 'e', 'd' // "pushed" + }; + scoped_ptr<spdy::SpdyFrame> + stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> + stream1_body(ConstructSpdyBodyFrame(1, true)); + MockWrite writes[] = { + CreateMockWrite(*stream1_syn, 1), + }; + + static const char* const kInitialHeaders[] = { + "url", + "http://www.google.com/foo.dat", + }; + static const char* const kLateHeaders[] = { + "hello", + "bye", + "status", + "200", + "version", + "HTTP/1.1" + }; + scoped_ptr<spdy::SpdyFrame> + stream2_syn(ConstructSpdyControlFrame(kInitialHeaders, + arraysize(kInitialHeaders) / 2, + false, + 2, + LOWEST, + spdy::SYN_STREAM, + spdy::CONTROL_FLAG_NONE, + NULL, + 0, + 1)); + scoped_ptr<spdy::SpdyFrame> + stream2_headers(ConstructSpdyControlFrame(kLateHeaders, + arraysize(kLateHeaders) / 2, + false, + 2, + LOWEST, + spdy::HEADERS, + spdy::CONTROL_FLAG_NONE, + NULL, + 0, + 0)); + + scoped_ptr<spdy::SpdyFrame> + stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1)); + MockRead reads[] = { + CreateMockRead(*stream1_reply, 2), + CreateMockRead(*stream2_syn, 3), + CreateMockRead(*stream2_headers, 4), + CreateMockRead(*stream1_body, 5, SYNCHRONOUS), + MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame), + arraysize(kPushBodyFrame), 6), + MockRead(ASYNC, ERR_IO_PENDING, 7), // Force a pause + }; + + HttpResponseInfo response; + HttpResponseInfo response2; + std::string expected_push_result("pushed"); + scoped_ptr<OrderedSocketData> data(new OrderedSocketData( + reads, + arraysize(reads), + writes, + arraysize(writes))); + RunServerPushTest(data.get(), + &response, + &response2, + expected_push_result); + + // Verify the SYN_REPLY. + EXPECT_TRUE(response.headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); + + // Verify the pushed stream. + EXPECT_TRUE(response2.headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine()); +} + +TEST_P(SpdyNetworkTransactionSpdy21Test, ServerPushClaimBeforeHeaders) { + // We push a stream and attempt to claim it before the headers come down. + static const unsigned char kPushBodyFrame[] = { + 0x00, 0x00, 0x00, 0x02, // header, ID + 0x01, 0x00, 0x00, 0x06, // FIN, length + 'p', 'u', 's', 'h', 'e', 'd' // "pushed" + }; + scoped_ptr<spdy::SpdyFrame> + stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> + stream1_body(ConstructSpdyBodyFrame(1, true)); + MockWrite writes[] = { + CreateMockWrite(*stream1_syn, 0, SYNCHRONOUS), + }; + + static const char* const kInitialHeaders[] = { + "url", + "http://www.google.com/foo.dat", + }; + static const char* const kLateHeaders[] = { + "hello", + "bye", + "status", + "200", + "version", + "HTTP/1.1" + }; + scoped_ptr<spdy::SpdyFrame> + stream2_syn(ConstructSpdyControlFrame(kInitialHeaders, + arraysize(kInitialHeaders) / 2, + false, + 2, + LOWEST, + spdy::SYN_STREAM, + spdy::CONTROL_FLAG_NONE, + NULL, + 0, + 1)); + scoped_ptr<spdy::SpdyFrame> + stream2_headers(ConstructSpdyControlFrame(kLateHeaders, + arraysize(kLateHeaders) / 2, + false, + 2, + LOWEST, + spdy::HEADERS, + spdy::CONTROL_FLAG_NONE, + NULL, + 0, + 0)); + + scoped_ptr<spdy::SpdyFrame> + stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1)); + MockRead reads[] = { + CreateMockRead(*stream1_reply, 1), + CreateMockRead(*stream2_syn, 2), + CreateMockRead(*stream1_body, 3), + CreateMockRead(*stream2_headers, 4), + MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame), + arraysize(kPushBodyFrame), 5), + MockRead(ASYNC, 0, 5), // EOF + }; + + HttpResponseInfo response; + HttpResponseInfo response2; + std::string expected_push_result("pushed"); + scoped_refptr<DeterministicSocketData> data(new DeterministicSocketData( + reads, + arraysize(reads), + writes, + arraysize(writes))); + + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.SetDeterministic(); + helper.AddDeterministicData(static_cast<DeterministicSocketData*>(data)); + helper.RunPreTestSetup(); + + HttpNetworkTransaction* trans = helper.trans(); + + // Run until we've received the primary SYN_STREAM, the pushed SYN_STREAM, + // and the body of the primary stream, but before we've received the HEADERS + // for the pushed stream. + data->SetStop(3); + + // Start the transaction. + TestCompletionCallback callback; + int rv = trans->Start( + &CreateGetRequest(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + data->Run(); + rv = callback.WaitForResult(); + EXPECT_EQ(0, rv); + + // Request the pushed path. At this point, we've received the push, but the + // headers are not yet complete. + scoped_ptr<HttpNetworkTransaction> trans2( + new HttpNetworkTransaction(helper.session())); + rv = trans2->Start( + &CreateGetPushRequest(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + data->RunFor(3); + MessageLoop::current()->RunAllPending(); + + // Read the server push body. + std::string result2; + ReadResult(trans2.get(), data.get(), &result2); + // Read the response body. + std::string result; + ReadResult(trans, data, &result); + + // Verify that we consumed all test data. + EXPECT_TRUE(data->at_read_eof()); + EXPECT_TRUE(data->at_write_eof()); + + // Verify that the received push data is same as the expected push data. + EXPECT_EQ(result2.compare(expected_push_result), 0) + << "Received data: " + << result2 + << "||||| Expected data: " + << expected_push_result; + + // Verify the SYN_REPLY. + // Copy the response info, because trans goes away. + response = *trans->GetResponseInfo(); + response2 = *trans2->GetResponseInfo(); + + VerifyStreamsClosed(helper); + + // Verify the SYN_REPLY. + EXPECT_TRUE(response.headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); + + // Verify the pushed stream. + EXPECT_TRUE(response2.headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine()); +} + +TEST_P(SpdyNetworkTransactionSpdy21Test, ServerPushWithTwoHeaderFrames) { + // We push a stream and attempt to claim it before the headers come down. + static const unsigned char kPushBodyFrame[] = { + 0x00, 0x00, 0x00, 0x02, // header, ID + 0x01, 0x00, 0x00, 0x06, // FIN, length + 'p', 'u', 's', 'h', 'e', 'd' // "pushed" + }; + scoped_ptr<spdy::SpdyFrame> + stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> + stream1_body(ConstructSpdyBodyFrame(1, true)); + MockWrite writes[] = { + CreateMockWrite(*stream1_syn, 0, SYNCHRONOUS), + }; + + static const char* const kInitialHeaders[] = { + "url", + "http://www.google.com/foo.dat", + }; + static const char* const kMiddleHeaders[] = { + "hello", + "bye", + }; + static const char* const kLateHeaders[] = { + "status", + "200", + "version", + "HTTP/1.1" + }; + scoped_ptr<spdy::SpdyFrame> + stream2_syn(ConstructSpdyControlFrame(kInitialHeaders, + arraysize(kInitialHeaders) / 2, + false, + 2, + LOWEST, + spdy::SYN_STREAM, + spdy::CONTROL_FLAG_NONE, + NULL, + 0, + 1)); + scoped_ptr<spdy::SpdyFrame> + stream2_headers1(ConstructSpdyControlFrame(kMiddleHeaders, + arraysize(kMiddleHeaders) / 2, + false, + 2, + LOWEST, + spdy::HEADERS, + spdy::CONTROL_FLAG_NONE, + NULL, + 0, + 0)); + scoped_ptr<spdy::SpdyFrame> + stream2_headers2(ConstructSpdyControlFrame(kLateHeaders, + arraysize(kLateHeaders) / 2, + false, + 2, + LOWEST, + spdy::HEADERS, + spdy::CONTROL_FLAG_NONE, + NULL, + 0, + 0)); + + scoped_ptr<spdy::SpdyFrame> + stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1)); + MockRead reads[] = { + CreateMockRead(*stream1_reply, 1), + CreateMockRead(*stream2_syn, 2), + CreateMockRead(*stream1_body, 3), + CreateMockRead(*stream2_headers1, 4), + CreateMockRead(*stream2_headers2, 5), + MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame), + arraysize(kPushBodyFrame), 6), + MockRead(ASYNC, 0, 6), // EOF + }; + + HttpResponseInfo response; + HttpResponseInfo response2; + std::string expected_push_result("pushed"); + scoped_refptr<DeterministicSocketData> data(new DeterministicSocketData( + reads, + arraysize(reads), + writes, + arraysize(writes))); + + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.SetDeterministic(); + helper.AddDeterministicData(static_cast<DeterministicSocketData*>(data)); + helper.RunPreTestSetup(); + + HttpNetworkTransaction* trans = helper.trans(); + + // Run until we've received the primary SYN_STREAM, the pushed SYN_STREAM, + // the first HEADERS frame, and the body of the primary stream, but before + // we've received the final HEADERS for the pushed stream. + data->SetStop(4); + + // Start the transaction. + TestCompletionCallback callback; + int rv = trans->Start( + &CreateGetRequest(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + data->Run(); + rv = callback.WaitForResult(); + EXPECT_EQ(0, rv); + + // Request the pushed path. At this point, we've received the push, but the + // headers are not yet complete. + scoped_ptr<HttpNetworkTransaction> trans2( + new HttpNetworkTransaction(helper.session())); + rv = trans2->Start( + &CreateGetPushRequest(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + data->RunFor(3); + MessageLoop::current()->RunAllPending(); + + // Read the server push body. + std::string result2; + ReadResult(trans2.get(), data, &result2); + // Read the response body. + std::string result; + ReadResult(trans, data, &result); + + // Verify that we consumed all test data. + EXPECT_TRUE(data->at_read_eof()); + EXPECT_TRUE(data->at_write_eof()); + + // Verify that the received push data is same as the expected push data. + EXPECT_EQ(result2.compare(expected_push_result), 0) + << "Received data: " + << result2 + << "||||| Expected data: " + << expected_push_result; + + // Verify the SYN_REPLY. + // Copy the response info, because trans goes away. + response = *trans->GetResponseInfo(); + response2 = *trans2->GetResponseInfo(); + + VerifyStreamsClosed(helper); + + // Verify the SYN_REPLY. + EXPECT_TRUE(response.headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); + + // Verify the pushed stream. + EXPECT_TRUE(response2.headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine()); + + // Verify we got all the headers + EXPECT_TRUE(response2.headers->HasHeaderValue( + "url", + "http://www.google.com/foo.dat")); + EXPECT_TRUE(response2.headers->HasHeaderValue("hello", "bye")); + EXPECT_TRUE(response2.headers->HasHeaderValue("status", "200")); + EXPECT_TRUE(response2.headers->HasHeaderValue("version", "HTTP/1.1")); +} + +TEST_P(SpdyNetworkTransactionSpdy21Test, SynReplyWithHeaders) { + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + MockWrite writes[] = { CreateMockWrite(*req) }; + + static const char* const kInitialHeaders[] = { + "status", + "200 OK", + "version", + "HTTP/1.1" + }; + static const char* const kLateHeaders[] = { + "hello", + "bye", + }; + scoped_ptr<spdy::SpdyFrame> + stream1_reply(ConstructSpdyControlFrame(kInitialHeaders, + arraysize(kInitialHeaders) / 2, + false, + 1, + LOWEST, + spdy::SYN_REPLY, + spdy::CONTROL_FLAG_NONE, + NULL, + 0, + 0)); + scoped_ptr<spdy::SpdyFrame> + stream1_headers(ConstructSpdyControlFrame(kLateHeaders, + arraysize(kLateHeaders) / 2, + false, + 1, + LOWEST, + spdy::HEADERS, + spdy::CONTROL_FLAG_NONE, + NULL, + 0, + 0)); + scoped_ptr<spdy::SpdyFrame> stream1_body(ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*stream1_reply), + CreateMockRead(*stream1_headers), + CreateMockRead(*stream1_body), + MockRead(ASYNC, 0, 0) // EOF + }; + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData(1, reads, arraysize(reads), + writes, arraysize(writes))); + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.RunToCompletion(data.get()); + TransactionHelperResult out = helper.output(); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!", out.response_data); +} + +TEST_P(SpdyNetworkTransactionSpdy21Test, SynReplyWithLateHeaders) { + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + MockWrite writes[] = { CreateMockWrite(*req) }; + + static const char* const kInitialHeaders[] = { + "status", + "200 OK", + "version", + "HTTP/1.1" + }; + static const char* const kLateHeaders[] = { + "hello", + "bye", + }; + scoped_ptr<spdy::SpdyFrame> + stream1_reply(ConstructSpdyControlFrame(kInitialHeaders, + arraysize(kInitialHeaders) / 2, + false, + 1, + LOWEST, + spdy::SYN_REPLY, + spdy::CONTROL_FLAG_NONE, + NULL, + 0, + 0)); + scoped_ptr<spdy::SpdyFrame> + stream1_headers(ConstructSpdyControlFrame(kLateHeaders, + arraysize(kLateHeaders) / 2, + false, + 1, + LOWEST, + spdy::HEADERS, + spdy::CONTROL_FLAG_NONE, + NULL, + 0, + 0)); + scoped_ptr<spdy::SpdyFrame> stream1_body(ConstructSpdyBodyFrame(1, false)); + scoped_ptr<spdy::SpdyFrame> stream1_body2(ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*stream1_reply), + CreateMockRead(*stream1_body), + CreateMockRead(*stream1_headers), + CreateMockRead(*stream1_body2), + MockRead(ASYNC, 0, 0) // EOF + }; + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData(1, reads, arraysize(reads), + writes, arraysize(writes))); + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.RunToCompletion(data.get()); + TransactionHelperResult out = helper.output(); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!hello!", out.response_data); +} + +TEST_P(SpdyNetworkTransactionSpdy21Test, SynReplyWithDuplicateLateHeaders) { + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + MockWrite writes[] = { CreateMockWrite(*req) }; + + static const char* const kInitialHeaders[] = { + "status", + "200 OK", + "version", + "HTTP/1.1" + }; + static const char* const kLateHeaders[] = { + "status", + "500 Server Error", + }; + scoped_ptr<spdy::SpdyFrame> + stream1_reply(ConstructSpdyControlFrame(kInitialHeaders, + arraysize(kInitialHeaders) / 2, + false, + 1, + LOWEST, + spdy::SYN_REPLY, + spdy::CONTROL_FLAG_NONE, + NULL, + 0, + 0)); + scoped_ptr<spdy::SpdyFrame> + stream1_headers(ConstructSpdyControlFrame(kLateHeaders, + arraysize(kLateHeaders) / 2, + false, + 1, + LOWEST, + spdy::HEADERS, + spdy::CONTROL_FLAG_NONE, + NULL, + 0, + 0)); + scoped_ptr<spdy::SpdyFrame> stream1_body(ConstructSpdyBodyFrame(1, false)); + scoped_ptr<spdy::SpdyFrame> stream1_body2(ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*stream1_reply), + CreateMockRead(*stream1_body), + CreateMockRead(*stream1_headers), + CreateMockRead(*stream1_body2), + MockRead(ASYNC, 0, 0) // EOF + }; + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData(1, reads, arraysize(reads), + writes, arraysize(writes))); + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.RunToCompletion(data.get()); + TransactionHelperResult out = helper.output(); + EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, out.rv); +} + +TEST_P(SpdyNetworkTransactionSpdy21Test, ServerPushCrossOriginCorrectness) { + // In this test we want to verify that we can't accidentally push content + // which can't be pushed by this content server. + // This test assumes that: + // - if we're requesting http://www.foo.com/barbaz + // - the browser has made a connection to "www.foo.com". + + // A list of the URL to fetch, followed by the URL being pushed. + static const char* const kTestCases[] = { + "http://www.google.com/foo.html", + "http://www.google.com:81/foo.js", // Bad port + + "http://www.google.com/foo.html", + "https://www.google.com/foo.js", // Bad protocol + + "http://www.google.com/foo.html", + "ftp://www.google.com/foo.js", // Invalid Protocol + + "http://www.google.com/foo.html", + "http://blat.www.google.com/foo.js", // Cross subdomain + + "http://www.google.com/foo.html", + "http://www.foo.com/foo.js", // Cross domain + }; + + + static const unsigned char kPushBodyFrame[] = { + 0x00, 0x00, 0x00, 0x02, // header, ID + 0x01, 0x00, 0x00, 0x06, // FIN, length + 'p', 'u', 's', 'h', 'e', 'd' // "pushed" + }; + + for (size_t index = 0; index < arraysize(kTestCases); index += 2) { + const char* url_to_fetch = kTestCases[index]; + const char* url_to_push = kTestCases[index + 1]; + + scoped_ptr<spdy::SpdyFrame> + stream1_syn(ConstructSpdyGet(url_to_fetch, false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> + stream1_body(ConstructSpdyBodyFrame(1, true)); + scoped_ptr<spdy::SpdyFrame> push_rst( + ConstructSpdyRstStream(2, spdy::REFUSED_STREAM)); + MockWrite writes[] = { + CreateMockWrite(*stream1_syn, 1), + CreateMockWrite(*push_rst, 4), + }; + + scoped_ptr<spdy::SpdyFrame> + stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> + stream2_syn(ConstructSpdyPush(NULL, + 0, + 2, + 1, + url_to_push)); + scoped_ptr<spdy::SpdyFrame> rst( + ConstructSpdyRstStream(2, spdy::CANCEL)); + + MockRead reads[] = { + CreateMockRead(*stream1_reply, 2), + CreateMockRead(*stream2_syn, 3), + CreateMockRead(*stream1_body, 5, SYNCHRONOUS), + MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame), + arraysize(kPushBodyFrame), 6), + MockRead(ASYNC, ERR_IO_PENDING, 7), // Force a pause + }; + + HttpResponseInfo response; + scoped_ptr<OrderedSocketData> data(new OrderedSocketData( + reads, + arraysize(reads), + writes, + arraysize(writes))); + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL(url_to_fetch); + request.load_flags = 0; + NormalSpdyTransactionHelper helper(request, + BoundNetLog(), GetParam()); + helper.RunPreTestSetup(); + helper.AddData(data.get()); + + HttpNetworkTransaction* trans = helper.trans(); + + // Start the transaction with basic parameters. + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback.WaitForResult(); + + // Read the response body. + std::string result; + ReadResult(trans, data.get(), &result); + + // Verify that we consumed all test data. + EXPECT_TRUE(data->at_read_eof()); + EXPECT_TRUE(data->at_write_eof()); + + // Verify the SYN_REPLY. + // Copy the response info, because trans goes away. + response = *trans->GetResponseInfo(); + + VerifyStreamsClosed(helper); + + // Verify the SYN_REPLY. + EXPECT_TRUE(response.headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); + } +} + +TEST_P(SpdyNetworkTransactionSpdy21Test, RetryAfterRefused) { + // Construct the request. + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 3, LOWEST)); + MockWrite writes[] = { + CreateMockWrite(*req, 1), + CreateMockWrite(*req2, 3), + }; + + scoped_ptr<spdy::SpdyFrame> refused( + ConstructSpdyRstStream(1, spdy::REFUSED_STREAM)); + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 3)); + scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(3, true)); + MockRead reads[] = { + CreateMockRead(*refused, 2), + CreateMockRead(*resp, 4), + CreateMockRead(*body, 5), + MockRead(ASYNC, 0, 6) // EOF + }; + + scoped_ptr<OrderedSocketData> data( + new OrderedSocketData(reads, arraysize(reads), + writes, arraysize(writes))); + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + + helper.RunPreTestSetup(); + helper.AddData(data.get()); + + HttpNetworkTransaction* trans = helper.trans(); + + // Start the transaction with basic parameters. + TestCompletionCallback callback; + int rv = trans->Start( + &CreateGetRequest(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + // Verify that we consumed all test data. + EXPECT_TRUE(data->at_read_eof()) << "Read count: " + << data->read_count() + << " Read index: " + << data->read_index(); + EXPECT_TRUE(data->at_write_eof()) << "Write count: " + << data->write_count() + << " Write index: " + << data->write_index(); + + // Verify the SYN_REPLY. + HttpResponseInfo response = *trans->GetResponseInfo(); + EXPECT_TRUE(response.headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); +} + +} // namespace net diff --git a/net/spdy/spdy_network_transaction_spdy2_unittest.cc b/net/spdy/spdy_network_transaction_spdy2_unittest.cc new file mode 100644 index 0000000..facb8d9 --- /dev/null +++ b/net/spdy/spdy_network_transaction_spdy2_unittest.cc @@ -0,0 +1,5850 @@ +// Copyright (c) 2012 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/http/http_network_transaction.h" + +#include <string> +#include <vector> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "net/base/auth.h" +#include "net/base/net_log_unittest.h" +#include "net/http/http_network_session_peer.h" +#include "net/http/http_transaction_unittest.h" +#include "net/socket/client_socket_pool_base.h" +#include "net/spdy/spdy_http_stream.h" +#include "net/spdy/spdy_http_utils.h" +#include "net/spdy/spdy_session.h" +#include "net/spdy/spdy_session_pool.h" +#include "net/spdy/spdy_test_util_spdy2.h" +#include "net/url_request/url_request_test_util.h" +#include "testing/platform_test.h" + +using namespace net::test_spdy2; + +//----------------------------------------------------------------------------- + +namespace net { + +enum SpdyNetworkTransactionSpdy2TestTypes { + SPDYNPN, + SPDYNOSSL, + SPDYSSL, +}; +class SpdyNetworkTransactionSpdy2Test + : public ::testing::TestWithParam<SpdyNetworkTransactionSpdy2TestTypes> { + protected: + + virtual void SetUp() { + // By default, all tests turn off compression. + EnableCompression(false); + google_get_request_initialized_ = false; + google_post_request_initialized_ = false; + google_chunked_post_request_initialized_ = false; + } + + virtual void TearDown() { + // Empty the current queue. + MessageLoop::current()->RunAllPending(); + } + + struct TransactionHelperResult { + int rv; + std::string status_line; + std::string response_data; + HttpResponseInfo response_info; + }; + + void EnableCompression(bool enabled) { + spdy::SpdyFramer::set_enable_compression_default(enabled); + } + + // A helper class that handles all the initial npn/ssl setup. + class NormalSpdyTransactionHelper { + public: + NormalSpdyTransactionHelper(const HttpRequestInfo& request, + const BoundNetLog& log, + SpdyNetworkTransactionSpdy2TestTypes test_type) + : request_(request), + session_deps_(new SpdySessionDependencies()), + session_(SpdySessionDependencies::SpdyCreateSession( + session_deps_.get())), + log_(log), + test_type_(test_type), + deterministic_(false), + spdy_enabled_(true) { + switch (test_type_) { + case SPDYNOSSL: + case SPDYSSL: + port_ = 80; + break; + case SPDYNPN: + port_ = 443; + break; + default: + NOTREACHED(); + } + } + + ~NormalSpdyTransactionHelper() { + // Any test which doesn't close the socket by sending it an EOF will + // have a valid session left open, which leaks the entire session pool. + // This is just fine - in fact, some of our tests intentionally do this + // so that we can check consistency of the SpdySessionPool as the test + // finishes. If we had put an EOF on the socket, the SpdySession would + // have closed and we wouldn't be able to check the consistency. + + // Forcefully close existing sessions here. + session()->spdy_session_pool()->CloseAllSessions(); + } + + void SetDeterministic() { + session_ = SpdySessionDependencies::SpdyCreateSessionDeterministic( + session_deps_.get()); + deterministic_ = true; + } + + void SetSpdyDisabled() { + spdy_enabled_ = false; + } + + void RunPreTestSetup() { + if (!session_deps_.get()) + session_deps_.reset(new SpdySessionDependencies()); + if (!session_.get()) + session_ = SpdySessionDependencies::SpdyCreateSession( + session_deps_.get()); + HttpStreamFactory::set_use_alternate_protocols(false); + HttpStreamFactory::set_force_spdy_over_ssl(false); + HttpStreamFactory::set_force_spdy_always(false); + + std::vector<std::string> next_protos; + next_protos.push_back("http/1.1"); + next_protos.push_back("spdy/2"); + next_protos.push_back("spdy/2.1"); + + switch (test_type_) { + case SPDYNPN: + session_->http_server_properties()->SetAlternateProtocol( + HostPortPair("www.google.com", 80), 443, + NPN_SPDY_2); + HttpStreamFactory::set_use_alternate_protocols(true); + HttpStreamFactory::SetNextProtos(next_protos); + break; + case SPDYNOSSL: + HttpStreamFactory::set_force_spdy_over_ssl(false); + HttpStreamFactory::set_force_spdy_always(true); + break; + case SPDYSSL: + HttpStreamFactory::set_force_spdy_over_ssl(true); + HttpStreamFactory::set_force_spdy_always(true); + break; + default: + NOTREACHED(); + } + + // We're now ready to use SSL-npn SPDY. + trans_.reset(new HttpNetworkTransaction(session_)); + } + + // Start the transaction, read some data, finish. + void RunDefaultTest() { + output_.rv = trans_->Start(&request_, callback.callback(), log_); + + // We expect an IO Pending or some sort of error. + EXPECT_LT(output_.rv, 0); + if (output_.rv != ERR_IO_PENDING) + return; + + output_.rv = callback.WaitForResult(); + if (output_.rv != OK) { + session_->spdy_session_pool()->CloseCurrentSessions(); + return; + } + + // Verify responses. + const HttpResponseInfo* response = trans_->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + EXPECT_EQ(spdy_enabled_, response->was_fetched_via_spdy); + if (test_type_ == SPDYNPN && spdy_enabled_) { + EXPECT_TRUE(response->was_npn_negotiated); + } else { + EXPECT_TRUE(!response->was_npn_negotiated); + } + // If SPDY is not enabled, a HTTP request should not be diverted + // over a SSL session. + if (!spdy_enabled_) { + EXPECT_EQ(request_.url.SchemeIs("https"), + response->was_npn_negotiated); + } + EXPECT_EQ("192.0.2.33", response->socket_address.host()); + EXPECT_EQ(0, response->socket_address.port()); + output_.status_line = response->headers->GetStatusLine(); + output_.response_info = *response; // Make a copy so we can verify. + output_.rv = ReadTransaction(trans_.get(), &output_.response_data); + } + + // Most tests will want to call this function. In particular, the MockReads + // should end with an empty read, and that read needs to be processed to + // ensure proper deletion of the spdy_session_pool. + void VerifyDataConsumed() { + for (DataVector::iterator it = data_vector_.begin(); + it != data_vector_.end(); ++it) { + EXPECT_TRUE((*it)->at_read_eof()) << "Read count: " + << (*it)->read_count() + << " Read index: " + << (*it)->read_index(); + EXPECT_TRUE((*it)->at_write_eof()) << "Write count: " + << (*it)->write_count() + << " Write index: " + << (*it)->write_index(); + } + } + + // Occasionally a test will expect to error out before certain reads are + // processed. In that case we want to explicitly ensure that the reads were + // not processed. + void VerifyDataNotConsumed() { + for (DataVector::iterator it = data_vector_.begin(); + it != data_vector_.end(); ++it) { + EXPECT_TRUE(!(*it)->at_read_eof()) << "Read count: " + << (*it)->read_count() + << " Read index: " + << (*it)->read_index(); + EXPECT_TRUE(!(*it)->at_write_eof()) << "Write count: " + << (*it)->write_count() + << " Write index: " + << (*it)->write_index(); + } + } + + void RunToCompletion(StaticSocketDataProvider* data) { + RunPreTestSetup(); + AddData(data); + RunDefaultTest(); + VerifyDataConsumed(); + } + + void AddData(StaticSocketDataProvider* data) { + DCHECK(!deterministic_); + data_vector_.push_back(data); + linked_ptr<SSLSocketDataProvider> ssl_( + new SSLSocketDataProvider(ASYNC, OK)); + if (test_type_ == SPDYNPN) { + ssl_->SetNextProto(SSLClientSocket::kProtoSPDY2); + } + ssl_vector_.push_back(ssl_); + if (test_type_ == SPDYNPN || test_type_ == SPDYSSL) + session_deps_->socket_factory->AddSSLSocketDataProvider(ssl_.get()); + session_deps_->socket_factory->AddSocketDataProvider(data); + if (test_type_ == SPDYNPN) { + MockConnect never_finishing_connect(SYNCHRONOUS, ERR_IO_PENDING); + linked_ptr<StaticSocketDataProvider> + hanging_non_alternate_protocol_socket( + new StaticSocketDataProvider(NULL, 0, NULL, 0)); + hanging_non_alternate_protocol_socket->set_connect_data( + never_finishing_connect); + session_deps_->socket_factory->AddSocketDataProvider( + hanging_non_alternate_protocol_socket.get()); + alternate_vector_.push_back(hanging_non_alternate_protocol_socket); + } + } + + void AddDeterministicData(DeterministicSocketData* data) { + DCHECK(deterministic_); + data_vector_.push_back(data); + linked_ptr<SSLSocketDataProvider> ssl_( + new SSLSocketDataProvider(ASYNC, OK)); + if (test_type_ == SPDYNPN) { + ssl_->SetNextProto(SSLClientSocket::kProtoSPDY2); + } + ssl_vector_.push_back(ssl_); + if (test_type_ == SPDYNPN || test_type_ == SPDYSSL) { + session_deps_->deterministic_socket_factory-> + AddSSLSocketDataProvider(ssl_.get()); + } + session_deps_->deterministic_socket_factory->AddSocketDataProvider(data); + if (test_type_ == SPDYNPN) { + MockConnect never_finishing_connect(SYNCHRONOUS, ERR_IO_PENDING); + scoped_refptr<DeterministicSocketData> + hanging_non_alternate_protocol_socket( + new DeterministicSocketData(NULL, 0, NULL, 0)); + hanging_non_alternate_protocol_socket->set_connect_data( + never_finishing_connect); + session_deps_->deterministic_socket_factory->AddSocketDataProvider( + hanging_non_alternate_protocol_socket); + alternate_deterministic_vector_.push_back( + hanging_non_alternate_protocol_socket); + } + } + + // This can only be called after RunPreTestSetup. It adds a Data Provider, + // but not a corresponding SSL data provider + void AddDataNoSSL(StaticSocketDataProvider* data) { + DCHECK(!deterministic_); + session_deps_->socket_factory->AddSocketDataProvider(data); + } + void AddDataNoSSL(DeterministicSocketData* data) { + DCHECK(deterministic_); + session_deps_->deterministic_socket_factory->AddSocketDataProvider(data); + } + + void SetSession(const scoped_refptr<HttpNetworkSession>& session) { + session_ = session; + } + HttpNetworkTransaction* trans() { return trans_.get(); } + void ResetTrans() { trans_.reset(); } + TransactionHelperResult& output() { return output_; } + const HttpRequestInfo& request() const { return request_; } + const scoped_refptr<HttpNetworkSession>& session() const { + return session_; + } + scoped_ptr<SpdySessionDependencies>& session_deps() { + return session_deps_; + } + int port() const { return port_; } + SpdyNetworkTransactionSpdy2TestTypes test_type() const { + return test_type_; + } + + private: + typedef std::vector<StaticSocketDataProvider*> DataVector; + typedef std::vector<linked_ptr<SSLSocketDataProvider> > SSLVector; + typedef std::vector<linked_ptr<StaticSocketDataProvider> > AlternateVector; + typedef std::vector<scoped_refptr<DeterministicSocketData> > + AlternateDeterministicVector; + HttpRequestInfo request_; + scoped_ptr<SpdySessionDependencies> session_deps_; + scoped_refptr<HttpNetworkSession> session_; + TransactionHelperResult output_; + scoped_ptr<StaticSocketDataProvider> first_transaction_; + SSLVector ssl_vector_; + TestCompletionCallback callback; + scoped_ptr<HttpNetworkTransaction> trans_; + scoped_ptr<HttpNetworkTransaction> trans_http_; + DataVector data_vector_; + AlternateVector alternate_vector_; + AlternateDeterministicVector alternate_deterministic_vector_; + const BoundNetLog& log_; + SpdyNetworkTransactionSpdy2TestTypes test_type_; + int port_; + bool deterministic_; + bool spdy_enabled_; + }; + + void ConnectStatusHelperWithExpectedStatus(const MockRead& status, + int expected_status); + + void ConnectStatusHelper(const MockRead& status); + + const HttpRequestInfo& CreateGetPushRequest() { + google_get_push_request_.method = "GET"; + google_get_push_request_.url = GURL("http://www.google.com/foo.dat"); + google_get_push_request_.load_flags = 0; + return google_get_push_request_; + } + + const HttpRequestInfo& CreateGetRequest() { + if (!google_get_request_initialized_) { + google_get_request_.method = "GET"; + google_get_request_.url = GURL(kDefaultURL); + google_get_request_.load_flags = 0; + google_get_request_initialized_ = true; + } + return google_get_request_; + } + + const HttpRequestInfo& CreateGetRequestWithUserAgent() { + if (!google_get_request_initialized_) { + google_get_request_.method = "GET"; + google_get_request_.url = GURL(kDefaultURL); + google_get_request_.load_flags = 0; + google_get_request_.extra_headers.SetHeader("User-Agent", "Chrome"); + google_get_request_initialized_ = true; + } + return google_get_request_; + } + + const HttpRequestInfo& CreatePostRequest() { + if (!google_post_request_initialized_) { + google_post_request_.method = "POST"; + google_post_request_.url = GURL(kDefaultURL); + google_post_request_.upload_data = new UploadData(); + google_post_request_.upload_data->AppendBytes(kUploadData, + kUploadDataSize); + google_post_request_initialized_ = true; + } + return google_post_request_; + } + + const HttpRequestInfo& CreateChunkedPostRequest() { + if (!google_chunked_post_request_initialized_) { + google_chunked_post_request_.method = "POST"; + google_chunked_post_request_.url = GURL(kDefaultURL); + google_chunked_post_request_.upload_data = new UploadData(); + google_chunked_post_request_.upload_data->set_is_chunked(true); + google_chunked_post_request_.upload_data->AppendChunk( + kUploadData, kUploadDataSize, false); + google_chunked_post_request_.upload_data->AppendChunk( + kUploadData, kUploadDataSize, true); + google_chunked_post_request_initialized_ = true; + } + return google_chunked_post_request_; + } + + // Read the result of a particular transaction, knowing that we've got + // multiple transactions in the read pipeline; so as we read, we may have + // to skip over data destined for other transactions while we consume + // the data for |trans|. + int ReadResult(HttpNetworkTransaction* trans, + StaticSocketDataProvider* data, + std::string* result) { + const int kSize = 3000; + + int bytes_read = 0; + scoped_refptr<net::IOBufferWithSize> buf(new net::IOBufferWithSize(kSize)); + TestCompletionCallback callback; + while (true) { + int rv = trans->Read(buf, kSize, callback.callback()); + if (rv == ERR_IO_PENDING) { + // Multiple transactions may be in the data set. Keep pulling off + // reads until we complete our callback. + while (!callback.have_result()) { + data->CompleteRead(); + MessageLoop::current()->RunAllPending(); + } + rv = callback.WaitForResult(); + } else if (rv <= 0) { + break; + } + result->append(buf->data(), rv); + bytes_read += rv; + } + return bytes_read; + } + + void VerifyStreamsClosed(const NormalSpdyTransactionHelper& helper) { + // This lengthy block is reaching into the pool to dig out the active + // session. Once we have the session, we verify that the streams are + // all closed and not leaked at this point. + const GURL& url = helper.request().url; + int port = helper.test_type() == SPDYNPN ? 443 : 80; + HostPortPair host_port_pair(url.host(), port); + HostPortProxyPair pair(host_port_pair, ProxyServer::Direct()); + BoundNetLog log; + const scoped_refptr<HttpNetworkSession>& session = helper.session(); + SpdySessionPool* pool(session->spdy_session_pool()); + EXPECT_TRUE(pool->HasSession(pair)); + scoped_refptr<SpdySession> spdy_session(pool->Get(pair, log)); + ASSERT_TRUE(spdy_session.get() != NULL); + EXPECT_EQ(0u, spdy_session->num_active_streams()); + EXPECT_EQ(0u, spdy_session->num_unclaimed_pushed_streams()); + } + + void RunServerPushTest(OrderedSocketData* data, + HttpResponseInfo* response, + HttpResponseInfo* push_response, + std::string& expected) { + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.RunPreTestSetup(); + helper.AddData(data); + + HttpNetworkTransaction* trans = helper.trans(); + + // Start the transaction with basic parameters. + TestCompletionCallback callback; + int rv = trans->Start( + &CreateGetRequest(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback.WaitForResult(); + + // Request the pushed path. + scoped_ptr<HttpNetworkTransaction> trans2( + new HttpNetworkTransaction(helper.session())); + rv = trans2->Start( + &CreateGetPushRequest(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + MessageLoop::current()->RunAllPending(); + + // The data for the pushed path may be coming in more than 1 packet. Compile + // the results into a single string. + + // Read the server push body. + std::string result2; + ReadResult(trans2.get(), data, &result2); + // Read the response body. + std::string result; + ReadResult(trans, data, &result); + + // Verify that we consumed all test data. + EXPECT_TRUE(data->at_read_eof()); + EXPECT_TRUE(data->at_write_eof()); + + // Verify that the received push data is same as the expected push data. + EXPECT_EQ(result2.compare(expected), 0) << "Received data: " + << result2 + << "||||| Expected data: " + << expected; + + // Verify the SYN_REPLY. + // Copy the response info, because trans goes away. + *response = *trans->GetResponseInfo(); + *push_response = *trans2->GetResponseInfo(); + + VerifyStreamsClosed(helper); + } + + static void DeleteSessionCallback(NormalSpdyTransactionHelper* helper, + int result) { + helper->ResetTrans(); + } + + static void StartTransactionCallback( + const scoped_refptr<HttpNetworkSession>& session, + int result) { + scoped_ptr<HttpNetworkTransaction> trans( + new HttpNetworkTransaction(session)); + TestCompletionCallback callback; + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + callback.WaitForResult(); + } + + private: + bool google_get_request_initialized_; + bool google_post_request_initialized_; + bool google_chunked_post_request_initialized_; + HttpRequestInfo google_get_request_; + HttpRequestInfo google_post_request_; + HttpRequestInfo google_chunked_post_request_; + HttpRequestInfo google_get_push_request_; +}; + +//----------------------------------------------------------------------------- +// All tests are run with three different connection types: SPDY after NPN +// negotiation, SPDY without SSL, and SPDY with SSL. +INSTANTIATE_TEST_CASE_P(Spdy, + SpdyNetworkTransactionSpdy2Test, + ::testing::Values(SPDYNOSSL, SPDYSSL, SPDYNPN)); + + +// Verify HttpNetworkTransaction constructor. +TEST_P(SpdyNetworkTransactionSpdy2Test, Constructor) { + SpdySessionDependencies session_deps; + scoped_refptr<HttpNetworkSession> session( + SpdySessionDependencies::SpdyCreateSession(&session_deps)); + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); +} + +TEST_P(SpdyNetworkTransactionSpdy2Test, Get) { + // Construct the request. + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + MockWrite writes[] = { CreateMockWrite(*req) }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*resp), + CreateMockRead(*body), + MockRead(ASYNC, 0, 0) // EOF + }; + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData(1, reads, arraysize(reads), + writes, arraysize(writes))); + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.RunToCompletion(data.get()); + TransactionHelperResult out = helper.output(); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!", out.response_data); +} + +TEST_P(SpdyNetworkTransactionSpdy2Test, GetAtEachPriority) { + for (RequestPriority p = HIGHEST; p < NUM_PRIORITIES; + p = RequestPriority(p+1)) { + // Construct the request. + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, p)); + MockWrite writes[] = { CreateMockWrite(*req) }; + + const int spdy_prio = reinterpret_cast<spdy::SpdySynStreamControlFrame*>( + req.get())->priority(); + // this repeats the RequestPriority-->SpdyPriority mapping from + // SpdyFramer::ConvertRequestPriorityToSpdyPriority to make + // sure it's being done right. + switch(p) { + case HIGHEST: + EXPECT_EQ(0, spdy_prio); + break; + case MEDIUM: + EXPECT_EQ(1, spdy_prio); + break; + case LOW: + case LOWEST: + EXPECT_EQ(2, spdy_prio); + break; + case IDLE: + EXPECT_EQ(3, spdy_prio); + break; + default: + FAIL(); + } + + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*resp), + CreateMockRead(*body), + MockRead(ASYNC, 0, 0) // EOF + }; + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData(1, reads, arraysize(reads), + writes, arraysize(writes))); + HttpRequestInfo http_req = CreateGetRequest(); + http_req.priority = p; + + NormalSpdyTransactionHelper helper(http_req, BoundNetLog(), GetParam()); + helper.RunToCompletion(data.get()); + TransactionHelperResult out = helper.output(); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!", out.response_data); + } +} + +// Start three gets simultaniously; making sure that multiplexed +// streams work properly. + +// This can't use the TransactionHelper method, since it only +// handles a single transaction, and finishes them as soon +// as it launches them. + +// TODO(gavinp): create a working generalized TransactionHelper that +// can allow multiple streams in flight. + +TEST_P(SpdyNetworkTransactionSpdy2Test, ThreeGets) { + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, false)); + scoped_ptr<spdy::SpdyFrame> fbody(ConstructSpdyBodyFrame(1, true)); + + scoped_ptr<spdy::SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 3, LOWEST)); + scoped_ptr<spdy::SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 3)); + scoped_ptr<spdy::SpdyFrame> body2(ConstructSpdyBodyFrame(3, false)); + scoped_ptr<spdy::SpdyFrame> fbody2(ConstructSpdyBodyFrame(3, true)); + + scoped_ptr<spdy::SpdyFrame> req3(ConstructSpdyGet(NULL, 0, false, 5, LOWEST)); + scoped_ptr<spdy::SpdyFrame> resp3(ConstructSpdyGetSynReply(NULL, 0, 5)); + scoped_ptr<spdy::SpdyFrame> body3(ConstructSpdyBodyFrame(5, false)); + scoped_ptr<spdy::SpdyFrame> fbody3(ConstructSpdyBodyFrame(5, true)); + + MockWrite writes[] = { + CreateMockWrite(*req), + CreateMockWrite(*req2), + CreateMockWrite(*req3), + }; + MockRead reads[] = { + CreateMockRead(*resp, 1), + CreateMockRead(*body), + CreateMockRead(*resp2, 4), + CreateMockRead(*body2), + CreateMockRead(*resp3, 7), + CreateMockRead(*body3), + + CreateMockRead(*fbody), + CreateMockRead(*fbody2), + CreateMockRead(*fbody3), + + MockRead(ASYNC, 0, 0), // EOF + }; + scoped_ptr<OrderedSocketData> data( + new OrderedSocketData(reads, arraysize(reads), + writes, arraysize(writes))); + scoped_ptr<OrderedSocketData> data_placeholder( + new OrderedSocketData(NULL, 0, NULL, 0)); + + BoundNetLog log; + TransactionHelperResult out; + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.RunPreTestSetup(); + helper.AddData(data.get()); + // We require placeholder data because three get requests are sent out, so + // there needs to be three sets of SSL connection data. + helper.AddData(data_placeholder.get()); + helper.AddData(data_placeholder.get()); + scoped_ptr<HttpNetworkTransaction> trans1( + new HttpNetworkTransaction(helper.session())); + scoped_ptr<HttpNetworkTransaction> trans2( + new HttpNetworkTransaction(helper.session())); + scoped_ptr<HttpNetworkTransaction> trans3( + new HttpNetworkTransaction(helper.session())); + + TestCompletionCallback callback1; + TestCompletionCallback callback2; + TestCompletionCallback callback3; + + HttpRequestInfo httpreq1 = CreateGetRequest(); + HttpRequestInfo httpreq2 = CreateGetRequest(); + HttpRequestInfo httpreq3 = CreateGetRequest(); + + out.rv = trans1->Start(&httpreq1, callback1.callback(), log); + ASSERT_EQ(ERR_IO_PENDING, out.rv); + out.rv = trans2->Start(&httpreq2, callback2.callback(), log); + ASSERT_EQ(ERR_IO_PENDING, out.rv); + out.rv = trans3->Start(&httpreq3, callback3.callback(), log); + ASSERT_EQ(ERR_IO_PENDING, out.rv); + + out.rv = callback1.WaitForResult(); + ASSERT_EQ(OK, out.rv); + out.rv = callback3.WaitForResult(); + ASSERT_EQ(OK, out.rv); + + const HttpResponseInfo* response1 = trans1->GetResponseInfo(); + EXPECT_TRUE(response1->headers != NULL); + EXPECT_TRUE(response1->was_fetched_via_spdy); + out.status_line = response1->headers->GetStatusLine(); + out.response_info = *response1; + + trans2->GetResponseInfo(); + + out.rv = ReadTransaction(trans1.get(), &out.response_data); + helper.VerifyDataConsumed(); + EXPECT_EQ(OK, out.rv); + + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!hello!", out.response_data); +} + +TEST_P(SpdyNetworkTransactionSpdy2Test, TwoGetsLateBinding) { + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, false)); + scoped_ptr<spdy::SpdyFrame> fbody(ConstructSpdyBodyFrame(1, true)); + + scoped_ptr<spdy::SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 3, LOWEST)); + scoped_ptr<spdy::SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 3)); + scoped_ptr<spdy::SpdyFrame> body2(ConstructSpdyBodyFrame(3, false)); + scoped_ptr<spdy::SpdyFrame> fbody2(ConstructSpdyBodyFrame(3, true)); + + MockWrite writes[] = { + CreateMockWrite(*req), + CreateMockWrite(*req2), + }; + MockRead reads[] = { + CreateMockRead(*resp, 1), + CreateMockRead(*body), + CreateMockRead(*resp2, 4), + CreateMockRead(*body2), + CreateMockRead(*fbody), + CreateMockRead(*fbody2), + MockRead(ASYNC, 0, 0), // EOF + }; + scoped_ptr<OrderedSocketData> data( + new OrderedSocketData(reads, arraysize(reads), + writes, arraysize(writes))); + + MockConnect never_finishing_connect(SYNCHRONOUS, ERR_IO_PENDING); + + scoped_ptr<OrderedSocketData> data_placeholder( + new OrderedSocketData(NULL, 0, NULL, 0)); + data_placeholder->set_connect_data(never_finishing_connect); + + BoundNetLog log; + TransactionHelperResult out; + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.RunPreTestSetup(); + helper.AddData(data.get()); + // We require placeholder data because two get requests are sent out, so + // there needs to be two sets of SSL connection data. + helper.AddData(data_placeholder.get()); + scoped_ptr<HttpNetworkTransaction> trans1( + new HttpNetworkTransaction(helper.session())); + scoped_ptr<HttpNetworkTransaction> trans2( + new HttpNetworkTransaction(helper.session())); + + TestCompletionCallback callback1; + TestCompletionCallback callback2; + + HttpRequestInfo httpreq1 = CreateGetRequest(); + HttpRequestInfo httpreq2 = CreateGetRequest(); + + out.rv = trans1->Start(&httpreq1, callback1.callback(), log); + ASSERT_EQ(ERR_IO_PENDING, out.rv); + out.rv = trans2->Start(&httpreq2, callback2.callback(), log); + ASSERT_EQ(ERR_IO_PENDING, out.rv); + + out.rv = callback1.WaitForResult(); + ASSERT_EQ(OK, out.rv); + out.rv = callback2.WaitForResult(); + ASSERT_EQ(OK, out.rv); + + const HttpResponseInfo* response1 = trans1->GetResponseInfo(); + EXPECT_TRUE(response1->headers != NULL); + EXPECT_TRUE(response1->was_fetched_via_spdy); + out.status_line = response1->headers->GetStatusLine(); + out.response_info = *response1; + out.rv = ReadTransaction(trans1.get(), &out.response_data); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!hello!", out.response_data); + + const HttpResponseInfo* response2 = trans2->GetResponseInfo(); + EXPECT_TRUE(response2->headers != NULL); + EXPECT_TRUE(response2->was_fetched_via_spdy); + out.status_line = response2->headers->GetStatusLine(); + out.response_info = *response2; + out.rv = ReadTransaction(trans2.get(), &out.response_data); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!hello!", out.response_data); + + helper.VerifyDataConsumed(); +} + +TEST_P(SpdyNetworkTransactionSpdy2Test, TwoGetsLateBindingFromPreconnect) { + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, false)); + scoped_ptr<spdy::SpdyFrame> fbody(ConstructSpdyBodyFrame(1, true)); + + scoped_ptr<spdy::SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 3, LOWEST)); + scoped_ptr<spdy::SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 3)); + scoped_ptr<spdy::SpdyFrame> body2(ConstructSpdyBodyFrame(3, false)); + scoped_ptr<spdy::SpdyFrame> fbody2(ConstructSpdyBodyFrame(3, true)); + + MockWrite writes[] = { + CreateMockWrite(*req), + CreateMockWrite(*req2), + }; + MockRead reads[] = { + CreateMockRead(*resp, 1), + CreateMockRead(*body), + CreateMockRead(*resp2, 4), + CreateMockRead(*body2), + CreateMockRead(*fbody), + CreateMockRead(*fbody2), + MockRead(ASYNC, 0, 0), // EOF + }; + scoped_ptr<OrderedSocketData> preconnect_data( + new OrderedSocketData(reads, arraysize(reads), + writes, arraysize(writes))); + + MockConnect never_finishing_connect(ASYNC, ERR_IO_PENDING); + + scoped_ptr<OrderedSocketData> data_placeholder( + new OrderedSocketData(NULL, 0, NULL, 0)); + data_placeholder->set_connect_data(never_finishing_connect); + + BoundNetLog log; + TransactionHelperResult out; + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.RunPreTestSetup(); + helper.AddData(preconnect_data.get()); + // We require placeholder data because 3 connections are attempted (first is + // the preconnect, 2nd and 3rd are the never finished connections. + helper.AddData(data_placeholder.get()); + helper.AddData(data_placeholder.get()); + + scoped_ptr<HttpNetworkTransaction> trans1( + new HttpNetworkTransaction(helper.session())); + scoped_ptr<HttpNetworkTransaction> trans2( + new HttpNetworkTransaction(helper.session())); + + TestCompletionCallback callback1; + TestCompletionCallback callback2; + + HttpRequestInfo httpreq = CreateGetRequest(); + + // Preconnect the first. + SSLConfig preconnect_ssl_config; + helper.session()->ssl_config_service()->GetSSLConfig(&preconnect_ssl_config); + HttpStreamFactory* http_stream_factory = + helper.session()->http_stream_factory(); + if (http_stream_factory->has_next_protos()) { + preconnect_ssl_config.next_protos = http_stream_factory->next_protos(); + } + + http_stream_factory->PreconnectStreams( + 1, httpreq, preconnect_ssl_config, preconnect_ssl_config); + + out.rv = trans1->Start(&httpreq, callback1.callback(), log); + ASSERT_EQ(ERR_IO_PENDING, out.rv); + out.rv = trans2->Start(&httpreq, callback2.callback(), log); + ASSERT_EQ(ERR_IO_PENDING, out.rv); + + out.rv = callback1.WaitForResult(); + ASSERT_EQ(OK, out.rv); + out.rv = callback2.WaitForResult(); + ASSERT_EQ(OK, out.rv); + + const HttpResponseInfo* response1 = trans1->GetResponseInfo(); + EXPECT_TRUE(response1->headers != NULL); + EXPECT_TRUE(response1->was_fetched_via_spdy); + out.status_line = response1->headers->GetStatusLine(); + out.response_info = *response1; + out.rv = ReadTransaction(trans1.get(), &out.response_data); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!hello!", out.response_data); + + const HttpResponseInfo* response2 = trans2->GetResponseInfo(); + EXPECT_TRUE(response2->headers != NULL); + EXPECT_TRUE(response2->was_fetched_via_spdy); + out.status_line = response2->headers->GetStatusLine(); + out.response_info = *response2; + out.rv = ReadTransaction(trans2.get(), &out.response_data); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!hello!", out.response_data); + + helper.VerifyDataConsumed(); +} + +// Similar to ThreeGets above, however this test adds a SETTINGS +// frame. The SETTINGS frame is read during the IO loop waiting on +// the first transaction completion, and sets a maximum concurrent +// stream limit of 1. This means that our IO loop exists after the +// second transaction completes, so we can assert on read_index(). +TEST_P(SpdyNetworkTransactionSpdy2Test, ThreeGetsWithMaxConcurrent) { + // Construct the request. + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, false)); + scoped_ptr<spdy::SpdyFrame> fbody(ConstructSpdyBodyFrame(1, true)); + + scoped_ptr<spdy::SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 3, LOWEST)); + scoped_ptr<spdy::SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 3)); + scoped_ptr<spdy::SpdyFrame> body2(ConstructSpdyBodyFrame(3, false)); + scoped_ptr<spdy::SpdyFrame> fbody2(ConstructSpdyBodyFrame(3, true)); + + scoped_ptr<spdy::SpdyFrame> req3(ConstructSpdyGet(NULL, 0, false, 5, LOWEST)); + scoped_ptr<spdy::SpdyFrame> resp3(ConstructSpdyGetSynReply(NULL, 0, 5)); + scoped_ptr<spdy::SpdyFrame> body3(ConstructSpdyBodyFrame(5, false)); + scoped_ptr<spdy::SpdyFrame> fbody3(ConstructSpdyBodyFrame(5, true)); + + spdy::SpdySettings settings; + spdy::SettingsFlagsAndId id(0); + id.set_id(spdy::SETTINGS_MAX_CONCURRENT_STREAMS); + const size_t max_concurrent_streams = 1; + + settings.push_back(spdy::SpdySetting(id, max_concurrent_streams)); + scoped_ptr<spdy::SpdyFrame> settings_frame(ConstructSpdySettings(settings)); + + MockWrite writes[] = { + CreateMockWrite(*req), + CreateMockWrite(*req2), + CreateMockWrite(*req3), + }; + + MockRead reads[] = { + CreateMockRead(*settings_frame, 1), + CreateMockRead(*resp), + CreateMockRead(*body), + CreateMockRead(*fbody), + CreateMockRead(*resp2, 7), + CreateMockRead(*body2), + CreateMockRead(*fbody2), + CreateMockRead(*resp3, 12), + CreateMockRead(*body3), + CreateMockRead(*fbody3), + + MockRead(ASYNC, 0, 0), // EOF + }; + + scoped_ptr<OrderedSocketData> data( + new OrderedSocketData(reads, arraysize(reads), + writes, arraysize(writes))); + scoped_ptr<OrderedSocketData> data_placeholder( + new OrderedSocketData(NULL, 0, NULL, 0)); + + BoundNetLog log; + TransactionHelperResult out; + { + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.RunPreTestSetup(); + helper.AddData(data.get()); + // We require placeholder data because three get requests are sent out, so + // there needs to be three sets of SSL connection data. + helper.AddData(data_placeholder.get()); + helper.AddData(data_placeholder.get()); + scoped_ptr<HttpNetworkTransaction> trans1( + new HttpNetworkTransaction(helper.session())); + scoped_ptr<HttpNetworkTransaction> trans2( + new HttpNetworkTransaction(helper.session())); + scoped_ptr<HttpNetworkTransaction> trans3( + new HttpNetworkTransaction(helper.session())); + + TestCompletionCallback callback1; + TestCompletionCallback callback2; + TestCompletionCallback callback3; + + HttpRequestInfo httpreq1 = CreateGetRequest(); + HttpRequestInfo httpreq2 = CreateGetRequest(); + HttpRequestInfo httpreq3 = CreateGetRequest(); + + out.rv = trans1->Start(&httpreq1, callback1.callback(), log); + ASSERT_EQ(out.rv, ERR_IO_PENDING); + // run transaction 1 through quickly to force a read of our SETTINGS + // frame + out.rv = callback1.WaitForResult(); + ASSERT_EQ(OK, out.rv); + + out.rv = trans2->Start(&httpreq2, callback2.callback(), log); + ASSERT_EQ(out.rv, ERR_IO_PENDING); + out.rv = trans3->Start(&httpreq3, callback3.callback(), log); + ASSERT_EQ(out.rv, ERR_IO_PENDING); + out.rv = callback2.WaitForResult(); + ASSERT_EQ(OK, out.rv); + EXPECT_EQ(7U, data->read_index()); // i.e. the third trans was queued + + out.rv = callback3.WaitForResult(); + ASSERT_EQ(OK, out.rv); + + const HttpResponseInfo* response1 = trans1->GetResponseInfo(); + ASSERT_TRUE(response1 != NULL); + EXPECT_TRUE(response1->headers != NULL); + EXPECT_TRUE(response1->was_fetched_via_spdy); + out.status_line = response1->headers->GetStatusLine(); + out.response_info = *response1; + out.rv = ReadTransaction(trans1.get(), &out.response_data); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!hello!", out.response_data); + + const HttpResponseInfo* response2 = trans2->GetResponseInfo(); + out.status_line = response2->headers->GetStatusLine(); + out.response_info = *response2; + out.rv = ReadTransaction(trans2.get(), &out.response_data); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!hello!", out.response_data); + + const HttpResponseInfo* response3 = trans3->GetResponseInfo(); + out.status_line = response3->headers->GetStatusLine(); + out.response_info = *response3; + out.rv = ReadTransaction(trans3.get(), &out.response_data); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!hello!", out.response_data); + + helper.VerifyDataConsumed(); + } + EXPECT_EQ(OK, out.rv); +} + +// Similar to ThreeGetsWithMaxConcurrent above, however this test adds +// a fourth transaction. The third and fourth transactions have +// different data ("hello!" vs "hello!hello!") and because of the +// user specified priority, we expect to see them inverted in +// the response from the server. +TEST_P(SpdyNetworkTransactionSpdy2Test, FourGetsWithMaxConcurrentPriority) { + // Construct the request. + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, false)); + scoped_ptr<spdy::SpdyFrame> fbody(ConstructSpdyBodyFrame(1, true)); + + scoped_ptr<spdy::SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 3, LOWEST)); + scoped_ptr<spdy::SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 3)); + scoped_ptr<spdy::SpdyFrame> body2(ConstructSpdyBodyFrame(3, false)); + scoped_ptr<spdy::SpdyFrame> fbody2(ConstructSpdyBodyFrame(3, true)); + + scoped_ptr<spdy::SpdyFrame> req4( + ConstructSpdyGet(NULL, 0, false, 5, HIGHEST)); + scoped_ptr<spdy::SpdyFrame> resp4(ConstructSpdyGetSynReply(NULL, 0, 5)); + scoped_ptr<spdy::SpdyFrame> fbody4(ConstructSpdyBodyFrame(5, true)); + + scoped_ptr<spdy::SpdyFrame> req3(ConstructSpdyGet(NULL, 0, false, 7, LOWEST)); + scoped_ptr<spdy::SpdyFrame> resp3(ConstructSpdyGetSynReply(NULL, 0, 7)); + scoped_ptr<spdy::SpdyFrame> body3(ConstructSpdyBodyFrame(7, false)); + scoped_ptr<spdy::SpdyFrame> fbody3(ConstructSpdyBodyFrame(7, true)); + + + spdy::SpdySettings settings; + spdy::SettingsFlagsAndId id(0); + id.set_id(spdy::SETTINGS_MAX_CONCURRENT_STREAMS); + const size_t max_concurrent_streams = 1; + + settings.push_back(spdy::SpdySetting(id, max_concurrent_streams)); + scoped_ptr<spdy::SpdyFrame> settings_frame(ConstructSpdySettings(settings)); + + MockWrite writes[] = { CreateMockWrite(*req), + CreateMockWrite(*req2), + CreateMockWrite(*req4), + CreateMockWrite(*req3), + }; + MockRead reads[] = { + CreateMockRead(*settings_frame, 1), + CreateMockRead(*resp), + CreateMockRead(*body), + CreateMockRead(*fbody), + CreateMockRead(*resp2, 7), + CreateMockRead(*body2), + CreateMockRead(*fbody2), + CreateMockRead(*resp4, 13), + CreateMockRead(*fbody4), + CreateMockRead(*resp3, 16), + CreateMockRead(*body3), + CreateMockRead(*fbody3), + + MockRead(ASYNC, 0, 0), // EOF + }; + + scoped_ptr<OrderedSocketData> data( + new OrderedSocketData(reads, arraysize(reads), + writes, arraysize(writes))); + scoped_ptr<OrderedSocketData> data_placeholder( + new OrderedSocketData(NULL, 0, NULL, 0)); + + BoundNetLog log; + TransactionHelperResult out; + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.RunPreTestSetup(); + helper.AddData(data.get()); + // We require placeholder data because four get requests are sent out, so + // there needs to be four sets of SSL connection data. + helper.AddData(data_placeholder.get()); + helper.AddData(data_placeholder.get()); + helper.AddData(data_placeholder.get()); + scoped_ptr<HttpNetworkTransaction> trans1( + new HttpNetworkTransaction(helper.session())); + scoped_ptr<HttpNetworkTransaction> trans2( + new HttpNetworkTransaction(helper.session())); + scoped_ptr<HttpNetworkTransaction> trans3( + new HttpNetworkTransaction(helper.session())); + scoped_ptr<HttpNetworkTransaction> trans4( + new HttpNetworkTransaction(helper.session())); + + TestCompletionCallback callback1; + TestCompletionCallback callback2; + TestCompletionCallback callback3; + TestCompletionCallback callback4; + + HttpRequestInfo httpreq1 = CreateGetRequest(); + HttpRequestInfo httpreq2 = CreateGetRequest(); + HttpRequestInfo httpreq3 = CreateGetRequest(); + HttpRequestInfo httpreq4 = CreateGetRequest(); + httpreq4.priority = HIGHEST; + + out.rv = trans1->Start(&httpreq1, callback1.callback(), log); + ASSERT_EQ(ERR_IO_PENDING, out.rv); + // run transaction 1 through quickly to force a read of our SETTINGS + // frame + out.rv = callback1.WaitForResult(); + ASSERT_EQ(OK, out.rv); + + out.rv = trans2->Start(&httpreq2, callback2.callback(), log); + ASSERT_EQ(ERR_IO_PENDING, out.rv); + out.rv = trans3->Start(&httpreq3, callback3.callback(), log); + ASSERT_EQ(ERR_IO_PENDING, out.rv); + out.rv = trans4->Start(&httpreq4, callback4.callback(), log); + ASSERT_EQ(ERR_IO_PENDING, out.rv); + + out.rv = callback2.WaitForResult(); + ASSERT_EQ(OK, out.rv); + EXPECT_EQ(data->read_index(), 7U); // i.e. the third & fourth trans queued + + out.rv = callback3.WaitForResult(); + ASSERT_EQ(OK, out.rv); + + const HttpResponseInfo* response1 = trans1->GetResponseInfo(); + EXPECT_TRUE(response1->headers != NULL); + EXPECT_TRUE(response1->was_fetched_via_spdy); + out.status_line = response1->headers->GetStatusLine(); + out.response_info = *response1; + out.rv = ReadTransaction(trans1.get(), &out.response_data); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!hello!", out.response_data); + + const HttpResponseInfo* response2 = trans2->GetResponseInfo(); + out.status_line = response2->headers->GetStatusLine(); + out.response_info = *response2; + out.rv = ReadTransaction(trans2.get(), &out.response_data); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!hello!", out.response_data); + + // notice: response3 gets two hellos, response4 gets one + // hello, so we know dequeuing priority was respected. + const HttpResponseInfo* response3 = trans3->GetResponseInfo(); + out.status_line = response3->headers->GetStatusLine(); + out.response_info = *response3; + out.rv = ReadTransaction(trans3.get(), &out.response_data); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!hello!", out.response_data); + + out.rv = callback4.WaitForResult(); + EXPECT_EQ(OK, out.rv); + const HttpResponseInfo* response4 = trans4->GetResponseInfo(); + out.status_line = response4->headers->GetStatusLine(); + out.response_info = *response4; + out.rv = ReadTransaction(trans4.get(), &out.response_data); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!", out.response_data); + helper.VerifyDataConsumed(); + EXPECT_EQ(OK, out.rv); +} + +// Similar to ThreeGetsMaxConcurrrent above, however, this test +// deletes a session in the middle of the transaction to insure +// that we properly remove pendingcreatestream objects from +// the spdy_session +TEST_P(SpdyNetworkTransactionSpdy2Test, ThreeGetsWithMaxConcurrentDelete) { + // Construct the request. + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, false)); + scoped_ptr<spdy::SpdyFrame> fbody(ConstructSpdyBodyFrame(1, true)); + + scoped_ptr<spdy::SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 3, LOWEST)); + scoped_ptr<spdy::SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 3)); + scoped_ptr<spdy::SpdyFrame> body2(ConstructSpdyBodyFrame(3, false)); + scoped_ptr<spdy::SpdyFrame> fbody2(ConstructSpdyBodyFrame(3, true)); + + spdy::SpdySettings settings; + spdy::SettingsFlagsAndId id(0); + id.set_id(spdy::SETTINGS_MAX_CONCURRENT_STREAMS); + const size_t max_concurrent_streams = 1; + + settings.push_back(spdy::SpdySetting(id, max_concurrent_streams)); + scoped_ptr<spdy::SpdyFrame> settings_frame(ConstructSpdySettings(settings)); + + MockWrite writes[] = { CreateMockWrite(*req), + CreateMockWrite(*req2), + }; + MockRead reads[] = { + CreateMockRead(*settings_frame, 1), + CreateMockRead(*resp), + CreateMockRead(*body), + CreateMockRead(*fbody), + CreateMockRead(*resp2, 7), + CreateMockRead(*body2), + CreateMockRead(*fbody2), + MockRead(ASYNC, 0, 0), // EOF + }; + + scoped_ptr<OrderedSocketData> data( + new OrderedSocketData(reads, arraysize(reads), + writes, arraysize(writes))); + scoped_ptr<OrderedSocketData> data_placeholder( + new OrderedSocketData(NULL, 0, NULL, 0)); + + BoundNetLog log; + TransactionHelperResult out; + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.RunPreTestSetup(); + helper.AddData(data.get()); + // We require placeholder data because three get requests are sent out, so + // there needs to be three sets of SSL connection data. + helper.AddData(data_placeholder.get()); + helper.AddData(data_placeholder.get()); + scoped_ptr<HttpNetworkTransaction> trans1( + new HttpNetworkTransaction(helper.session())); + scoped_ptr<HttpNetworkTransaction> trans2( + new HttpNetworkTransaction(helper.session())); + scoped_ptr<HttpNetworkTransaction> trans3( + new HttpNetworkTransaction(helper.session())); + + TestCompletionCallback callback1; + TestCompletionCallback callback2; + TestCompletionCallback callback3; + + HttpRequestInfo httpreq1 = CreateGetRequest(); + HttpRequestInfo httpreq2 = CreateGetRequest(); + HttpRequestInfo httpreq3 = CreateGetRequest(); + + out.rv = trans1->Start(&httpreq1, callback1.callback(), log); + ASSERT_EQ(out.rv, ERR_IO_PENDING); + // run transaction 1 through quickly to force a read of our SETTINGS + // frame + out.rv = callback1.WaitForResult(); + ASSERT_EQ(OK, out.rv); + + out.rv = trans2->Start(&httpreq2, callback2.callback(), log); + ASSERT_EQ(out.rv, ERR_IO_PENDING); + out.rv = trans3->Start(&httpreq3, callback3.callback(), log); + delete trans3.release(); + ASSERT_EQ(out.rv, ERR_IO_PENDING); + out.rv = callback2.WaitForResult(); + ASSERT_EQ(OK, out.rv); + + EXPECT_EQ(8U, data->read_index()); + + const HttpResponseInfo* response1 = trans1->GetResponseInfo(); + ASSERT_TRUE(response1 != NULL); + EXPECT_TRUE(response1->headers != NULL); + EXPECT_TRUE(response1->was_fetched_via_spdy); + out.status_line = response1->headers->GetStatusLine(); + out.response_info = *response1; + out.rv = ReadTransaction(trans1.get(), &out.response_data); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!hello!", out.response_data); + + const HttpResponseInfo* response2 = trans2->GetResponseInfo(); + ASSERT_TRUE(response2 != NULL); + out.status_line = response2->headers->GetStatusLine(); + out.response_info = *response2; + out.rv = ReadTransaction(trans2.get(), &out.response_data); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!hello!", out.response_data); + helper.VerifyDataConsumed(); + EXPECT_EQ(OK, out.rv); +} + +namespace { + +// The KillerCallback will delete the transaction on error as part of the +// callback. +class KillerCallback : public TestCompletionCallbackBase { + public: + explicit KillerCallback(HttpNetworkTransaction* transaction) + : transaction_(transaction), + ALLOW_THIS_IN_INITIALIZER_LIST(callback_( + base::Bind(&KillerCallback::OnComplete, base::Unretained(this)))) { + } + + virtual ~KillerCallback() {} + + const CompletionCallback& callback() const { return callback_; } + + private: + void OnComplete(int result) { + if (result < 0) + delete transaction_; + + SetResult(result); + } + + HttpNetworkTransaction* transaction_; + CompletionCallback callback_; +}; + +} // namespace + +// Similar to ThreeGetsMaxConcurrrentDelete above, however, this test +// closes the socket while we have a pending transaction waiting for +// a pending stream creation. http://crbug.com/52901 +TEST_P(SpdyNetworkTransactionSpdy2Test, ThreeGetsWithMaxConcurrentSocketClose) { + // Construct the request. + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, false)); + scoped_ptr<spdy::SpdyFrame> fin_body(ConstructSpdyBodyFrame(1, true)); + + scoped_ptr<spdy::SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 3, LOWEST)); + scoped_ptr<spdy::SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 3)); + + spdy::SpdySettings settings; + spdy::SettingsFlagsAndId id(0); + id.set_id(spdy::SETTINGS_MAX_CONCURRENT_STREAMS); + const size_t max_concurrent_streams = 1; + + settings.push_back(spdy::SpdySetting(id, max_concurrent_streams)); + scoped_ptr<spdy::SpdyFrame> settings_frame(ConstructSpdySettings(settings)); + + MockWrite writes[] = { CreateMockWrite(*req), + CreateMockWrite(*req2), + }; + MockRead reads[] = { + CreateMockRead(*settings_frame, 1), + CreateMockRead(*resp), + CreateMockRead(*body), + CreateMockRead(*fin_body), + CreateMockRead(*resp2, 7), + MockRead(ASYNC, ERR_CONNECTION_RESET, 0), // Abort! + }; + + scoped_ptr<OrderedSocketData> data( + new OrderedSocketData(reads, arraysize(reads), + writes, arraysize(writes))); + scoped_ptr<OrderedSocketData> data_placeholder( + new OrderedSocketData(NULL, 0, NULL, 0)); + + BoundNetLog log; + TransactionHelperResult out; + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.RunPreTestSetup(); + helper.AddData(data.get()); + // We require placeholder data because three get requests are sent out, so + // there needs to be three sets of SSL connection data. + helper.AddData(data_placeholder.get()); + helper.AddData(data_placeholder.get()); + HttpNetworkTransaction trans1(helper.session()); + HttpNetworkTransaction trans2(helper.session()); + HttpNetworkTransaction* trans3(new HttpNetworkTransaction(helper.session())); + + TestCompletionCallback callback1; + TestCompletionCallback callback2; + KillerCallback callback3(trans3); + + HttpRequestInfo httpreq1 = CreateGetRequest(); + HttpRequestInfo httpreq2 = CreateGetRequest(); + HttpRequestInfo httpreq3 = CreateGetRequest(); + + out.rv = trans1.Start(&httpreq1, callback1.callback(), log); + ASSERT_EQ(out.rv, ERR_IO_PENDING); + // run transaction 1 through quickly to force a read of our SETTINGS + // frame + out.rv = callback1.WaitForResult(); + ASSERT_EQ(OK, out.rv); + + out.rv = trans2.Start(&httpreq2, callback2.callback(), log); + ASSERT_EQ(out.rv, ERR_IO_PENDING); + out.rv = trans3->Start(&httpreq3, callback3.callback(), log); + ASSERT_EQ(out.rv, ERR_IO_PENDING); + out.rv = callback3.WaitForResult(); + ASSERT_EQ(ERR_ABORTED, out.rv); + + EXPECT_EQ(6U, data->read_index()); + + const HttpResponseInfo* response1 = trans1.GetResponseInfo(); + ASSERT_TRUE(response1 != NULL); + EXPECT_TRUE(response1->headers != NULL); + EXPECT_TRUE(response1->was_fetched_via_spdy); + out.status_line = response1->headers->GetStatusLine(); + out.response_info = *response1; + out.rv = ReadTransaction(&trans1, &out.response_data); + EXPECT_EQ(OK, out.rv); + + const HttpResponseInfo* response2 = trans2.GetResponseInfo(); + ASSERT_TRUE(response2 != NULL); + out.status_line = response2->headers->GetStatusLine(); + out.response_info = *response2; + out.rv = ReadTransaction(&trans2, &out.response_data); + EXPECT_EQ(ERR_CONNECTION_RESET, out.rv); + + helper.VerifyDataConsumed(); +} + +// Test that a simple PUT request works. +TEST_P(SpdyNetworkTransactionSpdy2Test, Put) { + // Setup the request + HttpRequestInfo request; + request.method = "PUT"; + request.url = GURL("http://www.google.com/"); + + const SpdyHeaderInfo kSynStartHeader = { + spdy::SYN_STREAM, // Kind = Syn + 1, // Stream ID + 0, // Associated stream ID + net::ConvertRequestPriorityToSpdyPriority(LOWEST), // Priority + spdy::CONTROL_FLAG_FIN, // Control Flags + false, // Compressed + spdy::INVALID, // Status + NULL, // Data + 0, // Length + spdy::DATA_FLAG_NONE // Data Flags + }; + const char* const kPutHeaders[] = { + "method", "PUT", + "url", "/", + "host", "www.google.com", + "scheme", "http", + "version", "HTTP/1.1", + "content-length", "0" + }; + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyPacket(kSynStartHeader, NULL, 0, + kPutHeaders, arraysize(kPutHeaders) / 2)); + MockWrite writes[] = { + CreateMockWrite(*req) + }; + + scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true)); + const SpdyHeaderInfo kSynReplyHeader = { + spdy::SYN_REPLY, // Kind = SynReply + 1, // Stream ID + 0, // Associated stream ID + net::ConvertRequestPriorityToSpdyPriority(LOWEST), // Priority + spdy::CONTROL_FLAG_NONE, // Control Flags + false, // Compressed + spdy::INVALID, // Status + NULL, // Data + 0, // Length + spdy::DATA_FLAG_NONE // Data Flags + }; + static const char* const kStandardGetHeaders[] = { + "status", "200", + "version", "HTTP/1.1" + "content-length", "1234" + }; + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyPacket(kSynReplyHeader, + NULL, 0, kStandardGetHeaders, arraysize(kStandardGetHeaders) / 2)); + MockRead reads[] = { + CreateMockRead(*resp), + CreateMockRead(*body), + MockRead(ASYNC, 0, 0) // EOF + }; + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData(1, reads, arraysize(reads), + writes, arraysize(writes))); + NormalSpdyTransactionHelper helper(request, + BoundNetLog(), GetParam()); + helper.RunToCompletion(data.get()); + TransactionHelperResult out = helper.output(); + + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); +} + +// Test that a simple HEAD request works. +TEST_P(SpdyNetworkTransactionSpdy2Test, Head) { + // Setup the request + HttpRequestInfo request; + request.method = "HEAD"; + request.url = GURL("http://www.google.com/"); + + const SpdyHeaderInfo kSynStartHeader = { + spdy::SYN_STREAM, // Kind = Syn + 1, // Stream ID + 0, // Associated stream ID + net::ConvertRequestPriorityToSpdyPriority(LOWEST), // Priority + spdy::CONTROL_FLAG_FIN, // Control Flags + false, // Compressed + spdy::INVALID, // Status + NULL, // Data + 0, // Length + spdy::DATA_FLAG_NONE // Data Flags + }; + const char* const kHeadHeaders[] = { + "method", "HEAD", + "url", "/", + "host", "www.google.com", + "scheme", "http", + "version", "HTTP/1.1", + "content-length", "0" + }; + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyPacket(kSynStartHeader, NULL, 0, + kHeadHeaders, arraysize(kHeadHeaders) / 2)); + MockWrite writes[] = { + CreateMockWrite(*req) + }; + + scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true)); + const SpdyHeaderInfo kSynReplyHeader = { + spdy::SYN_REPLY, // Kind = SynReply + 1, // Stream ID + 0, // Associated stream ID + net::ConvertRequestPriorityToSpdyPriority(LOWEST), // Priority + spdy::CONTROL_FLAG_NONE, // Control Flags + false, // Compressed + spdy::INVALID, // Status + NULL, // Data + 0, // Length + spdy::DATA_FLAG_NONE // Data Flags + }; + static const char* const kStandardGetHeaders[] = { + "status", "200", + "version", "HTTP/1.1" + "content-length", "1234" + }; + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyPacket(kSynReplyHeader, + NULL, 0, kStandardGetHeaders, arraysize(kStandardGetHeaders) / 2)); + MockRead reads[] = { + CreateMockRead(*resp), + CreateMockRead(*body), + MockRead(ASYNC, 0, 0) // EOF + }; + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData(1, reads, arraysize(reads), + writes, arraysize(writes))); + NormalSpdyTransactionHelper helper(request, + BoundNetLog(), GetParam()); + helper.RunToCompletion(data.get()); + TransactionHelperResult out = helper.output(); + + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); +} + +// Test that a simple POST works. +TEST_P(SpdyNetworkTransactionSpdy2Test, Post) { + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyPost(kUploadDataSize, NULL, 0)); + scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true)); + MockWrite writes[] = { + CreateMockWrite(*req), + CreateMockWrite(*body), // POST upload frame + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyPostSynReply(NULL, 0)); + MockRead reads[] = { + CreateMockRead(*resp), + CreateMockRead(*body), + MockRead(ASYNC, 0, 0) // EOF + }; + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData(2, reads, arraysize(reads), + writes, arraysize(writes))); + NormalSpdyTransactionHelper helper(CreatePostRequest(), + BoundNetLog(), GetParam()); + helper.RunToCompletion(data.get()); + TransactionHelperResult out = helper.output(); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!", out.response_data); +} + +// Test that a chunked POST works. +TEST_P(SpdyNetworkTransactionSpdy2Test, ChunkedPost) { + UploadDataStream::set_merge_chunks(false); + scoped_ptr<spdy::SpdyFrame> req(ConstructChunkedSpdyPost(NULL, 0)); + scoped_ptr<spdy::SpdyFrame> chunk1(ConstructSpdyBodyFrame(1, false)); + scoped_ptr<spdy::SpdyFrame> chunk2(ConstructSpdyBodyFrame(1, true)); + MockWrite writes[] = { + CreateMockWrite(*req), + CreateMockWrite(*chunk1), + CreateMockWrite(*chunk2), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyPostSynReply(NULL, 0)); + MockRead reads[] = { + CreateMockRead(*resp), + CreateMockRead(*chunk1), + CreateMockRead(*chunk2), + MockRead(ASYNC, 0, 0) // EOF + }; + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData(2, reads, arraysize(reads), + writes, arraysize(writes))); + NormalSpdyTransactionHelper helper(CreateChunkedPostRequest(), + BoundNetLog(), GetParam()); + helper.RunToCompletion(data.get()); + TransactionHelperResult out = helper.output(); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!hello!", out.response_data); +} + +// Test that a POST without any post data works. +TEST_P(SpdyNetworkTransactionSpdy2Test, NullPost) { + // Setup the request + HttpRequestInfo request; + request.method = "POST"; + request.url = GURL("http://www.google.com/"); + // Create an empty UploadData. + request.upload_data = NULL; + + // When request.upload_data is NULL for post, content-length is + // expected to be 0. + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyPost(0, NULL, 0)); + // Set the FIN bit since there will be no body. + req->set_flags(spdy::CONTROL_FLAG_FIN); + MockWrite writes[] = { + CreateMockWrite(*req), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyPostSynReply(NULL, 0)); + scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*resp), + CreateMockRead(*body), + MockRead(ASYNC, 0, 0) // EOF + }; + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData(1, reads, arraysize(reads), + writes, arraysize(writes))); + + NormalSpdyTransactionHelper helper(request, + BoundNetLog(), GetParam()); + helper.RunToCompletion(data.get()); + TransactionHelperResult out = helper.output(); + 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_P(SpdyNetworkTransactionSpdy2Test, EmptyPost) { + // Setup the request + HttpRequestInfo request; + request.method = "POST"; + request.url = GURL("http://www.google.com/"); + // Create an empty UploadData. + request.upload_data = new UploadData(); + + // Http POST Content-Length is using UploadDataStream::size(). + // It is the same as request.upload_data->GetContentLengthSync(). + scoped_ptr<UploadDataStream> stream( + new UploadDataStream(request.upload_data)); + ASSERT_EQ(OK, stream->Init()); + ASSERT_EQ(request.upload_data->GetContentLengthSync(), + stream->size()); + + scoped_ptr<spdy::SpdyFrame> + req(ConstructSpdyPost( + request.upload_data->GetContentLengthSync(), NULL, 0)); + // Set the FIN bit since there will be no body. + req->set_flags(spdy::CONTROL_FLAG_FIN); + MockWrite writes[] = { + CreateMockWrite(*req), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyPostSynReply(NULL, 0)); + scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*resp), + CreateMockRead(*body), + MockRead(ASYNC, 0, 0) // EOF + }; + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData(1, reads, arraysize(reads), + writes, arraysize(writes))); + + NormalSpdyTransactionHelper helper(request, + BoundNetLog(), GetParam()); + helper.RunToCompletion(data.get()); + TransactionHelperResult out = helper.output(); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!", out.response_data); +} + +// While we're doing a post, the server sends back a SYN_REPLY. +TEST_P(SpdyNetworkTransactionSpdy2Test, PostWithEarlySynReply) { + static const char upload[] = { "hello!" }; + + // 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)); + + // Http POST Content-Length is using UploadDataStream::size(). + // It is the same as request.upload_data->GetContentLengthSync(). + scoped_ptr<UploadDataStream> stream( + new UploadDataStream(request.upload_data)); + ASSERT_EQ(OK, stream->Init()); + ASSERT_EQ(request.upload_data->GetContentLengthSync(), + stream->size()); + scoped_ptr<spdy::SpdyFrame> stream_reply(ConstructSpdyPostSynReply(NULL, 0)); + scoped_ptr<spdy::SpdyFrame> stream_body(ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*stream_reply, 2), + CreateMockRead(*stream_body, 3), + MockRead(SYNCHRONOUS, 0, 0) // EOF + }; + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData(0, reads, arraysize(reads), NULL, 0)); + NormalSpdyTransactionHelper helper(request, + BoundNetLog(), GetParam()); + helper.RunPreTestSetup(); + helper.AddData(data.get()); + helper.RunDefaultTest(); + helper.VerifyDataConsumed(); + + TransactionHelperResult out = helper.output(); + EXPECT_EQ(ERR_SYN_REPLY_NOT_RECEIVED, out.rv); +} + +// The client upon cancellation tries to send a RST_STREAM frame. The mock +// socket causes the TCP write to return zero. This test checks that the client +// tries to queue up the RST_STREAM frame again. +TEST_P(SpdyNetworkTransactionSpdy2Test, SocketWriteReturnsZero) { + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> rst( + ConstructSpdyRstStream(1, spdy::CANCEL)); + MockWrite writes[] = { + CreateMockWrite(*req.get(), 0, SYNCHRONOUS), + MockWrite(SYNCHRONOUS, 0, 0, 2), + CreateMockWrite(*rst.get(), 3, SYNCHRONOUS), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + MockRead reads[] = { + CreateMockRead(*resp.get(), 1, ASYNC), + MockRead(ASYNC, 0, 0, 4) // EOF + }; + + scoped_refptr<DeterministicSocketData> data( + new DeterministicSocketData(reads, arraysize(reads), + writes, arraysize(writes))); + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.SetDeterministic(); + helper.RunPreTestSetup(); + helper.AddDeterministicData(data.get()); + HttpNetworkTransaction* trans = helper.trans(); + + TestCompletionCallback callback; + int rv = trans->Start( + &CreateGetRequest(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + data->SetStop(2); + data->Run(); + helper.ResetTrans(); + data->SetStop(20); + data->Run(); + + helper.VerifyDataConsumed(); +} + +// Test that the transaction doesn't crash when we don't have a reply. +TEST_P(SpdyNetworkTransactionSpdy2Test, ResponseWithoutSynReply) { + scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*body), + MockRead(ASYNC, 0, 0) // EOF + }; + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData(1, reads, arraysize(reads), NULL, 0)); + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.RunToCompletion(data.get()); + TransactionHelperResult out = helper.output(); + EXPECT_EQ(ERR_SYN_REPLY_NOT_RECEIVED, out.rv); +} + +// Test that the transaction doesn't crash when we get two replies on the same +// stream ID. See http://crbug.com/45639. +TEST_P(SpdyNetworkTransactionSpdy2Test, ResponseWithTwoSynReplies) { + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + MockWrite writes[] = { CreateMockWrite(*req) }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*resp), + CreateMockRead(*resp), + CreateMockRead(*body), + MockRead(ASYNC, 0, 0) // EOF + }; + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData(1, reads, arraysize(reads), + writes, arraysize(writes))); + + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.RunPreTestSetup(); + helper.AddData(data.get()); + + HttpNetworkTransaction* trans = helper.trans(); + + TestCompletionCallback callback; + int rv = trans->Start(&helper.request(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(response->headers != NULL); + EXPECT_TRUE(response->was_fetched_via_spdy); + std::string response_data; + rv = ReadTransaction(trans, &response_data); + EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, rv); + + helper.VerifyDataConsumed(); +} + +// Test that sent data frames and received WINDOW_UPDATE frames change +// the send_window_size_ correctly. + +// WINDOW_UPDATE is different than most other frames in that it can arrive +// while the client is still sending the request body. In order to enforce +// this scenario, we feed a couple of dummy frames and give a delay of 0 to +// socket data provider, so that initial read that is done as soon as the +// stream is created, succeeds and schedules another read. This way reads +// and writes are interleaved; after doing a full frame write, SpdyStream +// will break out of DoLoop and will read and process a WINDOW_UPDATE. +// Once our WINDOW_UPDATE is read, we cannot send SYN_REPLY right away +// since request has not been completely written, therefore we feed +// enough number of WINDOW_UPDATEs to finish the first read and cause a +// write, leading to a complete write of request body; after that we send +// a reply with a body, to cause a graceful shutdown. + +// TODO(agayev): develop a socket data provider where both, reads and +// writes are ordered so that writing tests like these are easy and rewrite +// all these tests using it. Right now we are working around the +// limitations as described above and it's not deterministic, tests may +// fail under specific circumstances. +TEST_P(SpdyNetworkTransactionSpdy2Test, WindowUpdateReceived) { + SpdySession::set_use_flow_control(SpdySession::kEnableFlowControl); + + static int kFrameCount = 2; + scoped_ptr<std::string> content( + new std::string(kMaxSpdyFrameChunkSize, 'a')); + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyPost( + kMaxSpdyFrameChunkSize * kFrameCount, NULL, 0)); + scoped_ptr<spdy::SpdyFrame> body( + ConstructSpdyBodyFrame(1, content->c_str(), content->size(), false)); + scoped_ptr<spdy::SpdyFrame> body_end( + ConstructSpdyBodyFrame(1, content->c_str(), content->size(), true)); + + MockWrite writes[] = { + CreateMockWrite(*req), + CreateMockWrite(*body), + CreateMockWrite(*body_end), + }; + + static const int32 kDeltaWindowSize = 0xff; + static const int kDeltaCount = 4; + scoped_ptr<spdy::SpdyFrame> window_update( + ConstructSpdyWindowUpdate(1, kDeltaWindowSize)); + scoped_ptr<spdy::SpdyFrame> window_update_dummy( + ConstructSpdyWindowUpdate(2, kDeltaWindowSize)); + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyPostSynReply(NULL, 0)); + MockRead reads[] = { + CreateMockRead(*window_update_dummy), + CreateMockRead(*window_update_dummy), + CreateMockRead(*window_update_dummy), + CreateMockRead(*window_update), // Four updates, therefore window + CreateMockRead(*window_update), // size should increase by + CreateMockRead(*window_update), // kDeltaWindowSize * 4 + CreateMockRead(*window_update), + CreateMockRead(*resp), + CreateMockRead(*body_end), + MockRead(ASYNC, 0, 0) // EOF + }; + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData(0, reads, arraysize(reads), + writes, arraysize(writes))); + + // Setup the request + HttpRequestInfo request; + request.method = "POST"; + request.url = GURL(kDefaultURL); + request.upload_data = new UploadData(); + for (int i = 0; i < kFrameCount; ++i) + request.upload_data->AppendBytes(content->c_str(), content->size()); + + NormalSpdyTransactionHelper helper(request, BoundNetLog(), GetParam()); + helper.AddData(data.get()); + helper.RunPreTestSetup(); + + HttpNetworkTransaction* trans = helper.trans(); + + TestCompletionCallback callback; + int rv = trans->Start(&helper.request(), callback.callback(), BoundNetLog()); + + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + SpdyHttpStream* stream = static_cast<SpdyHttpStream*>(trans->stream_.get()); + ASSERT_TRUE(stream != NULL); + ASSERT_TRUE(stream->stream() != NULL); + EXPECT_EQ(static_cast<int>(spdy::kSpdyStreamInitialWindowSize) + + kDeltaWindowSize * kDeltaCount - + kMaxSpdyFrameChunkSize * kFrameCount, + stream->stream()->send_window_size()); + helper.VerifyDataConsumed(); + + SpdySession::set_use_flow_control(SpdySession::kFlowControlBasedOnNPN); +} + +// Test that received data frames and sent WINDOW_UPDATE frames change +// the recv_window_size_ correctly. +TEST_P(SpdyNetworkTransactionSpdy2Test, WindowUpdateSent) { + SpdySession::set_use_flow_control(SpdySession::kEnableFlowControl); + + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> window_update( + ConstructSpdyWindowUpdate(1, kUploadDataSize)); + + MockWrite writes[] = { + CreateMockWrite(*req), + CreateMockWrite(*window_update), + }; + + scoped_ptr<spdy::SpdyFrame> resp( + ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> body_no_fin( + ConstructSpdyBodyFrame(1, false)); + scoped_ptr<spdy::SpdyFrame> body_fin( + ConstructSpdyBodyFrame(1, NULL, 0, true)); + MockRead reads[] = { + CreateMockRead(*resp), + CreateMockRead(*body_no_fin), + MockRead(ASYNC, ERR_IO_PENDING, 0), // Force a pause + CreateMockRead(*body_fin), + MockRead(ASYNC, ERR_IO_PENDING, 0), // Force a pause + MockRead(ASYNC, 0, 0) // EOF + }; + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData(1, reads, arraysize(reads), + writes, arraysize(writes))); + + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.AddData(data.get()); + helper.RunPreTestSetup(); + HttpNetworkTransaction* trans = helper.trans(); + + TestCompletionCallback callback; + int rv = trans->Start(&helper.request(), callback.callback(), BoundNetLog()); + + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + SpdyHttpStream* stream = + static_cast<SpdyHttpStream*>(trans->stream_.get()); + ASSERT_TRUE(stream != NULL); + ASSERT_TRUE(stream->stream() != NULL); + + EXPECT_EQ( + static_cast<int>(spdy::kSpdyStreamInitialWindowSize) - kUploadDataSize, + stream->stream()->recv_window_size()); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + EXPECT_TRUE(response->was_fetched_via_spdy); + + // Force sending of WINDOW_UPDATE by setting initial_recv_window_size to a + // small value. + stream->stream()->set_initial_recv_window_size(kUploadDataSize / 2); + + // Issue a read which will cause a WINDOW_UPDATE to be sent and window + // size increased to default. + scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(kUploadDataSize)); + rv = trans->Read(buf, kUploadDataSize, CompletionCallback()); + EXPECT_EQ(kUploadDataSize, rv); + std::string content(buf->data(), buf->data()+kUploadDataSize); + EXPECT_STREQ(kUploadData, content.c_str()); + + // Schedule the reading of empty data frame with FIN + data->CompleteRead(); + + // Force write of WINDOW_UPDATE which was scheduled during the above + // read. + MessageLoop::current()->RunAllPending(); + + // Read EOF. + data->CompleteRead(); + + helper.VerifyDataConsumed(); + + SpdySession::set_use_flow_control(SpdySession::kFlowControlBasedOnNPN); +} + +// Test that WINDOW_UPDATE frame causing overflow is handled correctly. We +// use the same trick as in the above test to enforce our scenario. +TEST_P(SpdyNetworkTransactionSpdy2Test, WindowUpdateOverflow) { + SpdySession::set_use_flow_control(SpdySession::kEnableFlowControl); + + // number of full frames we hope to write (but will not, used to + // set content-length header correctly) + static int kFrameCount = 3; + + scoped_ptr<std::string> content( + new std::string(kMaxSpdyFrameChunkSize, 'a')); + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyPost( + kMaxSpdyFrameChunkSize * kFrameCount, NULL, 0)); + scoped_ptr<spdy::SpdyFrame> body( + ConstructSpdyBodyFrame(1, content->c_str(), content->size(), false)); + scoped_ptr<spdy::SpdyFrame> rst( + ConstructSpdyRstStream(1, spdy::FLOW_CONTROL_ERROR)); + + // We're not going to write a data frame with FIN, we'll receive a bad + // WINDOW_UPDATE while sending a request and will send a RST_STREAM frame. + MockWrite writes[] = { + CreateMockWrite(*req), + CreateMockWrite(*body), + CreateMockWrite(*rst), + }; + + static const int32 kDeltaWindowSize = 0x7fffffff; // cause an overflow + scoped_ptr<spdy::SpdyFrame> window_update( + ConstructSpdyWindowUpdate(1, kDeltaWindowSize)); + scoped_ptr<spdy::SpdyFrame> window_update2( + ConstructSpdyWindowUpdate(2, kDeltaWindowSize)); + scoped_ptr<spdy::SpdyFrame> reply(ConstructSpdyPostSynReply(NULL, 0)); + + MockRead reads[] = { + CreateMockRead(*window_update2), + CreateMockRead(*window_update2), + CreateMockRead(*window_update), + CreateMockRead(*window_update), + CreateMockRead(*window_update), + MockRead(ASYNC, ERR_IO_PENDING, 0), // Wait for the RST to be written. + MockRead(ASYNC, 0, 0) // EOF + }; + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData(0, reads, arraysize(reads), + writes, arraysize(writes))); + + // Setup the request + HttpRequestInfo request; + request.method = "POST"; + request.url = GURL("http://www.google.com/"); + request.upload_data = new UploadData(); + for (int i = 0; i < kFrameCount; ++i) + request.upload_data->AppendBytes(content->c_str(), content->size()); + + NormalSpdyTransactionHelper helper(request, + BoundNetLog(), GetParam()); + helper.AddData(data.get()); + helper.RunPreTestSetup(); + + HttpNetworkTransaction* trans = helper.trans(); + + TestCompletionCallback callback; + int rv = trans->Start(&helper.request(), callback.callback(), BoundNetLog()); + + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback.WaitForResult(); + EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, rv); + + data->CompleteRead(); + + ASSERT_TRUE(helper.session() != NULL); + ASSERT_TRUE(helper.session()->spdy_session_pool() != NULL); + helper.session()->spdy_session_pool()->CloseAllSessions(); + helper.VerifyDataConsumed(); + + SpdySession::set_use_flow_control(SpdySession::kFlowControlBasedOnNPN); +} + +// Test that after hitting a send window size of 0, the write process +// stalls and upon receiving WINDOW_UPDATE frame write resumes. + +// This test constructs a POST request followed by enough data frames +// containing 'a' that would make the window size 0, followed by another +// data frame containing default content (which is "hello!") and this frame +// also contains a FIN flag. DelayedSocketData is used to enforce all +// writes go through before a read could happen. However, the last frame +// ("hello!") is not supposed to go through since by the time its turn +// arrives, window size is 0. At this point MessageLoop::Run() called via +// callback would block. Therefore we call MessageLoop::RunAllPending() +// which returns after performing all possible writes. We use DCHECKS to +// ensure that last data frame is still there and stream has stalled. +// After that, next read is artifically enforced, which causes a +// WINDOW_UPDATE to be read and I/O process resumes. +TEST_P(SpdyNetworkTransactionSpdy2Test, FlowControlStallResume) { + SpdySession::set_use_flow_control(SpdySession::kEnableFlowControl); + + // Number of frames we need to send to zero out the window size: data + // frames plus SYN_STREAM plus the last data frame; also we need another + // data frame that we will send once the WINDOW_UPDATE is received, + // therefore +3. + size_t nwrites = + spdy::kSpdyStreamInitialWindowSize / kMaxSpdyFrameChunkSize + 3; + + // Calculate last frame's size; 0 size data frame is legal. + size_t last_frame_size = + spdy::kSpdyStreamInitialWindowSize % kMaxSpdyFrameChunkSize; + + // Construct content for a data frame of maximum size. + scoped_ptr<std::string> content( + new std::string(kMaxSpdyFrameChunkSize, 'a')); + + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyPost( + spdy::kSpdyStreamInitialWindowSize + kUploadDataSize, NULL, 0)); + + // Full frames. + scoped_ptr<spdy::SpdyFrame> body1( + ConstructSpdyBodyFrame(1, content->c_str(), content->size(), false)); + + // Last frame to zero out the window size. + scoped_ptr<spdy::SpdyFrame> body2( + ConstructSpdyBodyFrame(1, content->c_str(), last_frame_size, false)); + + // Data frame to be sent once WINDOW_UPDATE frame is received. + scoped_ptr<spdy::SpdyFrame> body3(ConstructSpdyBodyFrame(1, true)); + + // Fill in mock writes. + scoped_array<MockWrite> writes(new MockWrite[nwrites]); + size_t i = 0; + writes[i] = CreateMockWrite(*req); + for (i = 1; i < nwrites-2; i++) + writes[i] = CreateMockWrite(*body1); + writes[i++] = CreateMockWrite(*body2); + writes[i] = CreateMockWrite(*body3); + + // Construct read frame, give enough space to upload the rest of the + // data. + scoped_ptr<spdy::SpdyFrame> window_update( + ConstructSpdyWindowUpdate(1, kUploadDataSize)); + scoped_ptr<spdy::SpdyFrame> reply(ConstructSpdyPostSynReply(NULL, 0)); + MockRead reads[] = { + CreateMockRead(*window_update), + CreateMockRead(*window_update), + CreateMockRead(*reply), + CreateMockRead(*body2), + CreateMockRead(*body3), + MockRead(ASYNC, 0, 0) // EOF + }; + + // Force all writes to happen before any read, last write will not + // actually queue a frame, due to window size being 0. + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData(nwrites, reads, arraysize(reads), + writes.get(), nwrites)); + + HttpRequestInfo request; + request.method = "POST"; + request.url = GURL("http://www.google.com/"); + request.upload_data = new UploadData(); + scoped_ptr<std::string> upload_data( + new std::string(spdy::kSpdyStreamInitialWindowSize, 'a')); + upload_data->append(kUploadData, kUploadDataSize); + request.upload_data->AppendBytes(upload_data->c_str(), upload_data->size()); + NormalSpdyTransactionHelper helper(request, + BoundNetLog(), GetParam()); + helper.AddData(data.get()); + helper.RunPreTestSetup(); + + HttpNetworkTransaction* trans = helper.trans(); + + TestCompletionCallback callback; + int rv = trans->Start(&helper.request(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + MessageLoop::current()->RunAllPending(); // Write as much as we can. + + SpdyHttpStream* stream = static_cast<SpdyHttpStream*>(trans->stream_.get()); + ASSERT_TRUE(stream != NULL); + ASSERT_TRUE(stream->stream() != NULL); + EXPECT_EQ(0, stream->stream()->send_window_size()); + // All the body data should have been read. + // TODO(satorux): This is because of the weirdness in reading the request + // body in OnSendBodyComplete(). See crbug.com/113107. + EXPECT_TRUE(stream->request_body_stream_->IsEOF()); + // But the body is not yet fully sent ("hello!" is not yet sent). + EXPECT_FALSE(stream->stream()->body_sent()); + + data->ForceNextRead(); // Read in WINDOW_UPDATE frame. + rv = callback.WaitForResult(); + helper.VerifyDataConsumed(); + + SpdySession::set_use_flow_control(SpdySession::kFlowControlBasedOnNPN); +} + +TEST_P(SpdyNetworkTransactionSpdy2Test, CancelledTransaction) { + // Construct the request. + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + MockWrite writes[] = { + CreateMockWrite(*req), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + MockRead reads[] = { + CreateMockRead(*resp), + // This following read isn't used by the test, except during the + // RunAllPending() call at the end since the SpdySession survives the + // HttpNetworkTransaction and still tries to continue Read()'ing. Any + // MockRead will do here. + MockRead(ASYNC, 0, 0) // EOF + }; + + StaticSocketDataProvider data(reads, arraysize(reads), + writes, arraysize(writes)); + + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.RunPreTestSetup(); + helper.AddData(&data); + HttpNetworkTransaction* trans = helper.trans(); + + TestCompletionCallback callback; + int rv = trans->Start( + &CreateGetRequest(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + helper.ResetTrans(); // Cancel the transaction. + + // Flush the MessageLoop while the SpdySessionDependencies (in particular, the + // MockClientSocketFactory) are still alive. + MessageLoop::current()->RunAllPending(); + helper.VerifyDataNotConsumed(); +} + +// Verify that the client sends a Rst Frame upon cancelling the stream. +TEST_P(SpdyNetworkTransactionSpdy2Test, CancelledTransactionSendRst) { + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> rst( + ConstructSpdyRstStream(1, spdy::CANCEL)); + MockWrite writes[] = { + CreateMockWrite(*req, 0, SYNCHRONOUS), + CreateMockWrite(*rst, 2, SYNCHRONOUS), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + MockRead reads[] = { + CreateMockRead(*resp, 1, ASYNC), + MockRead(ASYNC, 0, 0, 3) // EOF + }; + + scoped_refptr<DeterministicSocketData> data( + new DeterministicSocketData(reads, arraysize(reads), + writes, arraysize(writes))); + + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), + GetParam()); + helper.SetDeterministic(); + helper.RunPreTestSetup(); + helper.AddDeterministicData(data.get()); + HttpNetworkTransaction* trans = helper.trans(); + + TestCompletionCallback callback; + + int rv = trans->Start( + &CreateGetRequest(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + data->SetStop(2); + data->Run(); + helper.ResetTrans(); + data->SetStop(20); + data->Run(); + + helper.VerifyDataConsumed(); +} + +// Verify that the client can correctly deal with the user callback attempting +// to start another transaction on a session that is closing down. See +// http://crbug.com/47455 +TEST_P(SpdyNetworkTransactionSpdy2Test, StartTransactionOnReadCallback) { + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + MockWrite writes[] = { CreateMockWrite(*req) }; + MockWrite writes2[] = { CreateMockWrite(*req) }; + + // The indicated length of this packet is longer than its actual length. When + // the session receives an empty packet after this one, it shuts down the + // session, and calls the read callback with the incomplete data. + const uint8 kGetBodyFrame2[] = { + 0x00, 0x00, 0x00, 0x01, + 0x01, 0x00, 0x00, 0x07, + 'h', 'e', 'l', 'l', 'o', '!', + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + MockRead reads[] = { + CreateMockRead(*resp, 2), + MockRead(ASYNC, ERR_IO_PENDING, 3), // Force a pause + MockRead(ASYNC, reinterpret_cast<const char*>(kGetBodyFrame2), + arraysize(kGetBodyFrame2), 4), + MockRead(ASYNC, ERR_IO_PENDING, 5), // Force a pause + MockRead(ASYNC, 0, 0, 6), // EOF + }; + MockRead reads2[] = { + CreateMockRead(*resp, 2), + MockRead(ASYNC, 0, 0, 3), // EOF + }; + + scoped_ptr<OrderedSocketData> data( + new OrderedSocketData(reads, arraysize(reads), + writes, arraysize(writes))); + scoped_ptr<DelayedSocketData> data2( + new DelayedSocketData(1, reads2, arraysize(reads2), + writes2, arraysize(writes2))); + + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.RunPreTestSetup(); + helper.AddData(data.get()); + helper.AddData(data2.get()); + HttpNetworkTransaction* trans = helper.trans(); + + // Start the transaction with basic parameters. + TestCompletionCallback callback; + int rv = trans->Start(&helper.request(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback.WaitForResult(); + + const int kSize = 3000; + scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(kSize)); + rv = trans->Read( + buf, kSize, + base::Bind(&SpdyNetworkTransactionSpdy2Test::StartTransactionCallback, + helper.session())); + // This forces an err_IO_pending, which sets the callback. + data->CompleteRead(); + // This finishes the read. + data->CompleteRead(); + helper.VerifyDataConsumed(); +} + +// Verify that the client can correctly deal with the user callback deleting the +// transaction. Failures will usually be valgrind errors. See +// http://crbug.com/46925 +TEST_P(SpdyNetworkTransactionSpdy2Test, DeleteSessionOnReadCallback) { + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + MockWrite writes[] = { CreateMockWrite(*req) }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*resp.get(), 2), + MockRead(ASYNC, ERR_IO_PENDING, 3), // Force a pause + CreateMockRead(*body.get(), 4), + MockRead(ASYNC, 0, 0, 5), // EOF + }; + + scoped_ptr<OrderedSocketData> data( + new OrderedSocketData(reads, arraysize(reads), + writes, arraysize(writes))); + + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.RunPreTestSetup(); + helper.AddData(data.get()); + HttpNetworkTransaction* trans = helper.trans(); + + // Start the transaction with basic parameters. + TestCompletionCallback callback; + int rv = trans->Start(&helper.request(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback.WaitForResult(); + + // Setup a user callback which will delete the session, and clear out the + // memory holding the stream object. Note that the callback deletes trans. + const int kSize = 3000; + scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(kSize)); + rv = trans->Read( + buf, kSize, + base::Bind(&SpdyNetworkTransactionSpdy2Test::DeleteSessionCallback, + base::Unretained(&helper))); + ASSERT_EQ(ERR_IO_PENDING, rv); + data->CompleteRead(); + + // Finish running rest of tasks. + MessageLoop::current()->RunAllPending(); + helper.VerifyDataConsumed(); +} + +// Send a spdy request to www.google.com that gets redirected to www.foo.com. +TEST_P(SpdyNetworkTransactionSpdy2Test, RedirectGetRequest) { + // These are headers which the net::URLRequest tacks on. + const char* const kExtraHeaders[] = { + "accept-encoding", + "gzip,deflate", + }; + const SpdyHeaderInfo kSynStartHeader = MakeSpdyHeader(spdy::SYN_STREAM); + const char* const kStandardGetHeaders[] = { + "host", + "www.google.com", + "method", + "GET", + "scheme", + "http", + "url", + "/", + "user-agent", + "", + "version", + "HTTP/1.1" + }; + const char* const kStandardGetHeaders2[] = { + "host", + "www.foo.com", + "method", + "GET", + "scheme", + "http", + "url", + "/index.php", + "user-agent", + "", + "version", + "HTTP/1.1" + }; + + // Setup writes/reads to www.google.com + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyPacket( + kSynStartHeader, kExtraHeaders, arraysize(kExtraHeaders) / 2, + kStandardGetHeaders, arraysize(kStandardGetHeaders) / 2)); + scoped_ptr<spdy::SpdyFrame> req2(ConstructSpdyPacket( + kSynStartHeader, kExtraHeaders, arraysize(kExtraHeaders) / 2, + kStandardGetHeaders2, arraysize(kStandardGetHeaders2) / 2)); + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReplyRedirect(1)); + MockWrite writes[] = { + CreateMockWrite(*req, 1), + }; + MockRead reads[] = { + CreateMockRead(*resp, 2), + MockRead(ASYNC, 0, 0, 3) // EOF + }; + + // Setup writes/reads to www.foo.com + scoped_ptr<spdy::SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> body2(ConstructSpdyBodyFrame(1, true)); + MockWrite writes2[] = { + CreateMockWrite(*req2, 1), + }; + MockRead reads2[] = { + CreateMockRead(*resp2, 2), + CreateMockRead(*body2, 3), + MockRead(ASYNC, 0, 0, 4) // EOF + }; + scoped_ptr<OrderedSocketData> data( + new OrderedSocketData(reads, arraysize(reads), + writes, arraysize(writes))); + scoped_ptr<OrderedSocketData> data2( + new OrderedSocketData(reads2, arraysize(reads2), + writes2, arraysize(writes2))); + + // TODO(erikchen): Make test support SPDYSSL, SPDYNPN + HttpStreamFactory::set_force_spdy_over_ssl(false); + HttpStreamFactory::set_force_spdy_always(true); + TestDelegate d; + { + net::URLRequest r(GURL("http://www.google.com/"), &d); + SpdyURLRequestContext* spdy_url_request_context = + new SpdyURLRequestContext(); + r.set_context(spdy_url_request_context); + spdy_url_request_context->socket_factory(). + AddSocketDataProvider(data.get()); + spdy_url_request_context->socket_factory(). + AddSocketDataProvider(data2.get()); + + d.set_quit_on_redirect(true); + r.Start(); + MessageLoop::current()->Run(); + + EXPECT_EQ(1, d.received_redirect_count()); + + r.FollowDeferredRedirect(); + MessageLoop::current()->Run(); + EXPECT_EQ(1, d.response_started_count()); + EXPECT_FALSE(d.received_data_before_response()); + EXPECT_EQ(net::URLRequestStatus::SUCCESS, r.status().status()); + std::string contents("hello!"); + EXPECT_EQ(contents, d.data_received()); + } + EXPECT_TRUE(data->at_read_eof()); + EXPECT_TRUE(data->at_write_eof()); + EXPECT_TRUE(data2->at_read_eof()); + EXPECT_TRUE(data2->at_write_eof()); +} + +// Detect response with upper case headers and reset the stream. +TEST_P(SpdyNetworkTransactionSpdy2Test, UpperCaseHeaders) { + scoped_ptr<spdy::SpdyFrame> + syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> + rst(ConstructSpdyRstStream(1, spdy::PROTOCOL_ERROR)); + MockWrite writes[] = { + CreateMockWrite(*syn, 0), + CreateMockWrite(*rst, 2), + }; + + const char* const kExtraHeaders[] = {"X-UpperCase", "yes"}; + scoped_ptr<spdy::SpdyFrame> + reply(ConstructSpdyGetSynReply(kExtraHeaders, 1, 1)); + MockRead reads[] = { + CreateMockRead(*reply, 1), + MockRead(ASYNC, ERR_IO_PENDING, 3), // Force a pause + }; + + HttpResponseInfo response; + HttpResponseInfo response2; + scoped_ptr<OrderedSocketData> data(new OrderedSocketData( + reads, + arraysize(reads), + writes, + arraysize(writes))); + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.RunToCompletion(data.get()); + TransactionHelperResult out = helper.output(); + EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, out.rv); +} + +// Detect response with upper case headers in a HEADERS frame and reset the +// stream. +TEST_P(SpdyNetworkTransactionSpdy2Test, UpperCaseHeadersInHeadersFrame) { + scoped_ptr<spdy::SpdyFrame> + syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> + rst(ConstructSpdyRstStream(1, spdy::PROTOCOL_ERROR)); + MockWrite writes[] = { + CreateMockWrite(*syn, 0), + CreateMockWrite(*rst, 2), + }; + + static const char* const kInitialHeaders[] = { + "status", "200 OK", + "version", "HTTP/1.1" + }; + static const char* const kLateHeaders[] = { + "X-UpperCase", "yes", + }; + scoped_ptr<spdy::SpdyFrame> + stream1_reply(ConstructSpdyControlFrame(kInitialHeaders, + arraysize(kInitialHeaders) / 2, + false, + 1, + LOWEST, + spdy::SYN_REPLY, + spdy::CONTROL_FLAG_NONE, + NULL, + 0, + 0)); + scoped_ptr<spdy::SpdyFrame> + stream1_headers(ConstructSpdyControlFrame(kLateHeaders, + arraysize(kLateHeaders) / 2, + false, + 1, + LOWEST, + spdy::HEADERS, + spdy::CONTROL_FLAG_NONE, + NULL, + 0, + 0)); + scoped_ptr<spdy::SpdyFrame> stream1_body(ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*stream1_reply), + CreateMockRead(*stream1_headers), + CreateMockRead(*stream1_body), + MockRead(ASYNC, 0, 0) // EOF + }; + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData(1, reads, arraysize(reads), + writes, arraysize(writes))); + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.RunToCompletion(data.get()); + TransactionHelperResult out = helper.output(); + EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, out.rv); +} + +// Detect push stream with upper case headers and reset the stream. +TEST_P(SpdyNetworkTransactionSpdy2Test, UpperCaseHeadersOnPush) { + scoped_ptr<spdy::SpdyFrame> + syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> + rst(ConstructSpdyRstStream(2, spdy::PROTOCOL_ERROR)); + MockWrite writes[] = { + CreateMockWrite(*syn, 0), + CreateMockWrite(*rst, 2), + }; + + scoped_ptr<spdy::SpdyFrame> + reply(ConstructSpdyGetSynReply(NULL, 0, 1)); + const char* const extra_headers[] = {"X-UpperCase", "yes"}; + scoped_ptr<spdy::SpdyFrame> + push(ConstructSpdyPush(extra_headers, 1, 2, 1)); + scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*reply, 1), + CreateMockRead(*push, 1), + CreateMockRead(*body, 1), + MockRead(ASYNC, ERR_IO_PENDING, 3), // Force a pause + }; + + HttpResponseInfo response; + HttpResponseInfo response2; + scoped_ptr<OrderedSocketData> data(new OrderedSocketData( + reads, + arraysize(reads), + writes, + arraysize(writes))); + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.RunToCompletion(data.get()); + TransactionHelperResult out = helper.output(); + EXPECT_EQ(OK, out.rv); +} + +// Send a spdy request to www.google.com. Get a pushed stream that redirects to +// www.foo.com. +TEST_P(SpdyNetworkTransactionSpdy2Test, RedirectServerPush) { + // These are headers which the net::URLRequest tacks on. + const char* const kExtraHeaders[] = { + "accept-encoding", + "gzip,deflate", + }; + const SpdyHeaderInfo kSynStartHeader = MakeSpdyHeader(spdy::SYN_STREAM); + const char* const kStandardGetHeaders[] = { + "host", + "www.google.com", + "method", + "GET", + "scheme", + "http", + "url", + "/", + "user-agent", + "", + "version", + "HTTP/1.1" + }; + + // Setup writes/reads to www.google.com + scoped_ptr<spdy::SpdyFrame> req( + ConstructSpdyPacket(kSynStartHeader, + kExtraHeaders, + arraysize(kExtraHeaders) / 2, + kStandardGetHeaders, + arraysize(kStandardGetHeaders) / 2)); + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> rep( + ConstructSpdyPush(NULL, + 0, + 2, + 1, + "http://www.google.com/foo.dat", + "301 Moved Permanently", + "http://www.foo.com/index.php")); + scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true)); + scoped_ptr<spdy::SpdyFrame> rst(ConstructSpdyRstStream(2, spdy::CANCEL)); + MockWrite writes[] = { + CreateMockWrite(*req, 1), + CreateMockWrite(*rst, 6), + }; + MockRead reads[] = { + CreateMockRead(*resp, 2), + CreateMockRead(*rep, 3), + CreateMockRead(*body, 4), + MockRead(ASYNC, ERR_IO_PENDING, 5), // Force a pause + MockRead(ASYNC, 0, 0, 7) // EOF + }; + + // Setup writes/reads to www.foo.com + const char* const kStandardGetHeaders2[] = { + "host", + "www.foo.com", + "method", + "GET", + "scheme", + "http", + "url", + "/index.php", + "user-agent", + "", + "version", + "HTTP/1.1" + }; + scoped_ptr<spdy::SpdyFrame> req2( + ConstructSpdyPacket(kSynStartHeader, + kExtraHeaders, + arraysize(kExtraHeaders) / 2, + kStandardGetHeaders2, + arraysize(kStandardGetHeaders2) / 2)); + scoped_ptr<spdy::SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> body2(ConstructSpdyBodyFrame(1, true)); + MockWrite writes2[] = { + CreateMockWrite(*req2, 1), + }; + MockRead reads2[] = { + CreateMockRead(*resp2, 2), + CreateMockRead(*body2, 3), + MockRead(ASYNC, 0, 0, 5) // EOF + }; + scoped_ptr<OrderedSocketData> data( + new OrderedSocketData(reads, arraysize(reads), + writes, arraysize(writes))); + scoped_ptr<OrderedSocketData> data2( + new OrderedSocketData(reads2, arraysize(reads2), + writes2, arraysize(writes2))); + + // TODO(erikchen): Make test support SPDYSSL, SPDYNPN + HttpStreamFactory::set_force_spdy_over_ssl(false); + HttpStreamFactory::set_force_spdy_always(true); + TestDelegate d; + TestDelegate d2; + scoped_refptr<SpdyURLRequestContext> spdy_url_request_context( + new SpdyURLRequestContext()); + { + net::URLRequest r(GURL("http://www.google.com/"), &d); + r.set_context(spdy_url_request_context); + spdy_url_request_context->socket_factory(). + AddSocketDataProvider(data.get()); + + r.Start(); + MessageLoop::current()->Run(); + + EXPECT_EQ(0, d.received_redirect_count()); + std::string contents("hello!"); + EXPECT_EQ(contents, d.data_received()); + + net::URLRequest r2(GURL("http://www.google.com/foo.dat"), &d2); + r2.set_context(spdy_url_request_context); + spdy_url_request_context->socket_factory(). + AddSocketDataProvider(data2.get()); + + d2.set_quit_on_redirect(true); + r2.Start(); + MessageLoop::current()->Run(); + EXPECT_EQ(1, d2.received_redirect_count()); + + r2.FollowDeferredRedirect(); + MessageLoop::current()->Run(); + EXPECT_EQ(1, d2.response_started_count()); + EXPECT_FALSE(d2.received_data_before_response()); + EXPECT_EQ(net::URLRequestStatus::SUCCESS, r2.status().status()); + std::string contents2("hello!"); + EXPECT_EQ(contents2, d2.data_received()); + } + data->CompleteRead(); + data2->CompleteRead(); + EXPECT_TRUE(data->at_read_eof()); + EXPECT_TRUE(data->at_write_eof()); + EXPECT_TRUE(data2->at_read_eof()); + EXPECT_TRUE(data2->at_write_eof()); +} + +TEST_P(SpdyNetworkTransactionSpdy2Test, ServerPushSingleDataFrame) { + static const unsigned char kPushBodyFrame[] = { + 0x00, 0x00, 0x00, 0x02, // header, ID + 0x01, 0x00, 0x00, 0x06, // FIN, length + 'p', 'u', 's', 'h', 'e', 'd' // "pushed" + }; + scoped_ptr<spdy::SpdyFrame> + stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> + stream1_body(ConstructSpdyBodyFrame(1, true)); + MockWrite writes[] = { + CreateMockWrite(*stream1_syn, 1), + }; + + scoped_ptr<spdy::SpdyFrame> + stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> + stream2_syn(ConstructSpdyPush(NULL, + 0, + 2, + 1, + "http://www.google.com/foo.dat")); + MockRead reads[] = { + CreateMockRead(*stream1_reply, 2), + CreateMockRead(*stream2_syn, 3), + CreateMockRead(*stream1_body, 4, SYNCHRONOUS), + MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame), + arraysize(kPushBodyFrame), 5), + MockRead(ASYNC, ERR_IO_PENDING, 6), // Force a pause + }; + + HttpResponseInfo response; + HttpResponseInfo response2; + std::string expected_push_result("pushed"); + scoped_ptr<OrderedSocketData> data(new OrderedSocketData( + reads, + arraysize(reads), + writes, + arraysize(writes))); + RunServerPushTest(data.get(), + &response, + &response2, + expected_push_result); + + // Verify the SYN_REPLY. + EXPECT_TRUE(response.headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); + + // Verify the pushed stream. + EXPECT_TRUE(response2.headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine()); +} + +TEST_P(SpdyNetworkTransactionSpdy2Test, ServerPushSingleDataFrame2) { + static const unsigned char kPushBodyFrame[] = { + 0x00, 0x00, 0x00, 0x02, // header, ID + 0x01, 0x00, 0x00, 0x06, // FIN, length + 'p', 'u', 's', 'h', 'e', 'd' // "pushed" + }; + scoped_ptr<spdy::SpdyFrame> + stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + MockWrite writes[] = { + CreateMockWrite(*stream1_syn, 1), + }; + + scoped_ptr<spdy::SpdyFrame> + stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> + stream2_syn(ConstructSpdyPush(NULL, + 0, + 2, + 1, + "http://www.google.com/foo.dat")); + scoped_ptr<spdy::SpdyFrame> + stream1_body(ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*stream1_reply, 2), + CreateMockRead(*stream2_syn, 3), + MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame), + arraysize(kPushBodyFrame), 5), + CreateMockRead(*stream1_body, 4, SYNCHRONOUS), + MockRead(ASYNC, ERR_IO_PENDING, 6), // Force a pause + }; + + HttpResponseInfo response; + HttpResponseInfo response2; + std::string expected_push_result("pushed"); + scoped_ptr<OrderedSocketData> data(new OrderedSocketData( + reads, + arraysize(reads), + writes, + arraysize(writes))); + RunServerPushTest(data.get(), + &response, + &response2, + expected_push_result); + + // Verify the SYN_REPLY. + EXPECT_TRUE(response.headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); + + // Verify the pushed stream. + EXPECT_TRUE(response2.headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine()); +} + +TEST_P(SpdyNetworkTransactionSpdy2Test, ServerPushServerAborted) { + scoped_ptr<spdy::SpdyFrame> + stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> + stream1_body(ConstructSpdyBodyFrame(1, true)); + MockWrite writes[] = { + CreateMockWrite(*stream1_syn, 1), + }; + + scoped_ptr<spdy::SpdyFrame> + stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> + stream2_syn(ConstructSpdyPush(NULL, + 0, + 2, + 1, + "http://www.google.com/foo.dat")); + scoped_ptr<spdy::SpdyFrame> + stream2_rst(ConstructSpdyRstStream(2, spdy::PROTOCOL_ERROR)); + MockRead reads[] = { + CreateMockRead(*stream1_reply, 2), + CreateMockRead(*stream2_syn, 3), + CreateMockRead(*stream2_rst, 4), + CreateMockRead(*stream1_body, 5, SYNCHRONOUS), + MockRead(ASYNC, ERR_IO_PENDING, 6), // Force a pause + }; + + scoped_ptr<OrderedSocketData> data( + new OrderedSocketData(reads, arraysize(reads), + writes, arraysize(writes))); + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + + helper.RunPreTestSetup(); + helper.AddData(data.get()); + + HttpNetworkTransaction* trans = helper.trans(); + + // Start the transaction with basic parameters. + TestCompletionCallback callback; + int rv = trans->Start( + &CreateGetRequest(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + // Verify that we consumed all test data. + EXPECT_TRUE(data->at_read_eof()) << "Read count: " + << data->read_count() + << " Read index: " + << data->read_index(); + EXPECT_TRUE(data->at_write_eof()) << "Write count: " + << data->write_count() + << " Write index: " + << data->write_index(); + + // Verify the SYN_REPLY. + HttpResponseInfo response = *trans->GetResponseInfo(); + EXPECT_TRUE(response.headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); +} + +TEST_P(SpdyNetworkTransactionSpdy2Test, ServerPushDuplicate) { + // Verify that we don't leak streams and that we properly send a reset + // if the server pushes the same stream twice. + static const unsigned char kPushBodyFrame[] = { + 0x00, 0x00, 0x00, 0x02, // header, ID + 0x01, 0x00, 0x00, 0x06, // FIN, length + 'p', 'u', 's', 'h', 'e', 'd' // "pushed" + }; + + scoped_ptr<spdy::SpdyFrame> + stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> + stream1_body(ConstructSpdyBodyFrame(1, true)); + scoped_ptr<spdy::SpdyFrame> + stream3_rst(ConstructSpdyRstStream(4, spdy::PROTOCOL_ERROR)); + MockWrite writes[] = { + CreateMockWrite(*stream1_syn, 1), + CreateMockWrite(*stream3_rst, 5), + }; + + scoped_ptr<spdy::SpdyFrame> + stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> + stream2_syn(ConstructSpdyPush(NULL, + 0, + 2, + 1, + "http://www.google.com/foo.dat")); + scoped_ptr<spdy::SpdyFrame> + stream3_syn(ConstructSpdyPush(NULL, + 0, + 4, + 1, + "http://www.google.com/foo.dat")); + MockRead reads[] = { + CreateMockRead(*stream1_reply, 2), + CreateMockRead(*stream2_syn, 3), + CreateMockRead(*stream3_syn, 4), + CreateMockRead(*stream1_body, 6, SYNCHRONOUS), + MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame), + arraysize(kPushBodyFrame), 7), + MockRead(ASYNC, ERR_IO_PENDING, 8), // Force a pause + }; + + HttpResponseInfo response; + HttpResponseInfo response2; + std::string expected_push_result("pushed"); + scoped_ptr<OrderedSocketData> data(new OrderedSocketData( + reads, + arraysize(reads), + writes, + arraysize(writes))); + RunServerPushTest(data.get(), + &response, + &response2, + expected_push_result); + + // Verify the SYN_REPLY. + EXPECT_TRUE(response.headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); + + // Verify the pushed stream. + EXPECT_TRUE(response2.headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine()); +} + +TEST_P(SpdyNetworkTransactionSpdy2Test, ServerPushMultipleDataFrame) { + static const unsigned char kPushBodyFrame1[] = { + 0x00, 0x00, 0x00, 0x02, // header, ID + 0x01, 0x00, 0x00, 0x1F, // FIN, length + 'p', 'u', 's', 'h', 'e', 'd' // "pushed" + }; + static const char kPushBodyFrame2[] = " my darling"; + static const char kPushBodyFrame3[] = " hello"; + static const char kPushBodyFrame4[] = " my baby"; + + scoped_ptr<spdy::SpdyFrame> + stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> + stream1_body(ConstructSpdyBodyFrame(1, true)); + MockWrite writes[] = { + CreateMockWrite(*stream1_syn, 1), + }; + + scoped_ptr<spdy::SpdyFrame> + stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> + stream2_syn(ConstructSpdyPush(NULL, + 0, + 2, + 1, + "http://www.google.com/foo.dat")); + MockRead reads[] = { + CreateMockRead(*stream1_reply, 2), + CreateMockRead(*stream2_syn, 3), + MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame1), + arraysize(kPushBodyFrame1), 4), + MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame2), + arraysize(kPushBodyFrame2) - 1, 5), + MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame3), + arraysize(kPushBodyFrame3) - 1, 6), + MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame4), + arraysize(kPushBodyFrame4) - 1, 7), + CreateMockRead(*stream1_body, 8, SYNCHRONOUS), + MockRead(ASYNC, ERR_IO_PENDING, 9), // Force a pause + }; + + HttpResponseInfo response; + HttpResponseInfo response2; + std::string expected_push_result("pushed my darling hello my baby"); + scoped_ptr<OrderedSocketData> data(new OrderedSocketData( + reads, + arraysize(reads), + writes, + arraysize(writes))); + RunServerPushTest(data.get(), + &response, + &response2, + expected_push_result); + + // Verify the SYN_REPLY. + EXPECT_TRUE(response.headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); + + // Verify the pushed stream. + EXPECT_TRUE(response2.headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine()); +} + +TEST_P(SpdyNetworkTransactionSpdy2Test, + ServerPushMultipleDataFrameInterrupted) { + SpdySession::set_use_flow_control(SpdySession::kDisableFlowControl); + + static const unsigned char kPushBodyFrame1[] = { + 0x00, 0x00, 0x00, 0x02, // header, ID + 0x01, 0x00, 0x00, 0x1F, // FIN, length + 'p', 'u', 's', 'h', 'e', 'd' // "pushed" + }; + static const char kPushBodyFrame2[] = " my darling"; + static const char kPushBodyFrame3[] = " hello"; + static const char kPushBodyFrame4[] = " my baby"; + + scoped_ptr<spdy::SpdyFrame> + stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> + stream1_body(ConstructSpdyBodyFrame(1, true)); + MockWrite writes[] = { + CreateMockWrite(*stream1_syn, 1), + }; + + scoped_ptr<spdy::SpdyFrame> + stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> + stream2_syn(ConstructSpdyPush(NULL, + 0, + 2, + 1, + "http://www.google.com/foo.dat")); + MockRead reads[] = { + CreateMockRead(*stream1_reply, 2), + CreateMockRead(*stream2_syn, 3), + MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame1), + arraysize(kPushBodyFrame1), 4), + MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame2), + arraysize(kPushBodyFrame2) - 1, 5), + MockRead(ASYNC, ERR_IO_PENDING, 6), // Force a pause + MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame3), + arraysize(kPushBodyFrame3) - 1, 7), + MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame4), + arraysize(kPushBodyFrame4) - 1, 8), + CreateMockRead(*stream1_body.get(), 9, SYNCHRONOUS), + MockRead(ASYNC, ERR_IO_PENDING, 10) // Force a pause. + }; + + HttpResponseInfo response; + HttpResponseInfo response2; + std::string expected_push_result("pushed my darling hello my baby"); + scoped_ptr<OrderedSocketData> data(new OrderedSocketData( + reads, + arraysize(reads), + writes, + arraysize(writes))); + RunServerPushTest(data.get(), + &response, + &response2, + expected_push_result); + + // Verify the SYN_REPLY. + EXPECT_TRUE(response.headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); + + // Verify the pushed stream. + EXPECT_TRUE(response2.headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine()); + + SpdySession::set_use_flow_control(SpdySession::kFlowControlBasedOnNPN); +} + +TEST_P(SpdyNetworkTransactionSpdy2Test, ServerPushInvalidAssociatedStreamID0) { + scoped_ptr<spdy::SpdyFrame> + stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> + stream1_body(ConstructSpdyBodyFrame(1, true)); + scoped_ptr<spdy::SpdyFrame> + stream2_rst(ConstructSpdyRstStream(2, spdy::INVALID_STREAM)); + MockWrite writes[] = { + CreateMockWrite(*stream1_syn, 1), + CreateMockWrite(*stream2_rst, 4), + }; + + scoped_ptr<spdy::SpdyFrame> + stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> + stream2_syn(ConstructSpdyPush(NULL, + 0, + 2, + 0, + "http://www.google.com/foo.dat")); + MockRead reads[] = { + CreateMockRead(*stream1_reply, 2), + CreateMockRead(*stream2_syn, 3), + CreateMockRead(*stream1_body, 4), + MockRead(ASYNC, ERR_IO_PENDING, 5) // Force a pause + }; + + scoped_ptr<OrderedSocketData> data( + new OrderedSocketData(reads, arraysize(reads), + writes, arraysize(writes))); + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + + helper.RunPreTestSetup(); + helper.AddData(data.get()); + + HttpNetworkTransaction* trans = helper.trans(); + + // Start the transaction with basic parameters. + TestCompletionCallback callback; + int rv = trans->Start( + &CreateGetRequest(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + // Verify that we consumed all test data. + EXPECT_TRUE(data->at_read_eof()) << "Read count: " + << data->read_count() + << " Read index: " + << data->read_index(); + EXPECT_TRUE(data->at_write_eof()) << "Write count: " + << data->write_count() + << " Write index: " + << data->write_index(); + + // Verify the SYN_REPLY. + HttpResponseInfo response = *trans->GetResponseInfo(); + EXPECT_TRUE(response.headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); +} + +TEST_P(SpdyNetworkTransactionSpdy2Test, ServerPushInvalidAssociatedStreamID9) { + scoped_ptr<spdy::SpdyFrame> + stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> + stream1_body(ConstructSpdyBodyFrame(1, true)); + scoped_ptr<spdy::SpdyFrame> + stream2_rst(ConstructSpdyRstStream(2, spdy::INVALID_ASSOCIATED_STREAM)); + MockWrite writes[] = { + CreateMockWrite(*stream1_syn, 1), + CreateMockWrite(*stream2_rst, 4), + }; + + scoped_ptr<spdy::SpdyFrame> + stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> + stream2_syn(ConstructSpdyPush(NULL, + 0, + 2, + 9, + "http://www.google.com/foo.dat")); + MockRead reads[] = { + CreateMockRead(*stream1_reply, 2), + CreateMockRead(*stream2_syn, 3), + CreateMockRead(*stream1_body, 4), + MockRead(ASYNC, ERR_IO_PENDING, 5), // Force a pause + }; + + scoped_ptr<OrderedSocketData> data( + new OrderedSocketData(reads, arraysize(reads), + writes, arraysize(writes))); + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + + helper.RunPreTestSetup(); + helper.AddData(data.get()); + + HttpNetworkTransaction* trans = helper.trans(); + + // Start the transaction with basic parameters. + TestCompletionCallback callback; + int rv = trans->Start( + &CreateGetRequest(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + // Verify that we consumed all test data. + EXPECT_TRUE(data->at_read_eof()) << "Read count: " + << data->read_count() + << " Read index: " + << data->read_index(); + EXPECT_TRUE(data->at_write_eof()) << "Write count: " + << data->write_count() + << " Write index: " + << data->write_index(); + + // Verify the SYN_REPLY. + HttpResponseInfo response = *trans->GetResponseInfo(); + EXPECT_TRUE(response.headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); +} + +TEST_P(SpdyNetworkTransactionSpdy2Test, ServerPushNoURL) { + scoped_ptr<spdy::SpdyFrame> + stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> + stream1_body(ConstructSpdyBodyFrame(1, true)); + scoped_ptr<spdy::SpdyFrame> + stream2_rst(ConstructSpdyRstStream(2, spdy::PROTOCOL_ERROR)); + MockWrite writes[] = { + CreateMockWrite(*stream1_syn, 1), + CreateMockWrite(*stream2_rst, 4), + }; + + scoped_ptr<spdy::SpdyFrame> + stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> + stream2_syn(ConstructSpdyPush(NULL, 0, 2, 1)); + MockRead reads[] = { + CreateMockRead(*stream1_reply, 2), + CreateMockRead(*stream2_syn, 3), + CreateMockRead(*stream1_body, 4), + MockRead(ASYNC, ERR_IO_PENDING, 5) // Force a pause + }; + + scoped_ptr<OrderedSocketData> data( + new OrderedSocketData(reads, arraysize(reads), + writes, arraysize(writes))); + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + + helper.RunPreTestSetup(); + helper.AddData(data.get()); + + HttpNetworkTransaction* trans = helper.trans(); + + // Start the transaction with basic parameters. + TestCompletionCallback callback; + int rv = trans->Start( + &CreateGetRequest(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + // Verify that we consumed all test data. + EXPECT_TRUE(data->at_read_eof()) << "Read count: " + << data->read_count() + << " Read index: " + << data->read_index(); + EXPECT_TRUE(data->at_write_eof()) << "Write count: " + << data->write_count() + << " Write index: " + << data->write_index(); + + // Verify the SYN_REPLY. + HttpResponseInfo response = *trans->GetResponseInfo(); + EXPECT_TRUE(response.headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); +} + +// Verify that various SynReply headers parse correctly through the +// HTTP layer. +TEST_P(SpdyNetworkTransactionSpdy2Test, SynReplyHeaders) { + struct SynReplyHeadersTests { + int num_headers; + const char* extra_headers[5]; + const char* expected_headers; + } test_cases[] = { + // This uses a multi-valued cookie header. + { 2, + { "cookie", "val1", + "cookie", "val2", // will get appended separated by NULL + NULL + }, + "cookie: val1\n" + "cookie: val2\n" + "hello: bye\n" + "status: 200\n" + "version: HTTP/1.1\n" + }, + // This is the minimalist set of headers. + { 0, + { NULL }, + "hello: bye\n" + "status: 200\n" + "version: HTTP/1.1\n" + }, + // Headers with a comma separated list. + { 1, + { "cookie", "val1,val2", + NULL + }, + "cookie: val1,val2\n" + "hello: bye\n" + "status: 200\n" + "version: HTTP/1.1\n" + } + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) { + scoped_ptr<spdy::SpdyFrame> req( + ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + MockWrite writes[] = { CreateMockWrite(*req) }; + + scoped_ptr<spdy::SpdyFrame> resp( + ConstructSpdyGetSynReply(test_cases[i].extra_headers, + test_cases[i].num_headers, + 1)); + scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*resp), + CreateMockRead(*body), + MockRead(ASYNC, 0, 0) // EOF + }; + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData(1, reads, arraysize(reads), + writes, arraysize(writes))); + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.RunToCompletion(data.get()); + TransactionHelperResult out = helper.output(); + + 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 various SynReply headers parse vary fields correctly +// through the HTTP layer, and the response matches the request. +TEST_P(SpdyNetworkTransactionSpdy2Test, SynReplyHeadersVary) { + static const SpdyHeaderInfo syn_reply_info = { + spdy::SYN_REPLY, // Syn Reply + 1, // Stream ID + 0, // Associated Stream ID + net::ConvertRequestPriorityToSpdyPriority(LOWEST), + // Priority + spdy::CONTROL_FLAG_NONE, // Control Flags + false, // Compressed + spdy::INVALID, // Status + NULL, // Data + 0, // Data Length + spdy::DATA_FLAG_NONE // Data Flags + }; + // Modify the following data to change/add test cases: + struct SynReplyTests { + const SpdyHeaderInfo* syn_reply; + bool vary_matches; + int num_headers[2]; + const char* extra_headers[2][16]; + } 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_reply_info, + true, + { 1, 4 }, + { { "cookie", "val1,val2", + NULL + }, + { "vary", "cookie", + "status", "200", + "url", "/index.php", + "version", "HTTP/1.1", + NULL + } + } + }, { // Multiple vary fields. + &syn_reply_info, + true, + { 2, 5 }, + { { "friend", "barney", + "enemy", "snaggletooth", + NULL + }, + { "vary", "friend", + "vary", "enemy", + "status", "200", + "url", "/index.php", + "version", "HTTP/1.1", + NULL + } + } + }, { // Test a '*' vary field. + &syn_reply_info, + false, + { 1, 4 }, + { { "cookie", "val1,val2", + NULL + }, + { "vary", "*", + "status", "200", + "url", "/index.php", + "version", "HTTP/1.1", + NULL + } + } + }, { // Multiple comma-separated vary fields. + &syn_reply_info, + true, + { 2, 4 }, + { { "friend", "barney", + "enemy", "snaggletooth", + NULL + }, + { "vary", "friend,enemy", + "status", "200", + "url", "/index.php", + "version", "HTTP/1.1", + NULL + } + } + } + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) { + // Construct the request. + scoped_ptr<spdy::SpdyFrame> frame_req( + ConstructSpdyGet(test_cases[i].extra_headers[0], + test_cases[i].num_headers[0], + false, 1, LOWEST)); + + MockWrite writes[] = { + CreateMockWrite(*frame_req), + }; + + // Construct the reply. + scoped_ptr<spdy::SpdyFrame> frame_reply( + ConstructSpdyPacket(*test_cases[i].syn_reply, + test_cases[i].extra_headers[1], + test_cases[i].num_headers[1], + NULL, + 0)); + + scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*frame_reply), + CreateMockRead(*body), + MockRead(ASYNC, 0, 0) // EOF + }; + + // Attach the headers to the request. + int header_count = test_cases[i].num_headers[0]; + + HttpRequestInfo request = CreateGetRequest(); + for (int ct = 0; ct < header_count; ct++) { + const char* header_key = test_cases[i].extra_headers[0][ct * 2]; + const char* header_value = test_cases[i].extra_headers[0][ct * 2 + 1]; + request.extra_headers.SetHeader(header_key, header_value); + } + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData(1, reads, arraysize(reads), + writes, arraysize(writes))); + NormalSpdyTransactionHelper helper(request, + BoundNetLog(), GetParam()); + helper.RunToCompletion(data.get()); + TransactionHelperResult out = helper.output(); + + EXPECT_EQ(OK, out.rv) << i; + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line) << i; + EXPECT_EQ("hello!", out.response_data) << i; + + // Test the response information. + EXPECT_TRUE(out.response_info.response_time > + out.response_info.request_time) << i; + base::TimeDelta test_delay = out.response_info.response_time - + out.response_info.request_time; + base::TimeDelta min_expected_delay; + min_expected_delay.FromMilliseconds(10); + EXPECT_GT(test_delay.InMillisecondsF(), + min_expected_delay.InMillisecondsF()) << i; + EXPECT_EQ(out.response_info.vary_data.is_valid(), + test_cases[i].vary_matches) << i; + + // Check the headers. + scoped_refptr<HttpResponseHeaders> headers = out.response_info.headers; + ASSERT_TRUE(headers.get() != NULL) << i; + 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"); + } + + // Construct the expected header reply string. + char reply_buffer[256] = ""; + ConstructSpdyReplyString(test_cases[i].extra_headers[1], + test_cases[i].num_headers[1], + reply_buffer, + 256); + + EXPECT_EQ(std::string(reply_buffer), lines) << i; + } +} + +// Verify that we don't crash on invalid SynReply responses. +TEST_P(SpdyNetworkTransactionSpdy2Test, InvalidSynReply) { + const SpdyHeaderInfo kSynStartHeader = { + spdy::SYN_REPLY, // Kind = SynReply + 1, // Stream ID + 0, // Associated stream ID + net::ConvertRequestPriorityToSpdyPriority(LOWEST), + // Priority + spdy::CONTROL_FLAG_NONE, // Control Flags + false, // Compressed + spdy::INVALID, // Status + NULL, // Data + 0, // Length + spdy::DATA_FLAG_NONE // Data Flags + }; + + struct InvalidSynReplyTests { + int num_headers; + const char* headers[10]; + } test_cases[] = { + // SYN_REPLY missing status header + { 4, + { "cookie", "val1", + "cookie", "val2", + "url", "/index.php", + "version", "HTTP/1.1", + NULL + }, + }, + // SYN_REPLY missing version header + { 2, + { "status", "200", + "url", "/index.php", + NULL + }, + }, + // SYN_REPLY with no headers + { 0, { NULL }, }, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) { + scoped_ptr<spdy::SpdyFrame> req( + ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + MockWrite writes[] = { + CreateMockWrite(*req), + }; + + scoped_ptr<spdy::SpdyFrame> resp( + ConstructSpdyPacket(kSynStartHeader, + NULL, 0, + test_cases[i].headers, + test_cases[i].num_headers)); + scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*resp), + CreateMockRead(*body), + MockRead(ASYNC, 0, 0) // EOF + }; + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData(1, reads, arraysize(reads), + writes, arraysize(writes))); + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.RunToCompletion(data.get()); + TransactionHelperResult out = helper.output(); + EXPECT_EQ(ERR_INCOMPLETE_SPDY_HEADERS, out.rv); + } +} + +// Verify that we don't crash on some corrupt frames. +TEST_P(SpdyNetworkTransactionSpdy2Test, CorruptFrameSessionError) { + // This is the length field that's too short. + scoped_ptr<spdy::SpdyFrame> syn_reply_wrong_length( + ConstructSpdyGetSynReply(NULL, 0, 1)); + syn_reply_wrong_length->set_length(syn_reply_wrong_length->length() - 4); + + struct SynReplyTests { + const spdy::SpdyFrame* syn_reply; + } test_cases[] = { + { syn_reply_wrong_length.get(), }, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) { + scoped_ptr<spdy::SpdyFrame> req( + ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + MockWrite writes[] = { + CreateMockWrite(*req), + MockWrite(ASYNC, 0, 0) // EOF + }; + + scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*test_cases[i].syn_reply), + CreateMockRead(*body), + MockRead(ASYNC, 0, 0) // EOF + }; + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData(1, reads, arraysize(reads), + writes, arraysize(writes))); + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.RunToCompletion(data.get()); + TransactionHelperResult out = helper.output(); + EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, out.rv); + } +} + +// Test that we shutdown correctly on write errors. +TEST_P(SpdyNetworkTransactionSpdy2Test, WriteError) { + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + MockWrite writes[] = { + // We'll write 10 bytes successfully + MockWrite(ASYNC, req->data(), 10), + // Followed by ERROR! + MockWrite(ASYNC, ERR_FAILED), + }; + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData(2, NULL, 0, + writes, arraysize(writes))); + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.RunToCompletion(data.get()); + TransactionHelperResult out = helper.output(); + EXPECT_EQ(ERR_FAILED, out.rv); + data->Reset(); +} + +// Test that partial writes work. +TEST_P(SpdyNetworkTransactionSpdy2Test, PartialWrite) { + // Chop the SYN_STREAM frame into 5 chunks. + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + const int kChunks = 5; + scoped_array<MockWrite> writes(ChopWriteFrame(*req.get(), kChunks)); + + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*resp), + CreateMockRead(*body), + MockRead(ASYNC, 0, 0) // EOF + }; + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData(kChunks, reads, arraysize(reads), + writes.get(), kChunks)); + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.RunToCompletion(data.get()); + TransactionHelperResult out = helper.output(); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!", out.response_data); +} + +// In this test, we enable compression, but get a uncompressed SynReply from +// the server. Verify that teardown is all clean. +TEST_P(SpdyNetworkTransactionSpdy2Test, DecompressFailureOnSynReply) { + // For this test, we turn on the normal compression. + EnableCompression(true); + + scoped_ptr<spdy::SpdyFrame> compressed( + ConstructSpdyGet(NULL, 0, true, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> rst( + ConstructSpdyRstStream(1, spdy::PROTOCOL_ERROR)); + MockWrite writes[] = { + CreateMockWrite(*compressed), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*resp), + }; + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData(1, reads, arraysize(reads), + writes, arraysize(writes))); + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.RunToCompletion(data.get()); + TransactionHelperResult out = helper.output(); + EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, out.rv); + data->Reset(); + + EnableCompression(false); +} + +// Test that the NetLog contains good data for a simple GET request. +TEST_P(SpdyNetworkTransactionSpdy2Test, NetLog) { + static const char* const kExtraHeaders[] = { + "user-agent", "Chrome", + }; + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(kExtraHeaders, 1, false, 1, + LOWEST)); + MockWrite writes[] = { CreateMockWrite(*req) }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*resp), + CreateMockRead(*body), + MockRead(ASYNC, 0, 0) // EOF + }; + + net::CapturingBoundNetLog log(net::CapturingNetLog::kUnbounded); + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData(1, reads, arraysize(reads), + writes, arraysize(writes))); + NormalSpdyTransactionHelper helper(CreateGetRequestWithUserAgent(), + log.bound(), GetParam()); + helper.RunToCompletion(data.get()); + TransactionHelperResult out = helper.output(); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!", out.response_data); + + // Check that the NetLog 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, and that + // they are in the right order. + net::CapturingNetLog::EntryList entries; + log.GetEntries(&entries); + + EXPECT_LT(0u, entries.size()); + int pos = 0; + pos = net::ExpectLogContainsSomewhere(entries, 0, + net::NetLog::TYPE_HTTP_TRANSACTION_SEND_REQUEST, + net::NetLog::PHASE_BEGIN); + pos = net::ExpectLogContainsSomewhere(entries, pos + 1, + net::NetLog::TYPE_HTTP_TRANSACTION_SEND_REQUEST, + net::NetLog::PHASE_END); + pos = net::ExpectLogContainsSomewhere(entries, pos + 1, + net::NetLog::TYPE_HTTP_TRANSACTION_READ_HEADERS, + net::NetLog::PHASE_BEGIN); + pos = net::ExpectLogContainsSomewhere(entries, pos + 1, + net::NetLog::TYPE_HTTP_TRANSACTION_READ_HEADERS, + net::NetLog::PHASE_END); + pos = net::ExpectLogContainsSomewhere(entries, pos + 1, + net::NetLog::TYPE_HTTP_TRANSACTION_READ_BODY, + net::NetLog::PHASE_BEGIN); + pos = net::ExpectLogContainsSomewhere(entries, pos + 1, + net::NetLog::TYPE_HTTP_TRANSACTION_READ_BODY, + net::NetLog::PHASE_END); + + // Check that we logged all the headers correctly + pos = net::ExpectLogContainsSomewhere( + entries, 0, + net::NetLog::TYPE_SPDY_SESSION_SYN_STREAM, + net::NetLog::PHASE_NONE); + CapturingNetLog::Entry entry = entries[pos]; + NetLogSpdySynParameter* request_params = + static_cast<NetLogSpdySynParameter*>(entry.extra_parameters.get()); + spdy::SpdyHeaderBlock* headers = + request_params->GetHeaders().get(); + + spdy::SpdyHeaderBlock expected; + expected["host"] = "www.google.com"; + expected["url"] = "/"; + expected["scheme"] = "http"; + expected["version"] = "HTTP/1.1"; + expected["method"] = "GET"; + expected["user-agent"] = "Chrome"; + EXPECT_EQ(expected.size(), headers->size()); + spdy::SpdyHeaderBlock::const_iterator end = expected.end(); + for (spdy::SpdyHeaderBlock::const_iterator it = expected.begin(); + it != end; + ++it) { + EXPECT_EQ(it->second, (*headers)[it->first]); + } +} + +// Since we buffer the IO from the stream to the renderer, this test verifies +// that when we read out the maximum amount of data (e.g. we received 50 bytes +// on the network, but issued a Read for only 5 of those bytes) that the data +// flow still works correctly. +TEST_P(SpdyNetworkTransactionSpdy2Test, BufferFull) { + SpdySession::set_use_flow_control(SpdySession::kDisableFlowControl); + + spdy::SpdyFramer framer; + + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + MockWrite writes[] = { CreateMockWrite(*req) }; + + // 2 data frames in a single read. + scoped_ptr<spdy::SpdyFrame> data_frame_1( + framer.CreateDataFrame(1, "goodby", 6, spdy::DATA_FLAG_NONE)); + scoped_ptr<spdy::SpdyFrame> data_frame_2( + framer.CreateDataFrame(1, "e worl", 6, spdy::DATA_FLAG_NONE)); + const spdy::SpdyFrame* data_frames[2] = { + data_frame_1.get(), + data_frame_2.get(), + }; + char combined_data_frames[100]; + int combined_data_frames_len = + CombineFrames(data_frames, arraysize(data_frames), + combined_data_frames, arraysize(combined_data_frames)); + scoped_ptr<spdy::SpdyFrame> last_frame( + framer.CreateDataFrame(1, "d", 1, spdy::DATA_FLAG_FIN)); + + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + MockRead reads[] = { + CreateMockRead(*resp), + MockRead(ASYNC, ERR_IO_PENDING), // Force a pause + MockRead(ASYNC, combined_data_frames, combined_data_frames_len), + MockRead(ASYNC, ERR_IO_PENDING), // Force a pause + CreateMockRead(*last_frame), + MockRead(ASYNC, 0, 0) // EOF + }; + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData(1, reads, arraysize(reads), + writes, arraysize(writes))); + + + TestCompletionCallback callback; + + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.RunPreTestSetup(); + helper.AddData(data.get()); + HttpNetworkTransaction* trans = helper.trans(); + int rv = trans->Start( + &CreateGetRequest(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + TransactionHelperResult out = helper.output(); + out.rv = callback.WaitForResult(); + EXPECT_EQ(out.rv, OK); + + 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. + + // Read Data + TestCompletionCallback read_callback; + + std::string content; + do { + // Read small chunks at a time. + const int kSmallReadSize = 3; + scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(kSmallReadSize)); + rv = trans->Read(buf, kSmallReadSize, read_callback.callback()); + if (rv == net::ERR_IO_PENDING) { + data->CompleteRead(); + rv = read_callback.WaitForResult(); + } + if (rv > 0) { + content.append(buf->data(), rv); + } else if (rv < 0) { + NOTREACHED(); + } + } while (rv > 0); + + out.response_data.swap(content); + + // Flush the MessageLoop while the SpdySessionDependencies (in particular, the + // MockClientSocketFactory) are still alive. + MessageLoop::current()->RunAllPending(); + + // Verify that we consumed all test data. + helper.VerifyDataConsumed(); + + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("goodbye world", out.response_data); + + SpdySession::set_use_flow_control(SpdySession::kFlowControlBasedOnNPN); +} + +// Verify that basic buffering works; when multiple data frames arrive +// at the same time, ensure that we don't notify a read completion for +// each data frame individually. +TEST_P(SpdyNetworkTransactionSpdy2Test, Buffering) { + SpdySession::set_use_flow_control(SpdySession::kDisableFlowControl); + + spdy::SpdyFramer framer; + + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + MockWrite writes[] = { CreateMockWrite(*req) }; + + // 4 data frames in a single read. + scoped_ptr<spdy::SpdyFrame> data_frame( + framer.CreateDataFrame(1, "message", 7, spdy::DATA_FLAG_NONE)); + scoped_ptr<spdy::SpdyFrame> data_frame_fin( + framer.CreateDataFrame(1, "message", 7, spdy::DATA_FLAG_FIN)); + const spdy::SpdyFrame* data_frames[4] = { + data_frame.get(), + data_frame.get(), + data_frame.get(), + data_frame_fin.get() + }; + char combined_data_frames[100]; + int combined_data_frames_len = + CombineFrames(data_frames, arraysize(data_frames), + combined_data_frames, arraysize(combined_data_frames)); + + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + MockRead reads[] = { + CreateMockRead(*resp), + MockRead(ASYNC, ERR_IO_PENDING), // Force a pause + MockRead(ASYNC, combined_data_frames, combined_data_frames_len), + MockRead(ASYNC, 0, 0) // EOF + }; + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData(1, reads, arraysize(reads), + writes, arraysize(writes))); + + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.RunPreTestSetup(); + helper.AddData(data.get()); + HttpNetworkTransaction* trans = helper.trans(); + + TestCompletionCallback callback; + int rv = trans->Start( + &CreateGetRequest(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + TransactionHelperResult out = helper.output(); + out.rv = callback.WaitForResult(); + EXPECT_EQ(out.rv, OK); + + 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. + + // Read Data + TestCompletionCallback read_callback; + + std::string content; + int reads_completed = 0; + do { + // Read small chunks at a time. + const int kSmallReadSize = 14; + scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(kSmallReadSize)); + rv = trans->Read(buf, kSmallReadSize, read_callback.callback()); + if (rv == net::ERR_IO_PENDING) { + data->CompleteRead(); + rv = read_callback.WaitForResult(); + } + if (rv > 0) { + EXPECT_EQ(kSmallReadSize, rv); + content.append(buf->data(), rv); + } else if (rv < 0) { + FAIL() << "Unexpected read error: " << rv; + } + reads_completed++; + } while (rv > 0); + + EXPECT_EQ(3, reads_completed); // Reads are: 14 bytes, 14 bytes, 0 bytes. + + out.response_data.swap(content); + + // Flush the MessageLoop while the SpdySessionDependencies (in particular, the + // MockClientSocketFactory) are still alive. + MessageLoop::current()->RunAllPending(); + + // Verify that we consumed all test data. + helper.VerifyDataConsumed(); + + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("messagemessagemessagemessage", out.response_data); + + SpdySession::set_use_flow_control(SpdySession::kFlowControlBasedOnNPN); +} + +// Verify the case where we buffer data but read it after it has been buffered. +TEST_P(SpdyNetworkTransactionSpdy2Test, BufferedAll) { + spdy::SpdyFramer framer; + + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + MockWrite writes[] = { CreateMockWrite(*req) }; + + // 5 data frames in a single read. + scoped_ptr<spdy::SpdyFrame> syn_reply( + ConstructSpdyGetSynReply(NULL, 0, 1)); + syn_reply->set_flags(spdy::CONTROL_FLAG_NONE); // turn off FIN bit + scoped_ptr<spdy::SpdyFrame> data_frame( + framer.CreateDataFrame(1, "message", 7, spdy::DATA_FLAG_NONE)); + scoped_ptr<spdy::SpdyFrame> data_frame_fin( + framer.CreateDataFrame(1, "message", 7, spdy::DATA_FLAG_FIN)); + const spdy::SpdyFrame* frames[5] = { + syn_reply.get(), + data_frame.get(), + data_frame.get(), + data_frame.get(), + data_frame_fin.get() + }; + char combined_frames[200]; + int combined_frames_len = + CombineFrames(frames, arraysize(frames), + combined_frames, arraysize(combined_frames)); + + MockRead reads[] = { + MockRead(ASYNC, combined_frames, combined_frames_len), + MockRead(ASYNC, 0, 0) // EOF + }; + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData(1, reads, arraysize(reads), + writes, arraysize(writes))); + + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.RunPreTestSetup(); + helper.AddData(data.get()); + HttpNetworkTransaction* trans = helper.trans(); + + TestCompletionCallback callback; + int rv = trans->Start( + &CreateGetRequest(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + TransactionHelperResult out = helper.output(); + out.rv = callback.WaitForResult(); + EXPECT_EQ(out.rv, OK); + + 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. + + // Read Data + TestCompletionCallback read_callback; + + std::string content; + int reads_completed = 0; + do { + // Read small chunks at a time. + const int kSmallReadSize = 14; + scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(kSmallReadSize)); + rv = trans->Read(buf, kSmallReadSize, read_callback.callback()); + if (rv > 0) { + EXPECT_EQ(kSmallReadSize, rv); + content.append(buf->data(), rv); + } else if (rv < 0) { + FAIL() << "Unexpected read error: " << rv; + } + reads_completed++; + } while (rv > 0); + + EXPECT_EQ(3, reads_completed); + + out.response_data.swap(content); + + // Flush the MessageLoop while the SpdySessionDependencies (in particular, the + // MockClientSocketFactory) are still alive. + MessageLoop::current()->RunAllPending(); + + // Verify that we consumed all test data. + helper.VerifyDataConsumed(); + + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("messagemessagemessagemessage", out.response_data); +} + +// Verify the case where we buffer data and close the connection. +TEST_P(SpdyNetworkTransactionSpdy2Test, BufferedClosed) { + spdy::SpdyFramer framer; + + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + MockWrite writes[] = { CreateMockWrite(*req) }; + + // All data frames in a single read. + // NOTE: We don't FIN the stream. + scoped_ptr<spdy::SpdyFrame> data_frame( + framer.CreateDataFrame(1, "message", 7, spdy::DATA_FLAG_NONE)); + const spdy::SpdyFrame* data_frames[4] = { + data_frame.get(), + data_frame.get(), + data_frame.get(), + data_frame.get() + }; + char combined_data_frames[100]; + int combined_data_frames_len = + CombineFrames(data_frames, arraysize(data_frames), + combined_data_frames, arraysize(combined_data_frames)); + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + MockRead reads[] = { + CreateMockRead(*resp), + MockRead(ASYNC, ERR_IO_PENDING), // Force a wait + MockRead(ASYNC, combined_data_frames, combined_data_frames_len), + MockRead(ASYNC, 0, 0) // EOF + }; + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData(1, reads, arraysize(reads), + writes, arraysize(writes))); + + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.RunPreTestSetup(); + helper.AddData(data.get()); + HttpNetworkTransaction* trans = helper.trans(); + + TestCompletionCallback callback; + + int rv = trans->Start( + &CreateGetRequest(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + TransactionHelperResult out = helper.output(); + out.rv = callback.WaitForResult(); + EXPECT_EQ(out.rv, OK); + + 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. + + // Read Data + TestCompletionCallback read_callback; + + std::string content; + int reads_completed = 0; + do { + // Read small chunks at a time. + const int kSmallReadSize = 14; + scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(kSmallReadSize)); + rv = trans->Read(buf, kSmallReadSize, read_callback.callback()); + if (rv == net::ERR_IO_PENDING) { + data->CompleteRead(); + rv = read_callback.WaitForResult(); + } + if (rv > 0) { + content.append(buf->data(), rv); + } else if (rv < 0) { + // This test intentionally closes the connection, and will get an error. + EXPECT_EQ(ERR_CONNECTION_CLOSED, rv); + break; + } + reads_completed++; + } while (rv > 0); + + EXPECT_EQ(0, reads_completed); + + out.response_data.swap(content); + + // Flush the MessageLoop while the SpdySessionDependencies (in particular, the + // MockClientSocketFactory) are still alive. + MessageLoop::current()->RunAllPending(); + + // Verify that we consumed all test data. + helper.VerifyDataConsumed(); +} + +// Verify the case where we buffer data and cancel the transaction. +TEST_P(SpdyNetworkTransactionSpdy2Test, BufferedCancelled) { + spdy::SpdyFramer framer; + + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + MockWrite writes[] = { CreateMockWrite(*req) }; + + // NOTE: We don't FIN the stream. + scoped_ptr<spdy::SpdyFrame> data_frame( + framer.CreateDataFrame(1, "message", 7, spdy::DATA_FLAG_NONE)); + + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + MockRead reads[] = { + CreateMockRead(*resp), + MockRead(ASYNC, ERR_IO_PENDING), // Force a wait + CreateMockRead(*data_frame), + MockRead(ASYNC, 0, 0) // EOF + }; + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData(1, reads, arraysize(reads), + writes, arraysize(writes))); + + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.RunPreTestSetup(); + helper.AddData(data.get()); + HttpNetworkTransaction* trans = helper.trans(); + TestCompletionCallback callback; + + int rv = trans->Start( + &CreateGetRequest(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + TransactionHelperResult out = helper.output(); + out.rv = callback.WaitForResult(); + EXPECT_EQ(out.rv, OK); + + 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. + + // Read Data + TestCompletionCallback read_callback; + + do { + const int kReadSize = 256; + scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(kReadSize)); + rv = trans->Read(buf, kReadSize, read_callback.callback()); + if (rv == net::ERR_IO_PENDING) { + // Complete the read now, which causes buffering to start. + data->CompleteRead(); + // Destroy the transaction, causing the stream to get cancelled + // and orphaning the buffered IO task. + helper.ResetTrans(); + break; + } + // We shouldn't get here in this test. + FAIL() << "Unexpected read: " << rv; + } while (rv > 0); + + // Flush the MessageLoop; this will cause the buffered IO task + // to run for the final time. + MessageLoop::current()->RunAllPending(); + + // Verify that we consumed all test data. + helper.VerifyDataConsumed(); +} + +// Test that if the server requests persistence of settings, that we save +// the settings in the SpdySettingsStorage. +TEST_P(SpdyNetworkTransactionSpdy2Test, SettingsSaved) { + static const SpdyHeaderInfo kSynReplyInfo = { + spdy::SYN_REPLY, // Syn Reply + 1, // Stream ID + 0, // Associated Stream ID + net::ConvertRequestPriorityToSpdyPriority(LOWEST), + // Priority + spdy::CONTROL_FLAG_NONE, // Control Flags + false, // Compressed + spdy::INVALID, // Status + NULL, // Data + 0, // Data Length + spdy::DATA_FLAG_NONE // Data Flags + }; + static const char* const kExtraHeaders[] = { + "status", "200", + "version", "HTTP/1.1" + }; + + BoundNetLog net_log; + NormalSpdyTransactionHelper helper(CreateGetRequest(), net_log, GetParam()); + helper.RunPreTestSetup(); + + // Verify that no settings exist initially. + HostPortPair host_port_pair("www.google.com", helper.port()); + SpdySessionPool* spdy_session_pool = helper.session()->spdy_session_pool(); + EXPECT_TRUE(spdy_session_pool->http_server_properties()->GetSpdySettings( + host_port_pair).empty()); + + // Construct the request. + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + MockWrite writes[] = { CreateMockWrite(*req) }; + + // Construct the reply. + scoped_ptr<spdy::SpdyFrame> reply( + ConstructSpdyPacket(kSynReplyInfo, + kExtraHeaders, + arraysize(kExtraHeaders) / 2, + NULL, + 0)); + + unsigned int kSampleId1 = 0x1; + unsigned int kSampleValue1 = 0x0a0a0a0a; + unsigned int kSampleId2 = 0x2; + unsigned int kSampleValue2 = 0x0b0b0b0b; + unsigned int kSampleId3 = 0xababab; + unsigned int kSampleValue3 = 0x0c0c0c0c; + scoped_ptr<spdy::SpdyFrame> settings_frame; + { + // Construct the SETTINGS frame. + spdy::SpdySettings settings; + spdy::SettingsFlagsAndId setting(0); + // First add a persisted setting + setting.set_flags(spdy::SETTINGS_FLAG_PLEASE_PERSIST); + setting.set_id(kSampleId1); + settings.push_back(std::make_pair(setting, kSampleValue1)); + // Next add a non-persisted setting + setting.set_flags(0); + setting.set_id(kSampleId2); + settings.push_back(std::make_pair(setting, kSampleValue2)); + // Next add another persisted setting + setting.set_flags(spdy::SETTINGS_FLAG_PLEASE_PERSIST); + setting.set_id(kSampleId3); + settings.push_back(std::make_pair(setting, kSampleValue3)); + settings_frame.reset(ConstructSpdySettings(settings)); + } + + scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*reply), + CreateMockRead(*body), + CreateMockRead(*settings_frame), + MockRead(ASYNC, 0, 0) // EOF + }; + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData(1, reads, arraysize(reads), + writes, arraysize(writes))); + helper.AddData(data.get()); + helper.RunDefaultTest(); + helper.VerifyDataConsumed(); + TransactionHelperResult out = helper.output(); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!", out.response_data); + + { + // Verify we had two persisted settings. + spdy::SpdySettings saved_settings = + spdy_session_pool->http_server_properties()->GetSpdySettings( + host_port_pair); + ASSERT_EQ(2u, saved_settings.size()); + + // Verify the first persisted setting. + spdy::SpdySetting setting = saved_settings.front(); + saved_settings.pop_front(); + EXPECT_EQ(spdy::SETTINGS_FLAG_PERSISTED, setting.first.flags()); + EXPECT_EQ(kSampleId1, setting.first.id()); + EXPECT_EQ(kSampleValue1, setting.second); + + // Verify the second persisted setting. + setting = saved_settings.front(); + saved_settings.pop_front(); + EXPECT_EQ(spdy::SETTINGS_FLAG_PERSISTED, setting.first.flags()); + EXPECT_EQ(kSampleId3, setting.first.id()); + EXPECT_EQ(kSampleValue3, setting.second); + } +} + +// Test that when there are settings saved that they are sent back to the +// server upon session establishment. +TEST_P(SpdyNetworkTransactionSpdy2Test, SettingsPlayback) { + static const SpdyHeaderInfo kSynReplyInfo = { + spdy::SYN_REPLY, // Syn Reply + 1, // Stream ID + 0, // Associated Stream ID + net::ConvertRequestPriorityToSpdyPriority(LOWEST), + // Priority + spdy::CONTROL_FLAG_NONE, // Control Flags + false, // Compressed + spdy::INVALID, // Status + NULL, // Data + 0, // Data Length + spdy::DATA_FLAG_NONE // Data Flags + }; + static const char* kExtraHeaders[] = { + "status", "200", + "version", "HTTP/1.1" + }; + + BoundNetLog net_log; + NormalSpdyTransactionHelper helper(CreateGetRequest(), net_log, GetParam()); + helper.RunPreTestSetup(); + + // Verify that no settings exist initially. + HostPortPair host_port_pair("www.google.com", helper.port()); + SpdySessionPool* spdy_session_pool = helper.session()->spdy_session_pool(); + EXPECT_TRUE(spdy_session_pool->http_server_properties()->GetSpdySettings( + host_port_pair).empty()); + + unsigned int kSampleId1 = 0x1; + unsigned int kSampleValue1 = 0x0a0a0a0a; + unsigned int kSampleId2 = 0xababab; + unsigned int kSampleValue2 = 0x0c0c0c0c; + // Manually insert settings into the SpdySettingsStorage here. + { + spdy::SpdySettings settings; + spdy::SettingsFlagsAndId setting(0); + // First add a persisted setting + setting.set_flags(spdy::SETTINGS_FLAG_PLEASE_PERSIST); + setting.set_id(kSampleId1); + settings.push_back(std::make_pair(setting, kSampleValue1)); + // Next add another persisted setting + setting.set_flags(spdy::SETTINGS_FLAG_PLEASE_PERSIST); + setting.set_id(kSampleId2); + settings.push_back(std::make_pair(setting, kSampleValue2)); + + spdy_session_pool->http_server_properties()->SetSpdySettings( + host_port_pair, settings); + } + + EXPECT_EQ(2u, spdy_session_pool->http_server_properties()->GetSpdySettings( + host_port_pair).size()); + + // Construct the SETTINGS frame. + const spdy::SpdySettings& settings = + spdy_session_pool->http_server_properties()->GetSpdySettings( + host_port_pair); + scoped_ptr<spdy::SpdyFrame> settings_frame(ConstructSpdySettings(settings)); + + // Construct the request. + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + + MockWrite writes[] = { + CreateMockWrite(*settings_frame), + CreateMockWrite(*req), + }; + + // Construct the reply. + scoped_ptr<spdy::SpdyFrame> reply( + ConstructSpdyPacket(kSynReplyInfo, + kExtraHeaders, + arraysize(kExtraHeaders) / 2, + NULL, + 0)); + + scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*reply), + CreateMockRead(*body), + MockRead(ASYNC, 0, 0) // EOF + }; + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData(2, reads, arraysize(reads), + writes, arraysize(writes))); + helper.AddData(data.get()); + helper.RunDefaultTest(); + helper.VerifyDataConsumed(); + TransactionHelperResult out = helper.output(); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!", out.response_data); + + { + // Verify we had two persisted settings. + spdy::SpdySettings saved_settings = + spdy_session_pool->http_server_properties()->GetSpdySettings( + host_port_pair); + ASSERT_EQ(2u, saved_settings.size()); + + // Verify the first persisted setting. + spdy::SpdySetting setting = saved_settings.front(); + saved_settings.pop_front(); + EXPECT_EQ(spdy::SETTINGS_FLAG_PERSISTED, setting.first.flags()); + EXPECT_EQ(kSampleId1, setting.first.id()); + EXPECT_EQ(kSampleValue1, setting.second); + + // Verify the second persisted setting. + setting = saved_settings.front(); + saved_settings.pop_front(); + EXPECT_EQ(spdy::SETTINGS_FLAG_PERSISTED, setting.first.flags()); + EXPECT_EQ(kSampleId2, setting.first.id()); + EXPECT_EQ(kSampleValue2, setting.second); + } +} + +TEST_P(SpdyNetworkTransactionSpdy2Test, GoAwayWithActiveStream) { + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + MockWrite writes[] = { CreateMockWrite(*req) }; + + scoped_ptr<spdy::SpdyFrame> go_away(ConstructSpdyGoAway()); + MockRead reads[] = { + CreateMockRead(*go_away), + MockRead(ASYNC, 0, 0), // EOF + }; + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData(1, reads, arraysize(reads), + writes, arraysize(writes))); + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.AddData(data.get()); + helper.RunToCompletion(data.get()); + TransactionHelperResult out = helper.output(); + EXPECT_EQ(ERR_ABORTED, out.rv); +} + +TEST_P(SpdyNetworkTransactionSpdy2Test, CloseWithActiveStream) { + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + MockWrite writes[] = { CreateMockWrite(*req) }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + MockRead reads[] = { + CreateMockRead(*resp), + MockRead(SYNCHRONOUS, 0, 0) // EOF + }; + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData(1, reads, arraysize(reads), + writes, arraysize(writes))); + BoundNetLog log; + NormalSpdyTransactionHelper helper(CreateGetRequest(), + log, GetParam()); + helper.RunPreTestSetup(); + helper.AddData(data.get()); + HttpNetworkTransaction* trans = helper.trans(); + + TestCompletionCallback callback; + TransactionHelperResult out; + out.rv = trans->Start(&CreateGetRequest(), callback.callback(), log); + + EXPECT_EQ(out.rv, ERR_IO_PENDING); + out.rv = callback.WaitForResult(); + EXPECT_EQ(out.rv, OK); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + EXPECT_TRUE(response->headers != NULL); + EXPECT_TRUE(response->was_fetched_via_spdy); + out.rv = ReadTransaction(trans, &out.response_data); + EXPECT_EQ(ERR_CONNECTION_CLOSED, out.rv); + + // Verify that we consumed all test data. + helper.VerifyDataConsumed(); +} + +// Test to make sure we can correctly connect through a proxy. +TEST_P(SpdyNetworkTransactionSpdy2Test, ProxyConnect) { + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.session_deps().reset(new SpdySessionDependencies( + ProxyService::CreateFixedFromPacResult("PROXY myproxy:70"))); + helper.SetSession(make_scoped_refptr( + SpdySessionDependencies::SpdyCreateSession(helper.session_deps().get()))); + helper.RunPreTestSetup(); + HttpNetworkTransaction* trans = helper.trans(); + + const char kConnect443[] = {"CONNECT www.google.com:443 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n\r\n"}; + const char kConnect80[] = {"CONNECT www.google.com:80 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n\r\n"}; + const char kHTTP200[] = {"HTTP/1.1 200 OK\r\n\r\n"}; + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true)); + + MockWrite writes_SPDYNPN[] = { + MockWrite(SYNCHRONOUS, kConnect443, arraysize(kConnect443) - 1, 0), + CreateMockWrite(*req, 2), + }; + MockRead reads_SPDYNPN[] = { + MockRead(SYNCHRONOUS, kHTTP200, arraysize(kHTTP200) - 1, 1), + CreateMockRead(*resp, 3), + CreateMockRead(*body.get(), 4), + MockRead(ASYNC, 0, 0, 5), + }; + + MockWrite writes_SPDYSSL[] = { + MockWrite(SYNCHRONOUS, kConnect80, arraysize(kConnect80) - 1, 0), + CreateMockWrite(*req, 2), + }; + MockRead reads_SPDYSSL[] = { + MockRead(SYNCHRONOUS, kHTTP200, arraysize(kHTTP200) - 1, 1), + CreateMockRead(*resp, 3), + CreateMockRead(*body.get(), 4), + MockRead(ASYNC, 0, 0, 5), + }; + + MockWrite writes_SPDYNOSSL[] = { + CreateMockWrite(*req, 0), + }; + + MockRead reads_SPDYNOSSL[] = { + CreateMockRead(*resp, 1), + CreateMockRead(*body.get(), 2), + MockRead(ASYNC, 0, 0, 3), + }; + + scoped_ptr<OrderedSocketData> data; + switch(GetParam()) { + case SPDYNOSSL: + data.reset(new OrderedSocketData(reads_SPDYNOSSL, + arraysize(reads_SPDYNOSSL), + writes_SPDYNOSSL, + arraysize(writes_SPDYNOSSL))); + break; + case SPDYSSL: + data.reset(new OrderedSocketData(reads_SPDYSSL, + arraysize(reads_SPDYSSL), + writes_SPDYSSL, + arraysize(writes_SPDYSSL))); + break; + case SPDYNPN: + data.reset(new OrderedSocketData(reads_SPDYNPN, + arraysize(reads_SPDYNPN), + writes_SPDYNPN, + arraysize(writes_SPDYNPN))); + break; + default: + NOTREACHED(); + } + + helper.AddData(data.get()); + TestCompletionCallback callback; + + int rv = trans->Start( + &CreateGetRequest(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(0, rv); + + // Verify the SYN_REPLY. + HttpResponseInfo response = *trans->GetResponseInfo(); + EXPECT_TRUE(response.headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); + + std::string response_data; + ASSERT_EQ(OK, ReadTransaction(trans, &response_data)); + EXPECT_EQ("hello!", response_data); + helper.VerifyDataConsumed(); +} + +// Test to make sure we can correctly connect through a proxy to www.google.com, +// if there already exists a direct spdy connection to www.google.com. See +// http://crbug.com/49874 +TEST_P(SpdyNetworkTransactionSpdy2Test, DirectConnectProxyReconnect) { + // When setting up the first transaction, we store the SpdySessionPool so that + // we can use the same pool in the second transaction. + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + + // Use a proxy service which returns a proxy fallback list from DIRECT to + // myproxy:70. For this test there will be no fallback, so it is equivalent + // to simply DIRECT. The reason for appending the second proxy is to verify + // that the session pool key used does is just "DIRECT". + helper.session_deps().reset(new SpdySessionDependencies( + ProxyService::CreateFixedFromPacResult("DIRECT; PROXY myproxy:70"))); + helper.SetSession(make_scoped_refptr( + SpdySessionDependencies::SpdyCreateSession(helper.session_deps().get()))); + + SpdySessionPool* spdy_session_pool = helper.session()->spdy_session_pool(); + helper.RunPreTestSetup(); + + // Construct and send a simple GET request. + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + MockWrite writes[] = { + CreateMockWrite(*req, 1), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*resp, 2), + CreateMockRead(*body, 3), + MockRead(ASYNC, ERR_IO_PENDING, 4), // Force a pause + MockRead(ASYNC, 0, 5) // EOF + }; + scoped_ptr<OrderedSocketData> data( + new OrderedSocketData(reads, arraysize(reads), + writes, arraysize(writes))); + helper.AddData(data.get()); + HttpNetworkTransaction* trans = helper.trans(); + + TestCompletionCallback callback; + TransactionHelperResult out; + out.rv = trans->Start( + &CreateGetRequest(), callback.callback(), BoundNetLog()); + + EXPECT_EQ(out.rv, ERR_IO_PENDING); + out.rv = callback.WaitForResult(); + EXPECT_EQ(out.rv, OK); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + EXPECT_TRUE(response->headers != NULL); + EXPECT_TRUE(response->was_fetched_via_spdy); + out.rv = ReadTransaction(trans, &out.response_data); + EXPECT_EQ(OK, out.rv); + out.status_line = response->headers->GetStatusLine(); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!", out.response_data); + + // Check that the SpdySession is still in the SpdySessionPool. + HostPortPair host_port_pair("www.google.com", helper.port()); + HostPortProxyPair session_pool_key_direct( + host_port_pair, ProxyServer::Direct()); + EXPECT_TRUE(spdy_session_pool->HasSession(session_pool_key_direct)); + HostPortProxyPair session_pool_key_proxy( + host_port_pair, + ProxyServer::FromURI("www.foo.com", ProxyServer::SCHEME_HTTP)); + EXPECT_FALSE(spdy_session_pool->HasSession(session_pool_key_proxy)); + + // Set up data for the proxy connection. + const char kConnect443[] = {"CONNECT www.google.com:443 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n\r\n"}; + const char kConnect80[] = {"CONNECT www.google.com:80 HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "Proxy-Connection: keep-alive\r\n\r\n"}; + const char kHTTP200[] = {"HTTP/1.1 200 OK\r\n\r\n"}; + scoped_ptr<spdy::SpdyFrame> req2(ConstructSpdyGet( + "http://www.google.com/foo.dat", false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> body2(ConstructSpdyBodyFrame(1, true)); + + MockWrite writes_SPDYNPN[] = { + MockWrite(SYNCHRONOUS, kConnect443, arraysize(kConnect443) - 1, 0), + CreateMockWrite(*req2, 2), + }; + MockRead reads_SPDYNPN[] = { + MockRead(SYNCHRONOUS, kHTTP200, arraysize(kHTTP200) - 1, 1), + CreateMockRead(*resp2, 3), + CreateMockRead(*body2, 4), + MockRead(ASYNC, 0, 5) // EOF + }; + + MockWrite writes_SPDYNOSSL[] = { + CreateMockWrite(*req2, 0), + }; + MockRead reads_SPDYNOSSL[] = { + CreateMockRead(*resp2, 1), + CreateMockRead(*body2, 2), + MockRead(ASYNC, 0, 3) // EOF + }; + + MockWrite writes_SPDYSSL[] = { + MockWrite(SYNCHRONOUS, kConnect80, arraysize(kConnect80) - 1, 0), + CreateMockWrite(*req2, 2), + }; + MockRead reads_SPDYSSL[] = { + MockRead(SYNCHRONOUS, kHTTP200, arraysize(kHTTP200) - 1, 1), + CreateMockRead(*resp2, 3), + CreateMockRead(*body2, 4), + MockRead(ASYNC, 0, 0, 5), + }; + + scoped_ptr<OrderedSocketData> data_proxy; + switch(GetParam()) { + case SPDYNPN: + data_proxy.reset(new OrderedSocketData(reads_SPDYNPN, + arraysize(reads_SPDYNPN), + writes_SPDYNPN, + arraysize(writes_SPDYNPN))); + break; + case SPDYNOSSL: + data_proxy.reset(new OrderedSocketData(reads_SPDYNOSSL, + arraysize(reads_SPDYNOSSL), + writes_SPDYNOSSL, + arraysize(writes_SPDYNOSSL))); + break; + case SPDYSSL: + data_proxy.reset(new OrderedSocketData(reads_SPDYSSL, + arraysize(reads_SPDYSSL), + writes_SPDYSSL, + arraysize(writes_SPDYSSL))); + break; + default: + NOTREACHED(); + } + + // Create another request to www.google.com, but this time through a proxy. + HttpRequestInfo request_proxy; + request_proxy.method = "GET"; + request_proxy.url = GURL("http://www.google.com/foo.dat"); + request_proxy.load_flags = 0; + scoped_ptr<SpdySessionDependencies> ssd_proxy(new SpdySessionDependencies()); + // Ensure that this transaction uses the same SpdySessionPool. + scoped_refptr<HttpNetworkSession> session_proxy( + SpdySessionDependencies::SpdyCreateSession(ssd_proxy.get())); + NormalSpdyTransactionHelper helper_proxy(request_proxy, + BoundNetLog(), GetParam()); + HttpNetworkSessionPeer session_peer(session_proxy); + scoped_ptr<net::ProxyService> proxy_service( + ProxyService::CreateFixedFromPacResult("PROXY myproxy:70")); + session_peer.SetProxyService(proxy_service.get()); + helper_proxy.session_deps().swap(ssd_proxy); + helper_proxy.SetSession(session_proxy); + helper_proxy.RunPreTestSetup(); + helper_proxy.AddData(data_proxy.get()); + + HttpNetworkTransaction* trans_proxy = helper_proxy.trans(); + TestCompletionCallback callback_proxy; + int rv = trans_proxy->Start( + &request_proxy, callback_proxy.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback_proxy.WaitForResult(); + EXPECT_EQ(0, rv); + + HttpResponseInfo response_proxy = *trans_proxy->GetResponseInfo(); + EXPECT_TRUE(response_proxy.headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response_proxy.headers->GetStatusLine()); + + std::string response_data; + ASSERT_EQ(OK, ReadTransaction(trans_proxy, &response_data)); + EXPECT_EQ("hello!", response_data); + + data->CompleteRead(); + helper_proxy.VerifyDataConsumed(); +} + +// When we get a TCP-level RST, we need to retry a HttpNetworkTransaction +// on a new connection, if the connection was previously known to be good. +// This can happen when a server reboots without saying goodbye, or when +// we're behind a NAT that masked the RST. +TEST_P(SpdyNetworkTransactionSpdy2Test, VerifyRetryOnConnectionReset) { + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*resp), + CreateMockRead(*body), + MockRead(ASYNC, ERR_IO_PENDING), + MockRead(ASYNC, ERR_CONNECTION_RESET), + }; + + MockRead reads2[] = { + CreateMockRead(*resp), + CreateMockRead(*body), + MockRead(ASYNC, 0, 0) // EOF + }; + + // This test has a couple of variants. + enum { + // Induce the RST while waiting for our transaction to send. + VARIANT_RST_DURING_SEND_COMPLETION, + // Induce the RST while waiting for our transaction to read. + // In this case, the send completed - everything copied into the SNDBUF. + VARIANT_RST_DURING_READ_COMPLETION + }; + + for (int variant = VARIANT_RST_DURING_SEND_COMPLETION; + variant <= VARIANT_RST_DURING_READ_COMPLETION; + ++variant) { + scoped_ptr<DelayedSocketData> data1( + new DelayedSocketData(1, reads, arraysize(reads), + NULL, 0)); + + scoped_ptr<DelayedSocketData> data2( + new DelayedSocketData(1, reads2, arraysize(reads2), + NULL, 0)); + + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.AddData(data1.get()); + helper.AddData(data2.get()); + helper.RunPreTestSetup(); + + for (int i = 0; i < 2; ++i) { + scoped_ptr<HttpNetworkTransaction> trans( + new HttpNetworkTransaction(helper.session())); + + TestCompletionCallback callback; + int rv = trans->Start( + &helper.request(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + // On the second transaction, we trigger the RST. + if (i == 1) { + if (variant == VARIANT_RST_DURING_READ_COMPLETION) { + // Writes to the socket complete asynchronously on SPDY by running + // through the message loop. Complete the write here. + MessageLoop::current()->RunAllPending(); + } + + // Now schedule the ERR_CONNECTION_RESET. + EXPECT_EQ(3u, data1->read_index()); + data1->CompleteRead(); + EXPECT_EQ(4u, data1->read_index()); + } + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + EXPECT_TRUE(response->headers != NULL); + EXPECT_TRUE(response->was_fetched_via_spdy); + std::string response_data; + rv = ReadTransaction(trans.get(), &response_data); + EXPECT_EQ(OK, rv); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + EXPECT_EQ("hello!", response_data); + } + + helper.VerifyDataConsumed(); + } +} + +// Test that turning SPDY on and off works properly. +TEST_P(SpdyNetworkTransactionSpdy2Test, SpdyOnOffToggle) { + net::HttpStreamFactory::set_spdy_enabled(true); + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + MockWrite spdy_writes[] = { CreateMockWrite(*req) }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true)); + MockRead spdy_reads[] = { + CreateMockRead(*resp), + CreateMockRead(*body), + MockRead(ASYNC, 0, 0) // EOF + }; + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData(1, + spdy_reads, arraysize(spdy_reads), + spdy_writes, arraysize(spdy_writes))); + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.RunToCompletion(data.get()); + TransactionHelperResult out = helper.output(); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!", out.response_data); + + net::HttpStreamFactory::set_spdy_enabled(false); + MockRead http_reads[] = { + MockRead("HTTP/1.1 200 OK\r\n\r\n"), + MockRead("hello from http"), + MockRead(SYNCHRONOUS, OK), + }; + scoped_ptr<DelayedSocketData> data2( + new DelayedSocketData(1, http_reads, arraysize(http_reads), + NULL, 0)); + NormalSpdyTransactionHelper helper2(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper2.SetSpdyDisabled(); + helper2.RunToCompletion(data2.get()); + TransactionHelperResult out2 = helper2.output(); + EXPECT_EQ(OK, out2.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out2.status_line); + EXPECT_EQ("hello from http", out2.response_data); + + net::HttpStreamFactory::set_spdy_enabled(true); +} + +// Tests that Basic authentication works over SPDY +TEST_P(SpdyNetworkTransactionSpdy2Test, SpdyBasicAuth) { + net::HttpStreamFactory::set_spdy_enabled(true); + + // The first request will be a bare GET, the second request will be a + // GET with an Authorization header. + scoped_ptr<spdy::SpdyFrame> req_get( + ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + const char* const kExtraAuthorizationHeaders[] = { + "authorization", + "Basic Zm9vOmJhcg==", + }; + scoped_ptr<spdy::SpdyFrame> req_get_authorization( + ConstructSpdyGet( + kExtraAuthorizationHeaders, + arraysize(kExtraAuthorizationHeaders) / 2, + false, 3, LOWEST)); + MockWrite spdy_writes[] = { + CreateMockWrite(*req_get, 1), + CreateMockWrite(*req_get_authorization, 4), + }; + + // The first response is a 401 authentication challenge, and the second + // response will be a 200 response since the second request includes a valid + // Authorization header. + const char* const kExtraAuthenticationHeaders[] = { + "www-authenticate", + "Basic realm=\"MyRealm\"" + }; + scoped_ptr<spdy::SpdyFrame> resp_authentication( + ConstructSpdySynReplyError( + "401 Authentication Required", + kExtraAuthenticationHeaders, + arraysize(kExtraAuthenticationHeaders) / 2, + 1)); + scoped_ptr<spdy::SpdyFrame> body_authentication( + ConstructSpdyBodyFrame(1, true)); + scoped_ptr<spdy::SpdyFrame> resp_data(ConstructSpdyGetSynReply(NULL, 0, 3)); + scoped_ptr<spdy::SpdyFrame> body_data(ConstructSpdyBodyFrame(3, true)); + MockRead spdy_reads[] = { + CreateMockRead(*resp_authentication, 2), + CreateMockRead(*body_authentication, 3), + CreateMockRead(*resp_data, 5), + CreateMockRead(*body_data, 6), + MockRead(ASYNC, 0, 7), + }; + + scoped_ptr<OrderedSocketData> data( + new OrderedSocketData(spdy_reads, arraysize(spdy_reads), + spdy_writes, arraysize(spdy_writes))); + HttpRequestInfo request(CreateGetRequest()); + BoundNetLog net_log; + NormalSpdyTransactionHelper helper(request, net_log, GetParam()); + + helper.RunPreTestSetup(); + helper.AddData(data.get()); + HttpNetworkTransaction* trans = helper.trans(); + TestCompletionCallback callback; + const int rv_start = trans->Start(&request, callback.callback(), net_log); + EXPECT_EQ(ERR_IO_PENDING, rv_start); + const int rv_start_complete = callback.WaitForResult(); + EXPECT_EQ(OK, rv_start_complete); + + // Make sure the response has an auth challenge. + const HttpResponseInfo* const response_start = trans->GetResponseInfo(); + ASSERT_TRUE(response_start != NULL); + ASSERT_TRUE(response_start->headers != NULL); + EXPECT_EQ(401, response_start->headers->response_code()); + EXPECT_TRUE(response_start->was_fetched_via_spdy); + AuthChallengeInfo* auth_challenge = response_start->auth_challenge.get(); + ASSERT_TRUE(auth_challenge != NULL); + EXPECT_FALSE(auth_challenge->is_proxy); + EXPECT_EQ("basic", auth_challenge->scheme); + EXPECT_EQ("MyRealm", auth_challenge->realm); + + // Restart with a username/password. + AuthCredentials credentials(ASCIIToUTF16("foo"), ASCIIToUTF16("bar")); + TestCompletionCallback callback_restart; + const int rv_restart = trans->RestartWithAuth( + credentials, callback_restart.callback()); + EXPECT_EQ(ERR_IO_PENDING, rv_restart); + const int rv_restart_complete = callback_restart.WaitForResult(); + EXPECT_EQ(OK, rv_restart_complete); + // TODO(cbentzel): This is actually the same response object as before, but + // data has changed. + const HttpResponseInfo* const response_restart = trans->GetResponseInfo(); + ASSERT_TRUE(response_restart != NULL); + ASSERT_TRUE(response_restart->headers != NULL); + EXPECT_EQ(200, response_restart->headers->response_code()); + EXPECT_TRUE(response_restart->auth_challenge.get() == NULL); +} + +TEST_P(SpdyNetworkTransactionSpdy2Test, ServerPushWithHeaders) { + static const unsigned char kPushBodyFrame[] = { + 0x00, 0x00, 0x00, 0x02, // header, ID + 0x01, 0x00, 0x00, 0x06, // FIN, length + 'p', 'u', 's', 'h', 'e', 'd' // "pushed" + }; + scoped_ptr<spdy::SpdyFrame> + stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> + stream1_body(ConstructSpdyBodyFrame(1, true)); + MockWrite writes[] = { + CreateMockWrite(*stream1_syn, 1), + }; + + static const char* const kInitialHeaders[] = { + "url", + "http://www.google.com/foo.dat", + }; + static const char* const kLateHeaders[] = { + "hello", + "bye", + "status", + "200", + "version", + "HTTP/1.1" + }; + scoped_ptr<spdy::SpdyFrame> + stream2_syn(ConstructSpdyControlFrame(kInitialHeaders, + arraysize(kInitialHeaders) / 2, + false, + 2, + LOWEST, + spdy::SYN_STREAM, + spdy::CONTROL_FLAG_NONE, + NULL, + 0, + 1)); + scoped_ptr<spdy::SpdyFrame> + stream2_headers(ConstructSpdyControlFrame(kLateHeaders, + arraysize(kLateHeaders) / 2, + false, + 2, + LOWEST, + spdy::HEADERS, + spdy::CONTROL_FLAG_NONE, + NULL, + 0, + 0)); + + scoped_ptr<spdy::SpdyFrame> + stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1)); + MockRead reads[] = { + CreateMockRead(*stream1_reply, 2), + CreateMockRead(*stream2_syn, 3), + CreateMockRead(*stream2_headers, 4), + CreateMockRead(*stream1_body, 5, SYNCHRONOUS), + MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame), + arraysize(kPushBodyFrame), 6), + MockRead(ASYNC, ERR_IO_PENDING, 7), // Force a pause + }; + + HttpResponseInfo response; + HttpResponseInfo response2; + std::string expected_push_result("pushed"); + scoped_ptr<OrderedSocketData> data(new OrderedSocketData( + reads, + arraysize(reads), + writes, + arraysize(writes))); + RunServerPushTest(data.get(), + &response, + &response2, + expected_push_result); + + // Verify the SYN_REPLY. + EXPECT_TRUE(response.headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); + + // Verify the pushed stream. + EXPECT_TRUE(response2.headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine()); +} + +TEST_P(SpdyNetworkTransactionSpdy2Test, ServerPushClaimBeforeHeaders) { + // We push a stream and attempt to claim it before the headers come down. + static const unsigned char kPushBodyFrame[] = { + 0x00, 0x00, 0x00, 0x02, // header, ID + 0x01, 0x00, 0x00, 0x06, // FIN, length + 'p', 'u', 's', 'h', 'e', 'd' // "pushed" + }; + scoped_ptr<spdy::SpdyFrame> + stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> + stream1_body(ConstructSpdyBodyFrame(1, true)); + MockWrite writes[] = { + CreateMockWrite(*stream1_syn, 0, SYNCHRONOUS), + }; + + static const char* const kInitialHeaders[] = { + "url", + "http://www.google.com/foo.dat", + }; + static const char* const kLateHeaders[] = { + "hello", + "bye", + "status", + "200", + "version", + "HTTP/1.1" + }; + scoped_ptr<spdy::SpdyFrame> + stream2_syn(ConstructSpdyControlFrame(kInitialHeaders, + arraysize(kInitialHeaders) / 2, + false, + 2, + LOWEST, + spdy::SYN_STREAM, + spdy::CONTROL_FLAG_NONE, + NULL, + 0, + 1)); + scoped_ptr<spdy::SpdyFrame> + stream2_headers(ConstructSpdyControlFrame(kLateHeaders, + arraysize(kLateHeaders) / 2, + false, + 2, + LOWEST, + spdy::HEADERS, + spdy::CONTROL_FLAG_NONE, + NULL, + 0, + 0)); + + scoped_ptr<spdy::SpdyFrame> + stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1)); + MockRead reads[] = { + CreateMockRead(*stream1_reply, 1), + CreateMockRead(*stream2_syn, 2), + CreateMockRead(*stream1_body, 3), + CreateMockRead(*stream2_headers, 4), + MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame), + arraysize(kPushBodyFrame), 5), + MockRead(ASYNC, 0, 5), // EOF + }; + + HttpResponseInfo response; + HttpResponseInfo response2; + std::string expected_push_result("pushed"); + scoped_refptr<DeterministicSocketData> data(new DeterministicSocketData( + reads, + arraysize(reads), + writes, + arraysize(writes))); + + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.SetDeterministic(); + helper.AddDeterministicData(static_cast<DeterministicSocketData*>(data)); + helper.RunPreTestSetup(); + + HttpNetworkTransaction* trans = helper.trans(); + + // Run until we've received the primary SYN_STREAM, the pushed SYN_STREAM, + // and the body of the primary stream, but before we've received the HEADERS + // for the pushed stream. + data->SetStop(3); + + // Start the transaction. + TestCompletionCallback callback; + int rv = trans->Start( + &CreateGetRequest(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + data->Run(); + rv = callback.WaitForResult(); + EXPECT_EQ(0, rv); + + // Request the pushed path. At this point, we've received the push, but the + // headers are not yet complete. + scoped_ptr<HttpNetworkTransaction> trans2( + new HttpNetworkTransaction(helper.session())); + rv = trans2->Start( + &CreateGetPushRequest(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + data->RunFor(3); + MessageLoop::current()->RunAllPending(); + + // Read the server push body. + std::string result2; + ReadResult(trans2.get(), data.get(), &result2); + // Read the response body. + std::string result; + ReadResult(trans, data, &result); + + // Verify that we consumed all test data. + EXPECT_TRUE(data->at_read_eof()); + EXPECT_TRUE(data->at_write_eof()); + + // Verify that the received push data is same as the expected push data. + EXPECT_EQ(result2.compare(expected_push_result), 0) + << "Received data: " + << result2 + << "||||| Expected data: " + << expected_push_result; + + // Verify the SYN_REPLY. + // Copy the response info, because trans goes away. + response = *trans->GetResponseInfo(); + response2 = *trans2->GetResponseInfo(); + + VerifyStreamsClosed(helper); + + // Verify the SYN_REPLY. + EXPECT_TRUE(response.headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); + + // Verify the pushed stream. + EXPECT_TRUE(response2.headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine()); +} + +TEST_P(SpdyNetworkTransactionSpdy2Test, ServerPushWithTwoHeaderFrames) { + // We push a stream and attempt to claim it before the headers come down. + static const unsigned char kPushBodyFrame[] = { + 0x00, 0x00, 0x00, 0x02, // header, ID + 0x01, 0x00, 0x00, 0x06, // FIN, length + 'p', 'u', 's', 'h', 'e', 'd' // "pushed" + }; + scoped_ptr<spdy::SpdyFrame> + stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> + stream1_body(ConstructSpdyBodyFrame(1, true)); + MockWrite writes[] = { + CreateMockWrite(*stream1_syn, 0, SYNCHRONOUS), + }; + + static const char* const kInitialHeaders[] = { + "url", + "http://www.google.com/foo.dat", + }; + static const char* const kMiddleHeaders[] = { + "hello", + "bye", + }; + static const char* const kLateHeaders[] = { + "status", + "200", + "version", + "HTTP/1.1" + }; + scoped_ptr<spdy::SpdyFrame> + stream2_syn(ConstructSpdyControlFrame(kInitialHeaders, + arraysize(kInitialHeaders) / 2, + false, + 2, + LOWEST, + spdy::SYN_STREAM, + spdy::CONTROL_FLAG_NONE, + NULL, + 0, + 1)); + scoped_ptr<spdy::SpdyFrame> + stream2_headers1(ConstructSpdyControlFrame(kMiddleHeaders, + arraysize(kMiddleHeaders) / 2, + false, + 2, + LOWEST, + spdy::HEADERS, + spdy::CONTROL_FLAG_NONE, + NULL, + 0, + 0)); + scoped_ptr<spdy::SpdyFrame> + stream2_headers2(ConstructSpdyControlFrame(kLateHeaders, + arraysize(kLateHeaders) / 2, + false, + 2, + LOWEST, + spdy::HEADERS, + spdy::CONTROL_FLAG_NONE, + NULL, + 0, + 0)); + + scoped_ptr<spdy::SpdyFrame> + stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1)); + MockRead reads[] = { + CreateMockRead(*stream1_reply, 1), + CreateMockRead(*stream2_syn, 2), + CreateMockRead(*stream1_body, 3), + CreateMockRead(*stream2_headers1, 4), + CreateMockRead(*stream2_headers2, 5), + MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame), + arraysize(kPushBodyFrame), 6), + MockRead(ASYNC, 0, 6), // EOF + }; + + HttpResponseInfo response; + HttpResponseInfo response2; + std::string expected_push_result("pushed"); + scoped_refptr<DeterministicSocketData> data(new DeterministicSocketData( + reads, + arraysize(reads), + writes, + arraysize(writes))); + + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.SetDeterministic(); + helper.AddDeterministicData(static_cast<DeterministicSocketData*>(data)); + helper.RunPreTestSetup(); + + HttpNetworkTransaction* trans = helper.trans(); + + // Run until we've received the primary SYN_STREAM, the pushed SYN_STREAM, + // the first HEADERS frame, and the body of the primary stream, but before + // we've received the final HEADERS for the pushed stream. + data->SetStop(4); + + // Start the transaction. + TestCompletionCallback callback; + int rv = trans->Start( + &CreateGetRequest(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + data->Run(); + rv = callback.WaitForResult(); + EXPECT_EQ(0, rv); + + // Request the pushed path. At this point, we've received the push, but the + // headers are not yet complete. + scoped_ptr<HttpNetworkTransaction> trans2( + new HttpNetworkTransaction(helper.session())); + rv = trans2->Start( + &CreateGetPushRequest(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + data->RunFor(3); + MessageLoop::current()->RunAllPending(); + + // Read the server push body. + std::string result2; + ReadResult(trans2.get(), data, &result2); + // Read the response body. + std::string result; + ReadResult(trans, data, &result); + + // Verify that we consumed all test data. + EXPECT_TRUE(data->at_read_eof()); + EXPECT_TRUE(data->at_write_eof()); + + // Verify that the received push data is same as the expected push data. + EXPECT_EQ(result2.compare(expected_push_result), 0) + << "Received data: " + << result2 + << "||||| Expected data: " + << expected_push_result; + + // Verify the SYN_REPLY. + // Copy the response info, because trans goes away. + response = *trans->GetResponseInfo(); + response2 = *trans2->GetResponseInfo(); + + VerifyStreamsClosed(helper); + + // Verify the SYN_REPLY. + EXPECT_TRUE(response.headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); + + // Verify the pushed stream. + EXPECT_TRUE(response2.headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine()); + + // Verify we got all the headers + EXPECT_TRUE(response2.headers->HasHeaderValue( + "url", + "http://www.google.com/foo.dat")); + EXPECT_TRUE(response2.headers->HasHeaderValue("hello", "bye")); + EXPECT_TRUE(response2.headers->HasHeaderValue("status", "200")); + EXPECT_TRUE(response2.headers->HasHeaderValue("version", "HTTP/1.1")); +} + +TEST_P(SpdyNetworkTransactionSpdy2Test, SynReplyWithHeaders) { + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + MockWrite writes[] = { CreateMockWrite(*req) }; + + static const char* const kInitialHeaders[] = { + "status", + "200 OK", + "version", + "HTTP/1.1" + }; + static const char* const kLateHeaders[] = { + "hello", + "bye", + }; + scoped_ptr<spdy::SpdyFrame> + stream1_reply(ConstructSpdyControlFrame(kInitialHeaders, + arraysize(kInitialHeaders) / 2, + false, + 1, + LOWEST, + spdy::SYN_REPLY, + spdy::CONTROL_FLAG_NONE, + NULL, + 0, + 0)); + scoped_ptr<spdy::SpdyFrame> + stream1_headers(ConstructSpdyControlFrame(kLateHeaders, + arraysize(kLateHeaders) / 2, + false, + 1, + LOWEST, + spdy::HEADERS, + spdy::CONTROL_FLAG_NONE, + NULL, + 0, + 0)); + scoped_ptr<spdy::SpdyFrame> stream1_body(ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*stream1_reply), + CreateMockRead(*stream1_headers), + CreateMockRead(*stream1_body), + MockRead(ASYNC, 0, 0) // EOF + }; + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData(1, reads, arraysize(reads), + writes, arraysize(writes))); + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.RunToCompletion(data.get()); + TransactionHelperResult out = helper.output(); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!", out.response_data); +} + +TEST_P(SpdyNetworkTransactionSpdy2Test, SynReplyWithLateHeaders) { + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + MockWrite writes[] = { CreateMockWrite(*req) }; + + static const char* const kInitialHeaders[] = { + "status", + "200 OK", + "version", + "HTTP/1.1" + }; + static const char* const kLateHeaders[] = { + "hello", + "bye", + }; + scoped_ptr<spdy::SpdyFrame> + stream1_reply(ConstructSpdyControlFrame(kInitialHeaders, + arraysize(kInitialHeaders) / 2, + false, + 1, + LOWEST, + spdy::SYN_REPLY, + spdy::CONTROL_FLAG_NONE, + NULL, + 0, + 0)); + scoped_ptr<spdy::SpdyFrame> + stream1_headers(ConstructSpdyControlFrame(kLateHeaders, + arraysize(kLateHeaders) / 2, + false, + 1, + LOWEST, + spdy::HEADERS, + spdy::CONTROL_FLAG_NONE, + NULL, + 0, + 0)); + scoped_ptr<spdy::SpdyFrame> stream1_body(ConstructSpdyBodyFrame(1, false)); + scoped_ptr<spdy::SpdyFrame> stream1_body2(ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*stream1_reply), + CreateMockRead(*stream1_body), + CreateMockRead(*stream1_headers), + CreateMockRead(*stream1_body2), + MockRead(ASYNC, 0, 0) // EOF + }; + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData(1, reads, arraysize(reads), + writes, arraysize(writes))); + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.RunToCompletion(data.get()); + TransactionHelperResult out = helper.output(); + EXPECT_EQ(OK, out.rv); + EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); + EXPECT_EQ("hello!hello!", out.response_data); +} + +TEST_P(SpdyNetworkTransactionSpdy2Test, SynReplyWithDuplicateLateHeaders) { + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + MockWrite writes[] = { CreateMockWrite(*req) }; + + static const char* const kInitialHeaders[] = { + "status", + "200 OK", + "version", + "HTTP/1.1" + }; + static const char* const kLateHeaders[] = { + "status", + "500 Server Error", + }; + scoped_ptr<spdy::SpdyFrame> + stream1_reply(ConstructSpdyControlFrame(kInitialHeaders, + arraysize(kInitialHeaders) / 2, + false, + 1, + LOWEST, + spdy::SYN_REPLY, + spdy::CONTROL_FLAG_NONE, + NULL, + 0, + 0)); + scoped_ptr<spdy::SpdyFrame> + stream1_headers(ConstructSpdyControlFrame(kLateHeaders, + arraysize(kLateHeaders) / 2, + false, + 1, + LOWEST, + spdy::HEADERS, + spdy::CONTROL_FLAG_NONE, + NULL, + 0, + 0)); + scoped_ptr<spdy::SpdyFrame> stream1_body(ConstructSpdyBodyFrame(1, false)); + scoped_ptr<spdy::SpdyFrame> stream1_body2(ConstructSpdyBodyFrame(1, true)); + MockRead reads[] = { + CreateMockRead(*stream1_reply), + CreateMockRead(*stream1_body), + CreateMockRead(*stream1_headers), + CreateMockRead(*stream1_body2), + MockRead(ASYNC, 0, 0) // EOF + }; + + scoped_ptr<DelayedSocketData> data( + new DelayedSocketData(1, reads, arraysize(reads), + writes, arraysize(writes))); + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + helper.RunToCompletion(data.get()); + TransactionHelperResult out = helper.output(); + EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, out.rv); +} + +TEST_P(SpdyNetworkTransactionSpdy2Test, ServerPushCrossOriginCorrectness) { + // In this test we want to verify that we can't accidentally push content + // which can't be pushed by this content server. + // This test assumes that: + // - if we're requesting http://www.foo.com/barbaz + // - the browser has made a connection to "www.foo.com". + + // A list of the URL to fetch, followed by the URL being pushed. + static const char* const kTestCases[] = { + "http://www.google.com/foo.html", + "http://www.google.com:81/foo.js", // Bad port + + "http://www.google.com/foo.html", + "https://www.google.com/foo.js", // Bad protocol + + "http://www.google.com/foo.html", + "ftp://www.google.com/foo.js", // Invalid Protocol + + "http://www.google.com/foo.html", + "http://blat.www.google.com/foo.js", // Cross subdomain + + "http://www.google.com/foo.html", + "http://www.foo.com/foo.js", // Cross domain + }; + + + static const unsigned char kPushBodyFrame[] = { + 0x00, 0x00, 0x00, 0x02, // header, ID + 0x01, 0x00, 0x00, 0x06, // FIN, length + 'p', 'u', 's', 'h', 'e', 'd' // "pushed" + }; + + for (size_t index = 0; index < arraysize(kTestCases); index += 2) { + const char* url_to_fetch = kTestCases[index]; + const char* url_to_push = kTestCases[index + 1]; + + scoped_ptr<spdy::SpdyFrame> + stream1_syn(ConstructSpdyGet(url_to_fetch, false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> + stream1_body(ConstructSpdyBodyFrame(1, true)); + scoped_ptr<spdy::SpdyFrame> push_rst( + ConstructSpdyRstStream(2, spdy::REFUSED_STREAM)); + MockWrite writes[] = { + CreateMockWrite(*stream1_syn, 1), + CreateMockWrite(*push_rst, 4), + }; + + scoped_ptr<spdy::SpdyFrame> + stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> + stream2_syn(ConstructSpdyPush(NULL, + 0, + 2, + 1, + url_to_push)); + scoped_ptr<spdy::SpdyFrame> rst( + ConstructSpdyRstStream(2, spdy::CANCEL)); + + MockRead reads[] = { + CreateMockRead(*stream1_reply, 2), + CreateMockRead(*stream2_syn, 3), + CreateMockRead(*stream1_body, 5, SYNCHRONOUS), + MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame), + arraysize(kPushBodyFrame), 6), + MockRead(ASYNC, ERR_IO_PENDING, 7), // Force a pause + }; + + HttpResponseInfo response; + scoped_ptr<OrderedSocketData> data(new OrderedSocketData( + reads, + arraysize(reads), + writes, + arraysize(writes))); + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL(url_to_fetch); + request.load_flags = 0; + NormalSpdyTransactionHelper helper(request, + BoundNetLog(), GetParam()); + helper.RunPreTestSetup(); + helper.AddData(data.get()); + + HttpNetworkTransaction* trans = helper.trans(); + + // Start the transaction with basic parameters. + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback.WaitForResult(); + + // Read the response body. + std::string result; + ReadResult(trans, data.get(), &result); + + // Verify that we consumed all test data. + EXPECT_TRUE(data->at_read_eof()); + EXPECT_TRUE(data->at_write_eof()); + + // Verify the SYN_REPLY. + // Copy the response info, because trans goes away. + response = *trans->GetResponseInfo(); + + VerifyStreamsClosed(helper); + + // Verify the SYN_REPLY. + EXPECT_TRUE(response.headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); + } +} + +TEST_P(SpdyNetworkTransactionSpdy2Test, RetryAfterRefused) { + // Construct the request. + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 3, LOWEST)); + MockWrite writes[] = { + CreateMockWrite(*req, 1), + CreateMockWrite(*req2, 3), + }; + + scoped_ptr<spdy::SpdyFrame> refused( + ConstructSpdyRstStream(1, spdy::REFUSED_STREAM)); + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 3)); + scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(3, true)); + MockRead reads[] = { + CreateMockRead(*refused, 2), + CreateMockRead(*resp, 4), + CreateMockRead(*body, 5), + MockRead(ASYNC, 0, 6) // EOF + }; + + scoped_ptr<OrderedSocketData> data( + new OrderedSocketData(reads, arraysize(reads), + writes, arraysize(writes))); + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + + helper.RunPreTestSetup(); + helper.AddData(data.get()); + + HttpNetworkTransaction* trans = helper.trans(); + + // Start the transaction with basic parameters. + TestCompletionCallback callback; + int rv = trans->Start( + &CreateGetRequest(), callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + // Verify that we consumed all test data. + EXPECT_TRUE(data->at_read_eof()) << "Read count: " + << data->read_count() + << " Read index: " + << data->read_index(); + EXPECT_TRUE(data->at_write_eof()) << "Write count: " + << data->write_count() + << " Write index: " + << data->write_index(); + + // Verify the SYN_REPLY. + HttpResponseInfo response = *trans->GetResponseInfo(); + EXPECT_TRUE(response.headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); +} + +} // namespace net diff --git a/net/spdy/spdy_network_transaction_unittest.cc b/net/spdy/spdy_network_transaction_spdy3_unittest.cc index c000944..4a81474 100644 --- a/net/spdy/spdy_network_transaction_unittest.cc +++ b/net/spdy/spdy_network_transaction_spdy3_unittest.cc @@ -18,21 +18,23 @@ #include "net/spdy/spdy_http_utils.h" #include "net/spdy/spdy_session.h" #include "net/spdy/spdy_session_pool.h" -#include "net/spdy/spdy_test_util.h" +#include "net/spdy/spdy_test_util_spdy3.h" #include "net/url_request/url_request_test_util.h" #include "testing/platform_test.h" +using namespace net::test_spdy3; + //----------------------------------------------------------------------------- namespace net { -enum SpdyNetworkTransactionTestTypes { +enum SpdyNetworkTransactionSpdy3TestTypes { SPDYNPN, SPDYNOSSL, SPDYSSL, }; -class SpdyNetworkTransactionTest - : public ::testing::TestWithParam<SpdyNetworkTransactionTestTypes> { +class SpdyNetworkTransactionSpdy3Test + : public ::testing::TestWithParam<SpdyNetworkTransactionSpdy3TestTypes> { protected: virtual void SetUp() { @@ -64,7 +66,7 @@ class SpdyNetworkTransactionTest public: NormalSpdyTransactionHelper(const HttpRequestInfo& request, const BoundNetLog& log, - SpdyNetworkTransactionTestTypes test_type) + SpdyNetworkTransactionSpdy3TestTypes test_type) : request_(request), session_deps_(new SpdySessionDependencies()), session_(SpdySessionDependencies::SpdyCreateSession( @@ -305,7 +307,9 @@ class SpdyNetworkTransactionTest return session_deps_; } int port() const { return port_; } - SpdyNetworkTransactionTestTypes test_type() const { return test_type_; } + SpdyNetworkTransactionSpdy3TestTypes test_type() const { + return test_type_; + } private: typedef std::vector<StaticSocketDataProvider*> DataVector; @@ -326,7 +330,7 @@ class SpdyNetworkTransactionTest AlternateVector alternate_vector_; AlternateDeterministicVector alternate_deterministic_vector_; const BoundNetLog& log_; - SpdyNetworkTransactionTestTypes test_type_; + SpdyNetworkTransactionSpdy3TestTypes test_type_; int port_; bool deterministic_; bool spdy_enabled_; @@ -529,19 +533,19 @@ class SpdyNetworkTransactionTest // All tests are run with three different connection types: SPDY after NPN // negotiation, SPDY without SSL, and SPDY with SSL. INSTANTIATE_TEST_CASE_P(Spdy, - SpdyNetworkTransactionTest, + SpdyNetworkTransactionSpdy3Test, ::testing::Values(SPDYNOSSL, SPDYSSL, SPDYNPN)); // Verify HttpNetworkTransaction constructor. -TEST_P(SpdyNetworkTransactionTest, Constructor) { +TEST_P(SpdyNetworkTransactionSpdy3Test, Constructor) { SpdySessionDependencies session_deps; scoped_refptr<HttpNetworkSession> session( SpdySessionDependencies::SpdyCreateSession(&session_deps)); scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); } -TEST_P(SpdyNetworkTransactionTest, Get) { +TEST_P(SpdyNetworkTransactionSpdy3Test, Get) { // Construct the request. scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); MockWrite writes[] = { CreateMockWrite(*req) }; @@ -566,7 +570,7 @@ TEST_P(SpdyNetworkTransactionTest, Get) { EXPECT_EQ("hello!", out.response_data); } -TEST_P(SpdyNetworkTransactionTest, GetAtEachPriority) { +TEST_P(SpdyNetworkTransactionSpdy3Test, GetAtEachPriority) { for (RequestPriority p = HIGHEST; p < NUM_PRIORITIES; p = RequestPriority(p+1)) { // Construct the request. @@ -629,7 +633,7 @@ TEST_P(SpdyNetworkTransactionTest, GetAtEachPriority) { // TODO(gavinp): create a working generalized TransactionHelper that // can allow multiple streams in flight. -TEST_P(SpdyNetworkTransactionTest, ThreeGets) { +TEST_P(SpdyNetworkTransactionSpdy3Test, ThreeGets) { scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, false)); @@ -724,7 +728,7 @@ TEST_P(SpdyNetworkTransactionTest, ThreeGets) { EXPECT_EQ("hello!hello!", out.response_data); } -TEST_P(SpdyNetworkTransactionTest, TwoGetsLateBinding) { +TEST_P(SpdyNetworkTransactionSpdy3Test, TwoGetsLateBinding) { scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, false)); @@ -811,7 +815,7 @@ TEST_P(SpdyNetworkTransactionTest, TwoGetsLateBinding) { helper.VerifyDataConsumed(); } -TEST_P(SpdyNetworkTransactionTest, TwoGetsLateBindingFromPreconnect) { +TEST_P(SpdyNetworkTransactionSpdy3Test, TwoGetsLateBindingFromPreconnect) { scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, false)); @@ -916,7 +920,7 @@ TEST_P(SpdyNetworkTransactionTest, TwoGetsLateBindingFromPreconnect) { // the first transaction completion, and sets a maximum concurrent // stream limit of 1. This means that our IO loop exists after the // second transaction completes, so we can assert on read_index(). -TEST_P(SpdyNetworkTransactionTest, ThreeGetsWithMaxConcurrent) { +TEST_P(SpdyNetworkTransactionSpdy3Test, ThreeGetsWithMaxConcurrent) { // Construct the request. scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); @@ -1049,7 +1053,7 @@ TEST_P(SpdyNetworkTransactionTest, ThreeGetsWithMaxConcurrent) { // different data ("hello!" vs "hello!hello!") and because of the // user specified priority, we expect to see them inverted in // the response from the server. -TEST_P(SpdyNetworkTransactionTest, FourGetsWithMaxConcurrentPriority) { +TEST_P(SpdyNetworkTransactionSpdy3Test, FourGetsWithMaxConcurrentPriority) { // Construct the request. scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); @@ -1205,7 +1209,7 @@ TEST_P(SpdyNetworkTransactionTest, FourGetsWithMaxConcurrentPriority) { // deletes a session in the middle of the transaction to insure // that we properly remove pendingcreatestream objects from // the spdy_session -TEST_P(SpdyNetworkTransactionTest, ThreeGetsWithMaxConcurrentDelete) { +TEST_P(SpdyNetworkTransactionSpdy3Test, ThreeGetsWithMaxConcurrentDelete) { // Construct the request. scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); @@ -1310,6 +1314,8 @@ TEST_P(SpdyNetworkTransactionTest, ThreeGetsWithMaxConcurrentDelete) { EXPECT_EQ(OK, out.rv); } +namespace { + // The KillerCallback will delete the transaction on error as part of the // callback. class KillerCallback : public TestCompletionCallbackBase { @@ -1336,10 +1342,12 @@ class KillerCallback : public TestCompletionCallbackBase { CompletionCallback callback_; }; +} // namespace + // Similar to ThreeGetsMaxConcurrrentDelete above, however, this test // closes the socket while we have a pending transaction waiting for // a pending stream creation. http://crbug.com/52901 -TEST_P(SpdyNetworkTransactionTest, ThreeGetsWithMaxConcurrentSocketClose) { +TEST_P(SpdyNetworkTransactionSpdy3Test, ThreeGetsWithMaxConcurrentSocketClose) { // Construct the request. scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); @@ -1433,7 +1441,7 @@ TEST_P(SpdyNetworkTransactionTest, ThreeGetsWithMaxConcurrentSocketClose) { } // Test that a simple PUT request works. -TEST_P(SpdyNetworkTransactionTest, Put) { +TEST_P(SpdyNetworkTransactionSpdy3Test, Put) { // Setup the request HttpRequestInfo request; request.method = "PUT"; @@ -1504,7 +1512,7 @@ TEST_P(SpdyNetworkTransactionTest, Put) { } // Test that a simple HEAD request works. -TEST_P(SpdyNetworkTransactionTest, Head) { +TEST_P(SpdyNetworkTransactionSpdy3Test, Head) { // Setup the request HttpRequestInfo request; request.method = "HEAD"; @@ -1575,7 +1583,7 @@ TEST_P(SpdyNetworkTransactionTest, Head) { } // Test that a simple POST works. -TEST_P(SpdyNetworkTransactionTest, Post) { +TEST_P(SpdyNetworkTransactionSpdy3Test, Post) { scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyPost(kUploadDataSize, NULL, 0)); scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true)); MockWrite writes[] = { @@ -1603,7 +1611,7 @@ TEST_P(SpdyNetworkTransactionTest, Post) { } // Test that a chunked POST works. -TEST_P(SpdyNetworkTransactionTest, ChunkedPost) { +TEST_P(SpdyNetworkTransactionSpdy3Test, ChunkedPost) { UploadDataStream::set_merge_chunks(false); scoped_ptr<spdy::SpdyFrame> req(ConstructChunkedSpdyPost(NULL, 0)); scoped_ptr<spdy::SpdyFrame> chunk1(ConstructSpdyBodyFrame(1, false)); @@ -1635,7 +1643,7 @@ TEST_P(SpdyNetworkTransactionTest, ChunkedPost) { } // Test that a POST without any post data works. -TEST_P(SpdyNetworkTransactionTest, NullPost) { +TEST_P(SpdyNetworkTransactionSpdy3Test, NullPost) { // Setup the request HttpRequestInfo request; request.method = "POST"; @@ -1674,7 +1682,7 @@ TEST_P(SpdyNetworkTransactionTest, NullPost) { } // Test that a simple POST works. -TEST_P(SpdyNetworkTransactionTest, EmptyPost) { +TEST_P(SpdyNetworkTransactionSpdy3Test, EmptyPost) { // Setup the request HttpRequestInfo request; request.method = "POST"; @@ -1721,7 +1729,7 @@ TEST_P(SpdyNetworkTransactionTest, EmptyPost) { } // While we're doing a post, the server sends back a SYN_REPLY. -TEST_P(SpdyNetworkTransactionTest, PostWithEarlySynReply) { +TEST_P(SpdyNetworkTransactionSpdy3Test, PostWithEarlySynReply) { static const char upload[] = { "hello!" }; // Setup the request @@ -1762,7 +1770,7 @@ TEST_P(SpdyNetworkTransactionTest, PostWithEarlySynReply) { // The client upon cancellation tries to send a RST_STREAM frame. The mock // socket causes the TCP write to return zero. This test checks that the client // tries to queue up the RST_STREAM frame again. -TEST_P(SpdyNetworkTransactionTest, SocketWriteReturnsZero) { +TEST_P(SpdyNetworkTransactionSpdy3Test, SocketWriteReturnsZero) { scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); scoped_ptr<spdy::SpdyFrame> rst( ConstructSpdyRstStream(1, spdy::CANCEL)); @@ -1803,7 +1811,7 @@ TEST_P(SpdyNetworkTransactionTest, SocketWriteReturnsZero) { } // Test that the transaction doesn't crash when we don't have a reply. -TEST_P(SpdyNetworkTransactionTest, ResponseWithoutSynReply) { +TEST_P(SpdyNetworkTransactionSpdy3Test, ResponseWithoutSynReply) { scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true)); MockRead reads[] = { CreateMockRead(*body), @@ -1821,7 +1829,7 @@ TEST_P(SpdyNetworkTransactionTest, ResponseWithoutSynReply) { // Test that the transaction doesn't crash when we get two replies on the same // stream ID. See http://crbug.com/45639. -TEST_P(SpdyNetworkTransactionTest, ResponseWithTwoSynReplies) { +TEST_P(SpdyNetworkTransactionSpdy3Test, ResponseWithTwoSynReplies) { scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); MockWrite writes[] = { CreateMockWrite(*req) }; @@ -1883,7 +1891,7 @@ TEST_P(SpdyNetworkTransactionTest, ResponseWithTwoSynReplies) { // all these tests using it. Right now we are working around the // limitations as described above and it's not deterministic, tests may // fail under specific circumstances. -TEST_P(SpdyNetworkTransactionTest, WindowUpdateReceived) { +TEST_P(SpdyNetworkTransactionSpdy3Test, WindowUpdateReceived) { SpdySession::set_use_flow_control(SpdySession::kEnableFlowControl); static int kFrameCount = 2; @@ -1961,7 +1969,7 @@ TEST_P(SpdyNetworkTransactionTest, WindowUpdateReceived) { // Test that received data frames and sent WINDOW_UPDATE frames change // the recv_window_size_ correctly. -TEST_P(SpdyNetworkTransactionTest, WindowUpdateSent) { +TEST_P(SpdyNetworkTransactionSpdy3Test, WindowUpdateSent) { SpdySession::set_use_flow_control(SpdySession::kEnableFlowControl); scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); @@ -2049,7 +2057,7 @@ TEST_P(SpdyNetworkTransactionTest, WindowUpdateSent) { // Test that WINDOW_UPDATE frame causing overflow is handled correctly. We // use the same trick as in the above test to enforce our scenario. -TEST_P(SpdyNetworkTransactionTest, WindowUpdateOverflow) { +TEST_P(SpdyNetworkTransactionSpdy3Test, WindowUpdateOverflow) { SpdySession::set_use_flow_control(SpdySession::kEnableFlowControl); // number of full frames we hope to write (but will not, used to @@ -2141,7 +2149,7 @@ TEST_P(SpdyNetworkTransactionTest, WindowUpdateOverflow) { // ensure that last data frame is still there and stream has stalled. // After that, next read is artifically enforced, which causes a // WINDOW_UPDATE to be read and I/O process resumes. -TEST_P(SpdyNetworkTransactionTest, FlowControlStallResume) { +TEST_P(SpdyNetworkTransactionSpdy3Test, FlowControlStallResume) { SpdySession::set_use_flow_control(SpdySession::kEnableFlowControl); // Number of frames we need to send to zero out the window size: data @@ -2241,7 +2249,7 @@ TEST_P(SpdyNetworkTransactionTest, FlowControlStallResume) { SpdySession::set_use_flow_control(SpdySession::kFlowControlBasedOnNPN); } -TEST_P(SpdyNetworkTransactionTest, CancelledTransaction) { +TEST_P(SpdyNetworkTransactionSpdy3Test, CancelledTransaction) { // Construct the request. scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); MockWrite writes[] = { @@ -2280,7 +2288,7 @@ TEST_P(SpdyNetworkTransactionTest, CancelledTransaction) { } // Verify that the client sends a Rst Frame upon cancelling the stream. -TEST_P(SpdyNetworkTransactionTest, CancelledTransactionSendRst) { +TEST_P(SpdyNetworkTransactionSpdy3Test, CancelledTransactionSendRst) { scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); scoped_ptr<spdy::SpdyFrame> rst( ConstructSpdyRstStream(1, spdy::CANCEL)); @@ -2325,7 +2333,7 @@ TEST_P(SpdyNetworkTransactionTest, CancelledTransactionSendRst) { // Verify that the client can correctly deal with the user callback attempting // to start another transaction on a session that is closing down. See // http://crbug.com/47455 -TEST_P(SpdyNetworkTransactionTest, StartTransactionOnReadCallback) { +TEST_P(SpdyNetworkTransactionSpdy3Test, StartTransactionOnReadCallback) { scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); MockWrite writes[] = { CreateMockWrite(*req) }; MockWrite writes2[] = { CreateMockWrite(*req) }; @@ -2377,7 +2385,7 @@ TEST_P(SpdyNetworkTransactionTest, StartTransactionOnReadCallback) { scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(kSize)); rv = trans->Read( buf, kSize, - base::Bind(&SpdyNetworkTransactionTest::StartTransactionCallback, + base::Bind(&SpdyNetworkTransactionSpdy3Test::StartTransactionCallback, helper.session())); // This forces an err_IO_pending, which sets the callback. data->CompleteRead(); @@ -2389,7 +2397,7 @@ TEST_P(SpdyNetworkTransactionTest, StartTransactionOnReadCallback) { // Verify that the client can correctly deal with the user callback deleting the // transaction. Failures will usually be valgrind errors. See // http://crbug.com/46925 -TEST_P(SpdyNetworkTransactionTest, DeleteSessionOnReadCallback) { +TEST_P(SpdyNetworkTransactionSpdy3Test, DeleteSessionOnReadCallback) { scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); MockWrite writes[] = { CreateMockWrite(*req) }; @@ -2424,7 +2432,7 @@ TEST_P(SpdyNetworkTransactionTest, DeleteSessionOnReadCallback) { scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(kSize)); rv = trans->Read( buf, kSize, - base::Bind(&SpdyNetworkTransactionTest::DeleteSessionCallback, + base::Bind(&SpdyNetworkTransactionSpdy3Test::DeleteSessionCallback, base::Unretained(&helper))); ASSERT_EQ(ERR_IO_PENDING, rv); data->CompleteRead(); @@ -2435,7 +2443,7 @@ TEST_P(SpdyNetworkTransactionTest, DeleteSessionOnReadCallback) { } // Send a spdy request to www.google.com that gets redirected to www.foo.com. -TEST_P(SpdyNetworkTransactionTest, RedirectGetRequest) { +TEST_P(SpdyNetworkTransactionSpdy3Test, RedirectGetRequest) { // These are headers which the net::URLRequest tacks on. const char* const kExtraHeaders[] = { "accept-encoding", @@ -2540,7 +2548,7 @@ TEST_P(SpdyNetworkTransactionTest, RedirectGetRequest) { } // Detect response with upper case headers and reset the stream. -TEST_P(SpdyNetworkTransactionTest, UpperCaseHeaders) { +TEST_P(SpdyNetworkTransactionSpdy3Test, UpperCaseHeaders) { scoped_ptr<spdy::SpdyFrame> syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); scoped_ptr<spdy::SpdyFrame> @@ -2574,7 +2582,7 @@ TEST_P(SpdyNetworkTransactionTest, UpperCaseHeaders) { // Detect response with upper case headers in a HEADERS frame and reset the // stream. -TEST_P(SpdyNetworkTransactionTest, UpperCaseHeadersInHeadersFrame) { +TEST_P(SpdyNetworkTransactionSpdy3Test, UpperCaseHeadersInHeadersFrame) { scoped_ptr<spdy::SpdyFrame> syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); scoped_ptr<spdy::SpdyFrame> @@ -2632,7 +2640,7 @@ TEST_P(SpdyNetworkTransactionTest, UpperCaseHeadersInHeadersFrame) { } // Detect push stream with upper case headers and reset the stream. -TEST_P(SpdyNetworkTransactionTest, UpperCaseHeadersOnPush) { +TEST_P(SpdyNetworkTransactionSpdy3Test, UpperCaseHeadersOnPush) { scoped_ptr<spdy::SpdyFrame> syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); scoped_ptr<spdy::SpdyFrame> @@ -2671,7 +2679,7 @@ TEST_P(SpdyNetworkTransactionTest, UpperCaseHeadersOnPush) { // Send a spdy request to www.google.com. Get a pushed stream that redirects to // www.foo.com. -TEST_P(SpdyNetworkTransactionTest, RedirectServerPush) { +TEST_P(SpdyNetworkTransactionSpdy3Test, RedirectServerPush) { // These are headers which the net::URLRequest tacks on. const char* const kExtraHeaders[] = { "accept-encoding", @@ -2807,7 +2815,7 @@ TEST_P(SpdyNetworkTransactionTest, RedirectServerPush) { EXPECT_TRUE(data2->at_write_eof()); } -TEST_P(SpdyNetworkTransactionTest, ServerPushSingleDataFrame) { +TEST_P(SpdyNetworkTransactionSpdy3Test, ServerPushSingleDataFrame) { static const unsigned char kPushBodyFrame[] = { 0x00, 0x00, 0x00, 0x02, // header, ID 0x01, 0x00, 0x00, 0x06, // FIN, length @@ -2860,7 +2868,7 @@ TEST_P(SpdyNetworkTransactionTest, ServerPushSingleDataFrame) { EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine()); } -TEST_P(SpdyNetworkTransactionTest, ServerPushSingleDataFrame2) { +TEST_P(SpdyNetworkTransactionSpdy3Test, ServerPushSingleDataFrame2) { static const unsigned char kPushBodyFrame[] = { 0x00, 0x00, 0x00, 0x02, // header, ID 0x01, 0x00, 0x00, 0x06, // FIN, length @@ -2913,7 +2921,7 @@ TEST_P(SpdyNetworkTransactionTest, ServerPushSingleDataFrame2) { EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine()); } -TEST_P(SpdyNetworkTransactionTest, ServerPushServerAborted) { +TEST_P(SpdyNetworkTransactionSpdy3Test, ServerPushServerAborted) { scoped_ptr<spdy::SpdyFrame> stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); scoped_ptr<spdy::SpdyFrame> @@ -2975,7 +2983,7 @@ TEST_P(SpdyNetworkTransactionTest, ServerPushServerAborted) { EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); } -TEST_P(SpdyNetworkTransactionTest, ServerPushDuplicate) { +TEST_P(SpdyNetworkTransactionSpdy3Test, ServerPushDuplicate) { // Verify that we don't leak streams and that we properly send a reset // if the server pushes the same stream twice. static const unsigned char kPushBodyFrame[] = { @@ -3041,7 +3049,7 @@ TEST_P(SpdyNetworkTransactionTest, ServerPushDuplicate) { EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine()); } -TEST_P(SpdyNetworkTransactionTest, ServerPushMultipleDataFrame) { +TEST_P(SpdyNetworkTransactionSpdy3Test, ServerPushMultipleDataFrame) { static const unsigned char kPushBodyFrame1[] = { 0x00, 0x00, 0x00, 0x02, // header, ID 0x01, 0x00, 0x00, 0x1F, // FIN, length @@ -3104,7 +3112,8 @@ TEST_P(SpdyNetworkTransactionTest, ServerPushMultipleDataFrame) { EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine()); } -TEST_P(SpdyNetworkTransactionTest, ServerPushMultipleDataFrameInterrupted) { +TEST_P(SpdyNetworkTransactionSpdy3Test, + ServerPushMultipleDataFrameInterrupted) { SpdySession::set_use_flow_control(SpdySession::kDisableFlowControl); static const unsigned char kPushBodyFrame1[] = { @@ -3172,7 +3181,7 @@ TEST_P(SpdyNetworkTransactionTest, ServerPushMultipleDataFrameInterrupted) { SpdySession::set_use_flow_control(SpdySession::kFlowControlBasedOnNPN); } -TEST_P(SpdyNetworkTransactionTest, ServerPushInvalidAssociatedStreamID0) { +TEST_P(SpdyNetworkTransactionSpdy3Test, ServerPushInvalidAssociatedStreamID0) { scoped_ptr<spdy::SpdyFrame> stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); scoped_ptr<spdy::SpdyFrame> @@ -3234,7 +3243,7 @@ TEST_P(SpdyNetworkTransactionTest, ServerPushInvalidAssociatedStreamID0) { EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); } -TEST_P(SpdyNetworkTransactionTest, ServerPushInvalidAssociatedStreamID9) { +TEST_P(SpdyNetworkTransactionSpdy3Test, ServerPushInvalidAssociatedStreamID9) { scoped_ptr<spdy::SpdyFrame> stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); scoped_ptr<spdy::SpdyFrame> @@ -3296,7 +3305,7 @@ TEST_P(SpdyNetworkTransactionTest, ServerPushInvalidAssociatedStreamID9) { EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); } -TEST_P(SpdyNetworkTransactionTest, ServerPushNoURL) { +TEST_P(SpdyNetworkTransactionSpdy3Test, ServerPushNoURL) { scoped_ptr<spdy::SpdyFrame> stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); scoped_ptr<spdy::SpdyFrame> @@ -3355,7 +3364,7 @@ TEST_P(SpdyNetworkTransactionTest, ServerPushNoURL) { // Verify that various SynReply headers parse correctly through the // HTTP layer. -TEST_P(SpdyNetworkTransactionTest, SynReplyHeaders) { +TEST_P(SpdyNetworkTransactionSpdy3Test, SynReplyHeaders) { struct SynReplyHeadersTests { int num_headers; const char* extra_headers[5]; @@ -3436,7 +3445,7 @@ TEST_P(SpdyNetworkTransactionTest, SynReplyHeaders) { // Verify that various SynReply headers parse vary fields correctly // through the HTTP layer, and the response matches the request. -TEST_P(SpdyNetworkTransactionTest, SynReplyHeadersVary) { +TEST_P(SpdyNetworkTransactionSpdy3Test, SynReplyHeadersVary) { static const SpdyHeaderInfo syn_reply_info = { spdy::SYN_REPLY, // Syn Reply 1, // Stream ID @@ -3605,7 +3614,7 @@ TEST_P(SpdyNetworkTransactionTest, SynReplyHeadersVary) { } // Verify that we don't crash on invalid SynReply responses. -TEST_P(SpdyNetworkTransactionTest, InvalidSynReply) { +TEST_P(SpdyNetworkTransactionSpdy3Test, InvalidSynReply) { const SpdyHeaderInfo kSynStartHeader = { spdy::SYN_REPLY, // Kind = SynReply 1, // Stream ID @@ -3675,7 +3684,7 @@ TEST_P(SpdyNetworkTransactionTest, InvalidSynReply) { } // Verify that we don't crash on some corrupt frames. -TEST_P(SpdyNetworkTransactionTest, CorruptFrameSessionError) { +TEST_P(SpdyNetworkTransactionSpdy3Test, CorruptFrameSessionError) { // This is the length field that's too short. scoped_ptr<spdy::SpdyFrame> syn_reply_wrong_length( ConstructSpdyGetSynReply(NULL, 0, 1)); @@ -3714,7 +3723,7 @@ TEST_P(SpdyNetworkTransactionTest, CorruptFrameSessionError) { } // Test that we shutdown correctly on write errors. -TEST_P(SpdyNetworkTransactionTest, WriteError) { +TEST_P(SpdyNetworkTransactionSpdy3Test, WriteError) { scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); MockWrite writes[] = { // We'll write 10 bytes successfully @@ -3735,7 +3744,7 @@ TEST_P(SpdyNetworkTransactionTest, WriteError) { } // Test that partial writes work. -TEST_P(SpdyNetworkTransactionTest, PartialWrite) { +TEST_P(SpdyNetworkTransactionSpdy3Test, PartialWrite) { // Chop the SYN_STREAM frame into 5 chunks. scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); const int kChunks = 5; @@ -3763,7 +3772,7 @@ TEST_P(SpdyNetworkTransactionTest, PartialWrite) { // In this test, we enable compression, but get a uncompressed SynReply from // the server. Verify that teardown is all clean. -TEST_P(SpdyNetworkTransactionTest, DecompressFailureOnSynReply) { +TEST_P(SpdyNetworkTransactionSpdy3Test, DecompressFailureOnSynReply) { // For this test, we turn on the normal compression. EnableCompression(true); @@ -3795,7 +3804,7 @@ TEST_P(SpdyNetworkTransactionTest, DecompressFailureOnSynReply) { } // Test that the NetLog contains good data for a simple GET request. -TEST_P(SpdyNetworkTransactionTest, NetLog) { +TEST_P(SpdyNetworkTransactionSpdy3Test, NetLog) { static const char* const kExtraHeaders[] = { "user-agent", "Chrome", }; @@ -3883,7 +3892,7 @@ TEST_P(SpdyNetworkTransactionTest, NetLog) { // that when we read out the maximum amount of data (e.g. we received 50 bytes // on the network, but issued a Read for only 5 of those bytes) that the data // flow still works correctly. -TEST_P(SpdyNetworkTransactionTest, BufferFull) { +TEST_P(SpdyNetworkTransactionSpdy3Test, BufferFull) { SpdySession::set_use_flow_control(SpdySession::kDisableFlowControl); spdy::SpdyFramer framer; @@ -3982,7 +3991,7 @@ TEST_P(SpdyNetworkTransactionTest, BufferFull) { // Verify that basic buffering works; when multiple data frames arrive // at the same time, ensure that we don't notify a read completion for // each data frame individually. -TEST_P(SpdyNetworkTransactionTest, Buffering) { +TEST_P(SpdyNetworkTransactionSpdy3Test, Buffering) { SpdySession::set_use_flow_control(SpdySession::kDisableFlowControl); spdy::SpdyFramer framer; @@ -4081,7 +4090,7 @@ TEST_P(SpdyNetworkTransactionTest, Buffering) { } // Verify the case where we buffer data but read it after it has been buffered. -TEST_P(SpdyNetworkTransactionTest, BufferedAll) { +TEST_P(SpdyNetworkTransactionSpdy3Test, BufferedAll) { spdy::SpdyFramer framer; scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); @@ -4173,7 +4182,7 @@ TEST_P(SpdyNetworkTransactionTest, BufferedAll) { } // Verify the case where we buffer data and close the connection. -TEST_P(SpdyNetworkTransactionTest, BufferedClosed) { +TEST_P(SpdyNetworkTransactionSpdy3Test, BufferedClosed) { spdy::SpdyFramer framer; scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); @@ -4264,7 +4273,7 @@ TEST_P(SpdyNetworkTransactionTest, BufferedClosed) { } // Verify the case where we buffer data and cancel the transaction. -TEST_P(SpdyNetworkTransactionTest, BufferedCancelled) { +TEST_P(SpdyNetworkTransactionSpdy3Test, BufferedCancelled) { spdy::SpdyFramer framer; scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); @@ -4336,7 +4345,7 @@ TEST_P(SpdyNetworkTransactionTest, BufferedCancelled) { // Test that if the server requests persistence of settings, that we save // the settings in the SpdySettingsStorage. -TEST_P(SpdyNetworkTransactionTest, SettingsSaved) { +TEST_P(SpdyNetworkTransactionSpdy3Test, SettingsSaved) { static const SpdyHeaderInfo kSynReplyInfo = { spdy::SYN_REPLY, // Syn Reply 1, // Stream ID @@ -4447,7 +4456,7 @@ TEST_P(SpdyNetworkTransactionTest, SettingsSaved) { // Test that when there are settings saved that they are sent back to the // server upon session establishment. -TEST_P(SpdyNetworkTransactionTest, SettingsPlayback) { +TEST_P(SpdyNetworkTransactionSpdy3Test, SettingsPlayback) { static const SpdyHeaderInfo kSynReplyInfo = { spdy::SYN_REPLY, // Syn Reply 1, // Stream ID @@ -4563,7 +4572,7 @@ TEST_P(SpdyNetworkTransactionTest, SettingsPlayback) { } } -TEST_P(SpdyNetworkTransactionTest, GoAwayWithActiveStream) { +TEST_P(SpdyNetworkTransactionSpdy3Test, GoAwayWithActiveStream) { scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); MockWrite writes[] = { CreateMockWrite(*req) }; @@ -4584,7 +4593,7 @@ TEST_P(SpdyNetworkTransactionTest, GoAwayWithActiveStream) { EXPECT_EQ(ERR_ABORTED, out.rv); } -TEST_P(SpdyNetworkTransactionTest, CloseWithActiveStream) { +TEST_P(SpdyNetworkTransactionSpdy3Test, CloseWithActiveStream) { scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); MockWrite writes[] = { CreateMockWrite(*req) }; @@ -4623,7 +4632,7 @@ TEST_P(SpdyNetworkTransactionTest, CloseWithActiveStream) { } // Test to make sure we can correctly connect through a proxy. -TEST_P(SpdyNetworkTransactionTest, ProxyConnect) { +TEST_P(SpdyNetworkTransactionSpdy3Test, ProxyConnect) { NormalSpdyTransactionHelper helper(CreateGetRequest(), BoundNetLog(), GetParam()); helper.session_deps().reset(new SpdySessionDependencies( @@ -4724,7 +4733,7 @@ TEST_P(SpdyNetworkTransactionTest, ProxyConnect) { // Test to make sure we can correctly connect through a proxy to www.google.com, // if there already exists a direct spdy connection to www.google.com. See // http://crbug.com/49874 -TEST_P(SpdyNetworkTransactionTest, DirectConnectProxyReconnect) { +TEST_P(SpdyNetworkTransactionSpdy3Test, DirectConnectProxyReconnect) { // When setting up the first transaction, we store the SpdySessionPool so that // we can use the same pool in the second transaction. NormalSpdyTransactionHelper helper(CreateGetRequest(), @@ -4902,7 +4911,7 @@ TEST_P(SpdyNetworkTransactionTest, DirectConnectProxyReconnect) { // on a new connection, if the connection was previously known to be good. // This can happen when a server reboots without saying goodbye, or when // we're behind a NAT that masked the RST. -TEST_P(SpdyNetworkTransactionTest, VerifyRetryOnConnectionReset) { +TEST_P(SpdyNetworkTransactionSpdy3Test, VerifyRetryOnConnectionReset) { scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true)); MockRead reads[] = { @@ -4984,7 +4993,7 @@ TEST_P(SpdyNetworkTransactionTest, VerifyRetryOnConnectionReset) { } // Test that turning SPDY on and off works properly. -TEST_P(SpdyNetworkTransactionTest, SpdyOnOffToggle) { +TEST_P(SpdyNetworkTransactionSpdy3Test, SpdyOnOffToggle) { net::HttpStreamFactory::set_spdy_enabled(true); scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); MockWrite spdy_writes[] = { CreateMockWrite(*req) }; @@ -5031,7 +5040,7 @@ TEST_P(SpdyNetworkTransactionTest, SpdyOnOffToggle) { } // Tests that Basic authentication works over SPDY -TEST_P(SpdyNetworkTransactionTest, SpdyBasicAuth) { +TEST_P(SpdyNetworkTransactionSpdy3Test, SpdyBasicAuth) { net::HttpStreamFactory::set_spdy_enabled(true); // The first request will be a bare GET, the second request will be a @@ -5122,7 +5131,7 @@ TEST_P(SpdyNetworkTransactionTest, SpdyBasicAuth) { EXPECT_TRUE(response_restart->auth_challenge.get() == NULL); } -TEST_P(SpdyNetworkTransactionTest, ServerPushWithHeaders) { +TEST_P(SpdyNetworkTransactionSpdy3Test, ServerPushWithHeaders) { static const unsigned char kPushBodyFrame[] = { 0x00, 0x00, 0x00, 0x02, // header, ID 0x01, 0x00, 0x00, 0x06, // FIN, length @@ -5205,7 +5214,7 @@ TEST_P(SpdyNetworkTransactionTest, ServerPushWithHeaders) { EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine()); } -TEST_P(SpdyNetworkTransactionTest, ServerPushClaimBeforeHeaders) { +TEST_P(SpdyNetworkTransactionSpdy3Test, ServerPushClaimBeforeHeaders) { // We push a stream and attempt to claim it before the headers come down. static const unsigned char kPushBodyFrame[] = { 0x00, 0x00, 0x00, 0x02, // header, ID @@ -5342,7 +5351,7 @@ TEST_P(SpdyNetworkTransactionTest, ServerPushClaimBeforeHeaders) { EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine()); } -TEST_P(SpdyNetworkTransactionTest, ServerPushWithTwoHeaderFrames) { +TEST_P(SpdyNetworkTransactionSpdy3Test, ServerPushWithTwoHeaderFrames) { // We push a stream and attempt to claim it before the headers come down. static const unsigned char kPushBodyFrame[] = { 0x00, 0x00, 0x00, 0x02, // header, ID @@ -5501,7 +5510,7 @@ TEST_P(SpdyNetworkTransactionTest, ServerPushWithTwoHeaderFrames) { EXPECT_TRUE(response2.headers->HasHeaderValue("version", "HTTP/1.1")); } -TEST_P(SpdyNetworkTransactionTest, SynReplyWithHeaders) { +TEST_P(SpdyNetworkTransactionSpdy3Test, SynReplyWithHeaders) { scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); MockWrite writes[] = { CreateMockWrite(*req) }; @@ -5557,7 +5566,7 @@ TEST_P(SpdyNetworkTransactionTest, SynReplyWithHeaders) { EXPECT_EQ("hello!", out.response_data); } -TEST_P(SpdyNetworkTransactionTest, SynReplyWithLateHeaders) { +TEST_P(SpdyNetworkTransactionSpdy3Test, SynReplyWithLateHeaders) { scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); MockWrite writes[] = { CreateMockWrite(*req) }; @@ -5615,7 +5624,7 @@ TEST_P(SpdyNetworkTransactionTest, SynReplyWithLateHeaders) { EXPECT_EQ("hello!hello!", out.response_data); } -TEST_P(SpdyNetworkTransactionTest, SynReplyWithDuplicateLateHeaders) { +TEST_P(SpdyNetworkTransactionSpdy3Test, SynReplyWithDuplicateLateHeaders) { scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); MockWrite writes[] = { CreateMockWrite(*req) }; @@ -5671,7 +5680,7 @@ TEST_P(SpdyNetworkTransactionTest, SynReplyWithDuplicateLateHeaders) { EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, out.rv); } -TEST_P(SpdyNetworkTransactionTest, ServerPushCrossOriginCorrectness) { +TEST_P(SpdyNetworkTransactionSpdy3Test, ServerPushCrossOriginCorrectness) { // In this test we want to verify that we can't accidentally push content // which can't be pushed by this content server. // This test assumes that: @@ -5783,7 +5792,7 @@ TEST_P(SpdyNetworkTransactionTest, ServerPushCrossOriginCorrectness) { } } -TEST_P(SpdyNetworkTransactionTest, RetryAfterRefused) { +TEST_P(SpdyNetworkTransactionSpdy3Test, RetryAfterRefused) { // Construct the request. scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); scoped_ptr<spdy::SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 3, LOWEST)); diff --git a/net/spdy/spdy_proxy_client_socket_unittest.cc b/net/spdy/spdy_proxy_client_socket_spdy2_unittest.cc index b6a3d4e..e20a189 100644 --- a/net/spdy/spdy_proxy_client_socket_unittest.cc +++ b/net/spdy/spdy_proxy_client_socket_spdy2_unittest.cc @@ -21,10 +21,12 @@ #include "net/spdy/spdy_http_utils.h" #include "net/spdy/spdy_protocol.h" #include "net/spdy/spdy_session_pool.h" -#include "net/spdy/spdy_test_util.h" +#include "net/spdy/spdy_test_util_spdy2.h" #include "testing/platform_test.h" #include "testing/gtest/include/gtest/gtest.h" +using namespace net::test_spdy2; + //----------------------------------------------------------------------------- namespace { @@ -55,9 +57,9 @@ static const int kLen333 = kLen3 + kLen3 + kLen3; namespace net { -class SpdyProxyClientSocketTest : public PlatformTest { +class SpdyProxyClientSocketSpdy2Test : public PlatformTest { public: - SpdyProxyClientSocketTest(); + SpdyProxyClientSocketSpdy2Test(); virtual void TearDown(); @@ -122,10 +124,10 @@ class SpdyProxyClientSocketTest : public PlatformTest { HostPortProxyPair endpoint_host_port_proxy_pair_; scoped_refptr<TransportSocketParams> transport_params_; - DISALLOW_COPY_AND_ASSIGN(SpdyProxyClientSocketTest); + DISALLOW_COPY_AND_ASSIGN(SpdyProxyClientSocketSpdy2Test); }; -SpdyProxyClientSocketTest::SpdyProxyClientSocketTest() +SpdyProxyClientSocketSpdy2Test::SpdyProxyClientSocketSpdy2Test() : sock_(NULL), data_(NULL), session_(NULL), @@ -147,7 +149,7 @@ SpdyProxyClientSocketTest::SpdyProxyClientSocketTest() false)) { } -void SpdyProxyClientSocketTest::TearDown() { +void SpdyProxyClientSocketSpdy2Test::TearDown() { sock_.reset(NULL); if (session_ != NULL) session_->spdy_session_pool()->CloseAllSessions(); @@ -158,7 +160,7 @@ void SpdyProxyClientSocketTest::TearDown() { PlatformTest::TearDown(); } -void SpdyProxyClientSocketTest::Initialize(MockRead* reads, +void SpdyProxyClientSocketSpdy2Test::Initialize(MockRead* reads, size_t reads_count, MockWrite* writes, size_t writes_count) { @@ -203,33 +205,33 @@ void SpdyProxyClientSocketTest::Initialize(MockRead* reads, session_->http_auth_handler_factory())); } -scoped_refptr<IOBufferWithSize> SpdyProxyClientSocketTest::CreateBuffer( +scoped_refptr<IOBufferWithSize> SpdyProxyClientSocketSpdy2Test::CreateBuffer( const char* data, int size) { scoped_refptr<IOBufferWithSize> buf(new IOBufferWithSize(size)); memcpy(buf->data(), data, size); return buf; } -void SpdyProxyClientSocketTest::AssertConnectSucceeds() { +void SpdyProxyClientSocketSpdy2Test::AssertConnectSucceeds() { ASSERT_EQ(ERR_IO_PENDING, sock_->Connect(read_callback_.callback())); data_->Run(); ASSERT_EQ(OK, read_callback_.WaitForResult()); } -void SpdyProxyClientSocketTest::AssertConnectFails(int result) { +void SpdyProxyClientSocketSpdy2Test::AssertConnectFails(int result) { ASSERT_EQ(ERR_IO_PENDING, sock_->Connect(read_callback_.callback())); data_->Run(); ASSERT_EQ(result, read_callback_.WaitForResult()); } -void SpdyProxyClientSocketTest::AssertConnectionEstablished() { +void SpdyProxyClientSocketSpdy2Test::AssertConnectionEstablished() { const HttpResponseInfo* response = sock_->GetConnectResponseInfo(); ASSERT_TRUE(response != NULL); ASSERT_EQ(200, response->headers->response_code()); ASSERT_EQ("Connection Established", response->headers->GetStatusText()); } -void SpdyProxyClientSocketTest::AssertSyncReadEquals(const char* data, +void SpdyProxyClientSocketSpdy2Test::AssertSyncReadEquals(const char* data, int len) { scoped_refptr<IOBuffer> buf(new IOBuffer(len)); ASSERT_EQ(len, sock_->Read(buf, len, CompletionCallback())); @@ -237,8 +239,8 @@ void SpdyProxyClientSocketTest::AssertSyncReadEquals(const char* data, ASSERT_TRUE(sock_->IsConnected()); } -void SpdyProxyClientSocketTest::AssertAsyncReadEquals(const char* data, - int len) { +void SpdyProxyClientSocketSpdy2Test::AssertAsyncReadEquals(const char* data, + int len) { data_->StopAfter(1); // Issue the read, which will be completed asynchronously scoped_refptr<IOBuffer> buf(new IOBuffer(len)); @@ -253,7 +255,8 @@ void SpdyProxyClientSocketTest::AssertAsyncReadEquals(const char* data, ASSERT_EQ(std::string(data, len), std::string(buf->data(), len)); } -void SpdyProxyClientSocketTest::AssertReadStarts(const char* data, int len) { +void SpdyProxyClientSocketSpdy2Test::AssertReadStarts(const char* data, + int len) { data_->StopAfter(1); // Issue the read, which will be completed asynchronously read_buf_ = new IOBuffer(len); @@ -262,7 +265,8 @@ void SpdyProxyClientSocketTest::AssertReadStarts(const char* data, int len) { EXPECT_TRUE(sock_->IsConnected()); } -void SpdyProxyClientSocketTest::AssertReadReturns(const char* data, int len) { +void SpdyProxyClientSocketSpdy2Test::AssertReadReturns(const char* data, + int len) { EXPECT_TRUE(sock_->IsConnected()); // Now the read will return @@ -270,24 +274,25 @@ void SpdyProxyClientSocketTest::AssertReadReturns(const char* data, int len) { ASSERT_EQ(std::string(data, len), std::string(read_buf_->data(), len)); } -void SpdyProxyClientSocketTest::AssertAsyncWriteSucceeds(const char* data, - int len) { +void SpdyProxyClientSocketSpdy2Test::AssertAsyncWriteSucceeds(const char* data, + int len) { AssertWriteReturns(data, len, ERR_IO_PENDING); data_->RunFor(1); AssertWriteLength(len); } -void SpdyProxyClientSocketTest::AssertWriteReturns(const char* data, int len, - int rv) { +void SpdyProxyClientSocketSpdy2Test::AssertWriteReturns(const char* data, + int len, + int rv) { scoped_refptr<IOBufferWithSize> buf(CreateBuffer(data, len)); EXPECT_EQ(rv, sock_->Write(buf, buf->size(), write_callback_.callback())); } -void SpdyProxyClientSocketTest::AssertWriteLength(int len) { +void SpdyProxyClientSocketSpdy2Test::AssertWriteLength(int len) { EXPECT_EQ(len, write_callback_.WaitForResult()); } -void SpdyProxyClientSocketTest::AssertAsyncWriteWithReadsSucceeds( +void SpdyProxyClientSocketSpdy2Test::AssertAsyncWriteWithReadsSucceeds( const char* data, int len, int num_reads) { scoped_refptr<IOBufferWithSize> buf(CreateBuffer(data, len)); @@ -303,7 +308,8 @@ void SpdyProxyClientSocketTest::AssertAsyncWriteWithReadsSucceeds( } // Constructs a standard SPDY SYN_STREAM frame for a CONNECT request. -spdy::SpdyFrame* SpdyProxyClientSocketTest::ConstructConnectRequestFrame() { +spdy::SpdyFrame* +SpdyProxyClientSocketSpdy2Test::ConstructConnectRequestFrame() { const SpdyHeaderInfo kSynStartHeader = { spdy::SYN_STREAM, kStreamId, @@ -329,7 +335,8 @@ spdy::SpdyFrame* SpdyProxyClientSocketTest::ConstructConnectRequestFrame() { // Constructs a SPDY SYN_STREAM frame for a CONNECT request which includes // Proxy-Authorization headers. -spdy::SpdyFrame* SpdyProxyClientSocketTest::ConstructConnectAuthRequestFrame() { +spdy::SpdyFrame* +SpdyProxyClientSocketSpdy2Test::ConstructConnectAuthRequestFrame() { const SpdyHeaderInfo kSynStartHeader = { spdy::SYN_STREAM, kStreamId, @@ -355,7 +362,7 @@ spdy::SpdyFrame* SpdyProxyClientSocketTest::ConstructConnectAuthRequestFrame() { } // Constructs a standard SPDY SYN_REPLY frame to match the SPDY CONNECT. -spdy::SpdyFrame* SpdyProxyClientSocketTest::ConstructConnectReplyFrame() { +spdy::SpdyFrame* SpdyProxyClientSocketSpdy2Test::ConstructConnectReplyFrame() { const char* const kStandardReplyHeaders[] = { "status", "200 Connection Established", "version", "HTTP/1.1" @@ -372,7 +379,8 @@ spdy::SpdyFrame* SpdyProxyClientSocketTest::ConstructConnectReplyFrame() { } // Constructs a standard SPDY SYN_REPLY frame to match the SPDY CONNECT. -spdy::SpdyFrame* SpdyProxyClientSocketTest::ConstructConnectAuthReplyFrame() { +spdy::SpdyFrame* +SpdyProxyClientSocketSpdy2Test::ConstructConnectAuthReplyFrame() { const char* const kStandardReplyHeaders[] = { "status", "407 Proxy Authentication Required", "version", "HTTP/1.1", @@ -391,7 +399,8 @@ spdy::SpdyFrame* SpdyProxyClientSocketTest::ConstructConnectAuthReplyFrame() { } // Constructs a SPDY SYN_REPLY frame with an HTTP 500 error. -spdy::SpdyFrame* SpdyProxyClientSocketTest::ConstructConnectErrorReplyFrame() { +spdy::SpdyFrame* +SpdyProxyClientSocketSpdy2Test::ConstructConnectErrorReplyFrame() { const char* const kStandardReplyHeaders[] = { "status", "500 Internal Server Error", "version", "HTTP/1.1", @@ -408,14 +417,14 @@ spdy::SpdyFrame* SpdyProxyClientSocketTest::ConstructConnectErrorReplyFrame() { arraysize(kStandardReplyHeaders)); } -spdy::SpdyFrame* SpdyProxyClientSocketTest::ConstructBodyFrame(const char* data, - int length) { +spdy::SpdyFrame* SpdyProxyClientSocketSpdy2Test::ConstructBodyFrame( + const char* data, int length) { return framer_.CreateDataFrame(kStreamId, data, length, spdy::DATA_FLAG_NONE); } // ----------- Connect -TEST_F(SpdyProxyClientSocketTest, ConnectSendsCorrectRequest) { +TEST_F(SpdyProxyClientSocketSpdy2Test, ConnectSendsCorrectRequest) { scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); MockWrite writes[] = { CreateMockWrite(*conn, 0, SYNCHRONOUS), @@ -436,7 +445,7 @@ TEST_F(SpdyProxyClientSocketTest, ConnectSendsCorrectRequest) { AssertConnectionEstablished(); } -TEST_F(SpdyProxyClientSocketTest, ConnectWithAuthRequested) { +TEST_F(SpdyProxyClientSocketSpdy2Test, ConnectWithAuthRequested) { scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); MockWrite writes[] = { CreateMockWrite(*conn, 0, SYNCHRONOUS), @@ -459,7 +468,7 @@ TEST_F(SpdyProxyClientSocketTest, ConnectWithAuthRequested) { response->headers->GetStatusText()); } -TEST_F(SpdyProxyClientSocketTest, ConnectWithAuthCredentials) { +TEST_F(SpdyProxyClientSocketSpdy2Test, ConnectWithAuthCredentials) { scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectAuthRequestFrame()); MockWrite writes[] = { CreateMockWrite(*conn, 0, SYNCHRONOUS), @@ -479,7 +488,7 @@ TEST_F(SpdyProxyClientSocketTest, ConnectWithAuthCredentials) { AssertConnectionEstablished(); } -TEST_F(SpdyProxyClientSocketTest, ConnectFails) { +TEST_F(SpdyProxyClientSocketSpdy2Test, ConnectFails) { scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); MockWrite writes[] = { CreateMockWrite(*conn, 0, SYNCHRONOUS), @@ -501,7 +510,7 @@ TEST_F(SpdyProxyClientSocketTest, ConnectFails) { // ----------- WasEverUsed -TEST_F(SpdyProxyClientSocketTest, WasEverUsedReturnsCorrectValues) { +TEST_F(SpdyProxyClientSocketSpdy2Test, WasEverUsedReturnsCorrectValues) { scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); MockWrite writes[] = { CreateMockWrite(*conn, 0, SYNCHRONOUS), @@ -524,7 +533,7 @@ TEST_F(SpdyProxyClientSocketTest, WasEverUsedReturnsCorrectValues) { // ----------- GetPeerAddress -TEST_F(SpdyProxyClientSocketTest, GetPeerAddressReturnsCorrectValues) { +TEST_F(SpdyProxyClientSocketSpdy2Test, GetPeerAddressReturnsCorrectValues) { scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); MockWrite writes[] = { CreateMockWrite(*conn, 0, SYNCHRONOUS), @@ -557,7 +566,7 @@ TEST_F(SpdyProxyClientSocketTest, GetPeerAddressReturnsCorrectValues) { // ----------- Write -TEST_F(SpdyProxyClientSocketTest, WriteSendsDataInDataFrame) { +TEST_F(SpdyProxyClientSocketSpdy2Test, WriteSendsDataInDataFrame) { scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); scoped_ptr<spdy::SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1)); scoped_ptr<spdy::SpdyFrame> msg2(ConstructBodyFrame(kMsg2, kLen2)); @@ -581,7 +590,7 @@ TEST_F(SpdyProxyClientSocketTest, WriteSendsDataInDataFrame) { AssertAsyncWriteSucceeds(kMsg2, kLen2); } -TEST_F(SpdyProxyClientSocketTest, WriteSplitsLargeDataIntoMultipleFrames) { +TEST_F(SpdyProxyClientSocketSpdy2Test, WriteSplitsLargeDataIntoMultipleFrames) { std::string chunk_data(kMaxSpdyFrameChunkSize, 'x'); scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); scoped_ptr<spdy::SpdyFrame> chunk(ConstructBodyFrame(chunk_data.data(), @@ -616,7 +625,7 @@ TEST_F(SpdyProxyClientSocketTest, WriteSplitsLargeDataIntoMultipleFrames) { // ----------- Read -TEST_F(SpdyProxyClientSocketTest, ReadReadsDataInDataFrame) { +TEST_F(SpdyProxyClientSocketSpdy2Test, ReadReadsDataInDataFrame) { scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); MockWrite writes[] = { CreateMockWrite(*conn, 0, SYNCHRONOUS), @@ -639,7 +648,7 @@ TEST_F(SpdyProxyClientSocketTest, ReadReadsDataInDataFrame) { AssertSyncReadEquals(kMsg1, kLen1); } -TEST_F(SpdyProxyClientSocketTest, ReadDataFromBufferedFrames) { +TEST_F(SpdyProxyClientSocketSpdy2Test, ReadDataFromBufferedFrames) { scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); MockWrite writes[] = { CreateMockWrite(*conn, 0, SYNCHRONOUS), @@ -667,7 +676,7 @@ TEST_F(SpdyProxyClientSocketTest, ReadDataFromBufferedFrames) { AssertSyncReadEquals(kMsg2, kLen2); } -TEST_F(SpdyProxyClientSocketTest, ReadDataMultipleBufferedFrames) { +TEST_F(SpdyProxyClientSocketSpdy2Test, ReadDataMultipleBufferedFrames) { scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); MockWrite writes[] = { CreateMockWrite(*conn, 0, SYNCHRONOUS), @@ -693,7 +702,8 @@ TEST_F(SpdyProxyClientSocketTest, ReadDataMultipleBufferedFrames) { AssertSyncReadEquals(kMsg2, kLen2); } -TEST_F(SpdyProxyClientSocketTest, LargeReadWillMergeDataFromDifferentFrames) { +TEST_F(SpdyProxyClientSocketSpdy2Test, + LargeReadWillMergeDataFromDifferentFrames) { scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); MockWrite writes[] = { CreateMockWrite(*conn, 0, SYNCHRONOUS), @@ -720,7 +730,7 @@ TEST_F(SpdyProxyClientSocketTest, LargeReadWillMergeDataFromDifferentFrames) { AssertSyncReadEquals(kMsg33, kLen33); } -TEST_F(SpdyProxyClientSocketTest, MultipleShortReadsThenMoreRead) { +TEST_F(SpdyProxyClientSocketSpdy2Test, MultipleShortReadsThenMoreRead) { scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); MockWrite writes[] = { CreateMockWrite(*conn, 0, SYNCHRONOUS), @@ -752,7 +762,7 @@ TEST_F(SpdyProxyClientSocketTest, MultipleShortReadsThenMoreRead) { AssertSyncReadEquals(kMsg2, kLen2); } -TEST_F(SpdyProxyClientSocketTest, ReadWillSplitDataFromLargeFrame) { +TEST_F(SpdyProxyClientSocketSpdy2Test, ReadWillSplitDataFromLargeFrame) { scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); MockWrite writes[] = { CreateMockWrite(*conn, 0, SYNCHRONOUS), @@ -782,7 +792,7 @@ TEST_F(SpdyProxyClientSocketTest, ReadWillSplitDataFromLargeFrame) { AssertSyncReadEquals(kMsg3, kLen3); } -TEST_F(SpdyProxyClientSocketTest, MultipleReadsFromSameLargeFrame) { +TEST_F(SpdyProxyClientSocketSpdy2Test, MultipleReadsFromSameLargeFrame) { scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); MockWrite writes[] = { CreateMockWrite(*conn, 0, SYNCHRONOUS), @@ -813,7 +823,7 @@ TEST_F(SpdyProxyClientSocketTest, MultipleReadsFromSameLargeFrame) { ASSERT_TRUE(sock_->IsConnected()); } -TEST_F(SpdyProxyClientSocketTest, ReadAuthResponseBody) { +TEST_F(SpdyProxyClientSocketSpdy2Test, ReadAuthResponseBody) { scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); MockWrite writes[] = { CreateMockWrite(*conn, 0, SYNCHRONOUS), @@ -839,7 +849,7 @@ TEST_F(SpdyProxyClientSocketTest, ReadAuthResponseBody) { AssertSyncReadEquals(kMsg2, kLen2); } -TEST_F(SpdyProxyClientSocketTest, ReadErrorResponseBody) { +TEST_F(SpdyProxyClientSocketSpdy2Test, ReadErrorResponseBody) { scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); MockWrite writes[] = { CreateMockWrite(*conn, 0, SYNCHRONOUS), @@ -870,7 +880,7 @@ TEST_F(SpdyProxyClientSocketTest, ReadErrorResponseBody) { // ----------- Reads and Writes -TEST_F(SpdyProxyClientSocketTest, AsyncReadAroundWrite) { +TEST_F(SpdyProxyClientSocketSpdy2Test, AsyncReadAroundWrite) { scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); scoped_ptr<spdy::SpdyFrame> msg2(ConstructBodyFrame(kMsg2, kLen2)); MockWrite writes[] = { @@ -906,7 +916,7 @@ TEST_F(SpdyProxyClientSocketTest, AsyncReadAroundWrite) { AssertReadReturns(kMsg3, kLen3); } -TEST_F(SpdyProxyClientSocketTest, AsyncWriteAroundReads) { +TEST_F(SpdyProxyClientSocketSpdy2Test, AsyncWriteAroundReads) { scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); scoped_ptr<spdy::SpdyFrame> msg2(ConstructBodyFrame(kMsg2, kLen2)); MockWrite writes[] = { @@ -945,7 +955,7 @@ TEST_F(SpdyProxyClientSocketTest, AsyncWriteAroundReads) { // ----------- Reading/Writing on Closed socket // Reading from an already closed socket should return 0 -TEST_F(SpdyProxyClientSocketTest, ReadOnClosedSocketReturnsZero) { +TEST_F(SpdyProxyClientSocketSpdy2Test, ReadOnClosedSocketReturnsZero) { scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); MockWrite writes[] = { CreateMockWrite(*conn, 0, SYNCHRONOUS), @@ -971,7 +981,7 @@ TEST_F(SpdyProxyClientSocketTest, ReadOnClosedSocketReturnsZero) { } // Read pending when socket is closed should return 0 -TEST_F(SpdyProxyClientSocketTest, PendingReadOnCloseReturnsZero) { +TEST_F(SpdyProxyClientSocketSpdy2Test, PendingReadOnCloseReturnsZero) { scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); MockWrite writes[] = { CreateMockWrite(*conn, 0, SYNCHRONOUS), @@ -995,7 +1005,8 @@ TEST_F(SpdyProxyClientSocketTest, PendingReadOnCloseReturnsZero) { } // Reading from a disconnected socket is an error -TEST_F(SpdyProxyClientSocketTest, ReadOnDisconnectSocketReturnsNotConnected) { +TEST_F(SpdyProxyClientSocketSpdy2Test, + ReadOnDisconnectSocketReturnsNotConnected) { scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); MockWrite writes[] = { CreateMockWrite(*conn, 0, SYNCHRONOUS), @@ -1019,7 +1030,7 @@ TEST_F(SpdyProxyClientSocketTest, ReadOnDisconnectSocketReturnsNotConnected) { // Reading buffered data from an already closed socket should return // buffered data, then 0. -TEST_F(SpdyProxyClientSocketTest, ReadOnClosedSocketReturnsBufferedData) { +TEST_F(SpdyProxyClientSocketSpdy2Test, ReadOnClosedSocketReturnsBufferedData) { scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); MockWrite writes[] = { CreateMockWrite(*conn, 0, SYNCHRONOUS), @@ -1052,7 +1063,7 @@ TEST_F(SpdyProxyClientSocketTest, ReadOnClosedSocketReturnsBufferedData) { } // Calling Write() on a closed socket is an error -TEST_F(SpdyProxyClientSocketTest, WriteOnClosedStream) { +TEST_F(SpdyProxyClientSocketSpdy2Test, WriteOnClosedStream) { scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); MockWrite writes[] = { CreateMockWrite(*conn, 0, SYNCHRONOUS), @@ -1076,7 +1087,7 @@ TEST_F(SpdyProxyClientSocketTest, WriteOnClosedStream) { } // Calling Write() on a disconnected socket is an error -TEST_F(SpdyProxyClientSocketTest, WriteOnDisconnectedSocket) { +TEST_F(SpdyProxyClientSocketSpdy2Test, WriteOnDisconnectedSocket) { scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); MockWrite writes[] = { CreateMockWrite(*conn, 0, SYNCHRONOUS), @@ -1102,7 +1113,7 @@ TEST_F(SpdyProxyClientSocketTest, WriteOnDisconnectedSocket) { // If the socket is closed with a pending Write(), the callback // should be called with ERR_CONNECTION_CLOSED. -TEST_F(SpdyProxyClientSocketTest, WritePendingOnClose) { +TEST_F(SpdyProxyClientSocketSpdy2Test, WritePendingOnClose) { scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); MockWrite writes[] = { CreateMockWrite(*conn, 0, SYNCHRONOUS), @@ -1132,7 +1143,7 @@ TEST_F(SpdyProxyClientSocketTest, WritePendingOnClose) { // If the socket is Disconnected with a pending Write(), the callback // should not be called. -TEST_F(SpdyProxyClientSocketTest, DisconnectWithWritePending) { +TEST_F(SpdyProxyClientSocketSpdy2Test, DisconnectWithWritePending) { scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); MockWrite writes[] = { CreateMockWrite(*conn, 0, SYNCHRONOUS), @@ -1163,7 +1174,7 @@ TEST_F(SpdyProxyClientSocketTest, DisconnectWithWritePending) { // If the socket is Disconnected with a pending Read(), the callback // should not be called. -TEST_F(SpdyProxyClientSocketTest, DisconnectWithReadPending) { +TEST_F(SpdyProxyClientSocketSpdy2Test, DisconnectWithReadPending) { scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); MockWrite writes[] = { CreateMockWrite(*conn, 0, SYNCHRONOUS), @@ -1193,7 +1204,7 @@ TEST_F(SpdyProxyClientSocketTest, DisconnectWithReadPending) { // If the socket is Reset when both a read and write are pending, // both should be called back. -TEST_F(SpdyProxyClientSocketTest, RstWithReadAndWritePending) { +TEST_F(SpdyProxyClientSocketSpdy2Test, RstWithReadAndWritePending) { scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); MockWrite writes[] = { CreateMockWrite(*conn, 0, SYNCHRONOUS), @@ -1260,7 +1271,7 @@ class DeleteSockCallback : public TestCompletionCallbackBase { // If the socket is Reset when both a read and write are pending, and the // read callback causes the socket to be deleted, the write callback should // not be called. -TEST_F(SpdyProxyClientSocketTest, RstWithReadAndWritePendingDelete) { +TEST_F(SpdyProxyClientSocketSpdy2Test, RstWithReadAndWritePendingDelete) { scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); MockWrite writes[] = { CreateMockWrite(*conn, 0, SYNCHRONOUS), diff --git a/net/spdy/spdy_proxy_client_socket_spdy3_unittest.cc b/net/spdy/spdy_proxy_client_socket_spdy3_unittest.cc new file mode 100644 index 0000000..f3921a1 --- /dev/null +++ b/net/spdy/spdy_proxy_client_socket_spdy3_unittest.cc @@ -0,0 +1,1312 @@ +// Copyright (c) 2012 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_proxy_client_socket.h" + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/utf_string_conversions.h" +#include "net/base/address_list.h" +#include "net/base/net_log.h" +#include "net/base/net_log_unittest.h" +#include "net/base/mock_host_resolver.h" +#include "net/base/test_completion_callback.h" +#include "net/base/winsock_init.h" +#include "net/http/http_response_info.h" +#include "net/http/http_response_headers.h" +#include "net/socket/client_socket_factory.h" +#include "net/socket/tcp_client_socket.h" +#include "net/socket/socket_test_util.h" +#include "net/spdy/spdy_http_utils.h" +#include "net/spdy/spdy_protocol.h" +#include "net/spdy/spdy_session_pool.h" +#include "net/spdy/spdy_test_util_spdy3.h" +#include "testing/platform_test.h" +#include "testing/gtest/include/gtest/gtest.h" + +using namespace net::test_spdy3; + +//----------------------------------------------------------------------------- + +namespace { + +static const char kUrl[] = "https://www.google.com/"; +static const char kOriginHost[] = "www.google.com"; +static const int kOriginPort = 443; +static const char kOriginHostPort[] = "www.google.com:443"; +static const char kProxyUrl[] = "https://myproxy:6121/"; +static const char kProxyHost[] = "myproxy"; +static const int kProxyPort = 6121; +static const char kUserAgent[] = "Mozilla/1.0"; + +static const int kStreamId = 1; + +static const char kMsg1[] = "\0hello!\xff"; +static const int kLen1 = 8; +static const char kMsg2[] = "\00012345678\0"; +static const int kLen2 = 10; +static const char kMsg3[] = "bye!"; +static const int kLen3 = 4; +static const char kMsg33[] = "bye!bye!"; +static const int kLen33 = kLen3 + kLen3; +static const char kMsg333[] = "bye!bye!bye!"; +static const int kLen333 = kLen3 + kLen3 + kLen3; + +} // anonymous namespace + +namespace net { + +class SpdyProxyClientSocketSpdy3Test : public PlatformTest { + public: + SpdyProxyClientSocketSpdy3Test(); + + virtual void TearDown(); + + protected: + void Initialize(MockRead* reads, size_t reads_count, MockWrite* writes, + size_t writes_count); + spdy::SpdyFrame* ConstructConnectRequestFrame(); + spdy::SpdyFrame* ConstructConnectAuthRequestFrame(); + spdy::SpdyFrame* ConstructConnectReplyFrame(); + spdy::SpdyFrame* ConstructConnectAuthReplyFrame(); + spdy::SpdyFrame* ConstructConnectErrorReplyFrame(); + spdy::SpdyFrame* ConstructBodyFrame(const char* data, int length); + scoped_refptr<IOBufferWithSize> CreateBuffer(const char* data, int size); + void AssertConnectSucceeds(); + void AssertConnectFails(int result); + void AssertConnectionEstablished(); + void AssertSyncReadEquals(const char* data, int len); + void AssertAsyncReadEquals(const char* data, int len); + void AssertReadStarts(const char* data, int len); + void AssertReadReturns(const char* data, int len); + void AssertAsyncWriteSucceeds(const char* data, int len); + void AssertWriteReturns(const char* data, int len, int rv); + void AssertWriteLength(int len); + void AssertAsyncWriteWithReadsSucceeds(const char* data, int len, + int num_reads); + + void AddAuthToCache() { + const string16 kFoo(ASCIIToUTF16("foo")); + const string16 kBar(ASCIIToUTF16("bar")); + session_->http_auth_cache()->Add(GURL(kProxyUrl), + "MyRealm1", + HttpAuth::AUTH_SCHEME_BASIC, + "Basic realm=MyRealm1", + AuthCredentials(kFoo, kBar), + "/"); + } + + void Run(int steps) { + data_->StopAfter(steps); + data_->Run(); + } + + scoped_ptr<SpdyProxyClientSocket> sock_; + TestCompletionCallback read_callback_; + TestCompletionCallback write_callback_; + scoped_refptr<DeterministicSocketData> data_; + + private: + scoped_refptr<HttpNetworkSession> session_; + scoped_refptr<IOBuffer> read_buf_; + SpdySessionDependencies session_deps_; + MockConnect connect_data_; + scoped_refptr<SpdySession> spdy_session_; + scoped_refptr<SpdyStream> spdy_stream_; + spdy::SpdyFramer framer_; + + std::string user_agent_; + GURL url_; + HostPortPair proxy_host_port_; + HostPortPair endpoint_host_port_pair_; + ProxyServer proxy_; + HostPortProxyPair endpoint_host_port_proxy_pair_; + scoped_refptr<TransportSocketParams> transport_params_; + + DISALLOW_COPY_AND_ASSIGN(SpdyProxyClientSocketSpdy3Test); +}; + +SpdyProxyClientSocketSpdy3Test::SpdyProxyClientSocketSpdy3Test() + : sock_(NULL), + data_(NULL), + session_(NULL), + read_buf_(NULL), + session_deps_(), + connect_data_(SYNCHRONOUS, OK), + spdy_session_(NULL), + spdy_stream_(NULL), + framer_(), + user_agent_(kUserAgent), + url_(kUrl), + proxy_host_port_(kProxyHost, kProxyPort), + endpoint_host_port_pair_(kOriginHost, kOriginPort), + proxy_(ProxyServer::SCHEME_HTTPS, proxy_host_port_), + endpoint_host_port_proxy_pair_(endpoint_host_port_pair_, proxy_), + transport_params_(new TransportSocketParams(proxy_host_port_, + LOWEST, + false, + false)) { +} + +void SpdyProxyClientSocketSpdy3Test::TearDown() { + sock_.reset(NULL); + if (session_ != NULL) + session_->spdy_session_pool()->CloseAllSessions(); + + spdy::SpdyFramer::set_enable_compression_default(true); + // Empty the current queue. + MessageLoop::current()->RunAllPending(); + PlatformTest::TearDown(); +} + +void SpdyProxyClientSocketSpdy3Test::Initialize(MockRead* reads, + size_t reads_count, + MockWrite* writes, + size_t writes_count) { + data_ = new DeterministicSocketData(reads, reads_count, writes, writes_count); + data_->set_connect_data(connect_data_); + data_->SetStop(2); + + session_deps_.deterministic_socket_factory->AddSocketDataProvider( + data_.get()); + session_deps_.host_resolver->set_synchronous_mode(true); + + session_ = SpdySessionDependencies::SpdyCreateSessionDeterministic( + &session_deps_); + SpdySession::SetSSLMode(false); + spdy::SpdyFramer::set_enable_compression_default(false); + + // Creates a new spdy session + spdy_session_ = + session_->spdy_session_pool()->Get(endpoint_host_port_proxy_pair_, + BoundNetLog()); + + // Perform the TCP connect + scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle); + EXPECT_EQ(OK, + connection->Init(endpoint_host_port_pair_.ToString(), + transport_params_, LOWEST, CompletionCallback(), + session_->GetTransportSocketPool(), + BoundNetLog())); + spdy_session_->InitializeWithSocket(connection.release(), false, OK); + + // Create the SPDY Stream + ASSERT_EQ( + OK, + spdy_session_->CreateStream(url_, LOWEST, &spdy_stream_, BoundNetLog(), + CompletionCallback())); + + // Create the SpdyProxyClientSocket + sock_.reset( + new SpdyProxyClientSocket(spdy_stream_, user_agent_, + endpoint_host_port_pair_, url_, + proxy_host_port_, session_->http_auth_cache(), + session_->http_auth_handler_factory())); +} + +scoped_refptr<IOBufferWithSize> SpdyProxyClientSocketSpdy3Test::CreateBuffer( + const char* data, int size) { + scoped_refptr<IOBufferWithSize> buf(new IOBufferWithSize(size)); + memcpy(buf->data(), data, size); + return buf; +} + +void SpdyProxyClientSocketSpdy3Test::AssertConnectSucceeds() { + ASSERT_EQ(ERR_IO_PENDING, sock_->Connect(read_callback_.callback())); + data_->Run(); + ASSERT_EQ(OK, read_callback_.WaitForResult()); +} + +void SpdyProxyClientSocketSpdy3Test::AssertConnectFails(int result) { + ASSERT_EQ(ERR_IO_PENDING, sock_->Connect(read_callback_.callback())); + data_->Run(); + ASSERT_EQ(result, read_callback_.WaitForResult()); +} + +void SpdyProxyClientSocketSpdy3Test::AssertConnectionEstablished() { + const HttpResponseInfo* response = sock_->GetConnectResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_EQ(200, response->headers->response_code()); + ASSERT_EQ("Connection Established", response->headers->GetStatusText()); +} + +void SpdyProxyClientSocketSpdy3Test::AssertSyncReadEquals(const char* data, + int len) { + scoped_refptr<IOBuffer> buf(new IOBuffer(len)); + ASSERT_EQ(len, sock_->Read(buf, len, CompletionCallback())); + ASSERT_EQ(std::string(data, len), std::string(buf->data(), len)); + ASSERT_TRUE(sock_->IsConnected()); +} + +void SpdyProxyClientSocketSpdy3Test::AssertAsyncReadEquals(const char* data, + int len) { + data_->StopAfter(1); + // Issue the read, which will be completed asynchronously + scoped_refptr<IOBuffer> buf(new IOBuffer(len)); + ASSERT_EQ(ERR_IO_PENDING, sock_->Read(buf, len, read_callback_.callback())); + EXPECT_TRUE(sock_->IsConnected()); + data_->Run(); + + EXPECT_TRUE(sock_->IsConnected()); + + // Now the read will return + EXPECT_EQ(len, read_callback_.WaitForResult()); + ASSERT_EQ(std::string(data, len), std::string(buf->data(), len)); +} + +void SpdyProxyClientSocketSpdy3Test::AssertReadStarts(const char* data, + int len) { + data_->StopAfter(1); + // Issue the read, which will be completed asynchronously + read_buf_ = new IOBuffer(len); + ASSERT_EQ(ERR_IO_PENDING, + sock_->Read(read_buf_, len, read_callback_.callback())); + EXPECT_TRUE(sock_->IsConnected()); +} + +void SpdyProxyClientSocketSpdy3Test::AssertReadReturns(const char* data, + int len) { + EXPECT_TRUE(sock_->IsConnected()); + + // Now the read will return + EXPECT_EQ(len, read_callback_.WaitForResult()); + ASSERT_EQ(std::string(data, len), std::string(read_buf_->data(), len)); +} + +void SpdyProxyClientSocketSpdy3Test::AssertAsyncWriteSucceeds(const char* data, + int len) { + AssertWriteReturns(data, len, ERR_IO_PENDING); + data_->RunFor(1); + AssertWriteLength(len); +} + +void SpdyProxyClientSocketSpdy3Test::AssertWriteReturns(const char* data, + int len, + int rv) { + scoped_refptr<IOBufferWithSize> buf(CreateBuffer(data, len)); + EXPECT_EQ(rv, sock_->Write(buf, buf->size(), write_callback_.callback())); +} + +void SpdyProxyClientSocketSpdy3Test::AssertWriteLength(int len) { + EXPECT_EQ(len, write_callback_.WaitForResult()); +} + +void SpdyProxyClientSocketSpdy3Test::AssertAsyncWriteWithReadsSucceeds( + const char* data, int len, int num_reads) { + scoped_refptr<IOBufferWithSize> buf(CreateBuffer(data, len)); + + EXPECT_EQ(ERR_IO_PENDING, sock_->Write(buf, buf->size(), + write_callback_.callback())); + + for (int i = 0; i < num_reads; i++) { + Run(1); + AssertSyncReadEquals(kMsg2, kLen2); + } + + write_callback_.WaitForResult(); +} + +// Constructs a standard SPDY SYN_STREAM frame for a CONNECT request. +spdy::SpdyFrame* +SpdyProxyClientSocketSpdy3Test::ConstructConnectRequestFrame() { + const SpdyHeaderInfo kSynStartHeader = { + spdy::SYN_STREAM, + kStreamId, + 0, + net::ConvertRequestPriorityToSpdyPriority(LOWEST), + spdy::CONTROL_FLAG_NONE, + false, + spdy::INVALID, + NULL, + 0, + spdy::DATA_FLAG_NONE + }; + const char* const kConnectHeaders[] = { + "method", "CONNECT", + "url", kOriginHostPort, + "host", kOriginHost, + "user-agent", kUserAgent, + "version", "HTTP/1.1", + }; + return ConstructSpdyPacket( + kSynStartHeader, NULL, 0, kConnectHeaders, arraysize(kConnectHeaders)/2); +} + +// Constructs a SPDY SYN_STREAM frame for a CONNECT request which includes +// Proxy-Authorization headers. +spdy::SpdyFrame* +SpdyProxyClientSocketSpdy3Test::ConstructConnectAuthRequestFrame() { + const SpdyHeaderInfo kSynStartHeader = { + spdy::SYN_STREAM, + kStreamId, + 0, + net::ConvertRequestPriorityToSpdyPriority(LOWEST), + spdy::CONTROL_FLAG_NONE, + false, + spdy::INVALID, + NULL, + 0, + spdy::DATA_FLAG_NONE + }; + const char* const kConnectHeaders[] = { + "method", "CONNECT", + "url", kOriginHostPort, + "host", kOriginHost, + "user-agent", kUserAgent, + "version", "HTTP/1.1", + "proxy-authorization", "Basic Zm9vOmJhcg==", + }; + return ConstructSpdyPacket( + kSynStartHeader, NULL, 0, kConnectHeaders, arraysize(kConnectHeaders)/2); +} + +// Constructs a standard SPDY SYN_REPLY frame to match the SPDY CONNECT. +spdy::SpdyFrame* SpdyProxyClientSocketSpdy3Test::ConstructConnectReplyFrame() { + const char* const kStandardReplyHeaders[] = { + "status", "200 Connection Established", + "version", "HTTP/1.1" + }; + return ConstructSpdyControlFrame(NULL, + 0, + false, + kStreamId, + LOWEST, + spdy::SYN_REPLY, + spdy::CONTROL_FLAG_NONE, + kStandardReplyHeaders, + arraysize(kStandardReplyHeaders)); +} + +// Constructs a standard SPDY SYN_REPLY frame to match the SPDY CONNECT. +spdy::SpdyFrame* +SpdyProxyClientSocketSpdy3Test::ConstructConnectAuthReplyFrame() { + const char* const kStandardReplyHeaders[] = { + "status", "407 Proxy Authentication Required", + "version", "HTTP/1.1", + "proxy-authenticate", "Basic realm=\"MyRealm1\"", + }; + + return ConstructSpdyControlFrame(NULL, + 0, + false, + kStreamId, + LOWEST, + spdy::SYN_REPLY, + spdy::CONTROL_FLAG_NONE, + kStandardReplyHeaders, + arraysize(kStandardReplyHeaders)); +} + +// Constructs a SPDY SYN_REPLY frame with an HTTP 500 error. +spdy::SpdyFrame* +SpdyProxyClientSocketSpdy3Test::ConstructConnectErrorReplyFrame() { + const char* const kStandardReplyHeaders[] = { + "status", "500 Internal Server Error", + "version", "HTTP/1.1", + }; + + return ConstructSpdyControlFrame(NULL, + 0, + false, + kStreamId, + LOWEST, + spdy::SYN_REPLY, + spdy::CONTROL_FLAG_NONE, + kStandardReplyHeaders, + arraysize(kStandardReplyHeaders)); +} + +spdy::SpdyFrame* SpdyProxyClientSocketSpdy3Test::ConstructBodyFrame( + const char* data, + int length) { + return framer_.CreateDataFrame(kStreamId, data, length, spdy::DATA_FLAG_NONE); +} + +// ----------- Connect + +TEST_F(SpdyProxyClientSocketSpdy3Test, ConnectSendsCorrectRequest) { + scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 0, SYNCHRONOUS), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructConnectReplyFrame()); + MockRead reads[] = { + CreateMockRead(*resp, 1, ASYNC), + MockRead(ASYNC, 0, 3), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + ASSERT_FALSE(sock_->IsConnected()); + + AssertConnectSucceeds(); + + AssertConnectionEstablished(); +} + +TEST_F(SpdyProxyClientSocketSpdy3Test, ConnectWithAuthRequested) { + scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 0, SYNCHRONOUS), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructConnectAuthReplyFrame()); + MockRead reads[] = { + CreateMockRead(*resp, 1, ASYNC), + MockRead(ASYNC, 0, 3), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + AssertConnectFails(ERR_PROXY_AUTH_REQUESTED); + + const HttpResponseInfo* response = sock_->GetConnectResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_EQ(407, response->headers->response_code()); + ASSERT_EQ("Proxy Authentication Required", + response->headers->GetStatusText()); +} + +TEST_F(SpdyProxyClientSocketSpdy3Test, ConnectWithAuthCredentials) { + scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectAuthRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 0, SYNCHRONOUS), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructConnectReplyFrame()); + MockRead reads[] = { + CreateMockRead(*resp, 1, ASYNC), + MockRead(ASYNC, 0, 3), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + AddAuthToCache(); + + AssertConnectSucceeds(); + + AssertConnectionEstablished(); +} + +TEST_F(SpdyProxyClientSocketSpdy3Test, ConnectFails) { + scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 0, SYNCHRONOUS), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructConnectReplyFrame()); + MockRead reads[] = { + MockRead(ASYNC, 0, 1), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + ASSERT_FALSE(sock_->IsConnected()); + + AssertConnectFails(ERR_CONNECTION_CLOSED); + + ASSERT_FALSE(sock_->IsConnected()); +} + +// ----------- WasEverUsed + +TEST_F(SpdyProxyClientSocketSpdy3Test, WasEverUsedReturnsCorrectValues) { + scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 0, SYNCHRONOUS), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructConnectReplyFrame()); + MockRead reads[] = { + CreateMockRead(*resp, 1, ASYNC), + MockRead(ASYNC, 0, 2), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + EXPECT_FALSE(sock_->WasEverUsed()); + AssertConnectSucceeds(); + EXPECT_TRUE(sock_->WasEverUsed()); + sock_->Disconnect(); + EXPECT_TRUE(sock_->WasEverUsed()); +} + +// ----------- GetPeerAddress + +TEST_F(SpdyProxyClientSocketSpdy3Test, GetPeerAddressReturnsCorrectValues) { + scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 0, SYNCHRONOUS), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructConnectReplyFrame()); + MockRead reads[] = { + CreateMockRead(*resp, 1, ASYNC), + MockRead(ASYNC, 0, 2), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + net::AddressList addr; + EXPECT_EQ(ERR_SOCKET_NOT_CONNECTED, sock_->GetPeerAddress(&addr)); + + AssertConnectSucceeds(); + EXPECT_TRUE(sock_->IsConnected()); + EXPECT_EQ(OK, sock_->GetPeerAddress(&addr)); + + Run(1); + + EXPECT_FALSE(sock_->IsConnected()); + EXPECT_EQ(ERR_SOCKET_NOT_CONNECTED, sock_->GetPeerAddress(&addr)); + + sock_->Disconnect(); + + EXPECT_EQ(ERR_SOCKET_NOT_CONNECTED, sock_->GetPeerAddress(&addr)); +} + +// ----------- Write + +TEST_F(SpdyProxyClientSocketSpdy3Test, WriteSendsDataInDataFrame) { + scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); + scoped_ptr<spdy::SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1)); + scoped_ptr<spdy::SpdyFrame> msg2(ConstructBodyFrame(kMsg2, kLen2)); + MockWrite writes[] = { + CreateMockWrite(*conn, 0, SYNCHRONOUS), + CreateMockWrite(*msg1, 2, SYNCHRONOUS), + CreateMockWrite(*msg2, 3, SYNCHRONOUS), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructConnectReplyFrame()); + MockRead reads[] = { + CreateMockRead(*resp, 1, ASYNC), + MockRead(ASYNC, 0, 4), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + AssertConnectSucceeds(); + + AssertAsyncWriteSucceeds(kMsg1, kLen1); + AssertAsyncWriteSucceeds(kMsg2, kLen2); +} + +TEST_F(SpdyProxyClientSocketSpdy3Test, WriteSplitsLargeDataIntoMultipleFrames) { + std::string chunk_data(kMaxSpdyFrameChunkSize, 'x'); + scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); + scoped_ptr<spdy::SpdyFrame> chunk(ConstructBodyFrame(chunk_data.data(), + chunk_data.length())); + MockWrite writes[] = { + CreateMockWrite(*conn, 0, SYNCHRONOUS), + CreateMockWrite(*chunk, 2, SYNCHRONOUS), + CreateMockWrite(*chunk, 3, SYNCHRONOUS), + CreateMockWrite(*chunk, 4, SYNCHRONOUS) + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructConnectReplyFrame()); + MockRead reads[] = { + CreateMockRead(*resp, 1, ASYNC), + MockRead(ASYNC, 0, 5), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + AssertConnectSucceeds(); + + std::string big_data(kMaxSpdyFrameChunkSize * 3, 'x'); + scoped_refptr<IOBufferWithSize> buf(CreateBuffer(big_data.data(), + big_data.length())); + + EXPECT_EQ(ERR_IO_PENDING, sock_->Write(buf, buf->size(), + write_callback_.callback())); + data_->RunFor(3); + + EXPECT_EQ(buf->size(), write_callback_.WaitForResult()); +} + +// ----------- Read + +TEST_F(SpdyProxyClientSocketSpdy3Test, ReadReadsDataInDataFrame) { + scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 0, SYNCHRONOUS), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructConnectReplyFrame()); + scoped_ptr<spdy::SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1)); + MockRead reads[] = { + CreateMockRead(*resp, 1, ASYNC), + CreateMockRead(*msg1, 2, ASYNC), + MockRead(ASYNC, 0, 3), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + AssertConnectSucceeds(); + + Run(1); // SpdySession consumes the next read and sends it to + // sock_ to be buffered. + AssertSyncReadEquals(kMsg1, kLen1); +} + +TEST_F(SpdyProxyClientSocketSpdy3Test, ReadDataFromBufferedFrames) { + scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 0, SYNCHRONOUS), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructConnectReplyFrame()); + scoped_ptr<spdy::SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1)); + scoped_ptr<spdy::SpdyFrame> msg2(ConstructBodyFrame(kMsg2, kLen2)); + MockRead reads[] = { + CreateMockRead(*resp, 1, ASYNC), + CreateMockRead(*msg1, 2, ASYNC), + CreateMockRead(*msg2, 3, ASYNC), + MockRead(ASYNC, 0, 4), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + AssertConnectSucceeds(); + + Run(1); // SpdySession consumes the next read and sends it to + // sock_ to be buffered. + AssertSyncReadEquals(kMsg1, kLen1); + Run(1); // SpdySession consumes the next read and sends it to + // sock_ to be buffered. + AssertSyncReadEquals(kMsg2, kLen2); +} + +TEST_F(SpdyProxyClientSocketSpdy3Test, ReadDataMultipleBufferedFrames) { + scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 0, SYNCHRONOUS), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructConnectReplyFrame()); + scoped_ptr<spdy::SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1)); + scoped_ptr<spdy::SpdyFrame> msg2(ConstructBodyFrame(kMsg2, kLen2)); + MockRead reads[] = { + CreateMockRead(*resp, 1, ASYNC), + CreateMockRead(*msg1, 2, ASYNC), + CreateMockRead(*msg2, 3, ASYNC), + MockRead(ASYNC, 0, 4), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + AssertConnectSucceeds(); + + Run(2); // SpdySession consumes the next two reads and sends then to + // sock_ to be buffered. + AssertSyncReadEquals(kMsg1, kLen1); + AssertSyncReadEquals(kMsg2, kLen2); +} + +TEST_F(SpdyProxyClientSocketSpdy3Test, + LargeReadWillMergeDataFromDifferentFrames) { + scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 0, SYNCHRONOUS), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructConnectReplyFrame()); + scoped_ptr<spdy::SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1)); + scoped_ptr<spdy::SpdyFrame> msg3(ConstructBodyFrame(kMsg3, kLen3)); + MockRead reads[] = { + CreateMockRead(*resp, 1, ASYNC), + CreateMockRead(*msg3, 2, ASYNC), + CreateMockRead(*msg3, 3, ASYNC), + MockRead(ASYNC, 0, 4), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + AssertConnectSucceeds(); + + Run(2); // SpdySession consumes the next two reads and sends then to + // sock_ to be buffered. + // The payload from two data frames, each with kMsg3 will be combined + // together into a single read(). + AssertSyncReadEquals(kMsg33, kLen33); +} + +TEST_F(SpdyProxyClientSocketSpdy3Test, MultipleShortReadsThenMoreRead) { + scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 0, SYNCHRONOUS), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructConnectReplyFrame()); + scoped_ptr<spdy::SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1)); + scoped_ptr<spdy::SpdyFrame> msg3(ConstructBodyFrame(kMsg3, kLen3)); + scoped_ptr<spdy::SpdyFrame> msg2(ConstructBodyFrame(kMsg2, kLen2)); + MockRead reads[] = { + CreateMockRead(*resp, 1, ASYNC), + CreateMockRead(*msg1, 2, ASYNC), + CreateMockRead(*msg3, 3, ASYNC), + CreateMockRead(*msg3, 4, ASYNC), + CreateMockRead(*msg2, 5, ASYNC), + MockRead(ASYNC, 0, 6), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + AssertConnectSucceeds(); + + Run(4); // SpdySession consumes the next four reads and sends then to + // sock_ to be buffered. + AssertSyncReadEquals(kMsg1, kLen1); + // The payload from two data frames, each with kMsg3 will be combined + // together into a single read(). + AssertSyncReadEquals(kMsg33, kLen33); + AssertSyncReadEquals(kMsg2, kLen2); +} + +TEST_F(SpdyProxyClientSocketSpdy3Test, ReadWillSplitDataFromLargeFrame) { + scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 0, SYNCHRONOUS), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructConnectReplyFrame()); + scoped_ptr<spdy::SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1)); + scoped_ptr<spdy::SpdyFrame> msg33(ConstructBodyFrame(kMsg33, kLen33)); + scoped_ptr<spdy::SpdyFrame> msg2(ConstructBodyFrame(kMsg2, kLen2)); + MockRead reads[] = { + CreateMockRead(*resp, 1, ASYNC), + CreateMockRead(*msg1, 2, ASYNC), + CreateMockRead(*msg33, 3, ASYNC), + MockRead(ASYNC, 0, 4), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + AssertConnectSucceeds(); + + Run(2); // SpdySession consumes the next two reads and sends then to + // sock_ to be buffered. + AssertSyncReadEquals(kMsg1, kLen1); + // The payload from the single large data frame will be read across + // two different reads. + AssertSyncReadEquals(kMsg3, kLen3); + AssertSyncReadEquals(kMsg3, kLen3); +} + +TEST_F(SpdyProxyClientSocketSpdy3Test, MultipleReadsFromSameLargeFrame) { + scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 0, SYNCHRONOUS), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructConnectReplyFrame()); + scoped_ptr<spdy::SpdyFrame> msg333(ConstructBodyFrame(kMsg333, kLen333)); + MockRead reads[] = { + CreateMockRead(*resp, 1, ASYNC), + CreateMockRead(*msg333, 2, ASYNC), + MockRead(ASYNC, 0, 3), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + AssertConnectSucceeds(); + + Run(1); // SpdySession consumes the next read and sends it to + // sock_ to be buffered. + // The payload from the single large data frame will be read across + // two different reads. + AssertSyncReadEquals(kMsg33, kLen33); + + // Now attempt to do a read of more data than remains buffered + scoped_refptr<IOBuffer> buf(new IOBuffer(kLen33)); + ASSERT_EQ(kLen3, sock_->Read(buf, kLen33, read_callback_.callback())); + ASSERT_EQ(std::string(kMsg3, kLen3), std::string(buf->data(), kLen3)); + ASSERT_TRUE(sock_->IsConnected()); +} + +TEST_F(SpdyProxyClientSocketSpdy3Test, ReadAuthResponseBody) { + scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 0, SYNCHRONOUS), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructConnectAuthReplyFrame()); + scoped_ptr<spdy::SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1)); + scoped_ptr<spdy::SpdyFrame> msg2(ConstructBodyFrame(kMsg2, kLen2)); + MockRead reads[] = { + CreateMockRead(*resp, 1, ASYNC), + CreateMockRead(*msg1, 2, ASYNC), + CreateMockRead(*msg2, 3, ASYNC), + MockRead(ASYNC, 0, 4), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + AssertConnectFails(ERR_PROXY_AUTH_REQUESTED); + + Run(2); // SpdySession consumes the next two reads and sends then to + // sock_ to be buffered. + AssertSyncReadEquals(kMsg1, kLen1); + AssertSyncReadEquals(kMsg2, kLen2); +} + +TEST_F(SpdyProxyClientSocketSpdy3Test, ReadErrorResponseBody) { + scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 0, SYNCHRONOUS), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructConnectErrorReplyFrame()); + scoped_ptr<spdy::SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1)); + scoped_ptr<spdy::SpdyFrame> msg2(ConstructBodyFrame(kMsg2, kLen2)); + MockRead reads[] = { + CreateMockRead(*resp, 1, ASYNC), + CreateMockRead(*msg1, 2, ASYNC), + CreateMockRead(*msg2, 3, ASYNC), + MockRead(ASYNC, 0, 4), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + AssertConnectFails(ERR_HTTPS_PROXY_TUNNEL_RESPONSE); + + Run(2); // SpdySession consumes the next two reads and sends then to + // sock_ to be buffered. + EXPECT_EQ(ERR_SOCKET_NOT_CONNECTED, + sock_->Read(NULL, 1, CompletionCallback())); + scoped_refptr<IOBuffer> buf(new IOBuffer(kLen1 + kLen2)); + scoped_ptr<HttpStream> stream(sock_->CreateConnectResponseStream()); + stream->ReadResponseBody(buf, kLen1 + kLen2, read_callback_.callback()); +} + +// ----------- Reads and Writes + +TEST_F(SpdyProxyClientSocketSpdy3Test, AsyncReadAroundWrite) { + scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); + scoped_ptr<spdy::SpdyFrame> msg2(ConstructBodyFrame(kMsg2, kLen2)); + MockWrite writes[] = { + CreateMockWrite(*conn, 0, SYNCHRONOUS), + CreateMockWrite(*msg2, 3, SYNCHRONOUS), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructConnectReplyFrame()); + scoped_ptr<spdy::SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1)); + scoped_ptr<spdy::SpdyFrame> msg3(ConstructBodyFrame(kMsg3, kLen3)); + MockRead reads[] = { + CreateMockRead(*resp, 1, ASYNC), + CreateMockRead(*msg1, 2, ASYNC), // sync read + CreateMockRead(*msg3, 4, ASYNC), // async read + MockRead(ASYNC, 0, 5), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + AssertConnectSucceeds(); + + Run(1); + AssertSyncReadEquals(kMsg1, kLen1); + + AssertReadStarts(kMsg3, kLen3); + // Read should block until after the write succeeds + + AssertAsyncWriteSucceeds(kMsg2, kLen2); // Runs 1 step + + ASSERT_FALSE(read_callback_.have_result()); + Run(1); + // Now the read will return + AssertReadReturns(kMsg3, kLen3); +} + +TEST_F(SpdyProxyClientSocketSpdy3Test, AsyncWriteAroundReads) { + scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); + scoped_ptr<spdy::SpdyFrame> msg2(ConstructBodyFrame(kMsg2, kLen2)); + MockWrite writes[] = { + CreateMockWrite(*conn, 0, SYNCHRONOUS), + CreateMockWrite(*msg2, 4, ASYNC), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructConnectReplyFrame()); + scoped_ptr<spdy::SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1)); + scoped_ptr<spdy::SpdyFrame> msg3(ConstructBodyFrame(kMsg3, kLen3)); + MockRead reads[] = { + CreateMockRead(*resp, 1, ASYNC), + CreateMockRead(*msg1, 2, ASYNC), + CreateMockRead(*msg3, 3, ASYNC), + MockRead(ASYNC, 0, 5), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + AssertConnectSucceeds(); + + Run(1); + AssertSyncReadEquals(kMsg1, kLen1); + // Write should block until the read completes + AssertWriteReturns(kMsg2, kLen2, ERR_IO_PENDING); + + AssertAsyncReadEquals(kMsg3, kLen3); + + ASSERT_FALSE(write_callback_.have_result()); + + // Now the write will complete + Run(1); + AssertWriteLength(kLen2); +} + +// ----------- Reading/Writing on Closed socket + +// Reading from an already closed socket should return 0 +TEST_F(SpdyProxyClientSocketSpdy3Test, ReadOnClosedSocketReturnsZero) { + scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 0, SYNCHRONOUS), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructConnectReplyFrame()); + MockRead reads[] = { + CreateMockRead(*resp, 1, ASYNC), + MockRead(ASYNC, 0, 2), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + AssertConnectSucceeds(); + + Run(1); + + ASSERT_FALSE(sock_->IsConnected()); + ASSERT_EQ(0, sock_->Read(NULL, 1, CompletionCallback())); + ASSERT_EQ(0, sock_->Read(NULL, 1, CompletionCallback())); + ASSERT_EQ(0, sock_->Read(NULL, 1, CompletionCallback())); + ASSERT_FALSE(sock_->IsConnectedAndIdle()); +} + +// Read pending when socket is closed should return 0 +TEST_F(SpdyProxyClientSocketSpdy3Test, PendingReadOnCloseReturnsZero) { + scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 0, SYNCHRONOUS), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructConnectReplyFrame()); + MockRead reads[] = { + CreateMockRead(*resp, 1, ASYNC), + MockRead(ASYNC, 0, 2), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + AssertConnectSucceeds(); + + AssertReadStarts(kMsg1, kLen1); + + Run(1); + + ASSERT_EQ(0, read_callback_.WaitForResult()); +} + +// Reading from a disconnected socket is an error +TEST_F(SpdyProxyClientSocketSpdy3Test, + ReadOnDisconnectSocketReturnsNotConnected) { + scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 0, SYNCHRONOUS), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructConnectReplyFrame()); + MockRead reads[] = { + CreateMockRead(*resp, 1, ASYNC), + MockRead(ASYNC, 0, 2), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + AssertConnectSucceeds(); + + sock_->Disconnect(); + + ASSERT_EQ(ERR_SOCKET_NOT_CONNECTED, + sock_->Read(NULL, 1, CompletionCallback())); +} + +// Reading buffered data from an already closed socket should return +// buffered data, then 0. +TEST_F(SpdyProxyClientSocketSpdy3Test, ReadOnClosedSocketReturnsBufferedData) { + scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 0, SYNCHRONOUS), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructConnectReplyFrame()); + scoped_ptr<spdy::SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1)); + MockRead reads[] = { + CreateMockRead(*resp, 1, ASYNC), + CreateMockRead(*msg1, 2, ASYNC), + MockRead(ASYNC, 0, 3), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + AssertConnectSucceeds(); + + Run(2); + + ASSERT_FALSE(sock_->IsConnected()); + scoped_refptr<IOBuffer> buf(new IOBuffer(kLen1)); + ASSERT_EQ(kLen1, sock_->Read(buf, kLen1, CompletionCallback())); + ASSERT_EQ(std::string(kMsg1, kLen1), std::string(buf->data(), kLen1)); + + ASSERT_EQ(0, sock_->Read(NULL, 1, CompletionCallback())); + ASSERT_EQ(0, sock_->Read(NULL, 1, CompletionCallback())); + sock_->Disconnect(); + ASSERT_EQ(ERR_SOCKET_NOT_CONNECTED, + sock_->Read(NULL, 1, CompletionCallback())); +} + +// Calling Write() on a closed socket is an error +TEST_F(SpdyProxyClientSocketSpdy3Test, WriteOnClosedStream) { + scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 0, SYNCHRONOUS), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructConnectReplyFrame()); + scoped_ptr<spdy::SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1)); + MockRead reads[] = { + CreateMockRead(*resp, 1, ASYNC), + MockRead(ASYNC, 0, 2), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + AssertConnectSucceeds(); + + Run(1); // Read EOF which will close the stream + scoped_refptr<IOBufferWithSize> buf(CreateBuffer(kMsg1, kLen1)); + EXPECT_EQ(ERR_SOCKET_NOT_CONNECTED, + sock_->Write(buf, buf->size(), CompletionCallback())); +} + +// Calling Write() on a disconnected socket is an error +TEST_F(SpdyProxyClientSocketSpdy3Test, WriteOnDisconnectedSocket) { + scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 0, SYNCHRONOUS), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructConnectReplyFrame()); + scoped_ptr<spdy::SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1)); + MockRead reads[] = { + CreateMockRead(*resp, 1, ASYNC), + MockRead(ASYNC, 0, 2), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + AssertConnectSucceeds(); + + sock_->Disconnect(); + + scoped_refptr<IOBufferWithSize> buf(CreateBuffer(kMsg1, kLen1)); + EXPECT_EQ(ERR_SOCKET_NOT_CONNECTED, + sock_->Write(buf, buf->size(), CompletionCallback())); +} + +// If the socket is closed with a pending Write(), the callback +// should be called with ERR_CONNECTION_CLOSED. +TEST_F(SpdyProxyClientSocketSpdy3Test, WritePendingOnClose) { + scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 0, SYNCHRONOUS), + MockWrite(ASYNC, ERR_IO_PENDING, 2), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructConnectReplyFrame()); + MockRead reads[] = { + CreateMockRead(*resp, 1, ASYNC), + MockRead(ASYNC, 0, 3), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + AssertConnectSucceeds(); + + EXPECT_TRUE(sock_->IsConnected()); + + scoped_refptr<IOBufferWithSize> buf(CreateBuffer(kMsg1, kLen1)); + EXPECT_EQ(ERR_IO_PENDING, + sock_->Write(buf, buf->size(), write_callback_.callback())); + + Run(1); + + EXPECT_EQ(ERR_CONNECTION_CLOSED, write_callback_.WaitForResult()); +} + +// If the socket is Disconnected with a pending Write(), the callback +// should not be called. +TEST_F(SpdyProxyClientSocketSpdy3Test, DisconnectWithWritePending) { + scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 0, SYNCHRONOUS), + MockWrite(SYNCHRONOUS, 0, 2), // EOF + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructConnectReplyFrame()); + MockRead reads[] = { + CreateMockRead(*resp, 1, ASYNC), + MockRead(ASYNC, 0, 3), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + AssertConnectSucceeds(); + + EXPECT_TRUE(sock_->IsConnected()); + + scoped_refptr<IOBufferWithSize> buf(CreateBuffer(kMsg1, kLen1)); + EXPECT_EQ(ERR_IO_PENDING, + sock_->Write(buf, buf->size(), write_callback_.callback())); + + sock_->Disconnect(); + + EXPECT_FALSE(sock_->IsConnected()); + EXPECT_FALSE(write_callback_.have_result()); +} + +// If the socket is Disconnected with a pending Read(), the callback +// should not be called. +TEST_F(SpdyProxyClientSocketSpdy3Test, DisconnectWithReadPending) { + scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 0, SYNCHRONOUS), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructConnectReplyFrame()); + MockRead reads[] = { + CreateMockRead(*resp, 1, ASYNC), + MockRead(ASYNC, 0, 2), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + AssertConnectSucceeds(); + + EXPECT_TRUE(sock_->IsConnected()); + + scoped_refptr<IOBuffer> buf(new IOBuffer(kLen1)); + ASSERT_EQ(ERR_IO_PENDING, + sock_->Read(buf, kLen1, read_callback_.callback())); + + sock_->Disconnect(); + + EXPECT_FALSE(sock_->IsConnected()); + EXPECT_FALSE(read_callback_.have_result()); +} + +// If the socket is Reset when both a read and write are pending, +// both should be called back. +TEST_F(SpdyProxyClientSocketSpdy3Test, RstWithReadAndWritePending) { + scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 0, SYNCHRONOUS), + MockWrite(ASYNC, ERR_IO_PENDING, 2), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructConnectReplyFrame()); + scoped_ptr<spdy::SpdyFrame> rst(ConstructSpdyRstStream(1, spdy::CANCEL)); + MockRead reads[] = { + CreateMockRead(*resp, 1, ASYNC), + CreateMockRead(*rst, 3, ASYNC), + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + AssertConnectSucceeds(); + + EXPECT_TRUE(sock_->IsConnected()); + + scoped_refptr<IOBuffer> read_buf(new IOBuffer(kLen1)); + ASSERT_EQ(ERR_IO_PENDING, + sock_->Read(read_buf, kLen1, read_callback_.callback())); + + scoped_refptr<IOBufferWithSize> write_buf(CreateBuffer(kMsg1, kLen1)); + EXPECT_EQ(ERR_IO_PENDING, + sock_->Write(write_buf, write_buf->size(), + write_callback_.callback())); + + Run(2); + + EXPECT_TRUE(sock_.get()); + EXPECT_TRUE(read_callback_.have_result()); + EXPECT_TRUE(write_callback_.have_result()); +} + +// CompletionCallback that causes the SpdyProxyClientSocket to be +// deleted when Run is invoked. +class DeleteSockCallback : public TestCompletionCallbackBase { + public: + explicit DeleteSockCallback(scoped_ptr<SpdyProxyClientSocket>* sock) + : sock_(sock), + ALLOW_THIS_IN_INITIALIZER_LIST(callback_( + base::Bind(&DeleteSockCallback::OnComplete, + base::Unretained(this)))) { + } + + virtual ~DeleteSockCallback() { + } + + const CompletionCallback& callback() const { return callback_; } + + private: + void OnComplete(int result) { + sock_->reset(NULL); + SetResult(result); + } + + scoped_ptr<SpdyProxyClientSocket>* sock_; + CompletionCallback callback_; + + DISALLOW_COPY_AND_ASSIGN(DeleteSockCallback); +}; + +// If the socket is Reset when both a read and write are pending, and the +// read callback causes the socket to be deleted, the write callback should +// not be called. +TEST_F(SpdyProxyClientSocketSpdy3Test, RstWithReadAndWritePendingDelete) { + scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 0, SYNCHRONOUS), + MockWrite(ASYNC, ERR_IO_PENDING, 2), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructConnectReplyFrame()); + scoped_ptr<spdy::SpdyFrame> rst(ConstructSpdyRstStream(1, spdy::CANCEL)); + MockRead reads[] = { + CreateMockRead(*resp, 1, ASYNC), + CreateMockRead(*rst, 3, ASYNC), + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + AssertConnectSucceeds(); + + EXPECT_TRUE(sock_->IsConnected()); + + DeleteSockCallback read_callback(&sock_); + + scoped_refptr<IOBuffer> read_buf(new IOBuffer(kLen1)); + ASSERT_EQ(ERR_IO_PENDING, + sock_->Read(read_buf, kLen1, read_callback.callback())); + + scoped_refptr<IOBufferWithSize> write_buf(CreateBuffer(kMsg1, kLen1)); + EXPECT_EQ(ERR_IO_PENDING, sock_->Write(write_buf, write_buf->size(), + write_callback_.callback())); + + Run(2); + + EXPECT_FALSE(sock_.get()); + EXPECT_TRUE(read_callback.have_result()); + EXPECT_FALSE(write_callback_.have_result()); +} + +} // namespace net diff --git a/net/spdy/spdy_session.h b/net/spdy/spdy_session.h index 0610709c..7104800 100644 --- a/net/spdy/spdy_session.h +++ b/net/spdy/spdy_session.h @@ -284,9 +284,12 @@ class NET_EXPORT SpdySession : public base::RefCounted<SpdySession>, friend class base::RefCounted<SpdySession>; // Allow tests to access our innards for testing purposes. - FRIEND_TEST_ALL_PREFIXES(SpdySessionTest, Ping); - FRIEND_TEST_ALL_PREFIXES(SpdySessionTest, FailedPing); - FRIEND_TEST_ALL_PREFIXES(SpdySessionTest, GetActivePushStream); + FRIEND_TEST_ALL_PREFIXES(SpdySessionSpdy2Test, Ping); + FRIEND_TEST_ALL_PREFIXES(SpdySessionSpdy2Test, FailedPing); + FRIEND_TEST_ALL_PREFIXES(SpdySessionSpdy2Test, GetActivePushStream); + FRIEND_TEST_ALL_PREFIXES(SpdySessionSpdy3Test, Ping); + FRIEND_TEST_ALL_PREFIXES(SpdySessionSpdy3Test, FailedPing); + FRIEND_TEST_ALL_PREFIXES(SpdySessionSpdy3Test, GetActivePushStream); struct PendingCreateStream { PendingCreateStream(const GURL& url, RequestPriority priority, diff --git a/net/spdy/spdy_session_pool.h b/net/spdy/spdy_session_pool.h index 93ddf22..b04394c 100644 --- a/net/spdy/spdy_session_pool.h +++ b/net/spdy/spdy_session_pool.h @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 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. @@ -34,6 +34,14 @@ class HostResolver; class HttpServerProperties; class SpdySession; +namespace test_spdy2 { +class SpdySessionPoolPeer; +} // namespace test_spdy + +namespace test_spdy3 { +class SpdySessionPoolPeer; +} // namespace test_spdy + // This is a very simple pool for open SpdySessions. class NET_EXPORT SpdySessionPool : public NetworkChangeNotifier::IPAddressObserver, @@ -135,9 +143,17 @@ class NET_EXPORT SpdySessionPool virtual void OnCertTrustChanged(const X509Certificate* cert) OVERRIDE; private: - friend class SpdySessionPoolPeer; // For testing. - friend class SpdyNetworkTransactionTest; // For testing. - FRIEND_TEST_ALL_PREFIXES(SpdyNetworkTransactionTest, WindowUpdateOverflow); + friend class test_spdy2::SpdySessionPoolPeer; // For testing. + friend class test_spdy3::SpdySessionPoolPeer; // For testing. + friend class SpdyNetworkTransactionSpdy2Test; // For testing. + friend class SpdyNetworkTransactionSpdy21Test; // For testing. + friend class SpdyNetworkTransactionSpdy3Test; // For testing. + FRIEND_TEST_ALL_PREFIXES(SpdyNetworkTransactionSpdy2Test, + WindowUpdateOverflow); + FRIEND_TEST_ALL_PREFIXES(SpdyNetworkTransactionSpdy21Test, + WindowUpdateOverflow); + FRIEND_TEST_ALL_PREFIXES(SpdyNetworkTransactionSpdy3Test, + WindowUpdateOverflow); typedef std::list<scoped_refptr<SpdySession> > SpdySessionList; typedef std::map<HostPortProxyPair, SpdySessionList*> SpdySessionsMap; diff --git a/net/spdy/spdy_session_unittest.cc b/net/spdy/spdy_session_spdy2_unittest.cc index 0dedc6d..f5f33fc 100644 --- a/net/spdy/spdy_session_unittest.cc +++ b/net/spdy/spdy_session_spdy2_unittest.cc @@ -9,14 +9,16 @@ #include "net/spdy/spdy_io_buffer.h" #include "net/spdy/spdy_session_pool.h" #include "net/spdy/spdy_stream.h" -#include "net/spdy/spdy_test_util.h" +#include "net/spdy/spdy_test_util_spdy2.h" #include "testing/platform_test.h" +using namespace net::test_spdy2; + namespace net { // TODO(cbentzel): Expose compression setter/getter in public SpdySession // interface rather than going through all these contortions. -class SpdySessionTest : public PlatformTest { +class SpdySessionSpdy2Test : public PlatformTest { public: static void TurnOffCompression() { spdy::SpdyFramer::set_enable_compression_default(false); @@ -69,7 +71,7 @@ class TestSpdyStreamDelegate : public net::SpdyStream::Delegate { }; // Test the SpdyIOBuffer class. -TEST_F(SpdySessionTest, SpdyIOBuffer) { +TEST_F(SpdySessionSpdy2Test, SpdyIOBuffer) { std::priority_queue<SpdyIOBuffer> queue_; const size_t kQueueSize = 100; @@ -105,7 +107,7 @@ TEST_F(SpdySessionTest, SpdyIOBuffer) { } } -TEST_F(SpdySessionTest, GoAway) { +TEST_F(SpdySessionSpdy2Test, GoAway) { SpdySessionDependencies session_deps; session_deps.host_resolver->set_synchronous_mode(true); @@ -164,7 +166,7 @@ TEST_F(SpdySessionTest, GoAway) { session2 = NULL; } -TEST_F(SpdySessionTest, Ping) { +TEST_F(SpdySessionSpdy2Test, Ping) { SpdySessionDependencies session_deps; session_deps.host_resolver->set_synchronous_mode(true); @@ -255,7 +257,7 @@ TEST_F(SpdySessionTest, Ping) { session = NULL; } -TEST_F(SpdySessionTest, FailedPing) { +TEST_F(SpdySessionSpdy2Test, FailedPing) { SpdySessionDependencies session_deps; session_deps.host_resolver->set_synchronous_mode(true); @@ -385,7 +387,7 @@ class StreamReleaserCallback : public TestCompletionCallbackBase { // TODO(kristianm): Could also test with more sessions where some are idle, // and more than one session to a HostPortPair. -TEST_F(SpdySessionTest, CloseIdleSessions) { +TEST_F(SpdySessionSpdy2Test, CloseIdleSessions) { SpdySessionDependencies session_deps; scoped_refptr<HttpNetworkSession> http_session( SpdySessionDependencies::SpdyCreateSession(&session_deps)); @@ -490,7 +492,7 @@ TEST_F(SpdySessionTest, CloseIdleSessions) { // release the stream, which releases its reference (the last) to the session. // Make sure nothing blows up. // http://crbug.com/57331 -TEST_F(SpdySessionTest, OnSettings) { +TEST_F(SpdySessionSpdy2Test, OnSettings) { SpdySessionDependencies session_deps; session_deps.host_resolver->set_synchronous_mode(true); @@ -582,7 +584,7 @@ TEST_F(SpdySessionTest, OnSettings) { // first completes, have the callback close itself, which should trigger the // second stream creation. Then cancel that one immediately. Don't crash. // http://crbug.com/63532 -TEST_F(SpdySessionTest, CancelPendingCreateStream) { +TEST_F(SpdySessionSpdy2Test, CancelPendingCreateStream) { SpdySessionDependencies session_deps; session_deps.host_resolver->set_synchronous_mode(true); @@ -668,7 +670,7 @@ TEST_F(SpdySessionTest, CancelPendingCreateStream) { MessageLoop::current()->RunAllPending(); } -TEST_F(SpdySessionTest, SendSettingsOnNewSession) { +TEST_F(SpdySessionSpdy2Test, SendSettingsOnNewSession) { SpdySessionDependencies session_deps; session_deps.host_resolver->set_synchronous_mode(true); @@ -736,6 +738,7 @@ TEST_F(SpdySessionTest, SendSettingsOnNewSession) { EXPECT_TRUE(data.at_write_eof()); } +namespace { // This test has two variants, one for each style of closing the connection. // If |clean_via_close_current_sessions| is false, the sessions are closed // manually, calling SpdySessionPool::Remove() directly. If it is true, @@ -854,15 +857,17 @@ void IPPoolingTest(bool clean_via_close_current_sessions) { EXPECT_FALSE(spdy_session_pool->HasSession(test_hosts[2].pair)); } -TEST_F(SpdySessionTest, IPPooling) { +} // namespace + +TEST_F(SpdySessionSpdy2Test, IPPooling) { IPPoolingTest(false); } -TEST_F(SpdySessionTest, IPPoolingCloseCurrentSessions) { +TEST_F(SpdySessionSpdy2Test, IPPoolingCloseCurrentSessions) { IPPoolingTest(true); } -TEST_F(SpdySessionTest, ClearSettingsStorage) { +TEST_F(SpdySessionSpdy2Test, ClearSettingsStorage) { SpdySettingsStorage settings_storage; const std::string kTestHost("www.foo.com"); const int kTestPort = 80; @@ -880,7 +885,7 @@ TEST_F(SpdySessionTest, ClearSettingsStorage) { EXPECT_EQ(0u, settings_storage.Get(test_host_port_pair).size()); } -TEST_F(SpdySessionTest, ClearSettingsStorageOnIPAddressChanged) { +TEST_F(SpdySessionSpdy2Test, ClearSettingsStorageOnIPAddressChanged) { const std::string kTestHost("www.foo.com"); const int kTestPort = 80; HostPortPair test_host_port_pair(kTestHost, kTestPort); @@ -908,7 +913,7 @@ TEST_F(SpdySessionTest, ClearSettingsStorageOnIPAddressChanged) { test_host_port_pair).size()); } -TEST_F(SpdySessionTest, NeedsCredentials) { +TEST_F(SpdySessionSpdy2Test, NeedsCredentials) { SpdySessionDependencies session_deps; MockConnect connect_data(SYNCHRONOUS, OK); @@ -976,7 +981,7 @@ TEST_F(SpdySessionTest, NeedsCredentials) { EXPECT_FALSE(spdy_session_pool->HasSession(pair)); } -TEST_F(SpdySessionTest, SendCredentials) { +TEST_F(SpdySessionSpdy2Test, SendCredentials) { SpdySessionDependencies session_deps; MockConnect connect_data(SYNCHRONOUS, OK); @@ -1051,7 +1056,7 @@ TEST_F(SpdySessionTest, SendCredentials) { EXPECT_FALSE(spdy_session_pool->HasSession(pair)); } -TEST_F(SpdySessionTest, CloseSessionOnError) { +TEST_F(SpdySessionSpdy2Test, CloseSessionOnError) { SpdySessionDependencies session_deps; session_deps.host_resolver->set_synchronous_mode(true); diff --git a/net/spdy/spdy_session_spdy3_unittest.cc b/net/spdy/spdy_session_spdy3_unittest.cc new file mode 100644 index 0000000..7a8b086 --- /dev/null +++ b/net/spdy/spdy_session_spdy3_unittest.cc @@ -0,0 +1,1128 @@ +// Copyright (c) 2012 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_session.h" + +#include "net/base/ip_endpoint.h" +#include "net/base/net_log_unittest.h" +#include "net/spdy/spdy_io_buffer.h" +#include "net/spdy/spdy_session_pool.h" +#include "net/spdy/spdy_stream.h" +#include "net/spdy/spdy_test_util_spdy3.h" +#include "testing/platform_test.h" + +using namespace net::test_spdy3; + +namespace net { + +// TODO(cbentzel): Expose compression setter/getter in public SpdySession +// interface rather than going through all these contortions. +class SpdySessionSpdy3Test : public PlatformTest { + public: + static void TurnOffCompression() { + spdy::SpdyFramer::set_enable_compression_default(false); + } + protected: + virtual void TearDown() { + // Wanted to be 100% sure PING is disabled. + SpdySession::set_enable_ping_based_connection_checking(false); + } +}; + +class TestSpdyStreamDelegate : public net::SpdyStream::Delegate { + public: + explicit TestSpdyStreamDelegate(const CompletionCallback& callback) + : callback_(callback) {} + virtual ~TestSpdyStreamDelegate() {} + + virtual bool OnSendHeadersComplete(int status) { return true; } + + virtual int OnSendBody() { + return ERR_UNEXPECTED; + } + + virtual int OnSendBodyComplete(int /*status*/, bool* /*eof*/) { + return ERR_UNEXPECTED; + } + + virtual int OnResponseReceived(const spdy::SpdyHeaderBlock& response, + base::Time response_time, + int status) { + return status; + } + + virtual void OnDataReceived(const char* buffer, int bytes) { + } + + virtual void OnDataSent(int length) { + } + + virtual void OnClose(int status) { + CompletionCallback callback = callback_; + callback_.Reset(); + callback.Run(OK); + } + + virtual void set_chunk_callback(net::ChunkCallback *) {} + + private: + CompletionCallback callback_; +}; + +// Test the SpdyIOBuffer class. +TEST_F(SpdySessionSpdy3Test, SpdyIOBuffer) { + std::priority_queue<SpdyIOBuffer> queue_; + const size_t kQueueSize = 100; + + // Insert 100 items; pri 100 to 1. + for (size_t index = 0; index < kQueueSize; ++index) { + SpdyIOBuffer buffer(new IOBuffer(), 0, kQueueSize - index, NULL); + queue_.push(buffer); + } + + // Insert several priority 0 items last. + const size_t kNumDuplicates = 12; + IOBufferWithSize* buffers[kNumDuplicates]; + for (size_t index = 0; index < kNumDuplicates; ++index) { + buffers[index] = new IOBufferWithSize(index+1); + queue_.push(SpdyIOBuffer(buffers[index], buffers[index]->size(), 0, NULL)); + } + + EXPECT_EQ(kQueueSize + kNumDuplicates, queue_.size()); + + // Verify the P0 items come out in FIFO order. + for (size_t index = 0; index < kNumDuplicates; ++index) { + SpdyIOBuffer buffer = queue_.top(); + EXPECT_EQ(0, buffer.priority()); + EXPECT_EQ(index + 1, buffer.size()); + queue_.pop(); + } + + int priority = 1; + while (queue_.size()) { + SpdyIOBuffer buffer = queue_.top(); + EXPECT_EQ(priority++, buffer.priority()); + queue_.pop(); + } +} + +TEST_F(SpdySessionSpdy3Test, GoAway) { + SpdySessionDependencies session_deps; + session_deps.host_resolver->set_synchronous_mode(true); + + MockConnect connect_data(SYNCHRONOUS, OK); + scoped_ptr<spdy::SpdyFrame> goaway(ConstructSpdyGoAway()); + MockRead reads[] = { + CreateMockRead(*goaway), + MockRead(SYNCHRONOUS, 0, 0) // EOF + }; + StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0); + data.set_connect_data(connect_data); + session_deps.socket_factory->AddSocketDataProvider(&data); + + SSLSocketDataProvider ssl(SYNCHRONOUS, OK); + session_deps.socket_factory->AddSSLSocketDataProvider(&ssl); + + scoped_refptr<HttpNetworkSession> http_session( + SpdySessionDependencies::SpdyCreateSession(&session_deps)); + + const std::string kTestHost("www.foo.com"); + const int kTestPort = 80; + HostPortPair test_host_port_pair(kTestHost, kTestPort); + HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct()); + + SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool()); + EXPECT_FALSE(spdy_session_pool->HasSession(pair)); + scoped_refptr<SpdySession> session = + spdy_session_pool->Get(pair, BoundNetLog()); + EXPECT_TRUE(spdy_session_pool->HasSession(pair)); + + scoped_refptr<TransportSocketParams> transport_params( + new TransportSocketParams(test_host_port_pair, + MEDIUM, + false, + false)); + scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle); + EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(), + transport_params, MEDIUM, CompletionCallback(), + http_session->GetTransportSocketPool(), + BoundNetLog())); + EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK)); + + // Flush the SpdySession::OnReadComplete() task. + MessageLoop::current()->RunAllPending(); + + EXPECT_FALSE(spdy_session_pool->HasSession(pair)); + + scoped_refptr<SpdySession> session2 = + spdy_session_pool->Get(pair, BoundNetLog()); + + // Delete the first session. + session = NULL; + + // Delete the second session. + spdy_session_pool->Remove(session2); + session2 = NULL; +} + +TEST_F(SpdySessionSpdy3Test, Ping) { + SpdySessionDependencies session_deps; + session_deps.host_resolver->set_synchronous_mode(true); + + MockConnect connect_data(SYNCHRONOUS, OK); + scoped_ptr<spdy::SpdyFrame> read_ping(ConstructSpdyPing()); + MockRead reads[] = { + CreateMockRead(*read_ping), + CreateMockRead(*read_ping), + MockRead(SYNCHRONOUS, 0, 0) // EOF + }; + scoped_ptr<spdy::SpdyFrame> write_ping(ConstructSpdyPing()); + MockRead writes[] = { + CreateMockRead(*write_ping), + CreateMockRead(*write_ping), + }; + StaticSocketDataProvider data( + reads, arraysize(reads), writes, arraysize(writes)); + data.set_connect_data(connect_data); + session_deps.socket_factory->AddSocketDataProvider(&data); + + SSLSocketDataProvider ssl(SYNCHRONOUS, OK); + session_deps.socket_factory->AddSSLSocketDataProvider(&ssl); + + scoped_refptr<HttpNetworkSession> http_session( + SpdySessionDependencies::SpdyCreateSession(&session_deps)); + + static const char kStreamUrl[] = "http://www.google.com/"; + GURL url(kStreamUrl); + + const std::string kTestHost("www.google.com"); + const int kTestPort = 80; + HostPortPair test_host_port_pair(kTestHost, kTestPort); + HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct()); + + SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool()); + EXPECT_FALSE(spdy_session_pool->HasSession(pair)); + scoped_refptr<SpdySession> session = + spdy_session_pool->Get(pair, BoundNetLog()); + EXPECT_TRUE(spdy_session_pool->HasSession(pair)); + + + scoped_refptr<TransportSocketParams> transport_params( + new TransportSocketParams(test_host_port_pair, + MEDIUM, + false, + false)); + scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle); + EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(), + transport_params, MEDIUM, CompletionCallback(), + http_session->GetTransportSocketPool(), + BoundNetLog())); + EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK)); + + scoped_refptr<SpdyStream> spdy_stream1; + TestCompletionCallback callback1; + EXPECT_EQ(OK, session->CreateStream(url, + MEDIUM, + &spdy_stream1, + BoundNetLog(), + callback1.callback())); + scoped_ptr<TestSpdyStreamDelegate> delegate( + new TestSpdyStreamDelegate(callback1.callback())); + spdy_stream1->SetDelegate(delegate.get()); + + base::TimeTicks before_ping_time = base::TimeTicks::Now(); + + // Enable sending of PING. + SpdySession::set_enable_ping_based_connection_checking(true); + SpdySession::set_connection_at_risk_of_loss_seconds(0); + SpdySession::set_trailing_ping_delay_time_ms(0); + SpdySession::set_hung_interval_ms(50); + + session->SendPrefacePingIfNoneInFlight(); + + EXPECT_EQ(OK, callback1.WaitForResult()); + + session->CheckPingStatus(before_ping_time); + + EXPECT_EQ(0, session->pings_in_flight()); + EXPECT_GT(session->next_ping_id(), static_cast<uint32>(1)); + EXPECT_FALSE(session->trailing_ping_pending()); + EXPECT_FALSE(session->check_ping_status_pending()); + EXPECT_GE(session->received_data_time(), before_ping_time); + + EXPECT_FALSE(spdy_session_pool->HasSession(pair)); + + // Delete the first session. + session = NULL; +} + +TEST_F(SpdySessionSpdy3Test, FailedPing) { + SpdySessionDependencies session_deps; + session_deps.host_resolver->set_synchronous_mode(true); + + MockConnect connect_data(SYNCHRONOUS, OK); + scoped_ptr<spdy::SpdyFrame> read_ping(ConstructSpdyPing()); + MockRead reads[] = { + CreateMockRead(*read_ping), + MockRead(SYNCHRONOUS, 0, 0) // EOF + }; + scoped_ptr<spdy::SpdyFrame> write_ping(ConstructSpdyPing()); + MockRead writes[] = { + CreateMockRead(*write_ping), + }; + StaticSocketDataProvider data( + reads, arraysize(reads), writes, arraysize(writes)); + data.set_connect_data(connect_data); + session_deps.socket_factory->AddSocketDataProvider(&data); + + SSLSocketDataProvider ssl(SYNCHRONOUS, OK); + session_deps.socket_factory->AddSSLSocketDataProvider(&ssl); + + scoped_refptr<HttpNetworkSession> http_session( + SpdySessionDependencies::SpdyCreateSession(&session_deps)); + + static const char kStreamUrl[] = "http://www.gmail.com/"; + GURL url(kStreamUrl); + + const std::string kTestHost("www.gmail.com"); + const int kTestPort = 80; + HostPortPair test_host_port_pair(kTestHost, kTestPort); + HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct()); + + SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool()); + EXPECT_FALSE(spdy_session_pool->HasSession(pair)); + scoped_refptr<SpdySession> session = + spdy_session_pool->Get(pair, BoundNetLog()); + EXPECT_TRUE(spdy_session_pool->HasSession(pair)); + + scoped_refptr<TransportSocketParams> transport_params( + new TransportSocketParams(test_host_port_pair, + MEDIUM, + false, + false)); + scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle); + EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(), + transport_params, MEDIUM, CompletionCallback(), + http_session->GetTransportSocketPool(), + BoundNetLog())); + EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK)); + + scoped_refptr<SpdyStream> spdy_stream1; + TestCompletionCallback callback1; + EXPECT_EQ(OK, session->CreateStream(url, + MEDIUM, + &spdy_stream1, + BoundNetLog(), + callback1.callback())); + scoped_ptr<TestSpdyStreamDelegate> delegate( + new TestSpdyStreamDelegate(callback1.callback())); + spdy_stream1->SetDelegate(delegate.get()); + + // Enable sending of PING. + SpdySession::set_enable_ping_based_connection_checking(true); + SpdySession::set_connection_at_risk_of_loss_seconds(0); + SpdySession::set_trailing_ping_delay_time_ms(0); + SpdySession::set_hung_interval_ms(0); + + // Send a PING frame. + session->WritePingFrame(1); + EXPECT_LT(0, session->pings_in_flight()); + EXPECT_GT(session->next_ping_id(), static_cast<uint32>(1)); + EXPECT_TRUE(session->check_ping_status_pending()); + + // Assert session is not closed. + EXPECT_FALSE(session->IsClosed()); + EXPECT_LT(0u, session->num_active_streams()); + EXPECT_TRUE(spdy_session_pool->HasSession(pair)); + + // We set last time we have received any data in 1 sec less than now. + // CheckPingStatus will trigger timeout because hung interval is zero. + base::TimeTicks now = base::TimeTicks::Now(); + session->received_data_time_ = now - base::TimeDelta::FromSeconds(1); + session->CheckPingStatus(now); + + EXPECT_TRUE(session->IsClosed()); + EXPECT_EQ(0u, session->num_active_streams()); + EXPECT_EQ(0u, session->num_unclaimed_pushed_streams()); + EXPECT_FALSE(spdy_session_pool->HasSession(pair)); + + // Delete the first session. + session = NULL; +} + +class StreamReleaserCallback : public TestCompletionCallbackBase { + public: + StreamReleaserCallback(SpdySession* session, + SpdyStream* first_stream) + : session_(session), + first_stream_(first_stream), + ALLOW_THIS_IN_INITIALIZER_LIST(callback_( + base::Bind(&StreamReleaserCallback::OnComplete, + base::Unretained(this)))) { + } + + virtual ~StreamReleaserCallback() {} + + scoped_refptr<SpdyStream>* stream() { return &stream_; } + + const CompletionCallback& callback() const { return callback_; } + + private: + void OnComplete(int result) { + session_->CloseSessionOnError(ERR_FAILED, false, "On complete."); + session_ = NULL; + first_stream_->Cancel(); + first_stream_ = NULL; + stream_->Cancel(); + stream_ = NULL; + SetResult(result); + } + + scoped_refptr<SpdySession> session_; + scoped_refptr<SpdyStream> first_stream_; + scoped_refptr<SpdyStream> stream_; + CompletionCallback callback_; +}; + +// TODO(kristianm): Could also test with more sessions where some are idle, +// and more than one session to a HostPortPair. +TEST_F(SpdySessionSpdy3Test, CloseIdleSessions) { + SpdySessionDependencies session_deps; + scoped_refptr<HttpNetworkSession> http_session( + SpdySessionDependencies::SpdyCreateSession(&session_deps)); + SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool()); + + // Set up session 1 + const std::string kTestHost1("http://www.a.com"); + HostPortPair test_host_port_pair1(kTestHost1, 80); + HostPortProxyPair pair1(test_host_port_pair1, ProxyServer::Direct()); + scoped_refptr<SpdySession> session1 = + spdy_session_pool->Get(pair1, BoundNetLog()); + scoped_refptr<SpdyStream> spdy_stream1; + TestCompletionCallback callback1; + GURL url1(kTestHost1); + EXPECT_EQ(OK, session1->CreateStream(url1, + MEDIUM, /* priority, not important */ + &spdy_stream1, + BoundNetLog(), + callback1.callback())); + + // Set up session 2 + const std::string kTestHost2("http://www.b.com"); + HostPortPair test_host_port_pair2(kTestHost2, 80); + HostPortProxyPair pair2(test_host_port_pair2, ProxyServer::Direct()); + scoped_refptr<SpdySession> session2 = + spdy_session_pool->Get(pair2, BoundNetLog()); + scoped_refptr<SpdyStream> spdy_stream2; + TestCompletionCallback callback2; + GURL url2(kTestHost2); + EXPECT_EQ(OK, session2->CreateStream( + url2, MEDIUM, /* priority, not important */ + &spdy_stream2, BoundNetLog(), callback2.callback())); + + // Set up session 3 + const std::string kTestHost3("http://www.c.com"); + HostPortPair test_host_port_pair3(kTestHost3, 80); + HostPortProxyPair pair3(test_host_port_pair3, ProxyServer::Direct()); + scoped_refptr<SpdySession> session3 = + spdy_session_pool->Get(pair3, BoundNetLog()); + scoped_refptr<SpdyStream> spdy_stream3; + TestCompletionCallback callback3; + GURL url3(kTestHost3); + EXPECT_EQ(OK, session3->CreateStream( + url3, MEDIUM, /* priority, not important */ + &spdy_stream3, BoundNetLog(), callback3.callback())); + + // All sessions are active and not closed + EXPECT_TRUE(session1->is_active()); + EXPECT_FALSE(session1->IsClosed()); + EXPECT_TRUE(session2->is_active()); + EXPECT_FALSE(session2->IsClosed()); + EXPECT_TRUE(session3->is_active()); + EXPECT_FALSE(session3->IsClosed()); + + // Should not do anything, all are active + spdy_session_pool->CloseIdleSessions(); + EXPECT_TRUE(session1->is_active()); + EXPECT_FALSE(session1->IsClosed()); + EXPECT_TRUE(session2->is_active()); + EXPECT_FALSE(session2->IsClosed()); + EXPECT_TRUE(session3->is_active()); + EXPECT_FALSE(session3->IsClosed()); + + // Make sessions 1 and 3 inactive, but keep them open. + // Session 2 still open and active + session1->CloseStream(spdy_stream1->stream_id(), OK); + session3->CloseStream(spdy_stream3->stream_id(), OK); + EXPECT_FALSE(session1->is_active()); + EXPECT_FALSE(session1->IsClosed()); + EXPECT_TRUE(session2->is_active()); + EXPECT_FALSE(session2->IsClosed()); + EXPECT_FALSE(session3->is_active()); + EXPECT_FALSE(session3->IsClosed()); + + // Should close session 1 and 3, 2 should be left open + spdy_session_pool->CloseIdleSessions(); + EXPECT_FALSE(session1->is_active()); + EXPECT_TRUE(session1->IsClosed()); + EXPECT_TRUE(session2->is_active()); + EXPECT_FALSE(session2->IsClosed()); + EXPECT_FALSE(session3->is_active()); + EXPECT_TRUE(session3->IsClosed()); + + // Should not do anything + spdy_session_pool->CloseIdleSessions(); + EXPECT_TRUE(session2->is_active()); + EXPECT_FALSE(session2->IsClosed()); + + // Make 2 not active + session2->CloseStream(spdy_stream2->stream_id(), OK); + EXPECT_FALSE(session2->is_active()); + EXPECT_FALSE(session2->IsClosed()); + + // This should close session 2 + spdy_session_pool->CloseIdleSessions(); + EXPECT_FALSE(session2->is_active()); + EXPECT_TRUE(session2->IsClosed()); +} + +// Start with max concurrent streams set to 1. Request two streams. Receive a +// settings frame setting max concurrent streams to 2. Have the callback +// release the stream, which releases its reference (the last) to the session. +// Make sure nothing blows up. +// http://crbug.com/57331 +TEST_F(SpdySessionSpdy3Test, OnSettings) { + SpdySessionDependencies session_deps; + session_deps.host_resolver->set_synchronous_mode(true); + + spdy::SpdySettings new_settings; + spdy::SettingsFlagsAndId id(0); + id.set_id(spdy::SETTINGS_MAX_CONCURRENT_STREAMS); + const size_t max_concurrent_streams = 2; + new_settings.push_back(spdy::SpdySetting(id, max_concurrent_streams)); + + // Set up the socket so we read a SETTINGS frame that raises max concurrent + // streams to 2. + MockConnect connect_data(SYNCHRONOUS, OK); + scoped_ptr<spdy::SpdyFrame> settings_frame( + ConstructSpdySettings(new_settings)); + MockRead reads[] = { + CreateMockRead(*settings_frame), + MockRead(SYNCHRONOUS, 0, 0) // EOF + }; + + StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0); + data.set_connect_data(connect_data); + session_deps.socket_factory->AddSocketDataProvider(&data); + + SSLSocketDataProvider ssl(SYNCHRONOUS, OK); + session_deps.socket_factory->AddSSLSocketDataProvider(&ssl); + + scoped_refptr<HttpNetworkSession> http_session( + SpdySessionDependencies::SpdyCreateSession(&session_deps)); + + const std::string kTestHost("www.foo.com"); + const int kTestPort = 80; + HostPortPair test_host_port_pair(kTestHost, kTestPort); + HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct()); + + // Initialize the SpdySettingsStorage with 1 max concurrent streams. + SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool()); + spdy::SpdySettings old_settings; + id.set_flags(spdy::SETTINGS_FLAG_PLEASE_PERSIST); + old_settings.push_back(spdy::SpdySetting(id, 1)); + spdy_session_pool->http_server_properties()->SetSpdySettings( + test_host_port_pair, old_settings); + + // Create a session. + EXPECT_FALSE(spdy_session_pool->HasSession(pair)); + scoped_refptr<SpdySession> session = + spdy_session_pool->Get(pair, BoundNetLog()); + ASSERT_TRUE(spdy_session_pool->HasSession(pair)); + + scoped_refptr<TransportSocketParams> transport_params( + new TransportSocketParams(test_host_port_pair, + MEDIUM, + false, + false)); + scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle); + EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(), + transport_params, MEDIUM, CompletionCallback(), + http_session->GetTransportSocketPool(), + BoundNetLog())); + EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK)); + + // Create 2 streams. First will succeed. Second will be pending. + scoped_refptr<SpdyStream> spdy_stream1; + TestCompletionCallback callback1; + GURL url("http://www.google.com"); + EXPECT_EQ(OK, + session->CreateStream(url, + MEDIUM, /* priority, not important */ + &spdy_stream1, + BoundNetLog(), + callback1.callback())); + + StreamReleaserCallback stream_releaser(session, spdy_stream1); + + ASSERT_EQ(ERR_IO_PENDING, + session->CreateStream(url, + MEDIUM, /* priority, not important */ + stream_releaser.stream(), + BoundNetLog(), + stream_releaser.callback())); + + // Make sure |stream_releaser| holds the last refs. + session = NULL; + spdy_stream1 = NULL; + + EXPECT_EQ(OK, stream_releaser.WaitForResult()); +} + +// Start with max concurrent streams set to 1. Request two streams. When the +// first completes, have the callback close itself, which should trigger the +// second stream creation. Then cancel that one immediately. Don't crash. +// http://crbug.com/63532 +TEST_F(SpdySessionSpdy3Test, CancelPendingCreateStream) { + SpdySessionDependencies session_deps; + session_deps.host_resolver->set_synchronous_mode(true); + + MockRead reads[] = { + MockRead(SYNCHRONOUS, ERR_IO_PENDING) // Stall forever. + }; + + StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0); + MockConnect connect_data(SYNCHRONOUS, OK); + + data.set_connect_data(connect_data); + session_deps.socket_factory->AddSocketDataProvider(&data); + + SSLSocketDataProvider ssl(SYNCHRONOUS, OK); + session_deps.socket_factory->AddSSLSocketDataProvider(&ssl); + + scoped_refptr<HttpNetworkSession> http_session( + SpdySessionDependencies::SpdyCreateSession(&session_deps)); + + const std::string kTestHost("www.foo.com"); + const int kTestPort = 80; + HostPortPair test_host_port_pair(kTestHost, kTestPort); + HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct()); + + // Initialize the SpdySettingsStorage with 1 max concurrent streams. + SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool()); + spdy::SpdySettings settings; + spdy::SettingsFlagsAndId id(0); + id.set_id(spdy::SETTINGS_MAX_CONCURRENT_STREAMS); + id.set_flags(spdy::SETTINGS_FLAG_PLEASE_PERSIST); + settings.push_back(spdy::SpdySetting(id, 1)); + spdy_session_pool->http_server_properties()->SetSpdySettings( + test_host_port_pair, settings); + + // Create a session. + EXPECT_FALSE(spdy_session_pool->HasSession(pair)); + scoped_refptr<SpdySession> session = + spdy_session_pool->Get(pair, BoundNetLog()); + ASSERT_TRUE(spdy_session_pool->HasSession(pair)); + + scoped_refptr<TransportSocketParams> transport_params( + new TransportSocketParams(test_host_port_pair, + MEDIUM, + false, + false)); + scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle); + EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(), + transport_params, MEDIUM, CompletionCallback(), + http_session->GetTransportSocketPool(), + BoundNetLog())); + EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK)); + + // Use scoped_ptr to let us invalidate the memory when we want to, to trigger + // a valgrind error if the callback is invoked when it's not supposed to be. + scoped_ptr<TestCompletionCallback> callback(new TestCompletionCallback); + + // Create 2 streams. First will succeed. Second will be pending. + scoped_refptr<SpdyStream> spdy_stream1; + GURL url("http://www.google.com"); + ASSERT_EQ(OK, + session->CreateStream(url, + MEDIUM, /* priority, not important */ + &spdy_stream1, + BoundNetLog(), + callback->callback())); + + scoped_refptr<SpdyStream> spdy_stream2; + ASSERT_EQ(ERR_IO_PENDING, + session->CreateStream(url, + MEDIUM, /* priority, not important */ + &spdy_stream2, + BoundNetLog(), + callback->callback())); + + // Release the first one, this will allow the second to be created. + spdy_stream1->Cancel(); + spdy_stream1 = NULL; + + session->CancelPendingCreateStreams(&spdy_stream2); + callback.reset(); + + // Should not crash when running the pending callback. + MessageLoop::current()->RunAllPending(); +} + +TEST_F(SpdySessionSpdy3Test, SendSettingsOnNewSession) { + SpdySessionDependencies session_deps; + session_deps.host_resolver->set_synchronous_mode(true); + + MockRead reads[] = { + MockRead(SYNCHRONOUS, ERR_IO_PENDING) // Stall forever. + }; + + // Create the bogus setting that we want to verify is sent out. + // Note that it will be marked as SETTINGS_FLAG_PERSISTED when sent out. But + // to set it into the SpdySettingsStorage, we need to mark as + // SETTINGS_FLAG_PLEASE_PERSIST. + spdy::SpdySettings settings; + const uint32 kBogusSettingId = 0xABAB; + const uint32 kBogusSettingValue = 0xCDCD; + spdy::SettingsFlagsAndId id(0); + id.set_id(kBogusSettingId); + id.set_flags(spdy::SETTINGS_FLAG_PERSISTED); + settings.push_back(spdy::SpdySetting(id, kBogusSettingValue)); + MockConnect connect_data(SYNCHRONOUS, OK); + scoped_ptr<spdy::SpdyFrame> settings_frame( + ConstructSpdySettings(settings)); + MockWrite writes[] = { + CreateMockWrite(*settings_frame), + }; + + StaticSocketDataProvider data( + reads, arraysize(reads), writes, arraysize(writes)); + data.set_connect_data(connect_data); + session_deps.socket_factory->AddSocketDataProvider(&data); + + SSLSocketDataProvider ssl(SYNCHRONOUS, OK); + session_deps.socket_factory->AddSSLSocketDataProvider(&ssl); + + scoped_refptr<HttpNetworkSession> http_session( + SpdySessionDependencies::SpdyCreateSession(&session_deps)); + + const std::string kTestHost("www.foo.com"); + const int kTestPort = 80; + HostPortPair test_host_port_pair(kTestHost, kTestPort); + HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct()); + + id.set_flags(spdy::SETTINGS_FLAG_PLEASE_PERSIST); + settings.clear(); + settings.push_back(spdy::SpdySetting(id, kBogusSettingValue)); + SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool()); + spdy_session_pool->http_server_properties()->SetSpdySettings( + test_host_port_pair, settings); + EXPECT_FALSE(spdy_session_pool->HasSession(pair)); + scoped_refptr<SpdySession> session = + spdy_session_pool->Get(pair, BoundNetLog()); + EXPECT_TRUE(spdy_session_pool->HasSession(pair)); + + scoped_refptr<TransportSocketParams> transport_params( + new TransportSocketParams(test_host_port_pair, + MEDIUM, + false, + false)); + scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle); + EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(), + transport_params, MEDIUM, CompletionCallback(), + http_session->GetTransportSocketPool(), + BoundNetLog())); + EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK)); + MessageLoop::current()->RunAllPending(); + EXPECT_TRUE(data.at_write_eof()); +} + +namespace { +// This test has two variants, one for each style of closing the connection. +// If |clean_via_close_current_sessions| is false, the sessions are closed +// manually, calling SpdySessionPool::Remove() directly. If it is true, +// sessions are closed with SpdySessionPool::CloseCurrentSessions(). +void IPPoolingTest(bool clean_via_close_current_sessions) { + const int kTestPort = 80; + struct TestHosts { + std::string name; + std::string iplist; + HostPortProxyPair pair; + AddressList addresses; + } test_hosts[] = { + { "www.foo.com", "192.0.2.33,192.168.0.1,192.168.0.5" }, + { "images.foo.com", "192.168.0.2,192.168.0.3,192.168.0.5,192.0.2.33" }, + { "js.foo.com", "192.168.0.4,192.168.0.3" }, + }; + + SpdySessionDependencies session_deps; + session_deps.host_resolver->set_synchronous_mode(true); + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_hosts); i++) { + session_deps.host_resolver->rules()->AddIPLiteralRule(test_hosts[i].name, + test_hosts[i].iplist, ""); + + // This test requires that the HostResolver cache be populated. Normal + // code would have done this already, but we do it manually. + HostResolver::RequestInfo info(HostPortPair(test_hosts[i].name, kTestPort)); + session_deps.host_resolver->Resolve( + info, &test_hosts[i].addresses, CompletionCallback(), NULL, + BoundNetLog()); + + // Setup a HostPortProxyPair + test_hosts[i].pair = HostPortProxyPair( + HostPortPair(test_hosts[i].name, kTestPort), ProxyServer::Direct()); + } + + MockConnect connect_data(SYNCHRONOUS, OK); + MockRead reads[] = { + MockRead(SYNCHRONOUS, ERR_IO_PENDING) // Stall forever. + }; + + StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0); + data.set_connect_data(connect_data); + session_deps.socket_factory->AddSocketDataProvider(&data); + + SSLSocketDataProvider ssl(SYNCHRONOUS, OK); + session_deps.socket_factory->AddSSLSocketDataProvider(&ssl); + + scoped_refptr<HttpNetworkSession> http_session( + SpdySessionDependencies::SpdyCreateSession(&session_deps)); + + // Setup the first session to the first host. + SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool()); + EXPECT_FALSE(spdy_session_pool->HasSession(test_hosts[0].pair)); + scoped_refptr<SpdySession> session = + spdy_session_pool->Get(test_hosts[0].pair, BoundNetLog()); + EXPECT_TRUE(spdy_session_pool->HasSession(test_hosts[0].pair)); + + HostPortPair test_host_port_pair(test_hosts[0].name, kTestPort); + scoped_refptr<TransportSocketParams> transport_params( + new TransportSocketParams(test_host_port_pair, + MEDIUM, + false, + false)); + scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle); + EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(), + transport_params, MEDIUM, CompletionCallback(), + http_session->GetTransportSocketPool(), + BoundNetLog())); + EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK)); + + // TODO(rtenneti): MockClientSocket::GetPeerAddress return's 0 as the port + // number. Fix it to return port 80 and then use GetPeerAddress to AddAlias. + const addrinfo* address = test_hosts[0].addresses.head(); + SpdySessionPoolPeer pool_peer(spdy_session_pool); + pool_peer.AddAlias(address, test_hosts[0].pair); + + // Flush the SpdySession::OnReadComplete() task. + MessageLoop::current()->RunAllPending(); + + // The third host has no overlap with the first, so it can't pool IPs. + EXPECT_FALSE(spdy_session_pool->HasSession(test_hosts[2].pair)); + + // The second host overlaps with the first, and should IP pool. + EXPECT_TRUE(spdy_session_pool->HasSession(test_hosts[1].pair)); + + // Verify that the second host, through a proxy, won't share the IP. + HostPortProxyPair proxy_pair(test_hosts[1].pair.first, + ProxyServer::FromPacString("HTTP http://proxy.foo.com/")); + EXPECT_FALSE(spdy_session_pool->HasSession(proxy_pair)); + + // Overlap between 2 and 3 does is not transitive to 1. + EXPECT_FALSE(spdy_session_pool->HasSession(test_hosts[2].pair)); + + // Create a new session to host 2. + scoped_refptr<SpdySession> session2 = + spdy_session_pool->Get(test_hosts[2].pair, BoundNetLog()); + + // Verify that we have sessions for everything. + EXPECT_TRUE(spdy_session_pool->HasSession(test_hosts[0].pair)); + EXPECT_TRUE(spdy_session_pool->HasSession(test_hosts[1].pair)); + EXPECT_TRUE(spdy_session_pool->HasSession(test_hosts[2].pair)); + + // Cleanup the sessions. + if (!clean_via_close_current_sessions) { + spdy_session_pool->Remove(session); + session = NULL; + spdy_session_pool->Remove(session2); + session2 = NULL; + } else { + spdy_session_pool->CloseCurrentSessions(); + } + + // Verify that the map is all cleaned up. + EXPECT_FALSE(spdy_session_pool->HasSession(test_hosts[0].pair)); + EXPECT_FALSE(spdy_session_pool->HasSession(test_hosts[1].pair)); + EXPECT_FALSE(spdy_session_pool->HasSession(test_hosts[2].pair)); +} + +} // namespace + +TEST_F(SpdySessionSpdy3Test, IPPooling) { + IPPoolingTest(false); +} + +TEST_F(SpdySessionSpdy3Test, IPPoolingCloseCurrentSessions) { + IPPoolingTest(true); +} + +TEST_F(SpdySessionSpdy3Test, ClearSettingsStorage) { + SpdySettingsStorage settings_storage; + const std::string kTestHost("www.foo.com"); + const int kTestPort = 80; + HostPortPair test_host_port_pair(kTestHost, kTestPort); + spdy::SpdySettings test_settings; + spdy::SettingsFlagsAndId id(0); + id.set_id(spdy::SETTINGS_MAX_CONCURRENT_STREAMS); + id.set_flags(spdy::SETTINGS_FLAG_PLEASE_PERSIST); + const size_t max_concurrent_streams = 2; + test_settings.push_back(spdy::SpdySetting(id, max_concurrent_streams)); + + settings_storage.Set(test_host_port_pair, test_settings); + EXPECT_NE(0u, settings_storage.Get(test_host_port_pair).size()); + settings_storage.Clear(); + EXPECT_EQ(0u, settings_storage.Get(test_host_port_pair).size()); +} + +TEST_F(SpdySessionSpdy3Test, ClearSettingsStorageOnIPAddressChanged) { + const std::string kTestHost("www.foo.com"); + const int kTestPort = 80; + HostPortPair test_host_port_pair(kTestHost, kTestPort); + + SpdySessionDependencies session_deps; + scoped_refptr<HttpNetworkSession> http_session( + SpdySessionDependencies::SpdyCreateSession(&session_deps)); + SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool()); + + HttpServerProperties* test_http_server_properties = + spdy_session_pool->http_server_properties(); + spdy::SettingsFlagsAndId id(0); + id.set_id(spdy::SETTINGS_MAX_CONCURRENT_STREAMS); + id.set_flags(spdy::SETTINGS_FLAG_PLEASE_PERSIST); + const size_t max_concurrent_streams = 2; + spdy::SpdySettings test_settings; + test_settings.push_back(spdy::SpdySetting(id, max_concurrent_streams)); + + test_http_server_properties->SetSpdySettings(test_host_port_pair, + test_settings); + EXPECT_NE(0u, test_http_server_properties->GetSpdySettings( + test_host_port_pair).size()); + spdy_session_pool->OnIPAddressChanged(); + EXPECT_EQ(0u, test_http_server_properties->GetSpdySettings( + test_host_port_pair).size()); +} + +TEST_F(SpdySessionSpdy3Test, NeedsCredentials) { + SpdySessionDependencies session_deps; + + MockConnect connect_data(SYNCHRONOUS, OK); + MockRead reads[] = { + MockRead(SYNCHRONOUS, ERR_IO_PENDING) // Stall forever. + }; + StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0); + data.set_connect_data(connect_data); + session_deps.socket_factory->AddSocketDataProvider(&data); + + SSLSocketDataProvider ssl(SYNCHRONOUS, OK); + ssl.origin_bound_cert_type = CLIENT_CERT_ECDSA_SIGN; + ssl.protocol_negotiated = SSLClientSocket::kProtoSPDY3; + session_deps.socket_factory->AddSSLSocketDataProvider(&ssl); + + scoped_refptr<HttpNetworkSession> http_session( + SpdySessionDependencies::SpdyCreateSession(&session_deps)); + + const std::string kTestHost("www.foo.com"); + const int kTestPort = 80; + HostPortPair test_host_port_pair(kTestHost, kTestPort); + HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct()); + + SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool()); + EXPECT_FALSE(spdy_session_pool->HasSession(pair)); + scoped_refptr<SpdySession> session = + spdy_session_pool->Get(pair, BoundNetLog()); + EXPECT_TRUE(spdy_session_pool->HasSession(pair)); + + SSLConfig ssl_config; + scoped_refptr<TransportSocketParams> transport_params( + new TransportSocketParams(test_host_port_pair, + MEDIUM, + false, + false)); + scoped_refptr<SOCKSSocketParams> socks_params; + scoped_refptr<HttpProxySocketParams> http_proxy_params; + scoped_refptr<SSLSocketParams> ssl_params( + new SSLSocketParams(transport_params, + socks_params, + http_proxy_params, + ProxyServer::SCHEME_DIRECT, + test_host_port_pair, + ssl_config, + 0, + false, + false)); + scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle); + EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(), + ssl_params, MEDIUM, CompletionCallback(), + http_session->GetSSLSocketPool(), + BoundNetLog())); + + EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), true, OK)); + + EXPECT_FALSE(session->NeedsCredentials(test_host_port_pair)); + const std::string kTestHost2("www.bar.com"); + HostPortPair test_host_port_pair2(kTestHost2, kTestPort); + EXPECT_TRUE(session->NeedsCredentials(test_host_port_pair2)); + + // Flush the SpdySession::OnReadComplete() task. + MessageLoop::current()->RunAllPending(); + + spdy_session_pool->Remove(session); + EXPECT_FALSE(spdy_session_pool->HasSession(pair)); +} + +TEST_F(SpdySessionSpdy3Test, SendCredentials) { + SpdySessionDependencies session_deps; + + MockConnect connect_data(SYNCHRONOUS, OK); + MockRead reads[] = { + MockRead(SYNCHRONOUS, ERR_IO_PENDING) // Stall forever. + }; + spdy::SpdySettings settings; + scoped_ptr<spdy::SpdyFrame> settings_frame( + ConstructSpdySettings(settings)); + MockWrite writes[] = { + CreateMockWrite(*settings_frame), + }; + StaticSocketDataProvider data(reads, arraysize(reads), + writes, arraysize(writes)); + data.set_connect_data(connect_data); + session_deps.socket_factory->AddSocketDataProvider(&data); + + SSLSocketDataProvider ssl(SYNCHRONOUS, OK); + ssl.origin_bound_cert_type = CLIENT_CERT_ECDSA_SIGN; + ssl.protocol_negotiated = SSLClientSocket::kProtoSPDY3; + session_deps.socket_factory->AddSSLSocketDataProvider(&ssl); + + scoped_refptr<HttpNetworkSession> http_session( + SpdySessionDependencies::SpdyCreateSession(&session_deps)); + + const std::string kTestHost("www.foo.com"); + const int kTestPort = 80; + HostPortPair test_host_port_pair(kTestHost, kTestPort); + HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct()); + + SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool()); + EXPECT_FALSE(spdy_session_pool->HasSession(pair)); + scoped_refptr<SpdySession> session = + spdy_session_pool->Get(pair, BoundNetLog()); + EXPECT_TRUE(spdy_session_pool->HasSession(pair)); + + SSLConfig ssl_config; + scoped_refptr<TransportSocketParams> transport_params( + new TransportSocketParams(test_host_port_pair, + MEDIUM, + false, + false)); + scoped_refptr<SOCKSSocketParams> socks_params; + scoped_refptr<HttpProxySocketParams> http_proxy_params; + scoped_refptr<SSLSocketParams> ssl_params( + new SSLSocketParams(transport_params, + socks_params, + http_proxy_params, + ProxyServer::SCHEME_DIRECT, + test_host_port_pair, + ssl_config, + 0, + false, + false)); + scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle); + EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(), + ssl_params, MEDIUM, CompletionCallback(), + http_session->GetSSLSocketPool(), + BoundNetLog())); + + EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), true, OK)); + + EXPECT_FALSE(session->NeedsCredentials(test_host_port_pair)); + const std::string kTestHost2("www.bar.com"); + HostPortPair test_host_port_pair2(kTestHost2, kTestPort); + EXPECT_TRUE(session->NeedsCredentials(test_host_port_pair2)); + + // Flush the SpdySession::OnReadComplete() task. + MessageLoop::current()->RunAllPending(); + + spdy_session_pool->Remove(session); + EXPECT_FALSE(spdy_session_pool->HasSession(pair)); +} + +TEST_F(SpdySessionSpdy3Test, CloseSessionOnError) { + SpdySessionDependencies session_deps; + session_deps.host_resolver->set_synchronous_mode(true); + + MockConnect connect_data(SYNCHRONOUS, OK); + scoped_ptr<spdy::SpdyFrame> goaway(ConstructSpdyGoAway()); + MockRead reads[] = { + CreateMockRead(*goaway), + MockRead(SYNCHRONOUS, 0, 0) // EOF + }; + + net::CapturingBoundNetLog log(net::CapturingNetLog::kUnbounded); + + StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0); + data.set_connect_data(connect_data); + session_deps.socket_factory->AddSocketDataProvider(&data); + + SSLSocketDataProvider ssl(SYNCHRONOUS, OK); + session_deps.socket_factory->AddSSLSocketDataProvider(&ssl); + + scoped_refptr<HttpNetworkSession> http_session( + SpdySessionDependencies::SpdyCreateSession(&session_deps)); + + const std::string kTestHost("www.foo.com"); + const int kTestPort = 80; + HostPortPair test_host_port_pair(kTestHost, kTestPort); + HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct()); + + SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool()); + EXPECT_FALSE(spdy_session_pool->HasSession(pair)); + scoped_refptr<SpdySession> session = + spdy_session_pool->Get(pair, log.bound()); + EXPECT_TRUE(spdy_session_pool->HasSession(pair)); + + scoped_refptr<TransportSocketParams> transport_params( + new TransportSocketParams(test_host_port_pair, + MEDIUM, + false, + false)); + scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle); + EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(), + transport_params, MEDIUM, CompletionCallback(), + http_session->GetTransportSocketPool(), + log.bound())); + EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK)); + + // Flush the SpdySession::OnReadComplete() task. + MessageLoop::current()->RunAllPending(); + + EXPECT_FALSE(spdy_session_pool->HasSession(pair)); + + // Check that the NetLog was filled reasonably. + net::CapturingNetLog::EntryList entries; + log.GetEntries(&entries); + EXPECT_LT(0u, entries.size()); + + // Check that we logged SPDY_SESSION_CLOSE correctly. + int pos = net::ExpectLogContainsSomewhere( + entries, 0, + net::NetLog::TYPE_SPDY_SESSION_CLOSE, + net::NetLog::PHASE_NONE); + + CapturingNetLog::Entry entry = entries[pos]; + NetLogSpdySessionCloseParameter* request_params = + static_cast<NetLogSpdySessionCloseParameter*>( + entry.extra_parameters.get()); + EXPECT_EQ(ERR_CONNECTION_CLOSED, request_params->status()); +} + +} // namespace net diff --git a/net/spdy/spdy_stream_unittest.cc b/net/spdy/spdy_stream_spdy2_unittest.cc index f29b99e..b59324e 100644 --- a/net/spdy/spdy_stream_unittest.cc +++ b/net/spdy/spdy_stream_spdy2_unittest.cc @@ -8,9 +8,11 @@ #include "net/spdy/spdy_stream.h" #include "net/spdy/spdy_http_utils.h" #include "net/spdy/spdy_session.h" -#include "net/spdy/spdy_test_util.h" +#include "net/spdy/spdy_test_util_spdy2.h" #include "testing/gtest/include/gtest/gtest.h" +using namespace net::test_spdy2; + // TODO(ukai): factor out common part with spdy_http_stream_unittest.cc // namespace net { @@ -96,9 +98,9 @@ spdy::SpdyFrame* ConstructSpdyBodyFrame(const char* data, int length) { } // anonymous namespace -class SpdyStreamTest : public testing::Test { +class SpdyStreamSpdy2Test : public testing::Test { protected: - SpdyStreamTest() { + SpdyStreamSpdy2Test() { } scoped_refptr<SpdySession> CreateSpdySession() { @@ -117,7 +119,7 @@ class SpdyStreamTest : public testing::Test { scoped_refptr<HttpNetworkSession> session_; }; -TEST_F(SpdyStreamTest, SendDataAfterOpen) { +TEST_F(SpdyStreamSpdy2Test, SendDataAfterOpen) { SpdySessionDependencies session_deps; session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps); @@ -232,7 +234,7 @@ TEST_F(SpdyStreamTest, SendDataAfterOpen) { EXPECT_TRUE(delegate->closed()); } -TEST_F(SpdyStreamTest, PushedStream) { +TEST_F(SpdyStreamSpdy2Test, PushedStream) { const char kStreamUrl[] = "http://www.google.com/"; SpdySessionDependencies session_deps; @@ -266,7 +268,7 @@ TEST_F(SpdyStreamTest, PushedStream) { EXPECT_EQ(kStreamUrl, stream->GetUrl().spec()); } -TEST_F(SpdyStreamTest, StreamError) { +TEST_F(SpdyStreamSpdy2Test, StreamError) { SpdySessionDependencies session_deps; session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps); diff --git a/net/spdy/spdy_stream_spdy3_unittest.cc b/net/spdy/spdy_stream_spdy3_unittest.cc new file mode 100644 index 0000000..98e154f --- /dev/null +++ b/net/spdy/spdy_stream_spdy3_unittest.cc @@ -0,0 +1,407 @@ +// Copyright (c) 2012 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 "base/memory/ref_counted.h" +#include "net/base/completion_callback.h" +#include "net/base/net_log_unittest.h" +#include "net/spdy/spdy_stream.h" +#include "net/spdy/spdy_http_utils.h" +#include "net/spdy/spdy_session.h" +#include "net/spdy/spdy_test_util_spdy3.h" +#include "testing/gtest/include/gtest/gtest.h" + +using namespace net::test_spdy3; + +// TODO(ukai): factor out common part with spdy_http_stream_unittest.cc +// +namespace net { + +namespace { + +class TestSpdyStreamDelegate : public SpdyStream::Delegate { + public: + TestSpdyStreamDelegate(SpdyStream* stream, + IOBufferWithSize* buf, + const CompletionCallback& callback) + : stream_(stream), + buf_(buf), + callback_(callback), + send_headers_completed_(false), + response_(new spdy::SpdyHeaderBlock), + data_sent_(0), + closed_(false) {} + virtual ~TestSpdyStreamDelegate() {} + + virtual bool OnSendHeadersComplete(int status) { + send_headers_completed_ = true; + return true; + } + virtual int OnSendBody() { + ADD_FAILURE() << "OnSendBody should not be called"; + return ERR_UNEXPECTED; + } + virtual int OnSendBodyComplete(int /*status*/, bool* /*eof*/) { + ADD_FAILURE() << "OnSendBodyComplete should not be called"; + return ERR_UNEXPECTED; + } + + virtual int OnResponseReceived(const spdy::SpdyHeaderBlock& response, + base::Time response_time, + int status) { + EXPECT_TRUE(send_headers_completed_); + *response_ = response; + if (buf_) { + EXPECT_EQ(ERR_IO_PENDING, + stream_->WriteStreamData(buf_.get(), buf_->size(), + spdy::DATA_FLAG_NONE)); + } + return status; + } + virtual void OnDataReceived(const char* buffer, int bytes) { + received_data_ += std::string(buffer, bytes); + } + virtual void OnDataSent(int length) { + data_sent_ += length; + } + virtual void OnClose(int status) { + closed_ = true; + CompletionCallback callback = callback_; + callback_.Reset(); + callback.Run(OK); + } + virtual void set_chunk_callback(net::ChunkCallback *) {} + + bool send_headers_completed() const { return send_headers_completed_; } + const linked_ptr<spdy::SpdyHeaderBlock>& response() const { + return response_; + } + const std::string& received_data() const { return received_data_; } + int data_sent() const { return data_sent_; } + bool closed() const { return closed_; } + + private: + SpdyStream* stream_; + scoped_refptr<IOBufferWithSize> buf_; + CompletionCallback callback_; + bool send_headers_completed_; + linked_ptr<spdy::SpdyHeaderBlock> response_; + std::string received_data_; + int data_sent_; + bool closed_; +}; + +spdy::SpdyFrame* ConstructSpdyBodyFrame(const char* data, int length) { + spdy::SpdyFramer framer; + return framer.CreateDataFrame(1, data, length, spdy::DATA_FLAG_NONE); +} + +} // anonymous namespace + +class SpdyStreamSpdy3Test : public testing::Test { + protected: + SpdyStreamSpdy3Test() { + } + + scoped_refptr<SpdySession> CreateSpdySession() { + spdy::SpdyFramer::set_enable_compression_default(false); + HostPortPair host_port_pair("www.google.com", 80); + HostPortProxyPair pair(host_port_pair, ProxyServer::Direct()); + scoped_refptr<SpdySession> session( + session_->spdy_session_pool()->Get(pair, BoundNetLog())); + return session; + } + + virtual void TearDown() { + MessageLoop::current()->RunAllPending(); + } + + scoped_refptr<HttpNetworkSession> session_; +}; + +TEST_F(SpdyStreamSpdy3Test, SendDataAfterOpen) { + SpdySessionDependencies session_deps; + + session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps); + SpdySessionPoolPeer pool_peer_(session_->spdy_session_pool()); + + const SpdyHeaderInfo kSynStartHeader = { + spdy::SYN_STREAM, + 1, + 0, + net::ConvertRequestPriorityToSpdyPriority(LOWEST), + spdy::CONTROL_FLAG_NONE, + false, + spdy::INVALID, + NULL, + 0, + spdy::DATA_FLAG_NONE + }; + static const char* const kGetHeaders[] = { + "method", + "GET", + "scheme", + "http", + "host", + "www.google.com", + "path", + "/", + "version", + "HTTP/1.1", + }; + scoped_ptr<spdy::SpdyFrame> req( + ConstructSpdyPacket( + kSynStartHeader, NULL, 0, kGetHeaders, arraysize(kGetHeaders) / 2)); + scoped_ptr<spdy::SpdyFrame> msg( + ConstructSpdyBodyFrame("\0hello!\xff", 8)); + MockWrite writes[] = { + CreateMockWrite(*req), + CreateMockWrite(*msg), + }; + writes[0].sequence_number = 0; + writes[1].sequence_number = 2; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> echo( + ConstructSpdyBodyFrame("\0hello!\xff", 8)); + MockRead reads[] = { + CreateMockRead(*resp), + CreateMockRead(*echo), + MockRead(ASYNC, 0, 0), // EOF + }; + reads[0].sequence_number = 1; + reads[1].sequence_number = 3; + reads[2].sequence_number = 4; + + scoped_ptr<OrderedSocketData> data( + new OrderedSocketData(reads, arraysize(reads), + writes, arraysize(writes))); + MockConnect connect_data(SYNCHRONOUS, OK); + data->set_connect_data(connect_data); + + session_deps.socket_factory->AddSocketDataProvider(data.get()); + SpdySession::SetSSLMode(false); + + scoped_refptr<SpdySession> session(CreateSpdySession()); + const char* kStreamUrl = "http://www.google.com/"; + GURL url(kStreamUrl); + + HostPortPair host_port_pair("www.google.com", 80); + scoped_refptr<TransportSocketParams> transport_params( + new TransportSocketParams(host_port_pair, LOWEST, false, false)); + + scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle); + EXPECT_EQ(OK, connection->Init(host_port_pair.ToString(), transport_params, + LOWEST, CompletionCallback(), + session_->GetTransportSocketPool(), + BoundNetLog())); + session->InitializeWithSocket(connection.release(), false, OK); + + scoped_refptr<SpdyStream> stream; + ASSERT_EQ( + OK, + session->CreateStream(url, LOWEST, &stream, BoundNetLog(), + CompletionCallback())); + scoped_refptr<IOBufferWithSize> buf(new IOBufferWithSize(8)); + memcpy(buf->data(), "\0hello!\xff", 8); + TestCompletionCallback callback; + + scoped_ptr<TestSpdyStreamDelegate> delegate( + new TestSpdyStreamDelegate(stream.get(), buf.get(), callback.callback())); + stream->SetDelegate(delegate.get()); + + EXPECT_FALSE(stream->HasUrl()); + + linked_ptr<spdy::SpdyHeaderBlock> headers(new spdy::SpdyHeaderBlock); + (*headers)["method"] = "GET"; + (*headers)["scheme"] = url.scheme(); + (*headers)["host"] = url.host(); + (*headers)["path"] = url.path(); + (*headers)["version"] = "HTTP/1.1"; + stream->set_spdy_headers(headers); + EXPECT_TRUE(stream->HasUrl()); + EXPECT_EQ(kStreamUrl, stream->GetUrl().spec()); + + EXPECT_EQ(ERR_IO_PENDING, stream->SendRequest(true)); + + EXPECT_EQ(OK, callback.WaitForResult()); + + EXPECT_TRUE(delegate->send_headers_completed()); + EXPECT_EQ("200", (*delegate->response())["status"]); + EXPECT_EQ("HTTP/1.1", (*delegate->response())["version"]); + EXPECT_EQ(std::string("\0hello!\xff", 8), delegate->received_data()); + EXPECT_EQ(8, delegate->data_sent()); + EXPECT_TRUE(delegate->closed()); +} + +TEST_F(SpdyStreamSpdy3Test, PushedStream) { + const char kStreamUrl[] = "http://www.google.com/"; + + SpdySessionDependencies session_deps; + session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps); + SpdySessionPoolPeer pool_peer_(session_->spdy_session_pool()); + scoped_refptr<SpdySession> spdy_session(CreateSpdySession()); + BoundNetLog net_log; + + // Conjure up a stream. + scoped_refptr<SpdyStream> stream = new SpdyStream(spdy_session, + 2, + true, + net_log); + EXPECT_FALSE(stream->response_received()); + EXPECT_FALSE(stream->HasUrl()); + + // Set a couple of headers. + spdy::SpdyHeaderBlock response; + response["url"] = kStreamUrl; + stream->OnResponseReceived(response); + + // Send some basic headers. + spdy::SpdyHeaderBlock headers; + response["status"] = "200"; + response["version"] = "OK"; + stream->OnHeaders(headers); + + stream->set_response_received(); + EXPECT_TRUE(stream->response_received()); + EXPECT_TRUE(stream->HasUrl()); + EXPECT_EQ(kStreamUrl, stream->GetUrl().spec()); +} + +TEST_F(SpdyStreamSpdy3Test, StreamError) { + SpdySessionDependencies session_deps; + + session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps); + SpdySessionPoolPeer pool_peer_(session_->spdy_session_pool()); + + const SpdyHeaderInfo kSynStartHeader = { + spdy::SYN_STREAM, + 1, + 0, + net::ConvertRequestPriorityToSpdyPriority(LOWEST), + spdy::CONTROL_FLAG_NONE, + false, + spdy::INVALID, + NULL, + 0, + spdy::DATA_FLAG_NONE + }; + static const char* const kGetHeaders[] = { + "method", + "GET", + "scheme", + "http", + "host", + "www.google.com", + "path", + "/", + "version", + "HTTP/1.1", + }; + scoped_ptr<spdy::SpdyFrame> req( + ConstructSpdyPacket( + kSynStartHeader, NULL, 0, kGetHeaders, arraysize(kGetHeaders) / 2)); + scoped_ptr<spdy::SpdyFrame> msg( + ConstructSpdyBodyFrame("\0hello!\xff", 8)); + MockWrite writes[] = { + CreateMockWrite(*req), + CreateMockWrite(*msg), + }; + writes[0].sequence_number = 0; + writes[1].sequence_number = 2; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> echo( + ConstructSpdyBodyFrame("\0hello!\xff", 8)); + MockRead reads[] = { + CreateMockRead(*resp), + CreateMockRead(*echo), + MockRead(ASYNC, 0, 0), // EOF + }; + reads[0].sequence_number = 1; + reads[1].sequence_number = 3; + reads[2].sequence_number = 4; + + net::CapturingBoundNetLog log(net::CapturingNetLog::kUnbounded); + + scoped_ptr<OrderedSocketData> data( + new OrderedSocketData(reads, arraysize(reads), + writes, arraysize(writes))); + MockConnect connect_data(SYNCHRONOUS, OK); + data->set_connect_data(connect_data); + + session_deps.socket_factory->AddSocketDataProvider(data.get()); + SpdySession::SetSSLMode(false); + + scoped_refptr<SpdySession> session(CreateSpdySession()); + const char* kStreamUrl = "http://www.google.com/"; + GURL url(kStreamUrl); + + HostPortPair host_port_pair("www.google.com", 80); + scoped_refptr<TransportSocketParams> transport_params( + new TransportSocketParams(host_port_pair, LOWEST, false, false)); + + scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle); + EXPECT_EQ(OK, connection->Init(host_port_pair.ToString(), transport_params, + LOWEST, CompletionCallback(), + session_->GetTransportSocketPool(), + log.bound())); + session->InitializeWithSocket(connection.release(), false, OK); + + scoped_refptr<SpdyStream> stream; + ASSERT_EQ( + OK, + session->CreateStream(url, LOWEST, &stream, log.bound(), + CompletionCallback())); + scoped_refptr<IOBufferWithSize> buf(new IOBufferWithSize(8)); + memcpy(buf->data(), "\0hello!\xff", 8); + TestCompletionCallback callback; + + scoped_ptr<TestSpdyStreamDelegate> delegate( + new TestSpdyStreamDelegate(stream.get(), buf.get(), callback.callback())); + stream->SetDelegate(delegate.get()); + + EXPECT_FALSE(stream->HasUrl()); + + linked_ptr<spdy::SpdyHeaderBlock> headers(new spdy::SpdyHeaderBlock); + (*headers)["method"] = "GET"; + (*headers)["scheme"] = url.scheme(); + (*headers)["host"] = url.host(); + (*headers)["path"] = url.path(); + (*headers)["version"] = "HTTP/1.1"; + stream->set_spdy_headers(headers); + EXPECT_TRUE(stream->HasUrl()); + EXPECT_EQ(kStreamUrl, stream->GetUrl().spec()); + + EXPECT_EQ(ERR_IO_PENDING, stream->SendRequest(true)); + + const spdy::SpdyStreamId stream_id = stream->stream_id(); + + EXPECT_EQ(OK, callback.WaitForResult()); + + EXPECT_TRUE(delegate->send_headers_completed()); + EXPECT_EQ("200", (*delegate->response())["status"]); + EXPECT_EQ("HTTP/1.1", (*delegate->response())["version"]); + EXPECT_EQ(std::string("\0hello!\xff", 8), delegate->received_data()); + EXPECT_EQ(8, delegate->data_sent()); + EXPECT_TRUE(delegate->closed()); + + // Check that the NetLog was filled reasonably. + net::CapturingNetLog::EntryList entries; + log.GetEntries(&entries); + EXPECT_LT(0u, entries.size()); + + // Check that we logged SPDY_STREAM_ERROR correctly. + int pos = net::ExpectLogContainsSomewhere( + entries, 0, + net::NetLog::TYPE_SPDY_STREAM_ERROR, + net::NetLog::PHASE_NONE); + + CapturingNetLog::Entry entry = entries[pos]; + NetLogSpdyStreamErrorParameter* request_params = + static_cast<NetLogSpdyStreamErrorParameter*>( + entry.extra_parameters.get()); + EXPECT_EQ(stream_id, request_params->stream_id()); +} + +} // namespace net diff --git a/net/spdy/spdy_test_util.cc b/net/spdy/spdy_test_util_spdy2.cc index e0f2939..19f6c5e 100644 --- a/net/spdy/spdy_test_util.cc +++ b/net/spdy/spdy_test_util_spdy2.cc @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "net/spdy/spdy_test_util.h" +#include "net/spdy/spdy_test_util_spdy2.h" #include <string> @@ -17,6 +17,7 @@ #include "net/spdy/spdy_http_utils.h" namespace net { +namespace test_spdy2 { // Chop a frame into an array of MockWrites. // |data| is the frame to chop. @@ -1004,4 +1005,6 @@ const SpdyHeaderInfo MakeSpdyHeader(spdy::SpdyControlType type) { }; return kHeader; } + +} // namespace test_spdy2 } // namespace net diff --git a/net/spdy/spdy_test_util.h b/net/spdy/spdy_test_util_spdy2.h index 996730f..4d4f85c 100644 --- a/net/spdy/spdy_test_util.h +++ b/net/spdy/spdy_test_util_spdy2.h @@ -27,6 +27,8 @@ namespace net { +namespace test_spdy2 { + // Default upload data used by both, mock objects and framer when creating // data frames. const char kDefaultURL[] = "http://www.google.com"; @@ -409,6 +411,8 @@ class SpdySessionPoolPeer { DISALLOW_COPY_AND_ASSIGN(SpdySessionPoolPeer); }; +} // namespace test_spdy2 + } // namespace net #endif // NET_SPDY_SPDY_TEST_UTIL_H_ diff --git a/net/spdy/spdy_test_util_spdy3.cc b/net/spdy/spdy_test_util_spdy3.cc new file mode 100644 index 0000000..6ef747e --- /dev/null +++ b/net/spdy/spdy_test_util_spdy3.cc @@ -0,0 +1,1012 @@ +// Copyright (c) 2012 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_test_util_spdy3.h" + +#include <string> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/string_number_conversions.h" +#include "base/string_util.h" +#include "net/http/http_network_session.h" +#include "net/http/http_network_transaction.h" +#include "net/http/http_server_properties_impl.h" +#include "net/spdy/spdy_framer.h" +#include "net/spdy/spdy_http_utils.h" + +namespace net { + +namespace test_spdy3 { + +// 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* ChopWriteFrame(const char* data, int length, int num_chunks) { + MockWrite* chunks = new MockWrite[num_chunks]; + 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(ASYNC, ptr, chunk_size); + } + return chunks; +} + +// Chop a SpdyFrame into an array of MockWrites. +// |frame| is the frame to chop. +// |num_chunks| is the number of chunks to create. +MockWrite* ChopWriteFrame(const spdy::SpdyFrame& frame, int num_chunks) { + return ChopWriteFrame(frame.data(), + frame.length() + spdy::SpdyFrame::kHeaderSize, + num_chunks); +} + +// Chop a frame into an array of MockReads. +// |data| is the frame to chop. +// |length| is the length of the frame to chop. +// |num_chunks| is the number of chunks to create. +MockRead* ChopReadFrame(const char* data, int length, int num_chunks) { + MockRead* chunks = new MockRead[num_chunks]; + 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] = MockRead(ASYNC, ptr, chunk_size); + } + return chunks; +} + +// Chop a SpdyFrame into an array of MockReads. +// |frame| is the frame to chop. +// |num_chunks| is the number of chunks to create. +MockRead* ChopReadFrame(const spdy::SpdyFrame& frame, int num_chunks) { + return ChopReadFrame(frame.data(), + frame.length() + spdy::SpdyFrame::kHeaderSize, + num_chunks); +} + +// Adds headers and values to a map. +// |extra_headers| is an array of { name, value } pairs, arranged as strings +// where the even entries are the header names, and the odd entries are the +// header values. +// |headers| gets filled in from |extra_headers|. +void AppendHeadersToSpdyFrame(const char* const extra_headers[], + int extra_header_count, + spdy::SpdyHeaderBlock* headers) { + std::string this_header; + std::string this_value; + + if (!extra_header_count) + return; + + // Sanity check: Non-NULL header list. + DCHECK(NULL != extra_headers) << "NULL header value pair list"; + // Sanity check: Non-NULL header map. + DCHECK(NULL != headers) << "NULL header map"; + // Copy in the headers. + for (int i = 0; i < extra_header_count; i++) { + // Sanity check: Non-empty header. + DCHECK_NE('\0', *extra_headers[i * 2]) << "Empty header value pair"; + this_header = extra_headers[i * 2]; + std::string::size_type header_len = this_header.length(); + if (!header_len) + continue; + this_value = extra_headers[1 + (i * 2)]; + std::string new_value; + if (headers->find(this_header) != headers->end()) { + // More than one entry in the header. + // Don't add the header again, just the append to the value, + // separated by a NULL character. + + // Adjust the value. + new_value = (*headers)[this_header]; + // Put in a NULL separator. + new_value.append(1, '\0'); + // Append the new value. + new_value += this_value; + } else { + // Not a duplicate, just write the value. + new_value = this_value; + } + (*headers)[this_header] = new_value; + } +} + +// Writes |val| to a location of size |len|, in big-endian format. +// in the buffer pointed to by |buffer_handle|. +// Updates the |*buffer_handle| pointer by |len| +// Returns the number of bytes written +int AppendToBuffer(int val, + int len, + unsigned char** buffer_handle, + int* buffer_len_remaining) { + if (len <= 0) + return 0; + DCHECK((size_t) len <= sizeof(len)) << "Data length too long for data type"; + DCHECK(NULL != buffer_handle) << "NULL buffer handle"; + DCHECK(NULL != *buffer_handle) << "NULL pointer"; + DCHECK(NULL != buffer_len_remaining) + << "NULL buffer remainder length pointer"; + DCHECK_GE(*buffer_len_remaining, len) << "Insufficient buffer size"; + for (int i = 0; i < len; i++) { + int shift = (8 * (len - (i + 1))); + unsigned char val_chunk = (val >> shift) & 0x0FF; + *(*buffer_handle)++ = val_chunk; + *buffer_len_remaining += 1; + } + return len; +} + +// Construct a SPDY packet. +// |head| is the start of the packet, up to but not including +// the header value pairs. +// |extra_headers| are the extra header-value pairs, which typically +// will vary the most between calls. +// |tail| is any (relatively constant) header-value pairs to add. +// |buffer| is the buffer we're filling in. +// Returns a SpdyFrame. +spdy::SpdyFrame* ConstructSpdyPacket(const SpdyHeaderInfo& header_info, + const char* const extra_headers[], + int extra_header_count, + const char* const tail[], + int tail_header_count) { + spdy::SpdyFramer framer; + spdy::SpdyHeaderBlock headers; + // Copy in the extra headers to our map. + AppendHeadersToSpdyFrame(extra_headers, extra_header_count, &headers); + // Copy in the tail headers to our map. + if (tail && tail_header_count) + AppendHeadersToSpdyFrame(tail, tail_header_count, &headers); + spdy::SpdyFrame* frame = NULL; + switch (header_info.kind) { + case spdy::SYN_STREAM: + frame = framer.CreateSynStream(header_info.id, header_info.assoc_id, + header_info.priority, + header_info.control_flags, + header_info.compressed, &headers); + break; + case spdy::SYN_REPLY: + frame = framer.CreateSynReply(header_info.id, header_info.control_flags, + header_info.compressed, &headers); + break; + case spdy::RST_STREAM: + frame = framer.CreateRstStream(header_info.id, header_info.status); + break; + case spdy::HEADERS: + frame = framer.CreateHeaders(header_info.id, header_info.control_flags, + header_info.compressed, &headers); + break; + default: + frame = framer.CreateDataFrame(header_info.id, header_info.data, + header_info.data_length, + header_info.data_flags); + break; + } + return frame; +} + +// Construct an expected SPDY SETTINGS frame. +// |settings| are the settings to set. +// Returns the constructed frame. The caller takes ownership of the frame. +spdy::SpdyFrame* ConstructSpdySettings( + const spdy::SpdySettings& settings) { + spdy::SpdyFramer framer; + return framer.CreateSettings(settings); +} + +// Construct an expected SPDY CREDENTIAL frame. +// |credential| is the credential to sen. +// Returns the constructed frame. The caller takes ownership of the frame. +spdy::SpdyFrame* ConstructSpdyCredential( + const spdy::SpdyCredential& credential) { + spdy::SpdyFramer framer; + return framer.CreateCredentialFrame(credential); +} + +// Construct a SPDY PING frame. +// Returns the constructed frame. The caller takes ownership of the frame. +spdy::SpdyFrame* ConstructSpdyPing() { + spdy::SpdyFramer framer; + return framer.CreatePingFrame(1); +} + +// Construct a SPDY GOAWAY frame. +// Returns the constructed frame. The caller takes ownership of the frame. +spdy::SpdyFrame* ConstructSpdyGoAway() { + spdy::SpdyFramer framer; + return framer.CreateGoAway(0); +} + +// Construct a SPDY WINDOW_UPDATE frame. +// Returns the constructed frame. The caller takes ownership of the frame. +spdy::SpdyFrame* ConstructSpdyWindowUpdate( + const spdy::SpdyStreamId stream_id, uint32 delta_window_size) { + spdy::SpdyFramer framer; + return framer.CreateWindowUpdate(stream_id, delta_window_size); +} + +// Construct a SPDY RST_STREAM frame. +// Returns the constructed frame. The caller takes ownership of the frame. +spdy::SpdyFrame* ConstructSpdyRstStream(spdy::SpdyStreamId stream_id, + spdy::SpdyStatusCodes status) { + spdy::SpdyFramer framer; + return framer.CreateRstStream(stream_id, status); +} + +// Construct a single SPDY header entry, for validation. +// |extra_headers| are the extra header-value pairs. +// |buffer| is the buffer we're filling in. +// |index| is the index of the header we want. +// Returns the number of bytes written into |buffer|. +int ConstructSpdyHeader(const char* const extra_headers[], + int extra_header_count, + char* buffer, + int buffer_length, + int index) { + const char* this_header = NULL; + const char* this_value = NULL; + if (!buffer || !buffer_length) + return 0; + *buffer = '\0'; + // Sanity check: Non-empty header list. + DCHECK(NULL != extra_headers) << "NULL extra headers pointer"; + // Sanity check: Index out of range. + DCHECK((index >= 0) && (index < extra_header_count)) + << "Index " << index + << " out of range [0, " << extra_header_count << ")"; + this_header = extra_headers[index * 2]; + // Sanity check: Non-empty header. + if (!*this_header) + return 0; + std::string::size_type header_len = strlen(this_header); + if (!header_len) + return 0; + this_value = extra_headers[1 + (index * 2)]; + // Sanity check: Non-empty value. + if (!*this_value) + this_value = ""; + int n = base::snprintf(buffer, + buffer_length, + "%s: %s\r\n", + this_header, + this_value); + return n; +} + +spdy::SpdyFrame* ConstructSpdyControlFrame(const char* const extra_headers[], + int extra_header_count, + bool compressed, + int stream_id, + RequestPriority request_priority, + spdy::SpdyControlType type, + spdy::SpdyControlFlags flags, + const char* const* kHeaders, + int kHeadersSize) { + return ConstructSpdyControlFrame(extra_headers, + extra_header_count, + compressed, + stream_id, + request_priority, + type, + flags, + kHeaders, + kHeadersSize, + 0); +} + +spdy::SpdyFrame* ConstructSpdyControlFrame(const char* const extra_headers[], + int extra_header_count, + bool compressed, + int stream_id, + RequestPriority request_priority, + spdy::SpdyControlType type, + spdy::SpdyControlFlags flags, + const char* const* kHeaders, + int kHeadersSize, + int associated_stream_id) { + const SpdyHeaderInfo kSynStartHeader = { + type, // Kind = Syn + stream_id, // Stream ID + associated_stream_id, // Associated stream ID + ConvertRequestPriorityToSpdyPriority(request_priority), + // Priority + flags, // Control Flags + compressed, // Compressed + spdy::INVALID, // Status + NULL, // Data + 0, // Length + spdy::DATA_FLAG_NONE // Data Flags + }; + return ConstructSpdyPacket(kSynStartHeader, + extra_headers, + extra_header_count, + kHeaders, + kHeadersSize / 2); +} + +// Constructs a standard SPDY GET SYN packet, optionally compressed +// for the url |url|. +// |extra_headers| are the extra header-value pairs, which typically +// will vary the most between calls. +// Returns a SpdyFrame. +spdy::SpdyFrame* ConstructSpdyGet(const char* const url, + bool compressed, + int stream_id, + RequestPriority request_priority) { + const SpdyHeaderInfo kSynStartHeader = { + spdy::SYN_STREAM, // Kind = Syn + stream_id, // Stream ID + 0, // Associated stream ID + net::ConvertRequestPriorityToSpdyPriority(request_priority), + // Priority + spdy::CONTROL_FLAG_FIN, // Control Flags + compressed, // Compressed + spdy::INVALID, // Status + NULL, // Data + 0, // Length + spdy::DATA_FLAG_NONE // Data Flags + }; + + GURL gurl(url); + + // This is so ugly. Why are we using char* in here again? + std::string str_path = gurl.PathForRequest(); + std::string str_scheme = gurl.scheme(); + std::string str_host = gurl.host(); + if (gurl.has_port()) { + str_host += ":"; + str_host += gurl.port(); + } + scoped_array<char> req(new char[str_path.size() + 1]); + scoped_array<char> scheme(new char[str_scheme.size() + 1]); + scoped_array<char> host(new char[str_host.size() + 1]); + memcpy(req.get(), str_path.c_str(), str_path.size()); + memcpy(scheme.get(), str_scheme.c_str(), str_scheme.size()); + memcpy(host.get(), str_host.c_str(), str_host.size()); + req.get()[str_path.size()] = '\0'; + scheme.get()[str_scheme.size()] = '\0'; + host.get()[str_host.size()] = '\0'; + + const char* const headers[] = { + "method", + "GET", + "url", + req.get(), + "host", + host.get(), + "scheme", + scheme.get(), + "version", + "HTTP/1.1" + }; + return ConstructSpdyPacket( + kSynStartHeader, + NULL, + 0, + headers, + arraysize(headers) / 2); +} + +// Constructs a standard SPDY GET SYN packet, optionally compressed. +// |extra_headers| are the extra header-value pairs, which typically +// will vary the most between calls. +// Returns a SpdyFrame. +spdy::SpdyFrame* ConstructSpdyGet(const char* const extra_headers[], + int extra_header_count, + bool compressed, + int stream_id, + RequestPriority request_priority) { + return ConstructSpdyGet(extra_headers, extra_header_count, compressed, + stream_id, request_priority, true); +} + +// Constructs a standard SPDY GET SYN packet, optionally compressed. +// |extra_headers| are the extra header-value pairs, which typically +// will vary the most between calls. +// Returns a SpdyFrame. +spdy::SpdyFrame* ConstructSpdyGet(const char* const extra_headers[], + int extra_header_count, + bool compressed, + int stream_id, + RequestPriority request_priority, + bool direct) { + const char* const kStandardGetHeaders[] = { + "method", + "GET", + "url", + (direct ? "/" : "http://www.google.com/"), + "host", + "www.google.com", + "scheme", + "http", + "version", + "HTTP/1.1" + }; + return ConstructSpdyControlFrame(extra_headers, + extra_header_count, + compressed, + stream_id, + request_priority, + spdy::SYN_STREAM, + spdy::CONTROL_FLAG_FIN, + kStandardGetHeaders, + arraysize(kStandardGetHeaders)); +} + +// Constructs a standard SPDY SYN_STREAM frame for a CONNECT request. +spdy::SpdyFrame* ConstructSpdyConnect(const char* const extra_headers[], + int extra_header_count, + int stream_id) { + const char* const kConnectHeaders[] = { + "method", "CONNECT", + "url", "www.google.com:443", + "host", "www.google.com", + "version", "HTTP/1.1", + }; + return ConstructSpdyControlFrame(extra_headers, + extra_header_count, + /*compressed*/ false, + stream_id, + LOWEST, + spdy::SYN_STREAM, + spdy::CONTROL_FLAG_NONE, + kConnectHeaders, + arraysize(kConnectHeaders)); +} + +// Constructs a standard SPDY push SYN packet. +// |extra_headers| are the extra header-value pairs, which typically +// will vary the most between calls. +// Returns a SpdyFrame. +spdy::SpdyFrame* ConstructSpdyPush(const char* const extra_headers[], + int extra_header_count, + int stream_id, + int associated_stream_id) { + const char* const kStandardGetHeaders[] = { + "hello", + "bye", + "status", + "200", + "version", + "HTTP/1.1" + }; + return ConstructSpdyControlFrame(extra_headers, + extra_header_count, + false, + stream_id, + LOWEST, + spdy::SYN_STREAM, + spdy::CONTROL_FLAG_NONE, + kStandardGetHeaders, + arraysize(kStandardGetHeaders), + associated_stream_id); +} + +spdy::SpdyFrame* ConstructSpdyPush(const char* const extra_headers[], + int extra_header_count, + int stream_id, + int associated_stream_id, + const char* url) { + const char* const kStandardGetHeaders[] = { + "hello", + "bye", + "status", + "200 OK", + "url", + url, + "version", + "HTTP/1.1" + }; + return ConstructSpdyControlFrame(extra_headers, + extra_header_count, + false, + stream_id, + LOWEST, + spdy::SYN_STREAM, + spdy::CONTROL_FLAG_NONE, + kStandardGetHeaders, + arraysize(kStandardGetHeaders), + associated_stream_id); + +} +spdy::SpdyFrame* ConstructSpdyPush(const char* const extra_headers[], + int extra_header_count, + int stream_id, + int associated_stream_id, + const char* url, + const char* status, + const char* location) { + const char* const kStandardGetHeaders[] = { + "hello", + "bye", + "status", + status, + "location", + location, + "url", + url, + "version", + "HTTP/1.1" + }; + return ConstructSpdyControlFrame(extra_headers, + extra_header_count, + false, + stream_id, + LOWEST, + spdy::SYN_STREAM, + spdy::CONTROL_FLAG_NONE, + kStandardGetHeaders, + arraysize(kStandardGetHeaders), + associated_stream_id); +} + +spdy::SpdyFrame* ConstructSpdyPush(int stream_id, + int associated_stream_id, + const char* url) { + const char* const kStandardGetHeaders[] = { + "url", + url + }; + return ConstructSpdyControlFrame(0, + 0, + false, + stream_id, + LOWEST, + spdy::SYN_STREAM, + spdy::CONTROL_FLAG_NONE, + kStandardGetHeaders, + arraysize(kStandardGetHeaders), + associated_stream_id); +} + +spdy::SpdyFrame* ConstructSpdyPushHeaders(int stream_id, + const char* const extra_headers[], + int extra_header_count) { + const char* const kStandardGetHeaders[] = { + "status", + "200 OK", + "version", + "HTTP/1.1" + }; + return ConstructSpdyControlFrame(extra_headers, + extra_header_count, + false, + stream_id, + LOWEST, + spdy::HEADERS, + spdy::CONTROL_FLAG_NONE, + kStandardGetHeaders, + arraysize(kStandardGetHeaders)); +} + +// Constructs a standard SPDY SYN_REPLY packet with the specified status code. +// Returns a SpdyFrame. +spdy::SpdyFrame* ConstructSpdySynReplyError( + const char* const status, + const char* const* const extra_headers, + int extra_header_count, + int stream_id) { + const char* const kStandardGetHeaders[] = { + "hello", + "bye", + "status", + status, + "version", + "HTTP/1.1" + }; + return ConstructSpdyControlFrame(extra_headers, + extra_header_count, + false, + stream_id, + LOWEST, + spdy::SYN_REPLY, + spdy::CONTROL_FLAG_NONE, + kStandardGetHeaders, + arraysize(kStandardGetHeaders)); +} + +// Constructs a standard SPDY SYN_REPLY packet to match the SPDY GET. +// |extra_headers| are the extra header-value pairs, which typically +// will vary the most between calls. +// Returns a SpdyFrame. +spdy::SpdyFrame* ConstructSpdyGetSynReplyRedirect(int stream_id) { + static const char* const kExtraHeaders[] = { + "location", + "http://www.foo.com/index.php", + }; + return ConstructSpdySynReplyError("301 Moved Permanently", kExtraHeaders, + arraysize(kExtraHeaders)/2, stream_id); +} + +// Constructs a standard SPDY SYN_REPLY packet with an Internal Server +// Error status code. +// Returns a SpdyFrame. +spdy::SpdyFrame* ConstructSpdySynReplyError(int stream_id) { + return ConstructSpdySynReplyError("500 Internal Server Error", NULL, 0, 1); +} + + + + +// Constructs a standard SPDY SYN_REPLY packet to match the SPDY GET. +// |extra_headers| are the extra header-value pairs, which typically +// will vary the most between calls. +// Returns a SpdyFrame. +spdy::SpdyFrame* ConstructSpdyGetSynReply(const char* const extra_headers[], + int extra_header_count, + int stream_id) { + static const char* const kStandardGetHeaders[] = { + "hello", + "bye", + "status", + "200", + "version", + "HTTP/1.1" + }; + return ConstructSpdyControlFrame(extra_headers, + extra_header_count, + false, + stream_id, + LOWEST, + spdy::SYN_REPLY, + spdy::CONTROL_FLAG_NONE, + kStandardGetHeaders, + arraysize(kStandardGetHeaders)); +} + +// Constructs a standard SPDY POST SYN packet. +// |content_length| is the size of post data. +// |extra_headers| are the extra header-value pairs, which typically +// will vary the most between calls. +// Returns a SpdyFrame. +spdy::SpdyFrame* ConstructSpdyPost(int64 content_length, + const char* const extra_headers[], + int extra_header_count) { + std::string length_str = base::Int64ToString(content_length); + const char* post_headers[] = { + "method", + "POST", + "url", + "/", + "host", + "www.google.com", + "scheme", + "http", + "version", + "HTTP/1.1", + "content-length", + length_str.c_str() + }; + return ConstructSpdyControlFrame(extra_headers, + extra_header_count, + false, + 1, + LOWEST, + spdy::SYN_STREAM, + spdy::CONTROL_FLAG_NONE, + post_headers, + arraysize(post_headers)); +} + +// Constructs a chunked transfer SPDY POST SYN packet. +// |extra_headers| are the extra header-value pairs, which typically +// will vary the most between calls. +// Returns a SpdyFrame. +spdy::SpdyFrame* ConstructChunkedSpdyPost(const char* const extra_headers[], + int extra_header_count) { + const char* post_headers[] = { + "method", + "POST", + "url", + "/", + "host", + "www.google.com", + "scheme", + "http", + "version", + "HTTP/1.1" + }; + return ConstructSpdyControlFrame(extra_headers, + extra_header_count, + false, + 1, + LOWEST, + spdy::SYN_STREAM, + spdy::CONTROL_FLAG_NONE, + post_headers, + arraysize(post_headers)); +} + +// Constructs a standard SPDY SYN_REPLY packet to match the SPDY POST. +// |extra_headers| are the extra header-value pairs, which typically +// will vary the most between calls. +// Returns a SpdyFrame. +spdy::SpdyFrame* ConstructSpdyPostSynReply(const char* const extra_headers[], + int extra_header_count) { + static const char* const kStandardGetHeaders[] = { + "hello", + "bye", + "status", + "200", + "url", + "/index.php", + "version", + "HTTP/1.1" + }; + return ConstructSpdyControlFrame(extra_headers, + extra_header_count, + false, + 1, + LOWEST, + spdy::SYN_REPLY, + spdy::CONTROL_FLAG_NONE, + kStandardGetHeaders, + arraysize(kStandardGetHeaders)); +} + +// Constructs a single SPDY data frame with the default contents. +spdy::SpdyFrame* ConstructSpdyBodyFrame(int stream_id, bool fin) { + spdy::SpdyFramer framer; + return framer.CreateDataFrame( + stream_id, kUploadData, kUploadDataSize, + fin ? spdy::DATA_FLAG_FIN : spdy::DATA_FLAG_NONE); +} + +// Constructs a single SPDY data frame with the given content. +spdy::SpdyFrame* ConstructSpdyBodyFrame(int stream_id, const char* data, + uint32 len, bool fin) { + spdy::SpdyFramer framer; + return framer.CreateDataFrame( + stream_id, data, len, fin ? spdy::DATA_FLAG_FIN : spdy::DATA_FLAG_NONE); +} + +// Wraps |frame| in the payload of a data frame in stream |stream_id|. +spdy::SpdyFrame* ConstructWrappedSpdyFrame( + const scoped_ptr<spdy::SpdyFrame>& frame, + int stream_id) { + return ConstructSpdyBodyFrame(stream_id, frame->data(), + frame->length() + spdy::SpdyFrame::kHeaderSize, + false); +} + +// Construct an expected SPDY reply string. +// |extra_headers| are the extra header-value pairs, which typically +// will vary the most between calls. +// |buffer| is the buffer we're filling in. +// Returns the number of bytes written into |buffer|. +int ConstructSpdyReplyString(const char* const extra_headers[], + int extra_header_count, + char* buffer, + int buffer_length) { + int packet_size = 0; + char* buffer_write = buffer; + int buffer_left = buffer_length; + spdy::SpdyHeaderBlock headers; + if (!buffer || !buffer_length) + return 0; + // Copy in the extra headers. + AppendHeadersToSpdyFrame(extra_headers, extra_header_count, &headers); + // The iterator gets us the list of header/value pairs in sorted order. + spdy::SpdyHeaderBlock::iterator next = headers.begin(); + spdy::SpdyHeaderBlock::iterator last = headers.end(); + for ( ; next != last; ++next) { + // Write the header. + int value_len, current_len, offset; + const char* header_string = next->first.c_str(); + packet_size += AppendToBuffer(header_string, + next->first.length(), + &buffer_write, + &buffer_left); + packet_size += AppendToBuffer(": ", + strlen(": "), + &buffer_write, + &buffer_left); + // Write the value(s). + const char* value_string = next->second.c_str(); + // Check if it's split among two or more values. + value_len = next->second.length(); + current_len = strlen(value_string); + offset = 0; + // Handle the first N-1 values. + while (current_len < value_len) { + // Finish this line -- write the current value. + packet_size += AppendToBuffer(value_string + offset, + current_len - offset, + &buffer_write, + &buffer_left); + packet_size += AppendToBuffer("\n", + strlen("\n"), + &buffer_write, + &buffer_left); + // Advance to next value. + offset = current_len + 1; + current_len += 1 + strlen(value_string + offset); + // Start another line -- add the header again. + packet_size += AppendToBuffer(header_string, + next->first.length(), + &buffer_write, + &buffer_left); + packet_size += AppendToBuffer(": ", + strlen(": "), + &buffer_write, + &buffer_left); + } + EXPECT_EQ(value_len, current_len); + // Copy the last (or only) value. + packet_size += AppendToBuffer(value_string + offset, + value_len - offset, + &buffer_write, + &buffer_left); + packet_size += AppendToBuffer("\n", + strlen("\n"), + &buffer_write, + &buffer_left); + } + return packet_size; +} + +// Create a MockWrite from the given SpdyFrame. +MockWrite CreateMockWrite(const spdy::SpdyFrame& req) { + return MockWrite( + ASYNC, req.data(), req.length() + spdy::SpdyFrame::kHeaderSize); +} + +// Create a MockWrite from the given SpdyFrame and sequence number. +MockWrite CreateMockWrite(const spdy::SpdyFrame& req, int seq) { + return CreateMockWrite(req, seq, ASYNC); +} + +// Create a MockWrite from the given SpdyFrame and sequence number. +MockWrite CreateMockWrite(const spdy::SpdyFrame& req, int seq, IoMode mode) { + return MockWrite( + mode, req.data(), req.length() + spdy::SpdyFrame::kHeaderSize, seq); +} + +// Create a MockRead from the given SpdyFrame. +MockRead CreateMockRead(const spdy::SpdyFrame& resp) { + return MockRead( + ASYNC, resp.data(), resp.length() + spdy::SpdyFrame::kHeaderSize); +} + +// Create a MockRead from the given SpdyFrame and sequence number. +MockRead CreateMockRead(const spdy::SpdyFrame& resp, int seq) { + return CreateMockRead(resp, seq, ASYNC); +} + +// Create a MockRead from the given SpdyFrame and sequence number. +MockRead CreateMockRead(const spdy::SpdyFrame& resp, int seq, IoMode mode) { + return MockRead( + mode, resp.data(), resp.length() + spdy::SpdyFrame::kHeaderSize, seq); +} + +// Combines the given SpdyFrames into the given char array and returns +// the total length. +int CombineFrames(const spdy::SpdyFrame** frames, int num_frames, + char* buff, int buff_len) { + int total_len = 0; + for (int i = 0; i < num_frames; ++i) { + total_len += frames[i]->length() + spdy::SpdyFrame::kHeaderSize; + } + DCHECK_LE(total_len, buff_len); + char* ptr = buff; + for (int i = 0; i < num_frames; ++i) { + int len = frames[i]->length() + spdy::SpdyFrame::kHeaderSize; + memcpy(ptr, frames[i]->data(), len); + ptr += len; + } + return total_len; +} + +SpdySessionDependencies::SpdySessionDependencies() + : host_resolver(new MockCachingHostResolver), + cert_verifier(new CertVerifier), + proxy_service(ProxyService::CreateDirect()), + ssl_config_service(new SSLConfigServiceDefaults), + socket_factory(new MockClientSocketFactory), + deterministic_socket_factory(new DeterministicMockClientSocketFactory), + http_auth_handler_factory( + HttpAuthHandlerFactory::CreateDefault(host_resolver.get())) { + // 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); +} + +SpdySessionDependencies::SpdySessionDependencies(ProxyService* proxy_service) + : host_resolver(new MockHostResolver), + cert_verifier(new CertVerifier), + proxy_service(proxy_service), + ssl_config_service(new SSLConfigServiceDefaults), + socket_factory(new MockClientSocketFactory), + deterministic_socket_factory(new DeterministicMockClientSocketFactory), + http_auth_handler_factory( + HttpAuthHandlerFactory::CreateDefault(host_resolver.get())) {} + +SpdySessionDependencies::~SpdySessionDependencies() {} + +// static +HttpNetworkSession* SpdySessionDependencies::SpdyCreateSession( + SpdySessionDependencies* session_deps) { + net::HttpNetworkSession::Params params; + params.client_socket_factory = session_deps->socket_factory.get(); + params.host_resolver = session_deps->host_resolver.get(); + params.cert_verifier = session_deps->cert_verifier.get(); + params.proxy_service = session_deps->proxy_service.get(); + params.ssl_config_service = session_deps->ssl_config_service; + params.http_auth_handler_factory = + session_deps->http_auth_handler_factory.get(); + params.http_server_properties = &session_deps->http_server_properties; + return new HttpNetworkSession(params); +} + +// static +HttpNetworkSession* SpdySessionDependencies::SpdyCreateSessionDeterministic( + SpdySessionDependencies* session_deps) { + net::HttpNetworkSession::Params params; + params.client_socket_factory = + session_deps->deterministic_socket_factory.get(); + params.host_resolver = session_deps->host_resolver.get(); + params.cert_verifier = session_deps->cert_verifier.get(); + params.proxy_service = session_deps->proxy_service.get(); + params.ssl_config_service = session_deps->ssl_config_service; + params.http_auth_handler_factory = + session_deps->http_auth_handler_factory.get(); + params.http_server_properties = &session_deps->http_server_properties; + return new HttpNetworkSession(params); +} + +SpdyURLRequestContext::SpdyURLRequestContext() + : ALLOW_THIS_IN_INITIALIZER_LIST(storage_(this)) { + storage_.set_host_resolver(new MockHostResolver()); + storage_.set_cert_verifier(new CertVerifier); + storage_.set_proxy_service(ProxyService::CreateDirect()); + storage_.set_ssl_config_service(new SSLConfigServiceDefaults); + storage_.set_http_auth_handler_factory(HttpAuthHandlerFactory::CreateDefault( + host_resolver())); + storage_.set_http_server_properties(new HttpServerPropertiesImpl); + net::HttpNetworkSession::Params params; + params.client_socket_factory = &socket_factory_; + params.host_resolver = host_resolver(); + params.cert_verifier = cert_verifier(); + params.proxy_service = proxy_service(); + params.ssl_config_service = ssl_config_service(); + params.http_auth_handler_factory = http_auth_handler_factory(); + params.network_delegate = network_delegate(); + params.http_server_properties = http_server_properties(); + scoped_refptr<HttpNetworkSession> network_session( + new HttpNetworkSession(params)); + storage_.set_http_transaction_factory(new HttpCache( + network_session, + HttpCache::DefaultBackend::InMemory(0))); +} + +SpdyURLRequestContext::~SpdyURLRequestContext() { +} + +const SpdyHeaderInfo MakeSpdyHeader(spdy::SpdyControlType type) { + const SpdyHeaderInfo kHeader = { + type, // Kind = Syn + 1, // Stream ID + 0, // Associated stream ID + 2, // Priority + spdy::CONTROL_FLAG_FIN, // Control Flags + false, // Compressed + spdy::INVALID, // Status + NULL, // Data + 0, // Length + spdy::DATA_FLAG_NONE // Data Flags + }; + return kHeader; +} + +} // namespace test_spdy3 + +} // namespace net diff --git a/net/spdy/spdy_test_util_spdy3.h b/net/spdy/spdy_test_util_spdy3.h new file mode 100644 index 0000000..6cfebae --- /dev/null +++ b/net/spdy/spdy_test_util_spdy3.h @@ -0,0 +1,418 @@ +// Copyright (c) 2012 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. + +#ifndef NET_SPDY_SPDY_TEST_UTIL_H_ +#define NET_SPDY_SPDY_TEST_UTIL_H_ +#pragma once + +#include "base/basictypes.h" +#include "net/base/cert_verifier.h" +#include "net/base/host_port_pair.h" +#include "net/base/mock_host_resolver.h" +#include "net/base/request_priority.h" +#include "net/base/ssl_config_service_defaults.h" +#include "net/base/sys_addrinfo.h" +#include "net/http/http_auth_handler_factory.h" +#include "net/http/http_cache.h" +#include "net/http/http_network_session.h" +#include "net/http/http_network_layer.h" +#include "net/http/http_server_properties_impl.h" +#include "net/http/http_transaction_factory.h" +#include "net/proxy/proxy_service.h" +#include "net/socket/socket_test_util.h" +#include "net/spdy/spdy_framer.h" +#include "net/url_request/url_request_context.h" +#include "net/url_request/url_request_context_storage.h" + +namespace net { + +namespace test_spdy3 { + +// Default upload data used by both, mock objects and framer when creating +// data frames. +const char kDefaultURL[] = "http://www.google.com"; +const char kUploadData[] = "hello!"; +const int kUploadDataSize = arraysize(kUploadData)-1; + +// NOTE: In GCC, on a Mac, this can't be in an anonymous namespace! +// This struct holds information used to construct spdy control and data frames. +struct SpdyHeaderInfo { + spdy::SpdyControlType kind; + spdy::SpdyStreamId id; + spdy::SpdyStreamId assoc_id; + spdy::SpdyPriority priority; + spdy::SpdyControlFlags control_flags; + bool compressed; + spdy::SpdyStatusCodes status; + const char* data; + uint32 data_length; + spdy::SpdyDataFlags data_flags; +}; + +// 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* ChopWriteFrame(const char* data, int length, int num_chunks); + +// Chop a SpdyFrame into an array of MockWrites. +// |frame| is the frame to chop. +// |num_chunks| is the number of chunks to create. +MockWrite* ChopWriteFrame(const spdy::SpdyFrame& frame, int num_chunks); + +// Chop a frame into an array of MockReads. +// |data| is the frame to chop. +// |length| is the length of the frame to chop. +// |num_chunks| is the number of chunks to create. +MockRead* ChopReadFrame(const char* data, int length, int num_chunks); + +// Chop a SpdyFrame into an array of MockReads. +// |frame| is the frame to chop. +// |num_chunks| is the number of chunks to create. +MockRead* ChopReadFrame(const spdy::SpdyFrame& frame, int num_chunks); + +// Adds headers and values to a map. +// |extra_headers| is an array of { name, value } pairs, arranged as strings +// where the even entries are the header names, and the odd entries are the +// header values. +// |headers| gets filled in from |extra_headers|. +void AppendHeadersToSpdyFrame(const char* const extra_headers[], + int extra_header_count, + spdy::SpdyHeaderBlock* headers); + +// Writes |str| of the given |len| to the buffer pointed to by |buffer_handle|. +// Uses a template so buffer_handle can be a char* or an unsigned char*. +// Updates the |*buffer_handle| pointer by |len| +// Returns the number of bytes written into *|buffer_handle| +template<class T> +int AppendToBuffer(const char* str, + int len, + T** buffer_handle, + int* buffer_len_remaining) { + DCHECK_GT(len, 0); + DCHECK(NULL != buffer_handle) << "NULL buffer handle"; + DCHECK(NULL != *buffer_handle) << "NULL pointer"; + DCHECK(NULL != buffer_len_remaining) + << "NULL buffer remainder length pointer"; + DCHECK_GE(*buffer_len_remaining, len) << "Insufficient buffer size"; + memcpy(*buffer_handle, str, len); + *buffer_handle += len; + *buffer_len_remaining -= len; + return len; +} + +// Writes |val| to a location of size |len|, in big-endian format. +// in the buffer pointed to by |buffer_handle|. +// Updates the |*buffer_handle| pointer by |len| +// Returns the number of bytes written +int AppendToBuffer(int val, + int len, + unsigned char** buffer_handle, + int* buffer_len_remaining); + +// Construct a SPDY packet. +// |head| is the start of the packet, up to but not including +// the header value pairs. +// |extra_headers| are the extra header-value pairs, which typically +// will vary the most between calls. +// |tail| is any (relatively constant) header-value pairs to add. +// |buffer| is the buffer we're filling in. +// Returns a SpdyFrame. +spdy::SpdyFrame* ConstructSpdyPacket(const SpdyHeaderInfo& header_info, + const char* const extra_headers[], + int extra_header_count, + const char* const tail[], + int tail_header_count); + +// Construct a generic SpdyControlFrame. +spdy::SpdyFrame* ConstructSpdyControlFrame(const char* const extra_headers[], + int extra_header_count, + bool compressed, + int stream_id, + RequestPriority request_priority, + spdy::SpdyControlType type, + spdy::SpdyControlFlags flags, + const char* const* kHeaders, + int kHeadersSize); +spdy::SpdyFrame* ConstructSpdyControlFrame(const char* const extra_headers[], + int extra_header_count, + bool compressed, + int stream_id, + RequestPriority request_priority, + spdy::SpdyControlType type, + spdy::SpdyControlFlags flags, + const char* const* kHeaders, + int kHeadersSize, + int associated_stream_id); + +// Construct an expected SPDY reply string. +// |extra_headers| are the extra header-value pairs, which typically +// will vary the most between calls. +// |buffer| is the buffer we're filling in. +// Returns the number of bytes written into |buffer|. +int ConstructSpdyReplyString(const char* const extra_headers[], + int extra_header_count, + char* buffer, + int buffer_length); + +// Construct an expected SPDY SETTINGS frame. +// |settings| are the settings to set. +// Returns the constructed frame. The caller takes ownership of the frame. +spdy::SpdyFrame* ConstructSpdySettings( + const spdy::SpdySettings& settings); + +// Construct an expected SPDY CREDENTIAL frame. +// |credential| is the credential to send. +// Returns the constructed frame. The caller takes ownership of the frame. +spdy::SpdyFrame* ConstructSpdyCredential( + const spdy::SpdyCredential& credential); + +// Construct a SPDY PING frame. +// Returns the constructed frame. The caller takes ownership of the frame. +spdy::SpdyFrame* ConstructSpdyPing(); + +// Construct a SPDY GOAWAY frame. +// Returns the constructed frame. The caller takes ownership of the frame. +spdy::SpdyFrame* ConstructSpdyGoAway(); + +// Construct a SPDY WINDOW_UPDATE frame. +// Returns the constructed frame. The caller takes ownership of the frame. +spdy::SpdyFrame* ConstructSpdyWindowUpdate(spdy::SpdyStreamId, + uint32 delta_window_size); + +// Construct a SPDY RST_STREAM frame. +// Returns the constructed frame. The caller takes ownership of the frame. +spdy::SpdyFrame* ConstructSpdyRstStream(spdy::SpdyStreamId stream_id, + spdy::SpdyStatusCodes status); + +// Construct a single SPDY header entry, for validation. +// |extra_headers| are the extra header-value pairs. +// |buffer| is the buffer we're filling in. +// |index| is the index of the header we want. +// Returns the number of bytes written into |buffer|. +int ConstructSpdyHeader(const char* const extra_headers[], + int extra_header_count, + char* buffer, + int buffer_length, + int index); + +// Constructs a standard SPDY GET SYN packet, optionally compressed +// for the url |url|. +// |extra_headers| are the extra header-value pairs, which typically +// will vary the most between calls. +// Returns a SpdyFrame. +spdy::SpdyFrame* ConstructSpdyGet(const char* const url, + bool compressed, + int stream_id, + RequestPriority request_priority); + +// Constructs a standard SPDY GET SYN packet, optionally compressed. +// |extra_headers| are the extra header-value pairs, which typically +// will vary the most between calls. +// Returns a SpdyFrame. +spdy::SpdyFrame* ConstructSpdyGet(const char* const extra_headers[], + int extra_header_count, + bool compressed, + int stream_id, + RequestPriority request_priority); + +// Constructs a standard SPDY GET SYN packet, optionally compressed. +// |extra_headers| are the extra header-value pairs, which typically +// will vary the most between calls. If |direct| is false, the +// the full url will be used instead of simply the path. +// Returns a SpdyFrame. +spdy::SpdyFrame* ConstructSpdyGet(const char* const extra_headers[], + int extra_header_count, + bool compressed, + int stream_id, + RequestPriority request_priority, + bool direct); + +// Constructs a standard SPDY SYN_STREAM frame for a CONNECT request. +spdy::SpdyFrame* ConstructSpdyConnect(const char* const extra_headers[], + int extra_header_count, + int stream_id); + +// Constructs a standard SPDY push SYN packet. +// |extra_headers| are the extra header-value pairs, which typically +// will vary the most between calls. +// Returns a SpdyFrame. +spdy::SpdyFrame* ConstructSpdyPush(const char* const extra_headers[], + int extra_header_count, + int stream_id, + int associated_stream_id); +spdy::SpdyFrame* ConstructSpdyPush(const char* const extra_headers[], + int extra_header_count, + int stream_id, + int associated_stream_id, + const char* url); +spdy::SpdyFrame* ConstructSpdyPush(const char* const extra_headers[], + int extra_header_count, + int stream_id, + int associated_stream_id, + const char* url, + const char* status, + const char* location); +spdy::SpdyFrame* ConstructSpdyPush(int stream_id, + int associated_stream_id, + const char* url); + +spdy::SpdyFrame* ConstructSpdyPushHeaders(int stream_id, + const char* const extra_headers[], + int extra_header_count); + +// Constructs a standard SPDY SYN_REPLY packet to match the SPDY GET. +// |extra_headers| are the extra header-value pairs, which typically +// will vary the most between calls. +// Returns a SpdyFrame. +spdy::SpdyFrame* ConstructSpdyGetSynReply(const char* const extra_headers[], + int extra_header_count, + int stream_id); + +// Constructs a standard SPDY SYN_REPLY packet to match the SPDY GET. +// |extra_headers| are the extra header-value pairs, which typically +// will vary the most between calls. +// Returns a SpdyFrame. +spdy::SpdyFrame* ConstructSpdyGetSynReplyRedirect(int stream_id); + +// Constructs a standard SPDY SYN_REPLY packet with an Internal Server +// Error status code. +// Returns a SpdyFrame. +spdy::SpdyFrame* ConstructSpdySynReplyError(int stream_id); + +// Constructs a standard SPDY SYN_REPLY packet with the specified status code. +// Returns a SpdyFrame. +spdy::SpdyFrame* ConstructSpdySynReplyError( + const char* const status, + const char* const* const extra_headers, + int extra_header_count, + int stream_id); + +// Constructs a standard SPDY POST SYN packet. +// |extra_headers| are the extra header-value pairs, which typically +// will vary the most between calls. +// Returns a SpdyFrame. +spdy::SpdyFrame* ConstructSpdyPost(int64 content_length, + const char* const extra_headers[], + int extra_header_count); + +// Constructs a chunked transfer SPDY POST SYN packet. +// |extra_headers| are the extra header-value pairs, which typically +// will vary the most between calls. +// Returns a SpdyFrame. +spdy::SpdyFrame* ConstructChunkedSpdyPost(const char* const extra_headers[], + int extra_header_count); + +// Constructs a standard SPDY SYN_REPLY packet to match the SPDY POST. +// |extra_headers| are the extra header-value pairs, which typically +// will vary the most between calls. +// Returns a SpdyFrame. +spdy::SpdyFrame* ConstructSpdyPostSynReply(const char* const extra_headers[], + int extra_header_count); + +// Constructs a single SPDY data frame with the contents "hello!" +spdy::SpdyFrame* ConstructSpdyBodyFrame(int stream_id, + bool fin); + +// Constructs a single SPDY data frame with the given content. +spdy::SpdyFrame* ConstructSpdyBodyFrame(int stream_id, const char* data, + uint32 len, bool fin); + +// Wraps |frame| in the payload of a data frame in stream |stream_id|. +spdy::SpdyFrame* ConstructWrappedSpdyFrame( + const scoped_ptr<spdy::SpdyFrame>& frame, int stream_id); + +// Create an async MockWrite from the given SpdyFrame. +MockWrite CreateMockWrite(const spdy::SpdyFrame& req); + +// Create an async MockWrite from the given SpdyFrame and sequence number. +MockWrite CreateMockWrite(const spdy::SpdyFrame& req, int seq); + +MockWrite CreateMockWrite(const spdy::SpdyFrame& req, int seq, IoMode mode); + +// Create a MockRead from the given SpdyFrame. +MockRead CreateMockRead(const spdy::SpdyFrame& resp); + +// Create a MockRead from the given SpdyFrame and sequence number. +MockRead CreateMockRead(const spdy::SpdyFrame& resp, int seq); + +MockRead CreateMockRead(const spdy::SpdyFrame& resp, int seq, IoMode mode); + +// Combines the given SpdyFrames into the given char array and returns +// the total length. +int CombineFrames(const spdy::SpdyFrame** frames, int num_frames, + char* buff, int buff_len); + +// Helper to manage the lifetimes of the dependencies for a +// HttpNetworkTransaction. +class SpdySessionDependencies { + public: + // Default set of dependencies -- "null" proxy service. + SpdySessionDependencies(); + + // Custom proxy service dependency. + explicit SpdySessionDependencies(ProxyService* proxy_service); + + ~SpdySessionDependencies(); + + static HttpNetworkSession* SpdyCreateSession( + SpdySessionDependencies* session_deps); + static HttpNetworkSession* SpdyCreateSessionDeterministic( + SpdySessionDependencies* session_deps); + + // NOTE: host_resolver must be ordered before http_auth_handler_factory. + scoped_ptr<MockHostResolverBase> host_resolver; + scoped_ptr<CertVerifier> cert_verifier; + scoped_ptr<ProxyService> proxy_service; + scoped_refptr<SSLConfigService> ssl_config_service; + scoped_ptr<MockClientSocketFactory> socket_factory; + scoped_ptr<DeterministicMockClientSocketFactory> deterministic_socket_factory; + scoped_ptr<HttpAuthHandlerFactory> http_auth_handler_factory; + HttpServerPropertiesImpl http_server_properties; +}; + +class SpdyURLRequestContext : public URLRequestContext { + public: + SpdyURLRequestContext(); + + MockClientSocketFactory& socket_factory() { return socket_factory_; } + + protected: + virtual ~SpdyURLRequestContext(); + + private: + MockClientSocketFactory socket_factory_; + net::URLRequestContextStorage storage_; +}; + +const SpdyHeaderInfo MakeSpdyHeader(spdy::SpdyControlType type); + +class SpdySessionPoolPeer { + public: + explicit SpdySessionPoolPeer(SpdySessionPool* pool) + : pool_(pool) {} + + void AddAlias(const addrinfo* address, const HostPortProxyPair& pair) { + pool_->AddAlias(address, pair); + } + + void RemoveSpdySession(const scoped_refptr<SpdySession>& session) { + pool_->Remove(session); + } + + void DisableDomainAuthenticationVerification() { + pool_->verify_domain_authentication_ = false; + } + + private: + SpdySessionPool* const pool_; + + DISALLOW_COPY_AND_ASSIGN(SpdySessionPoolPeer); +}; + +} // namespace test_spdy3 + +} // namespace net + +#endif // NET_SPDY_SPDY_TEST_UTIL_H_ diff --git a/net/spdy/spdy_websocket_stream.h b/net/spdy/spdy_websocket_stream.h index d39c97c..5bce033 100644 --- a/net/spdy/spdy_websocket_stream.h +++ b/net/spdy/spdy_websocket_stream.h @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 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. @@ -81,7 +81,8 @@ class NET_EXPORT_PRIVATE SpdyWebSocketStream virtual void set_chunk_callback(ChunkCallback* callback) OVERRIDE; private: - FRIEND_TEST_ALL_PREFIXES(SpdyWebSocketStreamTest, Basic); + FRIEND_TEST_ALL_PREFIXES(SpdyWebSocketStreamSpdy2Test, Basic); + FRIEND_TEST_ALL_PREFIXES(SpdyWebSocketStreamSpdy3Test, Basic); void OnSpdyStreamCreated(int status); diff --git a/net/spdy/spdy_websocket_stream_unittest.cc b/net/spdy/spdy_websocket_stream_spdy2_unittest.cc index 6102dfa..aae61b8 100644 --- a/net/spdy/spdy_websocket_stream_unittest.cc +++ b/net/spdy/spdy_websocket_stream_spdy2_unittest.cc @@ -14,10 +14,12 @@ #include "net/spdy/spdy_http_utils.h" #include "net/spdy/spdy_protocol.h" #include "net/spdy/spdy_session.h" -#include "net/spdy/spdy_test_util.h" -#include "net/spdy/spdy_websocket_test_util.h" +#include "net/spdy/spdy_test_util_spdy2.h" +#include "net/spdy/spdy_websocket_test_util_spdy2.h" #include "testing/gtest/include/gtest/gtest.h" +using namespace net::test_spdy2; + namespace { struct SpdyWebSocketStreamEvent { @@ -48,6 +50,8 @@ struct SpdyWebSocketStreamEvent { namespace net { +namespace { + class SpdyWebSocketStreamEventRecorder : public SpdyWebSocketStream::Delegate { public: explicit SpdyWebSocketStreamEventRecorder(const CompletionCallback& callback) @@ -157,7 +161,9 @@ class SpdyWebSocketStreamEventRecorder : public SpdyWebSocketStream::Delegate { DISALLOW_COPY_AND_ASSIGN(SpdyWebSocketStreamEventRecorder); }; -class SpdyWebSocketStreamTest : public testing::Test { +} // namespace + +class SpdyWebSocketStreamSpdy2Test : public testing::Test { public: OrderedSocketData* data() { return data_.get(); } @@ -178,8 +184,8 @@ class SpdyWebSocketStreamTest : public testing::Test { } protected: - SpdyWebSocketStreamTest() {} - virtual ~SpdyWebSocketStreamTest() {} + SpdyWebSocketStreamSpdy2Test() {} + virtual ~SpdyWebSocketStreamSpdy2Test() {} virtual void SetUp() { EnableCompression(false); @@ -311,14 +317,14 @@ class SpdyWebSocketStreamTest : public testing::Test { static const size_t kClosingFrameLength; }; -const char SpdyWebSocketStreamTest::kMessageFrame[] = "\0hello\xff"; -const char SpdyWebSocketStreamTest::kClosingFrame[] = "\xff\0"; -const size_t SpdyWebSocketStreamTest::kMessageFrameLength = - arraysize(SpdyWebSocketStreamTest::kMessageFrame) - 1; -const size_t SpdyWebSocketStreamTest::kClosingFrameLength = - arraysize(SpdyWebSocketStreamTest::kClosingFrame) - 1; +const char SpdyWebSocketStreamSpdy2Test::kMessageFrame[] = "\0hello\xff"; +const char SpdyWebSocketStreamSpdy2Test::kClosingFrame[] = "\xff\0"; +const size_t SpdyWebSocketStreamSpdy2Test::kMessageFrameLength = + arraysize(SpdyWebSocketStreamSpdy2Test::kMessageFrame) - 1; +const size_t SpdyWebSocketStreamSpdy2Test::kClosingFrameLength = + arraysize(SpdyWebSocketStreamSpdy2Test::kClosingFrame) - 1; -TEST_F(SpdyWebSocketStreamTest, Basic) { +TEST_F(SpdyWebSocketStreamSpdy2Test, Basic) { Prepare(1); MockWrite writes[] = { CreateMockWrite(*request_frame_.get(), 1), @@ -339,10 +345,10 @@ TEST_F(SpdyWebSocketStreamTest, Basic) { SpdyWebSocketStreamEventRecorder delegate(completion_callback_.callback()); delegate.SetOnReceivedHeader( - base::Bind(&SpdyWebSocketStreamTest::DoSendHelloFrame, + base::Bind(&SpdyWebSocketStreamSpdy2Test::DoSendHelloFrame, base::Unretained(this))); delegate.SetOnReceivedData( - base::Bind(&SpdyWebSocketStreamTest::DoSendClosingFrame, + base::Bind(&SpdyWebSocketStreamSpdy2Test::DoSendClosingFrame, base::Unretained(this))); websocket_stream_.reset(new SpdyWebSocketStream(session_, &delegate)); @@ -393,7 +399,7 @@ TEST_F(SpdyWebSocketStreamTest, Basic) { EXPECT_TRUE(data()->at_write_eof()); } -TEST_F(SpdyWebSocketStreamTest, DestructionBeforeClose) { +TEST_F(SpdyWebSocketStreamSpdy2Test, DestructionBeforeClose) { Prepare(1); MockWrite writes[] = { CreateMockWrite(*request_frame_.get(), 1), @@ -411,10 +417,11 @@ TEST_F(SpdyWebSocketStreamTest, DestructionBeforeClose) { SpdyWebSocketStreamEventRecorder delegate(completion_callback_.callback()); delegate.SetOnReceivedHeader( - base::Bind(&SpdyWebSocketStreamTest::DoSendHelloFrame, + base::Bind(&SpdyWebSocketStreamSpdy2Test::DoSendHelloFrame, base::Unretained(this))); delegate.SetOnReceivedData( - base::Bind(&SpdyWebSocketStreamTest::DoSync, base::Unretained(this))); + base::Bind(&SpdyWebSocketStreamSpdy2Test::DoSync, + base::Unretained(this))); websocket_stream_.reset(new SpdyWebSocketStream(session_, &delegate)); @@ -454,7 +461,7 @@ TEST_F(SpdyWebSocketStreamTest, DestructionBeforeClose) { EXPECT_TRUE(data()->at_write_eof()); } -TEST_F(SpdyWebSocketStreamTest, DestructionAfterExplicitClose) { +TEST_F(SpdyWebSocketStreamSpdy2Test, DestructionAfterExplicitClose) { Prepare(1); MockWrite writes[] = { CreateMockWrite(*request_frame_.get(), 1), @@ -473,10 +480,11 @@ TEST_F(SpdyWebSocketStreamTest, DestructionAfterExplicitClose) { SpdyWebSocketStreamEventRecorder delegate(completion_callback_.callback()); delegate.SetOnReceivedHeader( - base::Bind(&SpdyWebSocketStreamTest::DoSendHelloFrame, + base::Bind(&SpdyWebSocketStreamSpdy2Test::DoSendHelloFrame, base::Unretained(this))); delegate.SetOnReceivedData( - base::Bind(&SpdyWebSocketStreamTest::DoClose, base::Unretained(this))); + base::Bind(&SpdyWebSocketStreamSpdy2Test::DoClose, + base::Unretained(this))); websocket_stream_.reset(new SpdyWebSocketStream(session_, &delegate)); @@ -514,7 +522,7 @@ TEST_F(SpdyWebSocketStreamTest, DestructionAfterExplicitClose) { host_port_proxy_pair_)); } -TEST_F(SpdyWebSocketStreamTest, IOPending) { +TEST_F(SpdyWebSocketStreamSpdy2Test, IOPending) { Prepare(3); scoped_ptr<spdy::SpdyFrame> settings_frame( ConstructSpdySettings(spdy_settings_to_send_)); @@ -553,12 +561,13 @@ TEST_F(SpdyWebSocketStreamTest, IOPending) { // Create a WebSocketStream under test. SpdyWebSocketStreamEventRecorder delegate(completion_callback_.callback()); delegate.SetOnCreated( - base::Bind(&SpdyWebSocketStreamTest::DoSync, base::Unretained(this))); + base::Bind(&SpdyWebSocketStreamSpdy2Test::DoSync, + base::Unretained(this))); delegate.SetOnReceivedHeader( - base::Bind(&SpdyWebSocketStreamTest::DoSendHelloFrame, + base::Bind(&SpdyWebSocketStreamSpdy2Test::DoSendHelloFrame, base::Unretained(this))); delegate.SetOnReceivedData( - base::Bind(&SpdyWebSocketStreamTest::DoSendClosingFrame, + base::Bind(&SpdyWebSocketStreamSpdy2Test::DoSendClosingFrame, base::Unretained(this))); websocket_stream_.reset(new SpdyWebSocketStream(session_, &delegate)); diff --git a/net/spdy/spdy_websocket_stream_spdy3_unittest.cc b/net/spdy/spdy_websocket_stream_spdy3_unittest.cc new file mode 100644 index 0000000..0e90553 --- /dev/null +++ b/net/spdy/spdy_websocket_stream_spdy3_unittest.cc @@ -0,0 +1,628 @@ +// Copyright (c) 2012 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_websocket_stream.h" + +#include <string> +#include <vector> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "net/base/completion_callback.h" +#include "net/proxy/proxy_server.h" +#include "net/spdy/spdy_http_utils.h" +#include "net/spdy/spdy_protocol.h" +#include "net/spdy/spdy_session.h" +#include "net/spdy/spdy_test_util_spdy3.h" +#include "net/spdy/spdy_websocket_test_util_spdy3.h" +#include "testing/gtest/include/gtest/gtest.h" + +using namespace net::test_spdy3; + +namespace { + +struct SpdyWebSocketStreamEvent { + enum EventType { + EVENT_CREATED, + EVENT_SENT_HEADERS, + EVENT_RECEIVED_HEADER, + EVENT_SENT_DATA, + EVENT_RECEIVED_DATA, + EVENT_CLOSE, + }; + SpdyWebSocketStreamEvent(EventType type, + const spdy::SpdyHeaderBlock& headers, + int result, + const std::string& data) + : event_type(type), + headers(headers), + result(result), + data(data) {} + + EventType event_type; + spdy::SpdyHeaderBlock headers; + int result; + std::string data; +}; + +} // namespace + +namespace net { + +namespace { + +class SpdyWebSocketStreamEventRecorder : public SpdyWebSocketStream::Delegate { + public: + explicit SpdyWebSocketStreamEventRecorder(const CompletionCallback& callback) + : callback_(callback) {} + virtual ~SpdyWebSocketStreamEventRecorder() {} + + typedef base::Callback<void(SpdyWebSocketStreamEvent*)> StreamEventCallback; + + void SetOnCreated(const StreamEventCallback& callback) { + on_created_ = callback; + } + void SetOnSentHeaders(const StreamEventCallback& callback) { + on_sent_headers_ = callback; + } + void SetOnReceivedHeader( + const StreamEventCallback& callback) { + on_received_header_ = callback; + } + void SetOnSentData(const StreamEventCallback& callback) { + on_sent_data_ = callback; + } + void SetOnReceivedData( + const StreamEventCallback& callback) { + on_received_data_ = callback; + } + void SetOnClose(const StreamEventCallback& callback) { + on_close_ = callback; + } + + virtual void OnCreatedSpdyStream(int result) { + events_.push_back( + SpdyWebSocketStreamEvent(SpdyWebSocketStreamEvent::EVENT_CREATED, + spdy::SpdyHeaderBlock(), + result, + std::string())); + if (!on_created_.is_null()) + on_created_.Run(&events_.back()); + } + virtual void OnSentSpdyHeaders(int result) { + events_.push_back( + SpdyWebSocketStreamEvent(SpdyWebSocketStreamEvent::EVENT_SENT_HEADERS, + spdy::SpdyHeaderBlock(), + result, + std::string())); + if (!on_sent_data_.is_null()) + on_sent_data_.Run(&events_.back()); + } + virtual int OnReceivedSpdyResponseHeader( + const spdy::SpdyHeaderBlock& headers, int status) { + events_.push_back( + SpdyWebSocketStreamEvent( + SpdyWebSocketStreamEvent::EVENT_RECEIVED_HEADER, + headers, + status, + std::string())); + if (!on_received_header_.is_null()) + on_received_header_.Run(&events_.back()); + return status; + } + virtual void OnSentSpdyData(int amount_sent) { + events_.push_back( + SpdyWebSocketStreamEvent( + SpdyWebSocketStreamEvent::EVENT_SENT_DATA, + spdy::SpdyHeaderBlock(), + amount_sent, + std::string())); + if (!on_sent_data_.is_null()) + on_sent_data_.Run(&events_.back()); + } + virtual void OnReceivedSpdyData(const char* data, int length) { + events_.push_back( + SpdyWebSocketStreamEvent( + SpdyWebSocketStreamEvent::EVENT_RECEIVED_DATA, + spdy::SpdyHeaderBlock(), + length, + std::string(data, length))); + if (!on_received_data_.is_null()) + on_received_data_.Run(&events_.back()); + } + virtual void OnCloseSpdyStream() { + events_.push_back( + SpdyWebSocketStreamEvent( + SpdyWebSocketStreamEvent::EVENT_CLOSE, + spdy::SpdyHeaderBlock(), + OK, + std::string())); + if (!on_close_.is_null()) + on_close_.Run(&events_.back()); + if (!callback_.is_null()) + callback_.Run(OK); + } + + const std::vector<SpdyWebSocketStreamEvent>& GetSeenEvents() const { + return events_; + } + + private: + std::vector<SpdyWebSocketStreamEvent> events_; + StreamEventCallback on_created_; + StreamEventCallback on_sent_headers_; + StreamEventCallback on_received_header_; + StreamEventCallback on_sent_data_; + StreamEventCallback on_received_data_; + StreamEventCallback on_close_; + CompletionCallback callback_; + + DISALLOW_COPY_AND_ASSIGN(SpdyWebSocketStreamEventRecorder); +}; + +} // namespace + +class SpdyWebSocketStreamSpdy3Test : public testing::Test { + public: + OrderedSocketData* data() { return data_.get(); } + + void DoSendHelloFrame(SpdyWebSocketStreamEvent* event) { + websocket_stream_->SendData(kMessageFrame, kMessageFrameLength); + } + + void DoSendClosingFrame(SpdyWebSocketStreamEvent* event) { + websocket_stream_->SendData(kClosingFrame, kClosingFrameLength); + } + + void DoClose(SpdyWebSocketStreamEvent* event) { + websocket_stream_->Close(); + } + + void DoSync(SpdyWebSocketStreamEvent* event) { + sync_callback_.SetResult(OK); + } + + protected: + SpdyWebSocketStreamSpdy3Test() {} + virtual ~SpdyWebSocketStreamSpdy3Test() {} + + virtual void SetUp() { + EnableCompression(false); + SpdySession::SetSSLMode(false); + + host_port_pair_.set_host("example.com"); + host_port_pair_.set_port(80); + host_port_proxy_pair_.first = host_port_pair_; + host_port_proxy_pair_.second = ProxyServer::Direct(); + + const size_t max_concurrent_streams = 1; + spdy::SettingsFlagsAndId id(0); + id.set_id(spdy::SETTINGS_MAX_CONCURRENT_STREAMS); + + id.set_flags(spdy::SETTINGS_FLAG_PLEASE_PERSIST); + spdy_settings_to_set_.push_back( + spdy::SpdySetting(id, max_concurrent_streams)); + + id.set_flags(spdy::SETTINGS_FLAG_PERSISTED); + spdy_settings_to_send_.push_back( + spdy::SpdySetting(id, max_concurrent_streams)); + } + virtual void TearDown() { + MessageLoop::current()->RunAllPending(); + } + + void EnableCompression(bool enabled) { + spdy::SpdyFramer::set_enable_compression_default(enabled); + } + void Prepare(spdy::SpdyStreamId stream_id) { + stream_id_ = stream_id; + + const char* const request_headers[] = { + "url", "ws://example.com/echo", + "origin", "http://example.com/wsdemo", + }; + + int request_header_count = arraysize(request_headers) / 2; + + const char* const response_headers[] = { + "sec-websocket-location", "ws://example.com/echo", + "sec-websocket-origin", "http://example.com/wsdemo", + }; + + int response_header_count = arraysize(response_headers) / 2; + + request_frame_.reset(ConstructSpdyWebSocketHandshakeRequestFrame( + request_headers, + request_header_count, + stream_id_, + HIGHEST)); + response_frame_.reset(ConstructSpdyWebSocketHandshakeResponseFrame( + response_headers, + response_header_count, + stream_id_, + HIGHEST)); + + message_frame_.reset(ConstructSpdyWebSocketDataFrame( + kMessageFrame, + kMessageFrameLength, + stream_id_, + false)); + + closing_frame_.reset(ConstructSpdyWebSocketDataFrame( + kClosingFrame, + kClosingFrameLength, + stream_id_, + false)); + } + int InitSession(MockRead* reads, size_t reads_count, + MockWrite* writes, size_t writes_count, + bool throttling) { + data_.reset(new OrderedSocketData(reads, reads_count, + writes, writes_count)); + session_deps_.socket_factory->AddSocketDataProvider(data_.get()); + http_session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps_); + SpdySessionPool* spdy_session_pool(http_session_->spdy_session_pool()); + + if (throttling) { + // Set max concurrent streams to 1. + spdy_session_pool->http_server_properties()->SetSpdySettings( + host_port_pair_, spdy_settings_to_set_); + } + + EXPECT_FALSE(spdy_session_pool->HasSession(host_port_proxy_pair_)); + session_ = spdy_session_pool->Get(host_port_proxy_pair_, BoundNetLog()); + EXPECT_TRUE(spdy_session_pool->HasSession(host_port_proxy_pair_)); + transport_params_ = new TransportSocketParams(host_port_pair_, MEDIUM, + false, false); + TestCompletionCallback callback; + scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle); + EXPECT_EQ(ERR_IO_PENDING, + connection->Init(host_port_pair_.ToString(), transport_params_, + MEDIUM, callback.callback(), + http_session_->GetTransportSocketPool(), + BoundNetLog())); + EXPECT_EQ(OK, callback.WaitForResult()); + return session_->InitializeWithSocket(connection.release(), false, OK); + } + void SendRequest() { + linked_ptr<spdy::SpdyHeaderBlock> headers(new spdy::SpdyHeaderBlock); + (*headers)["url"] = "ws://example.com/echo"; + (*headers)["origin"] = "http://example.com/wsdemo"; + + websocket_stream_->SendRequest(headers); + } + + spdy::SpdySettings spdy_settings_to_set_; + spdy::SpdySettings spdy_settings_to_send_; + SpdySessionDependencies session_deps_; + scoped_ptr<OrderedSocketData> data_; + scoped_refptr<HttpNetworkSession> http_session_; + scoped_refptr<SpdySession> session_; + scoped_refptr<TransportSocketParams> transport_params_; + scoped_ptr<SpdyWebSocketStream> websocket_stream_; + spdy::SpdyStreamId stream_id_; + scoped_ptr<spdy::SpdyFrame> request_frame_; + scoped_ptr<spdy::SpdyFrame> response_frame_; + scoped_ptr<spdy::SpdyFrame> message_frame_; + scoped_ptr<spdy::SpdyFrame> closing_frame_; + HostPortPair host_port_pair_; + HostPortProxyPair host_port_proxy_pair_; + TestCompletionCallback completion_callback_; + TestCompletionCallback sync_callback_; + + static const char kMessageFrame[]; + static const char kClosingFrame[]; + static const size_t kMessageFrameLength; + static const size_t kClosingFrameLength; +}; + +const char SpdyWebSocketStreamSpdy3Test::kMessageFrame[] = "\0hello\xff"; +const char SpdyWebSocketStreamSpdy3Test::kClosingFrame[] = "\xff\0"; +const size_t SpdyWebSocketStreamSpdy3Test::kMessageFrameLength = + arraysize(SpdyWebSocketStreamSpdy3Test::kMessageFrame) - 1; +const size_t SpdyWebSocketStreamSpdy3Test::kClosingFrameLength = + arraysize(SpdyWebSocketStreamSpdy3Test::kClosingFrame) - 1; + +TEST_F(SpdyWebSocketStreamSpdy3Test, Basic) { + Prepare(1); + MockWrite writes[] = { + CreateMockWrite(*request_frame_.get(), 1), + CreateMockWrite(*message_frame_.get(), 3), + CreateMockWrite(*closing_frame_.get(), 5) + }; + + MockRead reads[] = { + CreateMockRead(*response_frame_.get(), 2), + CreateMockRead(*message_frame_.get(), 4), + // Skip sequence 6 to notify closing has been sent. + CreateMockRead(*closing_frame_.get(), 7), + MockRead(SYNCHRONOUS, 0, 8) // EOF cause OnCloseSpdyStream event. + }; + + EXPECT_EQ(OK, InitSession(reads, arraysize(reads), + writes, arraysize(writes), false)); + + SpdyWebSocketStreamEventRecorder delegate(completion_callback_.callback()); + delegate.SetOnReceivedHeader( + base::Bind(&SpdyWebSocketStreamSpdy3Test::DoSendHelloFrame, + base::Unretained(this))); + delegate.SetOnReceivedData( + base::Bind(&SpdyWebSocketStreamSpdy3Test::DoSendClosingFrame, + base::Unretained(this))); + + websocket_stream_.reset(new SpdyWebSocketStream(session_, &delegate)); + + BoundNetLog net_log; + GURL url("ws://example.com/echo"); + ASSERT_EQ(OK, websocket_stream_->InitializeStream(url, HIGHEST, net_log)); + + ASSERT_TRUE(websocket_stream_->stream_); + EXPECT_EQ(stream_id_, websocket_stream_->stream_->stream_id()); + + SendRequest(); + + completion_callback_.WaitForResult(); + + websocket_stream_.reset(); + + const std::vector<SpdyWebSocketStreamEvent>& events = + delegate.GetSeenEvents(); + ASSERT_EQ(7U, events.size()); + + EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_SENT_HEADERS, + events[0].event_type); + EXPECT_LT(0, events[0].result); + EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_RECEIVED_HEADER, + events[1].event_type); + EXPECT_EQ(OK, events[1].result); + EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_SENT_DATA, + events[2].event_type); + EXPECT_EQ(static_cast<int>(kMessageFrameLength), events[2].result); + EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_RECEIVED_DATA, + events[3].event_type); + EXPECT_EQ(static_cast<int>(kMessageFrameLength), events[3].result); + EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_SENT_DATA, + events[4].event_type); + EXPECT_EQ(static_cast<int>(kClosingFrameLength), events[4].result); + EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_RECEIVED_DATA, + events[5].event_type); + EXPECT_EQ(static_cast<int>(kClosingFrameLength), events[5].result); + EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_CLOSE, + events[6].event_type); + EXPECT_EQ(OK, events[6].result); + + // EOF close SPDY session. + EXPECT_TRUE(!http_session_->spdy_session_pool()->HasSession( + host_port_proxy_pair_)); + EXPECT_TRUE(data()->at_read_eof()); + EXPECT_TRUE(data()->at_write_eof()); +} + +TEST_F(SpdyWebSocketStreamSpdy3Test, DestructionBeforeClose) { + Prepare(1); + MockWrite writes[] = { + CreateMockWrite(*request_frame_.get(), 1), + CreateMockWrite(*message_frame_.get(), 3) + }; + + MockRead reads[] = { + CreateMockRead(*response_frame_.get(), 2), + CreateMockRead(*message_frame_.get(), 4), + MockRead(ASYNC, ERR_IO_PENDING, 5) + }; + + EXPECT_EQ(OK, InitSession(reads, arraysize(reads), + writes, arraysize(writes), false)); + + SpdyWebSocketStreamEventRecorder delegate(completion_callback_.callback()); + delegate.SetOnReceivedHeader( + base::Bind(&SpdyWebSocketStreamSpdy3Test::DoSendHelloFrame, + base::Unretained(this))); + delegate.SetOnReceivedData( + base::Bind(&SpdyWebSocketStreamSpdy3Test::DoSync, + base::Unretained(this))); + + websocket_stream_.reset(new SpdyWebSocketStream(session_, &delegate)); + + BoundNetLog net_log; + GURL url("ws://example.com/echo"); + ASSERT_EQ(OK, websocket_stream_->InitializeStream(url, HIGHEST, net_log)); + + SendRequest(); + + sync_callback_.WaitForResult(); + + // WebSocketStream destruction remove its SPDY stream from the session. + EXPECT_TRUE(session_->IsStreamActive(stream_id_)); + websocket_stream_.reset(); + EXPECT_FALSE(session_->IsStreamActive(stream_id_)); + + const std::vector<SpdyWebSocketStreamEvent>& events = + delegate.GetSeenEvents(); + ASSERT_GE(4U, events.size()); + + EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_SENT_HEADERS, + events[0].event_type); + EXPECT_LT(0, events[0].result); + EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_RECEIVED_HEADER, + events[1].event_type); + EXPECT_EQ(OK, events[1].result); + EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_SENT_DATA, + events[2].event_type); + EXPECT_EQ(static_cast<int>(kMessageFrameLength), events[2].result); + EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_RECEIVED_DATA, + events[3].event_type); + EXPECT_EQ(static_cast<int>(kMessageFrameLength), events[3].result); + + EXPECT_TRUE(http_session_->spdy_session_pool()->HasSession( + host_port_proxy_pair_)); + EXPECT_TRUE(data()->at_read_eof()); + EXPECT_TRUE(data()->at_write_eof()); +} + +TEST_F(SpdyWebSocketStreamSpdy3Test, DestructionAfterExplicitClose) { + Prepare(1); + MockWrite writes[] = { + CreateMockWrite(*request_frame_.get(), 1), + CreateMockWrite(*message_frame_.get(), 3), + CreateMockWrite(*closing_frame_.get(), 5) + }; + + MockRead reads[] = { + CreateMockRead(*response_frame_.get(), 2), + CreateMockRead(*message_frame_.get(), 4), + MockRead(ASYNC, ERR_IO_PENDING, 6) + }; + + EXPECT_EQ(OK, InitSession(reads, arraysize(reads), + writes, arraysize(writes), false)); + + SpdyWebSocketStreamEventRecorder delegate(completion_callback_.callback()); + delegate.SetOnReceivedHeader( + base::Bind(&SpdyWebSocketStreamSpdy3Test::DoSendHelloFrame, + base::Unretained(this))); + delegate.SetOnReceivedData( + base::Bind(&SpdyWebSocketStreamSpdy3Test::DoClose, + base::Unretained(this))); + + websocket_stream_.reset(new SpdyWebSocketStream(session_, &delegate)); + + BoundNetLog net_log; + GURL url("ws://example.com/echo"); + ASSERT_EQ(OK, websocket_stream_->InitializeStream(url, HIGHEST, net_log)); + + SendRequest(); + + completion_callback_.WaitForResult(); + + // SPDY stream has already been removed from the session by Close(). + EXPECT_FALSE(session_->IsStreamActive(stream_id_)); + websocket_stream_.reset(); + + const std::vector<SpdyWebSocketStreamEvent>& events = + delegate.GetSeenEvents(); + ASSERT_EQ(5U, events.size()); + + EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_SENT_HEADERS, + events[0].event_type); + EXPECT_LT(0, events[0].result); + EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_RECEIVED_HEADER, + events[1].event_type); + EXPECT_EQ(OK, events[1].result); + EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_SENT_DATA, + events[2].event_type); + EXPECT_EQ(static_cast<int>(kMessageFrameLength), events[2].result); + EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_RECEIVED_DATA, + events[3].event_type); + EXPECT_EQ(static_cast<int>(kMessageFrameLength), events[3].result); + EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_CLOSE, events[4].event_type); + + EXPECT_TRUE(http_session_->spdy_session_pool()->HasSession( + host_port_proxy_pair_)); +} + +TEST_F(SpdyWebSocketStreamSpdy3Test, IOPending) { + Prepare(3); + scoped_ptr<spdy::SpdyFrame> settings_frame( + ConstructSpdySettings(spdy_settings_to_send_)); + MockWrite writes[] = { + // Setting throttling make SpdySession send settings frame automatically. + CreateMockWrite(*settings_frame.get(), 1), + CreateMockWrite(*request_frame_.get(), 3), + CreateMockWrite(*message_frame_.get(), 6), + CreateMockWrite(*closing_frame_.get(), 9) + }; + + MockRead reads[] = { + CreateMockRead(*settings_frame.get(), 2), + CreateMockRead(*response_frame_.get(), 4), + // Skip sequence 5 (I/O Pending) + CreateMockRead(*message_frame_.get(), 7), + // Skip sequence 8 (I/O Pending) + CreateMockRead(*closing_frame_.get(), 10), + MockRead(SYNCHRONOUS, 0, 11) // EOF cause OnCloseSpdyStream event. + }; + + EXPECT_EQ(OK, InitSession(reads, arraysize(reads), + writes, arraysize(writes), true)); + + // Create a dummy WebSocketStream which cause ERR_IO_PENDING to another + // WebSocketStream under test. + SpdyWebSocketStreamEventRecorder block_delegate((CompletionCallback())); + + scoped_ptr<SpdyWebSocketStream> block_stream( + new SpdyWebSocketStream(session_, &block_delegate)); + BoundNetLog block_net_log; + GURL block_url("ws://example.com/block"); + ASSERT_EQ(OK, + block_stream->InitializeStream(block_url, HIGHEST, block_net_log)); + + // Create a WebSocketStream under test. + SpdyWebSocketStreamEventRecorder delegate(completion_callback_.callback()); + delegate.SetOnCreated( + base::Bind(&SpdyWebSocketStreamSpdy3Test::DoSync, + base::Unretained(this))); + delegate.SetOnReceivedHeader( + base::Bind(&SpdyWebSocketStreamSpdy3Test::DoSendHelloFrame, + base::Unretained(this))); + delegate.SetOnReceivedData( + base::Bind(&SpdyWebSocketStreamSpdy3Test::DoSendClosingFrame, + base::Unretained(this))); + + websocket_stream_.reset(new SpdyWebSocketStream(session_, &delegate)); + BoundNetLog net_log; + GURL url("ws://example.com/echo"); + ASSERT_EQ(ERR_IO_PENDING, websocket_stream_->InitializeStream( + url, HIGHEST, net_log)); + + // Delete the fist stream to allow create the second stream. + block_stream.reset(); + ASSERT_EQ(OK, sync_callback_.WaitForResult()); + + SendRequest(); + + completion_callback_.WaitForResult(); + + websocket_stream_.reset(); + + const std::vector<SpdyWebSocketStreamEvent>& block_events = + block_delegate.GetSeenEvents(); + ASSERT_EQ(0U, block_events.size()); + + const std::vector<SpdyWebSocketStreamEvent>& events = + delegate.GetSeenEvents(); + ASSERT_EQ(8U, events.size()); + EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_CREATED, + events[0].event_type); + EXPECT_EQ(0, events[0].result); + EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_SENT_HEADERS, + events[1].event_type); + EXPECT_LT(0, events[1].result); + EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_RECEIVED_HEADER, + events[2].event_type); + EXPECT_EQ(OK, events[2].result); + EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_SENT_DATA, + events[3].event_type); + EXPECT_EQ(static_cast<int>(kMessageFrameLength), events[3].result); + EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_RECEIVED_DATA, + events[4].event_type); + EXPECT_EQ(static_cast<int>(kMessageFrameLength), events[4].result); + EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_SENT_DATA, + events[5].event_type); + EXPECT_EQ(static_cast<int>(kClosingFrameLength), events[5].result); + EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_RECEIVED_DATA, + events[6].event_type); + EXPECT_EQ(static_cast<int>(kClosingFrameLength), events[6].result); + EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_CLOSE, + events[7].event_type); + EXPECT_EQ(OK, events[7].result); + + // EOF close SPDY session. + EXPECT_TRUE(!http_session_->spdy_session_pool()->HasSession( + host_port_proxy_pair_)); + EXPECT_TRUE(data()->at_read_eof()); + EXPECT_TRUE(data()->at_write_eof()); +} + +} // namespace net diff --git a/net/spdy/spdy_websocket_test_util.cc b/net/spdy/spdy_websocket_test_util_spdy2.cc index ad635fb..2c31460 100644 --- a/net/spdy/spdy_websocket_test_util.cc +++ b/net/spdy/spdy_websocket_test_util_spdy2.cc @@ -1,12 +1,12 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 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_websocket_test_util.h" +#include "net/spdy/spdy_websocket_test_util_spdy2.h" #include "net/spdy/spdy_framer.h" #include "net/spdy/spdy_http_utils.h" -#include "net/spdy/spdy_test_util.h" +#include "net/spdy/spdy_test_util_spdy2.h" static const int kDefaultAssociatedStreamId = 0; static const bool kDefaultCompressed = false; @@ -17,6 +17,8 @@ static const int kDefaultExtraHeaderCount = 0; namespace net { +namespace test_spdy2 { + spdy::SpdyFrame* ConstructSpdyWebSocketHandshakeRequestFrame( const char* const headers[], int header_count, @@ -90,4 +92,6 @@ spdy::SpdyFrame* ConstructSpdyWebSocketDataFrame( fin ? spdy::DATA_FLAG_FIN : spdy::DATA_FLAG_NONE); } +} // namespace test_spdy2 + } // namespace net diff --git a/net/spdy/spdy_websocket_test_util.h b/net/spdy/spdy_websocket_test_util_spdy2.h index 5c16d07..acdd9b3 100644 --- a/net/spdy/spdy_websocket_test_util.h +++ b/net/spdy/spdy_websocket_test_util_spdy2.h @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 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. @@ -11,6 +11,8 @@ namespace net { +namespace test_spdy2 { + // Construct a WebSocket over SPDY handshake request packet. spdy::SpdyFrame* ConstructSpdyWebSocketHandshakeRequestFrame( const char* const headers[], @@ -32,6 +34,8 @@ spdy::SpdyFrame* ConstructSpdyWebSocketDataFrame( spdy::SpdyStreamId stream_id, bool fin); +} // namespace test_spdy2 + } // namespace net #endif // NET_SPDY_SPDY_WEBSOCKET_TEST_UTIL_H_ diff --git a/net/spdy/spdy_websocket_test_util_spdy3.cc b/net/spdy/spdy_websocket_test_util_spdy3.cc new file mode 100644 index 0000000..3c3e570 --- /dev/null +++ b/net/spdy/spdy_websocket_test_util_spdy3.cc @@ -0,0 +1,97 @@ +// Copyright (c) 2012 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_websocket_test_util_spdy3.h" + +#include "net/spdy/spdy_framer.h" +#include "net/spdy/spdy_http_utils.h" +#include "net/spdy/spdy_test_util_spdy3.h" + +static const int kDefaultAssociatedStreamId = 0; +static const bool kDefaultCompressed = false; +static const char* const kDefaultDataPointer = NULL; +static const uint32 kDefaultDataLength = 0; +static const char** const kDefaultExtraHeaders = NULL; +static const int kDefaultExtraHeaderCount = 0; + +namespace net { + +namespace test_spdy3 { + +spdy::SpdyFrame* ConstructSpdyWebSocketHandshakeRequestFrame( + const char* const headers[], + int header_count, + spdy::SpdyStreamId stream_id, + RequestPriority request_priority) { + + // SPDY SYN_STREAM control frame header. + const SpdyHeaderInfo kSynStreamHeader = { + spdy::SYN_STREAM, + stream_id, + kDefaultAssociatedStreamId, + ConvertRequestPriorityToSpdyPriority(request_priority), + spdy::CONTROL_FLAG_NONE, + kDefaultCompressed, + spdy::INVALID, + kDefaultDataPointer, + kDefaultDataLength, + spdy::DATA_FLAG_NONE + }; + + // Construct SPDY SYN_STREAM control frame. + return ConstructSpdyPacket( + kSynStreamHeader, + kDefaultExtraHeaders, + kDefaultExtraHeaderCount, + headers, + header_count); +} + +spdy::SpdyFrame* ConstructSpdyWebSocketHandshakeResponseFrame( + const char* const headers[], + int header_count, + spdy::SpdyStreamId stream_id, + RequestPriority request_priority) { + + // SPDY SYN_REPLY control frame header. + const SpdyHeaderInfo kSynReplyHeader = { + spdy::SYN_REPLY, + stream_id, + kDefaultAssociatedStreamId, + ConvertRequestPriorityToSpdyPriority(request_priority), + spdy::CONTROL_FLAG_NONE, + kDefaultCompressed, + spdy::INVALID, + kDefaultDataPointer, + kDefaultDataLength, + spdy::DATA_FLAG_NONE + }; + + // Construct SPDY SYN_REPLY control frame. + return ConstructSpdyPacket( + kSynReplyHeader, + kDefaultExtraHeaders, + kDefaultExtraHeaderCount, + headers, + header_count); +} + +spdy::SpdyFrame* ConstructSpdyWebSocketDataFrame( + const char* data, + int len, + spdy::SpdyStreamId stream_id, + bool fin) { + + // Construct SPDY data frame. + spdy::SpdyFramer framer; + return framer.CreateDataFrame( + stream_id, + data, + len, + fin ? spdy::DATA_FLAG_FIN : spdy::DATA_FLAG_NONE); +} + +} // namespace test_spdy3 + +} // namespace net diff --git a/net/spdy/spdy_websocket_test_util_spdy3.h b/net/spdy/spdy_websocket_test_util_spdy3.h new file mode 100644 index 0000000..d4d32f8 --- /dev/null +++ b/net/spdy/spdy_websocket_test_util_spdy3.h @@ -0,0 +1,41 @@ +// Copyright (c) 2012 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. + +#ifndef NET_SPDY_SPDY_WEBSOCKET_TEST_UTIL_H_ +#define NET_SPDY_SPDY_WEBSOCKET_TEST_UTIL_H_ +#pragma once + +#include "net/base/request_priority.h" +#include "net/spdy/spdy_protocol.h" + +namespace net { + +namespace test_spdy3 { + +// Construct a WebSocket over SPDY handshake request packet. +spdy::SpdyFrame* ConstructSpdyWebSocketHandshakeRequestFrame( + const char* const headers[], + int header_count, + spdy::SpdyStreamId stream_id, + RequestPriority request_priority); + +// Construct a WebSocket over SPDY handshake response packet. +spdy::SpdyFrame* ConstructSpdyWebSocketHandshakeResponseFrame( + const char* const headers[], + int header_count, + spdy::SpdyStreamId stream_id, + RequestPriority request_priority); + +// Construct a WebSocket over SPDY data packet. +spdy::SpdyFrame* ConstructSpdyWebSocketDataFrame( + const char* data, + int len, + spdy::SpdyStreamId stream_id, + bool fin); + +} // namespace test_spdy3 + +} // namespace net + +#endif // NET_SPDY_SPDY_WEBSOCKET_TEST_UTIL_H_ diff --git a/net/websockets/websocket_job.h b/net/websockets/websocket_job.h index 64f20ce..96fa054 100644 --- a/net/websockets/websocket_job.h +++ b/net/websockets/websocket_job.h @@ -84,7 +84,8 @@ class NET_EXPORT WebSocketJob private: friend class WebSocketThrottle; - friend class WebSocketJobTest; + friend class WebSocketJobSpdy2Test; + friend class WebSocketJobSpdy3Test; virtual ~WebSocketJob(); bool SendHandshakeRequest(const char* data, int len); diff --git a/net/websockets/websocket_job_unittest.cc b/net/websockets/websocket_job_spdy2_unittest.cc index 77dc9fb..36e7802 100644 --- a/net/websockets/websocket_job_unittest.cc +++ b/net/websockets/websocket_job_spdy2_unittest.cc @@ -28,14 +28,16 @@ #include "net/socket/socket_test_util.h" #include "net/socket_stream/socket_stream.h" #include "net/spdy/spdy_session.h" -#include "net/spdy/spdy_test_util.h" -#include "net/spdy/spdy_websocket_test_util.h" +#include "net/spdy/spdy_test_util_spdy2.h" +#include "net/spdy/spdy_websocket_test_util_spdy2.h" #include "net/url_request/url_request_context.h" #include "net/websockets/websocket_throttle.h" #include "testing/gtest/include/gtest/gtest.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/platform_test.h" +using namespace net::test_spdy2; + namespace { class MockSocketStream : public net::SocketStream { @@ -260,10 +262,10 @@ class MockHttpTransactionFactory : public net::HttpTransactionFactory { data_ = data; net::MockConnect connect_data(net::SYNCHRONOUS, net::OK); data_->set_connect_data(connect_data); - session_deps_.reset(new net::SpdySessionDependencies); + session_deps_.reset(new SpdySessionDependencies); session_deps_->socket_factory->AddSocketDataProvider(data_); http_session_ = - net::SpdySessionDependencies::SpdyCreateSession(session_deps_.get()); + SpdySessionDependencies::SpdyCreateSession(session_deps_.get()); host_port_pair_.set_host("example.com"); host_port_pair_.set_port(80); host_port_proxy_pair_.first = host_port_pair_; @@ -302,18 +304,18 @@ class MockHttpTransactionFactory : public net::HttpTransactionFactory { } private: net::OrderedSocketData* data_; - scoped_ptr<net::SpdySessionDependencies> session_deps_; + scoped_ptr<SpdySessionDependencies> session_deps_; scoped_refptr<net::HttpNetworkSession> http_session_; scoped_refptr<net::TransportSocketParams> transport_params_; scoped_refptr<net::SpdySession> session_; net::HostPortPair host_port_pair_; net::HostPortProxyPair host_port_proxy_pair_; }; -} +} // namespace namespace net { -class WebSocketJobTest : public PlatformTest { +class WebSocketJobSpdy2Test : public PlatformTest { public: virtual void SetUp() { spdy::SpdyFramer::set_enable_compression_default(false); @@ -473,7 +475,7 @@ class WebSocketJobTest : public PlatformTest { static const size_t kDataWorldLength; }; -const char WebSocketJobTest::kHandshakeRequestWithoutCookie[] = +const char WebSocketJobSpdy2Test::kHandshakeRequestWithoutCookie[] = "GET /demo HTTP/1.1\r\n" "Host: example.com\r\n" "Connection: Upgrade\r\n" @@ -485,7 +487,7 @@ const char WebSocketJobTest::kHandshakeRequestWithoutCookie[] = "\r\n" "^n:ds[4U"; -const char WebSocketJobTest::kHandshakeRequestWithCookie[] = +const char WebSocketJobSpdy2Test::kHandshakeRequestWithCookie[] = "GET /demo HTTP/1.1\r\n" "Host: example.com\r\n" "Connection: Upgrade\r\n" @@ -498,7 +500,7 @@ const char WebSocketJobTest::kHandshakeRequestWithCookie[] = "\r\n" "^n:ds[4U"; -const char WebSocketJobTest::kHandshakeRequestWithFilteredCookie[] = +const char WebSocketJobSpdy2Test::kHandshakeRequestWithFilteredCookie[] = "GET /demo HTTP/1.1\r\n" "Host: example.com\r\n" "Connection: Upgrade\r\n" @@ -511,7 +513,7 @@ const char WebSocketJobTest::kHandshakeRequestWithFilteredCookie[] = "\r\n" "^n:ds[4U"; -const char WebSocketJobTest::kHandshakeResponseWithoutCookie[] = +const char WebSocketJobSpdy2Test::kHandshakeResponseWithoutCookie[] = "HTTP/1.1 101 WebSocket Protocol Handshake\r\n" "Upgrade: WebSocket\r\n" "Connection: Upgrade\r\n" @@ -521,7 +523,7 @@ const char WebSocketJobTest::kHandshakeResponseWithoutCookie[] = "\r\n" "8jKS'y:G*Co,Wxa-"; -const char WebSocketJobTest::kHandshakeResponseWithCookie[] = +const char WebSocketJobSpdy2Test::kHandshakeResponseWithCookie[] = "HTTP/1.1 101 WebSocket Protocol Handshake\r\n" "Upgrade: WebSocket\r\n" "Connection: Upgrade\r\n" @@ -532,42 +534,42 @@ const char WebSocketJobTest::kHandshakeResponseWithCookie[] = "\r\n" "8jKS'y:G*Co,Wxa-"; -const char WebSocketJobTest::kDataHello[] = "Hello, "; +const char WebSocketJobSpdy2Test::kDataHello[] = "Hello, "; -const char WebSocketJobTest::kDataWorld[] = "World!\n"; +const char WebSocketJobSpdy2Test::kDataWorld[] = "World!\n"; // TODO(toyoshim): I should clarify which WebSocket headers for handshake must // be exported to SPDY SYN_STREAM and SYN_REPLY. // Because it depends on HyBi versions, just define it as follow for now. -const char* const WebSocketJobTest::kHandshakeRequestForSpdy[] = { +const char* const WebSocketJobSpdy2Test::kHandshakeRequestForSpdy[] = { "host", "example.com", "origin", "http://example.com", "sec-websocket-protocol", "sample", "url", "ws://example.com/demo" }; -const char* const WebSocketJobTest::kHandshakeResponseForSpdy[] = { +const char* const WebSocketJobSpdy2Test::kHandshakeResponseForSpdy[] = { "sec-websocket-origin", "http://example.com", "sec-websocket-location", "ws://example.com/demo", "sec-websocket-protocol", "sample", }; -const size_t WebSocketJobTest::kHandshakeRequestWithoutCookieLength = +const size_t WebSocketJobSpdy2Test::kHandshakeRequestWithoutCookieLength = arraysize(kHandshakeRequestWithoutCookie) - 1; -const size_t WebSocketJobTest::kHandshakeRequestWithCookieLength = +const size_t WebSocketJobSpdy2Test::kHandshakeRequestWithCookieLength = arraysize(kHandshakeRequestWithCookie) - 1; -const size_t WebSocketJobTest::kHandshakeRequestWithFilteredCookieLength = +const size_t WebSocketJobSpdy2Test::kHandshakeRequestWithFilteredCookieLength = arraysize(kHandshakeRequestWithFilteredCookie) - 1; -const size_t WebSocketJobTest::kHandshakeResponseWithoutCookieLength = +const size_t WebSocketJobSpdy2Test::kHandshakeResponseWithoutCookieLength = arraysize(kHandshakeResponseWithoutCookie) - 1; -const size_t WebSocketJobTest::kHandshakeResponseWithCookieLength = +const size_t WebSocketJobSpdy2Test::kHandshakeResponseWithCookieLength = arraysize(kHandshakeResponseWithCookie) - 1; -const size_t WebSocketJobTest::kDataHelloLength = +const size_t WebSocketJobSpdy2Test::kDataHelloLength = arraysize(kDataHello) - 1; -const size_t WebSocketJobTest::kDataWorldLength = +const size_t WebSocketJobSpdy2Test::kDataWorldLength = arraysize(kDataWorld) - 1; -void WebSocketJobTest::TestSimpleHandshake() { +void WebSocketJobSpdy2Test::TestSimpleHandshake() { GURL url("ws://example.com/demo"); MockSocketStreamDelegate delegate; InitWebSocketJob(url, &delegate, STREAM_MOCK_SOCKET); @@ -590,7 +592,7 @@ void WebSocketJobTest::TestSimpleHandshake() { CloseWebSocketJob(); } -void WebSocketJobTest::TestSlowHandshake() { +void WebSocketJobSpdy2Test::TestSlowHandshake() { GURL url("ws://example.com/demo"); MockSocketStreamDelegate delegate; InitWebSocketJob(url, &delegate, STREAM_MOCK_SOCKET); @@ -628,7 +630,7 @@ void WebSocketJobTest::TestSlowHandshake() { CloseWebSocketJob(); } -TEST_F(WebSocketJobTest, DelayedCookies) { +TEST_F(WebSocketJobSpdy2Test, DelayedCookies) { WebSocketJob::set_websocket_over_spdy_enabled(true); GURL url("ws://example.com/demo"); GURL cookieUrl("http://example.com/demo"); @@ -668,7 +670,7 @@ TEST_F(WebSocketJobTest, DelayedCookies) { CloseWebSocketJob(); } -void WebSocketJobTest::TestHandshakeWithCookie() { +void WebSocketJobSpdy2Test::TestHandshakeWithCookie() { GURL url("ws://example.com/demo"); GURL cookieUrl("http://example.com/demo"); CookieOptions cookie_options; @@ -711,7 +713,7 @@ void WebSocketJobTest::TestHandshakeWithCookie() { CloseWebSocketJob(); } -void WebSocketJobTest::TestHandshakeWithCookieButNotAllowed() { +void WebSocketJobSpdy2Test::TestHandshakeWithCookieButNotAllowed() { GURL url("ws://example.com/demo"); GURL cookieUrl("http://example.com/demo"); CookieOptions cookie_options; @@ -752,7 +754,7 @@ void WebSocketJobTest::TestHandshakeWithCookieButNotAllowed() { CloseWebSocketJob(); } -void WebSocketJobTest::TestHSTSUpgrade() { +void WebSocketJobSpdy2Test::TestHSTSUpgrade() { GURL url("ws://upgrademe.com/"); MockSocketStreamDelegate delegate; scoped_refptr<SocketStreamJob> job = @@ -770,7 +772,7 @@ void WebSocketJobTest::TestHSTSUpgrade() { job->DetachDelegate(); } -void WebSocketJobTest::TestInvalidSendData() { +void WebSocketJobSpdy2Test::TestInvalidSendData() { GURL url("ws://example.com/demo"); MockSocketStreamDelegate delegate; InitWebSocketJob(url, &delegate, STREAM_MOCK_SOCKET); @@ -801,7 +803,8 @@ void WebSocketJobTest::TestInvalidSendData() { // packets in comparison with the MockWrite array and emulating in-coming // packets with MockRead array. -void WebSocketJobTest::TestConnectByWebSocket(ThrottlingOption throttling) { +void WebSocketJobSpdy2Test::TestConnectByWebSocket( + ThrottlingOption throttling) { // This is a test for verifying cooperation between WebSocketJob and // SocketStream. If |throttling| was |THROTTLING_OFF|, it test basic // situation. If |throttling| was |THROTTLING_ON|, throttling limits the @@ -832,16 +835,17 @@ void WebSocketJobTest::TestConnectByWebSocket(ThrottlingOption throttling) { GURL url("ws://example.com/demo"); MockSocketStreamDelegate delegate; - WebSocketJobTest* test = this; + WebSocketJobSpdy2Test* test = this; if (throttling == THROTTLING_ON) delegate.SetOnStartOpenConnection( - base::Bind(&WebSocketJobTest::DoSync, base::Unretained(test))); + base::Bind(&WebSocketJobSpdy2Test::DoSync, base::Unretained(test))); delegate.SetOnConnected( - base::Bind(&WebSocketJobTest::DoSendRequest, base::Unretained(test))); + base::Bind(&WebSocketJobSpdy2Test::DoSendRequest, + base::Unretained(test))); delegate.SetOnReceivedData( - base::Bind(&WebSocketJobTest::DoSendData, base::Unretained(test))); + base::Bind(&WebSocketJobSpdy2Test::DoSendData, base::Unretained(test))); delegate.SetOnClose( - base::Bind(&WebSocketJobTest::DoSync, base::Unretained(test))); + base::Bind(&WebSocketJobSpdy2Test::DoSync, base::Unretained(test))); InitWebSocketJob(url, &delegate, STREAM_SOCKET); scoped_refptr<WebSocketJob> block_websocket; @@ -872,7 +876,7 @@ void WebSocketJobTest::TestConnectByWebSocket(ThrottlingOption throttling) { EXPECT_EQ(WebSocketJob::CLOSED, GetWebSocketJobState()); } -void WebSocketJobTest::TestConnectBySpdy( +void WebSocketJobSpdy2Test::TestConnectBySpdy( SpdyOption spdy, ThrottlingOption throttling) { // This is a test for verifying cooperation between WebSocketJob and // SocketStream in the situation we have SPDY session to the server. If @@ -947,16 +951,17 @@ void WebSocketJobTest::TestConnectBySpdy( GURL url("ws://example.com/demo"); MockSocketStreamDelegate delegate; - WebSocketJobTest* test = this; + WebSocketJobSpdy2Test* test = this; if (throttling == THROTTLING_ON) delegate.SetOnStartOpenConnection( - base::Bind(&WebSocketJobTest::DoSync, base::Unretained(test))); + base::Bind(&WebSocketJobSpdy2Test::DoSync, base::Unretained(test))); delegate.SetOnConnected( - base::Bind(&WebSocketJobTest::DoSendRequest, base::Unretained(test))); + base::Bind(&WebSocketJobSpdy2Test::DoSendRequest, + base::Unretained(test))); delegate.SetOnReceivedData( - base::Bind(&WebSocketJobTest::DoSendData, base::Unretained(test))); + base::Bind(&WebSocketJobSpdy2Test::DoSendData, base::Unretained(test))); delegate.SetOnClose( - base::Bind(&WebSocketJobTest::DoSync, base::Unretained(test))); + base::Bind(&WebSocketJobSpdy2Test::DoSync, base::Unretained(test))); InitWebSocketJob(url, &delegate, STREAM_SPDY_WEBSOCKET); scoped_refptr<WebSocketJob> block_websocket; @@ -988,102 +993,102 @@ void WebSocketJobTest::TestConnectBySpdy( } // Execute tests in both spdy-disabled mode and spdy-enabled mode. -TEST_F(WebSocketJobTest, SimpleHandshake) { +TEST_F(WebSocketJobSpdy2Test, SimpleHandshake) { WebSocketJob::set_websocket_over_spdy_enabled(false); TestSimpleHandshake(); } -TEST_F(WebSocketJobTest, SlowHandshake) { +TEST_F(WebSocketJobSpdy2Test, SlowHandshake) { WebSocketJob::set_websocket_over_spdy_enabled(false); TestSlowHandshake(); } -TEST_F(WebSocketJobTest, HandshakeWithCookie) { +TEST_F(WebSocketJobSpdy2Test, HandshakeWithCookie) { WebSocketJob::set_websocket_over_spdy_enabled(false); TestHandshakeWithCookie(); } -TEST_F(WebSocketJobTest, HandshakeWithCookieButNotAllowed) { +TEST_F(WebSocketJobSpdy2Test, HandshakeWithCookieButNotAllowed) { WebSocketJob::set_websocket_over_spdy_enabled(false); TestHandshakeWithCookieButNotAllowed(); } -TEST_F(WebSocketJobTest, HSTSUpgrade) { +TEST_F(WebSocketJobSpdy2Test, HSTSUpgrade) { WebSocketJob::set_websocket_over_spdy_enabled(false); TestHSTSUpgrade(); } -TEST_F(WebSocketJobTest, InvalidSendData) { +TEST_F(WebSocketJobSpdy2Test, InvalidSendData) { WebSocketJob::set_websocket_over_spdy_enabled(false); TestInvalidSendData(); } -TEST_F(WebSocketJobTest, SimpleHandshakeSpdyEnabled) { +TEST_F(WebSocketJobSpdy2Test, SimpleHandshakeSpdyEnabled) { WebSocketJob::set_websocket_over_spdy_enabled(true); TestSimpleHandshake(); } -TEST_F(WebSocketJobTest, SlowHandshakeSpdyEnabled) { +TEST_F(WebSocketJobSpdy2Test, SlowHandshakeSpdyEnabled) { WebSocketJob::set_websocket_over_spdy_enabled(true); TestSlowHandshake(); } -TEST_F(WebSocketJobTest, HandshakeWithCookieSpdyEnabled) { +TEST_F(WebSocketJobSpdy2Test, HandshakeWithCookieSpdyEnabled) { WebSocketJob::set_websocket_over_spdy_enabled(true); TestHandshakeWithCookie(); } -TEST_F(WebSocketJobTest, HandshakeWithCookieButNotAllowedSpdyEnabled) { +TEST_F(WebSocketJobSpdy2Test, HandshakeWithCookieButNotAllowedSpdyEnabled) { WebSocketJob::set_websocket_over_spdy_enabled(true); TestHandshakeWithCookieButNotAllowed(); } -TEST_F(WebSocketJobTest, HSTSUpgradeSpdyEnabled) { +TEST_F(WebSocketJobSpdy2Test, HSTSUpgradeSpdyEnabled) { WebSocketJob::set_websocket_over_spdy_enabled(true); TestHSTSUpgrade(); } -TEST_F(WebSocketJobTest, InvalidSendDataSpdyEnabled) { +TEST_F(WebSocketJobSpdy2Test, InvalidSendDataSpdyEnabled) { WebSocketJob::set_websocket_over_spdy_enabled(true); TestInvalidSendData(); } -TEST_F(WebSocketJobTest, ConnectByWebSocket) { +TEST_F(WebSocketJobSpdy2Test, ConnectByWebSocket) { WebSocketJob::set_websocket_over_spdy_enabled(false); TestConnectByWebSocket(THROTTLING_OFF); } -TEST_F(WebSocketJobTest, ConnectByWebSocketSpdyEnabled) { +TEST_F(WebSocketJobSpdy2Test, ConnectByWebSocketSpdyEnabled) { WebSocketJob::set_websocket_over_spdy_enabled(true); TestConnectByWebSocket(THROTTLING_OFF); } -TEST_F(WebSocketJobTest, ConnectBySpdy) { +TEST_F(WebSocketJobSpdy2Test, ConnectBySpdy) { WebSocketJob::set_websocket_over_spdy_enabled(false); TestConnectBySpdy(SPDY_OFF, THROTTLING_OFF); } -TEST_F(WebSocketJobTest, ConnectBySpdySpdyEnabled) { +TEST_F(WebSocketJobSpdy2Test, ConnectBySpdySpdyEnabled) { WebSocketJob::set_websocket_over_spdy_enabled(true); TestConnectBySpdy(SPDY_ON, THROTTLING_OFF); } -TEST_F(WebSocketJobTest, ThrottlingWebSocket) { +TEST_F(WebSocketJobSpdy2Test, ThrottlingWebSocket) { WebSocketJob::set_websocket_over_spdy_enabled(false); TestConnectByWebSocket(THROTTLING_ON); } -TEST_F(WebSocketJobTest, ThrottlingWebSocketSpdyEnabled) { +TEST_F(WebSocketJobSpdy2Test, ThrottlingWebSocketSpdyEnabled) { WebSocketJob::set_websocket_over_spdy_enabled(true); TestConnectByWebSocket(THROTTLING_ON); } -TEST_F(WebSocketJobTest, ThrottlingSpdy) { +TEST_F(WebSocketJobSpdy2Test, ThrottlingSpdy) { WebSocketJob::set_websocket_over_spdy_enabled(false); TestConnectBySpdy(SPDY_OFF, THROTTLING_ON); } -TEST_F(WebSocketJobTest, ThrottlingSpdySpdyEnabled) { +TEST_F(WebSocketJobSpdy2Test, ThrottlingSpdySpdyEnabled) { WebSocketJob::set_websocket_over_spdy_enabled(true); TestConnectBySpdy(SPDY_ON, THROTTLING_ON); } diff --git a/net/websockets/websocket_job_spdy3_unittest.cc b/net/websockets/websocket_job_spdy3_unittest.cc new file mode 100644 index 0000000..19a089d --- /dev/null +++ b/net/websockets/websocket_job_spdy3_unittest.cc @@ -0,0 +1,1099 @@ +// Copyright (c) 2012 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/websockets/websocket_job.h" + +#include <string> +#include <vector> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/callback.h" +#include "base/memory/ref_counted.h" +#include "base/string_split.h" +#include "base/string_util.h" +#include "googleurl/src/gurl.h" +#include "net/base/completion_callback.h" +#include "net/base/cookie_store.h" +#include "net/base/cookie_store_test_helpers.h" +#include "net/base/mock_host_resolver.h" +#include "net/base/net_errors.h" +#include "net/base/ssl_config_service.h" +#include "net/base/sys_addrinfo.h" +#include "net/base/test_completion_callback.h" +#include "net/base/transport_security_state.h" +#include "net/http/http_transaction_factory.h" +#include "net/proxy/proxy_service.h" +#include "net/socket/socket_test_util.h" +#include "net/socket_stream/socket_stream.h" +#include "net/spdy/spdy_session.h" +#include "net/spdy/spdy_test_util_spdy3.h" +#include "net/spdy/spdy_websocket_test_util_spdy3.h" +#include "net/url_request/url_request_context.h" +#include "net/websockets/websocket_throttle.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/platform_test.h" + +using namespace net::test_spdy3; + +namespace { + +class MockSocketStream : public net::SocketStream { + public: + MockSocketStream(const GURL& url, net::SocketStream::Delegate* delegate) + : SocketStream(url, delegate) {} + virtual ~MockSocketStream() {} + + virtual void Connect() OVERRIDE {} + virtual bool SendData(const char* data, int len) OVERRIDE { + sent_data_ += std::string(data, len); + return true; + } + + virtual void Close() OVERRIDE {} + virtual void RestartWithAuth( + const net::AuthCredentials& credentials) OVERRIDE { + } + + virtual void DetachDelegate() OVERRIDE { + delegate_ = NULL; + } + + const std::string& sent_data() const { + return sent_data_; + } + + private: + std::string sent_data_; +}; + +class MockSocketStreamDelegate : public net::SocketStream::Delegate { + public: + MockSocketStreamDelegate() + : amount_sent_(0), allow_all_cookies_(true) {} + void set_allow_all_cookies(bool allow_all_cookies) { + allow_all_cookies_ = allow_all_cookies; + } + virtual ~MockSocketStreamDelegate() {} + + void SetOnStartOpenConnection(const base::Closure& callback) { + on_start_open_connection_ = callback; + } + void SetOnConnected(const base::Closure& callback) { + on_connected_ = callback; + } + void SetOnSentData(const base::Closure& callback) { + on_sent_data_ = callback; + } + void SetOnReceivedData(const base::Closure& callback) { + on_received_data_ = callback; + } + void SetOnClose(const base::Closure& callback) { + on_close_ = callback; + } + + virtual int OnStartOpenConnection(net::SocketStream* socket, + const net::CompletionCallback& callback) { + if (!on_start_open_connection_.is_null()) + on_start_open_connection_.Run(); + return net::OK; + } + virtual void OnConnected(net::SocketStream* socket, + int max_pending_send_allowed) { + if (!on_connected_.is_null()) + on_connected_.Run(); + } + virtual void OnSentData(net::SocketStream* socket, int amount_sent) { + amount_sent_ += amount_sent; + if (!on_sent_data_.is_null()) + on_sent_data_.Run(); + } + virtual void OnReceivedData(net::SocketStream* socket, + const char* data, int len) { + received_data_ += std::string(data, len); + if (!on_received_data_.is_null()) + on_received_data_.Run(); + } + virtual void OnClose(net::SocketStream* socket) { + if (!on_close_.is_null()) + on_close_.Run(); + } + virtual bool CanGetCookies(net::SocketStream* socket, const GURL& url) { + return allow_all_cookies_; + } + virtual bool CanSetCookie(net::SocketStream* request, + const GURL& url, + const std::string& cookie_line, + net::CookieOptions* options) { + return allow_all_cookies_; + } + + size_t amount_sent() const { return amount_sent_; } + const std::string& received_data() const { return received_data_; } + + private: + int amount_sent_; + bool allow_all_cookies_; + std::string received_data_; + base::Closure on_start_open_connection_; + base::Closure on_connected_; + base::Closure on_sent_data_; + base::Closure on_received_data_; + base::Closure on_close_; +}; + +class MockCookieStore : public net::CookieStore { + public: + struct Entry { + GURL url; + std::string cookie_line; + net::CookieOptions options; + }; + MockCookieStore() {} + + virtual bool SetCookieWithOptions(const GURL& url, + const std::string& cookie_line, + const net::CookieOptions& options) { + Entry entry; + entry.url = url; + entry.cookie_line = cookie_line; + entry.options = options; + entries_.push_back(entry); + return true; + } + + virtual void SetCookieWithOptionsAsync( + const GURL& url, + const std::string& cookie_line, + const net::CookieOptions& options, + const SetCookiesCallback& callback) { + bool result = SetCookieWithOptions(url, cookie_line, options); + if (!callback.is_null()) + callback.Run(result); + } + virtual std::string GetCookiesWithOptions( + const GURL& url, + const net::CookieOptions& options) { + std::string result; + for (size_t i = 0; i < entries_.size(); i++) { + Entry &entry = entries_[i]; + if (url == entry.url) { + if (!result.empty()) { + result += "; "; + } + result += entry.cookie_line; + } + } + return result; + } + virtual void GetCookiesWithOptionsAsync( + const GURL& url, + const net::CookieOptions& options, + const GetCookiesCallback& callback) { + if (!callback.is_null()) + callback.Run(GetCookiesWithOptions(url, options)); + } + virtual void GetCookiesWithInfo(const GURL& url, + const net::CookieOptions& options, + std::string* cookie_line, + std::vector<CookieInfo>* cookie_infos) { + ADD_FAILURE(); + } + virtual void GetCookiesWithInfoAsync( + const GURL& url, + const net::CookieOptions& options, + const GetCookieInfoCallback& callback) { + ADD_FAILURE(); + } + virtual void DeleteCookie(const GURL& url, + const std::string& cookie_name) { + ADD_FAILURE(); + } + virtual void DeleteCookieAsync(const GURL& url, + const std::string& cookie_name, + const base::Closure& callback) { + ADD_FAILURE(); + } + virtual void DeleteAllCreatedBetweenAsync(const base::Time& delete_begin, + const base::Time& delete_end, + const DeleteCallback& callback) { + ADD_FAILURE(); + } + + virtual net::CookieMonster* GetCookieMonster() { return NULL; } + + const std::vector<Entry>& entries() const { return entries_; } + + private: + friend class base::RefCountedThreadSafe<MockCookieStore>; + virtual ~MockCookieStore() {} + + std::vector<Entry> entries_; +}; + +class MockSSLConfigService : public net::SSLConfigService { + public: + virtual void GetSSLConfig(net::SSLConfig* config) {}; +}; + +class MockURLRequestContext : public net::URLRequestContext { + public: + explicit MockURLRequestContext(net::CookieStore* cookie_store) + : transport_security_state_(std::string()) { + set_cookie_store(cookie_store); + set_transport_security_state(&transport_security_state_); + net::TransportSecurityState::DomainState state; + state.expiry = base::Time::Now() + base::TimeDelta::FromSeconds(1000); + transport_security_state_.EnableHost("upgrademe.com", state); + } + + private: + friend class base::RefCountedThreadSafe<MockURLRequestContext>; + virtual ~MockURLRequestContext() {} + + net::TransportSecurityState transport_security_state_; +}; + +class MockHttpTransactionFactory : public net::HttpTransactionFactory { + public: + MockHttpTransactionFactory(net::OrderedSocketData* data) { + data_ = data; + net::MockConnect connect_data(net::SYNCHRONOUS, net::OK); + data_->set_connect_data(connect_data); + session_deps_.reset(new SpdySessionDependencies); + session_deps_->socket_factory->AddSocketDataProvider(data_); + http_session_ = + SpdySessionDependencies::SpdyCreateSession(session_deps_.get()); + host_port_pair_.set_host("example.com"); + host_port_pair_.set_port(80); + host_port_proxy_pair_.first = host_port_pair_; + host_port_proxy_pair_.second = net::ProxyServer::Direct(); + net::SpdySessionPool* spdy_session_pool = + http_session_->spdy_session_pool(); + DCHECK(spdy_session_pool); + EXPECT_FALSE(spdy_session_pool->HasSession(host_port_proxy_pair_)); + session_ = + spdy_session_pool->Get(host_port_proxy_pair_, net::BoundNetLog()); + EXPECT_TRUE(spdy_session_pool->HasSession(host_port_proxy_pair_)); + + transport_params_ = new net::TransportSocketParams(host_port_pair_, + net::MEDIUM, + false, + false); + net::ClientSocketHandle* connection = new net::ClientSocketHandle; + EXPECT_EQ(net::OK, + connection->Init(host_port_pair_.ToString(), transport_params_, + net::MEDIUM, net::CompletionCallback(), + http_session_->GetTransportSocketPool(), + net::BoundNetLog())); + EXPECT_EQ(net::OK, + session_->InitializeWithSocket(connection, false, net::OK)); + } + virtual int CreateTransaction(scoped_ptr<net::HttpTransaction>* trans) { + NOTREACHED(); + return net::ERR_UNEXPECTED; + } + virtual net::HttpCache* GetCache() { + NOTREACHED(); + return NULL; + } + virtual net::HttpNetworkSession* GetSession() { + return http_session_.get(); + } + private: + net::OrderedSocketData* data_; + scoped_ptr<SpdySessionDependencies> session_deps_; + scoped_refptr<net::HttpNetworkSession> http_session_; + scoped_refptr<net::TransportSocketParams> transport_params_; + scoped_refptr<net::SpdySession> session_; + net::HostPortPair host_port_pair_; + net::HostPortProxyPair host_port_proxy_pair_; +}; +} // namespace + +namespace net { + +class WebSocketJobSpdy3Test : public PlatformTest { + public: + virtual void SetUp() { + spdy::SpdyFramer::set_enable_compression_default(false); + stream_type_ = STREAM_INVALID; + cookie_store_ = new MockCookieStore; + context_ = new MockURLRequestContext(cookie_store_.get()); + } + virtual void TearDown() { + cookie_store_ = NULL; + context_ = NULL; + websocket_ = NULL; + socket_ = NULL; + } + void DoSendRequest() { + EXPECT_TRUE(websocket_->SendData(kHandshakeRequestWithoutCookie, + kHandshakeRequestWithoutCookieLength)); + } + void DoSendData() { + if (received_data().size() == kHandshakeResponseWithoutCookieLength) + websocket_->SendData(kDataHello, kDataHelloLength); + } + void DoSync() { + sync_test_callback_.callback().Run(OK); + } + int WaitForResult() { + return sync_test_callback_.WaitForResult(); + } + protected: + enum StreamType { + STREAM_INVALID, + STREAM_MOCK_SOCKET, + STREAM_SOCKET, + STREAM_SPDY_WEBSOCKET, + }; + enum ThrottlingOption { + THROTTLING_OFF, + THROTTLING_ON, + }; + enum SpdyOption { + SPDY_OFF, + SPDY_ON, + }; + void InitWebSocketJob(const GURL& url, + MockSocketStreamDelegate* delegate, + StreamType stream_type) { + DCHECK_NE(STREAM_INVALID, stream_type); + stream_type_ = stream_type; + websocket_ = new WebSocketJob(delegate); + + if (stream_type == STREAM_MOCK_SOCKET) + socket_ = new MockSocketStream(url, websocket_.get()); + + if (stream_type == STREAM_SOCKET || stream_type == STREAM_SPDY_WEBSOCKET) { + if (stream_type == STREAM_SPDY_WEBSOCKET) { + http_factory_.reset(new MockHttpTransactionFactory(data_.get())); + context_->set_http_transaction_factory(http_factory_.get()); + } + + ssl_config_service_ = new MockSSLConfigService(); + context_->set_ssl_config_service(ssl_config_service_); + proxy_service_.reset(net::ProxyService::CreateDirect()); + context_->set_proxy_service(proxy_service_.get()); + host_resolver_.reset(new net::MockHostResolver); + context_->set_host_resolver(host_resolver_.get()); + + socket_ = new SocketStream(url, websocket_.get()); + socket_factory_.reset(new MockClientSocketFactory); + DCHECK(data_.get()); + socket_factory_->AddSocketDataProvider(data_.get()); + socket_->SetClientSocketFactory(socket_factory_.get()); + } + + websocket_->InitSocketStream(socket_.get()); + websocket_->set_context(context_.get()); + struct addrinfo addr; + memset(&addr, 0, sizeof(struct addrinfo)); + addr.ai_family = AF_INET; + addr.ai_addrlen = sizeof(struct sockaddr_in); + struct sockaddr_in sa_in; + memset(&sa_in, 0, sizeof(struct sockaddr_in)); + memcpy(&sa_in.sin_addr, "\x7f\0\0\1", 4); + addr.ai_addr = reinterpret_cast<sockaddr*>(&sa_in); + addr.ai_next = NULL; + websocket_->addresses_ = AddressList::CreateByCopying(&addr); + } + void SkipToConnecting() { + websocket_->state_ = WebSocketJob::CONNECTING; + WebSocketThrottle::GetInstance()->PutInQueue(websocket_); + } + WebSocketJob::State GetWebSocketJobState() { + return websocket_->state_; + } + void CloseWebSocketJob() { + if (websocket_->socket_) { + websocket_->socket_->DetachDelegate(); + WebSocketThrottle::GetInstance()->RemoveFromQueue(websocket_); + } + websocket_->state_ = WebSocketJob::CLOSED; + websocket_->delegate_ = NULL; + websocket_->socket_ = NULL; + } + SocketStream* GetSocket(SocketStreamJob* job) { + return job->socket_.get(); + } + const std::string& sent_data() const { + DCHECK_EQ(STREAM_MOCK_SOCKET, stream_type_); + MockSocketStream* socket = + static_cast<MockSocketStream*>(socket_.get()); + DCHECK(socket); + return socket->sent_data(); + } + const std::string& received_data() const { + DCHECK_NE(STREAM_INVALID, stream_type_); + MockSocketStreamDelegate* delegate = + static_cast<MockSocketStreamDelegate*>(websocket_->delegate_); + DCHECK(delegate); + return delegate->received_data(); + } + + void TestSimpleHandshake(); + void TestSlowHandshake(); + void TestHandshakeWithCookie(); + void TestHandshakeWithCookieButNotAllowed(); + void TestHSTSUpgrade(); + void TestInvalidSendData(); + void TestConnectByWebSocket(ThrottlingOption throttling); + void TestConnectBySpdy(SpdyOption spdy, ThrottlingOption throttling); + + StreamType stream_type_; + scoped_refptr<MockCookieStore> cookie_store_; + scoped_refptr<MockURLRequestContext> context_; + scoped_refptr<WebSocketJob> websocket_; + scoped_refptr<SocketStream> socket_; + scoped_ptr<MockClientSocketFactory> socket_factory_; + scoped_ptr<OrderedSocketData> data_; + TestCompletionCallback sync_test_callback_; + scoped_refptr<MockSSLConfigService> ssl_config_service_; + scoped_ptr<net::ProxyService> proxy_service_; + scoped_ptr<net::MockHostResolver> host_resolver_; + scoped_ptr<MockHttpTransactionFactory> http_factory_; + + static const char kHandshakeRequestWithoutCookie[]; + static const char kHandshakeRequestWithCookie[]; + static const char kHandshakeRequestWithFilteredCookie[]; + static const char kHandshakeResponseWithoutCookie[]; + static const char kHandshakeResponseWithCookie[]; + static const char kDataHello[]; + static const char kDataWorld[]; + static const char* const kHandshakeRequestForSpdy[]; + static const char* const kHandshakeResponseForSpdy[]; + static const size_t kHandshakeRequestWithoutCookieLength; + static const size_t kHandshakeRequestWithCookieLength; + static const size_t kHandshakeRequestWithFilteredCookieLength; + static const size_t kHandshakeResponseWithoutCookieLength; + static const size_t kHandshakeResponseWithCookieLength; + static const size_t kDataHelloLength; + static const size_t kDataWorldLength; +}; + +const char WebSocketJobSpdy3Test::kHandshakeRequestWithoutCookie[] = + "GET /demo HTTP/1.1\r\n" + "Host: example.com\r\n" + "Connection: Upgrade\r\n" + "Sec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\n" + "Sec-WebSocket-Protocol: sample\r\n" + "Upgrade: WebSocket\r\n" + "Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\n" + "Origin: http://example.com\r\n" + "\r\n" + "^n:ds[4U"; + +const char WebSocketJobSpdy3Test::kHandshakeRequestWithCookie[] = + "GET /demo HTTP/1.1\r\n" + "Host: example.com\r\n" + "Connection: Upgrade\r\n" + "Sec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\n" + "Sec-WebSocket-Protocol: sample\r\n" + "Upgrade: WebSocket\r\n" + "Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\n" + "Origin: http://example.com\r\n" + "Cookie: WK-test=1\r\n" + "\r\n" + "^n:ds[4U"; + +const char WebSocketJobSpdy3Test::kHandshakeRequestWithFilteredCookie[] = + "GET /demo HTTP/1.1\r\n" + "Host: example.com\r\n" + "Connection: Upgrade\r\n" + "Sec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\n" + "Sec-WebSocket-Protocol: sample\r\n" + "Upgrade: WebSocket\r\n" + "Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\n" + "Origin: http://example.com\r\n" + "Cookie: CR-test=1; CR-test-httponly=1\r\n" + "\r\n" + "^n:ds[4U"; + +const char WebSocketJobSpdy3Test::kHandshakeResponseWithoutCookie[] = + "HTTP/1.1 101 WebSocket Protocol Handshake\r\n" + "Upgrade: WebSocket\r\n" + "Connection: Upgrade\r\n" + "Sec-WebSocket-Origin: http://example.com\r\n" + "Sec-WebSocket-Location: ws://example.com/demo\r\n" + "Sec-WebSocket-Protocol: sample\r\n" + "\r\n" + "8jKS'y:G*Co,Wxa-"; + +const char WebSocketJobSpdy3Test::kHandshakeResponseWithCookie[] = + "HTTP/1.1 101 WebSocket Protocol Handshake\r\n" + "Upgrade: WebSocket\r\n" + "Connection: Upgrade\r\n" + "Sec-WebSocket-Origin: http://example.com\r\n" + "Sec-WebSocket-Location: ws://example.com/demo\r\n" + "Sec-WebSocket-Protocol: sample\r\n" + "Set-Cookie: CR-set-test=1\r\n" + "\r\n" + "8jKS'y:G*Co,Wxa-"; + +const char WebSocketJobSpdy3Test::kDataHello[] = "Hello, "; + +const char WebSocketJobSpdy3Test::kDataWorld[] = "World!\n"; + +// TODO(toyoshim): I should clarify which WebSocket headers for handshake must +// be exported to SPDY SYN_STREAM and SYN_REPLY. +// Because it depends on HyBi versions, just define it as follow for now. +const char* const WebSocketJobSpdy3Test::kHandshakeRequestForSpdy[] = { + "host", "example.com", + "origin", "http://example.com", + "sec-websocket-protocol", "sample", + "url", "ws://example.com/demo" +}; + +const char* const WebSocketJobSpdy3Test::kHandshakeResponseForSpdy[] = { + "sec-websocket-origin", "http://example.com", + "sec-websocket-location", "ws://example.com/demo", + "sec-websocket-protocol", "sample", +}; + +const size_t WebSocketJobSpdy3Test::kHandshakeRequestWithoutCookieLength = + arraysize(kHandshakeRequestWithoutCookie) - 1; +const size_t WebSocketJobSpdy3Test::kHandshakeRequestWithCookieLength = + arraysize(kHandshakeRequestWithCookie) - 1; +const size_t WebSocketJobSpdy3Test::kHandshakeRequestWithFilteredCookieLength = + arraysize(kHandshakeRequestWithFilteredCookie) - 1; +const size_t WebSocketJobSpdy3Test::kHandshakeResponseWithoutCookieLength = + arraysize(kHandshakeResponseWithoutCookie) - 1; +const size_t WebSocketJobSpdy3Test::kHandshakeResponseWithCookieLength = + arraysize(kHandshakeResponseWithCookie) - 1; +const size_t WebSocketJobSpdy3Test::kDataHelloLength = + arraysize(kDataHello) - 1; +const size_t WebSocketJobSpdy3Test::kDataWorldLength = + arraysize(kDataWorld) - 1; + +void WebSocketJobSpdy3Test::TestSimpleHandshake() { + GURL url("ws://example.com/demo"); + MockSocketStreamDelegate delegate; + InitWebSocketJob(url, &delegate, STREAM_MOCK_SOCKET); + SkipToConnecting(); + + DoSendRequest(); + MessageLoop::current()->RunAllPending(); + EXPECT_EQ(kHandshakeRequestWithoutCookie, sent_data()); + EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState()); + websocket_->OnSentData(socket_.get(), + kHandshakeRequestWithoutCookieLength); + EXPECT_EQ(kHandshakeRequestWithoutCookieLength, delegate.amount_sent()); + + websocket_->OnReceivedData(socket_.get(), + kHandshakeResponseWithoutCookie, + kHandshakeResponseWithoutCookieLength); + MessageLoop::current()->RunAllPending(); + EXPECT_EQ(kHandshakeResponseWithoutCookie, delegate.received_data()); + EXPECT_EQ(WebSocketJob::OPEN, GetWebSocketJobState()); + CloseWebSocketJob(); +} + +void WebSocketJobSpdy3Test::TestSlowHandshake() { + GURL url("ws://example.com/demo"); + MockSocketStreamDelegate delegate; + InitWebSocketJob(url, &delegate, STREAM_MOCK_SOCKET); + SkipToConnecting(); + + DoSendRequest(); + // We assume request is sent in one data chunk (from WebKit) + // We don't support streaming request. + MessageLoop::current()->RunAllPending(); + EXPECT_EQ(kHandshakeRequestWithoutCookie, sent_data()); + EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState()); + websocket_->OnSentData(socket_.get(), + kHandshakeRequestWithoutCookieLength); + EXPECT_EQ(kHandshakeRequestWithoutCookieLength, delegate.amount_sent()); + + std::vector<std::string> lines; + base::SplitString(kHandshakeResponseWithoutCookie, '\n', &lines); + for (size_t i = 0; i < lines.size() - 2; i++) { + std::string line = lines[i] + "\r\n"; + SCOPED_TRACE("Line: " + line); + websocket_->OnReceivedData(socket_, + line.c_str(), + line.size()); + MessageLoop::current()->RunAllPending(); + EXPECT_TRUE(delegate.received_data().empty()); + EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState()); + } + websocket_->OnReceivedData(socket_.get(), "\r\n", 2); + MessageLoop::current()->RunAllPending(); + EXPECT_TRUE(delegate.received_data().empty()); + EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState()); + websocket_->OnReceivedData(socket_.get(), "8jKS'y:G*Co,Wxa-", 16); + EXPECT_EQ(kHandshakeResponseWithoutCookie, delegate.received_data()); + EXPECT_EQ(WebSocketJob::OPEN, GetWebSocketJobState()); + CloseWebSocketJob(); +} + +TEST_F(WebSocketJobSpdy3Test, DelayedCookies) { + WebSocketJob::set_websocket_over_spdy_enabled(true); + GURL url("ws://example.com/demo"); + GURL cookieUrl("http://example.com/demo"); + CookieOptions cookie_options; + scoped_refptr<DelayedCookieMonster> cookie_store = new DelayedCookieMonster(); + context_->set_cookie_store(cookie_store); + cookie_store->SetCookieWithOptionsAsync( + cookieUrl, "CR-test=1", cookie_options, + net::CookieMonster::SetCookiesCallback()); + cookie_options.set_include_httponly(); + cookie_store->SetCookieWithOptionsAsync( + cookieUrl, "CR-test-httponly=1", cookie_options, + net::CookieMonster::SetCookiesCallback()); + + MockSocketStreamDelegate delegate; + InitWebSocketJob(url, &delegate, STREAM_MOCK_SOCKET); + SkipToConnecting(); + + bool sent = websocket_->SendData(kHandshakeRequestWithCookie, + kHandshakeRequestWithCookieLength); + EXPECT_TRUE(sent); + MessageLoop::current()->RunAllPending(); + EXPECT_EQ(kHandshakeRequestWithFilteredCookie, sent_data()); + EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState()); + websocket_->OnSentData(socket_, + kHandshakeRequestWithFilteredCookieLength); + EXPECT_EQ(kHandshakeRequestWithCookieLength, + delegate.amount_sent()); + + websocket_->OnReceivedData(socket_.get(), + kHandshakeResponseWithCookie, + kHandshakeResponseWithCookieLength); + MessageLoop::current()->RunAllPending(); + EXPECT_EQ(kHandshakeResponseWithoutCookie, delegate.received_data()); + EXPECT_EQ(WebSocketJob::OPEN, GetWebSocketJobState()); + + CloseWebSocketJob(); +} + +void WebSocketJobSpdy3Test::TestHandshakeWithCookie() { + GURL url("ws://example.com/demo"); + GURL cookieUrl("http://example.com/demo"); + CookieOptions cookie_options; + cookie_store_->SetCookieWithOptions( + cookieUrl, "CR-test=1", cookie_options); + cookie_options.set_include_httponly(); + cookie_store_->SetCookieWithOptions( + cookieUrl, "CR-test-httponly=1", cookie_options); + + MockSocketStreamDelegate delegate; + InitWebSocketJob(url, &delegate, STREAM_MOCK_SOCKET); + SkipToConnecting(); + + bool sent = websocket_->SendData(kHandshakeRequestWithCookie, + kHandshakeRequestWithCookieLength); + EXPECT_TRUE(sent); + MessageLoop::current()->RunAllPending(); + EXPECT_EQ(kHandshakeRequestWithFilteredCookie, sent_data()); + EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState()); + websocket_->OnSentData(socket_, + kHandshakeRequestWithFilteredCookieLength); + EXPECT_EQ(kHandshakeRequestWithCookieLength, + delegate.amount_sent()); + + websocket_->OnReceivedData(socket_.get(), + kHandshakeResponseWithCookie, + kHandshakeResponseWithCookieLength); + MessageLoop::current()->RunAllPending(); + EXPECT_EQ(kHandshakeResponseWithoutCookie, delegate.received_data()); + EXPECT_EQ(WebSocketJob::OPEN, GetWebSocketJobState()); + + EXPECT_EQ(3U, cookie_store_->entries().size()); + EXPECT_EQ(cookieUrl, cookie_store_->entries()[0].url); + EXPECT_EQ("CR-test=1", cookie_store_->entries()[0].cookie_line); + EXPECT_EQ(cookieUrl, cookie_store_->entries()[1].url); + EXPECT_EQ("CR-test-httponly=1", cookie_store_->entries()[1].cookie_line); + EXPECT_EQ(cookieUrl, cookie_store_->entries()[2].url); + EXPECT_EQ("CR-set-test=1", cookie_store_->entries()[2].cookie_line); + + CloseWebSocketJob(); +} + +void WebSocketJobSpdy3Test::TestHandshakeWithCookieButNotAllowed() { + GURL url("ws://example.com/demo"); + GURL cookieUrl("http://example.com/demo"); + CookieOptions cookie_options; + cookie_store_->SetCookieWithOptions( + cookieUrl, "CR-test=1", cookie_options); + cookie_options.set_include_httponly(); + cookie_store_->SetCookieWithOptions( + cookieUrl, "CR-test-httponly=1", cookie_options); + + MockSocketStreamDelegate delegate; + delegate.set_allow_all_cookies(false); + InitWebSocketJob(url, &delegate, STREAM_MOCK_SOCKET); + SkipToConnecting(); + + bool sent = websocket_->SendData(kHandshakeRequestWithCookie, + kHandshakeRequestWithCookieLength); + EXPECT_TRUE(sent); + MessageLoop::current()->RunAllPending(); + EXPECT_EQ(kHandshakeRequestWithoutCookie, sent_data()); + EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState()); + websocket_->OnSentData(socket_, kHandshakeRequestWithoutCookieLength); + EXPECT_EQ(kHandshakeRequestWithCookieLength, + delegate.amount_sent()); + + websocket_->OnReceivedData(socket_.get(), + kHandshakeResponseWithCookie, + kHandshakeResponseWithCookieLength); + MessageLoop::current()->RunAllPending(); + EXPECT_EQ(kHandshakeResponseWithoutCookie, delegate.received_data()); + EXPECT_EQ(WebSocketJob::OPEN, GetWebSocketJobState()); + + EXPECT_EQ(2U, cookie_store_->entries().size()); + EXPECT_EQ(cookieUrl, cookie_store_->entries()[0].url); + EXPECT_EQ("CR-test=1", cookie_store_->entries()[0].cookie_line); + EXPECT_EQ(cookieUrl, cookie_store_->entries()[1].url); + EXPECT_EQ("CR-test-httponly=1", cookie_store_->entries()[1].cookie_line); + + CloseWebSocketJob(); +} + +void WebSocketJobSpdy3Test::TestHSTSUpgrade() { + GURL url("ws://upgrademe.com/"); + MockSocketStreamDelegate delegate; + scoped_refptr<SocketStreamJob> job = + SocketStreamJob::CreateSocketStreamJob( + url, &delegate, context_->transport_security_state(), + context_->ssl_config_service()); + EXPECT_TRUE(GetSocket(job.get())->is_secure()); + job->DetachDelegate(); + + url = GURL("ws://donotupgrademe.com/"); + job = SocketStreamJob::CreateSocketStreamJob( + url, &delegate, context_->transport_security_state(), + context_->ssl_config_service()); + EXPECT_FALSE(GetSocket(job.get())->is_secure()); + job->DetachDelegate(); +} + +void WebSocketJobSpdy3Test::TestInvalidSendData() { + GURL url("ws://example.com/demo"); + MockSocketStreamDelegate delegate; + InitWebSocketJob(url, &delegate, STREAM_MOCK_SOCKET); + SkipToConnecting(); + + DoSendRequest(); + // We assume request is sent in one data chunk (from WebKit) + // We don't support streaming request. + MessageLoop::current()->RunAllPending(); + EXPECT_EQ(kHandshakeRequestWithoutCookie, sent_data()); + EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState()); + websocket_->OnSentData(socket_.get(), + kHandshakeRequestWithoutCookieLength); + EXPECT_EQ(kHandshakeRequestWithoutCookieLength, delegate.amount_sent()); + + // We could not send any data until connection is established. + bool sent = websocket_->SendData(kHandshakeRequestWithoutCookie, + kHandshakeRequestWithoutCookieLength); + EXPECT_FALSE(sent); + EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState()); + CloseWebSocketJob(); +} + +// Following tests verify cooperation between WebSocketJob and SocketStream. +// Other former tests use MockSocketStream as SocketStream, so we could not +// check SocketStream behavior. +// OrderedSocketData provide socket level verifiation by checking out-going +// packets in comparison with the MockWrite array and emulating in-coming +// packets with MockRead array. + +void WebSocketJobSpdy3Test::TestConnectByWebSocket( + ThrottlingOption throttling) { + // This is a test for verifying cooperation between WebSocketJob and + // SocketStream. If |throttling| was |THROTTLING_OFF|, it test basic + // situation. If |throttling| was |THROTTLING_ON|, throttling limits the + // latter connection. + MockWrite writes[] = { + MockWrite(ASYNC, + kHandshakeRequestWithoutCookie, + kHandshakeRequestWithoutCookieLength, + 1), + MockWrite(ASYNC, + kDataHello, + kDataHelloLength, + 3) + }; + MockRead reads[] = { + MockRead(ASYNC, + kHandshakeResponseWithoutCookie, + kHandshakeResponseWithoutCookieLength, + 2), + MockRead(ASYNC, + kDataWorld, + kDataWorldLength, + 4), + MockRead(SYNCHRONOUS, 0, 5) // EOF + }; + data_.reset(new OrderedSocketData( + reads, arraysize(reads), writes, arraysize(writes))); + + GURL url("ws://example.com/demo"); + MockSocketStreamDelegate delegate; + WebSocketJobSpdy3Test* test = this; + if (throttling == THROTTLING_ON) + delegate.SetOnStartOpenConnection( + base::Bind(&WebSocketJobSpdy3Test::DoSync, base::Unretained(test))); + delegate.SetOnConnected( + base::Bind(&WebSocketJobSpdy3Test::DoSendRequest, + base::Unretained(test))); + delegate.SetOnReceivedData( + base::Bind(&WebSocketJobSpdy3Test::DoSendData, base::Unretained(test))); + delegate.SetOnClose( + base::Bind(&WebSocketJobSpdy3Test::DoSync, base::Unretained(test))); + InitWebSocketJob(url, &delegate, STREAM_SOCKET); + + scoped_refptr<WebSocketJob> block_websocket; + if (throttling == THROTTLING_ON) { + // Create former WebSocket object which obstructs the latter one. + block_websocket = new WebSocketJob(NULL); + block_websocket->addresses_ = AddressList(websocket_->address_list()); + WebSocketThrottle::GetInstance()->PutInQueue(block_websocket.get()); + } + + websocket_->Connect(); + + if (throttling == THROTTLING_ON) { + EXPECT_EQ(OK, WaitForResult()); + EXPECT_TRUE(websocket_->IsWaiting()); + + // Remove the former WebSocket object from throttling queue to unblock the + // latter. + WebSocketThrottle::GetInstance()->RemoveFromQueue(block_websocket); + block_websocket->state_ = WebSocketJob::CLOSED; + block_websocket = NULL; + WebSocketThrottle::GetInstance()->WakeupSocketIfNecessary(); + } + + EXPECT_EQ(OK, WaitForResult()); + EXPECT_TRUE(data_->at_read_eof()); + EXPECT_TRUE(data_->at_write_eof()); + EXPECT_EQ(WebSocketJob::CLOSED, GetWebSocketJobState()); +} + +void WebSocketJobSpdy3Test::TestConnectBySpdy( + SpdyOption spdy, ThrottlingOption throttling) { + // This is a test for verifying cooperation between WebSocketJob and + // SocketStream in the situation we have SPDY session to the server. If + // |throttling| was |THROTTLING_ON|, throttling limits the latter connection. + // If you enabled spdy, you should specify |spdy| as |SPDY_ON|. Expected + // results depend on its configuration. + MockWrite writes_websocket[] = { + MockWrite(ASYNC, + kHandshakeRequestWithoutCookie, + kHandshakeRequestWithoutCookieLength, + 1), + MockWrite(ASYNC, + kDataHello, + kDataHelloLength, + 3) + }; + MockRead reads_websocket[] = { + MockRead(ASYNC, + kHandshakeResponseWithoutCookie, + kHandshakeResponseWithoutCookieLength, + 2), + MockRead(ASYNC, + kDataWorld, + kDataWorldLength, + 4), + MockRead(SYNCHRONOUS, 0, 5) // EOF + }; + + const spdy::SpdyStreamId kStreamId = 1; + scoped_ptr<spdy::SpdyFrame> request_frame( + ConstructSpdyWebSocketHandshakeRequestFrame( + kHandshakeRequestForSpdy, + arraysize(kHandshakeRequestForSpdy) / 2, + kStreamId, + MEDIUM)); + scoped_ptr<spdy::SpdyFrame> response_frame( + ConstructSpdyWebSocketHandshakeResponseFrame( + kHandshakeResponseForSpdy, + arraysize(kHandshakeResponseForSpdy) / 2, + kStreamId, + MEDIUM)); + scoped_ptr<spdy::SpdyFrame> data_hello_frame( + ConstructSpdyWebSocketDataFrame( + kDataHello, + kDataHelloLength, + kStreamId, + false)); + scoped_ptr<spdy::SpdyFrame> data_world_frame( + ConstructSpdyWebSocketDataFrame( + kDataWorld, + kDataWorldLength, + kStreamId, + false)); + MockWrite writes_spdy[] = { + CreateMockWrite(*request_frame.get(), 1), + CreateMockWrite(*data_hello_frame.get(), 3), + }; + MockRead reads_spdy[] = { + CreateMockRead(*response_frame.get(), 2), + CreateMockRead(*data_world_frame.get(), 4), + MockRead(SYNCHRONOUS, 0, 5) // EOF + }; + + if (spdy == SPDY_ON) + data_.reset(new OrderedSocketData( + reads_spdy, arraysize(reads_spdy), + writes_spdy, arraysize(writes_spdy))); + else + data_.reset(new OrderedSocketData( + reads_websocket, arraysize(reads_websocket), + writes_websocket, arraysize(writes_websocket))); + + GURL url("ws://example.com/demo"); + MockSocketStreamDelegate delegate; + WebSocketJobSpdy3Test* test = this; + if (throttling == THROTTLING_ON) + delegate.SetOnStartOpenConnection( + base::Bind(&WebSocketJobSpdy3Test::DoSync, base::Unretained(test))); + delegate.SetOnConnected( + base::Bind(&WebSocketJobSpdy3Test::DoSendRequest, + base::Unretained(test))); + delegate.SetOnReceivedData( + base::Bind(&WebSocketJobSpdy3Test::DoSendData, base::Unretained(test))); + delegate.SetOnClose( + base::Bind(&WebSocketJobSpdy3Test::DoSync, base::Unretained(test))); + InitWebSocketJob(url, &delegate, STREAM_SPDY_WEBSOCKET); + + scoped_refptr<WebSocketJob> block_websocket; + if (throttling == THROTTLING_ON) { + // Create former WebSocket object which obstructs the latter one. + block_websocket = new WebSocketJob(NULL); + block_websocket->addresses_ = AddressList(websocket_->address_list()); + WebSocketThrottle::GetInstance()->PutInQueue(block_websocket.get()); + } + + websocket_->Connect(); + + if (throttling == THROTTLING_ON) { + EXPECT_EQ(OK, WaitForResult()); + EXPECT_TRUE(websocket_->IsWaiting()); + + // Remove the former WebSocket object from throttling queue to unblock the + // latter. + WebSocketThrottle::GetInstance()->RemoveFromQueue(block_websocket); + block_websocket->state_ = WebSocketJob::CLOSED; + block_websocket = NULL; + WebSocketThrottle::GetInstance()->WakeupSocketIfNecessary(); + } + + EXPECT_EQ(OK, WaitForResult()); + EXPECT_TRUE(data_->at_read_eof()); + EXPECT_TRUE(data_->at_write_eof()); + EXPECT_EQ(WebSocketJob::CLOSED, GetWebSocketJobState()); +} + +// Execute tests in both spdy-disabled mode and spdy-enabled mode. +TEST_F(WebSocketJobSpdy3Test, SimpleHandshake) { + WebSocketJob::set_websocket_over_spdy_enabled(false); + TestSimpleHandshake(); +} + +TEST_F(WebSocketJobSpdy3Test, SlowHandshake) { + WebSocketJob::set_websocket_over_spdy_enabled(false); + TestSlowHandshake(); +} + +TEST_F(WebSocketJobSpdy3Test, HandshakeWithCookie) { + WebSocketJob::set_websocket_over_spdy_enabled(false); + TestHandshakeWithCookie(); +} + +TEST_F(WebSocketJobSpdy3Test, HandshakeWithCookieButNotAllowed) { + WebSocketJob::set_websocket_over_spdy_enabled(false); + TestHandshakeWithCookieButNotAllowed(); +} + +TEST_F(WebSocketJobSpdy3Test, HSTSUpgrade) { + WebSocketJob::set_websocket_over_spdy_enabled(false); + TestHSTSUpgrade(); +} + +TEST_F(WebSocketJobSpdy3Test, InvalidSendData) { + WebSocketJob::set_websocket_over_spdy_enabled(false); + TestInvalidSendData(); +} + +TEST_F(WebSocketJobSpdy3Test, SimpleHandshakeSpdyEnabled) { + WebSocketJob::set_websocket_over_spdy_enabled(true); + TestSimpleHandshake(); +} + +TEST_F(WebSocketJobSpdy3Test, SlowHandshakeSpdyEnabled) { + WebSocketJob::set_websocket_over_spdy_enabled(true); + TestSlowHandshake(); +} + +TEST_F(WebSocketJobSpdy3Test, HandshakeWithCookieSpdyEnabled) { + WebSocketJob::set_websocket_over_spdy_enabled(true); + TestHandshakeWithCookie(); +} + +TEST_F(WebSocketJobSpdy3Test, HandshakeWithCookieButNotAllowedSpdyEnabled) { + WebSocketJob::set_websocket_over_spdy_enabled(true); + TestHandshakeWithCookieButNotAllowed(); +} + +TEST_F(WebSocketJobSpdy3Test, HSTSUpgradeSpdyEnabled) { + WebSocketJob::set_websocket_over_spdy_enabled(true); + TestHSTSUpgrade(); +} + +TEST_F(WebSocketJobSpdy3Test, InvalidSendDataSpdyEnabled) { + WebSocketJob::set_websocket_over_spdy_enabled(true); + TestInvalidSendData(); +} + +TEST_F(WebSocketJobSpdy3Test, ConnectByWebSocket) { + WebSocketJob::set_websocket_over_spdy_enabled(false); + TestConnectByWebSocket(THROTTLING_OFF); +} + +TEST_F(WebSocketJobSpdy3Test, ConnectByWebSocketSpdyEnabled) { + WebSocketJob::set_websocket_over_spdy_enabled(true); + TestConnectByWebSocket(THROTTLING_OFF); +} + +TEST_F(WebSocketJobSpdy3Test, ConnectBySpdy) { + WebSocketJob::set_websocket_over_spdy_enabled(false); + TestConnectBySpdy(SPDY_OFF, THROTTLING_OFF); +} + +TEST_F(WebSocketJobSpdy3Test, ConnectBySpdySpdyEnabled) { + WebSocketJob::set_websocket_over_spdy_enabled(true); + TestConnectBySpdy(SPDY_ON, THROTTLING_OFF); +} + +TEST_F(WebSocketJobSpdy3Test, ThrottlingWebSocket) { + WebSocketJob::set_websocket_over_spdy_enabled(false); + TestConnectByWebSocket(THROTTLING_ON); +} + +TEST_F(WebSocketJobSpdy3Test, ThrottlingWebSocketSpdyEnabled) { + WebSocketJob::set_websocket_over_spdy_enabled(true); + TestConnectByWebSocket(THROTTLING_ON); +} + +TEST_F(WebSocketJobSpdy3Test, ThrottlingSpdy) { + WebSocketJob::set_websocket_over_spdy_enabled(false); + TestConnectBySpdy(SPDY_OFF, THROTTLING_ON); +} + +TEST_F(WebSocketJobSpdy3Test, ThrottlingSpdySpdyEnabled) { + WebSocketJob::set_websocket_over_spdy_enabled(true); + TestConnectBySpdy(SPDY_ON, THROTTLING_ON); +} + +// TODO(toyoshim): Add tests to verify throttling, SPDY stream limitation. +// TODO(toyoshim,yutak): Add tests to verify closing handshake. + +} // namespace net |