summaryrefslogtreecommitdiffstats
path: root/net
diff options
context:
space:
mode:
authorrch@chromium.org <rch@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-03-04 04:12:23 +0000
committerrch@chromium.org <rch@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-03-04 04:12:23 +0000
commit448d4ca50bf2216c6217acc13d000f4e451d485a (patch)
treea747072a0641d9488ef77b2a3c598e17a1f2c604 /net
parentf259ea51f61f8e97117ab142606d33d859de7763 (diff)
downloadchromium_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')
-rw-r--r--net/base/upload_data.h6
-rw-r--r--net/http/http_network_transaction.h37
-rw-r--r--net/http/http_network_transaction_spdy21_unittest.cc9376
-rw-r--r--net/http/http_network_transaction_spdy2_unittest.cc9375
-rw-r--r--net/http/http_network_transaction_spdy3_unittest.cc (renamed from net/http/http_network_transaction_unittest.cc)402
-rw-r--r--net/http/http_proxy_client_socket_pool_spdy21_unittest.cc541
-rw-r--r--net/http/http_proxy_client_socket_pool_spdy2_unittest.cc541
-rw-r--r--net/http/http_proxy_client_socket_pool_spdy3_unittest.cc (renamed from net/http/http_proxy_client_socket_pool_unittest.cc)32
-rw-r--r--net/net.gyp45
-rw-r--r--net/socket/ssl_client_socket_pool_unittest.cc4
-rw-r--r--net/socket_stream/socket_stream_job.h5
-rw-r--r--net/spdy/buffered_spdy_framer_spdy2_unittest.cc (renamed from net/spdy/buffered_spdy_framer_unittest.cc)22
-rw-r--r--net/spdy/buffered_spdy_framer_spdy3_unittest.cc248
-rw-r--r--net/spdy/spdy_http_stream.h7
-rw-r--r--net/spdy/spdy_http_stream_spdy2_unittest.cc (renamed from net/spdy/spdy_http_stream_unittest.cc)22
-rw-r--r--net/spdy/spdy_http_stream_spdy3_unittest.cc471
-rw-r--r--net/spdy/spdy_network_transaction_spdy21_unittest.cc5851
-rw-r--r--net/spdy/spdy_network_transaction_spdy2_unittest.cc5850
-rw-r--r--net/spdy/spdy_network_transaction_spdy3_unittest.cc (renamed from net/spdy/spdy_network_transaction_unittest.cc)173
-rw-r--r--net/spdy/spdy_proxy_client_socket_spdy2_unittest.cc (renamed from net/spdy/spdy_proxy_client_socket_unittest.cc)129
-rw-r--r--net/spdy/spdy_proxy_client_socket_spdy3_unittest.cc1312
-rw-r--r--net/spdy/spdy_session.h9
-rw-r--r--net/spdy/spdy_session_pool.h24
-rw-r--r--net/spdy/spdy_session_spdy2_unittest.cc (renamed from net/spdy/spdy_session_unittest.cc)39
-rw-r--r--net/spdy/spdy_session_spdy3_unittest.cc1128
-rw-r--r--net/spdy/spdy_stream_spdy2_unittest.cc (renamed from net/spdy/spdy_stream_unittest.cc)14
-rw-r--r--net/spdy/spdy_stream_spdy3_unittest.cc407
-rw-r--r--net/spdy/spdy_test_util_spdy2.cc (renamed from net/spdy/spdy_test_util.cc)5
-rw-r--r--net/spdy/spdy_test_util_spdy2.h (renamed from net/spdy/spdy_test_util.h)4
-rw-r--r--net/spdy/spdy_test_util_spdy3.cc1012
-rw-r--r--net/spdy/spdy_test_util_spdy3.h418
-rw-r--r--net/spdy/spdy_websocket_stream.h5
-rw-r--r--net/spdy/spdy_websocket_stream_spdy2_unittest.cc (renamed from net/spdy/spdy_websocket_stream_unittest.cc)57
-rw-r--r--net/spdy/spdy_websocket_stream_spdy3_unittest.cc628
-rw-r--r--net/spdy/spdy_websocket_test_util_spdy2.cc (renamed from net/spdy/spdy_websocket_test_util.cc)10
-rw-r--r--net/spdy/spdy_websocket_test_util_spdy2.h (renamed from net/spdy/spdy_websocket_test_util.h)6
-rw-r--r--net/spdy/spdy_websocket_test_util_spdy3.cc97
-rw-r--r--net/spdy/spdy_websocket_test_util_spdy3.h41
-rw-r--r--net/websockets/websocket_job.h3
-rw-r--r--net/websockets/websocket_job_spdy2_unittest.cc (renamed from net/websockets/websocket_job_unittest.cc)129
-rw-r--r--net/websockets/websocket_job_spdy3_unittest.cc1099
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