blob: 8fbed3e3af52af903275c2acd689443f66904701 [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 <limits>
#include <set>
#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/ptr_util.h"
#include "base/memory/weak_ptr.h"
#include "base/run_loop.h"
#include "base/strings/string_piece.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/test_file_util.h"
#include "base/threading/thread_task_runner_handle.h"
#include "net/base/auth.h"
#include "net/base/chunked_upload_data_stream.h"
#include "net/base/completion_once_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/proxy_server.h"
#include "net/base/request_priority.h"
#include "net/base/test_completion_callback.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/cert_status_flags.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_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_response_info.h"
#include "net/http/http_server_properties_impl.h"
#include "net/http/http_stream.h"
#include "net/http/http_stream_factory.h"
#include "net/http/http_transaction_test_util.h"
#include "net/log/net_log.h"
#include "net/log/net_log_event_type.h"
#include "net/log/net_log_source.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_resolution/mock_proxy_resolver.h"
#include "net/proxy_resolution/proxy_config_service_fixed.h"
#include "net/proxy_resolution/proxy_info.h"
#include "net/proxy_resolution/proxy_resolution_service.h"
#include "net/proxy_resolution/proxy_resolver.h"
#include "net/proxy_resolution/proxy_resolver_factory.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_tag.h"
#include "net/socket/socket_test_util.h"
#include "net/socket/ssl_client_socket.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_info.h"
#include "net/ssl/ssl_private_key.h"
#include "net/test/cert_test_util.h"
#include "net/test/gtest_util.h"
#include "net/test/test_data_directory.h"
#include "net/test/test_with_scoped_task_environment.h"
#include "net/third_party/quiche/src/spdy/core/spdy_framer.h"
#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
#include "net/websockets/websocket_handshake_stream_base.h"
#include "net/websockets/websocket_test_util.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/platform_test.h"
#include "url/gurl.h"
#if defined(NTLM_PORTABLE)
#include "base/base64.h"
#include "net/ntlm/ntlm_test_data.h"
#include "starboard/common/string.h"
#include "starboard/memory.h"
#include "starboard/types.h"
#endif
using net::test::IsError;
using net::test::IsOk;
using base::ASCIIToUTF16;
//-----------------------------------------------------------------------------
namespace net {
namespace {
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 kWrongPassword(ASCIIToUTF16("wrongpassword"));
const char kAlternativeServiceHttpHeader[] =
"Alt-Svc: h2=\"mail.example.org:443\"\r\n";
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(NetLogSource::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(NetLogSource::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(NetLogSource::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(NetLogSource::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());
}
std::unique_ptr<HttpNetworkSession> CreateSession(
SpdySessionDependencies* session_deps) {
return SpdySessionDependencies::SpdyCreateSession(session_deps);
}
class FailingProxyResolverFactory : public ProxyResolverFactory {
public:
FailingProxyResolverFactory() : ProxyResolverFactory(false) {}
// ProxyResolverFactory override.
int CreateProxyResolver(const scoped_refptr<PacFileData>& script_data,
std::unique_ptr<ProxyResolver>* result,
CompletionOnceCallback callback,
std::unique_ptr<Request>* request) override {
return ERR_PAC_SCRIPT_FAILED;
}
};
class TestSSLConfigService : public SSLConfigService {
public:
explicit TestSSLConfigService(const SSLConfig& config) : config_(config) {}
~TestSSLConfigService() override = default;
void GetSSLConfig(SSLConfig* config) override { *config = config_; }
bool CanShareConnectionWithClientCerts(
const std::string& hostname) const override {
return false;
}
private:
SSLConfig config_;
};
} // namespace
class HttpNetworkTransactionTest : public PlatformTest,
public WithScopedTaskEnvironment {
public:
~HttpNetworkTransactionTest() override {
// 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()
: ssl_(ASYNC, OK),
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_http2_alternative_service = true;
}
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::RunLoop().RunUntilIdle();
}
void TearDown() override {
NetworkChangeNotifier::NotifyObserversOfIPAddressChangeForTests();
base::RunLoop().RunUntilIdle();
// Empty the current queue.
base::RunLoop().RunUntilIdle();
PlatformTest::TearDown();
NetworkChangeNotifier::NotifyObserversOfIPAddressChangeForTests();
base::RunLoop().RunUntilIdle();
}
// 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(
base::span<StaticSocketDataProvider*> providers) {
SimpleGetHelperResult out;
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.example.org/");
request.traffic_annotation =
net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS);
BoundTestNetLog log;
session_deps_.net_log = log.bound().net_log();
std::unique_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
HttpNetworkTransaction trans(DEFAULT_PRIORITY, session.get());
for (auto* provider : providers) {
session_deps_.socket_factory->AddSocketDataProvider(provider);
}
TestCompletionCallback callback;
EXPECT_TRUE(log.bound().IsCapturing());
int rv = trans.Start(&request, callback.callback(), log.bound());
EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
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 || !response->headers) {
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, &out.response_data);
EXPECT_THAT(rv, IsOk());
TestNetLogEntry::List entries;
log.GetEntries(&entries);
size_t pos = ExpectLogContainsSomewhere(
entries, 0, NetLogEventType::HTTP_TRANSACTION_SEND_REQUEST_HEADERS,
NetLogEventPhase::NONE);
ExpectLogContainsSomewhere(
entries, pos, NetLogEventType::HTTP_TRANSACTION_READ_RESPONSE_HEADERS,
NetLogEventPhase::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(base::span<const MockRead> data_reads) {
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, data_writes);
StaticSocketDataProvider* data[] = {&reads};
SimpleGetHelperResult out = SimpleGetHelperForData(data);
EXPECT_EQ(CountWriteBytes(data_writes), out.total_sent_bytes);
return out;
}
void AddSSLSocketData() {
ssl_.next_proto = kProtoHTTP2;
ssl_.ssl_info.cert =
ImportCertFromFile(GetTestCertsDirectory(), "spdy_pooling.pem");
ASSERT_TRUE(ssl_.ssl_info.cert);
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_);
}
void ConnectStatusHelperWithExpectedStatus(const MockRead& status,
int expected_status);
void ConnectStatusHelper(const MockRead& status);
void CheckErrorIsPassedBack(int error, IoMode mode);
SpdyTestUtil spdy_util_;
SpdySessionDependencies session_deps_;
SSLSocketDataProvider ssl_;
// 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_;
};
namespace {
class BeforeHeadersSentHandler {
public:
BeforeHeadersSentHandler()
: observed_before_headers_sent_with_proxy_(false),
observed_before_headers_sent_(false) {}
void OnBeforeHeadersSent(const ProxyInfo& proxy_info,
HttpRequestHeaders* request_headers) {
observed_before_headers_sent_ = true;
if (!proxy_info.is_http() && !proxy_info.is_https() &&
!proxy_info.is_quic()) {
return;
}
observed_before_headers_sent_with_proxy_ = true;
observed_proxy_server_uri_ = proxy_info.proxy_server().ToURI();
}
bool observed_before_headers_sent_with_proxy() const {
return observed_before_headers_sent_with_proxy_;
}
bool observed_before_headers_sent() const {
return observed_before_headers_sent_;
}
std::string observed_proxy_server_uri() const {
return observed_proxy_server_uri_;
}
private:
bool observed_before_headers_sent_with_proxy_;
bool observed_before_headers_sent_;
std::string observed_proxy_server_uri_;
DISALLOW_COPY_AND_ASSIGN(BeforeHeadersSentHandler);
};
// 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)
uint64_t MockGetMSTime() {
// Tue, 23 May 2017 20:13:07 +0000
return 131400439870000000;
}
// Alternative functions that eliminate randomness and dependency on the local
// host name so that the generated NTLM messages are reproducible.
void MockGenerateRandom(uint8_t* output, size_t n) {
// This is set to 0xaa because the client challenge for testing in
// [MS-NLMP] Section 4.2.1 is 8 bytes of 0xaa.
memset(output, 0xaa, n);
}
std::string MockGetHostName() {
return ntlm::test::kHostnameAscii;
}
#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_;
}
bool socket_requested() const { return socket_requested_; }
int RequestSocket(const std::string& group_name,
const void* socket_params,
RequestPriority priority,
const SocketTag& socket_tag,
ClientSocketPool::RespectLimits respect_limits,
ClientSocketHandle* handle,
CompletionOnceCallback callback,
const NetLogWithSource& net_log) override {
last_group_name_ = group_name;
socket_requested_ = true;
return ERR_IO_PENDING;
}
void CancelRequest(const std::string& group_name,
ClientSocketHandle* handle) override {}
void ReleaseSocket(const std::string& group_name,
std::unique_ptr<StreamSocket> socket,
int id) override {}
void CloseIdleSockets() override {}
void CloseIdleSocketsInGroup(const std::string& group_name) 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_;
bool socket_requested_ = false;
};
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, NULL) {}
template <>
CaptureGroupNameHttpProxySocketPool::CaptureGroupNameSocketPool(
HostResolver* /* host_resolver */,
CertVerifier* /* cert_verifier */)
: HttpProxyClientSocketPool(0, 0, NULL, 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("http://www.example.org", auth_challenge->challenger.Serialize());
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("http://myproxy:70", auth_challenge->challenger.Serialize());
EXPECT_EQ("MyRealm1", auth_challenge->realm);
EXPECT_EQ(kBasicAuthScheme, auth_challenge->scheme);
return true;
}
bool CheckBasicSecureProxyAuth(const AuthChallengeInfo* auth_challenge) {
if (!auth_challenge)
return false;
EXPECT_TRUE(auth_challenge->is_proxy);
EXPECT_EQ("https://myproxy:70", auth_challenge->challenger.Serialize());
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("http://www.example.org", auth_challenge->challenger.Serialize());
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("https://server", auth_challenge->challenger.Serialize());
EXPECT_EQ(std::string(), auth_challenge->realm);
EXPECT_EQ(kNtlmAuthScheme, auth_challenge->scheme);
return true;
}
bool CheckNTLMProxyAuth(const AuthChallengeInfo* auth_challenge) {
if (!auth_challenge)
return false;
EXPECT_TRUE(auth_challenge->is_proxy);
EXPECT_EQ("http://server", auth_challenge->challenger.Serialize());
EXPECT_EQ(std::string(), auth_challenge->realm);
EXPECT_EQ(kNtlmAuthScheme, auth_challenge->scheme);
return true;
}
#endif // defined(NTLM_PORTABLE)
} // namespace
TEST_F(HttpNetworkTransactionTest, Basic) {
std::unique_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
HttpNetworkTransaction trans(DEFAULT_PRIORITY, session.get());
}
TEST_F(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);
EXPECT_THAT(out.rv, IsOk());
EXPECT_EQ("HTTP/1.0 200 OK", out.status_line);
EXPECT_EQ("hello world", out.response_data);
int64_t reads_size = CountReadBytes(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_F(HttpNetworkTransactionTest, SimpleGETNoHeaders) {
MockRead data_reads[] = {
MockRead("hello world"),
MockRead(SYNCHRONOUS, OK),
};
SimpleGetHelperResult out = SimpleGetHelper(data_reads);
EXPECT_THAT(out.rv, IsOk());
EXPECT_EQ("HTTP/0.9 200 OK", out.status_line);
EXPECT_EQ("hello world", out.response_data);
int64_t reads_size = CountReadBytes(data_reads);
EXPECT_EQ(reads_size, out.total_received_bytes);
}
// Response with no status line, and a weird port. Should fail by default.
TEST_F(HttpNetworkTransactionTest, SimpleGETNoHeadersWeirdPort) {
MockRead data_reads[] = {
MockRead("hello world"), MockRead(SYNCHRONOUS, OK),
};
StaticSocketDataProvider data(data_reads, base::span<MockWrite>());
session_deps_.socket_factory->AddSocketDataProvider(&data);
std::unique_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
HttpRequestInfo request;
auto trans =
std::make_unique<HttpNetworkTransaction>(DEFAULT_PRIORITY, session.get());
request.method = "GET";
request.url = GURL("http://www.example.com:2000/");
request.traffic_annotation =
net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS);
TestCompletionCallback callback;
int rv = trans->Start(&request, callback.callback(), NetLogWithSource());
EXPECT_THAT(callback.GetResult(rv), IsError(ERR_INVALID_HTTP_RESPONSE));
}
// Tests that request info can be destroyed after the headers phase is complete.
TEST_F(HttpNetworkTransactionTest, SimpleGETNoReadDestroyRequestInfo) {
std::unique_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
auto trans =
std::make_unique<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, base::span<MockWrite>());
session_deps_.socket_factory->AddSocketDataProvider(&data);
TestCompletionCallback callback;
{
auto request = std::make_unique<HttpRequestInfo>();
request->method = "GET";
request->url = GURL("http://www.example.org/");
request->traffic_annotation =
net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS);
int rv =
trans->Start(request.get(), callback.callback(), NetLogWithSource());
EXPECT_THAT(callback.GetResult(rv), IsOk());
} // Let request info be destroyed.
trans.reset();
}
// Response with no status line, and a weird port. Option to allow weird ports
// enabled.
TEST_F(HttpNetworkTransactionTest, SimpleGETNoHeadersWeirdPortAllowed) {
MockRead data_reads[] = {
MockRead("hello world"), MockRead(SYNCHRONOUS, OK),
};
StaticSocketDataProvider data(data_reads, base::span<MockWrite>());
session_deps_.socket_factory->AddSocketDataProvider(&data);
session_deps_.http_09_on_non_default_ports_enabled = true;
std::unique_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
HttpRequestInfo request;
auto trans =
std::make_unique<HttpNetworkTransaction>(DEFAULT_PRIORITY, session.get());
request.method = "GET";
request.url = GURL("http://www.example.com:2000/");
request.traffic_annotation =
net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS);
TestCompletionCallback callback;
int rv = trans->Start(&request, callback.callback(), NetLogWithSource());
EXPECT_THAT(callback.GetResult(rv), IsOk());
const HttpResponseInfo* info = trans->GetResponseInfo();
ASSERT_TRUE(info->headers);
EXPECT_EQ("HTTP/0.9 200 OK", info->headers->GetStatusLine());
// Don't bother to read the body - that's verified elsewhere, important thing
// is that the option to allow HTTP/0.9 on non-default ports is respected.
}
// Allow up to 4 bytes of junk to precede status line.
TEST_F(HttpNetworkTransactionTest, StatusLineJunk3Bytes) {
MockRead data_reads[] = {
MockRead("xxxHTTP/1.0 404 Not Found\nServer: blah\n\nDATA"),
MockRead(SYNCHRONOUS, OK),
};
SimpleGetHelperResult out = SimpleGetHelper(data_reads);
EXPECT_THAT(out.rv, IsOk());
EXPECT_EQ("HTTP/1.0 404 Not Found", out.status_line);
EXPECT_EQ("DATA", out.response_data);
int64_t reads_size = CountReadBytes(data_reads);
EXPECT_EQ(reads_size, out.total_received_bytes);
}
// Allow up to 4 bytes of junk to precede status line.
TEST_F(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);
EXPECT_THAT(out.rv, IsOk());
EXPECT_EQ("HTTP/1.0 404 Not Found", out.status_line);
EXPECT_EQ("DATA", out.response_data);
int64_t reads_size = CountReadBytes(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_F(HttpNetworkTransactionTest, StatusLineJunk5Bytes) {
MockRead data_reads[] = {
MockRead("xxxxxHTTP/1.1 404 Not Found\nServer: blah"),
MockRead(SYNCHRONOUS, OK),
};
SimpleGetHelperResult out = SimpleGetHelper(data_reads);
EXPECT_THAT(out.rv, IsOk());
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);
EXPECT_EQ(reads_size, out.total_received_bytes);
}
// Same as StatusLineJunk4Bytes, except the read chunks are smaller.
TEST_F(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);
EXPECT_THAT(out.rv, IsOk());
EXPECT_EQ("HTTP/1.0 404 Not Found", out.status_line);
EXPECT_EQ("DATA", out.response_data);
int64_t reads_size = CountReadBytes(data_reads);
EXPECT_EQ(reads_size, out.total_received_bytes);
}
// Close the connection before enough bytes to have a status line.
TEST_F(HttpNetworkTransactionTest, StatusLinePartial) {
MockRead data_reads[] = {
MockRead("HTT"),
MockRead(SYNCHRONOUS, OK),
};
SimpleGetHelperResult out = SimpleGetHelper(data_reads);
EXPECT_THAT(out.rv, IsOk());
EXPECT_EQ("HTTP/0.9 200 OK", out.status_line);
EXPECT_EQ("HTT", out.response_data);
int64_t reads_size = CountReadBytes(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_F(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);
EXPECT_THAT(out.rv, IsOk());
EXPECT_EQ("HTTP/1.1 204 No Content", out.status_line);
EXPECT_EQ("", out.response_data);
int64_t reads_size = CountReadBytes(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_F(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);
EXPECT_THAT(out.rv, IsOk());
EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
EXPECT_EQ("Hello world", out.response_data);
int64_t reads_size = CountReadBytes(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_F(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);
EXPECT_THAT(out.rv, IsError(ERR_RESPONSE_HEADERS_MULTIPLE_CONTENT_LENGTH));
}
TEST_F(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);
EXPECT_THAT(out.rv, IsOk());
EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
EXPECT_EQ("Hello", out.response_data);
}
TEST_F(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);
EXPECT_THAT(out.rv, IsOk());
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);
EXPECT_THAT(out.rv, IsOk());
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);
EXPECT_THAT(out.rv, IsError(ERR_RESPONSE_HEADERS_MULTIPLE_CONTENT_LENGTH));
}
}
TEST_F(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);
EXPECT_THAT(out.rv, IsOk());
EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
EXPECT_EQ("Hello world", out.response_data);
}
// Next tests deal with http://crbug.com/98895.
// Checks that a single Content-Disposition header results in no error.
TEST_F(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);
EXPECT_THAT(out.rv, IsOk());
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_F(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);
EXPECT_THAT(out.rv, IsOk());
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_F(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);
EXPECT_THAT(out.rv,
IsError(ERR_RESPONSE_HEADERS_MULTIPLE_CONTENT_DISPOSITION));
}
// Checks that two identical Location headers result in no error.
// Also tests Location header behavior.
TEST_F(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.traffic_annotation =
net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS);
std::unique_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
HttpNetworkTransaction trans(DEFAULT_PRIORITY, session.get());
StaticSocketDataProvider data(data_reads, base::span<MockWrite>());
session_deps_.socket_factory->AddSocketDataProvider(&data);
TestCompletionCallback callback;
int rv = trans.Start(&request, callback.callback(), NetLogWithSource());
EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
EXPECT_THAT(callback.WaitForResult(), IsOk());
const HttpResponseInfo* response = trans.GetResponseInfo();
ASSERT_TRUE(response);
ASSERT_TRUE(response->headers);
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.is_direct());
}
// Checks that two distinct Location headers result in an error.
TEST_F(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);
EXPECT_THAT(out.rv, IsError(ERR_RESPONSE_HEADERS_MULTIPLE_LOCATION));
}
// Do a request using the HEAD method. Verify that we don't try to read the
// message body (since HEAD has none).
TEST_F(HttpNetworkTransactionTest, Head) {
HttpRequestInfo request;
request.method = "HEAD";
request.url = GURL("http://www.example.org/");
request.traffic_annotation =
net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS);
std::unique_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
HttpNetworkTransaction trans(DEFAULT_PRIORITY, session.get());
BeforeHeadersSentHandler headers_handler;
trans.SetBeforeHeadersSentCallback(
base::Bind(&BeforeHeadersSentHandler::OnBeforeHeadersSent,
base::Unretained(&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, data_writes1);
session_deps_.socket_factory->AddSocketDataProvider(&data1);
TestCompletionCallback callback1;
int rv = trans.Start(&request, callback1.callback(), NetLogWithSource());
EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
rv = callback1.WaitForResult();
EXPECT_THAT(rv, IsOk());
const HttpResponseInfo* response = trans.GetResponseInfo();
ASSERT_TRUE(response);
// Check that the headers got parsed.
EXPECT_TRUE(response->headers);
EXPECT_EQ(1234, response->headers->GetContentLength());
EXPECT_EQ("HTTP/1.1 404 Not Found", response->headers->GetStatusLine());
EXPECT_TRUE(response->proxy_server.is_direct());
EXPECT_TRUE(headers_handler.observed_before_headers_sent());
EXPECT_FALSE(headers_handler.observed_before_headers_sent_with_proxy());
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, &response_data);
EXPECT_THAT(rv, IsOk());
EXPECT_EQ("", response_data);
}
TEST_F(HttpNetworkTransactionTest, ReuseConnection) {
std::unique_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, base::span<MockWrite>());
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.traffic_annotation =
net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS);
HttpNetworkTransaction trans(DEFAULT_PRIORITY, session.get());
TestCompletionCallback callback;
int rv = trans.Start(&request, callback.callback(), NetLogWithSource());
EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
rv = callback.WaitForResult();
EXPECT_THAT(rv, IsOk());
const HttpResponseInfo* response = trans.GetResponseInfo();
ASSERT_TRUE(response);
EXPECT_TRUE(response->headers);
EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
EXPECT_TRUE(response->proxy_server.is_direct());
std::string response_data;
rv = ReadTransaction(&trans, &response_data);
EXPECT_THAT(rv, IsOk());
EXPECT_EQ(kExpectedResponseData[i], response_data);
}
}
TEST_F(HttpNetworkTransactionTest, Ignores100) {
std::vector<std::unique_ptr<UploadElementReader>> element_readers;
element_readers.push_back(
std::make_unique<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.traffic_annotation =
net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS);
// Check the upload progress returned before initialization is correct.
UploadProgress progress = request.upload_data_stream->GetUploadProgress();
EXPECT_EQ(0u, progress.size());
EXPECT_EQ(0u, progress.position());
std::unique_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
HttpNetworkTransaction trans(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, base::span<MockWrite>());
session_deps_.socket_factory->AddSocketDataProvider(&data);
TestCompletionCallback callback;
int rv = trans.Start(&request, callback.callback(), NetLogWithSource());
EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
rv = callback.WaitForResult();
EXPECT_THAT(rv, IsOk());
const HttpResponseInfo* response = trans.GetResponseInfo();
ASSERT_TRUE(response);
EXPECT_TRUE(response->headers);
EXPECT_EQ("HTTP/1.0 200 OK", response->headers->GetStatusLine());
std::string response_data;
rv = ReadTransaction(&trans, &response_data);
EXPECT_THAT(rv, IsOk());
EXPECT_EQ("hello world", response_data);
}
// This test is almost the same as Ignores100 above, but the response contains
// a 102 instead of a 100. Also, instead of HTTP/1.0 the response is
// HTTP/1.1 and the two status headers are read in one read.
TEST_F(HttpNetworkTransactionTest, Ignores1xx) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.foo.com/");
request.traffic_annotation =
net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS);
std::unique_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
HttpNetworkTransaction trans(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, base::span<MockWrite>());
session_deps_.socket_factory->AddSocketDataProvider(&data);
TestCompletionCallback callback;
int rv = trans.Start(&request, callback.callback(), NetLogWithSource());
EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
rv = callback.WaitForResult();
EXPECT_THAT(rv, IsOk());
const HttpResponseInfo* response = trans.GetResponseInfo();
ASSERT_TRUE(response);
EXPECT_TRUE(response->headers);
EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
std::string response_data;
rv = ReadTransaction(&trans, &response_data);
EXPECT_THAT(rv, IsOk());
EXPECT_EQ("hello world", response_data);
}
TEST_F(HttpNetworkTransactionTest, Incomplete100ThenEOF) {
HttpRequestInfo request;
request.method = "POST";
request.url = GURL("http://www.foo.com/");
request.traffic_annotation =
net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS);
std::unique_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
HttpNetworkTransaction trans(DEFAULT_PRIORITY, session.get());
MockRead data_reads[] = {
MockRead(SYNCHRONOUS, "HTTP/1.0 100 Continue\r\n"),
MockRead(ASYNC, 0),
};
StaticSocketDataProvider data(data_reads, base::span<MockWrite>());
session_deps_.socket_factory->AddSocketDataProvider(&data);
TestCompletionCallback callback;
int rv = trans.Start(&request, callback.callback(), NetLogWithSource());
EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
rv = callback.WaitForResult();
EXPECT_THAT(rv, IsOk());
std::string response_data;
rv = ReadTransaction(&trans, &response_data);
EXPECT_THAT(rv, IsOk());
EXPECT_EQ("", response_data);
}
TEST_F(HttpNetworkTransactionTest, EmptyResponse) {
HttpRequestInfo request;
request.method = "POST";
request.url = GURL("http://www.foo.com/");
request.traffic_annotation =
net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS);
std::unique_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
HttpNetworkTransaction trans(DEFAULT_PRIORITY, session.get());
MockRead data_reads[] = {
MockRead(ASYNC, 0),
};
StaticSocketDataProvider data(data_reads, base::span<MockWrite>());
session_deps_.socket_factory->AddSocketDataProvider(&data);
TestCompletionCallback callback;
int rv = trans.Start(&request, callback.callback(), NetLogWithSource());
EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
rv = callback.WaitForResult();
EXPECT_THAT(rv, IsError(ERR_EMPTY_RESPONSE));
}
void HttpNetworkTransactionTest::KeepAliveConnectionResendRequestTest(
const MockWrite* write_failure,
const MockRead* read_failure) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.foo.com/");
request.traffic_annotation =
net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS);
TestNetLog net_log;
session_deps_.net_log = &net_log;
std::unique_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, 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, base::span<MockWrite>());
session_deps_.socket_factory->AddSocketDataProvider(&data2);
const char* const kExpectedResponseData[] = {
"hello", "world"
};
uint32_t first_socket_log_id = NetLogSource::kInvalidId;
for (int i = 0; i < 2; ++i) {
TestCompletionCallback callback;
HttpNetworkTransaction trans(DEFAULT_PRIORITY, session.get());
int rv = trans.Start(&request, callback.callback(), NetLogWithSource());
EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
rv = callback.WaitForResult();
EXPECT_THAT(rv, IsOk());
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);
EXPECT_TRUE(response->headers);
EXPECT_TRUE(response->proxy_server.is_direct());
EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
std::string response_data;
rv = ReadTransaction(&trans, &response_data);
EXPECT_THAT(rv, IsOk());
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.traffic_annotation =
net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS);
TestNetLog net_log;
session_deps_.net_log = &net_log;
std::unique_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
SSLSocketDataProvider ssl1(ASYNC, OK);
SSLSocketDataProvider ssl2(ASYNC, OK);
if (use_spdy) {
ssl1.next_proto = kProtoHTTP2;
ssl2.next_proto = kProtoHTTP2;
}
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl1);
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl2);
// SPDY versions of the request and response.
spdy::SpdySerializedFrame spdy_request(spdy_util_.ConstructSpdyGet(
request.url.spec().c_str(), 1, DEFAULT_PRIORITY));
spdy::SpdySerializedFrame spdy_response(
spdy_util_.ConstructSpdyGetReply(NULL, 0, 1));
spdy::SpdySerializedFrame spdy_data(
spdy_util_.ConstructSpdyDataFrame(1, "hello", 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, data1_writes);
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, data2_writes);
session_deps_.socket_factory->AddSocketDataProvider(&data2);
// Preconnect a socket.
session->http_stream_factory()->PreconnectStreams(1, request);
// 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;
HttpNetworkTransaction trans(DEFAULT_PRIORITY, session.get());
int rv = trans.Start(&request, callback.callback(), NetLogWithSource());
EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
rv = callback.WaitForResult();
EXPECT_THAT(rv, IsOk());
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);
EXPECT_TRUE(response->headers);
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, &response_data);
EXPECT_THAT(rv, IsOk());
EXPECT_EQ(kHttpData, response_data);
}
// Test that we do not retry indefinitely when a server sends an error like
// ERR_SPDY_PING_FAILED, ERR_SPDY_SERVER_REFUSED_STREAM,
// ERR_QUIC_HANDSHAKE_FAILED or ERR_QUIC_PROTOCOL_ERROR.
TEST_F(HttpNetworkTransactionTest, FiniteRetriesOnIOError) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("https://www.foo.com/");
request.traffic_annotation =
net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS);
// Check whether we give up after the third try.
// Construct an HTTP2 request and a "Go away" response.
spdy::SpdySerializedFrame spdy_request(spdy_util_.ConstructSpdyGet(
request.url.spec().c_str(), 1, DEFAULT_PRIORITY));
spdy::SpdySerializedFrame spdy_response_go_away(
spdy_util_.ConstructSpdyGoAway(0));
MockRead data_read1[] = {CreateMockRead(spdy_response_go_away)};
MockWrite data_write[] = {CreateMockWrite(spdy_request, 0)};
// Three go away responses.
StaticSocketDataProvider data1(data_read1, data_write);
StaticSocketDataProvider data2(data_read1, data_write);
StaticSocketDataProvider data3(data_read1, data_write);
session_deps_.socket_factory->AddSocketDataProvider(&data1);
AddSSLSocketData();
session_deps_.socket_factory->AddSocketDataProvider(&data2);
AddSSLSocketData();
session_deps_.socket_factory->AddSocketDataProvider(&data3);
AddSSLSocketData();
TestCompletionCallback callback;
std::unique_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
HttpNetworkTransaction trans(DEFAULT_PRIORITY, session.get());
int rv = trans.Start(&request, callback.callback(), NetLogWithSource());
EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
rv = callback.WaitForResult();
EXPECT_THAT(rv, IsError(ERR_SPDY_SERVER_REFUSED_STREAM));
}
TEST_F(HttpNetworkTransactionTest, RetryTwiceOnIOError) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("https://www.foo.com/");
request.traffic_annotation =
net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS);
// Check whether we try atleast thrice before giving up.
// Construct an HTTP2 request and a "Go away" response.
spdy::SpdySerializedFrame spdy_request(spdy_util_.ConstructSpdyGet(
request.url.spec().c_str(), 1, DEFAULT_PRIORITY));
spdy::SpdySerializedFrame spdy_response_go_away(
spdy_util_.ConstructSpdyGoAway(0));
MockRead data_read1[] = {CreateMockRead(spdy_response_go_away)};
MockWrite data_write[] = {CreateMockWrite(spdy_request, 0)};
// Construct a non error HTTP2 response.
spdy::SpdySerializedFrame spdy_response_no_error(
spdy_util_.ConstructSpdyGetReply(nullptr, 0, 1));
spdy::SpdySerializedFrame spdy_data(
spdy_util_.ConstructSpdyDataFrame(1, true));
MockRead data_read2[] = {CreateMockRead(spdy_response_no_error, 1),
CreateMockRead(spdy_data, 2)};
// Two error responses.
StaticSocketDataProvider data1(data_read1, data_write);
StaticSocketDataProvider data2(data_read1, data_write);
// Followed by a success response.
SequencedSocketData data3(data_read2, data_write);
session_deps_.socket_factory->AddSocketDataProvider(&data1);
AddSSLSocketData();
session_deps_.socket_factory->AddSocketDataProvider(&data2);
AddSSLSocketData();
session_deps_.socket_factory->AddSocketDataProvider(&data3);
AddSSLSocketData();
TestCompletionCallback callback;
std::unique_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
HttpNetworkTransaction trans(DEFAULT_PRIORITY, session.get());
int rv = trans.Start(&request, callback.callback(), NetLogWithSource());
EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
rv = callback.WaitForResult();
EXPECT_THAT(rv, IsOk());
}
TEST_F(HttpNetworkTransactionTest, KeepAliveConnectionNotConnectedOnWrite) {
MockWrite write_failure(ASYNC, ERR_SOCKET_NOT_CONNECTED);
KeepAliveConnectionResendRequestTest(&write_failure, NULL);
}
TEST_F(HttpNetworkTransactionTest, KeepAliveConnectionReset) {
MockRead read_failure(ASYNC, ERR_CONNECTION_RESET);
KeepAliveConnectionResendRequestTest(NULL, &read_failure);
}
TEST_F(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_F(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_F(HttpNetworkTransactionTest, PreconnectErrorNotConnectedOnWrite) {
MockWrite write_failure(ASYNC, ERR_SOCKET_NOT_CONNECTED);
PreconnectErrorResendRequestTest(&write_failure, NULL, false);
}
TEST_F(HttpNetworkTransactionTest, PreconnectErrorReset) {
MockRead read_failure(ASYNC, ERR_CONNECTION_RESET);
PreconnectErrorResendRequestTest(NULL, &read_failure, false);
}
TEST_F(HttpNetworkTransactionTest, PreconnectErrorEOF) {
MockRead read_failure(SYNCHRONOUS, OK); // EOF
PreconnectErrorResendRequestTest(NULL, &read_failure, false);
}
TEST_F(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_F(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_F(HttpNetworkTransactionTest, SpdyPreconnectErrorNotConnectedOnWrite) {
MockWrite write_failure(ASYNC, ERR_SOCKET_NOT_CONNECTED);
PreconnectErrorResendRequestTest(&write_failure, NULL, true);
}
TEST_F(HttpNetworkTransactionTest, SpdyPreconnectErrorReset) {
MockRead read_failure(ASYNC, ERR_CONNECTION_RESET);
PreconnectErrorResendRequestTest(NULL, &read_failure, true);
}
TEST_F(HttpNetworkTransactionTest, SpdyPreconnectErrorEOF) {
MockRead read_failure(SYNCHRONOUS, OK); // EOF
PreconnectErrorResendRequestTest(NULL, &read_failure, true);
}
TEST_F(HttpNetworkTransactionTest, SpdyPreconnectErrorAsyncEOF) {
MockRead read_failure(ASYNC, OK); // EOF
PreconnectErrorResendRequestTest(NULL, &read_failure, true);
}
TEST_F(HttpNetworkTransactionTest, NonKeepAliveConnectionReset) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.example.org/");
request.traffic_annotation =
net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS);
std::unique_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
HttpNetworkTransaction trans(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, base::span<MockWrite>());
session_deps_.socket_factory->AddSocketDataProvider(&data);
TestCompletionCallback callback;
int rv = trans.Start(&request, callback.callback(), NetLogWithSource());
EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
rv = callback.WaitForResult();
EXPECT_THAT(rv, IsError(ERR_CONNECTION_RESET));
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_F(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);
EXPECT_THAT(out.rv, IsError(ERR_EMPTY_RESPONSE));
}
// 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_F(HttpNetworkTransactionTest, KeepAliveEarlyClose) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.example.org/");
request.traffic_annotation =
net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS);
std::unique_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
auto trans =
std::make_unique<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, base::span<MockWrite>());
session_deps_.socket_factory->AddSocketDataProvider(&data);
TestCompletionCallback callback;
int rv = trans->Start(&request, callback.callback(), NetLogWithSource());
EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
rv = callback.WaitForResult();
EXPECT_THAT(rv, IsOk());
scoped_refptr<IOBufferWithSize> io_buf =
base::MakeRefCounted<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_THAT(rv, IsError(ERR_CONTENT_LENGTH_MISMATCH));
trans.reset();
base::RunLoop().RunUntilIdle();
EXPECT_EQ(0, GetIdleSocketCountInTransportSocketPool(session.get()));
}
TEST_F(HttpNetworkTransactionTest, KeepAliveEarlyClose2) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.example.org/");
request.traffic_annotation =
net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS);
std::unique_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
auto trans =
std::make_unique<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, base::span<MockWrite>());
session_deps_.socket_factory->AddSocketDataProvider(&data);
TestCompletionCallback callback;
int rv = trans->Start(&request, callback.callback(), NetLogWithSource());
EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
rv = callback.WaitForResult();
EXPECT_THAT(rv, IsOk());
scoped_refptr<IOBufferWithSize> io_buf(
base::MakeRefCounted<IOBufferWithSize>(100));
rv = trans->Read(io_buf.get(), io_buf->size(), callback.callback());
if (rv == ERR_IO_PENDING)
rv = callback.WaitForResult();
EXPECT_THAT(rv, IsError(ERR_CONTENT_LENGTH_MISMATCH));
trans.reset();
base::RunLoop().RunUntilIdle();
EXPECT_EQ(0, GetIdleSocketCountInTransportSocketPool(session.get()));
}
// Test that we correctly reuse a keep-alive connection after not explicitly
// reading the body.
TEST_F(HttpNetworkTransactionTest, KeepAliveAfterUnreadBody) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.foo.com/");
request.traffic_annotation =
net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS);
TestNetLog net_log;
session_deps_.net_log = &net_log;
std::unique_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, 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 = NetLogSource::kInvalidId;
for (size_t i = 0; i < kNumUnreadBodies; ++i) {
TestCompletionCallback callback;
auto trans = std::make_unique<HttpNetworkTransaction>(DEFAULT_PRIORITY,
session.get());
int rv = trans->Start(&request, callback.callback(), NetLogWithSource());
EXPECT_THAT(callback.GetResult(rv), IsOk());
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;
HttpNetworkTransaction trans(DEFAULT_PRIORITY, session.get());
int rv = trans.Start(&request, callback.callback(), NetLogWithSource());
EXPECT_THAT(callback.GetResult(rv), IsOk());
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, &response_data);
EXPECT_THAT(rv, IsOk());
EXPECT_EQ("hello", response_data);
}
// Sockets that receive extra data after a response is complete should not be
// reused.
TEST_F(HttpNetworkTransactionTest, KeepAliveWithUnusedData1) {
std::unique_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
MockWrite data_writes1[] = {
MockWrite("HEAD / HTTP/1.1\r\n"
"Host: www.borked.com\r\n"
"Connection: keep-alive\r\n\r\n"),
};
MockRead data_reads1[] = {
MockRead("HTTP/1.1 200 OK\r\n"
"Connection: keep-alive\r\n"
"Content-Length: 22\r\n\r\n"
"This server is borked."),
};
MockWrite data_writes2[] = {
MockWrite("GET /foo HTTP/1.1\r\n"
"Host: www.borked.com\r\n"
"Connection: keep-alive\r\n\r\n"),
};
MockRead data_reads2[] = {
MockRead("HTTP/1.1 200 OK\r\n"
"Content-Length: 3\r\n\r\n"
"foo"),
};
StaticSocketDataProvider data1(data_reads1, data_writes1);
session_deps_.socket_factory->AddSocketDataProvider(&data1);
StaticSocketDataProvider data2(data_reads2, data_writes2);
session_deps_.socket_factory->AddSocketDataProvider(&data2);
TestCompletionCallback callback;
HttpRequestInfo request1;
request1.method = "HEAD";
request1.url = GURL("http://www.borked.com/");
request1.traffic_annotation =
net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS);
auto trans1 =
std::make_unique<HttpNetworkTransaction>(DEFAULT_PRIORITY, session.get());
int rv = trans1->Start(&request1, callback.callback(), NetLogWithSource());
EXPECT_THAT(callback.GetResult(rv), IsOk());
const HttpResponseInfo* response1 = trans1->GetResponseInfo();
ASSERT_TRUE(response1);
ASSERT_TRUE(response1->headers);
EXPECT_EQ(200, response1->headers->response_code());
EXPECT_TRUE(response1->headers->IsKeepAlive());
std::string response_data1;
EXPECT_THAT(ReadTransaction(trans1.get(), &response_data1), IsOk());
EXPECT_EQ("", response_data1);
// Deleting the transaction attempts to release the socket back into the
// socket pool.
trans1.reset();
HttpRequestInfo request2;
request2.method = "GET";
request2.url = GURL("http://www.borked.com/foo");
request2.traffic_annotation =
net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS);
auto trans2 =
std::make_unique<HttpNetworkTransaction>(DEFAULT_PRIORITY, session.get());
rv = trans2->Start(&request2, callback.callback(), NetLogWithSource());
EXPECT_THAT(callback.GetResult(rv), IsOk());
const HttpResponseInfo* response2 = trans2->GetResponseInfo();
ASSERT_TRUE(response2);
ASSERT_TRUE(response2->headers);
EXPECT_EQ(200, response2->headers->response_code());
std::string response_data2;
EXPECT_THAT(ReadTransaction(trans2.get(), &response_data2), IsOk());
EXPECT_EQ("foo", response_data2);
}
TEST_F(HttpNetworkTransactionTest, KeepAliveWithUnusedData2) {
std::unique_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
MockWrite data_writes1[] = {
MockWrite("GET / HTTP/1.1\r\n"
"Host: www.borked.com\r\n"
"Connection: keep-alive\r\n\r\n"),
};
MockRead data_reads1[] = {
MockRead("HTTP/1.1 200 OK\r\n"
"Connection: keep-alive\r\n"
"Content-Length: 22\r\n\r\n"
"This server is borked."
"Bonus data!"),
};
MockWrite data_writes2[] = {
MockWrite("GET /foo HTTP/1.1\r\n"
"Host: www.borked.com\r\n"
"Connection: keep-alive\r\n\r\n"),
};
MockRead data_reads2[] = {
MockRead("HTTP/1.1 200 OK\r\n"
"Content-Length: 3\r\n\r\n"
"foo"),
};
StaticSocketDataProvider data1(data_reads1, data_writes1);
session_deps_.socket_factory->AddSocketDataProvider(&data1);
StaticSocketDataProvider data2(data_reads2, data_writes2);
session_deps_.socket_factory->AddSocketDataProvider(&data2);
TestCompletionCallback callback;
HttpRequestInfo request1;
request1.method = "GET";
request1.url = GURL("http://www.borked.com/");
request1.traffic_annotation =
net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS);
auto trans1 =
std::make_unique<HttpNetworkTransaction>(DEFAULT_PRIORITY, session.get());
int rv = trans1->Start(&request1, callback.callback(), NetLogWithSource());
EXPECT_THAT(callback.GetResult(rv), IsOk());
const HttpResponseInfo* response1 = trans1->GetResponseInfo();
ASSERT_TRUE(response1);
ASSERT_TRUE(response1->headers);
EXPECT_EQ(200, response1->headers->response_code());
EXPECT_TRUE(response1->headers->IsKeepAlive());
std::string response_data1;
EXPECT_THAT(ReadTransaction(trans1.get(), &response_data1), IsOk());
EXPECT_EQ("This server is borked.", response_data1);
// Deleting the transaction attempts to release the socket back into the
// socket pool.
trans1.reset();
HttpRequestInfo request2;
request2.method = "GET";
request2.url = GURL("http://www.borked.com/foo");
request2.traffic_annotation =
net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS);
auto trans2 =
std::make_unique<HttpNetworkTransaction>(DEFAULT_PRIORITY, session.get());
rv = trans2->Start(&request2, callback.callback(), NetLogWithSource());
EXPECT_THAT(callback.GetResult(rv), IsOk());
const HttpResponseInfo* response2 = trans2->GetResponseInfo();
ASSERT_TRUE(response2);
ASSERT_TRUE(response2->headers);
EXPECT_EQ(200, response2->headers->response_code());
std::string response_data2;
EXPECT_THAT(ReadTransaction(trans2.get(), &response_data2), IsOk());
EXPECT_EQ("foo", response_data2);
}
TEST_F(HttpNetworkTransactionTest, KeepAliveWithUnusedData3) {
std::unique_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
MockWrite data_writes1[] = {
MockWrite("GET / HTTP/1.1\r\n"
"Host: www.borked.com\r\n"
"Connection: keep-alive\r\n\r\n"),
};
MockRead data_reads1[] = {
MockRead("HTTP/1.1 200 OK\r\n"
"Connection: keep-alive\r\n"
"Transfer-Encoding: chunked\r\n\r\n"),
MockRead("16\r\nThis server is borked.\r\n"),
MockRead("0\r\n\r\nBonus data!"),
};
MockWrite data_writes2[] = {
MockWrite("GET /foo HTTP/1.1\r\n"
"Host: www.borked.com\r\n"
"Connection: keep-alive\r\n\r\n"),
};
MockRead data_reads2[] = {
MockRead("HTTP/1.1 200 OK\r\n"
"Content-Length: 3\r\n\r\n"
"foo"),
};
StaticSocketDataProvider data1(data_reads1, data_writes1);
session_deps_.socket_factory->AddSocketDataProvider(&data1);
StaticSocketDataProvider data2(data_reads2, data_writes2);
session_deps_.socket_factory->AddSocketDataProvider(&data2);
TestCompletionCallback callback;
HttpRequestInfo request1;
request1.method = "GET";
request1.url = GURL("http://www.borked.com/");
request1.traffic_annotation =
net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS);
auto trans1 =
std::make_unique<HttpNetworkTransaction>(DEFAULT_PRIORITY, session.get());
int rv = trans1->Start(&request1, callback.callback(), NetLogWithSource());
EXPECT_THAT(callback.GetResult(rv), IsOk());
const HttpResponseInfo* response1 = trans1->GetResponseInfo();
ASSERT_TRUE(response1);
ASSERT_TRUE(response1->headers);
EXPECT_EQ(200, response1->headers->response_code());
EXPECT_TRUE(response1->headers->IsKeepAlive());
std::string response_data1;
EXPECT_THAT(ReadTransaction(trans1.get(), &response_data1), IsOk());
EXPECT_EQ("This server is borked.", response_data1);
// Deleting the transaction attempts to release the socket back into the
// socket pool.
trans1.reset();
HttpRequestInfo request2;
request2.method = "GET";
request2.url = GURL("http://www.borked.com/foo");
request2.traffic_annotation =
net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS);
auto trans2 =
std::make_unique<HttpNetworkTransaction>(DEFAULT_PRIORITY, session.get());
rv = trans2->Start(&request2, callback.callback(), NetLogWithSource());
EXPECT_THAT(callback.GetResult(rv), IsOk());
const HttpResponseInfo* response2 = trans2->GetResponseInfo();
ASSERT_TRUE(response2);
ASSERT_TRUE(response2->headers);
EXPECT_EQ(200, response2->headers->response_code());
std::string response_data2;
EXPECT_THAT(ReadTransaction(trans2.get(), &response_data2), IsOk());
EXPECT_EQ("foo", response_data2);
}
// This is a little different from the others - it tests the case that the
// HttpStreamParser doesn't know if there's extra data on a socket or not when
// the HttpNetworkTransaction is torn down, because the response body hasn't
// been read from yet, but the request goes through the HttpResponseBodyDrainer.
TEST_F(HttpNetworkTransactionTest, KeepAliveWithUnusedData4) {
std::unique_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
MockWrite data_writes1[] = {
MockWrite("GET / HTTP/1.1\r\n"
"Host: www.borked.com\r\n"
"Connection: keep-alive\r\n\r\n"),
};
MockRead data_reads1[] = {
MockRead("HTTP/1.1 200 OK\r\n"
"Connection: keep-alive\r\n"
"Transfer-Encoding: chunked\r\n\r\n"),
MockRead("16\r\nThis server is borked.\r\n"),
MockRead("0\r\n\r\nBonus data!"),
};
StaticSocketDataProvider data1(data_reads1, data_writes1);
session_deps_.socket_factory->AddSocketDataProvider(&data1);
TestCompletionCallback callback;
HttpRequestInfo request1;
request1.method = "GET";
request1.url = GURL("http://www.borked.com/");
request1.traffic_annotation =
net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS);
auto trans =
std::make_unique<HttpNetworkTransaction>(DEFAULT_PRIORITY, session.get());
int rv = trans->Start(&request1, callback.callback(), NetLogWithSource());
EXPECT_THAT(callback.GetResult(rv), IsOk());
const HttpResponseInfo* response1 = trans->GetResponseInfo();
ASSERT_TRUE(response1);
ASSERT_TRUE(response1->headers);
EXPECT_EQ(200, response1->headers->response_code());
EXPECT_TRUE(response1->headers->IsKeepAlive());
// Deleting the transaction creates an HttpResponseBodyDrainer to read the
// response body.
trans.reset();
// Let the HttpResponseBodyDrainer drain the socket. It should determine the
// socket can't be reused, rather than returning it to the socket pool.
base::RunLoop().RunUntilIdle();
// There should be no idle sockets in the pool.
EXPECT_EQ(0, GetIdleSocketCountInTransportSocketPool(session.get()));
}
// Test the request-challenge-retry sequence for basic auth.
// (basic auth is the easiest to mock, because it has no randomness).
TEST_F(HttpNetworkTransactionTest, BasicAuth) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.example.org/");
request.traffic_annotation =
net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS);
TestNetLog log;
session_deps_.net_log = &log;
std::unique_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
HttpNetworkTransaction trans(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, data_writes1);
StaticSocketDataProvider data2(data_reads2, data_writes2);
session_deps_.socket_factory->AddSocketDataProvider(&data1);
session_deps_.socket_factory->AddSocketDataProvider(&data2);
TestCompletionCallback callback1;
int rv = trans.Start(&request, callback1.callback(), NetLogWithSource());
EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
rv = callback1.WaitForResult();
EXPECT_THAT(rv, IsOk());
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);
EXPECT_EQ(writes_size1, trans.GetTotalSentBytes());
int64_t reads_size1 = CountReadBytes(data_reads1);
EXPECT_EQ(reads_size1, trans.GetTotalReceivedBytes());
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());
EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
rv = callback2.WaitForResult();
EXPECT_THAT(rv, IsOk());
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);
EXPECT_EQ(writes_size1 + writes_size2, trans.GetTotalSentBytes());
int64_t reads_size2 = CountReadBytes(data_reads2);
EXPECT_EQ(reads_size1 + reads_size2, trans.GetTotalReceivedBytes());
response = trans.GetResponseInfo();
ASSERT_TRUE(response);
EXPECT_FALSE(response->auth_challenge);
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_F(HttpNetworkTransactionTest, BasicAuthWithAddressChange) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.example.org/");
request.traffic_annotation =
net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS);
TestNetLog log;
MockHostResolver* resolver = new MockHostResolver();
session_deps_.net_log = &log;
session_deps_.host_resolver.reset(resolver);
std::unique_ptr<HttpNetworkSession> session = CreateSession(&session_deps_);
HttpNetworkTransaction trans(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, data_writes1);
StaticSocketDataProvider data2(data_reads2, 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(),
NetLogWithSource())));
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);
EXPECT_EQ(writes_size1, trans.GetTotalSentBytes());
int64_t reads_size1 = CountReadBytes(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);
EXPECT_EQ(writes_size1 + writes_size2, trans.GetTotalSentBytes());
int64_t reads_size2 = CountReadBytes(data_reads2);
EXPECT_EQ(reads_size1 + reads_size2, trans.GetTotalReceivedBytes());
response = trans.GetResponseInfo();
ASSERT_TRUE(response);
EXPECT_FALSE(response->auth_challenge);
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 that, if the server requests auth indefinitely, HttpNetworkTransaction
// will eventually give up.
TEST_F(HttpNetworkTransactionTest, BasicAuthForever) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.example.org/");
request.traffic_annotation =
net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS);
TestNetLog log;
session_deps_.net_log = &log;
std::unique_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
HttpNetworkTransaction trans(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"),
// 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_writes_restart[] = {
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"),
};
StaticSocketDataProvider data(data_reads, data_writes);
session_deps_.socket_factory->AddSocketDataProvider(&data);
TestCompletionCallback callback;
int rv = callback.GetResult(
trans.Start(&request, callback.callback(), NetLogWithSource()));
std::vector<std::unique_ptr<StaticSocketDataProvider>> data_restarts;
for (int i = 0; i < 32; i++) {
// Check the previous response was a 401.
EXPECT_THAT(rv, IsOk());
const HttpResponseInfo* response = trans.GetResponseInfo();
ASSERT_TRUE(response);
EXPECT_TRUE(CheckBasicServerAuth(response->auth_challenge.get()));
data_restarts.push_back(std::make_unique<StaticSocketDataProvider>(
data_reads, data_writes_restart));
session_deps_.socket_factory->AddSocketDataProvider(
data_restarts.back().get());
rv = callback.GetResult(trans.RestartWithAuth(AuthCredentials(kFoo, kBar),
callback.callback()));
}
// After too many tries, the transaction should have given up.
EXPECT_THAT(rv, IsError(ERR_TOO_MANY_RETRIES));
}
TEST_F(HttpNetworkTransactionTest, DoNotSendAuth) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.example.org/");
request.load_flags = LOAD_DO_NOT_SEND_AUTH_DATA;
request.traffic_annotation =
net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS);
std::unique_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
HttpNetworkTransaction trans(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, data_writes);
session_deps_.socket_factory->AddSocketDataProvider(&data);
TestCompletionCallback callback;
int rv = trans.Start(&request, callback.callback(), NetLogWithSource());
EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
rv = callback.WaitForResult();
EXPECT_EQ(0, rv);
int64_t writes_size = CountWriteBytes(data_writes);
EXPECT_EQ(writes_size, trans.GetTotalSentBytes());
int64_t reads_size = CountReadBytes(data_reads);
EXPECT_EQ(reads_size, trans.GetTotalReceivedBytes());
const HttpResponseInfo* response = trans.GetResponseInfo();
ASSERT_TRUE(response);
EXPECT_FALSE(response->auth_challenge);
}
// Test the request-challenge-retry sequence for basic auth, over a keep-alive
// connection.
TEST_F(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.traffic_annotation =
net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS);
TestNetLog log;
session_deps_.net_log = &log;
std::unique_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, data_writes);
data.set_busy_before_sync_reads(true);
session_deps_.socket_factory->AddSocketDataProvider(&data);
TestCompletionCallback callback1;
HttpNetworkTransaction trans(DEFAULT_PRIORITY, session.get());
int rv = trans.Start(&request, callback1.callback(), NetLogWithSource());
ASSERT_THAT(callback1.GetResult(rv), IsOk());
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_THAT(callback2.GetResult(rv), IsOk());
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_THAT(ReadTransaction(&trans, &response_data), IsOk());
int64_t writes_size = CountWriteBytes(data_writes);
EXPECT_EQ(writes_size, trans.GetTotalSentBytes());
int64_t reads_size = CountReadBytes(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_F(HttpNetworkTransactionTest, BasicAuthKeepAliveNoBody) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.example.org/");
request.traffic_annotation =
net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS);
std::unique_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, data_writes1);
StaticSocketDataProvider data2(data_reads2, base::span<MockWrite>());
session_deps_.socket_factory->AddSocketDataProvider(&data1);
session_deps_.socket_factory->AddSocketDataProvider(&data2);
TestCompletionCallback callback1;
HttpNetworkTransaction trans(DEFAULT_PRIORITY, session.get());
int rv = trans.Start(&request, callback1.callback(), NetLogWithSource());
EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
rv = callback1.WaitForResult();
EXPECT_THAT(rv, IsOk());
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());
EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
rv = callback2.WaitForResult();
EXPECT_THAT(rv, IsOk());
response = trans.GetResponseInfo();
ASSERT_TRUE(response);
EXPECT_FALSE(response->auth_challenge);
EXPECT_EQ(5, response->headers->GetContentLength());
}
// Test the request-challenge-retry sequence for basic auth, over a keep-alive
// connection and with a large response body to drain.
TEST_F(HttpNetworkTransactionTest, BasicAuthKeepAliveLargeBody) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.example.org/");
request.traffic_annotation =
net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS);
std::unique_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, data_writes1);
StaticSocketDataProvider data2(data_reads2, base::span<MockWrite>());
session_deps_.socket_factory->AddSocketDataProvider(&data1);
session_deps_.socket_factory->AddSocketDataProvider(&data2);
TestCompletionCallback callback1;
HttpNetworkTransaction trans(DEFAULT_PRIORITY, session.get());
int rv = trans.Start(&request, callback1.callback(), NetLogWithSource());
EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
rv = callback1.WaitForResult();
EXPECT_THAT(rv, IsOk());
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());
EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
rv = callback2.WaitForResult();
EXPECT_THAT(rv, IsOk());
response = trans.GetResponseInfo();
ASSERT_TRUE(response);
EXPECT_FALSE(response->auth_challenge);
EXPECT_EQ(5, response->headers->GetContentLength());