blob: 3ebe1cb4e08c14493fdb51e155d268b8c12640ed [file] [log] [blame]
// Copyright 2013 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 <stdint.h>
#include <limits>
#include <string>
#include <utility>
#include <vector>
#include "base/compiler_specific.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/json/json_writer.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/memory/scoped_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/run_loop.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/test_file_util.h"
#include "base/thread_task_runner_handle.h"
#include "net/base/auth.h"
#include "net/base/chunked_upload_data_stream.h"
#include "net/base/completion_callback.h"
#include "net/base/elements_upload_data_stream.h"
#include "net/base/load_timing_info.h"
#include "net/base/load_timing_info_test_util.h"
#include "net/base/net_errors.h"
#include "net/base/proxy_delegate.h"
#include "net/base/request_priority.h"
#include "net/base/test_completion_callback.h"
#include "net/base/test_data_directory.h"
#include "net/base/test_proxy_delegate.h"
#include "net/base/upload_bytes_element_reader.h"
#include "net/base/upload_file_element_reader.h"
#include "net/cert/mock_cert_verifier.h"
#include "net/dns/host_cache.h"
#include "net/dns/mock_host_resolver.h"
#include "net/http/http_auth_challenge_tokenizer.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_auth_scheme.h"
#include "net/http/http_basic_state.h"
#include "net/http/http_basic_stream.h"
#include "net/http/http_network_session.h"
#include "net/http/http_network_session_peer.h"
#include "net/http/http_request_headers.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_stream_parser.h"
#include "net/http/http_transaction_test_util.h"
#include "net/log/net_log.h"
#include "net/log/test_net_log.h"
#include "net/log/test_net_log_entry.h"
#include "net/log/test_net_log_util.h"
#include "net/proxy/mock_proxy_resolver.h"
#include "net/proxy/proxy_config_service_fixed.h"
#include "net/proxy/proxy_info.h"
#include "net/proxy/proxy_resolver.h"
#include "net/proxy/proxy_server.h"
#include "net/proxy/proxy_service.h"
#include "net/socket/client_socket_factory.h"
#include "net/socket/client_socket_pool.h"
#include "net/socket/client_socket_pool_manager.h"
#include "net/socket/connection_attempts.h"
#include "net/socket/mock_client_socket_pool_manager.h"
#include "net/socket/next_proto.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_common.h"
#include "net/ssl/default_channel_id_store.h"
#include "net/ssl/ssl_cert_request_info.h"
#include "net/ssl/ssl_config_service.h"
#include "net/ssl/ssl_config_service_defaults.h"
#include "net/ssl/ssl_info.h"
#include "net/ssl/ssl_private_key.h"
#include "net/test/cert_test_util.h"
#include "net/websockets/websocket_handshake_stream_base.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/platform_test.h"
#include "url/gurl.h"
using base::ASCIIToUTF16;
//-----------------------------------------------------------------------------
namespace net {
namespace {
enum TestCase {
// Test using the SPDY/3.1 protocol.
kTestCaseSPDY31,
// Test using the HTTP/2 protocol, without specifying a stream
// dependency based on the RequestPriority.
kTestCaseHTTP2NoPriorityDependencies,
// Test using the HTTP/2 protocol, specifying a stream
// dependency based on the RequestPriority.
kTestCaseHTTP2PriorityDependencies
};
const base::string16 kBar(ASCIIToUTF16("bar"));
const base::string16 kBar2(ASCIIToUTF16("bar2"));
const base::string16 kBar3(ASCIIToUTF16("bar3"));
const base::string16 kBaz(ASCIIToUTF16("baz"));
const base::string16 kFirst(ASCIIToUTF16("first"));
const base::string16 kFoo(ASCIIToUTF16("foo"));
const base::string16 kFoo2(ASCIIToUTF16("foo2"));
const base::string16 kFoo3(ASCIIToUTF16("foo3"));
const base::string16 kFou(ASCIIToUTF16("fou"));
const base::string16 kSecond(ASCIIToUTF16("second"));
const base::string16 kTestingNTLM(ASCIIToUTF16("testing-ntlm"));
const base::string16 kWrongPassword(ASCIIToUTF16("wrongpassword"));
int GetIdleSocketCountInTransportSocketPool(HttpNetworkSession* session) {
return session->GetTransportSocketPool(HttpNetworkSession::NORMAL_SOCKET_POOL)
->IdleSocketCount();
}
int GetIdleSocketCountInSSLSocketPool(HttpNetworkSession* session) {
return session->GetSSLSocketPool(HttpNetworkSession::NORMAL_SOCKET_POOL)
->IdleSocketCount();
}
bool IsTransportSocketPoolStalled(HttpNetworkSession* session) {
return session->GetTransportSocketPool(HttpNetworkSession::NORMAL_SOCKET_POOL)
->IsStalled();
}
// Takes in a Value created from a NetLogHttpResponseParameter, and returns
// a JSONified list of headers as a single string. Uses single quotes instead
// of double quotes for easier comparison. Returns false on failure.
bool GetHeaders(base::DictionaryValue* params, std::string* headers) {
if (!params)
return false;
base::ListValue* header_list;
if (!params->GetList("headers", &header_list))
return false;
std::string double_quote_headers;
base::JSONWriter::Write(*header_list, &double_quote_headers);
base::ReplaceChars(double_quote_headers, "\"", "'", headers);
return true;
}
// Tests LoadTimingInfo in the case a socket is reused and no PAC script is
// used.
void TestLoadTimingReused(const LoadTimingInfo& load_timing_info) {
EXPECT_TRUE(load_timing_info.socket_reused);
EXPECT_NE(NetLog::Source::kInvalidId, load_timing_info.socket_log_id);
EXPECT_TRUE(load_timing_info.proxy_resolve_start.is_null());
EXPECT_TRUE(load_timing_info.proxy_resolve_end.is_null());
ExpectConnectTimingHasNoTimes(load_timing_info.connect_timing);
EXPECT_FALSE(load_timing_info.send_start.is_null());
EXPECT_LE(load_timing_info.send_start, load_timing_info.send_end);
// Set at a higher level.
EXPECT_TRUE(load_timing_info.request_start_time.is_null());
EXPECT_TRUE(load_timing_info.request_start.is_null());
EXPECT_TRUE(load_timing_info.receive_headers_end.is_null());
}
// Tests LoadTimingInfo in the case a new socket is used and no PAC script is
// used.
void TestLoadTimingNotReused(const LoadTimingInfo& load_timing_info,
int connect_timing_flags) {
EXPECT_FALSE(load_timing_info.socket_reused);
EXPECT_NE(NetLog::Source::kInvalidId, load_timing_info.socket_log_id);
EXPECT_TRUE(load_timing_info.proxy_resolve_start.is_null());
EXPECT_TRUE(load_timing_info.proxy_resolve_end.is_null());
ExpectConnectTimingHasTimes(load_timing_info.connect_timing,
connect_timing_flags);
EXPECT_LE(load_timing_info.connect_timing.connect_end,
load_timing_info.send_start);
EXPECT_LE(load_timing_info.send_start, load_timing_info.send_end);
// Set at a higher level.
EXPECT_TRUE(load_timing_info.request_start_time.is_null());
EXPECT_TRUE(load_timing_info.request_start.is_null());
EXPECT_TRUE(load_timing_info.receive_headers_end.is_null());
}
// Tests LoadTimingInfo in the case a socket is reused and a PAC script is
// used.
void TestLoadTimingReusedWithPac(const LoadTimingInfo& load_timing_info) {
EXPECT_TRUE(load_timing_info.socket_reused);
EXPECT_NE(NetLog::Source::kInvalidId, load_timing_info.socket_log_id);
ExpectConnectTimingHasNoTimes(load_timing_info.connect_timing);
EXPECT_FALSE(load_timing_info.proxy_resolve_start.is_null());
EXPECT_LE(load_timing_info.proxy_resolve_start,
load_timing_info.proxy_resolve_end);
EXPECT_LE(load_timing_info.proxy_resolve_end,
load_timing_info.send_start);
EXPECT_LE(load_timing_info.send_start, load_timing_info.send_end);
// Set at a higher level.
EXPECT_TRUE(load_timing_info.request_start_time.is_null());
EXPECT_TRUE(load_timing_info.request_start.is_null());
EXPECT_TRUE(load_timing_info.receive_headers_end.is_null());
}
// Tests LoadTimingInfo in the case a new socket is used and a PAC script is
// used.
void TestLoadTimingNotReusedWithPac(const LoadTimingInfo& load_timing_info,
int connect_timing_flags) {
EXPECT_FALSE(load_timing_info.socket_reused);
EXPECT_NE(NetLog::Source::kInvalidId, load_timing_info.socket_log_id);
EXPECT_FALSE(load_timing_info.proxy_resolve_start.is_null());
EXPECT_LE(load_timing_info.proxy_resolve_start,
load_timing_info.proxy_resolve_end);
EXPECT_LE(load_timing_info.proxy_resolve_end,
load_timing_info.connect_timing.connect_start);
ExpectConnectTimingHasTimes(load_timing_info.connect_timing,
connect_timing_flags);
EXPECT_LE(load_timing_info.connect_timing.connect_end,
load_timing_info.send_start);
EXPECT_LE(load_timing_info.send_start, load_timing_info.send_end);
// Set at a higher level.
EXPECT_TRUE(load_timing_info.request_start_time.is_null());
EXPECT_TRUE(load_timing_info.request_start.is_null());
EXPECT_TRUE(load_timing_info.receive_headers_end.is_null());
}
void AddWebSocketHeaders(HttpRequestHeaders* headers) {
headers->SetHeader("Connection", "Upgrade");
headers->SetHeader("Upgrade", "websocket");
headers->SetHeader("Origin", "http://www.example.org");
headers->SetHeader("Sec-WebSocket-Version", "13");
headers->SetHeader("Sec-WebSocket-Key", "dGhlIHNhbXBsZSBub25jZQ==");
}
scoped_ptr<HttpNetworkSession> CreateSession(
SpdySessionDependencies* session_deps) {
return SpdySessionDependencies::SpdyCreateSession(session_deps);
}
} // namespace
class HttpNetworkTransactionTest
: public PlatformTest,
public ::testing::WithParamInterface<TestCase> {
public:
virtual ~HttpNetworkTransactionTest() {
// Important to restore the per-pool limit first, since the pool limit must
// always be greater than group limit, and the tests reduce both limits.
ClientSocketPoolManager::set_max_sockets_per_pool(
HttpNetworkSession::NORMAL_SOCKET_POOL, old_max_pool_sockets_);
ClientSocketPoolManager::set_max_sockets_per_group(
HttpNetworkSession::NORMAL_SOCKET_POOL, old_max_group_sockets_);
}
protected:
HttpNetworkTransactionTest()
: spdy_util_(GetProtocol(), GetDependenciesFromPriority()),
session_deps_(GetProtocol()),
old_max_group_sockets_(ClientSocketPoolManager::max_sockets_per_group(
HttpNetworkSession::NORMAL_SOCKET_POOL)),
old_max_pool_sockets_(ClientSocketPoolManager::max_sockets_per_pool(
HttpNetworkSession::NORMAL_SOCKET_POOL)) {
session_deps_.enable_priority_dependencies = GetDependenciesFromPriority();
}
struct SimpleGetHelperResult {
int rv;
std::string status_line;
std::string response_data;
int64_t total_received_bytes;
int64_t total_sent_bytes;
LoadTimingInfo load_timing_info;
ConnectionAttempts connection_attempts;
IPEndPoint remote_endpoint_after_start;
};
void SetUp() override {
NetworkChangeNotifier::NotifyObserversOfIPAddressChangeForTests();
base::MessageLoop::current()->RunUntilIdle();
}
void TearDown() override {
NetworkChangeNotifier::NotifyObserversOfIPAddressChangeForTests();
base::MessageLoop::current()->RunUntilIdle();
// Empty the current queue.
base::MessageLoop::current()->RunUntilIdle();
PlatformTest::TearDown();
NetworkChangeNotifier::NotifyObserversOfIPAddressChangeForTests();
base::MessageLoop::current()->RunUntilIdle();
}
NextProto GetProtocol() const {
return GetParam() == kTestCaseSPDY31 ? kProtoSPDY31 : kProtoHTTP2;
}
bool GetDependenciesFromPriority() const {
return GetParam() == kTestCaseHTTP2PriorityDependencies;
}
const char* GetAlternateProtocolFromParam() {
return AlternateProtocolToString(
AlternateProtocolFromNextProto(GetProtocol()));
}
std::string GetAlternativeServiceHttpHeader() {
return std::string("Alt-Svc: ") + GetAlternateProtocolFromParam() +
"=\"www.example.com:443\"\r\n";
}
std::string GetAlternateProtocolHttpHeader() {
return std::string("Alternate-Protocol: 443:") +
GetAlternateProtocolFromParam() + "\r\n";
}
// 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);
// 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 PreconnectErrorResendRequestTest(const MockWrite* write_failure,
const MockRead* read_failure,
bool use_spdy);
SimpleGetHelperResult SimpleGetHelperForData(StaticSocketDataProvider* data[],
size_t data_count) {
SimpleGetHelperResult out;
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.example.org/");
request.load_flags = 0;
BoundTestNetLog log;
session_deps_.net_log = log.bound().net_log();
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
for (size_t i = 0; i < data_count; ++i) {
session_deps_.socket_factory->AddSocketDataProvider(data[i]);
}
TestCompletionCallback callback;
EXPECT_TRUE(log.bound().IsCapturing());
int rv = trans->Start(&request, callback.callback(), log.bound());
EXPECT_EQ(ERR_IO_PENDING, rv);
out.rv = callback.WaitForResult();
out.total_received_bytes = trans->GetTotalReceivedBytes();
out.total_sent_bytes = trans->GetTotalSentBytes();
// Even in the failure cases that use this function, connections are always
// successfully established before the error.
EXPECT_TRUE(trans->GetLoadTimingInfo(&out.load_timing_info));
TestLoadTimingNotReused(out.load_timing_info, CONNECT_TIMING_HAS_DNS_TIMES);
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.get() == NULL) {
out.rv = ERR_UNEXPECTED;
return out;
}
out.status_line = response->headers->GetStatusLine();
EXPECT_EQ("127.0.0.1", response->socket_address.host());
EXPECT_EQ(80, response->socket_address.port());
bool got_endpoint =
trans->GetRemoteEndpoint(&out.remote_endpoint_after_start);
EXPECT_EQ(got_endpoint,
out.remote_endpoint_after_start.address().size() > 0);
rv = ReadTransaction(trans.get(), &out.response_data);
EXPECT_EQ(OK, rv);
TestNetLogEntry::List 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);
std::string line;
EXPECT_TRUE(entries[pos].GetStringValue("line", &line));
EXPECT_EQ("GET / HTTP/1.1\r\n", line);
HttpRequestHeaders request_headers;
EXPECT_TRUE(trans->GetFullRequestHeaders(&request_headers));
std::string value;
EXPECT_TRUE(request_headers.GetHeader("Host", &value));
EXPECT_EQ("www.example.org", value);
EXPECT_TRUE(request_headers.GetHeader("Connection", &value));
EXPECT_EQ("keep-alive", value);
std::string response_headers;
EXPECT_TRUE(GetHeaders(entries[pos].params.get(), &response_headers));
EXPECT_EQ("['Host: www.example.org','Connection: keep-alive']",
response_headers);
out.total_received_bytes = trans->GetTotalReceivedBytes();
// The total number of sent bytes should not have changed.
EXPECT_EQ(out.total_sent_bytes, trans->GetTotalSentBytes());
trans->GetConnectionAttempts(&out.connection_attempts);
return out;
}
SimpleGetHelperResult SimpleGetHelper(MockRead data_reads[],
size_t reads_count) {
MockWrite data_writes[] = {
MockWrite("GET / HTTP/1.1\r\n"
"Host: www.example.org\r\n"
"Connection: keep-alive\r\n\r\n"),
};
StaticSocketDataProvider reads(data_reads, reads_count, data_writes,
arraysize(data_writes));
StaticSocketDataProvider* data[] = {&reads};
SimpleGetHelperResult out = SimpleGetHelperForData(data, 1);
EXPECT_EQ(CountWriteBytes(data_writes, arraysize(data_writes)),
out.total_sent_bytes);
return out;
}
void ConnectStatusHelperWithExpectedStatus(const MockRead& status,
int expected_status);
void ConnectStatusHelper(const MockRead& status);
void BypassHostCacheOnRefreshHelper(int load_flags);
void CheckErrorIsPassedBack(int error, IoMode mode);
SpdyTestUtil spdy_util_;
SpdySessionDependencies session_deps_;
// Original socket limits. Some tests set these. Safest to always restore
// them once each test has been run.
int old_max_group_sockets_;
int old_max_pool_sockets_;
};
INSTANTIATE_TEST_CASE_P(ProtoPlusDepend,
HttpNetworkTransactionTest,
testing::Values(kTestCaseSPDY31,
kTestCaseHTTP2NoPriorityDependencies,
kTestCaseHTTP2PriorityDependencies));
namespace {
class BeforeNetworkStartHandler {
public:
explicit BeforeNetworkStartHandler(bool defer)
: defer_on_before_network_start_(defer),
observed_before_network_start_(false) {}
void OnBeforeNetworkStart(bool* defer) {
*defer = defer_on_before_network_start_;
observed_before_network_start_ = true;
}
bool observed_before_network_start() const {
return observed_before_network_start_;
}
private:
const bool defer_on_before_network_start_;
bool observed_before_network_start_;
DISALLOW_COPY_AND_ASSIGN(BeforeNetworkStartHandler);
};
class BeforeProxyHeadersSentHandler {
public:
BeforeProxyHeadersSentHandler()
: observed_before_proxy_headers_sent_(false) {}
void OnBeforeProxyHeadersSent(const ProxyInfo& proxy_info,
HttpRequestHeaders* request_headers) {
observed_before_proxy_headers_sent_ = true;
observed_proxy_server_uri_ = proxy_info.proxy_server().ToURI();
}
bool observed_before_proxy_headers_sent() const {
return observed_before_proxy_headers_sent_;
}
std::string observed_proxy_server_uri() const {
return observed_proxy_server_uri_;
}
private:
bool observed_before_proxy_headers_sent_;
std::string observed_proxy_server_uri_;
DISALLOW_COPY_AND_ASSIGN(BeforeProxyHeadersSentHandler);
};
// 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);
}
#if defined(NTLM_PORTABLE)
// Alternative functions that eliminate randomness and dependency on the local
// host name so that the generated NTLM messages are reproducible.
void MockGenerateRandom1(uint8_t* output, size_t n) {
static const uint8_t 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_t* output, size_t n) {
static const uint8_t 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";
}
#endif // defined(NTLM_PORTABLE)
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_;
}
int RequestSocket(const std::string& group_name,
const void* socket_params,
RequestPriority priority,
ClientSocketPool::RespectLimits respect_limits,
ClientSocketHandle* handle,
const CompletionCallback& callback,
const BoundNetLog& net_log) override {
last_group_name_ = group_name;
return ERR_IO_PENDING;
}
void CancelRequest(const std::string& group_name,
ClientSocketHandle* handle) override {}
void ReleaseSocket(const std::string& group_name,
scoped_ptr<StreamSocket> socket,
int id) override {}
void CloseIdleSockets() override {}
int IdleSocketCount() const override { return 0; }
int IdleSocketCountInGroup(const std::string& group_name) const override {
return 0;
}
LoadState GetLoadState(const std::string& group_name,
const ClientSocketHandle* handle) const override {
return LOAD_STATE_IDLE;
}
base::TimeDelta ConnectionTimeout() const override {
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, host_resolver, NULL, NULL) {
}
template <>
CaptureGroupNameHttpProxySocketPool::CaptureGroupNameSocketPool(
HostResolver* /* host_resolver */,
CertVerifier* /* cert_verifier */)
: HttpProxyClientSocketPool(0, 0, NULL, NULL, NULL) {
}
template <>
CaptureGroupNameSSLSocketPool::CaptureGroupNameSocketPool(
HostResolver* /* host_resolver */,
CertVerifier* cert_verifier)
: SSLClientSocketPool(0,
0,
cert_verifier,
NULL,
NULL,
NULL,
NULL,
std::string(),
NULL,
NULL,
NULL,
NULL,
NULL,
NULL) {
}
//-----------------------------------------------------------------------------
// 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.example.org:80", auth_challenge->challenger.ToString());
EXPECT_EQ("MyRealm1", auth_challenge->realm);
EXPECT_EQ(kBasicAuthScheme, 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(kBasicAuthScheme, 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.example.org:80", auth_challenge->challenger.ToString());
EXPECT_EQ("digestive", auth_challenge->realm);
EXPECT_EQ(kDigestAuthScheme, auth_challenge->scheme);
return true;
}
#if defined(NTLM_PORTABLE)
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(kNtlmAuthScheme, auth_challenge->scheme);
return true;
}
#endif // defined(NTLM_PORTABLE)
} // namespace
TEST_P(HttpNetworkTransactionTest, Basic) {
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
}
TEST_P(HttpNetworkTransactionTest, 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);
int64_t reads_size = CountReadBytes(data_reads, arraysize(data_reads));
EXPECT_EQ(reads_size, out.total_received_bytes);
EXPECT_EQ(0u, out.connection_attempts.size());
EXPECT_FALSE(out.remote_endpoint_after_start.address().empty());
}
// Response with no status line.
TEST_P(HttpNetworkTransactionTest, 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);
int64_t reads_size = CountReadBytes(data_reads, arraysize(data_reads));
EXPECT_EQ(reads_size, out.total_received_bytes);
}
// Allow up to 4 bytes of junk to precede status line.
TEST_P(HttpNetworkTransactionTest, StatusLineJunk3Bytes) {
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);
int64_t reads_size = CountReadBytes(data_reads, arraysize(data_reads));
EXPECT_EQ(reads_size, out.total_received_bytes);
}
// Allow up to 4 bytes of junk to precede status line.
TEST_P(HttpNetworkTransactionTest, 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);
int64_t reads_size = CountReadBytes(data_reads, arraysize(data_reads));
EXPECT_EQ(reads_size, out.total_received_bytes);
}
// Beyond 4 bytes of slop and it should fail to find a status line.
TEST_P(HttpNetworkTransactionTest, 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);
int64_t reads_size = CountReadBytes(data_reads, arraysize(data_reads));
EXPECT_EQ(reads_size, out.total_received_bytes);
}
// Same as StatusLineJunk4Bytes, except the read chunks are smaller.
TEST_P(HttpNetworkTransactionTest, 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);
int64_t reads_size = CountReadBytes(data_reads, arraysize(data_reads));
EXPECT_EQ(reads_size, out.total_received_bytes);
}
// Close the connection before enough bytes to have a status line.
TEST_P(HttpNetworkTransactionTest, 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);
int64_t reads_size = CountReadBytes(data_reads, arraysize(data_reads));
EXPECT_EQ(reads_size, out.total_received_bytes);
}
// 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_P(HttpNetworkTransactionTest, StopsReading204) {
char junk[] = "junk";
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);
int64_t reads_size = CountReadBytes(data_reads, arraysize(data_reads));
int64_t response_size = reads_size - strlen(junk);
EXPECT_EQ(response_size, out.total_received_bytes);
}
// A simple request using chunked encoding with some extra data after.
TEST_P(HttpNetworkTransactionTest, ChunkedEncoding) {
std::string final_chunk = "0\r\n\r\n";
std::string extra_data = "HTTP/1.1 200 OK\r\n";
std::string last_read = final_chunk + extra_data;
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(last_read.data()),
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);
int64_t reads_size = CountReadBytes(data_reads, arraysize(data_reads));
int64_t response_size = reads_size - extra_data.size();
EXPECT_EQ(response_size, out.total_received_bytes);
}
// Next tests deal with http://crbug.com/56344.
TEST_P(HttpNetworkTransactionTest,
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_P(HttpNetworkTransactionTest,
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_P(HttpNetworkTransactionTest,
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_P(HttpNetworkTransactionTest,
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_P(HttpNetworkTransactionTest, 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 no error.
TEST_P(HttpNetworkTransactionTest,
TwoIdenticalContentDispositionHeaders) {
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(OK, out.rv);
EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
EXPECT_EQ("Hello", out.response_data);
}
// Checks that two distinct Content-Disposition headers result in an error.
TEST_P(HttpNetworkTransactionTest, TwoDistinctContentDispositionHeaders) {
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 that two identical Location headers result in no error.
// Also tests Location header behavior.
TEST_P(HttpNetworkTransactionTest, TwoIdenticalLocationHeaders) {
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),
};
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://redirect.com/");
request.load_flags = 0;
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
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.get() != 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);
EXPECT_TRUE(response->proxy_server.IsEmpty());
}
// Checks that two distinct Location headers result in an error.
TEST_P(HttpNetworkTransactionTest, TwoDistinctLocationHeaders) {
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_P(HttpNetworkTransactionTest, Head) {
HttpRequestInfo request;
request.method = "HEAD";
request.url = GURL("http://www.example.org/");
request.load_flags = 0;
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
BeforeProxyHeadersSentHandler proxy_headers_handler;
trans->SetBeforeProxyHeadersSentCallback(
base::Bind(&BeforeProxyHeadersSentHandler::OnBeforeProxyHeadersSent,
base::Unretained(&proxy_headers_handler)));
MockWrite data_writes1[] = {
MockWrite("HEAD / HTTP/1.1\r\n"
"Host: www.example.org\r\n"
"Connection: keep-alive\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),
};
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.get() != NULL);
EXPECT_EQ(1234, response->headers->GetContentLength());
EXPECT_EQ("HTTP/1.1 404 Not Found", response->headers->GetStatusLine());
EXPECT_TRUE(response->proxy_server.IsEmpty());
EXPECT_FALSE(proxy_headers_handler.observed_before_proxy_headers_sent());
std::string server_header;
size_t iter = 0;
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_P(HttpNetworkTransactionTest, ReuseConnection) {
scoped_ptr<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.example.org/");
request.load_flags = 0;
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
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.get() != NULL);
EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
EXPECT_TRUE(response->proxy_server.IsEmpty());
std::string response_data;
rv = ReadTransaction(trans.get(), &response_data);
EXPECT_EQ(OK, rv);
EXPECT_EQ(kExpectedResponseData[i], response_data);
}
}
TEST_P(HttpNetworkTransactionTest, Ignores100) {
std::vector<scoped_ptr<UploadElementReader>> element_readers;
element_readers.push_back(
make_scoped_ptr(new UploadBytesElementReader("foo", 3)));
ElementsUploadDataStream upload_data_stream(std::move(element_readers), 0);
HttpRequestInfo request;
request.method = "POST";
request.url = GURL("http://www.foo.com/");
request.upload_data_stream = &upload_data_stream;
request.load_flags = 0;
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
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.get() != 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_P(HttpNetworkTransactionTest, Ignores1xx) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.foo.com/");
request.load_flags = 0;
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
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.get() != 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_P(HttpNetworkTransactionTest, Incomplete100ThenEOF) {
HttpRequestInfo request;
request.method = "POST";
request.url = GURL("http://www.foo.com/");
request.load_flags = 0;
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
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_P(HttpNetworkTransactionTest, EmptyResponse) {
HttpRequestInfo request;
request.method = "POST";
request.url = GURL("http://www.foo.com/");
request.load_flags = 0;
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
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 HttpNetworkTransactionTest::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;
TestNetLog net_log;
session_deps_.net_log = &net_log;
scoped_ptr<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_FALSE(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* const kExpectedResponseData[] = {
"hello", "world"
};
uint32_t first_socket_log_id = NetLog::Source::kInvalidId;
for (int i = 0; i < 2; ++i) {
TestCompletionCallback callback;
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
int rv = trans->Start(&request, callback.callback(), BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback.WaitForResult();
EXPECT_EQ(OK, rv);
LoadTimingInfo load_timing_info;
EXPECT_TRUE(trans->GetLoadTimingInfo(&load_timing_info));
TestLoadTimingNotReused(load_timing_info, CONNECT_TIMING_HAS_DNS_TIMES);
if (i == 0) {
first_socket_log_id = load_timing_info.socket_log_id;
} else {
// The second request should be using a new socket.
EXPECT_NE(first_socket_log_id, load_timing_info.socket_log_id);
}
const HttpResponseInfo* response = trans->GetResponseInfo();
ASSERT_TRUE(response != NULL);
EXPECT_TRUE(response->headers.get() != 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);
}
}
void HttpNetworkTransactionTest::PreconnectErrorResendRequestTest(
const MockWrite* write_failure,
const MockRead* read_failure,
bool use_spdy) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("https://www.foo.com/");
request.load_flags = 0;
TestNetLog net_log;
session_deps_.net_log = &net_log;
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
SSLSocketDataProvider ssl1(ASYNC, OK);
SSLSocketDataProvider ssl2(ASYNC, OK);
if (use_spdy) {
ssl1.SetNextProto(GetProtocol());
ssl2.SetNextProto(GetProtocol());
}
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl1);
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl2);
// SPDY versions of the request and response.
scoped_ptr<SpdyFrame> spdy_request(spdy_util_.ConstructSpdyGet(
request.url.spec().c_str(), 1, DEFAULT_PRIORITY));
scoped_ptr<SpdyFrame> spdy_response(
spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
scoped_ptr<SpdyFrame> spdy_data(
spdy_util_.ConstructSpdyBodyFrame(1, "hello", 5, true));
// HTTP/1.1 versions of the request and response.
const char kHttpRequest[] = "GET / HTTP/1.1\r\n"
"Host: www.foo.com\r\n"
"Connection: keep-alive\r\n\r\n";
const char kHttpResponse[] = "HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\n";
const char kHttpData[] = "hello";
std::vector<MockRead> data1_reads;
std::vector<MockWrite> data1_writes;
if (write_failure) {
ASSERT_FALSE(read_failure);
data1_writes.push_back(*write_failure);
data1_reads.push_back(MockRead(ASYNC, OK));
} else {
ASSERT_TRUE(read_failure);
if (use_spdy) {
data1_writes.push_back(CreateMockWrite(*spdy_request));
} else {
data1_writes.push_back(MockWrite(kHttpRequest));
}
data1_reads.push_back(*read_failure);
}
StaticSocketDataProvider data1(&data1_reads[0], data1_reads.size(),
&data1_writes[0], data1_writes.size());
session_deps_.socket_factory->AddSocketDataProvider(&data1);
std::vector<MockRead> data2_reads;
std::vector<MockWrite> data2_writes;
if (use_spdy) {
data2_writes.push_back(CreateMockWrite(*spdy_request, 0, ASYNC));
data2_reads.push_back(CreateMockRead(*spdy_response, 1, ASYNC));
data2_reads.push_back(CreateMockRead(*spdy_data, 2, ASYNC));
data2_reads.push_back(MockRead(ASYNC, OK, 3));
} else {
data2_writes.push_back(
MockWrite(ASYNC, kHttpRequest, strlen(kHttpRequest), 0));
data2_reads.push_back(
MockRead(ASYNC, kHttpResponse, strlen(kHttpResponse), 1));
data2_reads.push_back(MockRead(ASYNC, kHttpData, strlen(kHttpData), 2));
data2_reads.push_back(MockRead(ASYNC, OK, 3));
}
SequencedSocketData data2(&data2_reads[0], data2_reads.size(),
&data2_writes[0], data2_writes.size());
session_deps_.socket_factory->AddSocketDataProvider(&data2);
// Preconnect a socket.
SSLConfig ssl_config;
session->ssl_config_service()->GetSSLConfig(&ssl_config);
session->GetAlpnProtos(&ssl_config.alpn_protos);
session->GetNpnProtos(&ssl_config.npn_protos);
session->http_stream_factory()->PreconnectStreams(1, request, ssl_config,
ssl_config);
// Wait for the preconnect to complete.
// TODO(davidben): Some way to wait for an idle socket count might be handy.
base::RunLoop().RunUntilIdle();
EXPECT_EQ(1, GetIdleSocketCountInSSLSocketPool(session.get()));
// Make the request.
TestCompletionCallback callback;
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
int rv = trans->Start(&request, callback.callback(), BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback.WaitForResult();
EXPECT_EQ(OK, rv);
LoadTimingInfo load_timing_info;
EXPECT_TRUE(trans->GetLoadTimingInfo(&load_timing_info));
TestLoadTimingNotReused(
load_timing_info,
CONNECT_TIMING_HAS_DNS_TIMES|CONNECT_TIMING_HAS_SSL_TIMES);
const HttpResponseInfo* response = trans->GetResponseInfo();
ASSERT_TRUE(response != NULL);
EXPECT_TRUE(response->headers.get() != NULL);
if (response->was_fetched_via_spdy) {
EXPECT_EQ("HTTP/1.1 200", response->headers->GetStatusLine());
} else {
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(kHttpData, response_data);
}
TEST_P(HttpNetworkTransactionTest,
KeepAliveConnectionNotConnectedOnWrite) {
MockWrite write_failure(ASYNC, ERR_SOCKET_NOT_CONNECTED);
KeepAliveConnectionResendRequestTest(&write_failure, NULL);
}
TEST_P(HttpNetworkTransactionTest, KeepAliveConnectionReset) {
MockRead read_failure(ASYNC, ERR_CONNECTION_RESET);
KeepAliveConnectionResendRequestTest(NULL, &read_failure);
}
TEST_P(HttpNetworkTransactionTest, KeepAliveConnectionEOF) {
MockRead read_failure(SYNCHRONOUS, OK); // EOF
KeepAliveConnectionResendRequestTest(NULL, &read_failure);
}
// Make sure that on a 408 response (Request Timeout), the request is retried,
// if the socket was a reused keep alive socket.
TEST_P(HttpNetworkTransactionTest, KeepAlive408) {
MockRead read_failure(SYNCHRONOUS,
"HTTP/1.1 408 Request Timeout\r\n"
"Connection: Keep-Alive\r\n"
"Content-Length: 6\r\n\r\n"
"Pickle");
KeepAliveConnectionResendRequestTest(NULL, &read_failure);
}
TEST_P(HttpNetworkTransactionTest,
PreconnectErrorNotConnectedOnWrite) {
MockWrite write_failure(ASYNC, ERR_SOCKET_NOT_CONNECTED);
PreconnectErrorResendRequestTest(&write_failure, NULL, false);
}
TEST_P(HttpNetworkTransactionTest, PreconnectErrorReset) {
MockRead read_failure(ASYNC, ERR_CONNECTION_RESET);
PreconnectErrorResendRequestTest(NULL, &read_failure, false);
}
TEST_P(HttpNetworkTransactionTest, PreconnectErrorEOF) {
MockRead read_failure(SYNCHRONOUS, OK); // EOF
PreconnectErrorResendRequestTest(NULL, &read_failure, false);
}
TEST_P(HttpNetworkTransactionTest, PreconnectErrorAsyncEOF) {
MockRead read_failure(ASYNC, OK); // EOF
PreconnectErrorResendRequestTest(NULL, &read_failure, false);
}
// Make sure that on a 408 response (Request Timeout), the request is retried,
// if the socket was a preconnected (UNUSED_IDLE) socket.
TEST_P(HttpNetworkTransactionTest, RetryOnIdle408) {
MockRead read_failure(SYNCHRONOUS,
"HTTP/1.1 408 Request Timeout\r\n"
"Connection: Keep-Alive\r\n"
"Content-Length: 6\r\n\r\n"
"Pickle");
KeepAliveConnectionResendRequestTest(NULL, &read_failure);
PreconnectErrorResendRequestTest(NULL, &read_failure, false);
}
TEST_P(HttpNetworkTransactionTest,
SpdyPreconnectErrorNotConnectedOnWrite) {
MockWrite write_failure(ASYNC, ERR_SOCKET_NOT_CONNECTED);
PreconnectErrorResendRequestTest(&write_failure, NULL, true);
}
TEST_P(HttpNetworkTransactionTest, SpdyPreconnectErrorReset) {
MockRead read_failure(ASYNC, ERR_CONNECTION_RESET);
PreconnectErrorResendRequestTest(NULL, &read_failure, true);
}
TEST_P(HttpNetworkTransactionTest, SpdyPreconnectErrorEOF) {
MockRead read_failure(SYNCHRONOUS, OK); // EOF
PreconnectErrorResendRequestTest(NULL, &read_failure, true);
}
TEST_P(HttpNetworkTransactionTest, SpdyPreconnectErrorAsyncEOF) {
MockRead read_failure(ASYNC, OK); // EOF
PreconnectErrorResendRequestTest(NULL, &read_failure, true);
}
TEST_P(HttpNetworkTransactionTest, NonKeepAliveConnectionReset) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.example.org/");
request.load_flags = 0;
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
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);
IPEndPoint endpoint;
EXPECT_TRUE(trans->GetRemoteEndpoint(&endpoint));
EXPECT_LT(0u, endpoint.address().size());
}
// 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_P(HttpNetworkTransactionTest, 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 network access can be deferred and resumed.
TEST_P(HttpNetworkTransactionTest, ThrottleBeforeNetworkStart) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.example.org/");
request.load_flags = 0;
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
// Defer on OnBeforeNetworkStart.
BeforeNetworkStartHandler net_start_handler(true); // defer
trans->SetBeforeNetworkStartCallback(
base::Bind(&BeforeNetworkStartHandler::OnBeforeNetworkStart,
base::Unretained(&net_start_handler)));
MockRead data_reads[] = {
MockRead("HTTP/1.0 200 OK\r\n"),
MockRead("Content-Length: 5\r\n\r\n"),
MockRead("hello"),
MockRead(SYNCHRONOUS, 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);
base::MessageLoop::current()->RunUntilIdle();
// Should have deferred for network start.
EXPECT_TRUE(net_start_handler.observed_before_network_start());
EXPECT_EQ(LOAD_STATE_WAITING_FOR_DELEGATE, trans->GetLoadState());
trans->ResumeNetworkStart();
rv = callback.WaitForResult();
EXPECT_EQ(OK, rv);
EXPECT_TRUE(trans->GetResponseInfo() != NULL);
scoped_refptr<IOBufferWithSize> io_buf(new IOBufferWithSize(100));
rv = trans->Read(io_buf.get(), io_buf->size(), callback.callback());
if (rv == ERR_IO_PENDING)
rv = callback.WaitForResult();
EXPECT_EQ(5, rv);
trans.reset();
}
// Test that network use can be deferred and canceled.
TEST_P(HttpNetworkTransactionTest, ThrottleAndCancelBeforeNetworkStart) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.example.org/");
request.load_flags = 0;
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
// Defer on OnBeforeNetworkStart.
BeforeNetworkStartHandler net_start_handler(true); // defer
trans->SetBeforeNetworkStartCallback(
base::Bind(&BeforeNetworkStartHandler::OnBeforeNetworkStart,
base::Unretained(&net_start_handler)));
TestCompletionCallback callback;
int rv = trans->Start(&request, callback.callback(), BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
base::MessageLoop::current()->RunUntilIdle();
// Should have deferred for network start.
EXPECT_TRUE(net_start_handler.observed_before_network_start());
EXPECT_EQ(LOAD_STATE_WAITING_FOR_DELEGATE, trans->GetLoadState());
}
// Next 2 cases (KeepAliveEarlyClose and KeepAliveEarlyClose2) are regression
// tests. There was a bug causing HttpNetworkTransaction to hang in the
// destructor in such situations.
// See http://crbug.com/154712 and http://crbug.com/156609.
TEST_P(HttpNetworkTransactionTest, KeepAliveEarlyClose) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.example.org/");
request.load_flags = 0;
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
MockRead data_reads[] = {
MockRead("HTTP/1.0 200 OK\r\n"),
MockRead("Connection: keep-alive\r\n"),
MockRead("Content-Length: 100\r\n\r\n"),
MockRead("hello"),
MockRead(SYNCHRONOUS, 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);
scoped_refptr<IOBufferWithSize> io_buf(new IOBufferWithSize(100));
rv = trans->Read(io_buf.get(), io_buf->size(), callback.callback());
if (rv == ERR_IO_PENDING)
rv = callback.WaitForResult();
EXPECT_EQ(5, rv);
rv = trans->Read(io_buf.get(), io_buf->size(), callback.callback());
EXPECT_EQ(ERR_CONTENT_LENGTH_MISMATCH, rv);
trans.reset();
base::MessageLoop::current()->RunUntilIdle();
EXPECT_EQ(0, GetIdleSocketCountInTransportSocketPool(session.get()));
}
TEST_P(HttpNetworkTransactionTest, KeepAliveEarlyClose2) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.example.org/");
request.load_flags = 0;
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
MockRead data_reads[] = {
MockRead("HTTP/1.0 200 OK\r\n"),
MockRead("Connection: keep-alive\r\n"),
MockRead("Content-Length: 100\r\n\r\n"),
MockRead(SYNCHRONOUS, 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);
scoped_refptr<IOBufferWithSize> io_buf(new IOBufferWithSize(100));
rv = trans->Read(io_buf.get(), io_buf->size(), callback.callback());
if (rv == ERR_IO_PENDING)
rv = callback.WaitForResult();
EXPECT_EQ(ERR_CONTENT_LENGTH_MISMATCH, rv);
trans.reset();
base::MessageLoop::current()->RunUntilIdle();
EXPECT_EQ(0, GetIdleSocketCountInTransportSocketPool(session.get()));
}
// Test that we correctly reuse a keep-alive connection after not explicitly
// reading the body.
TEST_P(HttpNetworkTransactionTest, KeepAliveAfterUnreadBody) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.foo.com/");
request.load_flags = 0;
TestNetLog net_log;
session_deps_.net_log = &net_log;
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
const char* request_data =
"GET / HTTP/1.1\r\n"
"Host: www.foo.com\r\n"
"Connection: keep-alive\r\n\r\n";
MockWrite data_writes[] = {
MockWrite(ASYNC, 0, request_data), MockWrite(ASYNC, 2, request_data),
MockWrite(ASYNC, 4, request_data), MockWrite(ASYNC, 6, request_data),
MockWrite(ASYNC, 8, request_data), MockWrite(ASYNC, 10, request_data),
MockWrite(ASYNC, 12, request_data), MockWrite(ASYNC, 14, request_data),
MockWrite(ASYNC, 17, request_data), MockWrite(ASYNC, 20, request_data),
};
// Note that because all these reads happen in the same
// StaticSocketDataProvider, it shows that the same socket is being reused for
// all transactions.
MockRead data_reads[] = {
MockRead(ASYNC, 1, "HTTP/1.1 204 No Content\r\n\r\n"),
MockRead(ASYNC, 3, "HTTP/1.1 205 Reset Content\r\n\r\n"),
MockRead(ASYNC, 5, "HTTP/1.1 304 Not Modified\r\n\r\n"),
MockRead(ASYNC, 7,
"HTTP/1.1 302 Found\r\n"
"Content-Length: 0\r\n\r\n"),
MockRead(ASYNC, 9,
"HTTP/1.1 302 Found\r\n"
"Content-Length: 5\r\n\r\n"
"hello"),
MockRead(ASYNC, 11,
"HTTP/1.1 301 Moved Permanently\r\n"
"Content-Length: 0\r\n\r\n"),
MockRead(ASYNC, 13,
"HTTP/1.1 301 Moved Permanently\r\n"
"Content-Length: 5\r\n\r\n"
"hello"),
// In the next two rounds, IsConnectedAndIdle returns false, due to
// the set_busy_before_sync_reads(true) call, while the
// HttpNetworkTransaction is being shut down, but the socket is still
// reuseable. See http://crbug.com/544255.
MockRead(ASYNC, 15,
"HTTP/1.1 200 Hunky-Dory\r\n"
"Content-Length: 5\r\n\r\n"),
MockRead(SYNCHRONOUS, 16, "hello"),
MockRead(ASYNC, 18,
"HTTP/1.1 200 Hunky-Dory\r\n"
"Content-Length: 5\r\n\r\n"
"he"),
MockRead(SYNCHRONOUS, 19, "llo"),
// The body of the final request is actually read.
MockRead(ASYNC, 21, "HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\n"),
MockRead(ASYNC, 22, "hello"),
};
SequencedSocketData data(data_reads, arraysize(data_reads), data_writes,
arraysize(data_writes));
data.set_busy_before_sync_reads(true);
session_deps_.socket_factory->AddSocketDataProvider(&data);
const int kNumUnreadBodies = arraysize(data_writes) - 1;
std::string response_lines[kNumUnreadBodies];
uint32_t first_socket_log_id = NetLog::Source::kInvalidId;
for (size_t i = 0; i < kNumUnreadBodies; ++i) {
TestCompletionCallback callback;
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
int rv = trans->Start(&request, callback.callback(), BoundNetLog());
EXPECT_EQ(OK, callback.GetResult(rv));
LoadTimingInfo load_timing_info;
EXPECT_TRUE(trans->GetLoadTimingInfo(&load_timing_info));
if (i == 0) {
TestLoadTimingNotReused(load_timing_info, CONNECT_TIMING_HAS_DNS_TIMES);
first_socket_log_id = load_timing_info.socket_log_id;
} else {
TestLoadTimingReused(load_timing_info);
EXPECT_EQ(first_socket_log_id, load_timing_info.socket_log_id);
}
const HttpResponseInfo* response = trans->GetResponseInfo();
ASSERT_TRUE(response);
ASSERT_TRUE(response->headers);
response_lines[i] = response->headers->GetStatusLine();
// Delete the transaction without reading the response bodies. Then spin
// the message loop, so the response bodies are drained.
trans.reset();
base::RunLoop().RunUntilIdle();
}
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",
"HTTP/1.1 200 Hunky-Dory",
"HTTP/1.1 200 Hunky-Dory",
};
static_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(DEFAULT_PRIORITY, session.get()));
int rv = trans->Start(&request, callback.callback(), BoundNetLog());
EXPECT_EQ(OK, callback.GetResult(rv));
const HttpResponseInfo* response = trans->GetResponseInfo();
ASSERT_TRUE(response);
ASSERT_TRUE(response->headers);
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_P(HttpNetworkTransactionTest, BasicAuth) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.example.org/");
request.load_flags = 0;
TestNetLog log;
session_deps_.net_log = &log;
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
MockWrite data_writes1[] = {
MockWrite(
"GET / HTTP/1.1\r\n"
"Host: www.example.org\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.example.org\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);
LoadTimingInfo load_timing_info1;
EXPECT_TRUE(trans->GetLoadTimingInfo(&load_timing_info1));
TestLoadTimingNotReused(load_timing_info1, CONNECT_TIMING_HAS_DNS_TIMES);
int64_t writes_size1 = CountWriteBytes(data_writes1, arraysize(data_writes1));
EXPECT_EQ(writes_size1, trans->GetTotalSentBytes());
int64_t reads_size1 = CountReadBytes(data_reads1, arraysize(data_reads1));
EXPECT_EQ(reads_size1, trans->GetTotalReceivedBytes());
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);
LoadTimingInfo load_timing_info2;
EXPECT_TRUE(trans->GetLoadTimingInfo(&load_timing_info2));
TestLoadTimingNotReused(load_timing_info2, CONNECT_TIMING_HAS_DNS_TIMES);
// The load timing after restart should have a new socket ID, and times after
// those of the first load timing.
EXPECT_LE(load_timing_info1.receive_headers_end,
load_timing_info2.connect_timing.connect_start);
EXPECT_NE(load_timing_info1.socket_log_id, load_timing_info2.socket_log_id);
int64_t writes_size2 = CountWriteBytes(data_writes2, arraysize(data_writes2));
EXPECT_EQ(writes_size1 + writes_size2, trans->GetTotalSentBytes());
int64_t reads_size2 = CountReadBytes(data_reads2, arraysize(data_reads2));
EXPECT_EQ(reads_size1 + reads_size2, trans->GetTotalReceivedBytes());
response = trans->GetResponseInfo();
ASSERT_TRUE(response != NULL);
EXPECT_TRUE(response->auth_challenge.get() == NULL);
EXPECT_EQ(100, response->headers->GetContentLength());
}
// Test the request-challenge-retry sequence for basic auth.
// (basic auth is the easiest to mock, because it has no randomness).
TEST_P(HttpNetworkTransactionTest, BasicAuthWithAddressChange) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.example.org/");
request.load_flags = 0;
TestNetLog log;
MockHostResolver* resolver = new MockHostResolver();
session_deps_.net_log = &log;
session_deps_.host_resolver.reset(resolver);
scoped_ptr<HttpNetworkSession> session = CreateSession(&session_deps_);
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
resolver->rules()->ClearRules();
resolver->rules()->AddRule("www.example.org", "127.0.0.1");
MockWrite data_writes1[] = {
MockWrite("GET / HTTP/1.1\r\n"
"Host: www.example.org\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.example.org\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;
EXPECT_EQ(OK, callback1.GetResult(trans->Start(&request, callback1.callback(),
BoundNetLog())));
LoadTimingInfo load_timing_info1;
EXPECT_TRUE(trans->GetLoadTimingInfo(&load_timing_info1));
TestLoadTimingNotReused(load_timing_info1, CONNECT_TIMING_HAS_DNS_TIMES);
int64_t writes_size1 = CountWriteBytes(data_writes1, arraysize(data_writes1));
EXPECT_EQ(writes_size1, trans->GetTotalSentBytes());
int64_t reads_size1 = CountReadBytes(data_reads1, arraysize(data_reads1));
EXPECT_EQ(reads_size1, trans->GetTotalReceivedBytes());
const HttpResponseInfo* response = trans->GetResponseInfo();
ASSERT_TRUE(response);
EXPECT_TRUE(CheckBasicServerAuth(response->auth_challenge.get()));
IPEndPoint endpoint;
EXPECT_TRUE(trans->GetRemoteEndpoint(&endpoint));
ASSERT_FALSE(endpoint.address().empty());
EXPECT_EQ("127.0.0.1:80", endpoint.ToString());
resolver->rules()->ClearRules();
resolver->rules()->AddRule("www.example.org", "127.0.0.2");
TestCompletionCallback callback2;
EXPECT_EQ(OK, callback2.GetResult(trans->RestartWithAuth(
AuthCredentials(kFoo, kBar), callback2.callback())));
LoadTimingInfo load_timing_info2;
EXPECT_TRUE(trans->GetLoadTimingInfo(&load_timing_info2));
TestLoadTimingNotReused(load_timing_info2, CONNECT_TIMING_HAS_DNS_TIMES);
// The load timing after restart should have a new socket ID, and times after
// those of the first load timing.
EXPECT_LE(load_timing_info1.receive_headers_end,
load_timing_info2.connect_timing.connect_start);
EXPECT_NE(load_timing_info1.socket_log_id, load_timing_info2.socket_log_id);
int64_t writes_size2 = CountWriteBytes(data_writes2, arraysize(data_writes2));
EXPECT_EQ(writes_size1 + writes_size2, trans->GetTotalSentBytes());
int64_t reads_size2 = CountReadBytes(data_reads2, arraysize(data_reads2));
EXPECT_EQ(reads_size1 + reads_size2, trans->GetTotalReceivedBytes());
response = trans->GetResponseInfo();
ASSERT_TRUE(response);
EXPECT_FALSE(response->auth_challenge.get());
EXPECT_EQ(100, response->headers->GetContentLength());
EXPECT_TRUE(trans->GetRemoteEndpoint(&endpoint));
ASSERT_FALSE(endpoint.address().empty());
EXPECT_EQ("127.0.0.2:80", endpoint.ToString());
}
TEST_P(HttpNetworkTransactionTest, DoNotSendAuth) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.example.org/");
request.load_flags = LOAD_DO_NOT_SEND_AUTH_DATA;
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
MockWrite data_writes[] = {
MockWrite(
"GET / HTTP/1.1\r\n"
"Host: www.example.org\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);
int64_t writes_size = CountWriteBytes(data_writes, arraysize(data_writes));
EXPECT_EQ(writes_size, trans->GetTotalSentBytes());
int64_t reads_size = CountReadBytes(data_reads, arraysize(data_reads));
EXPECT_EQ(reads_size, trans->GetTotalReceivedBytes());
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_P(HttpNetworkTransactionTest, BasicAuthKeepAlive) {
// On the second pass, the body read of the auth challenge is synchronous, so
// IsConnectedAndIdle returns false. The socket should still be drained and
// reused. See http://crbug.com/544255.
for (int i = 0; i < 2; ++i) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.example.org/");
request.load_flags = 0;
TestNetLog log;
session_deps_.net_log = &log;
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
MockWrite data_writes[] = {
MockWrite(ASYNC, 0,
"GET / HTTP/1.1\r\n"
"Host: www.example.org\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(ASYNC, 6,
"GET / HTTP/1.1\r\n"
"Host: www.example.org\r\n"
"Connection: keep-alive\r\n"
"Authorization: Basic Zm9vOmJhcg==\r\n\r\n"),
};
MockRead data_reads[] = {
MockRead(ASYNC, 1, "HTTP/1.1 401 Unauthorized\r\n"),
MockRead(ASYNC, 2, "WWW-Authenticate: Basic realm=\"MyRealm1\"\r\n"),
MockRead(ASYNC, 3, "Content-Type: text/html; charset=iso-8859-1\r\n"),
MockRead(ASYNC, 4, "Content-Length: 14\r\n\r\n"),
MockRead(i == 0 ? ASYNC : SYNCHRONOUS, 5, "Unauthorized\r\n"),
// Lastly, the server responds with the actual content.
MockRead(ASYNC, 7, "HTTP/1.1 200 OK\r\n"),
MockRead(ASYNC, 8, "Content-Type: text/html; charset=iso-8859-1\r\n"),
MockRead(ASYNC, 9, "Content-Length: 5\r\n\r\n"),
MockRead(ASYNC, 10, "Hello"),
};
SequencedSocketData data(data_reads, arraysize(data_reads), data_writes,
arraysize(data_writes));
data.set_busy_before_sync_reads(true);
session_deps_.socket_factory->AddSocketDataProvider(&data);
TestCompletionCallback callback1;
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
int rv = trans->Start(&request, callback1.callback(), BoundNetLog());
ASSERT_EQ(OK, callback1.GetResult(rv));
LoadTimingInfo load_timing_info1;
EXPECT_TRUE(trans->GetLoadTimingInfo(&load_timing_info1));
TestLoadTimingNotReused(load_timing_info1, CONNECT_TIMING_HAS_DNS_TIMES);
const HttpResponseInfo* response = trans->GetResponseInfo();
ASSERT_TRUE(response);
EXPECT_TRUE(CheckBasicServerAuth(response->auth_challenge.get()));
TestCompletionCallback callback2;
rv = trans->RestartWithAuth(AuthCredentials(kFoo, kBar),
callback2.callback());
ASSERT_EQ(OK, callback2.GetResult(rv));
LoadTimingInfo load_timing_info2;
EXPECT_TRUE(trans->GetLoadTimingInfo(&load_timing_info2));
TestLoadTimingReused(load_timing_info2);
// The load timing after restart should have the same socket ID, and times
// those of the first load timing.
EXPECT_LE(load_timing_info1.receive_headers_end,
load_timing_info2.send_start);
EXPECT_EQ(load_timing_info1.socket_log_id, load_timing_info2.socket_log_id);
response = trans->GetResponseInfo();
ASSERT_TRUE(response);
EXPECT_FALSE(response->auth_challenge);
EXPECT_EQ(5, response->headers->GetContentLength());
std::string response_data;
EXPECT_EQ(OK, ReadTransaction(trans.get(), &response_data));
int64_t writes_size = CountWriteBytes(data_writes, arraysize(data_writes));
EXPECT_EQ(writes_size, trans->GetTotalSentBytes());
int64_t reads_size = CountReadBytes(data_reads, arraysize(data_reads));
EXPECT_EQ(reads_size, trans->GetTotalReceivedBytes());
}
}
// Test the request-challenge-retry sequence for basic auth, over a keep-alive
// connection and with no response body to drain.
TEST_P(HttpNetworkTransactionTest, BasicAuthKeepAliveNoBody) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.example.org/");
request.load_flags = 0;
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
MockWrite data_writes1[] = {
MockWrite(
"GET / HTTP/1.1\r\n"
"Host: www.example.org\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.example.org\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(DEFAULT_PRIORITY, session.get()));
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_P(HttpNetworkTransactionTest, BasicAuthKeepAliveLargeBody) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.example.org/");
request.load_flags = 0;
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
MockWrite data_writes1[] = {
MockWrite(
"GET / HTTP/1.1\r\n"
"Host: www.example.org\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.example.org\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(DEFAULT_PRIORITY, session.get()));
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_P(HttpNetworkTransactionTest, BasicAuthKeepAliveImpatientServer) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.example.org/");
request.load_flags = 0;
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
MockWrite data_writes1[] = {
MockWrite(
"GET / HTTP/1.1\r\n"
"Host: www.example.org\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.example.org\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.example.org\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(DEFAULT_PRIORITY, session.get()));
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_P(HttpNetworkTransactionTest, BasicAuthProxyNoKeepAliveHttp10) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("https://www.example.org/");
// when the no authentication data flag is set.
request.load_flags = LOAD_DO_NOT_SEND_AUTH_DATA;
// Configure against proxy server "myproxy:70".
session_deps_.proxy_service =
ProxyService::CreateFixedFromPacResult("PROXY myproxy:70");
BoundTestNetLog log;
session_deps_.net_log = log.bound().net_log();
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
// Since we have proxy, should try to establish tunnel.
MockWrite data_writes1[] = {
MockWrite("CONNECT www.example.org:443 HTTP/1.1\r\n"
"Host: www.example.org:443\r\n"
"Proxy-Connection: keep-alive\r\n\r\n"),
};
// The proxy responds to the connect with a 407, using a non-persistent
// connection.
MockRead data_reads1[] = {
// No credentials.
MockRead("HTTP/1.0 407 Proxy Authentication Required\r\n"),
MockRead("Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n\r\n"),
};
// Since the first connection couldn't be reused, need to establish another
// once given credentials.
MockWrite data_writes2[] = {
// After calling trans->RestartWithAuth(), this is the request we should
// be issuing -- the final header line contains the credentials.
MockWrite("CONNECT www.example.org:443 HTTP/1.1\r\n"
"Host: www.example.org:443\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.example.org\r\n"
"Connection: keep-alive\r\n\r\n"),
};
MockRead data_reads2[] = {
MockRead("HTTP/1.0 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);
StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2),
data_writes2, arraysize(data_writes2));
session_deps_.socket_factory->AddSocketDataProvider(&data2);
SSLSocketDataProvider ssl(ASYNC, OK);
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
TestCompletionCallback callback1;
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
int rv = trans->Start(&request, callback1.callback(), log.bound());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback1.WaitForResult();
EXPECT_EQ(OK, rv);
TestNetLogEntry::List 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_FALSE(response->headers->IsKeepAlive());
ASSERT_FALSE(response->headers.get() == NULL);
EXPECT_EQ(407, response->headers->response_code());
EXPECT_TRUE(HttpVersion(1, 0) == response->headers->GetHttpVersion());
EXPECT_TRUE(CheckBasicProxyAuth(response->auth_challenge.get()));
LoadTimingInfo load_timing_info;
// CONNECT requests and responses are handled at the connect job level, so
// the transaction does not yet have a connection.
EXPECT_FALSE(trans->GetLoadTimingInfo(&load_timing_info));
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);
EXPECT_TRUE(trans->GetLoadTimingInfo(&load_timing_info));
TestLoadTimingNotReusedWithPac(load_timing_info,
CONNECT_TIMING_HAS_SSL_TIMES);
trans.reset();
session->CloseAllConnections();
}
// Test the request-challenge-retry sequence for basic auth, over a connection
// that requires a restart when setting up an SSL tunnel.
TEST_P(HttpNetworkTransactionTest, BasicAuthProxyNoKeepAliveHttp11) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("https://www.example.org/");
// when the no authentication data flag is set.
request.load_flags = LOAD_DO_NOT_SEND_AUTH_DATA;
// Configure against proxy server "myproxy:70".
session_deps_.proxy_service =
ProxyService::CreateFixedFromPacResult("PROXY myproxy:70");
BoundTestNetLog log;
session_deps_.net_log = log.bound().net_log();
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
// Since we have proxy, should try to establish tunnel.
MockWrite data_writes1[] = {
MockWrite("CONNECT www.example.org:443 HTTP/1.1\r\n"
"Host: www.example.org:443\r\n"
"Proxy-Connection: keep-alive\r\n\r\n"),
};
// The proxy responds to the connect with a 407, using a non-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"),
};
MockWrite data_writes2[] = {
// After calling trans->RestartWithAuth(), this is the request we should
// be issuing -- the final header line contains the credentials.
MockWrite("CONNECT www.example.org:443 HTTP/1.1\r\n"
"Host: www.example.org:443\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.example.org\r\n"
"Connection: keep-alive\r\n\r\n"),
};
MockRead data_reads2[] = {
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);
StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2),
data_writes2, arraysize(data_writes2));
session_deps_.socket_factory->AddSocketDataProvider(&data2);
SSLSocketDataProvider ssl(ASYNC, OK);
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
TestCompletionCallback callback1;
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
int rv = trans->Start(&request, callback1.callback(), log.bound());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback1.WaitForResult();
EXPECT_EQ(OK, rv);
TestNetLogEntry::List 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_FALSE(response->headers->IsKeepAlive());
ASSERT_FALSE(response->headers.get() == NULL);
EXPECT_EQ(407, response->headers->response_code());
EXPECT_TRUE(HttpVersion(1, 1) == response->headers->GetHttpVersion());
EXPECT_TRUE(CheckBasicProxyAuth(response->auth_challenge.get()));
LoadTimingInfo load_timing_info;
// CONNECT requests and responses are handled at the connect job level, so
// the transaction does not yet have a connection.
EXPECT_FALSE(trans->GetLoadTimingInfo(&load_timing_info));
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);
EXPECT_TRUE(trans->GetLoadTimingInfo(&load_timing_info));
TestLoadTimingNotReusedWithPac(load_timing_info,
CONNECT_TIMING_HAS_SSL_TIMES);
trans.reset();
session->CloseAllConnections();
}
// Test the request-challenge-retry sequence for basic auth, over a keep-alive
// proxy connection with HTTP/1.0 responses, when setting up an SSL tunnel.
TEST_P(HttpNetworkTransactionTest, BasicAuthProxyKeepAliveHttp10) {
// On the second pass, the body read of the auth challenge is synchronous, so
// IsConnectedAndIdle returns false. The socket should still be drained and
// reused. See http://crbug.com/544255.
for (int i = 0; i < 2; ++i) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("https://www.example.org/");
// Ensure that proxy authentication is attempted even
// when the no authentication data flag is set.
request.load_flags = LOAD_DO_NOT_SEND_AUTH_DATA;
// Configure against proxy server "myproxy:70".
session_deps_.proxy_service = ProxyService::CreateFixed("myproxy:70");
BoundTestNetLog log;
session_deps_.net_log = log.bound().net_log();
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
// Since we have proxy, should try to establish tunnel.
MockWrite data_writes1[] = {
MockWrite(ASYNC, 0,
"CONNECT www.example.org:443 HTTP/1.1\r\n"
"Host: www.example.org:443\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(ASYNC, 3,
"CONNECT www.example.org:443 HTTP/1.1\r\n"
"Host: www.example.org:443\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. (Since it's HTTP/1.0, keep-alive has to be explicit.)
MockRead data_reads1[] = {
// No credentials.
MockRead(ASYNC, 1,
"HTTP/1.0 407 Proxy Authentication Required\r\n"
"Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n"
"Proxy-Connection: keep-alive\r\n"
"Content-Length: 10\r\n\r\n"),
MockRead(i == 0 ? ASYNC : SYNCHRONOUS, 2, "0123456789"),
// Wrong credentials (wrong password).
MockRead(ASYNC, 4,
"HTTP/1.0 407 Proxy Authentication Required\r\n"
"Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n"
"Proxy-Connection: keep-alive\r\n"
"Content-Length: 10\r\n\r\n"),
// No response body because the test stops reading here.
MockRead(SYNCHRONOUS, ERR_UNEXPECTED, 5),
};
SequencedSocketData data1(data_reads1, arraysize(data_reads1), data_writes1,
arraysize(data_writes1));
data1.set_busy_before_sync_reads(true);
session_deps_.socket_factory->AddSocketDataProvider(&data1);
TestCompletionCallback callback1;
int rv = trans->Start(&request, callback1.callback(), log.bound());
EXPECT_EQ(OK, callback1.GetResult(rv));
TestNetLogEntry::List 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);
ASSERT_TRUE(response->headers);
EXPECT_TRUE(response->headers->IsKeepAlive());
EXPECT_EQ(407, response->headers->response_code());
EXPECT_EQ(10, response->headers->GetContentLength());
EXPECT_TRUE(HttpVersion(1, 0) == 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(OK, callback2.GetResult(rv));
response = trans->GetResponseInfo();
ASSERT_TRUE(response);
ASSERT_TRUE(response->headers);
EXPECT_TRUE(response->headers->IsKeepAlive());
EXPECT_EQ(407, response->headers->response_code());
EXPECT_EQ(10, response->headers->GetContentLength());
EXPECT_TRUE(HttpVersion(1, 0) == 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 the request-challenge-retry sequence for basic auth, over a keep-alive
// proxy connection with HTTP/1.1 responses, when setting up an SSL tunnel.
TEST_P(HttpNetworkTransactionTest, BasicAuthProxyKeepAliveHttp11) {
// On the second pass, the body read of the auth challenge is synchronous, so
// IsConnectedAndIdle returns false. The socket should still be drained and
// reused. See http://crbug.com/544255.
for (int i = 0; i < 2; ++i) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("https://www.example.org/");
// Ensure that proxy authentication is attempted even
// when the no authentication data flag is set.
request.load_flags = LOAD_DO_NOT_SEND_AUTH_DATA;
// Configure against proxy server "myproxy:70".
session_deps_.proxy_service = ProxyService::CreateFixed("myproxy:70");
BoundTestNetLog log;
session_deps_.net_log = log.bound().net_log();
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
// Since we have proxy, should try to establish tunnel.
MockWrite data_writes1[] = {
MockWrite(ASYNC, 0,
"CONNECT www.example.org:443 HTTP/1.1\r\n"
"Host: www.example.org:443\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(ASYNC, 3,
"CONNECT www.example.org:443 HTTP/1.1\r\n"
"Host: www.example.org:443\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. (Since it's HTTP/1.0, keep-alive has to be explicit.)
MockRead data_reads1[] = {
// No credentials.
MockRead(ASYNC, 1,
"HTTP/1.1 407 Proxy Authentication Required\r\n"
"Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n"
"Content-Length: 10\r\n\r\n"),
MockRead(i == 0 ? ASYNC : SYNCHRONOUS, 2, "0123456789"),
// Wrong credentials (wrong password).
MockRead(ASYNC, 4,
"HTTP/1.1 407 Proxy Authentication Required\r\n"
"Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n"
"Content-Length: 10\r\n\r\n"),
// No response body because the test stops reading here.
MockRead(SYNCHRONOUS, ERR_UNEXPECTED, 5),
};
SequencedSocketData data1(data_reads1, arraysize(data_reads1), data_writes1,
arraysize(data_writes1));
data1.set_busy_before_sync_reads(true);
session_deps_.socket_factory->AddSocketDataProvider(&data1);
TestCompletionCallback callback1;
int rv = trans->Start(&request, callback1.callback(), log.bound());
EXPECT_EQ(OK, callback1.GetResult(rv));
TestNetLogEntry::List 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);
ASSERT_TRUE(response->headers);
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(OK, callback2.GetResult(rv));
response = trans->GetResponseInfo();
ASSERT_TRUE(response);
ASSERT_TRUE(response->headers);
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 the request-challenge-retry sequence for basic auth, over a keep-alive
// proxy connection with HTTP/1.1 responses, when setting up an SSL tunnel, in
// the case the server sends extra data on the original socket, so it can't be
// reused.
TEST_P(HttpNetworkTransactionTest, BasicAuthProxyKeepAliveExtraData) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("https://www.example.org/");
// when the no authentication data flag is set.
request.load_flags = LOAD_DO_NOT_SEND_AUTH_DATA;
// Configure against proxy server "myproxy:70".
session_deps_.proxy_service =
ProxyService::CreateFixedFromPacResult("PROXY myproxy:70");
BoundTestNetLog log;
session_deps_.net_log = log.bound().net_log();
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
// Since we have proxy, should try to establish tunnel.
MockWrite data_writes1[] = {
MockWrite(ASYNC, 0,
"CONNECT www.example.org:443 HTTP/1.1\r\n"
"Host: www.example.org:443\r\n"
"Proxy-Connection: keep-alive\r\n\r\n"),
};
// The proxy responds to the connect with a 407, using a persistent, but sends
// extra data, so the socket cannot be reused.
MockRead data_reads1[] = {
// No credentials.
MockRead(ASYNC, 1,
"HTTP/1.1 407 Proxy Authentication Required\r\n"
"Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n"
"Content-Length: 10\r\n\r\n"),
MockRead(SYNCHRONOUS, 2, "0123456789"),
MockRead(SYNCHRONOUS, 3, "I'm broken!"),
};
MockWrite data_writes2[] = {
// After calling trans->RestartWithAuth(), this is the request we should
// be issuing -- the final header line contains the credentials.
MockWrite(ASYNC, 0,
"CONNECT www.example.org:443 HTTP/1.1\r\n"
"Host: www.example.org:443\r\n"
"Proxy-Connection: keep-alive\r\n"
"Proxy-Authorization: Basic Zm9vOmJhcg==\r\n\r\n"),
MockWrite(ASYNC, 2,
"GET / HTTP/1.1\r\n"
"Host: www.example.org\r\n"
"Connection: keep-alive\r\n\r\n"),
};
MockRead data_reads2[] = {
MockRead(ASYNC, 1, "HTTP/1.1 200 Connection Established\r\n\r\n"),
MockRead(ASYNC, 3,
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/html; charset=iso-8859-1\r\n"
"Content-Length: 5\r\n\r\n"),
// No response body because the test stops reading here.
MockRead(SYNCHRONOUS, ERR_UNEXPECTED, 4),
};
SequencedSocketData data1(data_reads1, arraysize(data_reads1), data_writes1,
arraysize(data_writes1));
data1.set_busy_before_sync_reads(true);
session_deps_.socket_factory->AddSocketDataProvider(&data1);
SequencedSocketData data2(data_reads2, arraysize(data_reads2), data_writes2,
arraysize(data_writes2));
session_deps_.socket_factory->AddSocketDataProvider(&data2);
SSLSocketDataProvider ssl(ASYNC, OK);
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
TestCompletionCallback callback1;
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
int rv = trans->Start(&request, callback1.callback(), log.bound());
EXPECT_EQ(OK, callback1.GetResult(rv));
TestNetLogEntry::List 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);
ASSERT_TRUE(response->headers);
EXPECT_TRUE(response->headers->IsKeepAlive());
EXPECT_EQ(407, response->headers->response_code());
EXPECT_TRUE(HttpVersion(1, 1) == response->headers->GetHttpVersion());
EXPECT_TRUE(CheckBasicProxyAuth(response->auth_challenge.get()));
LoadTimingInfo load_timing_info;
// CONNECT requests and responses are handled at the connect job level, so
// the transaction does not yet have a connection.
EXPECT_FALSE(trans->GetLoadTimingInfo(&load_timing_info));
TestCompletionCallback callback2;
rv =
trans->RestartWithAuth(AuthCredentials(kFoo, kBar), callback2.callback());
EXPECT_EQ(OK, callback2.GetResult(rv));
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_FALSE(response->auth_challenge);
EXPECT_TRUE(trans->GetLoadTimingInfo(&load_timing_info));
TestLoadTimingNotReusedWithPac(load_timing_info,
CONNECT_TIMING_HAS_SSL_TIMES);
trans.reset();
session->CloseAllConnections();
}
// Test the case a proxy closes a socket while the challenge body is being
// drained.
TEST_P(HttpNetworkTransactionTest, BasicAuthProxyKeepAliveHangupDuringBody) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("https://www.example.org/");
// Ensure that proxy authentication is attempted even
// when the no authentication data flag is set.
request.load_flags = LOAD_DO_NOT_SEND_AUTH_DATA;
// Configure against proxy server "myproxy:70".
session_deps_.proxy_service = ProxyService::CreateFixed("myproxy:70");
scoped_ptr<HttpNetworkSession> session = CreateSession(&session_deps_);
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
// Since we have proxy, should try to establish tunnel.
MockWrite data_writes1[] = {
MockWrite("CONNECT www.example.org:443 HTTP/1.1\r\n"
"Host: www.example.org:443\r\n"
"Proxy-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("Content-Length: 10\r\n\r\n"), MockRead("spam!"),
// Server hands up in the middle of the body.
MockRead(ASYNC, ERR_CONNECTION_CLOSED),
};
MockWrite data_writes2[] = {
// After calling trans->RestartWithAuth(), this is the request we should
// be issuing -- the final header line contains the credentials.
MockWrite("CONNECT www.example.org:443 HTTP/1.1\r\n"
"Host: www.example.org:443\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.example.org\r\n"
"Connection: keep-alive\r\n\r\n"),
};
MockRead data_reads2[] = {
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);
StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2),
data_writes2, arraysize(data_writes2));
session_deps_.socket_factory->AddSocketDataProvider(&data2);
SSLSocketDataProvider ssl(ASYNC, OK);
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
TestCompletionCallback callback;
int rv = trans->Start(&request, callback.callback(), BoundNetLog());
EXPECT_EQ(OK, callback.GetResult(rv));
const HttpResponseInfo* response = trans->GetResponseInfo();
ASSERT_TRUE(response);
ASSERT_TRUE(response->headers);
EXPECT_TRUE(response->headers->IsKeepAlive());
EXPECT_EQ(407, response->headers->response_code());
EXPECT_TRUE(CheckBasicProxyAuth(response->auth_challenge.get()));
rv = trans->RestartWithAuth(AuthCredentials(kFoo, kBar), callback.callback());
EXPECT_EQ(OK, callback.GetResult(rv));
response = trans->GetResponseInfo();
ASSERT_TRUE(response);
ASSERT_TRUE(response->headers);
EXPECT_TRUE(response->headers->IsKeepAlive());
EXPECT_EQ(200, response->headers->response_code());
std::string body;
EXPECT_EQ(OK, ReadTransaction(trans.get(), &body));
EXPECT_EQ("hello", body);
}
// 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_P(HttpNetworkTransactionTest, BasicAuthProxyCancelTunnel) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("https://www.example.org/");
request.load_flags = 0;
// Configure against proxy server "myproxy:70".
session_deps_.proxy_service = ProxyService::CreateFixed("myproxy:70");
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
// Since we have proxy, should try to establish tunnel.
MockWrite data_writes[] = {
MockWrite("CONNECT www.example.org:443 HTTP/1.1\r\n"
"Host: www.example.org:443\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("0123456789"),
MockRead(SYNCHRONOUS, ERR_UNEXPECTED),
};
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);
ASSERT_TRUE(response->headers);
EXPECT_TRUE(response->headers->IsKeepAlive());
EXPECT_EQ(407, response->headers->response_code());
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 that we don't pass extraneous headers from the proxy's response to the
// caller when the proxy responds to CONNECT with 407.
TEST_P(HttpNetworkTransactionTest, SanitizeProxyAuthHeaders) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("https://www.example.org/");
request.load_flags = 0;
// Configure against proxy server "myproxy:70".
session_deps_.proxy_service = ProxyService::CreateFixed("myproxy:70");
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
// Since we have proxy, should try to establish tunnel.
MockWrite data_writes[] = {
MockWrite("CONNECT www.example.org:443 HTTP/1.1\r\n"
"Host: www.example.org:443\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("X-Foo: bar\r\n"),
MockRead("Set-Cookie: foo=bar\r\n"),
MockRead("Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n"),
MockRead("Content-Length: 10\r\n\r\n"),
MockRead(SYNCHRONOUS, ERR_UNEXPECTED),
};
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);
ASSERT_TRUE(response->headers);
EXPECT_TRUE(response->headers->IsKeepAlive());
EXPECT_EQ(407, response->headers->response_code());
EXPECT_TRUE(HttpVersion(1, 1) == response->headers->GetHttpVersion());
EXPECT_FALSE(response->headers->HasHeader("X-Foo"));
EXPECT_FALSE(response->headers->HasHeader("Set-Cookie"));
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_P(HttpNetworkTransactionTest, UnexpectedProxyAuth) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.example.org/");
request.load_flags = 0;
// We are using a DIRECT connection (i.e. no proxy) for this session.
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
MockWrite data_writes1[] = {
MockWrite(
"GET / HTTP/1.1\r\n"
"Host: www.example.org\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_P(HttpNetworkTransactionTest,
HttpsServerRequestsProxyAuthThroughProxy) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("https://www.example.org/");
session_deps_.proxy_service = ProxyService::CreateFixed("myproxy:70");
BoundTestNetLog log;
session_deps_.net_log = log.bound().net_log();
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
// Since we have proxy, should try to establish tunnel.
MockWrite data_writes1[] = {
MockWrite("CONNECT www.example.org:443 HTTP/1.1\r\n"
"Host: www.example.org:443\r\n"
"Proxy-Connection: keep-alive\r\n\r\n"),
MockWrite("GET / HTTP/1.1\r\n"
"Host: www.example.org\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(DEFAULT_PRIORITY, session.get()));
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);
TestNetLogEntry::List 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 proxy auth scheme that allows default credentials and a proxy server
// that uses non-persistent connections.
TEST_P(HttpNetworkTransactionTest,
AuthAllowsDefaultCredentialsTunnelConnectionClose) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("https://www.example.org/");
// Configure against proxy server "myproxy:70".
session_deps_.proxy_service =
ProxyService::CreateFixedFromPacResult("PROXY myproxy:70");
scoped_ptr<HttpAuthHandlerMock::Factory> auth_handler_factory(
new HttpAuthHandlerMock::Factory());
auth_handler_factory->set_do_init_from_challenge(true);
scoped_ptr<HttpAuthHandlerMock> mock_handler(new HttpAuthHandlerMock());
mock_handler->set_allows_default_credentials(true);
auth_handler_factory->AddMockHandler(mock_handler.release(),
HttpAuth::AUTH_PROXY);
session_deps_.http_auth_handler_factory = std::move(auth_handler_factory);
// Add NetLog just so can verify load timing information gets a NetLog ID.
NetLog net_log;
session_deps_.net_log = &net_log;
scoped_ptr<HttpNetworkSession> session = CreateSession(&session_deps_);
// Since we have proxy, should try to establish tunnel.
MockWrite data_writes1[] = {
MockWrite("CONNECT www.example.org:443 HTTP/1.1\r\n"
"Host: www.example.org:443\r\n"
"Proxy-Connection: keep-alive\r\n\r\n"),
};
// The proxy responds to the connect with a 407, using a non-persistent
// connection.
MockRead data_reads1[] = {
// No credentials.
MockRead("HTTP/1.1 407 Proxy Authentication Required\r\n"),
MockRead("Proxy-Authenticate: Mock\r\n"),
MockRead("Proxy-Connection: close\r\n\r\n"),
};
// Since the first connection couldn't be reused, need to establish another
// once given credentials.
MockWrite data_writes2[] = {
// After calling trans->RestartWithAuth(), this is the request we should
// be issuing -- the final header line contains the credentials.
MockWrite("CONNECT www.example.org:443 HTTP/1.1\r\n"
"Host: www.example.org:443\r\n"
"Proxy-Connection: keep-alive\r\n"
"Proxy-Authorization: auth_token\r\n\r\n"),
MockWrite("GET / HTTP/1.1\r\n"
"Host: www.example.org\r\n"
"Connection: keep-alive\r\n\r\n"),
};
MockRead data_reads2[] = {
MockRead("HTTP/1.0 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);
StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2),
data_writes2, arraysize(data_writes2));
session_deps_.socket_factory->AddSocketDataProvider(&data2);
SSLSocketDataProvider ssl(ASYNC, OK);
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
TestCompletionCallback callback;
int rv = trans->Start(&request, callback.callback(), BoundNetLog());
EXPECT_EQ(OK, callback.GetResult(rv));
const HttpResponseInfo* response = trans->GetResponseInfo();
ASSERT_TRUE(response);
ASSERT_TRUE(response->headers);
EXPECT_FALSE(response->headers->IsKeepAlive());
EXPECT_EQ(407, response->headers->response_code());
EXPECT_TRUE(HttpVersion(1, 1) == response->headers->GetHttpVersion());
EXPECT_TRUE(trans->IsReadyToRestartForAuth());
EXPECT_FALSE(response->auth_challenge.get());
LoadTimingInfo load_timing_info;
// CONNECT requests and responses are handled at the connect job level, so
// the transaction does not yet have a connection.
EXPECT_FALSE(trans->GetLoadTimingInfo(&load_timing_info));
rv = trans->RestartWithAuth(AuthCredentials(), callback.callback());
EXPECT_EQ(OK, callback.GetResult(rv));
response = trans->GetResponseInfo();
ASSERT_TRUE(response);
ASSERT_TRUE(response->headers);
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_FALSE(response->auth_challenge);
EXPECT_TRUE(trans->GetLoadTimingInfo(&load_timing_info));
TestLoadTimingNotReusedWithPac(load_timing_info,
CONNECT_TIMING_HAS_SSL_TIMES);
trans.reset();
session->CloseAllConnections();
}
// Test a proxy auth scheme that allows default credentials and a proxy server
// that hangs up when credentials are initially sent.
TEST_P(HttpNetworkTransactionTest,
AuthAllowsDefaultCredentialsTunnelServerClosesConnection) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("https://www.example.org/");
// Configure against proxy server "myproxy:70".
session_deps_.proxy_service =
ProxyService::CreateFixedFromPacResult("PROXY myproxy:70");
scoped_ptr<HttpAuthHandlerMock::Factory> auth_handler_factory(
new HttpAuthHandlerMock::Factory());
auth_handler_factory->set_do_init_from_challenge(true);
scoped_ptr<HttpAuthHandlerMock> mock_handler(new HttpAuthHandlerMock());
mock_handler->set_allows_default_credentials(true);
auth_handler_factory->AddMockHandler(mock_handler.release(),
HttpAuth::AUTH_PROXY);
session_deps_.http_auth_handler_factory = std::move(auth_handler_factory);
// Add NetLog just so can verify load timing information gets a NetLog ID.
NetLog net_log;
session_deps_.net_log = &net_log;
scoped_ptr<HttpNetworkSession> session = CreateSession(&session_deps_);
// Should try to establish tunnel.
MockWrite data_writes1[] = {
MockWrite("CONNECT www.example.org:443 HTTP/1.1\r\n"
"Host: www.example.org:443\r\n"
"Proxy-Connection: keep-alive\r\n\r\n"),
MockWrite("CONNECT www.example.org:443 HTTP/1.1\r\n"
"Host: www.example.org:443\r\n"
"Proxy-Connection: keep-alive\r\n"
"Proxy-Authorization: auth_token\r\n\r\n"),
};
// The proxy responds to the connect with a 407, using a non-persistent
// connection.
MockRead data_reads1[] = {
// No credentials.
MockRead("HTTP/1.1 407 Proxy Authentication Required\r\n"),
MockRead("Proxy-Authenticate: Mock\r\n\r\n"),
MockRead(SYNCHRONOUS, ERR_CONNECTION_CLOSED),
};
// Since the first connection was closed, need to establish another once given
// credentials.
MockWrite data_writes2[] = {
MockWrite("CONNECT www.example.org:443 HTTP/1.1\r\n"
"Host: www.example.org:443\r\n"
"Proxy-Connection: keep-alive\r\n"
"Proxy-Authorization: auth_token\r\n\r\n"),
MockWrite("GET / HTTP/1.1\r\n"
"Host: www.example.org\r\n"
"Connection: keep-alive\r\n\r\n"),
};
MockRead data_reads2[] = {
MockRead("HTTP/1.0 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);
StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2),
data_writes2, arraysize(data_writes2));
session_deps_.socket_factory->AddSocketDataProvider(&data2);
SSLSocketDataProvider ssl(ASYNC, OK);
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
TestCompletionCallback callback;
int rv = trans->Start(&request, callback.callback(), BoundNetLog());
EXPECT_EQ(OK, callback.GetResult(rv));
const HttpResponseInfo* response = trans->GetResponseInfo();
ASSERT_TRUE(response);
ASSERT_TRUE(response->headers);
EXPECT_TRUE(response->headers->IsKeepAlive());
EXPECT_EQ(407, response->headers->response_code());
EXPECT_TRUE(HttpVersion(1, 1) == response->headers->GetHttpVersion());
EXPECT_TRUE(trans->IsReadyToRestartForAuth());
EXPECT_FALSE(response->auth_challenge);
LoadTimingInfo load_timing_info;
// CONNECT requests and responses are handled at the connect job level, so
// the transaction does not yet have a connection.
EXPECT_FALSE(trans->GetLoadTimingInfo(&load_timing_info));
rv = trans->RestartWithAuth(AuthCredentials(), callback.callback());
EXPECT_EQ(OK, callback.GetResult(rv));
response = trans->GetResponseInfo();
ASSERT_TRUE(response);
ASSERT_TRUE(response->headers);
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);
EXPECT_TRUE(trans->GetLoadTimingInfo(&load_timing_info));
TestLoadTimingNotReusedWithPac(load_timing_info,
CONNECT_TIMING_HAS_SSL_TIMES);
trans.reset();
session->CloseAllConnections();
}
// Test a proxy auth scheme that allows default credentials and a proxy server
// that hangs up when credentials are initially sent, and hangs up again when
// they are retried.
TEST_P(HttpNetworkTransactionTest,
AuthAllowsDefaultCredentialsTunnelServerClosesConnectionTwice) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("https://www.example.org/");
// Configure against proxy server "myproxy:70".
session_deps_.proxy_service =
ProxyService::CreateFixedFromPacResult("PROXY myproxy:70");
scoped_ptr<HttpAuthHandlerMock::Factory> auth_handler_factory(
new HttpAuthHandlerMock::Factory());
auth_handler_factory->set_do_init_from_challenge(true);
scoped_ptr<HttpAuthHandlerMock> mock_handler(new HttpAuthHandlerMock());
mock_handler->set_allows_default_credentials(true);
auth_handler_factory->AddMockHandler(mock_handler.release(),
HttpAuth::AUTH_PROXY);
session_deps_.http_auth_handler_factory = std::move(auth_handler_factory);
// Add NetLog just so can verify load timing information gets a NetLog ID.
NetLog net_log;
session_deps_.net_log = &net_log;
scoped_ptr<HttpNetworkSession> session = CreateSession(&session_deps_);
// Should try to establish tunnel.
MockWrite data_writes1[] = {
MockWrite("CONNECT www.example.org:443 HTTP/1.1\r\n"
"Host: www.example.org:443\r\n"
"Proxy-Connection: keep-alive\r\n\r\n"),
MockWrite("CONNECT www.example.org:443 HTTP/1.1\r\n"
"Host: www.example.org:443\r\n"
"Proxy-Connection: keep-alive\r\n"
"Proxy-Authorization: auth_token\r\n\r\n"),
};
// The proxy responds to the connect with a 407, and then hangs up after the
// second request is sent.
MockRead data_reads1[] = {
// No credentials.
MockRead("HTTP/1.1 407 Proxy Authentication Required\r\n"),
MockRead("Content-Length: 0\r\n"),
MockRead("Proxy-Connection: keep-alive\r\n"),
MockRead("Proxy-Authenticate: Mock\r\n\r\n"),
MockRead(SYNCHRONOUS, ERR_CONNECTION_CLOSED),
};
// HttpNetworkTransaction sees a reused connection that was closed with
// ERR_CONNECTION_CLOSED, realized it might have been a race, so retries the
// request.
MockWrite data_writes2[] = {
MockWrite("CONNECT www.example.org:443 HTTP/1.1\r\n"
"Host: www.example.org:443\r\n"
"Proxy-Connection: keep-alive\r\n\r\n"),
};
// The proxy, having had more than enough of us, just hangs up.
MockRead data_reads2[] = {
// No credentials.
MockRead(SYNCHRONOUS, ERR_CONNECTION_CLOSED),
};
StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
data_writes1, arraysize(data_writes1));
session_deps_.socket_factory->AddSocketDataProvider(&data1);
StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2),
data_writes2, arraysize(data_writes2));
session_deps_.socket_factory->AddSocketDataProvider(&data2);
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
TestCompletionCallback callback;
int rv = trans->Start(&request, callback.callback(), BoundNetLog());
EXPECT_EQ(OK, callback.GetResult(rv));
const HttpResponseInfo* response = trans->GetResponseInfo();
ASSERT_TRUE(response);
ASSERT_TRUE(response->headers);
EXPECT_TRUE(response->headers->IsKeepAlive());
EXPECT_EQ(407, response->headers->response_code());
EXPECT_TRUE(HttpVersion(1, 1) == response->headers->GetHttpVersion());
EXPECT_TRUE(trans->IsReadyToRestartForAuth());
EXPECT_FALSE(response->auth_challenge);
LoadTimingInfo load_timing_info;
EXPECT_FALSE(trans->GetLoadTimingInfo(&load_timing_info));
rv = trans->RestartWithAuth(AuthCredentials(), callback.callback());
EXPECT_EQ(ERR_EMPTY_RESPONSE, callback.GetResult(rv));
trans.reset();
session->CloseAllConnections();
}
// Test a proxy auth scheme that allows default credentials and a proxy server
// that hangs up when credentials are initially sent, and sends a challenge
// again they are retried.
TEST_P(HttpNetworkTransactionTest,
AuthAllowsDefaultCredentialsTunnelServerChallengesTwice) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("https://www.example.org/");
// Configure against proxy server "myproxy:70".
session_deps_.proxy_service =
ProxyService::CreateFixedFromPacResult("PROXY myproxy:70");
scoped_ptr<HttpAuthHandlerMock::Factory> auth_handler_factory(
new HttpAuthHandlerMock::Factory());
auth_handler_factory->set_do_init_from_challenge(true);
scoped_ptr<HttpAuthHandlerMock> mock_handler(new HttpAuthHandlerMock());
mock_handler->set_allows_default_credentials(true);
auth_handler_factory->AddMockHandler(mock_handler.release(),
HttpAuth::AUTH_PROXY);
// Add another handler for the second challenge. It supports default
// credentials, but they shouldn't be used, since they were already tried.
mock_handler.reset(new HttpAuthHandlerMock());
mock_handler->set_allows_default_credentials(true);
auth_handler_factory->AddMockHandler(mock_handler.release(),
HttpAuth::AUTH_PROXY);
session_deps_.http_auth_handler_factory = std::move(auth_handler_factory);
// Add NetLog just so can verify load timing information gets a NetLog ID.
NetLog net_log;
session_deps_.net_log = &net_log;
scoped_ptr<HttpNetworkSession> session = CreateSession(&session_deps_);
// Should try to establish tunnel.
MockWrite data_writes1[] = {
MockWrite("CONNECT www.example.org:443 HTTP/1.1\r\n"
"Host: www.example.org:443\r\n"
"Proxy-Connection: keep-alive\r\n\r\n"),
};
// The proxy responds to the connect with a 407, using a non-persistent
// connection.
MockRead data_reads1[] = {
// No credentials.
MockRead("HTTP/1.1 407 Proxy Authentication Required\r\n"),
MockRead("Proxy-Authenticate: Mock\r\n"),
MockRead("Proxy-Connection: close\r\n\r\n"),
};
// Since the first connection was closed, need to establish another once given
// credentials.
MockWrite data_writes2[] = {
MockWrite("CONNECT www.example.org:443 HTTP/1.1\r\n"
"Host: www.example.org:443\r\n"
"Proxy-Connection: keep-alive\r\n"
"Proxy-Authorization: auth_token\r\n\r\n"),
};
MockRead data_reads2[] = {
MockRead("HTTP/1.1 407 Proxy Authentication Required\r\n"),
MockRead("Proxy-Authenticate: Mock\r\n"),
MockRead("Proxy-Connection: close\r\n\r\n"),
};
StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
data_writes1, arraysize(data_writes1));
session_deps_.socket_factory->AddSocketDataProvider(&data1);
StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2),
data_writes2, arraysize(data_writes2));
session_deps_.socket_factory->AddSocketDataProvider(&data2);
SSLSocketDataProvider ssl(ASYNC, OK);
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
TestCompletionCallback callback;
int rv = trans->Start(&request, callback.callback(), BoundNetLog());
EXPECT_EQ(OK, callback.GetResult(rv));
const HttpResponseInfo* response = trans->GetResponseInfo();
ASSERT_TRUE(response);
ASSERT_TRUE(response->headers);
EXPECT_EQ(407, response->headers->response_code());
EXPECT_TRUE(HttpVersion(1, 1) == response->headers->GetHttpVersion());
EXPECT_TRUE(trans->IsReadyToRestartForAuth());
EXPECT_FALSE(response->auth_challenge);
LoadTimingInfo load_timing_info;
EXPECT_FALSE(trans->GetLoadTimingInfo(&load_timing_info));
rv = trans->RestartWithAuth(AuthCredentials(), callback.callback());
EXPECT_EQ(OK, callback.GetResult(rv));
response = trans->GetResponseInfo();
ASSERT_TRUE(response);
ASSERT_TRUE(response->headers);
EXPECT_EQ(407, response->headers->response_code());
EXPECT_FALSE(trans->IsReadyToRestartForAuth());
EXPECT_TRUE(response->auth_challenge);
trans.reset();
session->CloseAllConnections();
}
// Test the load timing for HTTPS requests with an HTTP proxy.
TEST_P(HttpNetworkTransactionTest, HttpProxyLoadTimingNoPacTwoRequests) {
HttpRequestInfo request1;
request1.method = "GET";
request1.url = GURL("https://www.example.org/1");
HttpRequestInfo request2;
request2.method = "GET";
request2.url = GURL("https://www.example.org/2");
// Configure against proxy server "myproxy:70".
session_deps_.proxy_service = ProxyService::CreateFixed("PROXY myproxy:70");
BoundTestNetLog log;
session_deps_.net_log = log.bound().net_log();
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
// Since we have proxy, should try to establish tunnel.
MockWrite data_writes1[] = {
MockWrite("CONNECT www.example.org:443 HTTP/1.1\r\n"
"Host: www.example.org:443\r\n"
"Proxy-Connection: keep-alive\r\n\r\n"),
MockWrite("GET /1 HTTP/1.1\r\n"
"Host: www.example.org\r\n"
"Connection: keep-alive\r\n\r\n"),
MockWrite("GET /2 HTTP/1.1\r\n"
"Host: www.example.org\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[] = {
MockRead("HTTP/1.1 200 Connection Established\r\n\r\n"),
MockRead("HTTP/1.1 200 OK\r\n"),
MockRead("Content-Length: 1\r\n\r\n"),
MockRead(SYNCHRONOUS, "1"),
MockRead("HTTP/1.1 200 OK\r\n"),
MockRead("Content-Length: 2\r\n\r\n"),
MockRead(SYNCHRONOUS, "22"),
};
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> trans1(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
int rv = trans1->Start(&request1, callback1.callback(), log.bound());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback1.WaitForResult();
EXPECT_EQ(OK, rv);
const HttpResponseInfo* response1 = trans1->GetResponseInfo();
ASSERT_TRUE(response1 != NULL);
ASSERT_TRUE(response1->headers.get() != NULL);
EXPECT_EQ(1, response1->headers->GetContentLength());
LoadTimingInfo load_timing_info1;
EXPECT_TRUE(trans1->GetLoadTimingInfo(&load_timing_info1));
TestLoadTimingNotReused(load_timing_info1, CONNECT_TIMING_HAS_SSL_TIMES);
trans1.reset();
TestCompletionCallback callback2;
scoped_ptr<HttpTransaction> trans2(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
rv = trans2->Start(&request2, callback2.callback(), log.bound());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback2.WaitForResult();
EXPECT_EQ(OK, rv);
const HttpResponseInfo* response2 = trans2->GetResponseInfo();
ASSERT_TRUE(response2 != NULL);
ASSERT_TRUE(response2->headers.get() != NULL);
EXPECT_EQ(2, response2->headers->GetContentLength());
LoadTimingInfo load_timing_info2;
EXPECT_TRUE(trans2->GetLoadTimingInfo(&load_timing_info2));
TestLoadTimingReused(load_timing_info2);
EXPECT_EQ(load_timing_info1.socket_log_id, load_timing_info2.socket_log_id);
trans2.reset();
session->CloseAllConnections();
}
// Test the load timing for HTTPS requests with an HTTP proxy and a PAC script.
TEST_P(HttpNetworkTransactionTest, HttpProxyLoadTimingWithPacTwoRequests) {
HttpRequestInfo request1;
request1.method = "GET";
request1.url = GURL("https://www.example.org/1");
HttpRequestInfo request2;
request2.method = "GET";
request2.url = GURL("https://www.example.org/2");
// Configure against proxy server "myproxy:70".
session_deps_.proxy_service =
ProxyService::CreateFixedFromPacResult("PROXY myproxy:70");
BoundTestNetLog log;
session_deps_.net_log = log.bound().net_log();
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
// Since we have proxy, should try to establish tunnel.
MockWrite data_writes1[] = {
MockWrite("CONNECT www.example.org:443 HTTP/1.1\r\n"
"Host: www.example.org:443\r\n"
"Proxy-Connection: keep-alive\r\n\r\n"),
MockWrite("GET /1 HTTP/1.1\r\n"
"Host: www.example.org\r\n"
"Connection: keep-alive\r\n\r\n"),
MockWrite("GET /2 HTTP/1.1\r\n"
"Host: www.example.org\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[] = {
MockRead("HTTP/1.1 200 Connection Established\r\n\r\n"),
MockRead("HTTP/1.1 200 OK\r\n"),
MockRead("Content-Length: 1\r\n\r\n"),
MockRead(SYNCHRONOUS, "1"),
MockRead("HTTP/1.1 200 OK\r\n"),
MockRead("Content-Length: 2\r\n\r\n"),
MockRead(SYNCHRONOUS, "22"),
};
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> trans1(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
int rv = trans1->Start(&request1, callback1.callback(), log.bound());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback1.WaitForResult();
EXPECT_EQ(OK, rv);
const HttpResponseInfo* response1 = trans1->GetResponseInfo();
ASSERT_TRUE(response1 != NULL);
ASSERT_TRUE(response1->headers.get() != NULL);
EXPECT_EQ(1, response1->headers->GetContentLength());
LoadTimingInfo load_timing_info1;
EXPECT_TRUE(trans1->GetLoadTimingInfo(&load_timing_info1));
TestLoadTimingNotReusedWithPac(load_timing_info1,
CONNECT_TIMING_HAS_SSL_TIMES);
trans1.reset();
TestCompletionCallback callback2;
scoped_ptr<HttpTransaction> trans2(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
rv = trans2->Start(&request2, callback2.callback(), log.bound());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback2.WaitForResult();
EXPECT_EQ(OK, rv);
const HttpResponseInfo* response2 = trans2->GetResponseInfo();
ASSERT_TRUE(response2 != NULL);
ASSERT_TRUE(response2->headers.get() != NULL);
EXPECT_EQ(2, response2->headers->GetContentLength());
LoadTimingInfo load_timing_info2;
EXPECT_TRUE(trans2->GetLoadTimingInfo(&load_timing_info2));
TestLoadTimingReusedWithPac(load_timing_info2);
EXPECT_EQ(load_timing_info1.socket_log_id, load_timing_info2.socket_log_id);
trans2.reset();
session->CloseAllConnections();
}
// Test a simple get through an HTTPS Proxy.
TEST_P(HttpNetworkTransactionTest, HttpsProxyGet) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.example.org/");
// Configure against https proxy server "proxy:70".
session_deps_.proxy_service = ProxyService::CreateFixed("https://proxy:70");
BoundTestNetLog log;
session_deps_.net_log = log.bound().net_log();
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
// Since we have proxy, should use full url
MockWrite data_writes1[] = {
MockWrite(
"GET http://www.example.org/ HTTP/1.1\r\n"
"Host: www.example.org\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(DEFAULT_PRIORITY, session.get()));
int rv = trans->Start(&request, callback1.callback(), log.bound());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback1.WaitForResult();
EXPECT_EQ(OK, rv);
LoadTimingInfo load_timing_info;
EXPECT_TRUE(trans->GetLoadTimingInfo(&load_timing_info));
TestLoadTimingNotReused(load_timing_info,
CONNECT_TIMING_HAS_CONNECT_TIMES_ONLY);
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_P(HttpNetworkTransactionTest, HttpsProxySpdyGet) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.example.org/");
request.load_flags = 0;
// Configure against https proxy server "proxy:70".
session_deps_.proxy_service = ProxyService::CreateFixed("https://proxy:70");
BoundTestNetLog log;
session_deps_.net_log = log.bound().net_log();
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
// fetch http://www.example.org/ via SPDY
scoped_ptr<SpdyFrame> req(
spdy_util_.ConstructSpdyGet(nullptr, 0, 1, LOWEST, false));
MockWrite spdy_writes[] = {CreateMockWrite(*req, 0)};
scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
scoped_ptr<SpdyFrame> data(spdy_util_.ConstructSpdyBodyFrame(1, true));
MockRead spdy_reads[] = {
CreateMockRead(*resp, 1), CreateMockRead(*data, 2), MockRead(ASYNC, 0, 3),
};
SequencedSocketData spdy_data(spdy_reads, arraysize(spdy_reads), spdy_writes,
arraysize(spdy_writes));
session_deps_.socket_factory->AddSocketDataProvider(&spdy_data);
SSLSocketDataProvider ssl(ASYNC, OK);
ssl.SetNextProto(GetProtocol());
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
TestCompletionCallback callback1;
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
int rv = trans->Start(&request, callback1.callback(), log.bound());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback1.WaitForResult();
EXPECT_EQ(OK, rv);
LoadTimingInfo load_timing_info;
EXPECT_TRUE(trans->GetLoadTimingInfo(&load_timing_info));
TestLoadTimingNotReused(load_timing_info,
CONNECT_TIMING_HAS_CONNECT_TIMES_ONLY);
const HttpResponseInfo* response = trans->GetResponseInfo();
ASSERT_TRUE(response != NULL);
ASSERT_TRUE(response->headers.get() != NULL);
EXPECT_EQ("HTTP/1.1 200", response->headers->GetStatusLine());
std::string response_data;
ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data));
EXPECT_EQ(kUploadData, response_data);
}
// Verifies that a session which races and wins against the owning transaction
// (completing prior to host resolution), doesn't fail the transaction.
// Regression test for crbug.com/334413.
TEST_P(HttpNetworkTransactionTest, HttpsProxySpdyGetWithSessionRace) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.example.org/");
request.load_flags = 0;
// Configure SPDY proxy server "proxy:70".
session_deps_.proxy_service = ProxyService::CreateFixed("https://proxy:70");
BoundTestNetLog log;
session_deps_.net_log = log.bound().net_log();
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
// Fetch http://www.example.org/ through the SPDY proxy.
scoped_ptr<SpdyFrame> req(
spdy_util_.ConstructSpdyGet(nullptr, 0, 1, LOWEST, false));
MockWrite spdy_writes[] = {CreateMockWrite(*req, 0)};
scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
scoped_ptr<SpdyFrame> data(spdy_util_.ConstructSpdyBodyFrame(1, true));
MockRead spdy_reads[] = {
CreateMockRead(*resp, 1), CreateMockRead(*data, 2), MockRead(ASYNC, 0, 3),
};
SequencedSocketData spdy_data(spdy_reads, arraysize(spdy_reads), spdy_writes,
arraysize(spdy_writes));
session_deps_.socket_factory->AddSocketDataProvider(&spdy_data);
SSLSocketDataProvider ssl(ASYNC, OK);
ssl.SetNextProto(GetProtocol());
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
TestCompletionCallback callback1;
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
// Stall the hostname resolution begun by the transaction.
session_deps_.host_resolver->set_synchronous_mode(false);
session_deps_.host_resolver->set_ondemand_mode(true);
int rv = trans->Start(&request, callback1.callback(), log.bound());
EXPECT_EQ(ERR_IO_PENDING, rv);
// Race a session to the proxy, which completes first.
session_deps_.host_resolver->set_ondemand_mode(false);
SpdySessionKey key(
HostPortPair("proxy", 70), ProxyServer::Direct(), PRIVACY_MODE_DISABLED);
base::WeakPtr<SpdySession> spdy_session =
CreateSecureSpdySession(session.get(), key, log.bound());
// Unstall the resolution begun by the transaction.
session_deps_.host_resolver->set_ondemand_mode(true);
session_deps_.host_resolver->ResolveAllPending();
EXPECT_FALSE(callback1.have_result());
rv = callback1.WaitForResult();
EXPECT_EQ(OK, rv);
const HttpResponseInfo* response = trans->GetResponseInfo();
ASSERT_TRUE(response != NULL);
ASSERT_TRUE(response->headers.get() != NULL);
EXPECT_EQ("HTTP/1.1 200", 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_P(HttpNetworkTransactionTest, HttpsProxySpdyGetWithProxyAuth) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.example.org/");
request.load_flags = 0;
// Configure against https proxy server "myproxy:70".
session_deps_.proxy_service = ProxyService::CreateFixed("https://myproxy:70");
BoundTestNetLog log;
session_deps_.net_log = log.bound().net_log();
scoped_ptr<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<SpdyFrame> req_get(
spdy_util_.ConstructSpdyGet(nullptr, 0, 1, LOWEST, false));
spdy_util_.UpdateWithStreamDestruction(1);
const char* const kExtraAuthorizationHeaders[] = {
"proxy-authorization", "Basic Zm9vOmJhcg=="
};
scoped_ptr<SpdyFrame> req_get_authorization(
spdy_util_.ConstructSpdyGet(kExtraAuthorizationHeaders,
arraysize(kExtraAuthorizationHeaders) / 2,
3,
LOWEST,
false));
MockWrite spdy_writes[] = {
CreateMockWrite(*req_get, 0), CreateMockWrite(*req_get_authorization, 3),
};
// 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<SpdyFrame> resp_authentication(
spdy_util_.ConstructSpdySynReplyError(
"407 Proxy Authentication Required",
kExtraAuthenticationHeaders, arraysize(kExtraAuthenticationHeaders)/2,
1));
scoped_ptr<SpdyFrame> body_authentication(
spdy_util_.ConstructSpdyBodyFrame(1, true));
scoped_ptr<SpdyFrame> resp_data(
spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 3));
scoped_ptr<SpdyFrame> body_data(spdy_util_.ConstructSpdyBodyFrame(3, true));
MockRead spdy_reads[] = {
CreateMockRead(*resp_authentication, 1),
CreateMockRead(*body_authentication, 2),
CreateMockRead(*resp_data, 4),
CreateMockRead(*body_data, 5),
MockRead(ASYNC, 0, 6),
};
SequencedSocketData data(spdy_reads, arraysize(spdy_reads), spdy_writes,
arraysize(spdy_writes));
session_deps_.socket_factory->AddSocketDataProvider(&data);
SSLSocketDataProvider ssl(ASYNC, OK);
ssl.SetNextProto(GetProtocol());
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
TestCompletionCallback callback1;
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
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.get() != 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.get() != 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_P(HttpNetworkTransactionTest, HttpsProxySpdyConnectHttps) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("https://www.example.org/");
request.load_flags = 0;
// Configure against https proxy server "proxy:70".
session_deps_.proxy_service = ProxyService::CreateFixed("https://proxy:70");
BoundTestNetLog log;
session_deps_.net_log = log.bound().net_log();
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
// CONNECT to www.example.org:443 via SPDY
scoped_ptr<SpdyFrame> connect(spdy_util_.ConstructSpdyConnect(
NULL, 0, 1, LOWEST, HostPortPair("www.example.org", 443)));
// fetch https://www.example.org/ via HTTP
const char get[] =
"GET / HTTP/1.1\r\n"
"Host: www.example.org\r\n"
"Connection: keep-alive\r\n\r\n";
scoped_ptr<SpdyFrame> wrapped_get(
spdy_util_.ConstructSpdyBodyFrame(1, get, strlen(get), false));
scoped_ptr<SpdyFrame> conn_resp(
spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
const char resp[] = "HTTP/1.1 200 OK\r\n"
"Content-Length: 10\r\n\r\n";
scoped_ptr<SpdyFrame> wrapped_get_resp(
spdy_util_.ConstructSpdyBodyFrame(1, resp, strlen(resp), false));
scoped_ptr<SpdyFrame> wrapped_body(
spdy_util_.ConstructSpdyBodyFrame(1, "1234567890", 10, false));
scoped_ptr<SpdyFrame> window_update(
spdy_util_.ConstructSpdyWindowUpdate(1, wrapped_get_resp->size()));
MockWrite spdy_writes[] = {
CreateMockWrite(*connect, 0),
CreateMockWrite(*wrapped_get, 2),
CreateMockWrite(*window_update, 6),
};
MockRead spdy_reads[] = {
CreateMockRead(*conn_resp, 1, ASYNC),
CreateMockRead(*wrapped_get_resp, 3, ASYNC),
CreateMockRead(*wrapped_body, 4, ASYNC),
CreateMockRead(*wrapped_body, 5, ASYNC),
MockRead(ASYNC, 0, 7),
};
SequencedSocketData spdy_data(spdy_reads, arraysize(spdy_reads), spdy_writes,
arraysize(spdy_writes));
session_deps_.socket_factory->AddSocketDataProvider(&spdy_data);
SSLSocketDataProvider ssl(ASYNC, OK);
ssl.SetNextProto(GetProtocol());
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
SSLSocketDataProvider ssl2(ASYNC, OK);
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();
ASSERT_EQ(OK, rv);
LoadTimingInfo load_timing_info;
EXPECT_TRUE(trans->GetLoadTimingInfo(&load_timing_info));
TestLoadTimingNotReused(load_timing_info, CONNECT_TIMING_HAS_SSL_TIMES);
const HttpResponseInfo* response = trans->GetResponseInfo();
ASSERT_TRUE(response != NULL);
ASSERT_TRUE(response->headers.get() != 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_P(HttpNetworkTransactionTest, HttpsProxySpdyConnectSpdy) {
SpdyTestUtil spdy_util_wrapped(GetProtocol(), GetDependenciesFromPriority());
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("https://www.example.org/");
request.load_flags = 0;
// Configure against https proxy server "proxy:70".
session_deps_.proxy_service = ProxyService::CreateFixed("https://proxy:70");
BoundTestNetLog log;
session_deps_.net_log = log.bound().net_log();
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
// CONNECT to www.example.org:443 via SPDY
scoped_ptr<SpdyFrame> connect(spdy_util_.ConstructSpdyConnect(
NULL, 0, 1, LOWEST, HostPortPair("www.example.org", 443)));
// fetch https://www.example.org/ via SPDY
const char kMyUrl[] = "https://www.example.org/";
scoped_ptr<SpdyFrame> get(
spdy_util_wrapped.ConstructSpdyGet(kMyUrl, 1, LOWEST));
scoped_ptr<SpdyFrame> wrapped_get(
spdy_util_.ConstructWrappedSpdyFrame(get, 1));
scoped_ptr<SpdyFrame> conn_resp(
spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
scoped_ptr<SpdyFrame> get_resp(
spdy_util_wrapped.ConstructSpdyGetSynReply(NULL, 0, 1));
scoped_ptr<SpdyFrame> wrapped_get_resp(
spdy_util_.ConstructWrappedSpdyFrame(get_resp, 1));
scoped_ptr<SpdyFrame> body(spdy_util_wrapped.ConstructSpdyBodyFrame(1, true));
scoped_ptr<SpdyFrame> wrapped_body(
spdy_util_.ConstructWrappedSpdyFrame(body, 1));
scoped_ptr<SpdyFrame> window_update_get_resp(
spdy_util_.ConstructSpdyWindowUpdate(1, wrapped_get_resp->size()));
scoped_ptr<SpdyFrame> window_update_body(
spdy_util_.ConstructSpdyWindowUpdate(1, wrapped_body->size()));
MockWrite spdy_writes[] = {
CreateMockWrite(*connect, 0),
CreateMockWrite(*wrapped_get, 2),
CreateMockWrite(*window_update_get_resp, 6),
CreateMockWrite(*window_update_body, 7),
};
MockRead spdy_reads[] = {
CreateMockRead(*conn_resp, 1, ASYNC),
MockRead(ASYNC, ERR_IO_PENDING, 3),
CreateMockRead(*wrapped_get_resp, 4, ASYNC),
CreateMockRead(*wrapped_body, 5, ASYNC),
MockRead(ASYNC, 0, 8),
};
SequencedSocketData spdy_data(spdy_reads, arraysize(spdy_reads), spdy_writes,
arraysize(spdy_writes));
session_deps_.socket_factory->AddSocketDataProvider(&spdy_data);
SSLSocketDataProvider ssl(ASYNC, OK);
ssl.SetNextProto(GetProtocol());
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
SSLSocketDataProvider ssl2(ASYNC, OK);
ssl2.SetNextProto(GetProtocol());
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl2);
TestCompletionCallback callback1;
int rv = trans->Start(&request, callback1.callback(), log.bound());
EXPECT_EQ(ERR_IO_PENDING, rv);
// Allow the SpdyProxyClientSocket's write callback to complete.
base::MessageLoop::current()->RunUntilIdle();
// Now allow the read of the response to complete.
spdy_data.Resume();
rv = callback1.WaitForResult();
EXPECT_EQ(OK, rv);
LoadTimingInfo load_timing_info;
EXPECT_TRUE(trans->GetLoadTimingInfo(&load_timing_info));
TestLoadTimingNotReused(load_timing_info, CONNECT_TIMING_HAS_SSL_TIMES);
const HttpResponseInfo* response = trans->GetResponseInfo();
ASSERT_TRUE(response != NULL);
ASSERT_TRUE(response->headers.get() != NULL);
EXPECT_EQ("HTTP/1.1 200", 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_P(HttpNetworkTransactionTest, HttpsProxySpdyConnectFailure) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("https://www.example.org/");
request.load_flags = 0;
// Configure against https proxy server "proxy:70".
session_deps_.proxy_service = ProxyService::CreateFixed("https://proxy:70");
BoundTestNetLog log;
session_deps_.net_log = log.bound().net_log();
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
// CONNECT to www.example.org:443 via SPDY
scoped_ptr<SpdyFrame> connect(spdy_util_.ConstructSpdyConnect(
NULL, 0, 1, LOWEST, HostPortPair("www.example.org", 443)));
scoped_ptr<SpdyFrame> get(
spdy_util_.ConstructSpdyRstStream(1, RST_STREAM_CANCEL));
MockWrite spdy_writes[] = {
CreateMockWrite(*connect, 0), CreateMockWrite(*get, 2),
};
scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdySynReplyError(1));
scoped_ptr<SpdyFrame> data(spdy_util_.ConstructSpdyBodyFrame(1, true));
MockRead spdy_reads[] = {
CreateMockRead(*resp, 1, ASYNC), MockRead(ASYNC, 0, 3),
};
SequencedSocketData spdy_data(spdy_reads, arraysize(spdy_reads), spdy_writes,
arraysize(spdy_writes));
session_deps_.socket_factory->AddSocketDataProvider(&spdy_data);
SSLSocketDataProvider ssl(ASYNC, OK);
ssl.SetNextProto(GetProtocol());
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
SSLSocketDataProvider ssl2(ASYNC, OK);
ssl2.SetNextProto(GetProtocol());
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(ERR_TUNNEL_CONNECTION_FAILED, rv);
// TODO(ttuttle): Anything else to check here?
}
// Test load timing in the case of two HTTPS (non-SPDY) requests through a SPDY
// HTTPS Proxy to different servers.
TEST_P(HttpNetworkTransactionTest,
HttpsProxySpdyConnectHttpsLoadTimingTwoRequestsTwoServers) {
// Configure against https proxy server "proxy:70".
session_deps_.proxy_service = ProxyService::CreateFixed("https://proxy:70");
BoundTestNetLog log;
session_deps_.net_log = log.bound().net_log();
scoped_ptr<HttpNetworkSession> session(
SpdySessionDependencies::SpdyCreateSession(&session_deps_));
HttpRequestInfo request1;
request1.method = "GET";
request1.url = GURL("https://www.example.org/");
request1.load_flags = 0;
HttpRequestInfo request2;
request2.method = "GET";
request2.url = GURL("https://mail.example.org/");
request2.load_flags = 0;
// CONNECT to www.example.org:443 via SPDY.
scoped_ptr<SpdyFrame> connect1(spdy_util_.ConstructSpdyConnect(
NULL, 0, 1, LOWEST, HostPortPair("www.example.org", 443)));
scoped_ptr<SpdyFrame> conn_resp1(
spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
// Fetch https://www.example.org/ via HTTP.
const char get1[] =
"GET / HTTP/1.1\r\n"
"Host: www.example.org\r\n"
"Connection: keep-alive\r\n\r\n";
scoped_ptr<SpdyFrame> wrapped_get1(
spdy_util_.ConstructSpdyBodyFrame(1, get1, strlen(get1), false));
const char resp1[] = "HTTP/1.1 200 OK\r\n"
"Content-Length: 1\r\n\r\n";
scoped_ptr<SpdyFrame> wrapped_get_resp1(
spdy_util_.ConstructSpdyBodyFrame(1, resp1, strlen(resp1), false));
scoped_ptr<SpdyFrame> wrapped_body1(
spdy_util_.ConstructSpdyBodyFrame(1, "1", 1, false));
scoped_ptr<SpdyFrame> window_update(
spdy_util_.ConstructSpdyWindowUpdate(1, wrapped_get_resp1->size()));
// CONNECT to mail.example.org:443 via SPDY.
SpdyHeaderBlock connect2_block;
spdy_util_.MaybeAddVersionHeader(&connect2_block);
connect2_block[spdy_util_.GetMethodKey()] = "CONNECT";
if (GetProtocol() == kProtoHTTP2) {
connect2_block[spdy_util_.GetHostKey()] = "mail.example.org:443";
} else {
connect2_block[spdy_util_.GetHostKey()] = "mail.example.org";
connect2_block[spdy_util_.GetPathKey()] = "mail.example.org:443";
}
scoped_ptr<SpdyFrame> connect2(
spdy_util_.ConstructSpdySyn(3, connect2_block, LOWEST, false));
scoped_ptr<SpdyFrame> conn_resp2(
spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 3));
// Fetch https://mail.example.org/ via HTTP.
const char get2[] =
"GET / HTTP/1.1\r\n"
"Host: mail.example.org\r\n"
"Connection: keep-alive\r\n\r\n";
scoped_ptr<SpdyFrame> wrapped_get2(
spdy_util_.ConstructSpdyBodyFrame(3, get2, strlen(get2), false));
const char resp2[] = "HTTP/1.1 200 OK\r\n"
"Content-Length: 2\r\n\r\n";
scoped_ptr<SpdyFrame> wrapped_get_resp2(
spdy_util_.ConstructSpdyBodyFrame(3, resp2, strlen(resp2), false));
scoped_ptr<SpdyFrame> wrapped_body2(
spdy_util_.ConstructSpdyBodyFrame(3, "22", 2, false));
MockWrite spdy_writes[] = {
CreateMockWrite(*connect1, 0),
CreateMockWrite(*wrapped_get1, 2),
CreateMockWrite(*connect2, 5),
CreateMockWrite(*wrapped_get2, 7),
};
MockRead spdy_reads[] = {
CreateMockRead(*conn_resp1, 1, ASYNC),
CreateMockRead(*wrapped_get_resp1, 3, ASYNC),
CreateMockRead(*wrapped_body1, 4, ASYNC),
CreateMockRead(*conn_resp2, 6, ASYNC),
CreateMockRead(*wrapped_get_resp2, 8, ASYNC),
CreateMockRead(*wrapped_body2, 9, ASYNC),
MockRead(ASYNC, 0, 10),
};
SequencedSocketData spdy_data(spdy_reads, arraysize(spdy_reads), spdy_writes,
arraysize(spdy_writes));
session_deps_.socket_factory->AddSocketDataProvider(&spdy_data);
SSLSocketDataProvider ssl(ASYNC, OK);
ssl.SetNextProto(GetProtocol());
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
SSLSocketDataProvider ssl2(ASYNC, OK);
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl2);
SSLSocketDataProvider ssl3(ASYNC, OK);
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl3);
TestCompletionCallback callback;
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
int rv = trans->Start(&request1, callback.callback(), BoundNetLog());
EXPECT_EQ(OK, callback.GetResult(rv));
LoadTimingInfo load_timing_info;
EXPECT_TRUE(trans->GetLoadTimingInfo(&load_timing_info));
TestLoadTimingNotReused(load_timing_info, CONNECT_TIMING_HAS_SSL_TIMES);
const HttpResponseInfo* response = trans->GetResponseInfo();
ASSERT_TRUE(response != NULL);
ASSERT_TRUE(response->headers.get() != NULL);
EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
std::string response_data;
scoped_refptr<IOBuffer> buf(new IOBuffer(256));
rv = trans->Read(buf.get(), 256, callback.callback());
EXPECT_EQ(1, callback.GetResult(rv));
scoped_ptr<HttpTransaction> trans2(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
rv = trans2->Start(&request2, callback.callback(), BoundNetLog());
EXPECT_EQ(OK, callback.GetResult(rv));
LoadTimingInfo load_timing_info2;
EXPECT_TRUE(trans2->GetLoadTimingInfo(&load_timing_info2));
// Even though the SPDY connection is reused, a new tunnelled connection has
// to be created, so the socket's load timing looks like a fresh connection.
TestLoadTimingNotReused(load_timing_info2, CONNECT_TIMING_HAS_SSL_TIMES);
// The requests should have different IDs, since they each are using their own
// separate stream.
EXPECT_NE(load_timing_info.socket_log_id, load_timing_info2.socket_log_id);
rv = trans2->Read(buf.get(), 256, callback.callback());
EXPECT_EQ(2, callback.GetResult(rv));
}
// Test load timing in the case of two HTTPS (non-SPDY) requests through a SPDY
// HTTPS Proxy to the same server.
TEST_P(HttpNetworkTransactionTest,
HttpsProxySpdyConnectHttpsLoadTimingTwoRequestsSameServer) {
// Configure against https proxy server "proxy:70".
session_deps_.proxy_service = ProxyService::CreateFixed("https://proxy:70");
BoundTestNetLog log;
session_deps_.net_log = log.bound().net_log();
scoped_ptr<HttpNetworkSession> session(
SpdySessionDependencies::SpdyCreateSession(&session_deps_));
HttpRequestInfo request1;
request1.method = "GET";
request1.url = GURL("https://www.example.org/");
request1.load_flags = 0;
HttpRequestInfo request2;
request2.method = "GET";
request2.url = GURL("https://www.example.org/2");
request2.load_flags = 0;
// CONNECT to www.example.org:443 via SPDY.
scoped_ptr<SpdyFrame> connect1(spdy_util_.ConstructSpdyConnect(
NULL, 0, 1, LOWEST, HostPortPair("www.example.org", 443)));
scoped_ptr<SpdyFrame> conn_resp1(
spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
// Fetch https://www.example.org/ via HTTP.
const char get1[] =
"GET / HTTP/1.1\r\n"
"Host: www.example.org\r\n"
"Connection: keep-alive\r\n\r\n";
scoped_ptr<SpdyFrame> wrapped_get1(
spdy_util_.ConstructSpdyBodyFrame(1, get1, strlen(get1), false));
const char resp1[] = "HTTP/1.1 200 OK\r\n"
"Content-Length: 1\r\n\r\n";
scoped_ptr<SpdyFrame> wrapped_get_resp1(
spdy_util_.ConstructSpdyBodyFrame(1, resp1, strlen(resp1), false));
scoped_ptr<SpdyFrame> wrapped_body1(
spdy_util_.ConstructSpdyBodyFrame(1, "1", 1, false));
scoped_ptr<SpdyFrame> window_update(
spdy_util_.ConstructSpdyWindowUpdate(1, wrapped_get_resp1->size()));
// Fetch https://www.example.org/2 via HTTP.
const char get2[] =
"GET /2 HTTP/1.1\r\n"
"Host: www.example.org\r\n"
"Connection: keep-alive\r\n\r\n";
scoped_ptr<SpdyFrame> wrapped_get2(
spdy_util_.ConstructSpdyBodyFrame(1, get2, strlen(get2), false));
const char resp2[] = "HTTP/1.1 200 OK\r\n"
"Content-Length: 2\r\n\r\n";
scoped_ptr<SpdyFrame> wrapped_get_resp2(
spdy_util_.ConstructSpdyBodyFrame(1, resp2, strlen(resp2), false));
scoped_ptr<SpdyFrame> wrapped_body2(
spdy_util_.ConstructSpdyBodyFrame(1, "22", 2, false));
MockWrite spdy_writes[] = {
CreateMockWrite(*connect1, 0),
CreateMockWrite(*wrapped_get1, 2),
CreateMockWrite(*wrapped_get2, 5),
};
MockRead spdy_reads[] = {
CreateMockRead(*conn_resp1, 1, ASYNC),
CreateMockRead(*wrapped_get_resp1, 3, ASYNC),
CreateMockRead(*wrapped_body1, 4, ASYNC),
CreateMockRead(*wrapped_get_resp2, 6, ASYNC),
CreateMockRead(*wrapped_body2, 7, ASYNC),
MockRead(ASYNC, 0, 8),
};
SequencedSocketData spdy_data(spdy_reads, arraysize(spdy_reads), spdy_writes,
arraysize(spdy_writes));
session_deps_.socket_factory->AddSocketDataProvider(&spdy_data);
SSLSocketDataProvider ssl(ASYNC, OK);
ssl.SetNextProto(GetProtocol());
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
SSLSocketDataProvider ssl2(ASYNC, OK);
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl2);
TestCompletionCallback callback;
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
int rv = trans->Start(&request1, callback.callback(), BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback.WaitForResult();
EXPECT_EQ(OK, rv);
LoadTimingInfo load_timing_info;
EXPECT_TRUE(trans->GetLoadTimingInfo(&load_timing_info));
TestLoadTimingNotReused(load_timing_info, CONNECT_TIMING_HAS_SSL_TIMES);
const HttpResponseInfo* response = trans->GetResponseInfo();
ASSERT_TRUE(response != NULL);
ASSERT_TRUE(response->headers.get() != NULL);
EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
std::string response_data;
scoped_refptr<IOBuffer> buf(new IOBuffer(256));
EXPECT_EQ(1, trans->Read(buf.get(), 256, callback.callback()));
trans.reset();
scoped_ptr<HttpTransaction> trans2(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
rv = trans2->Start(&request2, callback.callback(), BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback.WaitForResult();
EXPECT_EQ(OK, rv);
LoadTimingInfo load_timing_info2;
EXPECT_TRUE(trans2->GetLoadTimingInfo(&load_timing_info2));
TestLoadTimingReused(load_timing_info2);
// The requests should have the same ID.
EXPECT_EQ(load_timing_info.socket_log_id, load_timing_info2.socket_log_id);
EXPECT_EQ(2, trans2->Read(buf.get(), 256, callback.callback()));
}
// Test load timing in the case of of two HTTP requests through a SPDY HTTPS
// Proxy to different servers.
TEST_P(HttpNetworkTransactionTest, HttpsProxySpdyLoadTimingTwoHttpRequests) {
// Configure against https proxy server "proxy:70".
session_deps_.proxy_service = ProxyService::CreateFixed("https://proxy:70");
BoundTestNetLog log;
session_deps_.net_log = log.bound().net_log();
scoped_ptr<HttpNetworkSession> session(
SpdySessionDependencies::SpdyCreateSession(&session_deps_));
HttpRequestInfo request1;
request1.method = "GET";
request1.url = GURL("http://www.example.org/");
request1.load_flags = 0;
HttpRequestInfo request2;
request2.method = "GET";
request2.url = GURL("http://mail.example.org/");
request2.load_flags = 0;
// http://www.example.org/
scoped_ptr<SpdyHeaderBlock> headers(
spdy_util_.ConstructGetHeaderBlockForProxy("http://www.example.org/"));
scoped_ptr<SpdyFrame> get1(
spdy_util_.ConstructSpdySyn(1, *headers, LOWEST, true));
scoped_ptr<SpdyFrame> get_resp1(
spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
scoped_ptr<SpdyFrame> body1(
spdy_util_.ConstructSpdyBodyFrame(1, "1", 1, true));
spdy_util_.UpdateWithStreamDestruction(1);
// http://mail.example.org/
scoped_ptr<SpdyHeaderBlock> headers2(
spdy_util_.ConstructGetHeaderBlockForProxy("http://mail.example.org/"));
scoped_ptr<SpdyFrame> get2(
spdy_util_.ConstructSpdySyn(3, *headers2, LOWEST, true));
scoped_ptr<SpdyFrame> get_resp2(
spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 3));
scoped_ptr<SpdyFrame> body2(
spdy_util_.ConstructSpdyBodyFrame(3, "22", 2, true));
MockWrite spdy_writes[] = {
CreateMockWrite(*get1, 0),
CreateMockWrite(*get2, 3),
};
MockRead spdy_reads[] = {
CreateMockRead(*get_resp1, 1, ASYNC),
CreateMockRead(*body1, 2, ASYNC),
CreateMockRead(*get_resp2, 4, ASYNC),
CreateMockRead(*body2, 5, ASYNC),
MockRead(ASYNC, 0, 6),
};
SequencedSocketData spdy_data(spdy_reads, arraysize(spdy_reads), spdy_writes,
arraysize(spdy_writes));
session_deps_.socket_factory->AddSocketDataProvider(&spdy_data);
SSLSocketDataProvider ssl(ASYNC, OK);
ssl.SetNextProto(GetProtocol());
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
TestCompletionCallback callback;
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
int rv = trans->Start(&request1, callback.callback(), BoundNetLog());
EXPECT_EQ(OK, callback.GetResult(rv));
LoadTimingInfo load_timing_info;
EXPECT_TRUE(trans->GetLoadTimingInfo(&load_timing_info));
TestLoadTimingNotReused(load_timing_info,
CONNECT_TIMING_HAS_CONNECT_TIMES_ONLY);
const HttpResponseInfo* response = trans->GetResponseInfo();
ASSERT_TRUE(response != NULL);
ASSERT_TRUE(response->headers.get() != NULL);
EXPECT_EQ("HTTP/1.1 200", response->headers->GetStatusLine());
std::string response_data;
scoped_refptr<IOBuffer> buf(new IOBuffer(256));
rv = trans->Read(buf.get(), 256, callback.callback());
EXPECT_EQ(1, callback.GetResult(rv));
// Delete the first request, so the second one can reuse the socket.
trans.reset();
scoped_ptr<HttpTransaction> trans2(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
rv = trans2->Start(&request2, callback.callback(), BoundNetLog());
EXPECT_EQ(OK, callback.GetResult(rv));
LoadTimingInfo load_timing_info2;
EXPECT_TRUE(trans2->GetLoadTimingInfo(&load_timing_info2));
TestLoadTimingReused(load_timing_info2);
// The requests should have the same ID.
EXPECT_EQ(load_timing_info.socket_log_id, load_timing_info2.socket_log_id);
rv = trans2->Read(buf.get(), 256, callback.callback());
EXPECT_EQ(2, callback.GetResult(rv));
}
// Test the challenge-response-retry sequence through an HTTPS Proxy
TEST_P(HttpNetworkTransactionTest, HttpsProxyAuthRetry) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.example.org/");
// when the no authentication data flag is set.
request.load_flags = LOAD_DO_NOT_SEND_AUTH_DATA;
// Configure against https proxy server "myproxy:70".
session_deps_.proxy_service = ProxyService::CreateFixed("https://myproxy:70");
BoundTestNetLog log;
session_deps_.net_log = log.bound().net_log();
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
// Since we have proxy, should use full url
MockWrite data_writes1[] = {
MockWrite(
"GET http://www.example.org/ HTTP/1.1\r\n"
"Host: www.example.org\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.example.org/ HTTP/1.1\r\n"
"Host: www.example.org\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(DEFAULT_PRIORITY, session.get()));
int rv = trans->Start(&request, callback1.callback(), log.bound());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback1.WaitForResult();
EXPECT_EQ(OK, rv);
LoadTimingInfo load_timing_info;
EXPECT_TRUE(trans->GetLoadTimingInfo(&load_timing_info));
TestLoadTimingNotReused(load_timing_info,
CONNECT_TIMING_HAS_CONNECT_TIMES_ONLY);
const HttpResponseInfo* response = trans->GetResponseInfo();
ASSERT_TRUE(response != NULL);
ASSERT_FALSE(response->headers.get() == 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);
load_timing_info = LoadTimingInfo();
EXPECT_TRUE(trans->GetLoadTimingInfo(&load_timing_info));
// Retrying with HTTP AUTH is considered to be reusing a socket.
TestLoadTimingReused(load_timing_info);
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 HttpNetworkTransactionTest::ConnectStatusHelperWithExpectedStatus(
const MockRead& status, int expected_status) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("https://www.example.org/");
request.load_flags = 0;
// Configure against proxy server "myproxy:70".
session_deps_.proxy_service = ProxyService::CreateFixed("myproxy:70");
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
// Since we have proxy, should try to establish tunnel.
MockWrite data_writes[] = {
MockWrite("CONNECT www.example.org:443 HTTP/1.1\r\n"
"Host: www.example.org:443\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),
};
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(DEFAULT_PRIORITY, session.get()));
int rv = trans->Start(&request, callback.callback(), BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback.WaitForResult();
EXPECT_EQ(expected_status, rv);
}
void HttpNetworkTransactionTest::ConnectStatusHelper(
const MockRead& status) {
ConnectStatusHelperWithExpectedStatus(
status, ERR_TUNNEL_CONNECTION_FAILED);
}
TEST_P(HttpNetworkTransactionTest, ConnectStatus100) {
ConnectStatusHelper(MockRead("HTTP/1.1 100 Continue\r\n"));
}
TEST_P(HttpNetworkTransactionTest, ConnectStatus101) {
ConnectStatusHelper(MockRead("HTTP/1.1 101 Switching Protocols\r\n"));
}
TEST_P(HttpNetworkTransactionTest, ConnectStatus201) {
ConnectStatusHelper(MockRead("HTTP/1.1 201 Created\r\n"));
}
TEST_P(HttpNetworkTransactionTest, ConnectStatus202) {
ConnectStatusHelper(MockRead("HTTP/1.1 202 Accepted\r\n"));
}
TEST_P(HttpNetworkTransactionTest, ConnectStatus203) {
ConnectStatusHelper(
MockRead("HTTP/1.1 203 Non-Authoritative Information\r\n"));
}
TEST_P(HttpNetworkTransactionTest, ConnectStatus204) {
ConnectStatusHelper(MockRead("HTTP/1.1 204 No Content\r\n"));
}
TEST_P(HttpNetworkTransactionTest, ConnectStatus205) {
ConnectStatusHelper(MockRead("HTTP/1.1 205 Reset Content\r\n"));
}
TEST_P(HttpNetworkTransactionTest, ConnectStatus206) {
ConnectStatusHelper(MockRead("HTTP/1.1 206 Partial Content\r\n"));
}
TEST_P(HttpNetworkTransactionTest, ConnectStatus300) {
ConnectStatusHelper(MockRead("HTTP/1.1 300 Multiple Choices\r\n"));
}
TEST_P(HttpNetworkTransactionTest, ConnectStatus301) {
ConnectStatusHelper(MockRead("HTTP/1.1 301 Moved Permanently\r\n"));
}
TEST_P(HttpNetworkTransactionTest, ConnectStatus302) {
ConnectStatusHelper(MockRead("HTTP/1.1 302 Found\r\n"));
}
TEST_P(HttpNetworkTransactionTest, ConnectStatus303) {
ConnectStatusHelper(MockRead("HTTP/1.1 303 See Other\r\n"));
}
TEST_P(HttpNetworkTransactionTest, ConnectStatus304) {
ConnectStatusHelper(MockRead("HTTP/1.1 304 Not Modified\r\n"));
}
TEST_P(HttpNetworkTransactionTest, ConnectStatus305) {
ConnectStatusHelper(MockRead("HTTP/1.1 305 Use Proxy\r\n"));
}
TEST_P(HttpNetworkTransactionTest, ConnectStatus306) {
ConnectStatusHelper(MockRead("HTTP/1.1 306\r\n"));
}
TEST_P(HttpNetworkTransactionTest, ConnectStatus307) {
ConnectStatusHelper(MockRead("HTTP/1.1 307 Temporary Redirect\r\n"));
}
TEST_P(HttpNetworkTransactionTest, ConnectStatus308) {
ConnectStatusHelper(MockRead("HTTP/1.1 308 Permanent Redirect\r\n"));
}
TEST_P(HttpNetworkTransactionTest, ConnectStatus400) {
ConnectStatusHelper(MockRead("HTTP/1.1 400 Bad Request\r\n"));
}
TEST_P(HttpNetworkTransactionTest, ConnectStatus401) {
ConnectStatusHelper(MockRead("HTTP/1.1 401 Unauthorized\r\n"));
}
TEST_P(HttpNetworkTransactionTest, ConnectStatus402) {
ConnectStatusHelper(MockRead("HTTP/1.1 402 Payment Required\r\n"));
}
TEST_P(HttpNetworkTransactionTest, ConnectStatus403) {
ConnectStatusHelper(MockRead("HTTP/1.1 403 Forbidden\r\n"));
}
TEST_P(HttpNetworkTransactionTest, ConnectStatus404) {
ConnectStatusHelper(MockRead("HTTP/1.1 404 Not Found\r\n"));
}
TEST_P(HttpNetworkTransactionTest, ConnectStatus405) {
ConnectStatusHelper(MockRead("HTTP/1.1 405 Method Not Allowed\r\n"));
}
TEST_P(HttpNetworkTransactionTest, ConnectStatus406) {
ConnectStatusHelper(MockRead("HTTP/1.1 406 Not Acceptable\r\n"));
}
TEST_P(HttpNetworkTransactionTest, ConnectStatus407) {
ConnectStatusHelperWithExpectedStatus(
MockRead("HTTP/1.1 407 Proxy Authentication Required\r\n"),
ERR_PROXY_AUTH_UNSUPPORTED);
}
TEST_P(HttpNetworkTransactionTest, ConnectStatus408) {
ConnectStatusHelper(MockRead("HTTP/1.1 408 Request Timeout\r\n"));
}
TEST_P(HttpNetworkTransactionTest, ConnectStatus409) {
ConnectStatusHelper(MockRead("HTTP/1.1 409 Conflict\r\n"));
}
TEST_P(HttpNetworkTransactionTest, ConnectStatus410) {
ConnectStatusHelper(MockRead("HTTP/1.1 410 Gone\r\n"));
}
TEST_P(HttpNetworkTransactionTest, ConnectStatus411) {
ConnectStatusHelper(MockRead("HTTP/1.1 411 Length Required\r\n"));
}
TEST_P(HttpNetworkTransactionTest, ConnectStatus412) {
ConnectStatusHelper(MockRead("HTTP/1.1 412 Precondition Failed\r\n"));
}
TEST_P(HttpNetworkTransactionTest, ConnectStatus413) {
ConnectStatusHelper(MockRead("HTTP/1.1 413 Request Entity Too Large\r\n"));
}
TEST_P(HttpNetworkTransactionTest, ConnectStatus414) {
ConnectStatusHelper(MockRead("HTTP/1.1 414 Request-URI Too Long\r\n"));
}
TEST_P(HttpNetworkTransactionTest, ConnectStatus415) {
ConnectStatusHelper(MockRead("HTTP/1.1 415 Unsupported Media Type\r\n"));
}
TEST_P(HttpNetworkTransactionTest, ConnectStatus416) {
ConnectStatusHelper(
MockRead("HTTP/1.1 416 Requested Range Not Satisfiable\r\n"));
}
TEST_P(HttpNetworkTransactionTest, ConnectStatus417) {
ConnectStatusHelper(MockRead("HTTP/1.1 417 Expectation Failed\r\n"));
}
TEST_P(HttpNetworkTransactionTest, ConnectStatus500) {
ConnectStatusHelper(MockRead("HTTP/1.1 500 Internal Server Error\r\n"));
}
TEST_P(HttpNetworkTransactionTest, ConnectStatus501) {
ConnectStatusHelper(MockRead("HTTP/1.1 501 Not Implemented\r\n"));
}
TEST_P(HttpNetworkTransactionTest, ConnectStatus502) {
ConnectStatusHelper(MockRead("HTTP/1.1 502 Bad Gateway\r\n"));
}
TEST_P(HttpNetworkTransactionTest, ConnectStatus503) {
ConnectStatusHelper(MockRead("HTTP/1.1 503 Service Unavailable\r\n"));
}
TEST_P(HttpNetworkTransactionTest, ConnectStatus504) {
ConnectStatusHelper(MockRead("HTTP/1.1 504 Gateway Timeout\r\n"));
}
TEST_P(HttpNetworkTransactionTest, 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_P(HttpNetworkTransactionTest, BasicAuthProxyThenServer) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.example.org/");
request.load_flags = 0;
// Configure against proxy server "myproxy:70".
session_deps_.proxy_service = ProxyService::CreateFixed("myproxy:70");
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
MockWrite data_writes1[] = {
MockWrite(
"GET http://www.example.org/ HTTP/1.1\r\n"
"Host: www.example.org\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.example.org/ HTTP/1.1\r\n"
"Host: www.example.org\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.example.org/ HTTP/1.1\r\n"
"Host: www.example.org\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_P(HttpNetworkTransactionTest, NTLMAuth1) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://172.22.68.17/kids/login.aspx");
// Ensure load is not disrupted by flags which suppress behaviour specific
// to other auth schemes.
request.load_flags = LOAD_DO_NOT_USE_EMBEDDED_IDENTITY;
HttpAuthHandlerNTLM::ScopedProcSetter proc_setter(MockGenerateRandom1,
MockGetHostName);
scoped_ptr<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(DEFAULT_PRIORITY, session.get()));
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_P(HttpNetworkTransactionTest, 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);
scoped_ptr<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(DEFAULT_PRIORITY, session.get()));
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_P(HttpNetworkTransactionTest, LargeHeadersNoBody) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.example.org/");
request.load_flags = 0;
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
// 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);
}
// 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_P(HttpNetworkTransactionTest,
DontRecycleTransportSocketForSSLTunnel) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("https://www.example.org/");
request.load_flags = 0;
// Configure against proxy server "myproxy:70".
session_deps_.proxy_service = ProxyService::CreateFixed("myproxy:70");
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
// Since we have proxy, should try to establish tunnel.
MockWrite data_writes1[] = {
MockWrite("CONNECT www.example.org:443 HTTP/1.1\r\n"
"Host: www.example.org:443\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),
};
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);
// Empty the current queue. This is necessary because idle sockets are
// added to the connection pool asynchronously with a PostTask.
base::MessageLoop::current()->RunUntilIdle();
// We now check to make sure the TCPClientSocket was not added back to
// the pool.
EXPECT_EQ(0, GetIdleSocketCountInTransportSocketPool(session.get()));
trans.reset();
base::MessageLoop::current()->RunUntilIdle();
// Make sure that the socket didn't get recycled after calling the destructor.
EXPECT_EQ(0, GetIdleSocketCountInTransportSocketPool(session.get()));
}
// Make sure that we recycle a socket after reading all of the response body.
TEST_P(HttpNetworkTransactionTest, RecycleSocket) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.example.org/");
request.load_flags = 0;
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
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.get() != NULL);
std::string status_line = response->headers->GetStatusLine();
EXPECT_EQ("HTTP/1.1 200 OK", status_line);
EXPECT_EQ(0, GetIdleSocketCountInTransportSocketPool(session.get()));
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.
base::MessageLoop::current()->RunUntilIdle();
// We now check to make sure the socket was added back to the pool.
EXPECT_EQ(1, GetIdleSocketCountInTransportSocketPool(session.get()));
}
// Make sure that we recycle a SSL socket after reading all of the response
// body.
TEST_P(HttpNetworkTransactionTest, RecycleSSLSocket) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("https://www.example.org/");
request.load_flags = 0;
MockWrite data_writes[] = {
MockWrite(
"GET / HTTP/1.1\r\n"
"Host: www.example.org\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_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
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.get() != NULL);
EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
EXPECT_EQ(0, GetIdleSocketCountInTransportSocketPool(session.get()));
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.
base::MessageLoop::current()->RunUntilIdle();
// We now check to make sure the socket was added back to the pool.
EXPECT_EQ(1, GetIdleSocketCountInSSLSocketPool(session.get()));
}
// 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_P(HttpNetworkTransactionTest, RecycleDeadSSLSocket) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("https://www.example.org/");
request.load_flags = 0;
MockWrite data_writes[] = {
MockWrite(
"GET / HTTP/1.1\r\n"
"Host: www.example.org\r\n"
"Connection: keep-alive\r\n\r\n"),
MockWrite(
"GET / HTTP/1.1\r\n"
"Host: www.example.org\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(ASYNC, ERR_CONNECTION_CLOSED)};
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_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
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.get() != NULL);
EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
EXPECT_EQ(0, GetIdleSocketCountInTransportSocketPool(session.get()));
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.
base::MessageLoop::current()->RunUntilIdle();
// We now check to make sure the socket was added back to the pool.
EXPECT_EQ(1, GetIdleSocketCountInSSLSocketPool(session.get()));
// Now start the second transaction, which should reuse the previous socket.
trans.reset(new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
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.get() != NULL);
EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
EXPECT_EQ(0, GetIdleSocketCountInTransportSocketPool(session.get()));
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.
base::MessageLoop::current()->RunUntilIdle();
// We now check to make sure the socket was added back to the pool.
EXPECT_EQ(1, GetIdleSocketCountInSSLSocketPool(session.get()));
}
// Make sure that we recycle a socket after a zero-length response.
// http://crbug.com/9880
TEST_P(HttpNetworkTransactionTest, RecycleSocketAfterZeroContentLength) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL(
"http://www.example.org/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;
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
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);
// Transaction must be created after the MockReads, so it's destroyed before
// them.
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
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.get() != NULL);
std::string status_line = response->headers->GetStatusLine();
EXPECT_EQ("HTTP/1.1 204 No Content", status_line);
EXPECT_EQ(0, GetIdleSocketCountInTransportSocketPool(session.get()));
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.
base::MessageLoop::current()->RunUntilIdle();
// We now check to make sure the socket was added back to the pool.
EXPECT_EQ(1, GetIdleSocketCountInTransportSocketPool(session.get()));
}
TEST_P(HttpNetworkTransactionTest, ResendRequestOnWriteBodyError) {
std::vector<scoped_ptr<UploadElementReader>> element_readers;
element_readers.push_back(
make_scoped_ptr(new UploadBytesElementReader("foo", 3)));
ElementsUploadDataStream upload_data_stream(std::move(element_readers), 0);
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_stream = &upload_data_stream;
request[1].load_flags = 0;
scoped_ptr<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* const kExpectedResponseData[] = {
"hello world", "welcome"
};
for (int i = 0; i < 2; ++i) {
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
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.get() != 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 used to answer the challenge.
TEST_P(HttpNetworkTransactionTest, AuthIdentityInURL) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://foo:b@r@www.example.org/");
request.load_flags = LOAD_NORMAL;
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
// 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.example.org\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),
};
// After the challenge above, the transaction will be restarted using the
// identity from the url (foo, b@r) to answer the challenge.
MockWrite data_writes2[] = {
MockWrite(
"GET / HTTP/1.1\r\n"
"Host: www.example.org\r\n"
"Connection: keep-alive\r\n"
"Authorization: Basic Zm9vOmJAcg==\r\n\r\n"),
};
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);
// There is no challenge info, since the identity in URL worked.
EXPECT_TRUE(response->auth_challenge.get() == NULL);
EXPECT_EQ(100, response->headers->GetContentLength());
// Empty the current queue.
base::MessageLoop::current()->RunUntilIdle();
}
// Test the request-challenge-retry sequence for basic auth when there is an
// incorrect identity in the URL. The identity from the URL should be used only
// once.
TEST_P(HttpNetworkTransactionTest, WrongAuthIdentityInURL) {
HttpRequestInfo request;
request.method = "GET";
// Note: the URL has a username:password in it. The password "baz" is
// wrong (should be "bar").
request.url = GURL("http://foo:baz@www.example.org/");
request.load_flags = LOAD_NORMAL;
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
MockWrite data_writes1[] = {
MockWrite(
"GET / HTTP/1.1\r\n"
"Host: www.example.org\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),
};
// After the challenge above, the transaction will be restarted using the
// identity from the url (foo, baz) to answer the challenge.
MockWrite data_writes2[] = {
MockWrite(
"GET / HTTP/1.1\r\n"
"Host: www.example.org\r\n"
"Connection: keep-alive\r\n"
"Authorization: Basic Zm9vOmJheg==\r\n\r\n"),
};
MockRead data_reads2[] = {
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),
};
// After the challenge above, the transaction will be restarted using the
// identity supplied by the user (foo, bar) to answer the challenge.
MockWrite data_writes3[] = {
MockWrite(
"GET / HTTP/1.1\r\n"
"Host: www.example.org\r\n"
"Connection: keep-alive\r\n"
"Authorization: Basic Zm9vOmJhcg==\r\n\r\n"),
};
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(kFoo, kBar), callback3.callback());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback3.WaitForResult();
EXPECT_EQ(OK, rv);
EXPECT_FALSE(trans->IsReadyToRestartForAuth());
response = trans->GetResponseInfo();
ASSERT_TRUE(response != NULL);
// There is no challenge info, since the identity worked.
EXPECT_TRUE(response->auth_challenge.get() == NULL);
EXPECT_EQ(100, response->headers->GetContentLength());
// Empty the current queue.
base::MessageLoop::current()->RunUntilIdle();
}
// Test the request-challenge-retry sequence for basic auth when there is a
// correct identity in the URL, but its use is being suppressed. The identity
// from the URL should never be used.
TEST_P(HttpNetworkTransactionTest, AuthIdentityInURLSuppressed) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://foo:bar@www.example.org/");
request.load_flags = LOAD_DO_NOT_USE_EMBEDDED_IDENTITY;
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
MockWrite data_writes1[] = {
MockWrite(
"GET / HTTP/1.1\r\n"
"Host: www.example.org\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),
};
// After the challenge above, the transaction will be restarted using the
// identity supplied by the user, not the one in the URL, to answer the
// challenge.
MockWrite data_writes3[] = {
MockWrite(
"GET / HTTP/1.1\r\n"
"Host: www.example.org\r\n"
"Connection: keep-alive\r\n"
"Authorization: Basic Zm9vOmJhcg==\r\n\r\n"),
};
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 data3(data_reads3, arraysize(data_reads3),
data_writes3, arraysize(data_writes3));
session_deps_.socket_factory->AddSocketDataProvider(&data1);
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_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(kFoo, kBar), callback3.callback());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback3.WaitForResult();
EXPECT_EQ(OK, rv);
EXPECT_FALSE(trans->IsReadyToRestartForAuth());
response = trans->GetResponseInfo();
ASSERT_TRUE(response != NULL);
// There is no challenge info, since the identity worked.
EXPECT_TRUE(response->auth_challenge.get() == NULL);
EXPECT_EQ(100, response->headers->GetContentLength());
// Empty the current queue.
base::MessageLoop::current()->RunUntilIdle();
}
// Test that previously tried username/passwords for a realm get re-used.
TEST_P(HttpNetworkTransactionTest, BasicAuthCacheAndPreauth) {
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
// Transaction 1: authenticate (foo, bar) on MyRealm1
{
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.example.org/x/y/z");
request.load_flags = 0;
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
MockWrite data_writes1[] = {
MockWrite(
"GET /x/y/z HTTP/1.1\r\n"
"Host: www.example.org\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.example.org\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.example.org/x/y/a/b");
request.load_flags = 0;
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
MockWrite data_writes1[] = {
MockWrite(
"GET /x/y/a/b HTTP/1.1\r\n"
"Host: www.example.org\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.example.org\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.example.org:80",
response->auth_challenge->challenger.ToString());
EXPECT_EQ("MyRealm2", response->auth_challenge->realm);
EXPECT_EQ(kBasicAuthScheme, 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.example.org/x/y/z2");
request.load_flags = 0;
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
MockWrite data_writes1[] = {
MockWrite(
"GET /x/y/z2 HTTP/1.1\r\n"
"Host: www.example.org\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.example.org/x/1");
request.load_flags = 0;
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
MockWrite data_writes1[] = {
MockWrite(
"GET /x/1 HTTP/1.1\r\n"
"Host: www.example.org\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.example.org\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.example.org/p/q/t");
request.load_flags = 0;
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
MockWrite data_writes1[] = {
MockWrite(
"GET /p/q/t HTTP/1.1\r\n"
"Host: www.example.org\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.example.org\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.example.org\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_P(HttpNetworkTransactionTest, DigestPreAuthNonceCount) {
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_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
// Transaction 1: authenticate (foo, bar) on MyRealm1
{
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.example.org/x/y/z");
request.load_flags = 0;
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
MockWrite data_writes1[] = {
MockWrite(
"GET /x/y/z HTTP/1.1\r\n"
"Host: www.example.org\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.example.org\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.example.org/x/y/a/b");
request.load_flags = 0;
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
MockWrite data_writes1[] = {
MockWrite(
"GET /x/y/a/b HTTP/1.1\r\n"
"Host: www.example.org\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_P(HttpNetworkTransactionTest, ResetStateForRestart) {
// Create a transaction (the dependencies aren't important).
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpNetworkTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
// 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.get()));
}
// 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_P(HttpNetworkTransactionTest, HTTPSBadCertificate) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("https://www.example.org/");
request.load_flags = 0;
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
MockWrite data_writes[] = {
MockWrite(
"GET / HTTP/1.1\r\n"
"Host: www.example.org\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_P(HttpNetworkTransactionTest, HTTPSBadCertificateViaProxy) {
session_deps_.proxy_service = ProxyService::CreateFixed("myproxy:70");
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("https://www.example.org/");
request.load_flags = 0;
MockWrite proxy_writes[] = {
MockWrite("CONNECT www.example.org:443 HTTP/1.1\r\n"
"Host: www.example.org:443\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.example.org:443 HTTP/1.1\r\n"
"Host: www.example.org:443\r\n"
"Proxy-Connection: keep-alive\r\n\r\n"),
MockWrite("GET / HTTP/1.1\r\n"
"Host: www.example.org\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<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
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_P(HttpNetworkTransactionTest, HTTPSViaHttpsProxy) {
session_deps_.proxy_service =
ProxyService::CreateFixedFromPacResult("HTTPS proxy:70");
TestNetLog net_log;
session_deps_.net_log = &net_log;
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("https://www.example.org/");
request.load_flags = 0;
MockWrite data_writes[] = {
MockWrite("CONNECT www.example.org:443 HTTP/1.1\r\n"
"Host: www.example.org:443\r\n"
"Proxy-Connection: keep-alive\r\n\r\n"),
MockWrite("GET / HTTP/1.1\r\n"
"Host: www.example.org\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<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
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());
LoadTimingInfo load_timing_info;
EXPECT_TRUE(trans->GetLoadTimingInfo(&load_timing_info));
TestLoadTimingNotReusedWithPac(load_timing_info,
CONNECT_TIMING_HAS_SSL_TIMES);
}
// Test an HTTPS Proxy's ability to redirect a CONNECT request
TEST_P(HttpNetworkTransactionTest, RedirectOfHttpsConnectViaHttpsProxy) {
session_deps_.proxy_service =
ProxyService::CreateFixedFromPacResult("HTTPS proxy:70");
TestNetLog net_log;
session_deps_.net_log = &net_log;
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("https://www.example.org/");
request.load_flags = 0;
MockWrite data_writes[] = {
MockWrite("CONNECT www.example.org:443 HTTP/1.1\r\n"
"Host: www.example.org:443\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<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
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);
// In the case of redirects from proxies, HttpNetworkTransaction returns
// timing for the proxy connection instead of the connection to the host,
// and no send / receive times.
// See HttpNetworkTransaction::OnHttpsProxyTunnelResponse.
LoadTimingInfo load_timing_info;
EXPECT_TRUE(trans->GetLoadTimingInfo(&load_timing_info));
EXPECT_FALSE(load_timing_info.socket_reused);
EXPECT_NE(NetLog::Source::kInvalidId, load_timing_info.socket_log_id);
EXPECT_FALSE(load_timing_info.proxy_resolve_start.is_null());
EXPECT_LE(load_timing_info.proxy_resolve_start,
load_timing_info.proxy_resolve_end);
EXPECT_LE(load_timing_info.proxy_resolve_end,
load_timing_info.connect_timing.connect_start);
ExpectConnectTimingHasTimes(
load_timing_info.connect_timing,
CONNECT_TIMING_HAS_DNS_TIMES | CONNECT_TIMING_HAS_SSL_TIMES);
EXPECT_TRUE(load_timing_info.send_start.is_null());
EXPECT_TRUE(load_timing_info.send_end.is_null());
EXPECT_TRUE(load_timing_info.receive_headers_end.is_null());
}
// Test an HTTPS (SPDY) Proxy's ability to redirect a CONNECT request
TEST_P(HttpNetworkTransactionTest, RedirectOfHttpsConnectViaSpdyProxy) {
session_deps_.proxy_service = ProxyService::CreateFixed("https://proxy:70");
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("https://www.example.org/");
request.load_flags = 0;
scoped_ptr<SpdyFrame> conn(spdy_util_.ConstructSpdyConnect(
NULL, 0, 1, LOWEST, HostPortPair("www.example.org", 443)));
scoped_ptr<SpdyFrame> goaway(
spdy_util_.ConstructSpdyRstStream(1, RST_STREAM_CANCEL));
MockWrite data_writes[] = {
CreateMockWrite(*conn.get(), 0, SYNCHRONOUS),
CreateMockWrite(*goaway.get(), 2, SYNCHRONOUS),
};
static const char* const kExtraHeaders[] = {
"location",
"http://login.example.com/",
};
scoped_ptr<SpdyFrame> resp(
spdy_util_.ConstructSpdySynReplyError("302 Redirect", kExtraHeaders,
arraysize(kExtraHeaders)/2, 1));
MockRead data_reads[] = {
CreateMockRead(*resp.get(), 1), MockRead(ASYNC, 0, 3), // EOF
};
SequencedSocketData data(data_reads, arraysize(data_reads), data_writes,
arraysize(data_writes));
SSLSocketDataProvider proxy_ssl(ASYNC, OK); // SSL to the proxy
proxy_ssl.SetNextProto(GetProtocol());
session_deps_.socket_factory->AddSocketDataProvider(&data);
session_deps_.socket_factory->AddSSLSocketDataProvider(&proxy_ssl);
TestCompletionCallback callback;
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
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 that an HTTPS proxy's response to a CONNECT request is filtered.
TEST_P(HttpNetworkTransactionTest,
ErrorResponseToHttpsConnectViaHttpsProxy) {
session_deps_.proxy_service = ProxyService::CreateFixed("https://proxy:70");
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("https://www.example.org/");
request.load_flags = 0;
MockWrite data_writes[] = {
MockWrite("CONNECT www.example.org:443 HTTP/1.1\r\n"
"Host: www.example.org:443\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<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
int rv = trans->Start(&request, callback.callback(), BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback.WaitForResult();
EXPECT_EQ(ERR_TUNNEL_CONNECTION_FAILED, rv);
// TODO(ttuttle): Anything else to check here?
}
// Test that a SPDY proxy's response to a CONNECT request is filtered.
TEST_P(HttpNetworkTransactionTest,
ErrorResponseToHttpsConnectViaSpdyProxy) {
session_deps_.proxy_service = ProxyService::CreateFixed("https://proxy:70");
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("https://www.example.org/");
request.load_flags = 0;
scoped_ptr<SpdyFrame> conn(spdy_util_.ConstructSpdyConnect(
NULL, 0, 1, LOWEST, HostPortPair("www.example.org", 443)));
scoped_ptr<SpdyFrame> rst(
spdy_util_.ConstructSpdyRstStream(1, RST_STREAM_CANCEL));
MockWrite data_writes[] = {
CreateMockWrite(*conn.get(), 0), CreateMockWrite(*rst.get(), 3),
};
static const char* const kExtraHeaders[] = {
"location",
"http://login.example.com/",
};
scoped_ptr<SpdyFrame> resp(
spdy_util_.ConstructSpdySynReplyError("404 Not Found", kExtraHeaders,
arraysize(kExtraHeaders)/2, 1));
scoped_ptr<SpdyFrame> body(
spdy_util_.ConstructSpdyBodyFrame(
1, "The host does not exist", 23, true));
MockRead data_reads[] = {
CreateMockRead(*resp.get(), 1),
CreateMockRead(*body.get(), 2),
MockRead(ASYNC, 0, 4), // EOF
};
SequencedSocketData data(data_reads, arraysize(data_reads), data_writes,
arraysize(data_writes));
SSLSocketDataProvider proxy_ssl(ASYNC, OK); // SSL to the proxy
proxy_ssl.SetNextProto(GetProtocol());
session_deps_.socket_factory->AddSocketDataProvider(&data);
session_deps_.socket_factory->AddSSLSocketDataProvider(&proxy_ssl);
TestCompletionCallback callback;
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
int rv = trans->Start(&request, callback.callback(), BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback.WaitForResult();
EXPECT_EQ(ERR_TUNNEL_CONNECTION_FAILED, rv);
// TODO(ttuttle): Anything else to check here?
}
// Test the request-challenge-retry sequence for basic auth, through
// a SPDY proxy over a single SPDY session.
TEST_P(HttpNetworkTransactionTest, BasicAuthSpdyProxy) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("https://www.example.org/");
// when the no authentication data flag is set.
request.load_flags = LOAD_DO_NOT_SEND_AUTH_DATA;
// Configure against https proxy server "myproxy:70".
session_deps_.proxy_service =
ProxyService::CreateFixedFromPacResult("HTTPS myproxy:70");
BoundTestNetLog log;
session_deps_.net_log = log.bound().net_log();
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
// Since we have proxy, should try to establish tunnel.
scoped_ptr<SpdyFrame> req(spdy_util_.ConstructSpdyConnect(
NULL, 0, 1, LOWEST, HostPortPair("www.example.org", 443)));
scoped_ptr<SpdyFrame> rst(
spdy_util_.ConstructSpdyRstStream(1, RST_STREAM_CANCEL));
spdy_util_.UpdateWithStreamDestruction(1);
// 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<SpdyFrame> connect2(spdy_util_.ConstructSpdyConnect(
kAuthCredentials, arraysize(kAuthCredentials) / 2, 3, LOWEST,
HostPortPair("www.example.org", 443)));
// fetch https://www.example.org/ via HTTP
const char get[] =
"GET / HTTP/1.1\r\n"
"Host: www.example.org\r\n"
"Connection: keep-alive\r\n\r\n";
scoped_ptr<SpdyFrame> wrapped_get(
spdy_util_.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 kAuthStatus[] = "407";
const char* const kAuthChallenge[] = {
"proxy-authenticate", "Basic realm=\"MyRealm1\"",
};
scoped_ptr<SpdyFrame> conn_auth_resp(spdy_util_.ConstructSpdySynReplyError(
kAuthStatus, kAuthChallenge, arraysize(kAuthChallenge) / 2, 1));
scoped_ptr<SpdyFrame> conn_resp(
spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 3));
const char resp[] = "HTTP/1.1 200 OK\r\n"
"Content-Length: 5\r\n\r\n";
scoped_ptr<SpdyFrame> wrapped_get_resp(
spdy_util_.ConstructSpdyBodyFrame(3, resp, strlen(resp), false));
scoped_ptr<SpdyFrame> wrapped_body(
spdy_util_.ConstructSpdyBodyFrame(3, "hello", 5, false));
MockRead spdy_reads[] = {
CreateMockRead(*conn_auth_resp, 1, ASYNC),
CreateMockRead(*conn_resp, 4, ASYNC),
CreateMockRead(*wrapped_get_resp, 6, ASYNC),
CreateMockRead(*wrapped_body, 7, ASYNC),
MockRead(ASYNC, OK, 8), // EOF. May or may not be read.
};
SequencedSocketData spdy_data(spdy_reads, arraysize(spdy_reads), spdy_writes,
arraysize(spdy_writes));
session_deps_.socket_factory->AddSocketDataProvider(&spdy_data);
// Negotiate SPDY to the proxy
SSLSocketDataProvider proxy(ASYNC, OK);
proxy.SetNextProto(GetProtocol());
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(DEFAULT_PRIORITY, session.get()));
int rv = trans->Start(&request, callback1.callback(), log.bound());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback1.WaitForResult();
EXPECT_EQ(OK, rv);
TestNetLogEntry::List 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.get() == 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);
LoadTimingInfo load_timing_info;
EXPECT_TRUE(trans->GetLoadTimingInfo(&load_timing_info));
TestLoadTimingNotReusedWithPac(load_timing_info,
CONNECT_TIMING_HAS_SSL_TIMES);
trans.reset();
session->CloseAllConnections();
}
// Test that an explicitly trusted SPDY proxy can push a resource from an
// origin that is different from that of its associated resource.
TEST_P(HttpNetworkTransactionTest, CrossOriginSPDYProxyPush) {
// Configure the proxy delegate to allow cross-origin SPDY pushes.
scoped_ptr<TestProxyDelegate> proxy_delegate(new TestProxyDelegate());
proxy_delegate->set_trusted_spdy_proxy(net::ProxyServer::FromURI(
"https://myproxy:443", net::ProxyServer::SCHEME_HTTP));
HttpRequestInfo request;
HttpRequestInfo push_request;
request.method = "GET";
request.url = GURL("http://www.example.org/");
push_request.method = "GET";
push_request.url = GURL("http://www.another-origin.com/foo.dat");
// Configure against https proxy server "myproxy:443".
session_deps_.proxy_service =
ProxyService::CreateFixedFromPacResult("HTTPS myproxy:443");
BoundTestNetLog log;
session_deps_.net_log = log.bound().net_log();
session_deps_.proxy_delegate.reset(proxy_delegate.release());
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<SpdyFrame> stream1_syn(
spdy_util_.ConstructSpdyGet(nullptr, 0, 1, LOWEST, false));
MockWrite spdy_writes[] = {
CreateMockWrite(*stream1_syn, 0, ASYNC),
};
scoped_ptr<SpdyFrame>
stream1_reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
scoped_ptr<SpdyFrame>
stream1_body(spdy_util_.ConstructSpdyBodyFrame(1, true));
scoped_ptr<SpdyFrame>
stream2_syn(spdy_util_.ConstructSpdyPush(NULL,
0,
2,
1,
"http://www.another-origin.com/foo.dat"));
const char kPushedData[] = "pushed";
scoped_ptr<SpdyFrame> stream2_body(
spdy_util_.ConstructSpdyBodyFrame(
2, kPushedData, strlen(kPushedData), true));
MockRead spdy_reads[] = {
CreateMockRead(*stream1_reply, 1, ASYNC),
CreateMockRead(*stream2_syn, 2, ASYNC),
CreateMockRead(*stream1_body, 3, ASYNC),
CreateMockRead(*stream2_body, 4, ASYNC),
MockRead(SYNCHRONOUS, ERR_IO_PENDING, 5), // Force a hang
};
SequencedSocketData spdy_data(spdy_reads, arraysize(spdy_reads), spdy_writes,
arraysize(spdy_writes));
session_deps_.socket_factory->AddSocketDataProvider(&spdy_data);
// Negotiate SPDY to the proxy
SSLSocketDataProvider proxy(ASYNC, OK);
proxy.SetNextProto(GetProtocol());
session_deps_.socket_factory->AddSSLSocketDataProvider(&proxy);
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
TestCompletionCallback callback;
int rv = trans->Start(&request, callback.callback(), log.bound());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback.WaitForResult();
EXPECT_EQ(OK, rv);
const HttpResponseInfo* response = trans->GetResponseInfo();
scoped_ptr<HttpTransaction> push_trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
rv = push_trans->Start(&push_request, callback.callback(), log.bound());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback.WaitForResult();
EXPECT_EQ(OK, rv);
const HttpResponseInfo* push_response = push_trans->GetResponseInfo();
ASSERT_TRUE(response != NULL);
EXPECT_TRUE(response->headers->IsKeepAlive());
EXPECT_EQ(200, response->headers->response_code());
EXPECT_TRUE(HttpVersion(1, 1) == response->headers->GetHttpVersion());
std::string response_data;
rv = ReadTransaction(trans.get(), &response_data);
EXPECT_EQ(OK, rv);
EXPECT_EQ("hello!", response_data);
LoadTimingInfo load_timing_info;
EXPECT_TRUE(trans->GetLoadTimingInfo(&load_timing_info));
TestLoadTimingNotReusedWithPac(load_timing_info,
CONNECT_TIMING_HAS_CONNECT_TIMES_ONLY);
// Verify the pushed stream.
EXPECT_TRUE(push_response->headers.get() != NULL);
EXPECT_EQ(200, push_response->headers->response_code());
rv = ReadTransaction(push_trans.get(), &response_data);
EXPECT_EQ(OK, rv);
EXPECT_EQ("pushed", response_data);
LoadTimingInfo push_load_timing_info;
EXPECT_TRUE(push_trans->GetLoadTimingInfo(&push_load_timing_info));
TestLoadTimingReusedWithPac(push_load_timing_info);
// The transactions should share a socket ID, despite being for different
// origins.
EXPECT_EQ(load_timing_info.socket_log_id,
push_load_timing_info.socket_log_id);
trans.reset();
push_trans.reset();
session->CloseAllConnections();
}
// Test that an explicitly trusted SPDY proxy cannot push HTTPS content.
TEST_P(HttpNetworkTransactionTest, CrossOriginProxyPushCorrectness) {
// Configure the proxy delegate to allow cross-origin SPDY pushes.
scoped_ptr<TestProxyDelegate> proxy_delegate(new TestProxyDelegate());
proxy_delegate->set_trusted_spdy_proxy(net::ProxyServer::FromURI(
"https://myproxy:443", net::ProxyServer::SCHEME_HTTP));
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.example.org/");
session_deps_.proxy_service =
ProxyService::CreateFixed("https://myproxy:443");
BoundTestNetLog log;
session_deps_.net_log = log.bound().net_log();
// Enable cross-origin push.
session_deps_.proxy_delegate.reset(proxy_delegate.release());
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<SpdyFrame> stream1_syn(
spdy_util_.ConstructSpdyGet(nullptr, 0, 1, LOWEST, false));
scoped_ptr<SpdyFrame> push_rst(
spdy_util_.ConstructSpdyRstStream(2, RST_STREAM_REFUSED_STREAM));
MockWrite spdy_writes[] = {
CreateMockWrite(*stream1_syn, 0, ASYNC), CreateMockWrite(*push_rst, 3),
};
scoped_ptr<SpdyFrame>
stream1_reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
scoped_ptr<SpdyFrame>
stream1_body(spdy_util_.ConstructSpdyBodyFrame(1, true));
scoped_ptr<SpdyFrame>
stream2_syn(spdy_util_.ConstructSpdyPush(NULL,
0,
2,
1,
"https://www.another-origin.com/foo.dat"));
MockRead spdy_reads[] = {
CreateMockRead(*stream1_reply, 1, ASYNC),
CreateMockRead(*stream2_syn, 2, ASYNC),
CreateMockRead(*stream1_body, 4, ASYNC),
MockRead(SYNCHRONOUS, ERR_IO_PENDING, 5), // Force a hang
};
SequencedSocketData spdy_data(spdy_reads, arraysize(spdy_reads), spdy_writes,
arraysize(spdy_writes));
session_deps_.socket_factory->AddSocketDataProvider(&spdy_data);
// Negotiate SPDY to the proxy
SSLSocketDataProvider proxy(ASYNC, OK);
proxy.SetNextProto(GetProtocol());
session_deps_.socket_factory->AddSSLSocketDataProvider(&proxy);
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
TestCompletionCallback callback;
int rv = trans->Start(&request, callback.callback(), log.bound());
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_TRUE(HttpVersion(1, 1) == response->headers->GetHttpVersion());
std::string response_data;
rv = ReadTransaction(trans.get(), &response_data);
EXPECT_EQ(OK, rv);
EXPECT_EQ("hello!", response_data);
trans.reset();
session->CloseAllConnections();
}
// Test that an explicitly trusted SPDY proxy can push same-origin HTTPS
// resources.
TEST_P(HttpNetworkTransactionTest, SameOriginProxyPushCorrectness) {
// Configure the proxy delegate to allow cross-origin SPDY pushes.
scoped_ptr<TestProxyDelegate> proxy_delegate(new TestProxyDelegate());
proxy_delegate->set_trusted_spdy_proxy(
net::ProxyServer::FromURI("myproxy:70", net::ProxyServer::SCHEME_HTTP));
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.example.org/");
// Configure against https proxy server "myproxy:70".
session_deps_.proxy_service = ProxyService::CreateFixed("https://myproxy:70");
BoundTestNetLog log;
session_deps_.net_log = log.bound().net_log();
// Enable cross-origin push.
session_deps_.proxy_delegate.reset(proxy_delegate.release());
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<SpdyFrame> stream1_syn(
spdy_util_.ConstructSpdyGet(nullptr, 0, 1, LOWEST, false));
MockWrite spdy_writes[] = {
CreateMockWrite(*stream1_syn, 0, ASYNC),
};
scoped_ptr<SpdyFrame> stream1_reply(
spdy_util_.ConstructSpdyGetSynReply(nullptr, 0, 1));
scoped_ptr<SpdyFrame> stream2_syn(spdy_util_.ConstructSpdyPush(
nullptr, 0, 2, 1, "https://myproxy:70/foo.dat"));
scoped_ptr<SpdyFrame> stream1_body(
spdy_util_.ConstructSpdyBodyFrame(1, true));
scoped_ptr<SpdyFrame> stream2_reply(
spdy_util_.ConstructSpdyGetSynReply(nullptr, 0, 1));
scoped_ptr<SpdyFrame> stream2_body(
spdy_util_.ConstructSpdyBodyFrame(1, true));
MockRead spdy_reads[] = {
CreateMockRead(*stream1_reply, 1, ASYNC),
CreateMockRead(*stream2_syn, 2, ASYNC),
CreateMockRead(*stream1_body, 3, ASYNC),
CreateMockRead(*stream2_body, 4, ASYNC),
MockRead(SYNCHRONOUS, ERR_IO_PENDING, 5), // Force a hang
};
SequencedSocketData spdy_data(spdy_reads, arraysize(spdy_reads), spdy_writes,
arraysize(spdy_writes));
session_deps_.socket_factory->AddSocketDataProvider(&spdy_data);
// Negotiate SPDY to the proxy
SSLSocketDataProvider proxy(ASYNC, OK);
proxy.SetNextProto(GetProtocol());
session_deps_.socket_factory->AddSSLSocketDataProvider(&proxy);
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
TestCompletionCallback callback;
int rv = trans->Start(&request, callback.callback(), log.bound());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback.WaitForResult();
EXPECT_EQ(OK, rv);
const HttpResponseInfo* response = trans->GetResponseInfo();
ASSERT_TRUE(response != nullptr);
EXPECT_TRUE(response->headers->IsKeepAlive());
EXPECT_EQ(200, response->headers->response_code());
EXPECT_TRUE(HttpVersion(1, 1) == response->headers->GetHttpVersion());
std::string response_data;
rv = ReadTransaction(trans.get(), &response_data);
EXPECT_EQ(OK, rv);
EXPECT_EQ("hello!", response_data);
trans.reset();
session->CloseAllConnections();
}
// Test HTTPS connections to a site with a bad certificate, going through an
// HTTPS proxy
TEST_P(HttpNetworkTransactionTest, HTTPSBadCertificateViaHttpsProxy) {
session_deps_.proxy_service = ProxyService::CreateFixed("https://proxy:70");
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("https://www.example.org/");
request.load_flags = 0;
// Attempt to fetch the URL from a server with a bad cert
MockWrite bad_cert_writes[] = {
MockWrite("CONNECT www.example.org:443 HTTP/1.1\r\n"
"Host: www.example.org:443\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.example.org:443 HTTP/1.1\r\n"
"Host: www.example.org:443\r\n"
"Proxy-Connection: keep-alive\r\n\r\n"),
MockWrite("GET / HTTP/1.1\r\n"
"Host: www.example.org\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<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
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_P(HttpNetworkTransactionTest, BuildRequest_UserAgent) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.example.org/");
request.extra_headers.SetHeader(HttpRequestHeaders::kUserAgent,
"Chromium Ultra Awesome X Edition");
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
MockWrite data_writes[] = {
MockWrite(
"GET / HTTP/1.1\r\n"
"Host: www.example.org\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_P(HttpNetworkTransactionTest, BuildRequest_UserAgentOverTunnel) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("https://www.example.org/");
request.extra_headers.SetHeader(HttpRequestHeaders::kUserAgent,
"Chromium Ultra Awesome X Edition");
session_deps_.proxy_service = ProxyService::CreateFixed("myproxy:70");
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
MockWrite data_writes[] = {
MockWrite("CONNECT www.example.org:443 HTTP/1.1\r\n"
"Host: www.example.org:443\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_P(HttpNetworkTransactionTest, BuildRequest_Referer) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.example.org/");
request.load_flags = 0;
request.extra_headers.SetHeader(HttpRequestHeaders::kReferer,
"http://the.previous.site.com/");
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
MockWrite data_writes[] = {
MockWrite(
"GET / HTTP/1.1\r\n"
"Host: www.example.org\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_P(HttpNetworkTransactionTest, BuildRequest_PostContentLengthZero) {
HttpRequestInfo request;
request.method = "POST";
request.url = GURL("http://www.example.org/");
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
MockWrite data_writes[] = {
MockWrite(
"POST / HTTP/1.1\r\n"
"Host: www.example.org\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_P(HttpNetworkTransactionTest, BuildRequest_PutContentLengthZero) {
HttpRequestInfo request;
request.method = "PUT";
request.url = GURL("http://www.example.org/");
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
MockWrite data_writes[] = {
MockWrite(
"PUT / HTTP/1.1\r\n"
"Host: www.example.org\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_P(HttpNetworkTransactionTest, BuildRequest_HeadContentLengthZero) {
HttpRequestInfo request;
request.method = "HEAD";
request.url = GURL("http://www.example.org/");
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
MockWrite data_writes[] = {
MockWrite("HEAD / HTTP/1.1\r\n"
"Host: www.example.org\r\n"
"Connection: keep-alive\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_P(HttpNetworkTransactionTest, BuildRequest_CacheControlNoCache) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.example.org/");
request.load_flags = LOAD_BYPASS_CACHE;
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
MockWrite data_writes[] = {
MockWrite(
"GET / HTTP/1.1\r\n"
"Host: www.example.org\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_P(HttpNetworkTransactionTest,
BuildRequest_CacheControlValidateCache) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.example.org/");
request.load_flags = LOAD_VALIDATE_CACHE;
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
MockWrite data_writes[] = {
MockWrite(
"GET / HTTP/1.1\r\n"
"Host: www.example.org\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_P(HttpNetworkTransactionTest, BuildRequest_ExtraHeaders) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.example.org/");
request.extra_headers.SetHeader("FooHeader", "Bar");
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
MockWrite data_writes[] = {
MockWrite(
"GET / HTTP/1.1\r\n"
"Host: www.example.org\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_P(HttpNetworkTransactionTest, BuildRequest_ExtraHeadersStripped) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.example.org/");
request.extra_headers.SetHeader("referer", "www.foo.com");
request.extra_headers.SetHeader("hEllo", "Kitty");
request.extra_headers.SetHeader("FoO", "bar");
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
MockWrite data_writes[] = {
MockWrite(
"GET / HTTP/1.1\r\n"
"Host: www.example.org\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);
}
TEST_P(HttpNetworkTransactionTest, SOCKS4_HTTP_GET) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.example.org/");
request.load_flags = 0;
session_deps_.proxy_service =
ProxyService::CreateFixedFromPacResult("SOCKS myproxy:1080");
TestNetLog net_log;
session_deps_.net_log = &net_log;
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
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.example.org\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);
LoadTimingInfo load_timing_info;
EXPECT_TRUE(trans->GetLoadTimingInfo(&load_timing_info));
TestLoadTimingNotReusedWithPac(load_timing_info,
CONNECT_TIMING_HAS_CONNECT_TIMES_ONLY);
std::string response_text;
rv = ReadTransaction(trans.get(), &response_text);
EXPECT_EQ(OK, rv);
EXPECT_EQ("Payload", response_text);
}
TEST_P(HttpNetworkTransactionTest, SOCKS4_SSL_GET) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("https://www.example.org/");
request.load_flags = 0;
session_deps_.proxy_service =
ProxyService::CreateFixedFromPacResult("SOCKS myproxy:1080");
TestNetLog net_log;
session_deps_.net_log = &net_log;
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
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.example.org\r\n"
"Connection: keep-alive\r\n\r\n")};
MockRead data_reads[] = {
MockRead(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);
LoadTimingInfo load_timing_info;
EXPECT_TRUE(trans->GetLoadTimingInfo(&load_timing_info));
TestLoadTimingNotReusedWithPac(load_timing_info,
CONNECT_TIMING_HAS_SSL_TIMES);
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_P(HttpNetworkTransactionTest, SOCKS4_HTTP_GET_no_PAC) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.example.org/");
request.load_flags = 0;
session_deps_.proxy_service =
ProxyService::CreateFixed("socks4://myproxy:1080");
TestNetLog net_log;
session_deps_.net_log = &net_log;
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
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.example.org\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);
LoadTimingInfo load_timing_info;
EXPECT_TRUE(trans->GetLoadTimingInfo(&load_timing_info));
TestLoadTimingNotReused(load_timing_info,
CONNECT_TIMING_HAS_CONNECT_TIMES_ONLY);
std::string response_text;
rv = ReadTransaction(trans.get(), &response_text);
EXPECT_EQ(OK, rv);
EXPECT_EQ("Payload", response_text);
}
TEST_P(HttpNetworkTransactionTest, SOCKS5_HTTP_GET) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.example.org/");
request.load_flags = 0;
session_deps_.proxy_service =
ProxyService::CreateFixedFromPacResult("SOCKS5 myproxy:1080");
TestNetLog net_log;
session_deps_.net_log = &net_log;
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
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).
0x0F, // Length of domain (15)
'w', 'w', 'w', '.', 'e', 'x', 'a', 'm', 'p', 'l', 'e', // Domain string
'.', 'o', 'r', 'g', 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.example.org\r\n"
"Connection: keep-alive\r\n\r\n")};
MockRead data_reads[] = {
MockRead(ASYNC, kSOCKS5GreetResponse, arraysize(kSOCKS5GreetResponse)),
MockRead(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);
LoadTimingInfo load_timing_info;
EXPECT_TRUE(trans->GetLoadTimingInfo(&load_timing_info));
TestLoadTimingNotReusedWithPac(load_timing_info,
CONNECT_TIMING_HAS_CONNECT_TIMES_ONLY);
std::string response_text;
rv = ReadTransaction(trans.get(), &response_text);
EXPECT_EQ(OK, rv);
EXPECT_EQ("Payload", response_text);
}
TEST_P(HttpNetworkTransactionTest, SOCKS5_SSL_GET) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("https://www.example.org/");
request.load_flags = 0;
session_deps_.proxy_service =
ProxyService::CreateFixedFromPacResult("SOCKS5 myproxy:1080");
TestNetLog net_log;
session_deps_.net_log = &net_log;
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
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).
0x0F, // Length of domain (15)
'w', 'w', 'w', '.', 'e', 'x', 'a', 'm', 'p', 'l', 'e', // Domain string
'.', 'o', 'r', 'g', 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.example.org\r\n"
"Connection: keep-alive\r\n\r\n")};
MockRead data_reads[] = {
MockRead(ASYNC, kSOCKS5GreetResponse, arraysize(kSOCKS5GreetResponse)),
MockRead(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);
LoadTimingInfo load_timing_info;
EXPECT_TRUE(trans->GetLoadTimingInfo(&load_timing_info));
TestLoadTimingNotReusedWithPac(load_timing_info,
CONNECT_TIMING_HAS_SSL_TIMES);
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_ptr<HttpNetworkSession> SetupSessionForGroupNameTests(
NextProto next_proto,
SpdySessionDependencies* session_deps_) {
scoped_ptr<HttpNetworkSession> session(CreateSession(session_deps_));
base::WeakPtr<HttpServerProperties> http_server_properties =
session->http_server_properties();
AlternativeService alternative_service(
AlternateProtocolFromNextProto(next_proto), "", 443);
base::Time expiration = base::Time::Now() + base::TimeDelta::FromDays(1);
http_server_properties->SetAlternativeService(
HostPortPair("host.with.alternate", 80), alternative_service, expiration);
return session;
}
int GroupNameTransactionHelper(const std::string& url,
HttpNetworkSession* session) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL(url);
request.load_flags = 0;
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, 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_P(HttpNetworkTransactionTest, GroupNameForDirectConnections) {
const GroupNameTest tests[] = {
{
"", // unused
"http://www.example.org/direct",
"www.example.org:80",
false,
},
{
"", // unused
"http://[2001:1418:13:1::25]/direct",
"[2001:1418:13:1::25]:80",
false,
},
// SSL Tests
{
"", // unused
"https://www.example.org/direct_ssl",
"ssl/www.example.org: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,
},
};
session_deps_.parse_alternative_services = true;
session_deps_.enable_alternative_service_with_different_host = false;
for (size_t i = 0; i < arraysize(tests); ++i) {
session_deps_.proxy_service =
ProxyService::CreateFixed(tests[i].proxy_server);
scoped_ptr<HttpNetworkSession> session(
SetupSessionForGroupNameTests(GetProtocol(), &session_deps_));
HttpNetworkSessionPeer peer(session.get());
CaptureGroupNameTransportSocketPool* transport_conn_pool =
new CaptureGroupNameTransportSocketPool(NULL, NULL);
CaptureGroupNameSSLSocketPool* ssl_conn_pool =
new CaptureGroupNameSSLSocketPool(NULL, NULL);
scoped_ptr<MockClientSocketPoolManager> mock_pool_manager(
new MockClientSocketPoolManager);
mock_pool_manager->SetTransportSocketPool(transport_conn_pool);
mock_pool_manager->SetSSLSocketPool(ssl_conn_pool);
peer.SetClientSocketPoolManager(std::move(mock_pool_manager));
EXPECT_EQ(ERR_IO_PENDING,
GroupNameTransactionHelper(tests[i].url, session.get()));
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());
}
}
TEST_P(HttpNetworkTransactionTest, GroupNameForHTTPProxyConnections) {
const GroupNameTest tests[] = {
{
"http_proxy",
"http://www.example.org/http_proxy_normal",
"www.example.org:80",
false,
},
// SSL Tests
{
"http_proxy",
"https://www.example.org/http_connect_ssl",
"ssl/www.example.org:443",
true,
},
{
"http_proxy",
"http://host.with.alternate/direct",
"ssl/host.with.alternate:443",
true,
},
{
"http_proxy",
"ftp://ftp.google.com/http_proxy_normal",
"ftp/ftp.google.com:21",
false,
},
};
session_deps_.parse_alternative_services = true;
session_deps_.enable_alternative_service_with_different_host = false;
for (size_t i = 0; i < arraysize(tests); ++i) {
session_deps_.proxy_service =
ProxyService::CreateFixed(tests[i].proxy_server);
scoped_ptr<HttpNetworkSession> session(
SetupSessionForGroupNameTests(GetProtocol(), &session_deps_));
HttpNetworkSessionPeer peer(session.get());
HostPortPair proxy_host("http_proxy", 80);
CaptureGroupNameHttpProxySocketPool* http_proxy_pool =
new CaptureGroupNameHttpProxySocketPool(NULL, NULL);
CaptureGroupNameSSLSocketPool* ssl_conn_pool =
new CaptureGroupNameSSLSocketPool(NULL, NULL);
scoped_ptr<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(std::move(mock_pool_manager));
EXPECT_EQ(ERR_IO_PENDING,
GroupNameTransactionHelper(tests[i].url, session.get()));
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());
}
}
TEST_P(HttpNetworkTransactionTest, GroupNameForSOCKSConnections) {
const GroupNameTest tests[] = {
{
"socks4://socks_proxy:1080",
"http://www.example.org/socks4_direct",
"socks4/www.example.org:80",
false,
},
{
"socks5://socks_proxy:1080",
"http://www.example.org/socks5_direct",
"socks5/www.example.org:80",
false,
},
// SSL Tests
{
"socks4://socks_proxy:1080",
"https://www.example.org/socks4_ssl",
"socks4/ssl/www.example.org:443",
true,
},
{
"socks5://socks_proxy:1080",
"https://www.example.org/socks5_ssl",
"socks5/ssl/www.example.org:443",
true,
},
{
"socks4://socks_proxy:1080",
"http://host.with.alternate/direct",
"socks4/ssl/host.with.alternate:443",
true,
},
};
session_deps_.parse_alternative_services = true;
session_deps_.enable_alternative_service_with_different_host = false;
for (size_t i = 0; i < arraysize(tests); ++i) {
session_deps_.proxy_service =
ProxyService::CreateFixed(tests[i].proxy_server);
scoped_ptr<HttpNetworkSession> session(
SetupSessionForGroupNameTests(GetProtocol(), &session_deps_));
HttpNetworkSessionPeer peer(session.get());
HostPortPair proxy_host("socks_proxy", 1080);
CaptureGroupNameSOCKSSocketPool* socks_conn_pool =
new CaptureGroupNameSOCKSSocketPool(NULL, NULL);
CaptureGroupNameSSLSocketPool* ssl_conn_pool =
new CaptureGroupNameSSLSocketPool(NULL, NULL);
scoped_ptr<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(std::move(mock_pool_manager));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
EXPECT_EQ(ERR_IO_PENDING,
GroupNameTransactionHelper(tests[i].url, session.get()));
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());
}
}
TEST_P(HttpNetworkTransactionTest, ReconsiderProxyAfterFailedConnection) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.example.org/");
session_deps_.proxy_service =
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<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
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);
}
// 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 HttpNetworkTransactionTest::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.example.org/");
// Select a host resolver that does caching.
session_deps_.host_resolver.reset(new MockCachingHostResolver);
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
// Warm up the host cache so it has an entry for "www.example.org".
AddressList addrlist;
TestCompletionCallback callback;
int rv = session_deps_.host_resolver->Resolve(
HostResolver::RequestInfo(HostPortPair("www.example.org", 80)),
DEFAULT_PRIORITY, &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.example.org", 80)),
DEFAULT_PRIORITY, &addrlist, callback.callback(), NULL, BoundNetLog());
ASSERT_EQ(OK, rv);
// Inject a failure the next time that "www.example.org" 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.example.org");
// 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.example.org".
EXPECT_EQ(ERR_NAME_NOT_RESOLVED, rv);
}
// There are multiple load flags that should trigger the host cache bypass.
// Test each in isolation:
TEST_P(HttpNetworkTransactionTest, BypassHostCacheOnRefresh1) {
BypassHostCacheOnRefreshHelper(LOAD_BYPASS_CACHE);
}
TEST_P(HttpNetworkTransactionTest, BypassHostCacheOnRefresh2) {
BypassHostCacheOnRefreshHelper(LOAD_VALIDATE_CACHE);
}
TEST_P(HttpNetworkTransactionTest, BypassHostCacheOnRefresh3) {
BypassHostCacheOnRefreshHelper(LOAD_DISABLE_CACHE);
}
// Make sure we can handle an error when writing the request.
TEST_P(HttpNetworkTransactionTest, RequestWriteError) {
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);
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
TestCompletionCallback callback;
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
int rv = trans->Start(&request, callback.callback(), BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback.WaitForResult();
EXPECT_EQ(ERR_CONNECTION_RESET, rv);
IPEndPoint endpoint;
EXPECT_TRUE(trans->GetRemoteEndpoint(&endpoint));
EXPECT_LT(0u, endpoint.address().size());
}
// Check that a connection closed after the start of the headers finishes ok.
TEST_P(HttpNetworkTransactionTest, ConnectionClosedAfterStartOfHeaders) {
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);
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
TestCompletionCallback callback;
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
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.get() != 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);
IPEndPoint endpoint;
EXPECT_TRUE(trans->GetRemoteEndpoint(&endpoint));
EXPECT_LT(0u, endpoint.address().size());
}
// Make sure that a dropped connection while draining the body for auth
// restart does the right thing.
TEST_P(HttpNetworkTransactionTest, DrainResetOK) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.example.org/");
request.load_flags = 0;
MockWrite data_writes1[] = {
MockWrite(
"GET / HTTP/1.1\r\n"
"Host: www.example.org\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.example.org\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);
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
TestCompletionCallback callback1;
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
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_P(HttpNetworkTransactionTest, HTTPSViaProxyWithExtraData) {
session_deps_.proxy_service = ProxyService::CreateFixed("myproxy:70");
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("https://www.example.org/");
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<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
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_P(HttpNetworkTransactionTest, LargeContentLengthThenClose) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.example.org/");
request.load_flags = 0;
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
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.get() != NULL);
EXPECT_EQ("HTTP/1.0 200 OK", response->headers->GetStatusLine());
std::string response_data;
rv = ReadTransaction(trans.get(), &response_data);
EXPECT_EQ(ERR_CONTENT_LENGTH_MISMATCH, rv);
}
TEST_P(HttpNetworkTransactionTest, UploadFileSmallerThanLength) {
base::FilePath temp_file_path;
ASSERT_TRUE(base::CreateTemporaryFile(&temp_file_path));
const uint64_t kFakeSize = 100000; // file is actually blank
UploadFileElementReader::ScopedOverridingContentLengthForTests
overriding_content_length(kFakeSize);
std::vector<scoped_ptr<UploadElementReader>> element_readers;
element_readers.push_back(make_scoped_ptr(new UploadFileElementReader(
base::ThreadTaskRunnerHandle::Get().get(), temp_file_path, 0,
std::numeric_limits<uint64_t>::max(), base::Time())));
ElementsUploadDataStream upload_data_stream(std::move(element_readers), 0);
HttpRequestInfo request;
request.method = "POST";
request.url = GURL("http://www.example.org/upload");
request.upload_data_stream = &upload_data_stream;
request.load_flags = 0;
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
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.get() != 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);
base::DeleteFile(temp_file_path, false);
}
TEST_P(HttpNetworkTransactionTest, UploadUnreadableFile) {
base::FilePath temp_file;
ASSERT_TRUE(base::CreateTemporaryFile(&temp_file));
std::string temp_file_content("Unreadable file.");
ASSERT_TRUE(base::WriteFile(temp_file, temp_file_content.c_str(),
temp_file_content.length()));
ASSERT_TRUE(base::MakeFileUnreadable(temp_file));
std::vector<scoped_ptr<UploadElementReader>> element_readers;
element_readers.push_back(make_scoped_ptr(new UploadFileElementReader(
base::ThreadTaskRunnerHandle::Get().get(), temp_file, 0,
std::numeric_limits<uint64_t>::max(), base::Time())));
ElementsUploadDataStream upload_data_stream(std::move(element_readers), 0);
HttpRequestInfo request;
request.method = "POST";
request.url = GURL("http://www.example.org/upload");
request.upload_data_stream = &upload_data_stream;
request.load_flags = 0;
// If we try to upload an unreadable file, the transaction should fail.
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
StaticSocketDataProvider data(NULL, 0, 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_ACCESS_DENIED, rv);
base::DeleteFile(temp_file, false);
}
TEST_P(HttpNetworkTransactionTest, CancelDuringInitRequestBody) {
class FakeUploadElementReader : public UploadElementReader {
public:
FakeUploadElementReader() {}
~FakeUploadElementReader() override {}
const CompletionCallback& callback() const { return callback_; }
// UploadElementReader overrides:
int Init(const CompletionCallback& callback) override {
callback_ = callback;
return ERR_IO_PENDING;
}
uint64_t GetContentLength() const override { return 0; }
uint64_t BytesRemaining() const override { return 0; }
int Read(IOBuffer* buf,
int buf_length,
const CompletionCallback& callback) override {
return ERR_FAILED;
}
private:
CompletionCallback callback_;
};
FakeUploadElementReader* fake_reader = new FakeUploadElementReader;
std::vector<scoped_ptr<UploadElementReader>> element_readers;
element_readers.push_back(make_scoped_ptr(fake_reader));
ElementsUploadDataStream upload_data_stream(std::move(element_readers), 0);
HttpRequestInfo request;
request.method = "POST";
request.url = GURL("http://www.example.org/upload");
request.upload_data_stream = &upload_data_stream;
request.load_flags = 0;
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
StaticSocketDataProvider data;
session_deps_.socket_factory->AddSocketDataProvider(&data);
TestCompletionCallback callback;
int rv = trans->Start(&request, callback.callback(), BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
base::MessageLoop::current()->RunUntilIdle();
// Transaction is pending on request body initialization.
ASSERT_FALSE(fake_reader->callback().is_null());
// Return Init()'s result after the transaction gets destroyed.
trans.reset();
fake_reader->callback().Run(OK); // Should not crash.
}
// Tests that changes to Auth realms are treated like auth rejections.
TEST_P(HttpNetworkTransactionTest, ChangeAuthRealms) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.example.org/");
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.example.org\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.example.org\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.example.org\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.example.org\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<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
// 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.example.org:80", challenge->challenger.ToString());
EXPECT_EQ("first_realm", challenge->realm);
EXPECT_EQ(kBasicAuthScheme, 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.example.org:80", challenge->challenger.ToString());
EXPECT_EQ("second_realm", challenge->realm);
EXPECT_EQ(kBasicAuthScheme, 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.example.org:80", challenge->challenger.ToString());
EXPECT_EQ("first_realm", challenge->realm);
EXPECT_EQ(kBasicAuthScheme, 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_P(HttpNetworkTransactionTest, HonorAlternativeServiceHeader) {
session_deps_.parse_alternative_services = true;
session_deps_.enable_alternative_service_with_different_host = false;
std::string alternative_service_http_header =
GetAlternativeServiceHttpHeader();
MockRead data_reads[] = {
MockRead("HTTP/1.1 200 OK\r\n"),
MockRead(alternative_service_http_header.c_str()),
MockRead("\r\n"),
MockRead("hello world"),
MockRead(SYNCHRONOUS, OK),
};
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.example.org/");
request.load_flags = 0;
StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0);
session_deps_.socket_factory->AddSocketDataProvider(&data);
TestCompletionCallback callback;
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
int rv = trans->Start(&request, callback.callback(), BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
HostPortPair http_host_port_pair("www.example.org", 80);
HttpServerProperties& http_server_properties =
*session->http_server_properties();
AlternativeServiceVector alternative_service_vector =
http_server_properties.GetAlternativeServices(http_host_port_pair);
EXPECT_TRUE(alternative_service_vector.empty());
EXPECT_EQ(OK, callback.WaitForResult());
const HttpResponseInfo* response = trans->GetResponseInfo();
ASSERT_TRUE(response != NULL);
ASSERT_TRUE(response->headers.get() != 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);
alternative_service_vector =
http_server_properties.GetAlternativeServices(http_host_port_pair);
ASSERT_EQ(1u, alternative_service_vector.size());
EXPECT_EQ(AlternateProtocolFromNextProto(GetProtocol()),
alternative_service_vector[0].protocol);
EXPECT_EQ("www.example.com", alternative_service_vector[0].host);
EXPECT_EQ(443, alternative_service_vector[0].port);
}
TEST_P(HttpNetworkTransactionTest, ClearAlternativeServices) {
session_deps_.parse_alternative_services = true;
session_deps_.enable_alternative_service_with_different_host = false;
// Set an alternative service for origin.
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
HttpServerProperties& http_server_properties =
*session->http_server_properties();
HostPortPair http_host_port_pair("www.example.org", 80);
AlternativeService alternative_service(QUIC, "", 80);
base::Time expiration = base::Time::Now() + base::TimeDelta::FromDays(1);
http_server_properties.SetAlternativeService(http_host_port_pair,
alternative_service, expiration);
AlternativeServiceVector alternative_service_vector =
http_server_properties.GetAlternativeServices(http_host_port_pair);
EXPECT_EQ(1u, alternative_service_vector.size());
// Send a clear header.
MockRead data_reads[] = {
MockRead("HTTP/1.1 200 OK\r\n"),
MockRead("Alt-Svc: clear\r\n"),
MockRead("\r\n"),
MockRead("hello world"),
MockRead(SYNCHRONOUS, OK),
};
StaticSocketDataProvider data(data_reads, arraysize(data_reads), nullptr, 0);
session_deps_.socket_factory->AddSocketDataProvider(&data);
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.example.org/");
request.load_flags = 0;
TestCompletionCallback callback;
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
int rv = trans->Start(&request, callback.callback(), BoundNetLog());
EXPECT_EQ(OK, callback.GetResult(rv));
const HttpResponseInfo* response = trans->GetResponseInfo();
ASSERT_TRUE(response != nullptr);
ASSERT_TRUE(response->headers.get() != nullptr);
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);
alternative_service_vector =
http_server_properties.GetAlternativeServices(http_host_port_pair);
EXPECT_TRUE(alternative_service_vector.empty());
}
// Alternative Service headers must be ignored when
// |parse_alternative_services| is false.
TEST_P(HttpNetworkTransactionTest, DoNotHonorAlternativeServiceHeader) {
session_deps_.parse_alternative_services = false;
std::string alternative_service_http_header =
GetAlternativeServiceHttpHeader();
MockRead data_reads[] = {
MockRead("HTTP/1.1 200 OK\r\n"),
MockRead(alternative_service_http_header.c_str()),
MockRead("\r\n"),
MockRead("hello world"),
MockRead(SYNCHRONOUS, OK),
};
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.example.org/");
request.load_flags = 0;
StaticSocketDataProvider data(data_reads, arraysize(data_reads), nullptr, 0);
session_deps_.socket_factory->AddSocketDataProvider(&data);
TestCompletionCallback callback;
scoped_ptr<HttpNetworkSession> session = CreateSession(&session_deps_);
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
int rv = trans->Start(&request, callback.callback(), BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
HostPortPair http_host_port_pair("www.example.org", 80);
HttpServerProperties& http_server_properties =
*session->http_server_properties();
AlternativeServiceVector alternative_service_vector =
http_server_properties.GetAlternativeServices(http_host_port_pair);
EXPECT_TRUE(alternative_service_vector.empty());
EXPECT_EQ(OK, callback.WaitForResult());
const HttpResponseInfo* response = trans->GetResponseInfo();
ASSERT_TRUE(response != nullptr);
ASSERT_TRUE(response->headers.get() != nullptr);
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);
alternative_service_vector =
http_server_properties.GetAlternativeServices(http_host_port_pair);
EXPECT_TRUE(alternative_service_vector.empty());
}
TEST_P(HttpNetworkTransactionTest, HonorMultipleAlternativeServiceHeader) {
session_deps_.parse_alternative_services = true;
session_deps_.enable_alternative_service_with_different_host = false;
MockRead data_reads[] = {
MockRead("HTTP/1.1 200 OK\r\n"),
MockRead("Alt-Svc: "),
MockRead(GetAlternateProtocolFromParam()),
MockRead("=\"www.example.com:443\";p=\"1.0\","),
MockRead(GetAlternateProtocolFromParam()),
MockRead("=\":1234\"\r\n\r\n"),
MockRead("hello world"),
MockRead(SYNCHRONOUS, OK),
};
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.example.org/");
request.load_flags = 0;
StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0);
session_deps_.socket_factory->AddSocketDataProvider(&data);
TestCompletionCallback callback;
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
int rv = trans->Start(&request, callback.callback(), BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
HostPortPair http_host_port_pair("www.example.org", 80);
HttpServerProperties& http_server_properties =
*session->http_server_properties();
AlternativeServiceVector alternative_service_vector =
http_server_properties.GetAlternativeServices(http_host_port_pair);
EXPECT_TRUE(alternative_service_vector.empty());
EXPECT_EQ(OK, callback.WaitForResult());
const HttpResponseInfo* response = trans->GetResponseInfo();
ASSERT_TRUE(response != NULL);
ASSERT_TRUE(response->headers.get() != 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);
alternative_service_vector =
http_server_properties.GetAlternativeServices(http_host_port_pair);
ASSERT_EQ(2u, alternative_service_vector.size());
EXPECT_EQ(AlternateProtocolFromNextProto(GetProtocol()),
alternative_service_vector[0].protocol);
EXPECT_EQ("www.example.com", alternative_service_vector[0].host);
EXPECT_EQ(443, alternative_service_vector[0].port);
EXPECT_EQ(AlternateProtocolFromNextProto(GetProtocol()),
alternative_service_vector[1].protocol);
EXPECT_EQ("www.example.org", alternative_service_vector[1].host);
EXPECT_EQ(1234, alternative_service_vector[1].port);
}
// Alternate Protocol headers must be honored even if
// |parse_alternative_services| is false.
TEST_P(HttpNetworkTransactionTest, HonorAlternateProtocolHeader) {
session_deps_.parse_alternative_services = false;
std::string alternate_protocol_http_header =
GetAlternateProtocolHttpHeader();
MockRead data_reads[] = {
MockRead("HTTP/1.1 200 OK\r\n"),
MockRead(alternate_protocol_http_header.c_str()),
MockRead("\r\n"),
MockRead("hello world"),
MockRead(SYNCHRONOUS, OK),
};
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.example.org/");
request.load_flags = 0;
StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0);
session_deps_.socket_factory->AddSocketDataProvider(&data);
TestCompletionCallback callback;
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
int rv = trans->Start(&request, callback.callback(), BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
HostPortPair http_host_port_pair("www.example.org", 80);
HttpServerProperties& http_server_properties =
*session->http_server_properties();
AlternativeServiceVector alternative_service_vector =
http_server_properties.GetAlternativeServices(http_host_port_pair);
EXPECT_TRUE(alternative_service_vector.empty());
EXPECT_EQ(OK, callback.WaitForResult());
const HttpResponseInfo* response = trans->GetResponseInfo();
ASSERT_TRUE(response != NULL);
ASSERT_TRUE(response->headers.get() != 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);
alternative_service_vector =
http_server_properties.GetAlternativeServices(http_host_port_pair);
ASSERT_EQ(1u, alternative_service_vector.size());
EXPECT_EQ(443, alternative_service_vector[0].port);
EXPECT_EQ(AlternateProtocolFromNextProto(GetProtocol()),
alternative_service_vector[0].protocol);
}
TEST_P(HttpNetworkTransactionTest, EmptyAlternateProtocolHeader) {
session_deps_.parse_alternative_services = false;
session_deps_.enable_alternative_service_with_different_host = false;
MockRead data_reads[] = {
MockRead("HTTP/1.1 200 OK\r\n"),
MockRead("Alternate-Protocol: \r\n\r\n"),
MockRead("hello world"),
MockRead(SYNCHRONOUS, OK),
};
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.example.org/");
request.load_flags = 0;
StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0);
session_deps_.socket_factory->AddSocketDataProvider(&data);
TestCompletionCallback callback;
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
HostPortPair http_host_port_pair("www.example.org", 80);
HttpServerProperties& http_server_properties =
*session->http_server_properties();
AlternativeService alternative_service(QUIC, "", 80);
base::Time expiration = base::Time::Now() + base::TimeDelta::FromDays(1);
http_server_properties.SetAlternativeService(http_host_port_pair,
alternative_service, expiration);
AlternativeServiceVector alternative_service_vector =
http_server_properties.GetAlternativeServices(http_host_port_pair);
ASSERT_EQ(1u, alternative_service_vector.size());
EXPECT_EQ(QUIC, alternative_service_vector[0].protocol);
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
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.get() != 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);
alternative_service_vector =
http_server_properties.GetAlternativeServices(http_host_port_pair);
EXPECT_TRUE(alternative_service_vector.empty());
}
// When |session_deps_.parse_alternative_services = true| and the response has
// an Alt-Svc header, then the Alternate-Protocol header is not parsed.
TEST_P(HttpNetworkTransactionTest, AltSvcOverwritesAlternateProtocol) {
session_deps_.parse_alternative_services = true;
session_deps_.enable_alternative_service_with_different_host = false;
std::string alternative_service_http_header =
GetAlternativeServiceHttpHeader();
std::string alternate_protocol_http_header = GetAlternateProtocolHttpHeader();
MockRead data_reads[] = {
MockRead("HTTP/1.1 200 OK\r\n"),
MockRead(alternative_service_http_header.c_str()),
MockRead(alternate_protocol_http_header.c_str()),
MockRead("\r\n"),
MockRead("hello world"),
MockRead(SYNCHRONOUS, OK),
};
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.example.org/");
request.load_flags = 0;
StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0);
session_deps_.socket_factory->AddSocketDataProvider(&data);
TestCompletionCallback callback;
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
int rv = trans->Start(&request, callback.callback(), BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
HostPortPair http_host_port_pair("www.example.org", 80);
HttpServerProperties& http_server_properties =
*session->http_server_properties();
AlternativeServiceVector alternative_service_vector =
http_server_properties.GetAlternativeServices(http_host_port_pair);
EXPECT_TRUE(alternative_service_vector.empty());
EXPECT_EQ(OK, callback.WaitForResult());
const HttpResponseInfo* response = trans->GetResponseInfo();
ASSERT_TRUE(response != NULL);
ASSERT_TRUE(response->headers.get() != 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);
alternative_service_vector =
http_server_properties.GetAlternativeServices(http_host_port_pair);
ASSERT_EQ(1u, alternative_service_vector.size());
EXPECT_EQ(AlternateProtocolFromNextProto(GetProtocol()),
alternative_service_vector[0].protocol);
EXPECT_EQ("www.example.com", alternative_service_vector[0].host);
EXPECT_EQ(443, alternative_service_vector[0].port);
}
// When |enable_alternative_service_with_different_host| is false, do not
// observe alternative service entries that point to a different host.
TEST_P(HttpNetworkTransactionTest, DisableAlternativeServiceToDifferentHost) {
session_deps_.parse_alternative_services = true;
session_deps_.enable_alternative_service_with_different_host = false;
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.example.org/");
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),
nullptr, 0);
session_deps_.socket_factory->AddSocketDataProvider(&second_data);
scoped_ptr<HttpNetworkSession> session = CreateSession(&session_deps_);
base::WeakPtr<HttpServerProperties> http_server_properties =
session->http_server_properties();
AlternativeService alternative_service(
AlternateProtocolFromNextProto(GetProtocol()), "different.example.org",
80);
base::Time expiration = base::Time::Now() + base::TimeDelta::FromDays(1);
http_server_properties->SetAlternativeService(
HostPortPair::FromURL(request.url), alternative_service, expiration);
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
TestCompletionCallback callback;
int rv = trans->Start(&request, callback.callback(), BoundNetLog());
// The connetion to origin was refused, and the alternative service should not
// be used (even though mock data are there), therefore the request should
// fail.
EXPECT_EQ(ERR_CONNECTION_REFUSED, callback.GetResult(rv));
}
TEST_P(HttpNetworkTransactionTest, IdentifyQuicBroken) {
HostPortPair origin("origin.example.org", 443);
HostPortPair alternative("alternative.example.org", 443);
std::string origin_url = "https://origin.example.org:443";
std::string alternative_url = "https://alternative.example.org:443";
// Negotiate HTTP/1.1 with alternative.example.org.
SSLSocketDataProvider ssl(ASYNC, OK);
ssl.SetNextProto(kProtoHTTP11);
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
// HTTP/1.1 data for request.
MockWrite http_writes[] = {
MockWrite("GET / HTTP/1.1\r\n"
"Host: alternative.example.org\r\n"
"Connection: keep-alive\r\n\r\n"),
};
MockRead http_reads[] = {
MockRead("HTTP/1.1 200 OK\r\n"
"Content-Type: text/html; charset=iso-8859-1\r\n"
"Content-Length: 40\r\n\r\n"
"first HTTP/1.1 response from alternative"),
};
StaticSocketDataProvider http_data(http_reads, arraysize(http_reads),
http_writes, arraysize(http_writes));
session_deps_.socket_factory->AddSocketDataProvider(&http_data);
StaticSocketDataProvider data_refused;
data_refused.set_connect_data(MockConnect(ASYNC, ERR_CONNECTION_REFUSED));
session_deps_.socket_factory->AddSocketDataProvider(&data_refused);
// Set up a QUIC alternative service for origin.
session_deps_.parse_alternative_services = true;
session_deps_.enable_alternative_service_with_different_host = false;
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
base::WeakPtr<HttpServerProperties> http_server_properties =
session->http_server_properties();
AlternativeService alternative_service(QUIC, alternative);
base::Time expiration = base::Time::Now() + base::TimeDelta::FromDays(1);
http_server_properties->SetAlternativeService(origin, alternative_service,
expiration);
// Mark the QUIC alternative service as broken.
http_server_properties->MarkAlternativeServiceBroken(alternative_service);
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
HttpRequestInfo request;
request.method = "GET";
request.url = GURL(origin_url);
request.load_flags = 0;
TestCompletionCallback callback;
NetErrorDetails details;
EXPECT_FALSE(details.quic_broken);
trans->Start(&request, callback.callback(), BoundNetLog());
trans->PopulateNetErrorDetails(&details);
EXPECT_TRUE(details.quic_broken);
}
TEST_P(HttpNetworkTransactionTest, IdentifyQuicNotBroken) {
HostPortPair origin("origin.example.org", 443);
HostPortPair alternative1("alternative1.example.org", 443);
HostPortPair alternative2("alternative2.example.org", 443);
std::string origin_url = "https://origin.example.org:443";
std::string alternative_url1 = "https://alternative1.example.org:443";
std::string alternative_url2 = "https://alternative2.example.org:443";
// Negotiate HTTP/1.1 with alternative1.example.org.
SSLSocketDataProvider ssl(ASYNC, OK);
ssl.SetNextProto(kProtoHTTP11);
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
// HTTP/1.1 data for request.
MockWrite http_writes[] = {
MockWrite("GET / HTTP/1.1\r\n"
"Host: alternative1.example.org\r\n"
"Connection: keep-alive\r\n\r\n"),
};
MockRead http_reads[] = {
MockRead("HTTP/1.1 200 OK\r\n"
"Content-Type: text/html; charset=iso-8859-1\r\n"
"Content-Length: 40\r\n\r\n"
"first HTTP/1.1 response from alternative1"),
};
StaticSocketDataProvider http_data(http_reads, arraysize(http_reads),
http_writes, arraysize(http_writes));
session_deps_.socket_factory->AddSocketDataProvider(&http_data);
StaticSocketDataProvider data_refused;
data_refused.set_connect_data(MockConnect(ASYNC, ERR_CONNECTION_REFUSED));
session_deps_.socket_factory->AddSocketDataProvider(&data_refused);
session_deps_.parse_alternative_services = true;
session_deps_.enable_alternative_service_with_different_host = true;
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
base::WeakPtr<HttpServerProperties> http_server_properties =
session->http_server_properties();
// Set up two QUIC alternative services for origin.
AlternativeServiceInfoVector alternative_service_info_vector;
base::Time expiration = base::Time::Now() + base::TimeDelta::FromDays(1);
AlternativeService alternative_service1(QUIC, alternative1);
AlternativeServiceInfo alternative_service_info1(alternative_service1,
expiration);
alternative_service_info_vector.push_back(alternative_service_info1);
AlternativeService alternative_service2(QUIC, alternative2);
AlternativeServiceInfo alternative_service_info2(alternative_service2,
expiration);
alternative_service_info_vector.push_back(alternative_service_info2);
http_server_properties->SetAlternativeServices(
origin, alternative_service_info_vector);
// Mark one of the QUIC alternative service as broken.
http_server_properties->MarkAlternativeServiceBroken(alternative_service1);
const AlternativeServiceVector alternative_service_vector =
http_server_properties->GetAlternativeServices(origin);
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
HttpRequestInfo request;
request.method = "GET";
request.url = GURL(origin_url);
request.load_flags = 0;
TestCompletionCallback callback;
NetErrorDetails details;
EXPECT_FALSE(details.quic_broken);
trans->Start(&request, callback.callback(), BoundNetLog());
trans->PopulateNetErrorDetails(&details);
EXPECT_FALSE(details.quic_broken);
}
TEST_P(HttpNetworkTransactionTest,
MarkBrokenAlternateProtocolAndFallback) {
session_deps_.parse_alternative_services = true;
session_deps_.enable_alternative_service_with_different_host = false;
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.example.org/");
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_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
base::WeakPtr<HttpServerProperties> http_server_properties =
session->http_server_properties();
const HostPortPair host_port_pair = HostPortPair::FromURL(request.url);
// Port must be < 1024, or the header will be ignored (since initial port was
// port 80 (another restricted port).
const AlternativeService alternative_service(
AlternateProtocolFromNextProto(GetProtocol()), "www.example.org",
666); // Port is ignored by MockConnect anyway.
base::Time expiration = base::Time::Now() + base::TimeDelta::FromDays(1);
http_server_properties->SetAlternativeService(
host_port_pair, alternative_service, expiration);
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
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.get() != 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);
const AlternativeServiceVector alternative_service_vector =
http_server_properties->GetAlternativeServices(host_port_pair);
ASSERT_EQ(1u, alternative_service_vector.size());
EXPECT_EQ(alternative_service, alternative_service_vector[0]);
EXPECT_TRUE(http_server_properties->IsAlternativeServiceBroken(
alternative_service_vector[0]));
}
// Ensure that we are 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.
TEST_P(HttpNetworkTransactionTest,
AlternateProtocolPortRestrictedBlocked) {
session_deps_.parse_alternative_services = true;
session_deps_.enable_alternative_service_with_different_host = false;
HttpRequestInfo restricted_port_request;
restricted_port_request.method = "GET";
restricted_port_request.url = GURL("http://www.example.org: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_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
base::WeakPtr<HttpServerProperties> http_server_properties =
session->http_server_properties();
const int kUnrestrictedAlternatePort = 1024;
AlternativeService alternative_service(
AlternateProtocolFromNextProto(GetProtocol()), "www.example.org",
kUnrestrictedAlternatePort);
base::Time expiration = base::Time::Now() + base::TimeDelta::FromDays(1);
http_server_properties->SetAlternativeService(
HostPortPair::FromURL(restricted_port_request.url), alternative_service,
expiration);
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
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());
}
// Ensure that we are allowed to redirect traffic via an alternate protocol to
// an unrestricted (port >= 1024) when the original traffic was on a restricted
// port (port < 1024) if we set |enable_user_alternate_protocol_ports|.
TEST_P(HttpNetworkTransactionTest,
AlternateProtocolPortRestrictedPermitted) {
session_deps_.parse_alternative_services = true;
session_deps_.enable_alternative_service_with_different_host = false;
session_deps_.enable_user_alternate_protocol_ports = true;
HttpRequestInfo restricted_port_request;
restricted_port_request.method = "GET";
restricted_port_request.url = GURL("http://www.example.org: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_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
base::WeakPtr<HttpServerProperties> http_server_properties =
session->http_server_properties();
const int kUnrestrictedAlternatePort = 1024;
AlternativeService alternative_service(
AlternateProtocolFromNextProto(GetProtocol()), "www.example.org",
kUnrestrictedAlternatePort);
base::Time expiration = base::Time::Now() + base::TimeDelta::FromDays(1);
http_server_properties->SetAlternativeService(
HostPortPair::FromURL(restricted_port_request.url), alternative_service,
expiration);
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
TestCompletionCallback callback;
EXPECT_EQ(ERR_IO_PENDING, trans->Start(
&restricted_port_request,
callback.callback(), BoundNetLog()));
// Change to unrestricted port should succeed.
EXPECT_EQ(OK, callback.WaitForResult());
}
// Ensure that we are 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.
TEST_P(HttpNetworkTransactionTest,
AlternateProtocolPortRestrictedAllowed) {
session_deps_.parse_alternative_services = true;
session_deps_.enable_alternative_service_with_different_host = false;
HttpRequestInfo restricted_port_request;
restricted_port_request.method = "GET";
restricted_port_request.url = GURL("http://www.example.org: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_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
base::WeakPtr<HttpServerProperties> http_server_properties =
session->http_server_properties();
const int kRestrictedAlternatePort = 80;
AlternativeService alternative_service(
AlternateProtocolFromNextProto(GetProtocol()), "www.example.org",
kRestrictedAlternatePort);
base::Time expiration = base::Time::Now() + base::TimeDelta::FromDays(1);
http_server_properties->SetAlternativeService(
HostPortPair::FromURL(restricted_port_request.url), alternative_service,
expiration);
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
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());
}
// Ensure that we are 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.
TEST_P(HttpNetworkTransactionTest,
AlternateProtocolPortUnrestrictedAllowed1) {
session_deps_.parse_alternative_services = true;
session_deps_.enable_alternative_service_with_different_host = false;
HttpRequestInfo unrestricted_port_request;
unrestricted_port_request.method = "GET";
unrestricted_port_request.url = GURL("http://www.example.org: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_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
base::WeakPtr<HttpServerProperties> http_server_properties =
session->http_server_properties();
const int kRestrictedAlternatePort = 80;
AlternativeService alternative_service(
AlternateProtocolFromNextProto(GetProtocol()), "www.example.org",
kRestrictedAlternatePort);
base::Time expiration = base::Time::Now() + base::TimeDelta::FromDays(1);
http_server_properties->SetAlternativeService(
HostPortPair::FromURL(unrestricted_port_request.url), alternative_service,
expiration);
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
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());
}
// Ensure that we are 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.
TEST_P(HttpNetworkTransactionTest,
AlternateProtocolPortUnrestrictedAllowed2) {
session_deps_.parse_alternative_services = true;
session_deps_.enable_alternative_service_with_different_host = false;
HttpRequestInfo unrestricted_port_request;
unrestricted_port_request.method = "GET";
unrestricted_port_request.url = GURL("http://www.example.org: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_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
base::WeakPtr<HttpServerProperties> http_server_properties =
session->http_server_properties();
const int kUnrestrictedAlternatePort = 1025;
AlternativeService alternative_service(
AlternateProtocolFromNextProto(GetProtocol()), "www.example.org",
kUnrestrictedAlternatePort);
base::Time expiration = base::Time::Now() + base::TimeDelta::FromDays(1);
http_server_properties->SetAlternativeService(
HostPortPair::FromURL(unrestricted_port_request.url), alternative_service,
expiration);
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
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());
}
// Ensure that we are 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.
TEST_P(HttpNetworkTransactionTest, AlternateProtocolUnsafeBlocked) {
session_deps_.parse_alternative_services = true;
session_deps_.enable_alternative_service_with_different_host = false;
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.example.org/");
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_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
base::WeakPtr<HttpServerProperties> http_server_properties =
session->http_server_properties();
const int kUnsafePort = 7;
AlternativeService alternative_service(
AlternateProtocolFromNextProto(GetProtocol()), "www.example.org",
kUnsafePort);
base::Time expiration = base::Time::Now() + base::TimeDelta::FromDays(1);
http_server_properties->SetAlternativeService(
HostPortPair::FromURL(request.url), alternative_service, expiration);
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
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());
const HttpResponseInfo* response = trans->GetResponseInfo();
ASSERT_TRUE(response != NULL);
ASSERT_TRUE(response->headers.get() != 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_P(HttpNetworkTransactionTest, UseAlternateProtocolForNpnSpdy) {
session_deps_.parse_alternative_services = false;
session_deps_.enable_alternative_service_with_different_host = false;
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.example.org/");
request.load_flags = 0;
std::string alternate_protocol_http_header =
GetAlternateProtocolHttpHeader();
MockRead data_reads[] = {
MockRead("HTTP/1.1 200 OK\r\n"),
MockRead(alternate_protocol_http_header.c_str()),
MockRead("\r\n"),
MockRead("hello world"),
MockRead(SYNCHRONOUS, ERR_TEST_PEER_CLOSE_AFTER_NEXT_MOCK_READ),
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(GetProtocol());
ssl.cert = ImportCertFromFile(GetTestCertsDirectory(), "spdy_pooling.pem");
ASSERT_TRUE(ssl.cert.get());
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
scoped_ptr<SpdyFrame> req(
spdy_util_.ConstructSpdyGet(nullptr, 0, 1, LOWEST, true));
MockWrite spdy_writes[] = {CreateMockWrite(*req, 0)};
scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
scoped_ptr<SpdyFrame> data(spdy_util_.ConstructSpdyBodyFrame(1, true));
MockRead spdy_reads[] = {
CreateMockRead(*resp, 1), CreateMockRead(*data, 2), MockRead(ASYNC, 0, 3),
};
SequencedSocketData spdy_data(spdy_reads, arraysize(spdy_reads), spdy_writes,
arraysize(spdy_writes));
session_deps_.socket_factory->AddSocketDataProvider(&spdy_data);
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_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
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.get() != 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(DEFAULT_PRIORITY, session.get()));
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.get() != NULL);
EXPECT_EQ("HTTP/1.1 200", 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);
}
TEST_P(HttpNetworkTransactionTest, AlternateProtocolWithSpdyLateBinding) {
session_deps_.parse_alternative_services = false;
session_deps_.enable_alternative_service_with_different_host = false;
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.example.org/");
request.load_flags = 0;
std::string alternate_protocol_http_header =
GetAlternateProtocolHttpHeader();
MockRead data_reads[] = {
MockRead("HTTP/1.1 200 OK\r\n"),
MockRead(alternate_protocol_http_header.c_str()),
MockRead("\r\n"),
MockRead("hello world"),
MockRead(SYNCHRONOUS, ERR_TEST_PEER_CLOSE_AFTER_NEXT_MOCK_READ),
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_socket1(NULL, 0, NULL, 0);
hanging_socket1.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_socket1);
StaticSocketDataProvider hanging_socket2(NULL, 0, NULL, 0);
hanging_socket2.set_connect_data(never_finishing_connect);
session_deps_.socket_factory->AddSocketDataProvider(&hanging_socket2);
SSLSocketDataProvider ssl(ASYNC, OK);
ssl.SetNextProto(GetProtocol());
ssl.cert = ImportCertFromFile(GetTestCertsDirectory(), "spdy_pooling.pem");
ASSERT_TRUE(ssl.cert.get());
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
scoped_ptr<SpdyFrame> req1(
spdy_util_.ConstructSpdyGet(nullptr, 0, 1, LOWEST, true));
scoped_ptr<SpdyFrame> req2(
spdy_util_.ConstructSpdyGet(nullptr, 0, 3, LOWEST, true));
MockWrite spdy_writes[] = {
CreateMockWrite(*req1, 0), CreateMockWrite(*req2, 1),
};
scoped_ptr<SpdyFrame> resp1(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
scoped_ptr<SpdyFrame> data1(spdy_util_.ConstructSpdyBodyFrame(1, true));
scoped_ptr<SpdyFrame> resp2(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 3));
scoped_ptr<SpdyFrame> data2(spdy_util_.ConstructSpdyBodyFrame(3, true));
MockRead spdy_reads[] = {
CreateMockRead(*resp1, 2),
CreateMockRead(*data1, 3),
CreateMockRead(*resp2, 4),
CreateMockRead(*data2, 5),
MockRead(ASYNC, 0, 6),
};
SequencedSocketData spdy_data(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);
// Socket 5 is the unsuccessful non-Alternate-Protocol for transaction 3.
StaticSocketDataProvider hanging_socket3(NULL, 0, NULL, 0);
hanging_socket3.set_connect_data(never_finishing_connect);
session_deps_.socket_factory->AddSocketDataProvider(&hanging_socket3);
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
TestCompletionCallback callback1;
HttpNetworkTransaction trans1(DEFAULT_PRIORITY, session.get());
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.get() != 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(DEFAULT_PRIORITY, session.get());
rv = trans2.Start(&request, callback2.callback(), BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
TestCompletionCallback callback3;
HttpNetworkTransaction trans3(DEFAULT_PRIORITY, session.get());
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.get() != NULL);
EXPECT_EQ("HTTP/1.1 200", 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.get() != NULL);
EXPECT_EQ("HTTP/1.1 200", 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);
}
TEST_P(HttpNetworkTransactionTest, StallAlternateProtocolForNpnSpdy) {
session_deps_.parse_alternative_services = false;
session_deps_.enable_alternative_service_with_different_host = false;
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.example.org/");
request.load_flags = 0;
std::string alternate_protocol_http_header =
GetAlternateProtocolHttpHeader();
MockRead data_reads[] = {
MockRead("HTTP/1.1 200 OK\r\n"),
MockRead(alternate_protocol_http_header.c_str()),
MockRead("\r\n"),
MockRead("hello world"),
MockRead(SYNCHRONOUS, ERR_TEST_PEER_CLOSE_AFTER_NEXT_MOCK_READ),
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(GetProtocol());
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.
StaticSocketDataProvider second_transaction(data_reads, arraysize(data_reads),
NULL, 0);
session_deps_.socket_factory->AddSocketDataProvider(&second_transaction);
TestCompletionCallback callback;
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
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.get() != 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(DEFAULT_PRIORITY, session.get()));
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.get() != 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);
}
class CapturingProxyResolver : public ProxyResolver {
public:
CapturingProxyResolver() {}
~CapturingProxyResolver() override {}
int GetProxyForURL(const GURL& url,
ProxyInfo* results,
const CompletionCallback& callback,
RequestHandle* request,
const BoundNetLog& net_log) override {
ProxyServer proxy_server(ProxyServer::SCHEME_HTTP,
HostPortPair("myproxy", 80));
results->UseProxyServer(proxy_server);
resolved_.push_back(url);
return OK;
}
void CancelRequest(RequestHandle request) override { NOTREACHED(); }
LoadState GetLoadState(RequestHandle request) const override {
NOTREACHED();
return LOAD_STATE_IDLE;
}
const std::vector<GURL>& resolved() const { return resolved_; }
private:
std::vector<GURL> resolved_;
DISALLOW_COPY_AND_ASSIGN(CapturingProxyResolver);
};
class CapturingProxyResolverFactory : public ProxyResolverFactory {
public:
explicit CapturingProxyResolverFactory(CapturingProxyResolver* resolver)
: ProxyResolverFactory(false), resolver_(resolver) {}
int CreateProxyResolver(
const scoped_refptr<ProxyResolverScriptData>& pac_script,
scoped_ptr<ProxyResolver>* resolver,
const net::CompletionCallback& callback,
scoped_ptr<Request>* request) override {
resolver->reset(new ForwardingProxyResolver(resolver_));
return OK;
}
private:
ProxyResolver* resolver_;
};
TEST_P(HttpNetworkTransactionTest,
UseAlternateProtocolForTunneledNpnSpdy) {
session_deps_.parse_alternative_services = false;
session_deps_.enable_alternative_service_with_different_host = false;
ProxyConfig proxy_config;
proxy_config.set_auto_detect(true);
proxy_config.set_pac_url(GURL("http://fooproxyurl"));
CapturingProxyResolver capturing_proxy_resolver;
session_deps_.proxy_service.reset(new ProxyService(
make_scoped_ptr(new ProxyConfigServiceFixed(proxy_config)),
make_scoped_ptr(
new CapturingProxyResolverFactory(&capturing_proxy_resolver)),
NULL));
TestNetLog net_log;
session_deps_.net_log = &net_log;
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.example.org/");
request.load_flags = 0;
std::string alternate_protocol_http_header =
GetAlternateProtocolHttpHeader();
MockRead data_reads[] = {
MockRead("HTTP/1.1 200 OK\r\n"),
MockRead(alternate_protocol_http_header.c_str()),
MockRead("\r\n"),
MockRead("hello world"),
MockRead(SYNCHRONOUS, ERR_TEST_PEER_CLOSE_AFTER_NEXT_MOCK_READ),
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(GetProtocol());
ssl.cert = ImportCertFromFile(GetTestCertsDirectory(), "spdy_pooling.pem");
ASSERT_TRUE(ssl.cert.get());
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
scoped_ptr<SpdyFrame> req(
spdy_util_.ConstructSpdyGet(nullptr, 0, 1, LOWEST, true));
MockWrite spdy_writes[] = {
MockWrite(ASYNC, 0,
"CONNECT www.example.org:443 HTTP/1.1\r\n"
"Host: www.example.org:443\r\n"
"Proxy-Connection: keep-alive\r\n\r\n"),
CreateMockWrite(*req, 2),
};
const char kCONNECTResponse[] = "HTTP/1.1 200 Connected\r\n\r\n";
scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
scoped_ptr<SpdyFrame> data(spdy_util_.ConstructSpdyBodyFrame(1, true));
MockRead spdy_reads[] = {
MockRead(ASYNC, 1, kCONNECTResponse), CreateMockRead(*resp.get(), 3),
CreateMockRead(*data.get(), 4), MockRead(SYNCHRONOUS, ERR_IO_PENDING, 5),
};
SequencedSocketData spdy_data(spdy_reads, arraysize(spdy_reads), spdy_writes,
arraysize(spdy_writes));
session_deps_.socket_factory->AddSocketDataProvider(&spdy_data);
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_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
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.get() != 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(DEFAULT_PRIORITY, session.get()));
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.get() != NULL);
EXPECT_EQ("HTTP/1.1 200", 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.example.org/",
capturing_proxy_resolver.resolved()[0].spec());
EXPECT_EQ("https://www.example.org/",
capturing_proxy_resolver.resolved()[1].spec());
LoadTimingInfo load_timing_info;
EXPECT_TRUE(trans->GetLoadTimingInfo(&load_timing_info));
TestLoadTimingNotReusedWithPac(load_timing_info,
CONNECT_TIMING_HAS_SSL_TIMES);
}
TEST_P(HttpNetworkTransactionTest,
UseAlternateProtocolForNpnSpdyWithExistingSpdySession) {
session_deps_.parse_alternative_services = false;
session_deps_.enable_alternative_service_with_different_host = false;
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.example.org/");
request.load_flags = 0;
std::string alternate_protocol_http_header =
GetAlternateProtocolHttpHeader();
MockRead data_reads[] = {
MockRead("HTTP/1.1 200 OK\r\n"),
MockRead(alternate_protocol_http_header.c_str()),
MockRead("\r\n"),
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(GetProtocol());
ssl.cert = ImportCertFromFile(GetTestCertsDirectory(), "spdy_pooling.pem");
ASSERT_TRUE(ssl.cert.get());
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
scoped_ptr<SpdyFrame> req(
spdy_util_.ConstructSpdyGet(nullptr, 0, 1, LOWEST, true));
MockWrite spdy_writes[] = {CreateMockWrite(*req, 0)};
scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
scoped_ptr<SpdyFrame> data(spdy_util_.ConstructSpdyBodyFrame(1, true));
MockRead spdy_reads[] = {
CreateMockRead(*resp, 1), CreateMockRead(*data, 2), MockRead(ASYNC, 0, 3),
};
SequencedSocketData spdy_data(spdy_reads, arraysize(spdy_reads), spdy_writes,
arraysize(spdy_writes));
session_deps_.socket_factory->AddSocketDataProvider(&spdy_data);
TestCompletionCallback callback;
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
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.get() != 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.example.org", 443);
SpdySessionKey key(host_port_pair, ProxyServer::Direct(),
PRIVACY_MODE_DISABLED);
base::WeakPtr<SpdySession> spdy_session =
CreateSecureSpdySession(session.get(), key, BoundNetLog());
trans.reset(new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
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.get() != NULL);
EXPECT_EQ("HTTP/1.1 200", 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);
}
// 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_P(HttpNetworkTransactionTest, 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:443\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:443\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 MockRead* 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* const proxy_url;
AuthTiming proxy_auth_timing;
int proxy_auth_rv;
const char* const 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)}},
};
for (size_t i = 0; i < arraysize(test_configs); ++i) {
HttpAuthHandlerMock::Factory* auth_factory(
new HttpAuthHandlerMock::Factory());
session_deps_.http_auth_handler_factory.reset(auth_factory);
SSLInfo empty_ssl_info;
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);
HttpAuthChallengeTokenizer tokenizer(auth_challenge.begin(),
auth_challenge.end());
auth_handler->InitFromChallenge(&tokenizer, HttpAuth::AUTH_PROXY,
empty_ssl_info, 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);
HttpAuthChallengeTokenizer tokenizer(auth_challenge.begin(),
auth_challenge.end());
auth_handler->InitFromChallenge(&tokenizer, HttpAuth::AUTH_SERVER,
empty_ssl_info, 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 =
ProxyService::CreateFixed(test_config.proxy_url);
} else {
session_deps_.proxy_service = ProxyService::CreateDirect();
}
HttpRequestInfo request;
request.method = "GET";
request.url = GURL(test_config.server_url);
request.load_flags = 0;
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
SSLSocketDataProvider ssl_socket_data_provider(SYNCHRONOUS, OK);
std::vector<std::vector<MockRead>> mock_reads(1);
std::vector<std::vector<MockWrite>> mock_writes(1);
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.
mock_reads.back().push_back(read_write_round.read);
mock_writes.back().push_back(read_write_round.write);
// kProxyChallenge uses Proxy-Connection: close which means that the
// socket is closed and a new one will be created for the next request.
if (read_write_round.read.data == kProxyChallenge.data) {
mock_reads.push_back(std::vector<MockRead>());
mock_writes.push_back(std::vector<MockWrite>());
}
if (read_write_round.extra_read) {
mock_reads.back().push_back(*read_write_round.extra_read);
}
if (read_write_round.extra_write) {
mock_writes.back().push_back(*read_write_round.extra_write);
}
// Add an SSL sequence if necessary.
if (round >= test_config.first_ssl_round)
session_deps_.socket_factory->AddSSLSocketDataProvider(
&ssl_socket_data_provider);
}
std::vector<scoped_ptr<StaticSocketDataProvider>> data_providers;
for (size_t i = 0; i < mock_reads.size(); ++i) {
data_providers.push_back(make_scoped_ptr(new StaticSocketDataProvider(
mock_reads[i].data(), mock_reads[i].size(), mock_writes[i].data(),
mock_writes[i].size())));
session_deps_.socket_factory->AddSocketDataProvider(
data_providers.back().get());
}
// Transaction must be created after DataProviders, so it's destroyed before
// they are as well.
HttpNetworkTransaction trans(DEFAULT_PRIORITY, session.get());
for (int round = 0; round < test_config.num_auth_rounds; ++round) {
const TestRound& read_write_round = test_config.rounds[round];
// 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) {
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_P(HttpNetworkTransactionTest, MultiRoundAuth) {
// Do multi-round authentication and make sure it works correctly.
HttpAuthHandlerMock::Factory* auth_factory(
new HttpAuthHandlerMock::Factory());
session_deps_.http_auth_handler_factory.reset(auth_factory);
session_deps_.proxy_service = 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");
HttpAuthChallengeTokenizer tokenizer(auth_challenge.begin(),
auth_challenge.end());
SSLInfo empty_ssl_info;
auth_handler->InitFromChallenge(&tokenizer, HttpAuth::AUTH_SERVER,
empty_ssl_info, 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_ptr<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.get());
TransportClientSocketPool* transport_pool = new TransportClientSocketPool(
50, // Max sockets for pool
1, // Max sockets per group
session_deps_.host_resolver.get(),
session_deps_.socket_factory.get(),
session_deps_.net_log);
scoped_ptr<MockClientSocketPoolManager> mock_pool_manager(
new MockClientSocketPoolManager);
mock_pool_manager->SetTransportSocketPool(transport_pool);
session_peer.SetClientSocketPoolManager(std::move(mock_pool_manager));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
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 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(DEFAULT_PRIORITY, session.get()));
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.get(), io_buf->size(), callback.callback());
if (rv == ERR_IO_PENDING)
rv = callback.WaitForResult();
EXPECT_EQ(3, rv);
rv = trans->Read(io_buf.get(), 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.get(), io_buf->size(), callback.callback());
if (rv == ERR_IO_PENDING)
rv = callback.WaitForResult();
EXPECT_EQ(3, rv);
rv = trans_compete->Read(io_buf.get(), io_buf->size(), callback.callback());
EXPECT_EQ(0, rv);
// Finally, the socket is released to the group.
EXPECT_EQ(1, transport_pool->IdleSocketCountInGroup(kSocketGroup));
}
// This tests the case that a request is issued via http instead of spdy after
// npn is negotiated.
TEST_P(HttpNetworkTransactionTest, NpnWithHttpOverSSL) {
session_deps_.parse_alternative_services = true;
session_deps_.enable_alternative_service_with_different_host = false;
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("https://www.example.org/");
request.load_flags = 0;
MockWrite data_writes[] = {
MockWrite(
"GET / HTTP/1.1\r\n"
"Host: www.example.org\r\n"
"Connection: keep-alive\r\n\r\n"),
};
std::string alternate_protocol_http_header =
GetAlternateProtocolHttpHeader();
MockRead data_reads[] = {
MockRead("HTTP/1.1 200 OK\r\n"),
MockRead(alternate_protocol_http_header.c_str()),
MockRead("\r\n"),
MockRead("hello world"),
MockRead(SYNCHRONOUS, OK),
};
SSLSocketDataProvider ssl(ASYNC, OK);
ssl.SetNextProto(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_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
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.get() != 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);
}
// Simulate the SSL handshake completing with an NPN negotiation followed by an
// immediate server closing of the socket.
// Regression test for https://crbug.com/46369.
TEST_P(HttpNetworkTransactionTest, SpdyPostNPNServerHangup) {
session_deps_.parse_alternative_services = true;
session_deps_.enable_alternative_service_with_different_host = false;
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("https://www.example.org/");
request.load_flags = 0;
SSLSocketDataProvider ssl(ASYNC, OK);
ssl.SetNextProto(GetProtocol());
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
scoped_ptr<SpdyFrame> req(
spdy_util_.ConstructSpdyGet(nullptr, 0, 1, LOWEST, true));
MockWrite spdy_writes[] = {CreateMockWrite(*req, 1)};
MockRead spdy_reads[] = {
MockRead(SYNCHRONOUS, 0, 0) // Not async - return 0 immediately.
};
SequencedSocketData spdy_data(spdy_reads, arraysize(spdy_reads), spdy_writes,
arraysize(spdy_writes));
session_deps_.socket_factory->AddSocketDataProvider(&spdy_data);
TestCompletionCallback callback;
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
int rv = trans->Start(&request, callback.callback(), BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
EXPECT_EQ(ERR_CONNECTION_CLOSED, callback.WaitForResult());
}
// A subclass of HttpAuthHandlerMock that records the request URL when
// it gets it. This is needed since the auth handler may get destroyed
// before we get a chance to query it.
class UrlRecordingHttpAuthHandlerMock : public HttpAuthHandlerMock {
public:
explicit UrlRecordingHttpAuthHandlerMock(GURL* url) : url_(url) {}
~UrlRecordingHttpAuthHandlerMock() override {}
protected:
int GenerateAuthTokenImpl(const AuthCredentials* credentials,
const HttpRequestInfo* request,
const CompletionCallback& callback,
std::string* auth_token) override {
*url_ = request->url;
return HttpAuthHandlerMock::GenerateAuthTokenImpl(
credentials, request, callback, auth_token);
}
private:
GURL* url_;
};
// This test ensures that the URL passed into the proxy is upgraded to https
// when doing an Alternate Protocol upgrade.
TEST_P(HttpNetworkTransactionTest, SpdyAlternateProtocolThroughProxy) {
session_deps_.parse_alternative_services = false;
session_deps_.enable_alternative_service_with_different_host = false;
session_deps_.proxy_service =
ProxyService::CreateFixedFromPacResult("PROXY myproxy:70");
TestNetLog net_log;
session_deps_.net_log = &net_log;
GURL request_url;
{
HttpAuthHandlerMock::Factory* auth_factory =
new HttpAuthHandlerMock::Factory();
UrlRecordingHttpAuthHandlerMock* auth_handler =
new UrlRecordingHttpAuthHandlerMock(&request_url);
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.example.org");
request.load_flags = 0;
// First round goes unauthenticated through the proxy.
MockWrite data_writes_1[] = {
MockWrite(
"GET http://www.example.org/ HTTP/1.1\r\n"
"Host: www.example.org\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"),
MockRead("Alternate-Protocol: 443:"),
MockRead(GetAlternateProtocolFromParam()),
MockRead("\r\n"),
MockRead("Proxy-Connection: close\r\n"),
MockRead("\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.example.org 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.example.org 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<SpdyFrame> req(
spdy_util_.ConstructSpdyGet(nullptr, 0, 1, LOWEST, true));
scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
scoped_ptr<SpdyFrame> data(spdy_util_.ConstructSpdyBodyFrame(1, true));
MockWrite data_writes_2[] = {
// First connection attempt without Proxy-Authorization.
MockWrite(ASYNC, 0,
"CONNECT www.example.org:443 HTTP/1.1\r\n"
"Host: www.example.org:443\r\n"
"Proxy-Connection: keep-alive\r\n"
"\r\n"),
// Second connection attempt with Proxy-Authorization.
MockWrite(ASYNC, 2,
"CONNECT www.example.org:443 HTTP/1.1\r\n"
"Host: www.example.org:443\r\n"
"Proxy-Connection: keep-alive\r\n"
"Proxy-Authorization: auth_token\r\n"
"\r\n"),
// SPDY request
CreateMockWrite(*req, 4),
};
MockRead data_reads_2[] = {
// First connection attempt fails
MockRead(ASYNC, 1,
"HTTP/1.1 407 Unauthorized\r\n"
"Proxy-Authenticate: Mock\r\n"
"Content-Length: 0\r\n"
"Proxy-Connection: keep-alive\r\n"
"\r\n"),
// Second connection attempt passes
MockRead(ASYNC, 3, "HTTP/1.1 200 Connected\r\n\r\n"),
// SPDY response
CreateMockRead(*resp.get(), 5), CreateMockRead(*data.get(), 6),
MockRead(ASYNC, 0, 0, 7),
};
SequencedSocketData data_2(data_reads_2, arraysize(data_reads_2),
data_writes_2, arraysize(data_writes_2));
SSLSocketDataProvider ssl(ASYNC, OK);
ssl.SetNextProto(GetProtocol());
ssl.cert = ImportCertFromFile(GetTestCertsDirectory(), "spdy_pooling.pem");
ASSERT_TRUE(ssl.cert.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(&data_1);
session_deps_.socket_factory->AddSocketDataProvider(&data_2);
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
session_deps_.socket_factory->AddSocketDataProvider(
&hanging_non_alternate_protocol_socket);
scoped_ptr<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(DEFAULT_PRIORITY, session.get()));
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(DEFAULT_PRIORITY, session.get()));
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.
EXPECT_EQ("https", request_url.scheme());
EXPECT_EQ("www.example.org", request_url.host());
LoadTimingInfo load_timing_info;
EXPECT_TRUE(trans_2->GetLoadTimingInfo(&load_timing_info));
TestLoadTimingNotReusedWithPac(load_timing_info,
CONNECT_TIMING_HAS_SSL_TIMES);
}
// Test that if we cancel the transaction as the connection is completing, that
// everything tears down correctly.
TEST_P(HttpNetworkTransactionTest, 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.example.org/");
request.load_flags = 0;
session_deps_.host_resolver->set_synchronous_mode(true);
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0);
data.set_connect_data(mock_connect);
session_deps_.socket_factory->AddSocketDataProvider(&data);
TestCompletionCallback callback;
BoundTestNetLog log;
int rv = trans->Start(&request, callback.callback(), log.bound());
EXPECT_EQ(ERR_IO_PENDING, rv);
trans.reset(); // Cancel the transaction here.
base::MessageLoop::current()->RunUntilIdle();
}
// Test that if a transaction is cancelled after receiving the headers, the
// stream is drained properly and added back to the socket pool. The main
// purpose of this test is to make sure that an HttpStreamParser can be read
// from after the HttpNetworkTransaction and the objects it owns have been
// deleted.
// See http://crbug.com/368418
TEST_P(HttpNetworkTransactionTest, CancelAfterHeaders) {
MockRead data_reads[] = {
MockRead(ASYNC, "HTTP/1.1 200 OK\r\n"),
MockRead(ASYNC, "Content-Length: 2\r\n"),
MockRead(ASYNC, "Connection: Keep-Alive\r\n\r\n"),
MockRead(ASYNC, "1"),
// 2 async reads are necessary to trigger a ReadResponseBody call after the
// HttpNetworkTransaction has been deleted.
MockRead(ASYNC, "2"),
MockRead(SYNCHRONOUS, ERR_IO_PENDING), // Should never read this.
};
StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0);
session_deps_.socket_factory->AddSocketDataProvider(&data);
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
{
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.example.org/");
request.load_flags = 0;
HttpNetworkTransaction trans(DEFAULT_PRIORITY, session.get());
TestCompletionCallback callback;
int rv = trans.Start(&request, callback.callback(), BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
callback.WaitForResult();
const HttpResponseInfo* response = trans.GetResponseInfo();
ASSERT_TRUE(response != NULL);
EXPECT_TRUE(response->headers.get() != NULL);
EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
// The transaction and HttpRequestInfo are deleted.
}
// Let the HttpResponseBodyDrainer drain the socket.
base::MessageLoop::current()->RunUntilIdle();
// Socket should now be idle, waiting to be reused.
EXPECT_EQ(1, GetIdleSocketCountInTransportSocketPool(session.get()));
}
// Test a basic GET request through a proxy.
TEST_P(HttpNetworkTransactionTest, ProxyGet) {
session_deps_.proxy_service =
ProxyService::CreateFixedFromPacResult("PROXY myproxy:70");
BoundTestNetLog log;
session_deps_.net_log = log.bound().net_log();
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.example.org/");
MockWrite data_writes1[] = {
MockWrite(
"GET http://www.example.org/ HTTP/1.1\r\n"
"Host: www.example.org\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(DEFAULT_PRIORITY, session.get()));
BeforeProxyHeadersSentHandler proxy_headers_handler;
trans->SetBeforeProxyHeadersSentCallback(
base::Bind(&BeforeProxyHeadersSentHandler::OnBeforeProxyHeadersSent,
base::Unretained(&proxy_headers_handler)));
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(
response->proxy_server.Equals(HostPortPair::FromString("myproxy:70")));
EXPECT_TRUE(proxy_headers_handler.observed_before_proxy_headers_sent());
EXPECT_EQ("myproxy:70", proxy_headers_handler.observed_proxy_server_uri());
EXPECT_TRUE(HttpVersion(1, 1) == response->headers->GetHttpVersion());
LoadTimingInfo load_timing_info;
EXPECT_TRUE(trans->GetLoadTimingInfo(&load_timing_info));
TestLoadTimingNotReusedWithPac(load_timing_info,
CONNECT_TIMING_HAS_CONNECT_TIMES_ONLY);
}
// Test a basic HTTPS GET request through a proxy.
TEST_P(HttpNetworkTransactionTest, ProxyTunnelGet) {
session_deps_.proxy_service =
ProxyService::CreateFixedFromPacResult("PROXY myproxy:70");
BoundTestNetLog log;
session_deps_.net_log = log.bound().net_log();
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("https://www.example.org/");
// Since we have proxy, should try to establish tunnel.
MockWrite data_writes1[] = {
MockWrite("CONNECT www.example.org:443 HTTP/1.1\r\n"
"Host: www.example.org:443\r\n"
"Proxy-Connection: keep-alive\r\n\r\n"),
MockWrite("GET / HTTP/1.1\r\n"
"Host: www.example.org\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(DEFAULT_PRIORITY, session.get()));
int rv = trans->Start(&request, callback1.callback(), log.bound());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback1.WaitForResult();
EXPECT_EQ(OK, rv);
TestNetLogEntry::List 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);
EXPECT_TRUE(
response->proxy_server.Equals(HostPortPair::FromString("myproxy:70")));
LoadTimingInfo load_timing_info;
EXPECT_TRUE(trans->GetLoadTimingInfo(&load_timing_info));
TestLoadTimingNotReusedWithPac(load_timing_info,
CONNECT_TIMING_HAS_SSL_TIMES);
}
// Test a basic HTTPS GET request through a proxy, connecting to an IPv6
// literal host.
TEST_P(HttpNetworkTransactionTest, ProxyTunnelGetIPv6) {
session_deps_.proxy_service =
ProxyService::CreateFixedFromPacResult("PROXY myproxy:70");
BoundTestNetLog log;
session_deps_.net_log = log.bound().net_log();
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("https://[::1]:443/");
// Since we have proxy, should try to establish tunnel.
MockWrite data_writes1[] = {
MockWrite("CONNECT [::1]:443 HTTP/1.1\r\n"
"Host: [::1]:443\r\n"
"Proxy-Connection: keep-alive\r\n\r\n"),
MockWrite("GET / HTTP/1.1\r\n"
"Host: [::1]\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(DEFAULT_PRIORITY, session.get()));
int rv = trans->Start(&request, callback1.callback(), log.bound());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback1.WaitForResult();
EXPECT_EQ(OK, rv);
TestNetLogEntry::List 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);
EXPECT_TRUE(
response->proxy_server.Equals(HostPortPair::FromString("myproxy:70")));
LoadTimingInfo load_timing_info;
EXPECT_TRUE(trans->GetLoadTimingInfo(&load_timing_info));
TestLoadTimingNotReusedWithPac(load_timing_info,
CONNECT_TIMING_HAS_SSL_TIMES);
}
// Test a basic HTTPS GET request through a proxy, but the server hangs up
// while establishing the tunnel.
TEST_P(HttpNetworkTransactionTest, ProxyTunnelGetHangup) {
session_deps_.proxy_service = ProxyService::CreateFixed("myproxy:70");
BoundTestNetLog log;
session_deps_.net_log = log.bound().net_log();
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("https://www.example.org/");
// Since we have proxy, should try to establish tunnel.
MockWrite data_writes1[] = {
MockWrite("CONNECT www.example.org:443 HTTP/1.1\r\n"
"Host: www.example.org:443\r\n"
"Proxy-Connection: keep-alive\r\n\r\n"),
MockWrite("GET / HTTP/1.1\r\n"
"Host: www.example.org\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(DEFAULT_PRIORITY, session.get()));
int rv = trans->Start(&request, callback1.callback(), log.bound());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback1.WaitForResult();
EXPECT_EQ(ERR_EMPTY_RESPONSE, rv);
TestNetLogEntry::List 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_P(HttpNetworkTransactionTest, PreconnectWithExistingSpdySession) {
scoped_ptr<SpdyFrame> req(
spdy_util_.ConstructSpdyGet("https://www.example.org", 1, LOWEST));
MockWrite spdy_writes[] = {CreateMockWrite(*req, 0)};
scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
scoped_ptr<SpdyFrame> data(spdy_util_.ConstructSpdyBodyFrame(1, true));
MockRead spdy_reads[] = {
CreateMockRead(*resp, 1), CreateMockRead(*data, 2), MockRead(ASYNC, 0, 3),
};
SequencedSocketData spdy_data(spdy_reads, arraysize(spdy_reads), spdy_writes,
arraysize(spdy_writes));
session_deps_.socket_factory->AddSocketDataProvider(&spdy_data);
SSLSocketDataProvider ssl(ASYNC, OK);
ssl.SetNextProto(GetProtocol());
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
// Set up an initial SpdySession in the pool to reuse.
HostPortPair host_port_pair("www.example.org", 443);
SpdySessionKey key(host_port_pair, ProxyServer::Direct(),
PRIVACY_MODE_DISABLED);
base::WeakPtr<SpdySession> spdy_session =
CreateInsecureSpdySession(session.get(), key, BoundNetLog());
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("https://www.example.org/");
request.load_flags = 0;
// This is the important line that marks this as a preconnect.
request.motivation = HttpRequestInfo::PRECONNECT_MOTIVATED;
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
TestCompletionCallback callback;
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.
void HttpNetworkTransactionTest::CheckErrorIsPassedBack(
int error, IoMode mode) {
HttpRequestInfo request_info;
request_info.url = GURL("https://www.example.com/");
request_info.method = "GET";
request_info.load_flags = LOAD_NORMAL;
SSLSocketDataProvider ssl_data(mode, OK);
MockWrite data_writes[] = {
MockWrite(mode, error),
};
StaticSocketDataProvider data(NULL, 0, data_writes, arraysize(data_writes));
session_deps_.socket_factory->AddSocketDataProvider(&data);
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data);
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
TestCompletionCallback callback;
int rv = trans->Start(&request_info, callback.callback(), BoundNetLog());
if (rv == ERR_IO_PENDING)
rv = callback.WaitForResult();
ASSERT_EQ(error, rv);
}
TEST_P(HttpNetworkTransactionTest, 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_P(HttpNetworkTransactionTest,
ClientAuthCertCache_Direct_NoFalseStart) {
HttpRequestInfo request_info;
request_info.url = GURL("https://www.example.com/");
request_info.method = "GET";
request_info.load_flags = LOAD_NORMAL;
scoped_refptr<SSLCertRequestInfo> cert_request(new SSLCertRequestInfo());
cert_request->host_and_port = HostPortPair("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, ERR_SSL_CLIENT_AUTH_CERT_NEEDED);
ssl_data1.cert_request_info = cert_request.get();
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data1);
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, ERR_SSL_PROTOCOL_ERROR);
ssl_data2.cert_request_info = cert_request.get();
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data2);
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 TLSv1.1 if the previous
// connection was attempted with TLSv1.2. 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, ERR_SSL_PROTOCOL_ERROR);
ssl_data3.cert_request_info = cert_request.get();
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data3);
StaticSocketDataProvider data3(NULL, 0, NULL, 0);
session_deps_.socket_factory->AddSocketDataProvider(&data3);
// [ssl_]data4 contains the data for the fourth SSL handshake. When a
// connection to a server fails during an SSL handshake,
// HttpNetworkTransaction will attempt to fallback to TLSv1 if the previous
// connection was attempted with TLSv1.1. 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_data4(ASYNC, ERR_SSL_PROTOCOL_ERROR);
ssl_data4.cert_request_info = cert_request.get();
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data4);
StaticSocketDataProvider data4(NULL, 0, NULL, 0);
session_deps_.socket_factory->AddSocketDataProvider(&data4);
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
// Begin the SSL handshake with the peer. This consumes ssl_data1.
TestCompletionCallback callback;
int rv = trans->Start(&request_info, callback.callback(), BoundNetLog());
ASSERT_EQ(ERR_IO_PENDING, rv);
// Complete the SSL handshake, which should abort due to requiring a
// client certificate.
rv = callback.WaitForResult();
ASSERT_EQ(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, NULL, callback.callback());
ASSERT_EQ(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;
scoped_refptr<SSLPrivateKey> client_private_key;
ASSERT_TRUE(session->ssl_client_auth_cache()->Lookup(
HostPortPair("www.example.com", 443), &client_cert, &client_private_key));
ASSERT_EQ(NULL, client_cert.get());
// Restart the handshake. This will consume ssl_data2, which fails, and
// then consume ssl_data3 and ssl_data4, both of which should also fail.
// The result code is checked against what ssl_data4 should return.
rv = callback.WaitForResult();
ASSERT_EQ(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(
HostPortPair("www.example.com", 443), &client_cert, &client_private_key));
}
// 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_P(HttpNetworkTransactionTest,
ClientAuthCertCache_Direct_FalseStart) {
HttpRequestInfo request_info;
request_info.url = GURL("https://www.example.com/");
request_info.method = "GET";
request_info.load_flags = LOAD_NORMAL;
scoped_refptr<SSLCertRequestInfo> cert_request(new SSLCertRequestInfo());
cert_request->host_and_port = HostPortPair("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, ERR_SSL_CLIENT_AUTH_CERT_NEEDED);
ssl_data1.cert_request_info = cert_request.get();
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data1);
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, OK);
ssl_data2.cert_request_info = cert_request.get();
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data2);
MockRead data2_reads[] = {
MockRead(ASYNC /* async */, ERR_SSL_PROTOCOL_ERROR),
};
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.1 connection falls back to
// TLSv1. It has the same behaviour as [ssl_]data2.
SSLSocketDataProvider ssl_data3(ASYNC, OK);
ssl_data3.cert_request_info = cert_request.get();
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data3);
StaticSocketDataProvider data3(data2_reads, arraysize(data2_reads), NULL, 0);
session_deps_.socket_factory->AddSocketDataProvider(&data3);
// [ssl_]data4 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_data4(ASYNC, OK);
ssl_data4.cert_request_info = cert_request.get();
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data4);
StaticSocketDataProvider data4(data2_reads, arraysize(data2_reads), NULL, 0);
session_deps_.socket_factory->AddSocketDataProvider(&data4);
// Need one more if TLSv1.2 is enabled.
SSLSocketDataProvider ssl_data5(ASYNC, OK);
ssl_data5.cert_request_info = cert_request.get();
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data5);
StaticSocketDataProvider data5(data2_reads, arraysize(data2_reads), NULL, 0);
session_deps_.socket_factory->AddSocketDataProvider(&data5);
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
// Begin the initial SSL handshake.
TestCompletionCallback callback;
int rv = trans->Start(&request_info, callback.callback(), BoundNetLog());
ASSERT_EQ(ERR_IO_PENDING, rv);
// Complete the SSL handshake, which should abort due to requiring a
// client certificate.
rv = callback.WaitForResult();
ASSERT_EQ(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, NULL, callback.callback());
ASSERT_EQ(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;
scoped_refptr<SSLPrivateKey> client_private_key;
ASSERT_TRUE(session->ssl_client_auth_cache()->Lookup(
HostPortPair("www.example.com", 443), &client_cert, &client_private_key));
ASSERT_EQ(NULL, client_cert.get());
// Restart the handshake. This will consume ssl_data2, which fails, and
// then consume ssl_data3 and ssl_data4, both of which should also fail.
// The result code is checked against what ssl_data4 should return.
rv = callback.WaitForResult();
ASSERT_EQ(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(
HostPortPair("www.example.com", 443), &client_cert, &client_private_key));
}
// 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_P(HttpNetworkTransactionTest, ClientAuthCertCache_Proxy_Fail) {
session_deps_.proxy_service = ProxyService::CreateFixed("https://proxy:70");
BoundTestNetLog log;
session_deps_.net_log = log.bound().net_log();
scoped_refptr<SSLCertRequestInfo> cert_request(new SSLCertRequestInfo());
cert_request->host_and_port = HostPortPair("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, ERR_SSL_CLIENT_AUTH_CERT_NEEDED);
ssl_data1.cert_request_info = cert_request.get();
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data1);
StaticSocketDataProvider data1(NULL, 0, NULL, 0);
session_deps_.socket_factory->AddSocketDataProvider(&data1);
SSLSocketDataProvider ssl_data2(ASYNC, ERR_SSL_PROTOCOL_ERROR);
ssl_data2.cert_request_info = cert_request.get();
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data2);
StaticSocketDataProvider data2(NULL, 0, NULL, 0);
session_deps_.socket_factory->AddSocketDataProvider(&data2);
// TODO(wtc): find out why this unit test doesn't need [ssl_]data3.
#if 0
SSLSocketDataProvider ssl_data3(ASYNC, ERR_SSL_PROTOCOL_ERROR);
ssl_data3.cert_request_info = cert_request.get();
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data3);
StaticSocketDataProvider data3(NULL, 0, NULL, 0);
session_deps_.socket_factory->AddSocketDataProvider(&data3);
#endif
HttpRequestInfo requests[2];
requests[0].url = GURL("https://www.example.com/");
requests[0].method = "GET";
requests[0].load_flags = LOAD_NORMAL;
requests[1].url = GURL("http://www.example.com/");
requests[1].method = "GET";
requests[1].load_flags = LOAD_NORMAL;
for (size_t i = 0; i < arraysize(requests); ++i) {
session_deps_.socket_factory->ResetNextMockIndexes();
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpNetworkTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
// Begin the SSL handshake with the proxy.
TestCompletionCallback callback;
int rv = trans->Start(&requests[i], callback.callback(), BoundNetLog());
ASSERT_EQ(ERR_IO_PENDING, rv);
// Complete the SSL handshake, which should abort due to requiring a
// client certificate.
rv = callback.WaitForResult();
ASSERT_EQ(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, NULL, callback.callback());
ASSERT_EQ(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;
scoped_refptr<SSLPrivateKey> client_private_key;
ASSERT_TRUE(session->ssl_client_auth_cache()->Lookup(
HostPortPair("proxy", 70), &client_cert, &client_private_key));
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(
HostPortPair("www.example.com", 443), &client_cert,
&client_private_key));
// 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(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(
HostPortPair("proxy", 70), &client_cert, &client_private_key));
ASSERT_FALSE(session->ssl_client_auth_cache()->Lookup(
HostPortPair("www.example.com", 443), &client_cert,
&client_private_key));
}
}
TEST_P(HttpNetworkTransactionTest, UseIPConnectionPooling) {
session_deps_.parse_alternative_services = true;
session_deps_.enable_alternative_service_with_different_host = false;
// Set up a special HttpNetworkSession with a MockCachingHostResolver.
session_deps_.host_resolver.reset(new MockCachingHostResolver());
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
SpdySessionPoolPeer pool_peer(session->spdy_session_pool());
pool_peer.DisableDomainAuthenticationVerification();
SSLSocketDataProvider ssl(ASYNC, OK);
ssl.SetNextProto(GetProtocol());
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
scoped_ptr<SpdyFrame> host1_req(
spdy_util_.ConstructSpdyGet("https://www.example.org", 1, LOWEST));
spdy_util_.UpdateWithStreamDestruction(1);
scoped_ptr<SpdyFrame> host2_req(
spdy_util_.ConstructSpdyGet("https://www.gmail.com", 3, LOWEST));
MockWrite spdy_writes[] = {
CreateMockWrite(*host1_req, 0), CreateMockWrite(*host2_req, 3),
};
scoped_ptr<SpdyFrame> host1_resp(
spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
scoped_ptr<SpdyFrame> host1_resp_body(
spdy_util_.ConstructSpdyBodyFrame(1, true));
scoped_ptr<SpdyFrame> host2_resp(
spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 3));
scoped_ptr<SpdyFrame> host2_resp_body(
spdy_util_.ConstructSpdyBodyFrame(3, true));
MockRead spdy_reads[] = {
CreateMockRead(*host1_resp, 1),
CreateMockRead(*host1_resp_body, 2),
CreateMockRead(*host2_resp, 4),
CreateMockRead(*host2_resp_body, 5),
MockRead(ASYNC, 0, 6),
};
IPEndPoint peer_addr(IPAddress::IPv4Localhost(), 443);
MockConnect connect(ASYNC, OK, peer_addr);
SequencedSocketData spdy_data(connect, spdy_reads, arraysize(spdy_reads),
spdy_writes, arraysize(spdy_writes));
session_deps_.socket_factory->AddSocketDataProvider(&spdy_data);
TestCompletionCallback callback;
HttpRequestInfo request1;
request1.method = "GET";
request1.url = GURL("https://www.example.org/");
request1.load_flags = 0;
HttpNetworkTransaction trans1(DEFAULT_PRIORITY, session.get());
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.get() != NULL);
EXPECT_EQ("HTTP/1.1 200", 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 = session_deps_.host_resolver->Resolve(resolve_info,
DEFAULT_PRIORITY,
&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(DEFAULT_PRIORITY, session.get());
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.get() != NULL);
EXPECT_EQ("HTTP/1.1 200", 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);
}
TEST_P(HttpNetworkTransactionTest, UseIPConnectionPoolingAfterResolution) {
session_deps_.parse_alternative_services = true;
session_deps_.enable_alternative_service_with_different_host = false;
// Set up a special HttpNetworkSession with a MockCachingHostResolver.
session_deps_.host_resolver.reset(new MockCachingHostResolver());
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
SpdySessionPoolPeer pool_peer(session->spdy_session_pool());
pool_peer.DisableDomainAuthenticationVerification();
SSLSocketDataProvider ssl(ASYNC, OK);
ssl.SetNextProto(GetProtocol());
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
scoped_ptr<SpdyFrame> host1_req(
spdy_util_.ConstructSpdyGet("https://www.example.org", 1, LOWEST));
spdy_util_.UpdateWithStreamDestruction(1);
scoped_ptr<SpdyFrame> host2_req(
spdy_util_.ConstructSpdyGet("https://www.gmail.com", 3, LOWEST));
MockWrite spdy_writes[] = {
CreateMockWrite(*host1_req, 0), CreateMockWrite(*host2_req, 3),
};
scoped_ptr<SpdyFrame> host1_resp(
spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
scoped_ptr<SpdyFrame> host1_resp_body(
spdy_util_.ConstructSpdyBodyFrame(1, true));
scoped_ptr<SpdyFrame> host2_resp(
spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 3));
scoped_ptr<SpdyFrame> host2_resp_body(
spdy_util_.ConstructSpdyBodyFrame(3, true));
MockRead spdy_reads[] = {
CreateMockRead(*host1_resp, 1),
CreateMockRead(*host1_resp_body, 2),
CreateMockRead(*host2_resp, 4),
CreateMockRead(*host2_resp_body, 5),
MockRead(ASYNC, 0, 6),
};
IPEndPoint peer_addr(IPAddress::IPv4Localhost(), 443);
MockConnect connect(ASYNC, OK, peer_addr);
SequencedSocketData spdy_data(connect, spdy_reads, arraysize(spdy_reads),
spdy_writes, arraysize(spdy_writes));
session_deps_.socket_factory->AddSocketDataProvider(&spdy_data);
TestCompletionCallback callback;
HttpRequestInfo request1;
request1.method = "GET";
request1.url = GURL("https://www.example.org/");
request1.load_flags = 0;
HttpNetworkTransaction trans1(DEFAULT_PRIORITY, session.get());
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.get() != NULL);
EXPECT_EQ("HTTP/1.1 200", response->headers->GetStatusLine());
std::string response_data;
ASSERT_EQ(OK, ReadTransaction(&trans1, &response_data));
EXPECT_EQ("hello!", response_data);
HttpRequestInfo request2;
request2.method = "GET";
request2.url = GURL("https://www.gmail.com/");
request2.load_flags = 0;
HttpNetworkTransaction trans2(DEFAULT_PRIORITY, session.get());
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.get() != NULL);
EXPECT_EQ("HTTP/1.1 200", 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);
}
class OneTimeCachingHostResolver : public HostResolver {
public:
explicit OneTimeCachingHostResolver(const HostPortPair& host_port)
: host_port_(host_port) {}
~OneTimeCachingHostResolver() override {}
RuleBasedHostResolverProc* rules() { return host_resolver_.rules(); }
// HostResolver methods:
int Resolve(const RequestInfo& info,
RequestPriority priority,
AddressList* addresses,
const CompletionCallback& callback,
RequestHandle* out_req,
const BoundNetLog& net_log) override {
return host_resolver_.Resolve(
info, priority, addresses, callback, out_req, net_log);
}
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;
}
void CancelRequest(RequestHandle req) override {
host_resolver_.CancelRequest(req);
}
MockCachingHostResolver* GetMockHostResolver() {
return &host_resolver_;
}
private:
MockCachingHostResolver host_resolver_;
const HostPortPair host_port_;
};
TEST_P(HttpNetworkTransactionTest,
UseIPConnectionPoolingWithHostCacheExpiration) {
session_deps_.parse_alternative_services = true;
session_deps_.enable_alternative_service_with_different_host = false;
// Set up a special HttpNetworkSession with a OneTimeCachingHostResolver.
OneTimeCachingHostResolver host_resolver(HostPortPair("www.gmail.com", 443));
HttpNetworkSession::Params params =
SpdySessionDependencies::CreateSessionParams(&session_deps_);
params.host_resolver = &host_resolver;
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
SpdySessionPoolPeer pool_peer(session->spdy_session_pool());
pool_peer.DisableDomainAuthenticationVerification();
SSLSocketDataProvider ssl(ASYNC, OK);
ssl.SetNextProto(GetProtocol());
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
scoped_ptr<SpdyFrame> host1_req(
spdy_util_.ConstructSpdyGet("https://www.example.org", 1, LOWEST));
spdy_util_.UpdateWithStreamDestruction(1);
scoped_ptr<SpdyFrame> host2_req(
spdy_util_.ConstructSpdyGet("https://www.gmail.com", 3, LOWEST));
MockWrite spdy_writes[] = {
CreateMockWrite(*host1_req, 0), CreateMockWrite(*host2_req, 3),
};
scoped_ptr<SpdyFrame> host1_resp(
spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
scoped_ptr<SpdyFrame> host1_resp_body(
spdy_util_.ConstructSpdyBodyFrame(1, true));
scoped_ptr<SpdyFrame> host2_resp(
spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 3));
scoped_ptr<SpdyFrame> host2_resp_body(
spdy_util_.ConstructSpdyBodyFrame(3, true));
MockRead spdy_reads[] = {
CreateMockRead(*host1_resp, 1),
CreateMockRead(*host1_resp_body, 2),
CreateMockRead(*host2_resp, 4),
CreateMockRead(*host2_resp_body, 5),
MockRead(ASYNC, 0, 6),
};
IPEndPoint peer_addr(IPAddress::IPv4Localhost(), 443);
MockConnect connect(ASYNC, OK, peer_addr);
SequencedSocketData spdy_data(connect, spdy_reads, arraysize(spdy_reads),
spdy_writes, arraysize(spdy_writes));
session_deps_.socket_factory->AddSocketDataProvider(&spdy_data);
TestCompletionCallback callback;
HttpRequestInfo request1;
request1.method = "GET";
request1.url = GURL("https://www.example.org/");
request1.load_flags = 0;
HttpNetworkTransaction trans1(DEFAULT_PRIORITY, session.get());
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.get() != NULL);
EXPECT_EQ("HTTP/1.1 200", 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,
DEFAULT_PRIORITY,
&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(DEFAULT_PRIORITY, session.get());
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.get() != NULL);
EXPECT_EQ("HTTP/1.1 200", 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);
}
TEST_P(HttpNetworkTransactionTest, DoNotUseSpdySessionForHttp) {
const std::string https_url = "https://www.example.org:8080/";
const std::string http_url = "http://www.example.org:8080/";
// SPDY GET for HTTPS URL
scoped_ptr<SpdyFrame> req1(
spdy_util_.ConstructSpdyGet(https_url.c_str(), 1, LOWEST));
MockWrite writes1[] = {
CreateMockWrite(*req1, 0),
};
scoped_ptr<SpdyFrame> resp1(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
scoped_ptr<SpdyFrame> body1(spdy_util_.ConstructSpdyBodyFrame(1, true));
MockRead reads1[] = {CreateMockRead(*resp1, 1), CreateMockRead(*body1, 2),
MockRead(SYNCHRONOUS, ERR_IO_PENDING, 3)};
SequencedSocketData data1(reads1, arraysize(reads1), writes1,
arraysize(writes1));
MockConnect connect_data1(ASYNC, OK);
data1.set_connect_data(connect_data1);
// HTTP GET for the HTTP URL
MockWrite writes2[] = {
MockWrite(ASYNC, 0,
"GET / HTTP/1.1\r\n"
"Host: www.example.org:8080\r\n"
"Connection: keep-alive\r\n\r\n"),
};
MockRead reads2[] = {
MockRead(ASYNC, 1, "HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\n"),
MockRead(ASYNC, 2, "hello"),
MockRead(ASYNC, OK, 3),
};
SequencedSocketData data2(reads2, arraysize(reads2), writes2,
arraysize(writes2));
SSLSocketDataProvider ssl(ASYNC, OK);
ssl.SetNextProto(GetProtocol());
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
session_deps_.socket_factory->AddSocketDataProvider(&data1);
session_deps_.socket_factory->AddSocketDataProvider(&data2);
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
// Start the first transaction to set up the SpdySession
HttpRequestInfo request1;
request1.method = "GET";
request1.url = GURL(https_url);
request1.load_flags = 0;
HttpNetworkTransaction trans1(LOWEST, session.get());
TestCompletionCallback callback1;
EXPECT_EQ(ERR_IO_PENDING,
trans1.Start(&request1, callback1.callback(), BoundNetLog()));
base::MessageLoop::current()->RunUntilIdle();
EXPECT_EQ(OK, callback1.WaitForResult());
EXPECT_TRUE(trans1.GetResponseInfo()->was_fetched_via_spdy);
// Now, start the HTTP request
HttpRequestInfo request2;
request2.method = "GET";
request2.url = GURL(http_url);
request2.load_flags = 0;
HttpNetworkTransaction trans2(MEDIUM, session.get());
TestCompletionCallback callback2;
EXPECT_EQ(ERR_IO_PENDING,
trans2.Start(&request2, callback2.callback(), BoundNetLog()));
base::MessageLoop::current()->RunUntilIdle();
EXPECT_EQ(OK, callback2.WaitForResult());
EXPECT_FALSE(trans2.GetResponseInfo()->was_fetched_via_spdy);
}
class AltSvcCertificateVerificationTest : public HttpNetworkTransactionTest {
public:
void Run(bool pooling, bool valid) {
HostPortPair origin(valid ? "mail.example.org" : "invalid.example.org",
443);
HostPortPair alternative("www.example.org", 443);
base::FilePath certs_dir = GetTestCertsDirectory();
scoped_refptr<X509Certificate> cert(
ImportCertFromFile(certs_dir, "spdy_pooling.pem"));
ASSERT_TRUE(cert.get());
bool common_name_fallback_used;
EXPECT_EQ(valid,
cert->VerifyNameMatch(origin.host(), &common_name_fallback_used));
EXPECT_TRUE(
cert->VerifyNameMatch(alternative.host(), &common_name_fallback_used));
SSLSocketDataProvider ssl(ASYNC, OK);
ssl.SetNextProto(GetProtocol());
ssl.cert = cert;
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
// If pooling, then start a request to alternative first to create a
// SpdySession.
std::string url0 = "https://www.example.org:443";
// Second request to origin, which has an alternative service, and could
// open a connection to the alternative host or pool to the existing one.
std::string url1("https://");
url1.append(origin.host());
url1.append(":443");
scoped_ptr<SpdyFrame> req0;
scoped_ptr<SpdyFrame> req1;
scoped_ptr<SpdyFrame> resp0;
scoped_ptr<SpdyFrame> body0;
scoped_ptr<SpdyFrame> resp1;
scoped_ptr<SpdyFrame> body1;
std::vector<MockWrite> writes;
std::vector<MockRead> reads;
if (pooling) {
req0.reset(spdy_util_.ConstructSpdyGet(url0.c_str(), 1, LOWEST));
spdy_util_.UpdateWithStreamDestruction(1);
req1.reset(spdy_util_.ConstructSpdyGet(url1.c_str(), 3, LOWEST));
writes.push_back(CreateMockWrite(*req0, 0));
writes.push_back(CreateMockWrite(*req1, 3));
resp0.reset(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
body0.reset(spdy_util_.ConstructSpdyBodyFrame(1, true));
resp1.reset(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 3));
body1.reset(spdy_util_.ConstructSpdyBodyFrame(3, true));
reads.push_back(CreateMockRead(*resp0, 1));
reads.push_back(CreateMockRead(*body0, 2));
reads.push_back(MockRead(ASYNC, ERR_IO_PENDING, 4));
reads.push_back(CreateMockRead(*resp1, 5));
reads.push_back(CreateMockRead(*body1, 6));
reads.push_back(MockRead(ASYNC, OK, 7));
} else {
req1.reset(spdy_util_.ConstructSpdyGet(url1.c_str(), 1, LOWEST));
writes.push_back(CreateMockWrite(*req1, 0));
resp1.reset(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
body1.reset(spdy_util_.ConstructSpdyBodyFrame(1, true));
reads.push_back(CreateMockRead(*resp1, 1));
reads.push_back(CreateMockRead(*body1, 2));
reads.push_back(MockRead(ASYNC, OK, 3));
}
SequencedSocketData data(reads.data(), reads.size(), writes.data(),
writes.size());
session_deps_.socket_factory->AddSocketDataProvider(&data);
// Connection to the origin fails.
MockConnect mock_connect(ASYNC, ERR_CONNECTION_REFUSED);
StaticSocketDataProvider data_refused;
data_refused.set_connect_data(mock_connect);
session_deps_.socket_factory->AddSocketDataProvider(&data_refused);
session_deps_.parse_alternative_services = true;
session_deps_.enable_alternative_service_with_different_host = true;
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
base::WeakPtr<HttpServerProperties> http_server_properties =
session->http_server_properties();
AlternativeService alternative_service(
AlternateProtocolFromNextProto(GetProtocol()), alternative);
base::Time expiration = base::Time::Now() + base::TimeDelta::FromDays(1);
http_server_properties->SetAlternativeService(origin, alternative_service,
expiration);
// First request to alternative.
if (pooling) {
scoped_ptr<HttpTransaction> trans0(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
HttpRequestInfo request0;
request0.method = "GET";
request0.url = GURL(url0);
request0.load_flags = 0;
TestCompletionCallback callback0;
int rv = trans0->Start(&request0, callback0.callback(), BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback0.WaitForResult();
EXPECT_EQ(OK, rv);
}
// Second request to origin.
scoped_ptr<HttpTransaction> trans1(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
HttpRequestInfo request1;
request1.method = "GET";
request1.url = GURL(url1);
request1.load_flags = 0;
TestCompletionCallback callback1;
int rv = trans1->Start(&request1, callback1.callback(), BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
base::MessageLoop::current()->RunUntilIdle();
if (data.IsPaused())
data.Resume();
rv = callback1.WaitForResult();
if (valid) {
EXPECT_EQ(OK, rv);
} else {
if (pooling) {
EXPECT_EQ(ERR_CONNECTION_REFUSED, rv);
} else {
EXPECT_EQ(ERR_ALTERNATIVE_CERT_NOT_VALID_FOR_ORIGIN, rv);
}
}
}
};
INSTANTIATE_TEST_CASE_P(ProtoPlusDepend,
AltSvcCertificateVerificationTest,
testing::Values(kTestCaseSPDY31,
kTestCaseHTTP2NoPriorityDependencies,
kTestCaseHTTP2PriorityDependencies));
// The alternative service host must exhibit a certificate that is valid for the
// origin host. Test that this is enforced when pooling to an existing
// connection.
TEST_P(AltSvcCertificateVerificationTest, PoolingValid) {
Run(true, true);
}
TEST_P(AltSvcCertificateVerificationTest, PoolingInvalid) {
Run(true, false);
}
// The alternative service host must exhibit a certificate that is valid for the
// origin host. Test that this is enforced when opening a new connection.
TEST_P(AltSvcCertificateVerificationTest, NewConnectionValid) {
Run(false, true);
}
TEST_P(AltSvcCertificateVerificationTest, NewConnectionInvalid) {
Run(false, false);
}
// Alternative service requires HTTP/2 (or SPDY), but HTTP/1.1 is negotiated
// with the alternative server. That connection should not be used.
TEST_P(HttpNetworkTransactionTest, AlternativeServiceNotOnHttp11) {
HostPortPair origin("origin.example.org", 443);
HostPortPair alternative("alternative.example.org", 443);
// Negotiate HTTP/1.1 with alternative.example.org.
SSLSocketDataProvider ssl(ASYNC, OK);
ssl.SetNextProto(kProtoHTTP11);
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
// No data should be read from the alternative, because HTTP/1.1 is
// negotiated.
StaticSocketDataProvider data;
session_deps_.socket_factory->AddSocketDataProvider(&data);
// This test documents that an alternate Job should not be used if HTTP/1.1 is
// negotiated. In order to test this, a failed connection to the origin is
// mocked. This way the request relies on the alternate Job.
StaticSocketDataProvider data_refused;
data_refused.set_connect_data(MockConnect(ASYNC, ERR_CONNECTION_REFUSED));
session_deps_.socket_factory->AddSocketDataProvider(&data_refused);
// Set up alternative service for origin.
session_deps_.parse_alternative_services = true;
session_deps_.enable_alternative_service_with_different_host = true;
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
base::WeakPtr<HttpServerProperties> http_server_properties =
session->http_server_properties();
AlternativeService alternative_service(
AlternateProtocolFromNextProto(GetProtocol()), alternative);
base::Time expiration = base::Time::Now() + base::TimeDelta::FromDays(1);
http_server_properties->SetAlternativeService(origin, alternative_service,
expiration);
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("https://origin.example.org:443");
request.load_flags = 0;
TestCompletionCallback callback;
// HTTP/2 (or SPDY) is required for alternative service, if HTTP/1.1 is
// negotiated, the alternate Job should fail with ERR_NPN_NEGOTIATION_FAILED.
int rv = trans->Start(&request, callback.callback(), BoundNetLog());
EXPECT_EQ(ERR_NPN_NEGOTIATION_FAILED, callback.GetResult(rv));
}
// A request to a server with an alternative service fires two Jobs: one to the
// origin, and an alternate one to the alternative server. If the former
// succeeds, the request should succeed, even if the latter fails because
// HTTP/1.1 is negotiated which is insufficient for alternative service.
TEST_P(HttpNetworkTransactionTest, FailedAlternativeServiceIsNotUserVisible) {
HostPortPair origin("origin.example.org", 443);
HostPortPair alternative("alternative.example.org", 443);
// Negotiate HTTP/1.1 with alternative.
SSLSocketDataProvider alternative_ssl(ASYNC, OK);
alternative_ssl.SetNextProto(kProtoHTTP11);
session_deps_.socket_factory->AddSSLSocketDataProvider(&alternative_ssl);
// No data should be read from the alternative, because HTTP/1.1 is
// negotiated.
StaticSocketDataProvider data;
session_deps_.socket_factory->AddSocketDataProvider(&data);
// Negotiate HTTP/1.1 with origin.
SSLSocketDataProvider origin_ssl(ASYNC, OK);
origin_ssl.SetNextProto(kProtoHTTP11);
session_deps_.socket_factory->AddSSLSocketDataProvider(&origin_ssl);
MockWrite http_writes[] = {
MockWrite(
"GET / HTTP/1.1\r\n"
"Host: origin.example.org\r\n"
"Connection: keep-alive\r\n\r\n"),
MockWrite(
"GET /second HTTP/1.1\r\n"
"Host: origin.example.org\r\n"
"Connection: keep-alive\r\n\r\n"),
};
MockRead http_reads[] = {
MockRead("HTTP/1.1 200 OK\r\n"),
MockRead("Content-Type: text/html\r\n"),
MockRead("Content-Length: 6\r\n\r\n"),
MockRead("foobar"),
MockRead("HTTP/1.1 200 OK\r\n"),
MockRead("Content-Type: text/html\r\n"),
MockRead("Content-Length: 7\r\n\r\n"),
MockRead("another"),
};
StaticSocketDataProvider http_data(http_reads, arraysize(http_reads),
http_writes, arraysize(http_writes));
session_deps_.socket_factory->AddSocketDataProvider(&http_data);
// Set up alternative service for origin.
session_deps_.parse_alternative_services = true;
session_deps_.enable_alternative_service_with_different_host = true;
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
base::WeakPtr<HttpServerProperties> http_server_properties =
session->http_server_properties();
AlternativeService alternative_service(
AlternateProtocolFromNextProto(GetProtocol()), alternative);
base::Time expiration = base::Time::Now() + base::TimeDelta::FromDays(1);
http_server_properties->SetAlternativeService(origin, alternative_service,
expiration);
HttpNetworkTransaction trans1(DEFAULT_PRIORITY, session.get());
HttpRequestInfo request1;
request1.method = "GET";
request1.url = GURL("https://origin.example.org:443");
request1.load_flags = 0;
TestCompletionCallback callback1;
int rv = trans1.Start(&request1, callback1.callback(), BoundNetLog());
rv = callback1.GetResult(rv);
EXPECT_EQ(OK, rv);
const HttpResponseInfo* response1 = trans1.GetResponseInfo();
ASSERT_TRUE(response1 != nullptr);
ASSERT_TRUE(response1->headers.get() != nullptr);
EXPECT_EQ("HTTP/1.1 200 OK", response1->headers->GetStatusLine());
std::string response_data1;
ASSERT_EQ(OK, ReadTransaction(&trans1, &response_data1));
EXPECT_EQ("foobar", response_data1);
// Alternative should be marked as broken, because HTTP/1.1 is not sufficient
// for alternative service.
EXPECT_TRUE(
http_server_properties->IsAlternativeServiceBroken(alternative_service));
// Since |alternative_service| is broken, a second transaction to origin
// should not start an alternate Job. It should pool to existing connection
// to origin.
HttpNetworkTransaction trans2(DEFAULT_PRIORITY, session.get());
HttpRequestInfo request2;
request2.method = "GET";
request2.url = GURL("https://origin.example.org:443/second");
request2.load_flags = 0;
TestCompletionCallback callback2;
rv = trans2.Start(&request2, callback2.callback(), BoundNetLog());
rv = callback2.GetResult(rv);
EXPECT_EQ(OK, rv);
const HttpResponseInfo* response2 = trans2.GetResponseInfo();
ASSERT_TRUE(response2 != nullptr);
ASSERT_TRUE(response2->headers.get() != nullptr);
EXPECT_EQ("HTTP/1.1 200 OK", response2->headers->GetStatusLine());
std::string response_data2;
ASSERT_EQ(OK, ReadTransaction(&trans2, &response_data2));
EXPECT_EQ("another", response_data2);
}
// Alternative service requires HTTP/2 (or SPDY), but there is already a
// HTTP/1.1 socket open to the alternative server. That socket should not be
// used.
TEST_P(HttpNetworkTransactionTest, AlternativeServiceShouldNotPoolToHttp11) {
HostPortPair origin("origin.example.org", 443);
HostPortPair alternative("alternative.example.org", 443);
std::string origin_url = "https://origin.example.org:443";
std::string alternative_url = "https://alternative.example.org:443";
// Negotiate HTTP/1.1 with alternative.example.org.
SSLSocketDataProvider ssl(ASYNC, OK);
ssl.SetNextProto(kProtoHTTP11);
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
// HTTP/1.1 data for |request1| and |request2|.
MockWrite http_writes[] = {
MockWrite(
"GET / HTTP/1.1\r\n"
"Host: alternative.example.org\r\n"
"Connection: keep-alive\r\n\r\n"),
MockWrite(
"GET / HTTP/1.1\r\n"
"Host: alternative.example.org\r\n"
"Connection: keep-alive\r\n\r\n"),
};
MockRead http_reads[] = {
MockRead(
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/html; charset=iso-8859-1\r\n"
"Content-Length: 40\r\n\r\n"
"first HTTP/1.1 response from alternative"),
MockRead(
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/html; charset=iso-8859-1\r\n"
"Content-Length: 41\r\n\r\n"
"second HTTP/1.1 response from alternative"),
};
StaticSocketDataProvider http_data(http_reads, arraysize(http_reads),
http_writes, arraysize(http_writes));
session_deps_.socket_factory->AddSocketDataProvider(&http_data);
// This test documents that an alternate Job should not pool to an already
// existing HTTP/1.1 connection. In order to test this, a failed connection
// to the origin is mocked. This way |request2| relies on the alternate Job.
StaticSocketDataProvider data_refused;
data_refused.set_connect_data(MockConnect(ASYNC, ERR_CONNECTION_REFUSED));
session_deps_.socket_factory->AddSocketDataProvider(&data_refused);
// Set up alternative service for origin.
session_deps_.parse_alternative_services = true;
session_deps_.enable_alternative_service_with_different_host = false;
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
base::WeakPtr<HttpServerProperties> http_server_properties =
session->http_server_properties();
AlternativeService alternative_service(
AlternateProtocolFromNextProto(GetProtocol()), alternative);
base::Time expiration = base::Time::Now() + base::TimeDelta::FromDays(1);
http_server_properties->SetAlternativeService(origin, alternative_service,
expiration);
// First transaction to alternative to open an HTTP/1.1 socket.
scoped_ptr<HttpTransaction> trans1(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
HttpRequestInfo request1;
request1.method = "GET";
request1.url = GURL(alternative_url);
request1.load_flags = 0;
TestCompletionCallback callback1;
int rv = trans1->Start(&request1, callback1.callback(), BoundNetLog());
EXPECT_EQ(OK, callback1.GetResult(rv));
const HttpResponseInfo* response1 = trans1->GetResponseInfo();
ASSERT_TRUE(response1);
ASSERT_TRUE(response1->headers.get());
EXPECT_EQ("HTTP/1.1 200 OK", response1->headers->GetStatusLine());
EXPECT_TRUE(response1->was_npn_negotiated);
EXPECT_FALSE(response1->was_fetched_via_spdy);
std::string response_data1;
ASSERT_EQ(OK, ReadTransaction(trans1.get(), &response_data1));
EXPECT_EQ("first HTTP/1.1 response from alternative", response_data1);
// Request for origin.example.org, which has an alternative service. This
// will start two Jobs: the alternative looks for connections to pool to,
// finds one which is HTTP/1.1, and should ignore it, and should not try to
// open other connections to alternative server. The Job to origin fails, so
// this request fails.
scoped_ptr<HttpTransaction> trans2(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
HttpRequestInfo request2;
request2.method = "GET";
request2.url = GURL(origin_url);
request2.load_flags = 0;
TestCompletionCallback callback2;
rv = trans2->Start(&request2, callback2.callback(), BoundNetLog());
EXPECT_EQ(ERR_CONNECTION_REFUSED, callback2.GetResult(rv));
// Another transaction to alternative. This is to test that the HTTP/1.1
// socket is still open and in the pool.
scoped_ptr<HttpTransaction> trans3(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
HttpRequestInfo request3;
request3.method = "GET";
request3.url = GURL(alternative_url);
request3.load_flags = 0;
TestCompletionCallback callback3;
rv = trans3->Start(&request3, callback3.callback(), BoundNetLog());
EXPECT_EQ(OK, callback3.GetResult(rv));
const HttpResponseInfo* response3 = trans3->GetResponseInfo();
ASSERT_TRUE(response3);
ASSERT_TRUE(response3->headers.get());
EXPECT_EQ("HTTP/1.1 200 OK", response3->headers->GetStatusLine());
EXPECT_TRUE(response3->was_npn_negotiated);
EXPECT_FALSE(response3->was_fetched_via_spdy);
std::string response_data3;
ASSERT_EQ(OK, ReadTransaction(trans3.get(), &response_data3));
EXPECT_EQ("second HTTP/1.1 response from alternative", response_data3);
}
TEST_P(HttpNetworkTransactionTest, DoNotUseSpdySessionForHttpOverTunnel) {
const std::string https_url = "https://www.example.org:8080/";
const std::string http_url = "http://www.example.org:8080/";
// Separate SPDY util instance for naked and wrapped requests.
SpdyTestUtil spdy_util_wrapped(GetProtocol(), GetDependenciesFromPriority());
// SPDY GET for HTTPS URL (through CONNECT tunnel)
const HostPortPair host_port_pair("www.example.org", 8080);
scoped_ptr<SpdyFrame> connect(
spdy_util_.ConstructSpdyConnect(NULL, 0, 1, LOWEST, host_port_pair));
scoped_ptr<SpdyFrame> req1(
spdy_util_wrapped.ConstructSpdyGet(https_url.c_str(), 1, LOWEST));
scoped_ptr<SpdyFrame> wrapped_req1(
spdy_util_.ConstructWrappedSpdyFrame(req1, 1));
// SPDY GET for HTTP URL (through the proxy, but not the tunnel).
SpdyHeaderBlock req2_block;
spdy_util_.MaybeAddVersionHeader(&req2_block);
req2_block[spdy_util_.GetMethodKey()] = "GET";
req2_block[spdy_util_.GetHostKey()] = "www.example.org:8080";
req2_block[spdy_util_.GetSchemeKey()] = "http";
req2_block[spdy_util_.GetPathKey()] = "/";
scoped_ptr<SpdyFrame> req2(
spdy_util_.ConstructSpdySyn(3, req2_block, MEDIUM, true));
MockWrite writes1[] = {
CreateMockWrite(*connect, 0), CreateMockWrite(*wrapped_req1, 2),
CreateMockWrite(*req2, 6),
};
scoped_ptr<SpdyFrame> conn_resp(
spdy_util_.ConstructSpdyGetSynReply(nullptr, 0, 1));
scoped_ptr<SpdyFrame> resp1(
spdy_util_wrapped.ConstructSpdyGetSynReply(nullptr, 0, 1));
scoped_ptr<SpdyFrame> body1(
spdy_util_wrapped.ConstructSpdyBodyFrame(1, true));
scoped_ptr<SpdyFrame> wrapped_resp1(
spdy_util_wrapped.ConstructWrappedSpdyFrame(resp1, 1));
scoped_ptr<SpdyFrame> wrapped_body1(
spdy_util_wrapped.ConstructWrappedSpdyFrame(body1, 1));
scoped_ptr<SpdyFrame> resp2(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 3));
scoped_ptr<SpdyFrame> body2(spdy_util_.ConstructSpdyBodyFrame(3, true));
MockRead reads1[] = {
CreateMockRead(*conn_resp, 1),
MockRead(ASYNC, ERR_IO_PENDING, 3),
CreateMockRead(*wrapped_resp1, 4),
CreateMockRead(*wrapped_body1, 5),
MockRead(ASYNC, ERR_IO_PENDING, 7),
CreateMockRead(*resp2, 8),
CreateMockRead(*body2, 9),
MockRead(SYNCHRONOUS, ERR_IO_PENDING, 10),
};
SequencedSocketData data1(reads1, arraysize(reads1), writes1,
arraysize(writes1));
MockConnect connect_data1(ASYNC, OK);
data1.set_connect_data(connect_data1);
session_deps_.proxy_service =
ProxyService::CreateFixedFromPacResult("HTTPS proxy:70");
TestNetLog log;
session_deps_.net_log = &log;
SSLSocketDataProvider ssl1(ASYNC, OK); // to the proxy
ssl1.SetNextProto(GetProtocol());
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl1);
SSLSocketDataProvider ssl2(ASYNC, OK); // to the server
ssl2.SetNextProto(GetProtocol());
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl2);
session_deps_.socket_factory->AddSocketDataProvider(&data1);
scoped_ptr<HttpNetworkSession> session = CreateSession(&session_deps_);
// Start the first transaction to set up the SpdySession
HttpRequestInfo request1;
request1.method = "GET";
request1.url = GURL(https_url);
request1.load_flags = 0;
HttpNetworkTransaction trans1(LOWEST, session.get());
TestCompletionCallback callback1;
int rv = trans1.Start(&request1, callback1.callback(), BoundNetLog());
// This pause is a hack to avoid running into https://crbug.com/497228.
data1.RunUntilPaused();
base::RunLoop().RunUntilIdle();
data1.Resume();
EXPECT_EQ(OK, callback1.GetResult(rv));
EXPECT_TRUE(trans1.GetResponseInfo()->was_fetched_via_spdy);
LoadTimingInfo load_timing_info1;
EXPECT_TRUE(trans1.GetLoadTimingInfo(&load_timing_info1));
TestLoadTimingNotReusedWithPac(load_timing_info1,
CONNECT_TIMING_HAS_SSL_TIMES);
// Now, start the HTTP request.
HttpRequestInfo request2;
request2.method = "GET";
request2.url = GURL(http_url);
request2.load_flags = 0;
HttpNetworkTransaction trans2(MEDIUM, session.get());
TestCompletionCallback callback2;
rv = trans2.Start(&request2, callback2.callback(), BoundNetLog());
// This pause is a hack to avoid running into https://crbug.com/497228.
data1.RunUntilPaused();
base::RunLoop().RunUntilIdle();
data1.Resume();
EXPECT_EQ(OK, callback2.GetResult(rv));
EXPECT_TRUE(trans2.GetResponseInfo()->was_fetched_via_spdy);
LoadTimingInfo load_timing_info2;
EXPECT_TRUE(trans2.GetLoadTimingInfo(&load_timing_info2));
// The established SPDY sessions is considered reused by the HTTP request.
TestLoadTimingReusedWithPac(load_timing_info2);
// HTTP requests over a SPDY session should have a different connection
// socket_log_id than requests over a tunnel.
EXPECT_NE(load_timing_info1.socket_log_id, load_timing_info2.socket_log_id);
}
// Test that in the case where we have a SPDY session to a SPDY proxy
// that we do not pool other origins that resolve to the same IP when
// the certificate does not match the new origin.
// http://crbug.com/134690
TEST_P(HttpNetworkTransactionTest, DoNotUseSpdySessionIfCertDoesNotMatch) {
const std::string url1 = "http://www.example.org/";
const std::string url2 = "https://news.example.org/";
const std::string ip_addr = "1.2.3.4";
// Second SpdyTestUtil instance for the second socket.
SpdyTestUtil spdy_util_secure(GetProtocol(), GetDependenciesFromPriority());
// SPDY GET for HTTP URL (through SPDY proxy)
scoped_ptr<SpdyHeaderBlock> headers(
spdy_util_.ConstructGetHeaderBlockForProxy("http://www.example.org/"));
scoped_ptr<SpdyFrame> req1(
spdy_util_.ConstructSpdySyn(1, *headers, LOWEST, true));
MockWrite writes1[] = {
CreateMockWrite(*req1, 0),
};
scoped_ptr<SpdyFrame> resp1(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
scoped_ptr<SpdyFrame> body1(spdy_util_.ConstructSpdyBodyFrame(1, true));
MockRead reads1[] = {
MockRead(ASYNC, ERR_IO_PENDING, 1), CreateMockRead(*resp1, 2),
CreateMockRead(*body1, 3), MockRead(ASYNC, OK, 4), // EOF
};
SequencedSocketData data1(reads1, arraysize(reads1), writes1,
arraysize(writes1));
IPAddress ip;
ASSERT_TRUE(ip.AssignFromIPLiteral(ip_addr));
IPEndPoint peer_addr = IPEndPoint(ip, 443);
MockConnect connect_data1(ASYNC, OK, peer_addr);
data1.set_connect_data(connect_data1);
// SPDY GET for HTTPS URL (direct)
scoped_ptr<SpdyFrame> req2(
spdy_util_secure.ConstructSpdyGet(url2.c_str(), 1, MEDIUM));
MockWrite writes2[] = {
CreateMockWrite(*req2, 0),
};
scoped_ptr<SpdyFrame> resp2(
spdy_util_secure.ConstructSpdyGetSynReply(NULL, 0, 1));
scoped_ptr<SpdyFrame> body2(spdy_util_secure.ConstructSpdyBodyFrame(1, true));
MockRead reads2[] = {CreateMockRead(*resp2, 1), CreateMockRead(*body2, 2),
MockRead(ASYNC, OK, 3)};
SequencedSocketData data2(reads2, arraysize(reads2), writes2,
arraysize(writes2));
MockConnect connect_data2(ASYNC, OK);
data2.set_connect_data(connect_data2);
// Set up a proxy config that sends HTTP requests to a proxy, and
// all others direct.
ProxyConfig proxy_config;
proxy_config.proxy_rules().ParseFromString("http=https://proxy:443");
session_deps_.proxy_service.reset(new ProxyService(
make_scoped_ptr(new ProxyConfigServiceFixed(proxy_config)), nullptr,
NULL));
SSLSocketDataProvider ssl1(ASYNC, OK); // to the proxy
ssl1.SetNextProto(GetProtocol());
// Load a valid cert. Note, that this does not need to
// be valid for proxy because the MockSSLClientSocket does
// not actually verify it. But SpdySession will use this
// to see if it is valid for the new origin
ssl1.cert = ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem");
ASSERT_TRUE(ssl1.cert.get());
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl1);
session_deps_.socket_factory->AddSocketDataProvider(&data1);
SSLSocketDataProvider ssl2(ASYNC, OK); // to the server
ssl2.SetNextProto(GetProtocol());
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl2);
session_deps_.socket_factory->AddSocketDataProvider(&data2);
session_deps_.host_resolver.reset(new MockCachingHostResolver());
session_deps_.host_resolver->rules()->AddRule("news.example.org", ip_addr);
session_deps_.host_resolver->rules()->AddRule("proxy", ip_addr);
scoped_ptr<HttpNetworkSession> session = CreateSession(&session_deps_);
// Start the first transaction to set up the SpdySession
HttpRequestInfo request1;
request1.method = "GET";
request1.url = GURL(url1);
request1.load_flags = 0;
HttpNetworkTransaction trans1(LOWEST, session.get());
TestCompletionCallback callback1;
ASSERT_EQ(ERR_IO_PENDING,
trans1.Start(&request1, callback1.callback(), BoundNetLog()));
// This pause is a hack to avoid running into https://crbug.com/497228.
data1.RunUntilPaused();
base::RunLoop().RunUntilIdle();
data1.Resume();
EXPECT_EQ(OK, callback1.WaitForResult());
EXPECT_TRUE(trans1.GetResponseInfo()->was_fetched_via_spdy);
// Now, start the HTTP request
HttpRequestInfo request2;
request2.method = "GET";
request2.url = GURL(url2);
request2.load_flags = 0;
HttpNetworkTransaction trans2(MEDIUM, session.get());
TestCompletionCallback callback2;
EXPECT_EQ(ERR_IO_PENDING,
trans2.Start(&request2, callback2.callback(), BoundNetLog()));
base::MessageLoop::current()->RunUntilIdle();
ASSERT_TRUE(callback2.have_result());
EXPECT_EQ(OK, callback2.WaitForResult());
EXPECT_TRUE(trans2.GetResponseInfo()->was_fetched_via_spdy);
}
// Test to verify that a failed socket read (due to an ERR_CONNECTION_CLOSED
// error) in SPDY session, removes the socket from pool and closes the SPDY
// session. Verify that new url's from the same HttpNetworkSession (and a new
// SpdySession) do work. http://crbug.com/224701
TEST_P(HttpNetworkTransactionTest, ErrorSocketNotConnected) {
const std::string https_url = "https://www.example.org/";
MockRead reads1[] = {
MockRead(SYNCHRONOUS, ERR_CONNECTION_CLOSED, 0)
};
SequencedSocketData data1(reads1, arraysize(reads1), NULL, 0);
scoped_ptr<SpdyFrame> req2(
spdy_util_.ConstructSpdyGet(https_url.c_str(), 1, MEDIUM));
MockWrite writes2[] = {
CreateMockWrite(*req2, 0),
};
scoped_ptr<SpdyFrame> resp2(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
scoped_ptr<SpdyFrame> body2(spdy_util_.ConstructSpdyBodyFrame(1, true));
MockRead reads2[] = {
CreateMockRead(*resp2, 1),
CreateMockRead(*body2, 2),
MockRead(ASYNC, OK, 3) // EOF
};
SequencedSocketData data2(reads2, arraysize(reads2), writes2,
arraysize(writes2));
SSLSocketDataProvider ssl1(ASYNC, OK);
ssl1.SetNextProto(GetProtocol());
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl1);
session_deps_.socket_factory->AddSocketDataProvider(&data1);
SSLSocketDataProvider ssl2(ASYNC, OK);
ssl2.SetNextProto(GetProtocol());
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl2);
session_deps_.socket_factory->AddSocketDataProvider(&data2);
scoped_ptr<HttpNetworkSession> session(
SpdySessionDependencies::SpdyCreateSession(&session_deps_));
// Start the first transaction to set up the SpdySession and verify that
// connection was closed.
HttpRequestInfo request1;
request1.method = "GET";
request1.url = GURL(https_url);
request1.load_flags = 0;
HttpNetworkTransaction trans1(MEDIUM, session.get());
TestCompletionCallback callback1;
EXPECT_EQ(ERR_IO_PENDING,
trans1.Start(&request1, callback1.callback(), BoundNetLog()));
EXPECT_EQ(ERR_CONNECTION_CLOSED, callback1.WaitForResult());
// Now, start the second request and make sure it succeeds.
HttpRequestInfo request2;
request2.method = "GET";
request2.url = GURL(https_url);
request2.load_flags = 0;
HttpNetworkTransaction trans2(MEDIUM, session.get());
TestCompletionCallback callback2;
EXPECT_EQ(ERR_IO_PENDING,
trans2.Start(&request2, callback2.callback(), BoundNetLog()));
ASSERT_EQ(OK, callback2.WaitForResult());
EXPECT_TRUE(trans2.GetResponseInfo()->was_fetched_via_spdy);
}
TEST_P(HttpNetworkTransactionTest, CloseIdleSpdySessionToOpenNewOne) {
ClientSocketPoolManager::set_max_sockets_per_group(
HttpNetworkSession::NORMAL_SOCKET_POOL, 1);
ClientSocketPoolManager::set_max_sockets_per_pool(
HttpNetworkSession::NORMAL_SOCKET_POOL, 1);
// Use two different hosts with different IPs so they don't get pooled.
session_deps_.host_resolver->rules()->AddRule("www.a.com", "10.0.0.1");
session_deps_.host_resolver->rules()->AddRule("www.b.com", "10.0.0.2");
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
SSLSocketDataProvider ssl1(ASYNC, OK);
ssl1.SetNextProto(GetProtocol());
SSLSocketDataProvider ssl2(ASYNC, OK);
ssl2.SetNextProto(GetProtocol());
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl1);
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl2);
scoped_ptr<SpdyFrame> host1_req(
spdy_util_.ConstructSpdyGet("https://www.a.com", 1, DEFAULT_PRIORITY));
MockWrite spdy1_writes[] = {
CreateMockWrite(*host1_req, 0),
};
scoped_ptr<SpdyFrame> host1_resp(
spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
scoped_ptr<SpdyFrame> host1_resp_body(
spdy_util_.ConstructSpdyBodyFrame(1, true));
MockRead spdy1_reads[] = {
CreateMockRead(*host1_resp, 1), CreateMockRead(*host1_resp_body, 2),
MockRead(SYNCHRONOUS, ERR_IO_PENDING, 3),
};
// Use a separate test instance for the separate SpdySession that will be
// created.
SpdyTestUtil spdy_util_2(GetProtocol(), GetDependenciesFromPriority());
scoped_ptr<SequencedSocketData> spdy1_data(
new SequencedSocketData(spdy1_reads, arraysize(spdy1_reads), spdy1_writes,
arraysize(spdy1_writes)));
session_deps_.socket_factory->AddSocketDataProvider(spdy1_data.get());
scoped_ptr<SpdyFrame> host2_req(
spdy_util_2.ConstructSpdyGet("https://www.b.com", 1, DEFAULT_PRIORITY));
MockWrite spdy2_writes[] = {
CreateMockWrite(*host2_req, 0),
};
scoped_ptr<SpdyFrame> host2_resp(
spdy_util_2.ConstructSpdyGetSynReply(NULL, 0, 1));
scoped_ptr<SpdyFrame> host2_resp_body(
spdy_util_2.ConstructSpdyBodyFrame(1, true));
MockRead spdy2_reads[] = {
CreateMockRead(*host2_resp, 1), CreateMockRead(*host2_resp_body, 2),
MockRead(SYNCHRONOUS, ERR_IO_PENDING, 3),
};
scoped_ptr<SequencedSocketData> spdy2_data(
new SequencedSocketData(spdy2_reads, arraysize(spdy2_reads), spdy2_writes,
arraysize(spdy2_writes)));
session_deps_.socket_factory->AddSocketDataProvider(spdy2_data.get());
MockWrite http_write[] = {
MockWrite("GET / HTTP/1.1\r\n"
"Host: www.a.com\r\n"
"Connection: keep-alive\r\n\r\n"),
};
MockRead http_read[] = {
MockRead("HTTP/1.1 200 OK\r\n"),
MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"),
MockRead("Content-Length: 6\r\n\r\n"),
MockRead("hello!"),
};
StaticSocketDataProvider http_data(http_read, arraysize(http_read),
http_write, arraysize(http_write));
session_deps_.socket_factory->AddSocketDataProvider(&http_data);
HostPortPair host_port_pair_a("www.a.com", 443);
SpdySessionKey spdy_session_key_a(
host_port_pair_a, ProxyServer::Direct(), PRIVACY_MODE_DISABLED);
EXPECT_FALSE(
HasSpdySession(session->spdy_session_pool(), spdy_session_key_a));
TestCompletionCallback callback;
HttpRequestInfo request1;
request1.method = "GET";
request1.url = GURL("https://www.a.com/");
request1.load_flags = 0;
scoped_ptr<HttpNetworkTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
int rv = trans->Start(&request1, 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.get() != NULL);
EXPECT_EQ("HTTP/1.1 200", response->headers->GetStatusLine());
EXPECT_TRUE(response->was_fetched_via_spdy);
EXPECT_TRUE(response->was_npn_negotiated);
std::string response_data;
ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data));
EXPECT_EQ("hello!", response_data);
trans.reset();
EXPECT_TRUE(
HasSpdySession(session->spdy_session_pool(), spdy_session_key_a));
HostPortPair host_port_pair_b("www.b.com", 443);
SpdySessionKey spdy_session_key_b(
host_port_pair_b, ProxyServer::Direct(), PRIVACY_MODE_DISABLED);
EXPECT_FALSE(
HasSpdySession(session->spdy_session_pool(), spdy_session_key_b));
HttpRequestInfo request2;
request2.method = "GET";
request2.url = GURL("https://www.b.com/");
request2.load_flags = 0;
trans.reset(new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
rv = trans->Start(&request2, 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.get() != NULL);
EXPECT_EQ("HTTP/1.1 200", 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);
EXPECT_FALSE(
HasSpdySession(session->spdy_session_pool(), spdy_session_key_a));
EXPECT_TRUE(
HasSpdySession(session->spdy_session_pool(), spdy_session_key_b));
HostPortPair host_port_pair_a1("www.a.com", 80);
SpdySessionKey spdy_session_key_a1(
host_port_pair_a1, ProxyServer::Direct(), PRIVACY_MODE_DISABLED);
EXPECT_FALSE(
HasSpdySession(session->spdy_session_pool(), spdy_session_key_a1));
HttpRequestInfo request3;
request3.method = "GET";
request3.url = GURL("http://www.a.com/");
request3.load_flags = 0;
trans.reset(new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
rv = trans->Start(&request3, 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.get() != 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!", response_data);
EXPECT_FALSE(
HasSpdySession(session->spdy_session_pool(), spdy_session_key_a));
EXPECT_FALSE(
HasSpdySession(session->spdy_session_pool(), spdy_session_key_b));
}
TEST_P(HttpNetworkTransactionTest, HttpSyncConnectError) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.example.org/");
request.load_flags = 0;
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
MockConnect mock_connect(SYNCHRONOUS, ERR_NAME_NOT_RESOLVED);
StaticSocketDataProvider data;
data.set_connect_data(mock_connect);
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_NAME_NOT_RESOLVED, rv);
// We don't care whether this succeeds or fails, but it shouldn't crash.
HttpRequestHeaders request_headers;
trans->GetFullRequestHeaders(&request_headers);
ConnectionAttempts attempts;
trans->GetConnectionAttempts(&attempts);
ASSERT_EQ(1u, attempts.size());
EXPECT_EQ(ERR_NAME_NOT_RESOLVED, attempts[0].result);
IPEndPoint endpoint;
EXPECT_FALSE(trans->GetRemoteEndpoint(&endpoint));
EXPECT_TRUE(endpoint.address().empty());
}
TEST_P(HttpNetworkTransactionTest, HttpAsyncConnectError) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.example.org/");
request.load_flags = 0;
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
MockConnect mock_connect(ASYNC, ERR_NAME_NOT_RESOLVED);
StaticSocketDataProvider data;
data.set_connect_data(mock_connect);
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_NAME_NOT_RESOLVED, rv);
// We don't care whether this succeeds or fails, but it shouldn't crash.
HttpRequestHeaders request_headers;
trans->GetFullRequestHeaders(&request_headers);
ConnectionAttempts attempts;
trans->GetConnectionAttempts(&attempts);
ASSERT_EQ(1u, attempts.size());
EXPECT_EQ(ERR_NAME_NOT_RESOLVED, attempts[0].result);
IPEndPoint endpoint;
EXPECT_FALSE(trans->GetRemoteEndpoint(&endpoint));
EXPECT_TRUE(endpoint.address().empty());
}
TEST_P(HttpNetworkTransactionTest, HttpSyncWriteError) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.example.org/");
request.load_flags = 0;
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
MockWrite data_writes[] = {
MockWrite(SYNCHRONOUS, ERR_CONNECTION_RESET),
};
MockRead data_reads[] = {
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(ERR_CONNECTION_RESET, rv);
HttpRequestHeaders request_headers;
EXPECT_TRUE(trans->GetFullRequestHeaders(&request_headers));
EXPECT_TRUE(request_headers.HasHeader("Host"));
}
TEST_P(HttpNetworkTransactionTest, HttpAsyncWriteError) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.example.org/");
request.load_flags = 0;
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
MockWrite data_writes[] = {
MockWrite(ASYNC, ERR_CONNECTION_RESET),
};
MockRead data_reads[] = {
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(ERR_CONNECTION_RESET, rv);
HttpRequestHeaders request_headers;
EXPECT_TRUE(trans->GetFullRequestHeaders(&request_headers));
EXPECT_TRUE(request_headers.HasHeader("Host"));
}
TEST_P(HttpNetworkTransactionTest, HttpSyncReadError) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.example.org/");
request.load_flags = 0;
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
MockWrite data_writes[] = {
MockWrite(
"GET / HTTP/1.1\r\n"
"Host: www.example.org\r\n"
"Connection: keep-alive\r\n\r\n"),
};
MockRead data_reads[] = {
MockRead(SYNCHRONOUS, ERR_CONNECTION_RESET),
};
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(ERR_CONNECTION_RESET, rv);
HttpRequestHeaders request_headers;
EXPECT_TRUE(trans->GetFullRequestHeaders(&request_headers));
EXPECT_TRUE(request_headers.HasHeader("Host"));
}
TEST_P(HttpNetworkTransactionTest, HttpAsyncReadError) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.example.org/");
request.load_flags = 0;
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
MockWrite data_writes[] = {
MockWrite(
"GET / HTTP/1.1\r\n"
"Host: www.example.org\r\n"
"Connection: keep-alive\r\n\r\n"),
};
MockRead data_reads[] = {
MockRead(ASYNC, ERR_CONNECTION_RESET),
};
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(ERR_CONNECTION_RESET, rv);
HttpRequestHeaders request_headers;
EXPECT_TRUE(trans->GetFullRequestHeaders(&request_headers));
EXPECT_TRUE(request_headers.HasHeader("Host"));
}
TEST_P(HttpNetworkTransactionTest, GetFullRequestHeadersIncludesExtraHeader) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.example.org/");
request.load_flags = 0;
request.extra_headers.SetHeader("X-Foo", "bar");
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
MockWrite data_writes[] = {
MockWrite(
"GET / HTTP/1.1\r\n"
"Host: www.example.org\r\n"
"Connection: keep-alive\r\n"
"X-Foo: bar\r\n\r\n"),
};
MockRead data_reads[] = {
MockRead("HTTP/1.1 200 OK\r\n"
"Content-Length: 5\r\n\r\n"
"hello"),
MockRead(ASYNC, ERR_UNEXPECTED),
};
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);
HttpRequestHeaders request_headers;
EXPECT_TRUE(trans->GetFullRequestHeaders(&request_headers));
std::string foo;
EXPECT_TRUE(request_headers.GetHeader("X-Foo", &foo));
EXPECT_EQ("bar", foo);
}
namespace {
// Fake HttpStream that simply records calls to SetPriority().
class FakeStream : public HttpStream,
public base::SupportsWeakPtr<FakeStream> {
public:
explicit FakeStream(RequestPriority priority) : priority_(priority) {}
~FakeStream() override {}
RequestPriority priority() const { return priority_; }
int InitializeStream(const HttpRequestInfo* request_info,
RequestPriority priority,
const BoundNetLog& net_log,
const CompletionCallback& callback) override {
return ERR_IO_PENDING;
}
int SendRequest(const HttpRequestHeaders& request_headers,
HttpResponseInfo* response,
const CompletionCallback& callback) override {
ADD_FAILURE();
return ERR_UNEXPECTED;
}
int ReadResponseHeaders(const CompletionCallback& callback) override {
ADD_FAILURE();
return ERR_UNEXPECTED;
}
int ReadResponseBody(IOBuffer* buf,
int buf_len,
const CompletionCallback& callback) override {
ADD_FAILURE();
return ERR_UNEXPECTED;
}
void Close(bool not_reusable) override {}
bool IsResponseBodyComplete() const override {
ADD_FAILURE();
return false;
}
bool IsConnectionReused() const override {
ADD_FAILURE();
return false;
}
void SetConnectionReused() override { ADD_FAILURE(); }
bool CanReuseConnection() const override { return false; }
int64_t GetTotalReceivedBytes() const override {
ADD_FAILURE();
return 0;
}
int64_t GetTotalSentBytes() const override {
ADD_FAILURE();
return 0;
}
bool GetLoadTimingInfo(LoadTimingInfo* load_timing_info) const override {
ADD_FAILURE();
return false;
}
void GetSSLInfo(SSLInfo* ssl_info) override { ADD_FAILURE(); }
void GetSSLCertRequestInfo(SSLCertRequestInfo* cert_request_info) override {
ADD_FAILURE();
}
bool GetRemoteEndpoint(IPEndPoint* endpoint) override { return false; }
Error GetSignedEKMForTokenBinding(crypto::ECPrivateKey* key,
std::vector<uint8_t>* out) override {
ADD_FAILURE();
return ERR_NOT_IMPLEMENTED;
}
void Drain(HttpNetworkSession* session) override { ADD_FAILURE(); }
void PopulateNetErrorDetails(NetErrorDetails* details) override { return; }
void SetPriority(RequestPriority priority) override { priority_ = priority; }
UploadProgress GetUploadProgress() const override { return UploadProgress(); }
HttpStream* RenewStreamForAuth() override { return NULL; }
private:
RequestPriority priority_;
DISALLOW_COPY_AND_ASSIGN(FakeStream);
};
// Fake HttpStreamRequest that simply records calls to SetPriority()
// and vends FakeStreams with its current priority.
class FakeStreamRequest : public HttpStreamRequest,
public base::SupportsWeakPtr<FakeStreamRequest> {
public:
FakeStreamRequest(RequestPriority priority,
HttpStreamRequest::Delegate* delegate)
: priority_(priority),
delegate_(delegate),
websocket_stream_create_helper_(NULL) {}
FakeStreamRequest(RequestPriority priority,
HttpStreamRequest::Delegate* delegate,
WebSocketHandshakeStreamBase::CreateHelper* create_helper)
: priority_(priority),
delegate_(delegate),
websocket_stream_create_helper_(create_helper) {}
~FakeStreamRequest() override {}
RequestPriority priority() const { return priority_; }
const WebSocketHandshakeStreamBase::CreateHelper*
websocket_stream_create_helper() const {
return websocket_stream_create_helper_;
}
// Create a new FakeStream and pass it to the request's
// delegate. Returns a weak pointer to the FakeStream.
base::WeakPtr<FakeStream> FinishStreamRequest() {
FakeStream* fake_stream = new FakeStream(priority_);
// Do this before calling OnStreamReady() as OnStreamReady() may
// immediately delete |fake_stream|.
base::WeakPtr<FakeStream> weak_stream = fake_stream->AsWeakPtr();
delegate_->OnStreamReady(SSLConfig(), ProxyInfo(), fake_stream);
return weak_stream;
}
int RestartTunnelWithProxyAuth(const AuthCredentials& credentials) override {
ADD_FAILURE();
return ERR_UNEXPECTED;
}
LoadState GetLoadState() const override {
ADD_FAILURE();
return LoadState();
}
void SetPriority(RequestPriority priority) override { priority_ = priority; }
bool was_npn_negotiated() const override { return false; }
NextProto protocol_negotiated() const override { return kProtoUnknown; }
bool using_spdy() const override { return false; }
const ConnectionAttempts& connection_attempts() const override {
static ConnectionAttempts no_attempts;
return no_attempts;
}
private:
RequestPriority priority_;
HttpStreamRequest::Delegate* const delegate_;
WebSocketHandshakeStreamBase::CreateHelper* websocket_stream_create_helper_;
DISALLOW_COPY_AND_ASSIGN(FakeStreamRequest);
};
// Fake HttpStreamFactory that vends FakeStreamRequests.
class FakeStreamFactory : public HttpStreamFactory {
public:
FakeStreamFactory() {}
~FakeStreamFactory() override {}
// Returns a WeakPtr<> to the last HttpStreamRequest returned by
// RequestStream() (which may be NULL if it was destroyed already).
base::WeakPtr<FakeStreamRequest> last_stream_request() {
return last_stream_request_;
}
HttpStreamRequest* RequestStream(const HttpRequestInfo& info,
RequestPriority priority,
const SSLConfig& server_ssl_config,
const SSLConfig& proxy_ssl_config,
HttpStreamRequest::Delegate* delegate,
const BoundNetLog& net_log) override {
FakeStreamRequest* fake_request = new FakeStreamRequest(priority, delegate);
last_stream_request_ = fake_request->AsWeakPtr();
return fake_request;
}
HttpStreamRequest* RequestBidirectionalStreamImpl(
const HttpRequestInfo& info,
RequestPriority priority,
const SSLConfig& server_ssl_config,
const SSLConfig& proxy_ssl_config,
HttpStreamRequest::Delegate* delegate,
const BoundNetLog& net_log) override {
NOTREACHED();
return nullptr;
}
HttpStreamRequest* RequestWebSocketHandshakeStream(
const HttpRequestInfo& info,
RequestPriority priority,
const SSLConfig& server_ssl_config,
const SSLConfig& proxy_ssl_config,
HttpStreamRequest::Delegate* delegate,
WebSocketHandshakeStreamBase::CreateHelper* create_helper,
const BoundNetLog& net_log) override {
FakeStreamRequest* fake_request =
new FakeStreamRequest(priority, delegate, create_helper);
last_stream_request_ = fake_request->AsWeakPtr();
return fake_request;
}
void PreconnectStreams(int num_streams,
const HttpRequestInfo& info,
const SSLConfig& server_ssl_config,
const SSLConfig& proxy_ssl_config) override {
ADD_FAILURE();
}
const HostMappingRules* GetHostMappingRules() const override {
ADD_FAILURE();
return NULL;
}
private:
base::WeakPtr<FakeStreamRequest> last_stream_request_;
DISALLOW_COPY_AND_ASSIGN(FakeStreamFactory);
};
// TODO(ricea): Maybe unify this with the one in
// url_request_http_job_unittest.cc ?
class FakeWebSocketBasicHandshakeStream : public WebSocketHandshakeStreamBase {
public:
FakeWebSocketBasicHandshakeStream(scoped_ptr<ClientSocketHandle> connection,
bool using_proxy)
: state_(connection.release(), using_proxy) {}
// Fake implementation of HttpStreamBase methods.
// This ends up being quite "real" because this object has to really send data
// on the mock socket. It might be easier to use the real implementation, but
// the fact that the WebSocket code is not compiled on iOS makes that
// difficult.
int InitializeStream(const HttpRequestInfo* request_info,
RequestPriority priority,
const BoundNetLog& net_log,
const CompletionCallback& callback) override {
state_.Initialize(request_info, priority, net_log, callback);
return OK;
}
int SendRequest(const HttpRequestHeaders& request_headers,
HttpResponseInfo* response,
const CompletionCallback& callback) override {
return parser()->SendRequest(state_.GenerateRequestLine(), request_headers,
response, callback);
}
int ReadResponseHeaders(const CompletionCallback& callback) override {
return parser()->ReadResponseHeaders(callback);
}
int ReadResponseBody(IOBuffer* buf,
int buf_len,
const CompletionCallback& callback) override {
NOTREACHED();
return ERR_IO_PENDING;
}
void Close(bool not_reusable) override {
if (parser())
parser()->Close(true);
}
bool IsResponseBodyComplete() const override {
NOTREACHED();
return false;
}
bool IsConnectionReused() const override {
NOTREACHED();
return false;
}
void SetConnectionReused() override { NOTREACHED(); }
bool CanReuseConnection() const override { return false; }
int64_t GetTotalReceivedBytes() const override {
NOTREACHED();
return 0;
}
int64_t GetTotalSentBytes() const override {
NOTREACHED();
return 0;
}
bool GetLoadTimingInfo(LoadTimingInfo* load_timing_info) const override {
NOTREACHED();
return false;
}
void GetSSLInfo(SSLInfo* ssl_info) override {}
void GetSSLCertRequestInfo(SSLCertRequestInfo* cert_request_info) override {
NOTREACHED();
}
bool GetRemoteEndpoint(IPEndPoint* endpoint) override { return false; }
Error GetSignedEKMForTokenBinding(crypto::ECPrivateKey* key,
std::vector<uint8_t>* out) override {
ADD_FAILURE();
return ERR_NOT_IMPLEMENTED;
}
void Drain(HttpNetworkSession* session) override { NOTREACHED(); }
void PopulateNetErrorDetails(NetErrorDetails* details) override { return; }
void SetPriority(RequestPriority priority) override { NOTREACHED(); }
UploadProgress GetUploadProgress() const override {
NOTREACHED();
return UploadProgress();
}
HttpStream* RenewStreamForAuth() override {
NOTREACHED();
return nullptr;
}
// Fake implementation of WebSocketHandshakeStreamBase method(s)
scoped_ptr<WebSocketStream> Upgrade() override {
NOTREACHED();
return scoped_ptr<WebSocketStream>();
}
private:
HttpStreamParser* parser() const { return state_.parser(); }
HttpBasicState state_;
DISALLOW_COPY_AND_ASSIGN(FakeWebSocketBasicHandshakeStream);
};
// TODO(yhirano): Split this class out into a net/websockets file, if it is
// worth doing.
class FakeWebSocketStreamCreateHelper :
public WebSocketHandshakeStreamBase::CreateHelper {
public:
WebSocketHandshakeStreamBase* CreateBasicStream(
scoped_ptr<ClientSocketHandle> connection,
bool using_proxy) override {
return new FakeWebSocketBasicHandshakeStream(std::move(connection),
using_proxy);
}
WebSocketHandshakeStreamBase* CreateSpdyStream(
const base::WeakPtr<SpdySession>& session,
bool use_relative_url) override {
NOTREACHED();
return NULL;
};
~FakeWebSocketStreamCreateHelper() override {}
virtual scoped_ptr<WebSocketStream> Upgrade() {
NOTREACHED();
return scoped_ptr<WebSocketStream>();
}
};
} // namespace
// Make sure that HttpNetworkTransaction passes on its priority to its
// stream request on start.
TEST_P(HttpNetworkTransactionTest, SetStreamRequestPriorityOnStart) {
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
HttpNetworkSessionPeer peer(session.get());
FakeStreamFactory* fake_factory = new FakeStreamFactory();
peer.SetHttpStreamFactory(scoped_ptr<HttpStreamFactory>(fake_factory));
HttpNetworkTransaction trans(LOW, session.get());
ASSERT_TRUE(fake_factory->last_stream_request() == NULL);
HttpRequestInfo request;
TestCompletionCallback callback;
EXPECT_EQ(ERR_IO_PENDING,
trans.Start(&request, callback.callback(), BoundNetLog()));
base::WeakPtr<FakeStreamRequest> fake_request =
fake_factory->last_stream_request();
ASSERT_TRUE(fake_request != NULL);
EXPECT_EQ(LOW, fake_request->priority());
}
// Make sure that HttpNetworkTransaction passes on its priority
// updates to its stream request.
TEST_P(HttpNetworkTransactionTest, SetStreamRequestPriority) {
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
HttpNetworkSessionPeer peer(session.get());
FakeStreamFactory* fake_factory = new FakeStreamFactory();
peer.SetHttpStreamFactory(scoped_ptr<HttpStreamFactory>(fake_factory));
HttpNetworkTransaction trans(LOW, session.get());
HttpRequestInfo request;
TestCompletionCallback callback;
EXPECT_EQ(ERR_IO_PENDING,
trans.Start(&request, callback.callback(), BoundNetLog()));
base::WeakPtr<FakeStreamRequest> fake_request =
fake_factory->last_stream_request();
ASSERT_TRUE(fake_request != NULL);
EXPECT_EQ(LOW, fake_request->priority());
trans.SetPriority(LOWEST);
ASSERT_TRUE(fake_request != NULL);
EXPECT_EQ(LOWEST, fake_request->priority());
}
// Make sure that HttpNetworkTransaction passes on its priority
// updates to its stream.
TEST_P(HttpNetworkTransactionTest, SetStreamPriority) {
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
HttpNetworkSessionPeer peer(session.get());
FakeStreamFactory* fake_factory = new FakeStreamFactory();
peer.SetHttpStreamFactory(scoped_ptr<HttpStreamFactory>(fake_factory));
HttpNetworkTransaction trans(LOW, session.get());
HttpRequestInfo request;
TestCompletionCallback callback;
EXPECT_EQ(ERR_IO_PENDING,
trans.Start(&request, callback.callback(), BoundNetLog()));
base::WeakPtr<FakeStreamRequest> fake_request =
fake_factory->last_stream_request();
ASSERT_TRUE(fake_request != NULL);
base::WeakPtr<FakeStream> fake_stream = fake_request->FinishStreamRequest();
ASSERT_TRUE(fake_stream != NULL);
EXPECT_EQ(LOW, fake_stream->priority());
trans.SetPriority(LOWEST);
EXPECT_EQ(LOWEST, fake_stream->priority());
}
TEST_P(HttpNetworkTransactionTest, CreateWebSocketHandshakeStream) {
// The same logic needs to be tested for both ws: and wss: schemes, but this
// test is already parameterised on NextProto, so it uses a loop to verify
// that the different schemes work.
std::string test_cases[] = {"ws://www.example.org/",
"wss://www.example.org/"};
for (size_t i = 0; i < arraysize(test_cases); ++i) {
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
HttpNetworkSessionPeer peer(session.get());
FakeStreamFactory* fake_factory = new FakeStreamFactory();
FakeWebSocketStreamCreateHelper websocket_stream_create_helper;
peer.SetHttpStreamFactoryForWebSocket(
scoped_ptr<HttpStreamFactory>(fake_factory));
HttpNetworkTransaction trans(LOW, session.get());
trans.SetWebSocketHandshakeStreamCreateHelper(
&websocket_stream_create_helper);
HttpRequestInfo request;
TestCompletionCallback callback;
request.method = "GET";
request.url = GURL(test_cases[i]);
EXPECT_EQ(ERR_IO_PENDING,
trans.Start(&request, callback.callback(), BoundNetLog()));
base::WeakPtr<FakeStreamRequest> fake_request =
fake_factory->last_stream_request();
ASSERT_TRUE(fake_request != NULL);
EXPECT_EQ(&websocket_stream_create_helper,
fake_request->websocket_stream_create_helper());
}
}
// Tests that when a used socket is returned to the SSL socket pool, it's closed
// if the transport socket pool is stalled on the global socket limit.
TEST_P(HttpNetworkTransactionTest, CloseSSLSocketOnIdleForHttpRequest) {
ClientSocketPoolManager::set_max_sockets_per_group(
HttpNetworkSession::NORMAL_SOCKET_POOL, 1);
ClientSocketPoolManager::set_max_sockets_per_pool(
HttpNetworkSession::NORMAL_SOCKET_POOL, 1);
// Set up SSL request.
HttpRequestInfo ssl_request;
ssl_request.method = "GET";
ssl_request.url = GURL("https://www.example.org/");
MockWrite ssl_writes[] = {
MockWrite(
"GET / HTTP/1.1\r\n"
"Host: www.example.org\r\n"
"Connection: keep-alive\r\n\r\n"),
};
MockRead ssl_reads[] = {
MockRead("HTTP/1.1 200 OK\r\n"),
MockRead("Content-Length: 11\r\n\r\n"),
MockRead("hello world"),
MockRead(SYNCHRONOUS, OK),
};
StaticSocketDataProvider ssl_data(ssl_reads, arraysize(ssl_reads),
ssl_writes, arraysize(ssl_writes));
session_deps_.socket_factory->AddSocketDataProvider(&ssl_data);
SSLSocketDataProvider ssl(ASYNC, OK);
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
// Set up HTTP request.
HttpRequestInfo http_request;
http_request.method = "GET";
http_request.url = GURL("http://www.example.org/");
MockWrite http_writes[] = {
MockWrite(
"GET / HTTP/1.1\r\n"
"Host: www.example.org\r\n"
"Connection: keep-alive\r\n\r\n"),
};
MockRead http_reads[] = {
MockRead("HTTP/1.1 200 OK\r\n"),
MockRead("Content-Length: 7\r\n\r\n"),
MockRead("falafel"),
MockRead(SYNCHRONOUS, OK),
};
StaticSocketDataProvider http_data(http_reads, arraysize(http_reads),
http_writes, arraysize(http_writes));
session_deps_.socket_factory->AddSocketDataProvider(&http_data);
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
// Start the SSL request.
TestCompletionCallback ssl_callback;
scoped_ptr<HttpTransaction> ssl_trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
ASSERT_EQ(ERR_IO_PENDING,
ssl_trans->Start(&ssl_request, ssl_callback.callback(),
BoundNetLog()));
// Start the HTTP request. Pool should stall.
TestCompletionCallback http_callback;
scoped_ptr<HttpTransaction> http_trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
ASSERT_EQ(ERR_IO_PENDING,
http_trans->Start(&http_request, http_callback.callback(),
BoundNetLog()));
EXPECT_TRUE(IsTransportSocketPoolStalled(session.get()));
// Wait for response from SSL request.
ASSERT_EQ(OK, ssl_callback.WaitForResult());
std::string response_data;
ASSERT_EQ(OK, ReadTransaction(ssl_trans.get(), &response_data));
EXPECT_EQ("hello world", response_data);
// The SSL socket should automatically be closed, so the HTTP request can
// start.
EXPECT_EQ(0, GetIdleSocketCountInSSLSocketPool(session.get()));
ASSERT_FALSE(IsTransportSocketPoolStalled(session.get()));
// The HTTP request can now complete.
ASSERT_EQ(OK, http_callback.WaitForResult());
ASSERT_EQ(OK, ReadTransaction(http_trans.get(), &response_data));
EXPECT_EQ("falafel", response_data);
EXPECT_EQ(1, GetIdleSocketCountInTransportSocketPool(session.get()));
}
// Tests that when a SSL connection is established but there's no corresponding
// request that needs it, the new socket is closed if the transport socket pool
// is stalled on the global socket limit.
TEST_P(HttpNetworkTransactionTest, CloseSSLSocketOnIdleForHttpRequest2) {
ClientSocketPoolManager::set_max_sockets_per_group(
HttpNetworkSession::NORMAL_SOCKET_POOL, 1);
ClientSocketPoolManager::set_max_sockets_per_pool(
HttpNetworkSession::NORMAL_SOCKET_POOL, 1);
// Set up an ssl request.
HttpRequestInfo ssl_request;
ssl_request.method = "GET";
ssl_request.url = GURL("https://www.foopy.com/");
// No data will be sent on the SSL socket.
StaticSocketDataProvider ssl_data;
session_deps_.socket_factory->AddSocketDataProvider(&ssl_data);
SSLSocketDataProvider ssl(ASYNC, OK);
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
// Set up HTTP request.
HttpRequestInfo http_request;
http_request.method = "GET";
http_request.url = GURL("http://www.example.org/");
MockWrite http_writes[] = {
MockWrite(
"GET / HTTP/1.1\r\n"
"Host: www.example.org\r\n"
"Connection: keep-alive\r\n\r\n"),
};
MockRead http_reads[] = {
MockRead("HTTP/1.1 200 OK\r\n"),
MockRead("Content-Length: 7\r\n\r\n"),
MockRead("falafel"),
MockRead(SYNCHRONOUS, OK),
};
StaticSocketDataProvider http_data(http_reads, arraysize(http_reads),
http_writes, arraysize(http_writes));
session_deps_.socket_factory->AddSocketDataProvider(&http_data);
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
// Preconnect an SSL socket. A preconnect is needed because connect jobs are
// cancelled when a normal transaction is cancelled.
HttpStreamFactory* http_stream_factory = session->http_stream_factory();
SSLConfig ssl_config;
session->ssl_config_service()->GetSSLConfig(&ssl_config);
http_stream_factory->PreconnectStreams(1, ssl_request, ssl_config,
ssl_config);
EXPECT_EQ(0, GetIdleSocketCountInSSLSocketPool(session.get()));
// Start the HTTP request. Pool should stall.
TestCompletionCallback http_callback;
scoped_ptr<HttpTransaction> http_trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
ASSERT_EQ(ERR_IO_PENDING,
http_trans->Start(&http_request, http_callback.callback(),
BoundNetLog()));
EXPECT_TRUE(IsTransportSocketPoolStalled(session.get()));
// The SSL connection will automatically be closed once the connection is
// established, to let the HTTP request start.
ASSERT_EQ(OK, http_callback.WaitForResult());
std::string response_data;
ASSERT_EQ(OK, ReadTransaction(http_trans.get(), &response_data));
EXPECT_EQ("falafel", response_data);
EXPECT_EQ(1, GetIdleSocketCountInTransportSocketPool(session.get()));
}
TEST_P(HttpNetworkTransactionTest, PostReadsErrorResponseAfterReset) {
std::vector<scoped_ptr<UploadElementReader>> element_readers;
element_readers.push_back(
make_scoped_ptr(new UploadBytesElementReader("foo", 3)));
ElementsUploadDataStream upload_data_stream(std::move(element_readers), 0);
HttpRequestInfo request;
request.method = "POST";
request.url = GURL("http://www.foo.com/");
request.upload_data_stream = &upload_data_stream;
request.load_flags = 0;
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
// Send headers successfully, but get an error while sending the body.
MockWrite data_writes[] = {
MockWrite("POST / HTTP/1.1\r\n"
"Host: www.foo.com\r\n"
"Connection: keep-alive\r\n"
"Content-Length: 3\r\n\r\n"),
MockWrite(SYNCHRONOUS, ERR_CONNECTION_RESET),
};
MockRead data_reads[] = {
MockRead("HTTP/1.0 400 Not OK\r\n\r\n"),
MockRead("hello world"),
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);
EXPECT_TRUE(response->headers.get() != NULL);
EXPECT_EQ("HTTP/1.0 400 Not 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 makes sure the retry logic doesn't trigger when reading an error
// response from a server that rejected a POST with a CONNECTION_RESET.
TEST_P(HttpNetworkTransactionTest,
PostReadsErrorResponseAfterResetOnReusedSocket) {
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
MockWrite data_writes[] = {
MockWrite("GET / HTTP/1.1\r\n"
"Host: www.foo.com\r\n"
"Connection: keep-alive\r\n\r\n"),
MockWrite("POST / HTTP/1.1\r\n"
"Host: www.foo.com\r\n"
"Connection: keep-alive\r\n"
"Content-Length: 3\r\n\r\n"),
MockWrite(SYNCHRONOUS, ERR_CONNECTION_RESET),
};
MockRead data_reads[] = {
MockRead("HTTP/1.1 200 Peachy\r\n"
"Content-Length: 14\r\n\r\n"),
MockRead("first response"),
MockRead("HTTP/1.1 400 Not OK\r\n"
"Content-Length: 15\r\n\r\n"),
MockRead("second response"),
MockRead(SYNCHRONOUS, OK),
};
StaticSocketDataProvider data(data_reads, arraysize(data_reads), data_writes,
arraysize(data_writes));
session_deps_.socket_factory->AddSocketDataProvider(&data);
TestCompletionCallback callback;
HttpRequestInfo request1;
request1.method = "GET";
request1.url = GURL("http://www.foo.com/");
request1.load_flags = 0;
scoped_ptr<HttpTransaction> trans1(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
int rv = trans1->Start(&request1, callback.callback(), BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback.WaitForResult();
EXPECT_EQ(OK, rv);
const HttpResponseInfo* response1 = trans1->GetResponseInfo();
ASSERT_TRUE(response1 != NULL);
EXPECT_TRUE(response1->headers.get() != NULL);
EXPECT_EQ("HTTP/1.1 200 Peachy", response1->headers->GetStatusLine());
std::string response_data1;
rv = ReadTransaction(trans1.get(), &response_data1);
EXPECT_EQ(OK, rv);
EXPECT_EQ("first response", response_data1);
// Delete the transaction to release the socket back into the socket pool.
trans1.reset();
std::vector<scoped_ptr<UploadElementReader>> element_readers;
element_readers.push_back(
make_scoped_ptr(new UploadBytesElementReader("foo", 3)));
ElementsUploadDataStream upload_data_stream(std::move(element_readers), 0);
HttpRequestInfo request2;
request2.method = "POST";
request2.url = GURL("http://www.foo.com/");
request2.upload_data_stream = &upload_data_stream;
request2.load_flags = 0;
scoped_ptr<HttpTransaction> trans2(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
rv = trans2->Start(&request2, callback.callback(), BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback.WaitForResult();
EXPECT_EQ(OK, rv);
const HttpResponseInfo* response2 = trans2->GetResponseInfo();
ASSERT_TRUE(response2 != NULL);
EXPECT_TRUE(response2->headers.get() != NULL);
EXPECT_EQ("HTTP/1.1 400 Not OK", response2->headers->GetStatusLine());
std::string response_data2;
rv = ReadTransaction(trans2.get(), &response_data2);
EXPECT_EQ(OK, rv);
EXPECT_EQ("second response", response_data2);
}
TEST_P(HttpNetworkTransactionTest,
PostReadsErrorResponseAfterResetPartialBodySent) {
std::vector<scoped_ptr<UploadElementReader>> element_readers;
element_readers.push_back(
make_scoped_ptr(new UploadBytesElementReader("foo", 3)));
ElementsUploadDataStream upload_data_stream(std::move(element_readers), 0);
HttpRequestInfo request;
request.method = "POST";
request.url = GURL("http://www.foo.com/");
request.upload_data_stream = &upload_data_stream;
request.load_flags = 0;
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
// Send headers successfully, but get an error while sending the body.
MockWrite data_writes[] = {
MockWrite("POST / HTTP/1.1\r\n"
"Host: www.foo.com\r\n"
"Connection: keep-alive\r\n"
"Content-Length: 3\r\n\r\n"
"fo"),
MockWrite(SYNCHRONOUS, ERR_CONNECTION_RESET),
};
MockRead data_reads[] = {
MockRead("HTTP/1.0 400 Not OK\r\n\r\n"),
MockRead("hello world"),
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);
EXPECT_TRUE(response->headers.get() != NULL);
EXPECT_EQ("HTTP/1.0 400 Not 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 tests the more common case than the previous test, where headers and
// body are not merged into a single request.
TEST_P(HttpNetworkTransactionTest, ChunkedPostReadsErrorResponseAfterReset) {
ChunkedUploadDataStream upload_data_stream(0);
HttpRequestInfo request;
request.method = "POST";
request.url = GURL("http://www.foo.com/");
request.upload_data_stream = &upload_data_stream;
request.load_flags = 0;
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
// Send headers successfully, but get an error while sending the body.
MockWrite data_writes[] = {
MockWrite("POST / HTTP/1.1\r\n"
"Host: www.foo.com\r\n"
"Connection: keep-alive\r\n"
"Transfer-Encoding: chunked\r\n\r\n"),
MockWrite(SYNCHRONOUS, ERR_CONNECTION_RESET),
};
MockRead data_reads[] = {
MockRead("HTTP/1.0 400 Not OK\r\n\r\n"),
MockRead("hello world"),
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);
// Make sure the headers are sent before adding a chunk. This ensures that
// they can't be merged with the body in a single send. Not currently
// necessary since a chunked body is never merged with headers, but this makes
// the test more future proof.
base::RunLoop().RunUntilIdle();
upload_data_stream.AppendData("last chunk", 10, true);
rv = callback.WaitForResult();
EXPECT_EQ(OK, rv);
const HttpResponseInfo* response = trans->GetResponseInfo();
ASSERT_TRUE(response != NULL);
EXPECT_TRUE(response->headers.get() != NULL);
EXPECT_EQ("HTTP/1.0 400 Not 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_P(HttpNetworkTransactionTest, PostReadsErrorResponseAfterResetAnd100) {
std::vector<scoped_ptr<UploadElementReader>> element_readers;
element_readers.push_back(
make_scoped_ptr(new UploadBytesElementReader("foo", 3)));
ElementsUploadDataStream upload_data_stream(std::move(element_readers), 0);
HttpRequestInfo request;
request.method = "POST";
request.url = GURL("http://www.foo.com/");
request.upload_data_stream = &upload_data_stream;
request.load_flags = 0;
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
MockWrite data_writes[] = {
MockWrite("POST / HTTP/1.1\r\n"
"Host: www.foo.com\r\n"
"Connection: keep-alive\r\n"
"Content-Length: 3\r\n\r\n"),
MockWrite(SYNCHRONOUS, ERR_CONNECTION_RESET),
};
MockRead data_reads[] = {
MockRead("HTTP/1.0 100 Continue\r\n\r\n"),
MockRead("HTTP/1.0 400 Not OK\r\n\r\n"),
MockRead("hello world"),
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);
EXPECT_TRUE(response->headers.get() != NULL);
EXPECT_EQ("HTTP/1.0 400 Not 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_P(HttpNetworkTransactionTest, PostIgnoresNonErrorResponseAfterReset) {
std::vector<scoped_ptr<UploadElementReader>> element_readers;
element_readers.push_back(
make_scoped_ptr(new UploadBytesElementReader("foo", 3)));
ElementsUploadDataStream upload_data_stream(std::move(element_readers), 0);
HttpRequestInfo request;
request.method = "POST";
request.url = GURL("http://www.foo.com/");
request.upload_data_stream = &upload_data_stream;
request.load_flags = 0;
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
// Send headers successfully, but get an error while sending the body.
MockWrite data_writes[] = {
MockWrite("POST / HTTP/1.1\r\n"
"Host: www.foo.com\r\n"
"Connection: keep-alive\r\n"
"Content-Length: 3\r\n\r\n"),
MockWrite(SYNCHRONOUS, ERR_CONNECTION_RESET),
};
MockRead data_reads[] = {
MockRead("HTTP/1.0 200 Just Dandy\r\n\r\n"),
MockRead("hello world"),
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(ERR_CONNECTION_RESET, rv);
}
TEST_P(HttpNetworkTransactionTest,
PostIgnoresNonErrorResponseAfterResetAnd100) {
std::vector<scoped_ptr<UploadElementReader>> element_readers;
element_readers.push_back(
make_scoped_ptr(new UploadBytesElementReader("foo", 3)));
ElementsUploadDataStream upload_data_stream(std::move(element_readers), 0);
HttpRequestInfo request;
request.method = "POST";
request.url = GURL("http://www.foo.com/");
request.upload_data_stream = &upload_data_stream;
request.load_flags = 0;
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
// Send headers successfully, but get an error while sending the body.
MockWrite data_writes[] = {
MockWrite("POST / HTTP/1.1\r\n"
"Host: www.foo.com\r\n"
"Connection: keep-alive\r\n"
"Content-Length: 3\r\n\r\n"),
MockWrite(SYNCHRONOUS, ERR_CONNECTION_RESET),
};
MockRead data_reads[] = {
MockRead("HTTP/1.0 100 Continue\r\n\r\n"),
MockRead("HTTP/1.0 302 Redirect\r\n"),
MockRead("Location: http://somewhere-else.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));
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);
}
TEST_P(HttpNetworkTransactionTest, PostIgnoresHttp09ResponseAfterReset) {
std::vector<scoped_ptr<UploadElementReader>> element_readers;
element_readers.push_back(
make_scoped_ptr(new UploadBytesElementReader("foo", 3)));
ElementsUploadDataStream upload_data_stream(std::move(element_readers), 0);
HttpRequestInfo request;
request.method = "POST";
request.url = GURL("http://www.foo.com/");
request.upload_data_stream = &upload_data_stream;
request.load_flags = 0;
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
// Send headers successfully, but get an error while sending the body.
MockWrite data_writes[] = {
MockWrite("POST / HTTP/1.1\r\n"
"Host: www.foo.com\r\n"
"Connection: keep-alive\r\n"
"Content-Length: 3\r\n\r\n"),
MockWrite(SYNCHRONOUS, ERR_CONNECTION_RESET),
};
MockRead data_reads[] = {
MockRead("HTTP 0.9 rocks!"),
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(ERR_CONNECTION_RESET, rv);
}
TEST_P(HttpNetworkTransactionTest, PostIgnoresPartial400HeadersAfterReset) {
std::vector<scoped_ptr<UploadElementReader>> element_readers;
element_readers.push_back(
make_scoped_ptr(new UploadBytesElementReader("foo", 3)));
ElementsUploadDataStream upload_data_stream(std::move(element_readers), 0);
HttpRequestInfo request;
request.method = "POST";
request.url = GURL("http://www.foo.com/");
request.upload_data_stream = &upload_data_stream;
request.load_flags = 0;
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
// Send headers successfully, but get an error while sending the body.
MockWrite data_writes[] = {
MockWrite("POST / HTTP/1.1\r\n"
"Host: www.foo.com\r\n"
"Connection: keep-alive\r\n"
"Content-Length: 3\r\n\r\n"),
MockWrite(SYNCHRONOUS, ERR_CONNECTION_RESET),
};
MockRead data_reads[] = {
MockRead("HTTP/1.0 400 Not a Full Response\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(ERR_CONNECTION_RESET, rv);
}
// Verify that proxy headers are not sent to the destination server when
// establishing a tunnel for a secure WebSocket connection.
TEST_P(HttpNetworkTransactionTest, ProxyHeadersNotSentOverWssTunnel) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("wss://www.example.org/");
AddWebSocketHeaders(&request.extra_headers);
// Configure against proxy server "myproxy:70".
session_deps_.proxy_service =
ProxyService::CreateFixedFromPacResult("PROXY myproxy:70");
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
// Since a proxy is configured, try to establish a tunnel.
MockWrite data_writes[] = {
MockWrite("CONNECT www.example.org:443 HTTP/1.1\r\n"
"Host: www.example.org:443\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.example.org:443 HTTP/1.1\r\n"
"Host: www.example.org:443\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.example.org\r\n"
"Connection: Upgrade\r\n"
"Upgrade: websocket\r\n"
"Origin: http://www.example.org\r\n"
"Sec-WebSocket-Version: 13\r\n"
"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n\r\n"),
};
// The proxy responds to the connect with a 407, using a persistent
// connection.
MockRead data_reads[] = {
// No credentials.
MockRead("HTTP/1.1 407 Proxy Authentication Required\r\n"),
MockRead("Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n"),
MockRead("Content-Length: 0\r\n"),
MockRead("Proxy-Connection: keep-alive\r\n\r\n"),
MockRead("HTTP/1.1 200 Connection Established\r\n\r\n"),
MockRead("HTTP/1.1 101 Switching Protocols\r\n"),
MockRead("Upgrade: websocket\r\n"),
MockRead("Connection: Upgrade\r\n"),
MockRead("Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n\r\n"),
};
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);
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
FakeWebSocketStreamCreateHelper websocket_stream_create_helper;
trans->SetWebSocketHandshakeStreamCreateHelper(
&websocket_stream_create_helper);
{
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);
ASSERT_TRUE(response->headers.get());
EXPECT_EQ(407, response->headers->response_code());
{
TestCompletionCallback callback;
int rv = trans->RestartWithAuth(AuthCredentials(kFoo, kBar),
callback.callback());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback.WaitForResult();
EXPECT_EQ(OK, rv);
}
response = trans->GetResponseInfo();
ASSERT_TRUE(response);
ASSERT_TRUE(response->headers.get());
EXPECT_EQ(101, response->headers->response_code());
trans.reset();
session->CloseAllConnections();
}
// Verify that proxy headers are not sent to the destination server when
// establishing a tunnel for an insecure WebSocket connection.
// This requires the authentication info to be injected into the auth cache
// due to crbug.com/395064
// TODO(ricea): Change to use a 407 response once issue 395064 is fixed.
TEST_P(HttpNetworkTransactionTest, ProxyHeadersNotSentOverWsTunnel) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("ws://www.example.org/");
AddWebSocketHeaders(&request.extra_headers);
// Configure against proxy server "myproxy:70".
session_deps_.proxy_service =
ProxyService::CreateFixedFromPacResult("PROXY myproxy:70");
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
MockWrite data_writes[] = {
// Try to establish a tunnel for the WebSocket connection, with
// credentials. Because WebSockets have a separate set of socket pools,
// they cannot and will not use the same TCP/IP connection as the
// preflight HTTP request.
MockWrite(
"CONNECT www.example.org:80 HTTP/1.1\r\n"
"Host: www.example.org:80\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.example.org\r\n"
"Connection: Upgrade\r\n"
"Upgrade: websocket\r\n"
"Origin: http://www.example.org\r\n"
"Sec-WebSocket-Version: 13\r\n"
"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n\r\n"),
};
MockRead data_reads[] = {
// HTTP CONNECT with credentials.
MockRead("HTTP/1.1 200 Connection Established\r\n\r\n"),
// WebSocket connection established inside tunnel.
MockRead("HTTP/1.1 101 Switching Protocols\r\n"),
MockRead("Upgrade: websocket\r\n"),
MockRead("Connection: Upgrade\r\n"),
MockRead("Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n\r\n"),
};
StaticSocketDataProvider data(data_reads, arraysize(data_reads), data_writes,
arraysize(data_writes));
session_deps_.socket_factory->AddSocketDataProvider(&data);
session->http_auth_cache()->Add(
GURL("http://myproxy:70/"), "MyRealm1", HttpAuth::AUTH_SCHEME_BASIC,
"Basic realm=MyRealm1", AuthCredentials(kFoo, kBar), "/");
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
FakeWebSocketStreamCreateHelper websocket_stream_create_helper;
trans->SetWebSocketHandshakeStreamCreateHelper(
&websocket_stream_create_helper);
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);
ASSERT_TRUE(response->headers.get());
EXPECT_EQ(101, response->headers->response_code());
trans.reset();
session->CloseAllConnections();
}
TEST_P(HttpNetworkTransactionTest, TotalNetworkBytesPost) {
std::vector<scoped_ptr<UploadElementReader>> element_readers;
element_readers.push_back(
make_scoped_ptr(new UploadBytesElementReader("foo", 3)));
ElementsUploadDataStream upload_data_stream(std::move(element_readers), 0);
HttpRequestInfo request;
request.method = "POST";
request.url = GURL("http://www.foo.com/");
request.upload_data_stream = &upload_data_stream;
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
MockWrite data_writes[] = {
MockWrite("POST / HTTP/1.1\r\n"
"Host: www.foo.com\r\n"
"Connection: keep-alive\r\n"
"Content-Length: 3\r\n\r\n"),
MockWrite("foo"),
};
MockRead data_reads[] = {
MockRead("HTTP/1.1 200 OK\r\n\r\n"), MockRead("hello world"),
MockRead(SYNCHRONOUS, OK),
};
StaticSocketDataProvider data(data_reads, arraysize(data_reads), data_writes,
arraysize(data_writes));
session_deps_.socket_factory->AddSocketDataProvider(&data);
TestCompletionCallback callback;
EXPECT_EQ(ERR_IO_PENDING,
trans->Start(&request, callback.callback(), BoundNetLog()));
EXPECT_EQ(OK, callback.WaitForResult());
std::string response_data;
EXPECT_EQ(OK, ReadTransaction(trans.get(), &response_data));
EXPECT_EQ(CountWriteBytes(data_writes, arraysize(data_writes)),
trans->GetTotalSentBytes());
EXPECT_EQ(CountReadBytes(data_reads, arraysize(data_reads)),
trans->GetTotalReceivedBytes());
}
TEST_P(HttpNetworkTransactionTest, TotalNetworkBytesPost100Continue) {
std::vector<scoped_ptr<UploadElementReader>> element_readers;
element_readers.push_back(
make_scoped_ptr(new UploadBytesElementReader("foo", 3)));
ElementsUploadDataStream upload_data_stream(std::move(element_readers), 0);
HttpRequestInfo request;
request.method = "POST";
request.url = GURL("http://www.foo.com/");
request.upload_data_stream = &upload_data_stream;
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
MockWrite data_writes[] = {
MockWrite("POST / HTTP/1.1\r\n"
"Host: www.foo.com\r\n"
"Connection: keep-alive\r\n"
"Content-Length: 3\r\n\r\n"),
MockWrite("foo"),
};
MockRead data_reads[] = {
MockRead("HTTP/1.1 100 Continue\r\n\r\n"),
MockRead("HTTP/1.1 200 OK\r\n\r\n"), MockRead("hello world"),
MockRead(SYNCHRONOUS, OK),
};
StaticSocketDataProvider data(data_reads, arraysize(data_reads), data_writes,
arraysize(data_writes));
session_deps_.socket_factory->AddSocketDataProvider(&data);
TestCompletionCallback callback;
EXPECT_EQ(ERR_IO_PENDING,
trans->Start(&request, callback.callback(), BoundNetLog()));
EXPECT_EQ(OK, callback.WaitForResult());
std::string response_data;
EXPECT_EQ(OK, ReadTransaction(trans.get(), &response_data));
EXPECT_EQ(CountWriteBytes(data_writes, arraysize(data_writes)),
trans->GetTotalSentBytes());
EXPECT_EQ(CountReadBytes(data_reads, arraysize(data_reads)),
trans->GetTotalReceivedBytes());
}
TEST_P(HttpNetworkTransactionTest, TotalNetworkBytesChunkedPost) {
ChunkedUploadDataStream upload_data_stream(0);
HttpRequestInfo request;
request.method = "POST";
request.url = GURL("http://www.foo.com/");
request.upload_data_stream = &upload_data_stream;
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
// Send headers successfully, but get an error while sending the body.
MockWrite data_writes[] = {
MockWrite("POST / HTTP/1.1\r\n"
"Host: www.foo.com\r\n"
"Connection: keep-alive\r\n"
"Transfer-Encoding: chunked\r\n\r\n"),
MockWrite("1\r\nf\r\n"), MockWrite("2\r\noo\r\n"), MockWrite("0\r\n\r\n"),
};
MockRead data_reads[] = {
MockRead("HTTP/1.1 200 OK\r\n\r\n"), MockRead("hello world"),
MockRead(SYNCHRONOUS, OK),
};
StaticSocketDataProvider data(data_reads, arraysize(data_reads), data_writes,
arraysize(data_writes));
session_deps_.socket_factory->AddSocketDataProvider(&data);
TestCompletionCallback callback;
EXPECT_EQ(ERR_IO_PENDING,
trans->Start(&request, callback.callback(), BoundNetLog()));
base::RunLoop().RunUntilIdle();
upload_data_stream.AppendData("f", 1, false);
base::RunLoop().RunUntilIdle();
upload_data_stream.AppendData("oo", 2, true);
EXPECT_EQ(OK, callback.WaitForResult());
std::string response_data;
EXPECT_EQ(OK, ReadTransaction(trans.get(), &response_data));
EXPECT_EQ(CountWriteBytes(data_writes, arraysize(data_writes)),
trans->GetTotalSentBytes());
EXPECT_EQ(CountReadBytes(data_reads, arraysize(data_reads)),
trans->GetTotalReceivedBytes());
}
TEST_P(HttpNetworkTransactionTest, EnableNPN) {
session_deps_.enable_npn = true;
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
HttpNetworkTransaction trans(DEFAULT_PRIORITY, session.get());
EXPECT_THAT(trans.server_ssl_config_.alpn_protos,
testing::ElementsAre(kProtoHTTP2, kProtoSPDY31, kProtoHTTP11));
EXPECT_THAT(trans.server_ssl_config_.npn_protos,
testing::ElementsAre(kProtoHTTP2, kProtoSPDY31, kProtoHTTP11));
}
TEST_P(HttpNetworkTransactionTest, DisableNPN) {
session_deps_.enable_npn = false;
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
HttpNetworkTransaction trans(DEFAULT_PRIORITY, session.get());
EXPECT_THAT(trans.server_ssl_config_.alpn_protos,
testing::ElementsAre(kProtoHTTP2, kProtoSPDY31, kProtoHTTP11));
EXPECT_TRUE(trans.server_ssl_config_.npn_protos.empty());
}
#if !defined(OS_IOS)
TEST_P(HttpNetworkTransactionTest, TokenBindingSpdy) {
const std::string https_url = "https://www.example.com";
HttpRequestInfo request;
request.url = GURL(https_url);
request.method = "GET";
SSLSocketDataProvider ssl(ASYNC, OK);
ssl.token_binding_negotiated = true;
ssl.token_binding_key_param = TB_PARAM_ECDSAP256;
ssl.SetNextProto(GetProtocol());
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
scoped_ptr<SpdyFrame> resp(
spdy_util_.ConstructSpdyGetSynReply(nullptr, 0, 1));
scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true));
MockRead reads[] = {CreateMockRead(*resp), CreateMockRead(*body),
MockRead(ASYNC, ERR_IO_PENDING)};
StaticSocketDataProvider data(reads, arraysize(reads), nullptr, 0);
session_deps_.socket_factory->AddSocketDataProvider(&data);
session_deps_.channel_id_service.reset(new ChannelIDService(
new DefaultChannelIDStore(nullptr), base::ThreadTaskRunnerHandle::Get()));
scoped_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
HttpNetworkTransaction trans(DEFAULT_PRIORITY, session.get());
TestCompletionCallback callback;
EXPECT_EQ(ERR_IO_PENDING,
trans.Start(&request, callback.callback(), BoundNetLog()));
base::MessageLoop::current()->RunUntilIdle();
EXPECT_TRUE(trans.GetResponseInfo()->was_fetched_via_spdy);
HttpRequestHeaders headers;
ASSERT_TRUE(trans.GetFullRequestHeaders(&headers));
EXPECT_TRUE(headers.HasHeader(HttpRequestHeaders::kTokenBinding));
}
#endif // !defined(OS_IOS)
} // namespace net