blob: 3c652a92855f2e6f8b6e2c469698c8d5ebbc52e8 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "net/http/http_network_transaction.h"
#include <math.h> // ceil
#include <stdarg.h>
#include <string>
#include <vector>
#include "base/basictypes.h"
#include "base/compiler_specific.h"
#include "base/file_path.h"
#include "base/file_util.h"
#include "base/json/json_writer.h"
#include "base/memory/scoped_ptr.h"
#include "base/metrics/histogram.h"
#include "base/string_util.h"
#include "base/test/test_file_util.h"
#include "base/utf_string_conversions.h"
#include "net/base/auth.h"
#include "net/base/capturing_net_log.h"
#include "net/base/cert_test_util.h"
#include "net/base/completion_callback.h"
#include "net/base/host_cache.h"
#include "net/base/mock_cert_verifier.h"
#include "net/base/mock_host_resolver.h"
#include "net/base/net_log.h"
#include "net/base/net_log_unittest.h"
#include "net/base/request_priority.h"
#include "net/base/ssl_cert_request_info.h"
#include "net/base/ssl_config_service_defaults.h"
#include "net/base/ssl_info.h"
#include "net/base/test_completion_callback.h"
#include "net/base/test_data_directory.h"
#include "net/base/upload_bytes_element_reader.h"
#include "net/base/upload_data_stream.h"
#include "net/base/upload_file_element_reader.h"
#include "net/http/http_auth_handler_digest.h"
#include "net/http/http_auth_handler_mock.h"
#include "net/http/http_auth_handler_ntlm.h"
#include "net/http/http_basic_stream.h"
#include "net/http/http_network_session.h"
#include "net/http/http_network_session_peer.h"
#include "net/http/http_server_properties_impl.h"
#include "net/http/http_stream.h"
#include "net/http/http_stream_factory.h"
#include "net/http/http_transaction_unittest.h"
#include "net/proxy/proxy_config_service_fixed.h"
#include "net/proxy/proxy_resolver.h"
#include "net/proxy/proxy_service.h"
#include "net/socket/client_socket_factory.h"
#include "net/socket/mock_client_socket_pool_manager.h"
#include "net/socket/socket_test_util.h"
#include "net/socket/ssl_client_socket.h"
#include "net/spdy/spdy_framer.h"
#include "net/spdy/spdy_session.h"
#include "net/spdy/spdy_session_pool.h"
#include "net/spdy/spdy_test_util_spdy3.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/platform_test.h"
// Starboard has no way to make a file unreadable.
#if defined(OS_STARBOARD)
#define MAYBE_UploadUnreadableFile DISABLED_UploadUnreadableFile
#define MAYBE_UnreadableUploadFileAfterAuthRestart \
DISABLED_UnreadableUploadFileAfterAuthRestart
#else
#define MAYBE_UploadUnreadableFile UploadUnreadableFile
#define MAYBE_UnreadableUploadFileAfterAuthRestart \
UnreadableUploadFileAfterAuthRestart
#endif
using namespace net::test_spdy3;
//-----------------------------------------------------------------------------
namespace {
const string16 kBar(ASCIIToUTF16("bar"));
const string16 kBar2(ASCIIToUTF16("bar2"));
const string16 kBar3(ASCIIToUTF16("bar3"));
const string16 kBaz(ASCIIToUTF16("baz"));
const string16 kFirst(ASCIIToUTF16("first"));
const string16 kFoo(ASCIIToUTF16("foo"));
const string16 kFoo2(ASCIIToUTF16("foo2"));
const string16 kFoo3(ASCIIToUTF16("foo3"));
const string16 kFou(ASCIIToUTF16("fou"));
const string16 kSecond(ASCIIToUTF16("second"));
const string16 kTestingNTLM(ASCIIToUTF16("testing-ntlm"));
const string16 kWrongPassword(ASCIIToUTF16("wrongpassword"));
// MakeNextProtos is a utility function that returns a vector of std::strings
// from its arguments. Don't forget to terminate the argument list with a NULL.
std::vector<std::string> MakeNextProtos(const char* a, ...) {
std::vector<std::string> ret;
ret.push_back(a);
va_list args;
va_start(args, a);
for (;;) {
const char* value = va_arg(args, const char*);
if (value == NULL)
break;
ret.push_back(value);
}
va_end(args);
return ret;
}
// SpdyNextProtos returns a vector of NPN protocol strings for negotiating
// SPDY.
std::vector<std::string> SpdyNextProtos() {
return MakeNextProtos("http/1.1", "spdy/2", "spdy/3", NULL);
}
int GetIdleSocketCountInTransportSocketPool(net::HttpNetworkSession* session) {
return session->GetTransportSocketPool(
net::HttpNetworkSession::NORMAL_SOCKET_POOL)->IdleSocketCount();
}
int GetIdleSocketCountInSSLSocketPool(net::HttpNetworkSession* session) {
return session->GetSSLSocketPool(
net::HttpNetworkSession::NORMAL_SOCKET_POOL)->IdleSocketCount();
}
} // namespace
namespace net {
namespace {
HttpNetworkSession* CreateSession(SpdySessionDependencies* session_deps) {
return SpdySessionDependencies::SpdyCreateSession(session_deps);
}
// 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(DictionaryValue* params, std::string* headers) {
if (!params)
return false;
ListValue* header_list;
if (!params->GetList("headers", &header_list))
return false;
std::string double_quote_headers;
base::JSONWriter::Write(header_list, &double_quote_headers);
ReplaceChars(double_quote_headers, "\"", "'", headers);
return true;
}
} // namespace
class HttpNetworkTransactionSpdy3Test : public PlatformTest {
protected:
struct SimpleGetHelperResult {
int rv;
std::string status_line;
std::string response_data;
};
virtual void SetUp() {
NetworkChangeNotifier::NotifyObserversOfIPAddressChangeForTests();
MessageLoop::current()->RunUntilIdle();
}
virtual void TearDown() {
NetworkChangeNotifier::NotifyObserversOfIPAddressChangeForTests();
MessageLoop::current()->RunUntilIdle();
// Empty the current queue.
MessageLoop::current()->RunUntilIdle();
PlatformTest::TearDown();
NetworkChangeNotifier::NotifyObserversOfIPAddressChangeForTests();
MessageLoop::current()->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);
SimpleGetHelperResult SimpleGetHelperForData(StaticSocketDataProvider* data[],
size_t data_count) {
SimpleGetHelperResult out;
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.google.com/");
request.load_flags = 0;
SpdySessionDependencies session_deps;
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(CreateSession(&session_deps)));
for (size_t i = 0; i < data_count; ++i) {
session_deps.socket_factory->AddSocketDataProvider(data[i]);
}
TestCompletionCallback callback;
CapturingBoundNetLog log;
EXPECT_TRUE(log.bound().IsLoggingAllEvents());
int rv = trans->Start(&request, callback.callback(), log.bound());
EXPECT_EQ(ERR_IO_PENDING, rv);
out.rv = callback.WaitForResult();
if (out.rv != OK)
return out;
const HttpResponseInfo* response = trans->GetResponseInfo();
// Can't use ASSERT_* inside helper functions like this, so
// return an error.
if (response == NULL || response->headers == NULL) {
out.rv = ERR_UNEXPECTED;
return out;
}
out.status_line = response->headers->GetStatusLine();
EXPECT_EQ("127.0.0.1", response->socket_address.host());
EXPECT_EQ(80, response->socket_address.port());
rv = ReadTransaction(trans.get(), &out.response_data);
EXPECT_EQ(OK, rv);
CapturingNetLog::CapturedEntryList entries;
log.GetEntries(&entries);
size_t pos = ExpectLogContainsSomewhere(
entries, 0, NetLog::TYPE_HTTP_TRANSACTION_SEND_REQUEST_HEADERS,
NetLog::PHASE_NONE);
ExpectLogContainsSomewhere(
entries, pos,
NetLog::TYPE_HTTP_TRANSACTION_READ_RESPONSE_HEADERS,
NetLog::PHASE_NONE);
std::string line;
EXPECT_TRUE(entries[pos].GetStringValue("line", &line));
EXPECT_EQ("GET / HTTP/1.1\r\n", line);
std::string headers;
EXPECT_TRUE(GetHeaders(entries[pos].params.get(), &headers));
EXPECT_EQ("['Host: www.google.com','Connection: keep-alive']", headers);
return out;
}
SimpleGetHelperResult SimpleGetHelper(MockRead data_reads[],
size_t reads_count) {
StaticSocketDataProvider reads(data_reads, reads_count, NULL, 0);
StaticSocketDataProvider* data[] = { &reads };
return SimpleGetHelperForData(data, 1);
}
void ConnectStatusHelperWithExpectedStatus(const MockRead& status,
int expected_status);
void ConnectStatusHelper(const MockRead& status);
};
namespace {
// Fill |str| with a long header list that consumes >= |size| bytes.
void FillLargeHeadersString(std::string* str, int size) {
const char* row =
"SomeHeaderName: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\r\n";
const int sizeof_row = strlen(row);
const int num_rows = static_cast<int>(
ceil(static_cast<float>(size) / sizeof_row));
const int sizeof_data = num_rows * sizeof_row;
DCHECK(sizeof_data >= size);
str->reserve(sizeof_data);
for (int i = 0; i < num_rows; ++i)
str->append(row, sizeof_row);
}
// Alternative functions that eliminate randomness and dependency on the local
// host name so that the generated NTLM messages are reproducible.
void MockGenerateRandom1(uint8* output, size_t n) {
static const uint8 bytes[] = {
0x55, 0x29, 0x66, 0x26, 0x6b, 0x9c, 0x73, 0x54
};
static size_t current_byte = 0;
for (size_t i = 0; i < n; ++i) {
output[i] = bytes[current_byte++];
current_byte %= arraysize(bytes);
}
}
void MockGenerateRandom2(uint8* output, size_t n) {
static const uint8 bytes[] = {
0x96, 0x79, 0x85, 0xe7, 0x49, 0x93, 0x70, 0xa1,
0x4e, 0xe7, 0x87, 0x45, 0x31, 0x5b, 0xd3, 0x1f
};
static size_t current_byte = 0;
for (size_t i = 0; i < n; ++i) {
output[i] = bytes[current_byte++];
current_byte %= arraysize(bytes);
}
}
std::string MockGetHostName() {
return "WTC-WIN7";
}
template<typename ParentPool>
class CaptureGroupNameSocketPool : public ParentPool {
public:
CaptureGroupNameSocketPool(HostResolver* host_resolver,
CertVerifier* cert_verifier);
const std::string last_group_name_received() const {
return last_group_name_;
}
virtual int RequestSocket(const std::string& group_name,
const void* socket_params,
RequestPriority priority,
ClientSocketHandle* handle,
const CompletionCallback& callback,
const BoundNetLog& net_log) {
last_group_name_ = group_name;
return ERR_IO_PENDING;
}
virtual void CancelRequest(const std::string& group_name,
ClientSocketHandle* handle) {}
virtual void ReleaseSocket(const std::string& group_name,
StreamSocket* socket,
int id) {}
virtual void CloseIdleSockets() {}
virtual int IdleSocketCount() const {
return 0;
}
virtual int IdleSocketCountInGroup(const std::string& group_name) const {
return 0;
}
virtual LoadState GetLoadState(const std::string& group_name,
const ClientSocketHandle* handle) const {
return LOAD_STATE_IDLE;
}
virtual base::TimeDelta ConnectionTimeout() const {
return base::TimeDelta();
}
private:
std::string last_group_name_;
};
typedef CaptureGroupNameSocketPool<TransportClientSocketPool>
CaptureGroupNameTransportSocketPool;
typedef CaptureGroupNameSocketPool<HttpProxyClientSocketPool>
CaptureGroupNameHttpProxySocketPool;
typedef CaptureGroupNameSocketPool<SOCKSClientSocketPool>
CaptureGroupNameSOCKSSocketPool;
typedef CaptureGroupNameSocketPool<SSLClientSocketPool>
CaptureGroupNameSSLSocketPool;
template<typename ParentPool>
CaptureGroupNameSocketPool<ParentPool>::CaptureGroupNameSocketPool(
HostResolver* host_resolver,
CertVerifier* /* cert_verifier */)
: ParentPool(0, 0, NULL, host_resolver, NULL, NULL) {}
template<>
CaptureGroupNameHttpProxySocketPool::CaptureGroupNameSocketPool(
HostResolver* host_resolver,
CertVerifier* /* cert_verifier */)
: HttpProxyClientSocketPool(0, 0, NULL, host_resolver, NULL, NULL, NULL) {}
template<>
CaptureGroupNameSSLSocketPool::CaptureGroupNameSocketPool(
HostResolver* host_resolver,
CertVerifier* cert_verifier)
: SSLClientSocketPool(0, 0, NULL, host_resolver, cert_verifier, NULL,
NULL, "", NULL, NULL, NULL, NULL, NULL, NULL) {}
//-----------------------------------------------------------------------------
// This is the expected return from a current server advertising SPDY.
static const char kAlternateProtocolHttpHeader[] =
"Alternate-Protocol: 443:npn-spdy/3\r\n\r\n";
// Helper functions for validating that AuthChallengeInfo's are correctly
// configured for common cases.
bool CheckBasicServerAuth(const AuthChallengeInfo* auth_challenge) {
if (!auth_challenge)
return false;
EXPECT_FALSE(auth_challenge->is_proxy);
EXPECT_EQ("www.google.com:80", auth_challenge->challenger.ToString());
EXPECT_EQ("MyRealm1", auth_challenge->realm);
EXPECT_EQ("basic", auth_challenge->scheme);
return true;
}
bool CheckBasicProxyAuth(const AuthChallengeInfo* auth_challenge) {
if (!auth_challenge)
return false;
EXPECT_TRUE(auth_challenge->is_proxy);
EXPECT_EQ("myproxy:70", auth_challenge->challenger.ToString());
EXPECT_EQ("MyRealm1", auth_challenge->realm);
EXPECT_EQ("basic", auth_challenge->scheme);
return true;
}
bool CheckDigestServerAuth(const AuthChallengeInfo* auth_challenge) {
if (!auth_challenge)
return false;
EXPECT_FALSE(auth_challenge->is_proxy);
EXPECT_EQ("www.google.com:80", auth_challenge->challenger.ToString());
EXPECT_EQ("digestive", auth_challenge->realm);
EXPECT_EQ("digest", auth_challenge->scheme);
return true;
}
bool CheckNTLMServerAuth(const AuthChallengeInfo* auth_challenge) {
if (!auth_challenge)
return false;
EXPECT_FALSE(auth_challenge->is_proxy);
EXPECT_EQ("172.22.68.17:80", auth_challenge->challenger.ToString());
EXPECT_EQ(std::string(), auth_challenge->realm);
EXPECT_EQ("ntlm", auth_challenge->scheme);
return true;
}
} // namespace
TEST_F(HttpNetworkTransactionSpdy3Test, Basic) {
SpdySessionDependencies session_deps;
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(CreateSession(&session_deps)));
}
TEST_F(HttpNetworkTransactionSpdy3Test, SimpleGET) {
MockRead data_reads[] = {
MockRead("HTTP/1.0 200 OK\r\n\r\n"),
MockRead("hello world"),
MockRead(SYNCHRONOUS, OK),
};
SimpleGetHelperResult out = SimpleGetHelper(data_reads,
arraysize(data_reads));
EXPECT_EQ(OK, out.rv);
EXPECT_EQ("HTTP/1.0 200 OK", out.status_line);
EXPECT_EQ("hello world", out.response_data);
}
// Response with no status line.
TEST_F(HttpNetworkTransactionSpdy3Test, SimpleGETNoHeaders) {
MockRead data_reads[] = {
MockRead("hello world"),
MockRead(SYNCHRONOUS, OK),
};
SimpleGetHelperResult out = SimpleGetHelper(data_reads,
arraysize(data_reads));
EXPECT_EQ(OK, out.rv);
EXPECT_EQ("HTTP/0.9 200 OK", out.status_line);
EXPECT_EQ("hello world", out.response_data);
}
// Allow up to 4 bytes of junk to precede status line.
TEST_F(HttpNetworkTransactionSpdy3Test, StatusLineJunk2Bytes) {
MockRead data_reads[] = {
MockRead("xxxHTTP/1.0 404 Not Found\nServer: blah\n\nDATA"),
MockRead(SYNCHRONOUS, OK),
};
SimpleGetHelperResult out = SimpleGetHelper(data_reads,
arraysize(data_reads));
EXPECT_EQ(OK, out.rv);
EXPECT_EQ("HTTP/1.0 404 Not Found", out.status_line);
EXPECT_EQ("DATA", out.response_data);
}
// Allow up to 4 bytes of junk to precede status line.
TEST_F(HttpNetworkTransactionSpdy3Test, StatusLineJunk4Bytes) {
MockRead data_reads[] = {
MockRead("\n\nQJHTTP/1.0 404 Not Found\nServer: blah\n\nDATA"),
MockRead(SYNCHRONOUS, OK),
};
SimpleGetHelperResult out = SimpleGetHelper(data_reads,
arraysize(data_reads));
EXPECT_EQ(OK, out.rv);
EXPECT_EQ("HTTP/1.0 404 Not Found", out.status_line);
EXPECT_EQ("DATA", out.response_data);
}
// Beyond 4 bytes of slop and it should fail to find a status line.
TEST_F(HttpNetworkTransactionSpdy3Test, StatusLineJunk5Bytes) {
MockRead data_reads[] = {
MockRead("xxxxxHTTP/1.1 404 Not Found\nServer: blah"),
MockRead(SYNCHRONOUS, OK),
};
SimpleGetHelperResult out = SimpleGetHelper(data_reads,
arraysize(data_reads));
EXPECT_EQ(OK, out.rv);
EXPECT_EQ("HTTP/0.9 200 OK", out.status_line);
EXPECT_EQ("xxxxxHTTP/1.1 404 Not Found\nServer: blah", out.response_data);
}
// Same as StatusLineJunk4Bytes, except the read chunks are smaller.
TEST_F(HttpNetworkTransactionSpdy3Test, StatusLineJunk4Bytes_Slow) {
MockRead data_reads[] = {
MockRead("\n"),
MockRead("\n"),
MockRead("Q"),
MockRead("J"),
MockRead("HTTP/1.0 404 Not Found\nServer: blah\n\nDATA"),
MockRead(SYNCHRONOUS, OK),
};
SimpleGetHelperResult out = SimpleGetHelper(data_reads,
arraysize(data_reads));
EXPECT_EQ(OK, out.rv);
EXPECT_EQ("HTTP/1.0 404 Not Found", out.status_line);
EXPECT_EQ("DATA", out.response_data);
}
// Close the connection before enough bytes to have a status line.
TEST_F(HttpNetworkTransactionSpdy3Test, StatusLinePartial) {
MockRead data_reads[] = {
MockRead("HTT"),
MockRead(SYNCHRONOUS, OK),
};
SimpleGetHelperResult out = SimpleGetHelper(data_reads,
arraysize(data_reads));
EXPECT_EQ(OK, out.rv);
EXPECT_EQ("HTTP/0.9 200 OK", out.status_line);
EXPECT_EQ("HTT", out.response_data);
}
// Simulate a 204 response, lacking a Content-Length header, sent over a
// persistent connection. The response should still terminate since a 204
// cannot have a response body.
TEST_F(HttpNetworkTransactionSpdy3Test, StopsReading204) {
MockRead data_reads[] = {
MockRead("HTTP/1.1 204 No Content\r\n\r\n"),
MockRead("junk"), // Should not be read!!
MockRead(SYNCHRONOUS, OK),
};
SimpleGetHelperResult out = SimpleGetHelper(data_reads,
arraysize(data_reads));
EXPECT_EQ(OK, out.rv);
EXPECT_EQ("HTTP/1.1 204 No Content", out.status_line);
EXPECT_EQ("", out.response_data);
}
// A simple request using chunked encoding with some extra data after.
// (Like might be seen in a pipelined response.)
TEST_F(HttpNetworkTransactionSpdy3Test, ChunkedEncoding) {
MockRead data_reads[] = {
MockRead("HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n"),
MockRead("5\r\nHello\r\n"),
MockRead("1\r\n"),
MockRead(" \r\n"),
MockRead("5\r\nworld\r\n"),
MockRead("0\r\n\r\nHTTP/1.1 200 OK\r\n"),
MockRead(SYNCHRONOUS, OK),
};
SimpleGetHelperResult out = SimpleGetHelper(data_reads,
arraysize(data_reads));
EXPECT_EQ(OK, out.rv);
EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
EXPECT_EQ("Hello world", out.response_data);
}
// Next tests deal with http://crbug.com/56344.
TEST_F(HttpNetworkTransactionSpdy3Test,
MultipleContentLengthHeadersNoTransferEncoding) {
MockRead data_reads[] = {
MockRead("HTTP/1.1 200 OK\r\n"),
MockRead("Content-Length: 10\r\n"),
MockRead("Content-Length: 5\r\n\r\n"),
};
SimpleGetHelperResult out = SimpleGetHelper(data_reads,
arraysize(data_reads));
EXPECT_EQ(ERR_RESPONSE_HEADERS_MULTIPLE_CONTENT_LENGTH, out.rv);
}
TEST_F(HttpNetworkTransactionSpdy3Test,
DuplicateContentLengthHeadersNoTransferEncoding) {
MockRead data_reads[] = {
MockRead("HTTP/1.1 200 OK\r\n"),
MockRead("Content-Length: 5\r\n"),
MockRead("Content-Length: 5\r\n\r\n"),
MockRead("Hello"),
};
SimpleGetHelperResult out = SimpleGetHelper(data_reads,
arraysize(data_reads));
EXPECT_EQ(OK, out.rv);
EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
EXPECT_EQ("Hello", out.response_data);
}
TEST_F(HttpNetworkTransactionSpdy3Test,
ComplexContentLengthHeadersNoTransferEncoding) {
// More than 2 dupes.
{
MockRead data_reads[] = {
MockRead("HTTP/1.1 200 OK\r\n"),
MockRead("Content-Length: 5\r\n"),
MockRead("Content-Length: 5\r\n"),
MockRead("Content-Length: 5\r\n\r\n"),
MockRead("Hello"),
};
SimpleGetHelperResult out = SimpleGetHelper(data_reads,
arraysize(data_reads));
EXPECT_EQ(OK, out.rv);
EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
EXPECT_EQ("Hello", out.response_data);
}
// HTTP/1.0
{
MockRead data_reads[] = {
MockRead("HTTP/1.0 200 OK\r\n"),
MockRead("Content-Length: 5\r\n"),
MockRead("Content-Length: 5\r\n"),
MockRead("Content-Length: 5\r\n\r\n"),
MockRead("Hello"),
};
SimpleGetHelperResult out = SimpleGetHelper(data_reads,
arraysize(data_reads));
EXPECT_EQ(OK, out.rv);
EXPECT_EQ("HTTP/1.0 200 OK", out.status_line);
EXPECT_EQ("Hello", out.response_data);
}
// 2 dupes and one mismatched.
{
MockRead data_reads[] = {
MockRead("HTTP/1.1 200 OK\r\n"),
MockRead("Content-Length: 10\r\n"),
MockRead("Content-Length: 10\r\n"),
MockRead("Content-Length: 5\r\n\r\n"),
};
SimpleGetHelperResult out = SimpleGetHelper(data_reads,
arraysize(data_reads));
EXPECT_EQ(ERR_RESPONSE_HEADERS_MULTIPLE_CONTENT_LENGTH, out.rv);
}
}
TEST_F(HttpNetworkTransactionSpdy3Test,
MultipleContentLengthHeadersTransferEncoding) {
MockRead data_reads[] = {
MockRead("HTTP/1.1 200 OK\r\n"),
MockRead("Content-Length: 666\r\n"),
MockRead("Content-Length: 1337\r\n"),
MockRead("Transfer-Encoding: chunked\r\n\r\n"),
MockRead("5\r\nHello\r\n"),
MockRead("1\r\n"),
MockRead(" \r\n"),
MockRead("5\r\nworld\r\n"),
MockRead("0\r\n\r\nHTTP/1.1 200 OK\r\n"),
MockRead(SYNCHRONOUS, OK),
};
SimpleGetHelperResult out = SimpleGetHelper(data_reads,
arraysize(data_reads));
EXPECT_EQ(OK, out.rv);
EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
EXPECT_EQ("Hello world", out.response_data);
}
// Next tests deal with http://crbug.com/98895.
// Checks that a single Content-Disposition header results in no error.
TEST_F(HttpNetworkTransactionSpdy3Test, SingleContentDispositionHeader) {
MockRead data_reads[] = {
MockRead("HTTP/1.1 200 OK\r\n"),
MockRead("Content-Disposition: attachment;filename=\"salutations.txt\"r\n"),
MockRead("Content-Length: 5\r\n\r\n"),
MockRead("Hello"),
};
SimpleGetHelperResult out = SimpleGetHelper(data_reads,
arraysize(data_reads));
EXPECT_EQ(OK, out.rv);
EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
EXPECT_EQ("Hello", out.response_data);
}
// Checks that two identical Content-Disposition headers result in no error.
TEST_F(HttpNetworkTransactionSpdy3Test,
TwoIdenticalContentDispositionHeaders) {
MockRead data_reads[] = {
MockRead("HTTP/1.1 200 OK\r\n"),
MockRead("Content-Disposition: attachment;filename=\"greetings.txt\"r\n"),
MockRead("Content-Disposition: attachment;filename=\"greetings.txt\"r\n"),
MockRead("Content-Length: 5\r\n\r\n"),
MockRead("Hello"),
};
SimpleGetHelperResult out = SimpleGetHelper(data_reads,
arraysize(data_reads));
EXPECT_EQ(OK, out.rv);
EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
EXPECT_EQ("Hello", out.response_data);
}
// Checks that two distinct Content-Disposition headers result in an error.
TEST_F(HttpNetworkTransactionSpdy3Test, TwoDistinctContentDispositionHeaders) {
MockRead data_reads[] = {
MockRead("HTTP/1.1 200 OK\r\n"),
MockRead("Content-Disposition: attachment;filename=\"greetings.txt\"r\n"),
MockRead("Content-Disposition: attachment;filename=\"hi.txt\"r\n"),
MockRead("Content-Length: 5\r\n\r\n"),
MockRead("Hello"),
};
SimpleGetHelperResult out = SimpleGetHelper(data_reads,
arraysize(data_reads));
EXPECT_EQ(ERR_RESPONSE_HEADERS_MULTIPLE_CONTENT_DISPOSITION, out.rv);
}
// Checks that two identical Location headers result in no error.
// Also tests Location header behavior.
TEST_F(HttpNetworkTransactionSpdy3Test, TwoIdenticalLocationHeaders) {
MockRead data_reads[] = {
MockRead("HTTP/1.1 302 Redirect\r\n"),
MockRead("Location: http://good.com/\r\n"),
MockRead("Location: http://good.com/\r\n"),
MockRead("Content-Length: 0\r\n\r\n"),
MockRead(SYNCHRONOUS, OK),
};
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://redirect.com/");
request.load_flags = 0;
SpdySessionDependencies session_deps;
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(CreateSession(&session_deps)));
StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0);
session_deps.socket_factory->AddSocketDataProvider(&data);
TestCompletionCallback callback;
int rv = trans->Start(&request, callback.callback(), BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
EXPECT_EQ(OK, callback.WaitForResult());
const HttpResponseInfo* response = trans->GetResponseInfo();
ASSERT_TRUE(response != NULL && response->headers != NULL);
EXPECT_EQ("HTTP/1.1 302 Redirect", response->headers->GetStatusLine());
std::string url;
EXPECT_TRUE(response->headers->IsRedirect(&url));
EXPECT_EQ("http://good.com/", url);
}
// Checks that two distinct Location headers result in an error.
TEST_F(HttpNetworkTransactionSpdy3Test, TwoDistinctLocationHeaders) {
MockRead data_reads[] = {
MockRead("HTTP/1.1 302 Redirect\r\n"),
MockRead("Location: http://good.com/\r\n"),
MockRead("Location: http://evil.com/\r\n"),
MockRead("Content-Length: 0\r\n\r\n"),
MockRead(SYNCHRONOUS, OK),
};
SimpleGetHelperResult out = SimpleGetHelper(data_reads,
arraysize(data_reads));
EXPECT_EQ(ERR_RESPONSE_HEADERS_MULTIPLE_LOCATION, out.rv);
}
// Do a request using the HEAD method. Verify that we don't try to read the
// message body (since HEAD has none).
TEST_F(HttpNetworkTransactionSpdy3Test, Head) {
HttpRequestInfo request;
request.method = "HEAD";
request.url = GURL("http://www.google.com/");
request.load_flags = 0;
SpdySessionDependencies session_deps;
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(CreateSession(&session_deps)));
MockWrite data_writes1[] = {
MockWrite("HEAD / HTTP/1.1\r\n"
"Host: www.google.com\r\n"
"Connection: keep-alive\r\n"
"Content-Length: 0\r\n\r\n"),
};
MockRead data_reads1[] = {
MockRead("HTTP/1.1 404 Not Found\r\n"),
MockRead("Server: Blah\r\n"),
MockRead("Content-Length: 1234\r\n\r\n"),
// No response body because the test stops reading here.
MockRead(SYNCHRONOUS, ERR_UNEXPECTED), // Should not be reached.
};
StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
data_writes1, arraysize(data_writes1));
session_deps.socket_factory->AddSocketDataProvider(&data1);
TestCompletionCallback callback1;
int rv = trans->Start(&request, callback1.callback(), BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback1.WaitForResult();
EXPECT_EQ(OK, rv);
const HttpResponseInfo* response = trans->GetResponseInfo();
ASSERT_TRUE(response != NULL);
// Check that the headers got parsed.
EXPECT_TRUE(response->headers != NULL);
EXPECT_EQ(1234, response->headers->GetContentLength());
EXPECT_EQ("HTTP/1.1 404 Not Found", response->headers->GetStatusLine());
std::string server_header;
void* iter = NULL;
bool has_server_header = response->headers->EnumerateHeader(
&iter, "Server", &server_header);
EXPECT_TRUE(has_server_header);
EXPECT_EQ("Blah", server_header);
// Reading should give EOF right away, since there is no message body
// (despite non-zero content-length).
std::string response_data;
rv = ReadTransaction(trans.get(), &response_data);
EXPECT_EQ(OK, rv);
EXPECT_EQ("", response_data);
}
TEST_F(HttpNetworkTransactionSpdy3Test, ReuseConnection) {
SpdySessionDependencies session_deps;
scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps));
MockRead data_reads[] = {
MockRead("HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\n"),
MockRead("hello"),
MockRead("HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\n"),
MockRead("world"),
MockRead(SYNCHRONOUS, OK),
};
StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0);
session_deps.socket_factory->AddSocketDataProvider(&data);
const char* const kExpectedResponseData[] = {
"hello", "world"
};
for (int i = 0; i < 2; ++i) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.google.com/");
request.load_flags = 0;
scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session));
TestCompletionCallback callback;
int rv = trans->Start(&request, callback.callback(), BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback.WaitForResult();
EXPECT_EQ(OK, rv);
const HttpResponseInfo* response = trans->GetResponseInfo();
ASSERT_TRUE(response != NULL);
EXPECT_TRUE(response->headers != NULL);
EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
std::string response_data;
rv = ReadTransaction(trans.get(), &response_data);
EXPECT_EQ(OK, rv);
EXPECT_EQ(kExpectedResponseData[i], response_data);
}
}
TEST_F(HttpNetworkTransactionSpdy3Test, Ignores100) {
ScopedVector<UploadElementReader> element_readers;
element_readers.push_back(new UploadBytesElementReader("foo", 3));
UploadDataStream upload_data_stream(&element_readers, 0);
HttpRequestInfo request;
request.method = "POST";
request.url = GURL("http://www.foo.com/");
request.upload_data_stream = &upload_data_stream;
request.load_flags = 0;
SpdySessionDependencies session_deps;
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(CreateSession(&session_deps)));
MockRead data_reads[] = {
MockRead("HTTP/1.0 100 Continue\r\n\r\n"),
MockRead("HTTP/1.0 200 OK\r\n\r\n"),
MockRead("hello world"),
MockRead(SYNCHRONOUS, OK),
};
StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0);
session_deps.socket_factory->AddSocketDataProvider(&data);
TestCompletionCallback callback;
int rv = trans->Start(&request, callback.callback(), BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback.WaitForResult();
EXPECT_EQ(OK, rv);
const HttpResponseInfo* response = trans->GetResponseInfo();
ASSERT_TRUE(response != NULL);
EXPECT_TRUE(response->headers != NULL);
EXPECT_EQ("HTTP/1.0 200 OK", response->headers->GetStatusLine());
std::string response_data;
rv = ReadTransaction(trans.get(), &response_data);
EXPECT_EQ(OK, rv);
EXPECT_EQ("hello world", response_data);
}
// This test is almost the same as Ignores100 above, but the response contains
// a 102 instead of a 100. Also, instead of HTTP/1.0 the response is
// HTTP/1.1 and the two status headers are read in one read.
TEST_F(HttpNetworkTransactionSpdy3Test, Ignores1xx) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.foo.com/");
request.load_flags = 0;
SpdySessionDependencies session_deps;
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(CreateSession(&session_deps)));
MockRead data_reads[] = {
MockRead("HTTP/1.1 102 Unspecified status code\r\n\r\n"
"HTTP/1.1 200 OK\r\n\r\n"),
MockRead("hello world"),
MockRead(SYNCHRONOUS, OK),
};
StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0);
session_deps.socket_factory->AddSocketDataProvider(&data);
TestCompletionCallback callback;
int rv = trans->Start(&request, callback.callback(), BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback.WaitForResult();
EXPECT_EQ(OK, rv);
const HttpResponseInfo* response = trans->GetResponseInfo();
ASSERT_TRUE(response != NULL);
EXPECT_TRUE(response->headers != NULL);
EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
std::string response_data;
rv = ReadTransaction(trans.get(), &response_data);
EXPECT_EQ(OK, rv);
EXPECT_EQ("hello world", response_data);
}
TEST_F(HttpNetworkTransactionSpdy3Test, Incomplete100ThenEOF) {
HttpRequestInfo request;
request.method = "POST";
request.url = GURL("http://www.foo.com/");
request.load_flags = 0;
SpdySessionDependencies session_deps;
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(CreateSession(&session_deps)));
MockRead data_reads[] = {
MockRead(SYNCHRONOUS, "HTTP/1.0 100 Continue\r\n"),
MockRead(ASYNC, 0),
};
StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0);
session_deps.socket_factory->AddSocketDataProvider(&data);
TestCompletionCallback callback;
int rv = trans->Start(&request, callback.callback(), BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback.WaitForResult();
EXPECT_EQ(OK, rv);
std::string response_data;
rv = ReadTransaction(trans.get(), &response_data);
EXPECT_EQ(OK, rv);
EXPECT_EQ("", response_data);
}
TEST_F(HttpNetworkTransactionSpdy3Test, EmptyResponse) {
HttpRequestInfo request;
request.method = "POST";
request.url = GURL("http://www.foo.com/");
request.load_flags = 0;
SpdySessionDependencies session_deps;
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(CreateSession(&session_deps)));
MockRead data_reads[] = {
MockRead(ASYNC, 0),
};
StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0);
session_deps.socket_factory->AddSocketDataProvider(&data);
TestCompletionCallback callback;
int rv = trans->Start(&request, callback.callback(), BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback.WaitForResult();
EXPECT_EQ(ERR_EMPTY_RESPONSE, rv);
}
void HttpNetworkTransactionSpdy3Test::KeepAliveConnectionResendRequestTest(
const MockWrite* write_failure,
const MockRead* read_failure) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.foo.com/");
request.load_flags = 0;
SpdySessionDependencies session_deps;
scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps));
// Written data for successfully sending both requests.
MockWrite data1_writes[] = {
MockWrite("GET / HTTP/1.1\r\n"
"Host: www.foo.com\r\n"
"Connection: keep-alive\r\n\r\n"),
MockWrite("GET / HTTP/1.1\r\n"
"Host: www.foo.com\r\n"
"Connection: keep-alive\r\n\r\n")
};
// Read results for the first request.
MockRead data1_reads[] = {
MockRead("HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\n"),
MockRead("hello"),
MockRead(ASYNC, OK),
};
if (write_failure) {
ASSERT_TRUE(!read_failure);
data1_writes[1] = *write_failure;
} else {
ASSERT_TRUE(read_failure);
data1_reads[2] = *read_failure;
}
StaticSocketDataProvider data1(data1_reads, arraysize(data1_reads),
data1_writes, arraysize(data1_writes));
session_deps.socket_factory->AddSocketDataProvider(&data1);
MockRead data2_reads[] = {
MockRead("HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\n"),
MockRead("world"),
MockRead(ASYNC, OK),
};
StaticSocketDataProvider data2(data2_reads, arraysize(data2_reads), NULL, 0);
session_deps.socket_factory->AddSocketDataProvider(&data2);
const char* kExpectedResponseData[] = {
"hello", "world"
};
for (int i = 0; i < 2; ++i) {
TestCompletionCallback callback;
scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session));
int rv = trans->Start(&request, callback.callback(), BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback.WaitForResult();
EXPECT_EQ(OK, rv);
const HttpResponseInfo* response = trans->GetResponseInfo();
ASSERT_TRUE(response != NULL);
EXPECT_TRUE(response->headers != NULL);
EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
std::string response_data;
rv = ReadTransaction(trans.get(), &response_data);
EXPECT_EQ(OK, rv);
EXPECT_EQ(kExpectedResponseData[i], response_data);
}
}
TEST_F(HttpNetworkTransactionSpdy3Test,
KeepAliveConnectionNotConnectedOnWrite) {
MockWrite write_failure(ASYNC, ERR_SOCKET_NOT_CONNECTED);
KeepAliveConnectionResendRequestTest(&write_failure, NULL);
}
TEST_F(HttpNetworkTransactionSpdy3Test, KeepAliveConnectionReset) {
MockRead read_failure(ASYNC, ERR_CONNECTION_RESET);
KeepAliveConnectionResendRequestTest(NULL, &read_failure);
}
TEST_F(HttpNetworkTransactionSpdy3Test, KeepAliveConnectionEOF) {
MockRead read_failure(SYNCHRONOUS, OK); // EOF
KeepAliveConnectionResendRequestTest(NULL, &read_failure);
}
TEST_F(HttpNetworkTransactionSpdy3Test, NonKeepAliveConnectionReset) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.google.com/");
request.load_flags = 0;
SpdySessionDependencies session_deps;
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(CreateSession(&session_deps)));
MockRead data_reads[] = {
MockRead(ASYNC, ERR_CONNECTION_RESET),
MockRead("HTTP/1.0 200 OK\r\n\r\n"), // Should not be used
MockRead("hello world"),
MockRead(SYNCHRONOUS, OK),
};
StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0);
session_deps.socket_factory->AddSocketDataProvider(&data);
TestCompletionCallback callback;
int rv = trans->Start(&request, callback.callback(), BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback.WaitForResult();
EXPECT_EQ(ERR_CONNECTION_RESET, rv);
const HttpResponseInfo* response = trans->GetResponseInfo();
EXPECT_TRUE(response == NULL);
}
// What do various browsers do when the server closes a non-keepalive
// connection without sending any response header or body?
//
// IE7: error page
// Safari 3.1.2 (Windows): error page
// Firefox 3.0.1: blank page
// Opera 9.52: after five attempts, blank page
// Us with WinHTTP: error page (ERR_INVALID_RESPONSE)
// Us: error page (EMPTY_RESPONSE)
TEST_F(HttpNetworkTransactionSpdy3Test, NonKeepAliveConnectionEOF) {
MockRead data_reads[] = {
MockRead(SYNCHRONOUS, OK), // EOF
MockRead("HTTP/1.0 200 OK\r\n\r\n"), // Should not be used
MockRead("hello world"),
MockRead(SYNCHRONOUS, OK),
};
SimpleGetHelperResult out = SimpleGetHelper(data_reads,
arraysize(data_reads));
EXPECT_EQ(ERR_EMPTY_RESPONSE, out.rv);
}
// 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(HttpNetworkTransactionSpdy3Test, KeepAliveEarlyClose) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.google.com/");
request.load_flags = 0;
SpdySessionDependencies session_deps;
scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps));
scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session));
MockRead data_reads[] = {
MockRead("HTTP/1.0 200 OK\r\n"),
MockRead("Connection: keep-alive\r\n"),
MockRead("Content-Length: 100\r\n\r\n"),
MockRead("hello"),
MockRead(SYNCHRONOUS, 0),
};
StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0);
session_deps.socket_factory->AddSocketDataProvider(&data);
TestCompletionCallback callback;
int rv = trans->Start(&request, callback.callback(), BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback.WaitForResult();
EXPECT_EQ(OK, rv);
scoped_refptr<IOBufferWithSize> io_buf(new IOBufferWithSize(100));
rv = trans->Read(io_buf, io_buf->size(), callback.callback());
if (rv == ERR_IO_PENDING)
rv = callback.WaitForResult();
EXPECT_EQ(5, rv);
rv = trans->Read(io_buf, io_buf->size(), callback.callback());
EXPECT_EQ(ERR_CONTENT_LENGTH_MISMATCH, rv);
trans.reset();
MessageLoop::current()->RunUntilIdle();
EXPECT_EQ(0, GetIdleSocketCountInTransportSocketPool(session.get()));
}
TEST_F(HttpNetworkTransactionSpdy3Test, KeepAliveEarlyClose2) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.google.com/");
request.load_flags = 0;
SpdySessionDependencies session_deps;
scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps));
scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session));
MockRead data_reads[] = {
MockRead("HTTP/1.0 200 OK\r\n"),
MockRead("Connection: keep-alive\r\n"),
MockRead("Content-Length: 100\r\n\r\n"),
MockRead(SYNCHRONOUS, 0),
};
StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0);
session_deps.socket_factory->AddSocketDataProvider(&data);
TestCompletionCallback callback;
int rv = trans->Start(&request, callback.callback(), BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback.WaitForResult();
EXPECT_EQ(OK, rv);
scoped_refptr<IOBufferWithSize> io_buf(new IOBufferWithSize(100));
rv = trans->Read(io_buf, io_buf->size(), callback.callback());
if (rv == ERR_IO_PENDING)
rv = callback.WaitForResult();
EXPECT_EQ(ERR_CONTENT_LENGTH_MISMATCH, rv);
trans.reset();
MessageLoop::current()->RunUntilIdle();
EXPECT_EQ(0, GetIdleSocketCountInTransportSocketPool(session.get()));
}
// Test that we correctly reuse a keep-alive connection after not explicitly
// reading the body.
TEST_F(HttpNetworkTransactionSpdy3Test, KeepAliveAfterUnreadBody) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.foo.com/");
request.load_flags = 0;
SpdySessionDependencies session_deps;
scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps));
// Note that because all these reads happen in the same
// StaticSocketDataProvider, it shows that the same socket is being reused for
// all transactions.
MockRead data1_reads[] = {
MockRead("HTTP/1.1 204 No Content\r\n\r\n"),
MockRead("HTTP/1.1 205 Reset Content\r\n\r\n"),
MockRead("HTTP/1.1 304 Not Modified\r\n\r\n"),
MockRead("HTTP/1.1 302 Found\r\n"
"Content-Length: 0\r\n\r\n"),
MockRead("HTTP/1.1 302 Found\r\n"
"Content-Length: 5\r\n\r\n"
"hello"),
MockRead("HTTP/1.1 301 Moved Permanently\r\n"
"Content-Length: 0\r\n\r\n"),
MockRead("HTTP/1.1 301 Moved Permanently\r\n"
"Content-Length: 5\r\n\r\n"
"hello"),
MockRead("HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\n"),
MockRead("hello"),
};
StaticSocketDataProvider data1(data1_reads, arraysize(data1_reads), NULL, 0);
session_deps.socket_factory->AddSocketDataProvider(&data1);
MockRead data2_reads[] = {
MockRead(SYNCHRONOUS, ERR_UNEXPECTED), // Should not be reached.
};
StaticSocketDataProvider data2(data2_reads, arraysize(data2_reads), NULL, 0);
session_deps.socket_factory->AddSocketDataProvider(&data2);
const int kNumUnreadBodies = arraysize(data1_reads) - 2;
std::string response_lines[kNumUnreadBodies];
for (size_t i = 0; i < arraysize(data1_reads) - 2; ++i) {
TestCompletionCallback callback;
scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session));
int rv = trans->Start(&request, callback.callback(), BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback.WaitForResult();
EXPECT_EQ(OK, rv);
const HttpResponseInfo* response = trans->GetResponseInfo();
ASSERT_TRUE(response != NULL);
ASSERT_TRUE(response->headers != NULL);
response_lines[i] = response->headers->GetStatusLine();
// We intentionally don't read the response bodies.
}
const char* const kStatusLines[] = {
"HTTP/1.1 204 No Content",
"HTTP/1.1 205 Reset Content",
"HTTP/1.1 304 Not Modified",
"HTTP/1.1 302 Found",
"HTTP/1.1 302 Found",
"HTTP/1.1 301 Moved Permanently",
"HTTP/1.1 301 Moved Permanently",
};
COMPILE_ASSERT(kNumUnreadBodies == arraysize(kStatusLines),
forgot_to_update_kStatusLines);
for (int i = 0; i < kNumUnreadBodies; ++i)
EXPECT_EQ(kStatusLines[i], response_lines[i]);
TestCompletionCallback callback;
scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session));
int rv = trans->Start(&request, callback.callback(), BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback.WaitForResult();
EXPECT_EQ(OK, rv);
const HttpResponseInfo* response = trans->GetResponseInfo();
ASSERT_TRUE(response != NULL);
ASSERT_TRUE(response->headers != NULL);
EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
std::string response_data;
rv = ReadTransaction(trans.get(), &response_data);
EXPECT_EQ(OK, rv);
EXPECT_EQ("hello", response_data);
}
// Test the request-challenge-retry sequence for basic auth.
// (basic auth is the easiest to mock, because it has no randomness).
TEST_F(HttpNetworkTransactionSpdy3Test, BasicAuth) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.google.com/");
request.load_flags = 0;
SpdySessionDependencies session_deps;
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(CreateSession(&session_deps)));
MockWrite data_writes1[] = {
MockWrite("GET / HTTP/1.1\r\n"
"Host: www.google.com\r\n"
"Connection: keep-alive\r\n\r\n"),
};
MockRead data_reads1[] = {
MockRead("HTTP/1.0 401 Unauthorized\r\n"),
// Give a couple authenticate options (only the middle one is actually
// supported).
MockRead("WWW-Authenticate: Basic invalid\r\n"), // Malformed.
MockRead("WWW-Authenticate: Basic realm=\"MyRealm1\"\r\n"),
MockRead("WWW-Authenticate: UNSUPPORTED realm=\"FOO\"\r\n"),
MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"),
// Large content-length -- won't matter, as connection will be reset.
MockRead("Content-Length: 10000\r\n\r\n"),
MockRead(SYNCHRONOUS, ERR_FAILED),
};
// After calling trans->RestartWithAuth(), this is the request we should
// be issuing -- the final header line contains the credentials.
MockWrite data_writes2[] = {
MockWrite("GET / HTTP/1.1\r\n"
"Host: www.google.com\r\n"
"Connection: keep-alive\r\n"
"Authorization: Basic Zm9vOmJhcg==\r\n\r\n"),
};
// Lastly, the server responds with the actual content.
MockRead data_reads2[] = {
MockRead("HTTP/1.0 200 OK\r\n"),
MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"),
MockRead("Content-Length: 100\r\n\r\n"),
MockRead(SYNCHRONOUS, OK),
};
StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
data_writes1, arraysize(data_writes1));
StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2),
data_writes2, arraysize(data_writes2));
session_deps.socket_factory->AddSocketDataProvider(&data1);
session_deps.socket_factory->AddSocketDataProvider(&data2);
TestCompletionCallback callback1;
int rv = trans->Start(&request, callback1.callback(), BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback1.WaitForResult();
EXPECT_EQ(OK, rv);
const HttpResponseInfo* response = trans->GetResponseInfo();
ASSERT_TRUE(response != NULL);
EXPECT_TRUE(CheckBasicServerAuth(response->auth_challenge.get()));
TestCompletionCallback callback2;
rv = trans->RestartWithAuth(
AuthCredentials(kFoo, kBar), callback2.callback());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback2.WaitForResult();
EXPECT_EQ(OK, rv);
response = trans->GetResponseInfo();
ASSERT_TRUE(response != NULL);
EXPECT_TRUE(response->auth_challenge.get() == NULL);
EXPECT_EQ(100, response->headers->GetContentLength());
}
TEST_F(HttpNetworkTransactionSpdy3Test, DoNotSendAuth) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.google.com/");
request.load_flags = net::LOAD_DO_NOT_SEND_AUTH_DATA;
SpdySessionDependencies session_deps;
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(CreateSession(&session_deps)));
MockWrite data_writes[] = {
MockWrite("GET / HTTP/1.1\r\n"
"Host: www.google.com\r\n"
"Connection: keep-alive\r\n\r\n"),
};
MockRead data_reads[] = {
MockRead("HTTP/1.0 401 Unauthorized\r\n"),
MockRead("WWW-Authenticate: Basic realm=\"MyRealm1\"\r\n"),
MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"),
// Large content-length -- won't matter, as connection will be reset.
MockRead("Content-Length: 10000\r\n\r\n"),
MockRead(SYNCHRONOUS, ERR_FAILED),
};
StaticSocketDataProvider data(data_reads, arraysize(data_reads),
data_writes, arraysize(data_writes));
session_deps.socket_factory->AddSocketDataProvider(&data);
TestCompletionCallback callback;
int rv = trans->Start(&request, callback.callback(), BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback.WaitForResult();
EXPECT_EQ(0, rv);
const HttpResponseInfo* response = trans->GetResponseInfo();
ASSERT_TRUE(response != NULL);
EXPECT_TRUE(response->auth_challenge.get() == NULL);
}
// Test the request-challenge-retry sequence for basic auth, over a keep-alive
// connection.
TEST_F(HttpNetworkTransactionSpdy3Test, BasicAuthKeepAlive) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.google.com/");
request.load_flags = 0;
SpdySessionDependencies session_deps;
scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps));
MockWrite data_writes1[] = {
MockWrite("GET / HTTP/1.1\r\n"
"Host: www.google.com\r\n"
"Connection: keep-alive\r\n\r\n"),
// After calling trans->RestartWithAuth(), this is the request we should
// be issuing -- the final header line contains the credentials.
MockWrite("GET / HTTP/1.1\r\n"
"Host: www.google.com\r\n"
"Connection: keep-alive\r\n"
"Authorization: Basic Zm9vOmJhcg==\r\n\r\n"),
};
MockRead data_reads1[] = {
MockRead("HTTP/1.1 401 Unauthorized\r\n"),
MockRead("WWW-Authenticate: Basic realm=\"MyRealm1\"\r\n"),
MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"),
MockRead("Content-Length: 14\r\n\r\n"),
MockRead("Unauthorized\r\n"),
// Lastly, the server responds with the actual content.
MockRead("HTTP/1.1 200 OK\r\n"),
MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"),
MockRead("Content-Length: 5\r\n\r\n"),
MockRead("Hello"),
};
// If there is a regression where we disconnect a Keep-Alive
// connection during an auth roundtrip, we'll end up reading this.
MockRead data_reads2[] = {
MockRead(SYNCHRONOUS, ERR_FAILED),
};
StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
data_writes1, arraysize(data_writes1));
StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2),
NULL, 0);
session_deps.socket_factory->AddSocketDataProvider(&data1);
session_deps.socket_factory->AddSocketDataProvider(&data2);
TestCompletionCallback callback1;
scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session));
int rv = trans->Start(&request, callback1.callback(), BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback1.WaitForResult();
EXPECT_EQ(OK, rv);
const HttpResponseInfo* response = trans->GetResponseInfo();
ASSERT_TRUE(response != NULL);
EXPECT_TRUE(CheckBasicServerAuth(response->auth_challenge.get()));
TestCompletionCallback callback2;
rv = trans->RestartWithAuth(
AuthCredentials(kFoo, kBar), callback2.callback());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback2.WaitForResult();
EXPECT_EQ(OK, rv);
response = trans->GetResponseInfo();
ASSERT_TRUE(response != NULL);
EXPECT_TRUE(response->auth_challenge.get() == NULL);
EXPECT_EQ(5, response->headers->GetContentLength());
}
// Test the request-challenge-retry sequence for basic auth, over a keep-alive
// connection and with no response body to drain.
TEST_F(HttpNetworkTransactionSpdy3Test, BasicAuthKeepAliveNoBody) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.google.com/");
request.load_flags = 0;
SpdySessionDependencies session_deps;
scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps));
MockWrite data_writes1[] = {
MockWrite("GET / HTTP/1.1\r\n"
"Host: www.google.com\r\n"
"Connection: keep-alive\r\n\r\n"),
// After calling trans->RestartWithAuth(), this is the request we should
// be issuing -- the final header line contains the credentials.
MockWrite("GET / HTTP/1.1\r\n"
"Host: www.google.com\r\n"
"Connection: keep-alive\r\n"
"Authorization: Basic Zm9vOmJhcg==\r\n\r\n"),
};
MockRead data_reads1[] = {
MockRead("HTTP/1.1 401 Unauthorized\r\n"),
MockRead("WWW-Authenticate: Basic realm=\"MyRealm1\"\r\n"),
MockRead("Content-Length: 0\r\n\r\n"), // No response body.
// Lastly, the server responds with the actual content.
MockRead("HTTP/1.1 200 OK\r\n"),
MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"),
MockRead("Content-Length: 5\r\n\r\n"),
MockRead("hello"),
};
// An incorrect reconnect would cause this to be read.
MockRead data_reads2[] = {
MockRead(SYNCHRONOUS, ERR_FAILED),
};
StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
data_writes1, arraysize(data_writes1));
StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2),
NULL, 0);
session_deps.socket_factory->AddSocketDataProvider(&data1);
session_deps.socket_factory->AddSocketDataProvider(&data2);
TestCompletionCallback callback1;
scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session));
int rv = trans->Start(&request, callback1.callback(), BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback1.WaitForResult();
EXPECT_EQ(OK, rv);
const HttpResponseInfo* response = trans->GetResponseInfo();
ASSERT_TRUE(response != NULL);
EXPECT_TRUE(CheckBasicServerAuth(response->auth_challenge.get()));
TestCompletionCallback callback2;
rv = trans->RestartWithAuth(
AuthCredentials(kFoo, kBar), callback2.callback());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback2.WaitForResult();
EXPECT_EQ(OK, rv);
response = trans->GetResponseInfo();
ASSERT_TRUE(response != NULL);
EXPECT_TRUE(response->auth_challenge.get() == NULL);
EXPECT_EQ(5, response->headers->GetContentLength());
}
// Test the request-challenge-retry sequence for basic auth, over a keep-alive
// connection and with a large response body to drain.
TEST_F(HttpNetworkTransactionSpdy3Test, BasicAuthKeepAliveLargeBody) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.google.com/");
request.load_flags = 0;
SpdySessionDependencies session_deps;
scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps));
MockWrite data_writes1[] = {
MockWrite("GET / HTTP/1.1\r\n"
"Host: www.google.com\r\n"
"Connection: keep-alive\r\n\r\n"),
// After calling trans->RestartWithAuth(), this is the request we should
// be issuing -- the final header line contains the credentials.
MockWrite("GET / HTTP/1.1\r\n"
"Host: www.google.com\r\n"
"Connection: keep-alive\r\n"
"Authorization: Basic Zm9vOmJhcg==\r\n\r\n"),
};
// Respond with 5 kb of response body.
std::string large_body_string("Unauthorized");
large_body_string.append(5 * 1024, ' ');
large_body_string.append("\r\n");
MockRead data_reads1[] = {
MockRead("HTTP/1.1 401 Unauthorized\r\n"),
MockRead("WWW-Authenticate: Basic realm=\"MyRealm1\"\r\n"),
MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"),
// 5134 = 12 + 5 * 1024 + 2
MockRead("Content-Length: 5134\r\n\r\n"),
MockRead(ASYNC, large_body_string.data(), large_body_string.size()),
// Lastly, the server responds with the actual content.
MockRead("HTTP/1.1 200 OK\r\n"),
MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"),
MockRead("Content-Length: 5\r\n\r\n"),
MockRead("hello"),
};
// An incorrect reconnect would cause this to be read.
MockRead data_reads2[] = {
MockRead(SYNCHRONOUS, ERR_FAILED),
};
StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
data_writes1, arraysize(data_writes1));
StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2),
NULL, 0);
session_deps.socket_factory->AddSocketDataProvider(&data1);
session_deps.socket_factory->AddSocketDataProvider(&data2);
TestCompletionCallback callback1;
scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session));
int rv = trans->Start(&request, callback1.callback(), BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback1.WaitForResult();
EXPECT_EQ(OK, rv);
const HttpResponseInfo* response = trans->GetResponseInfo();
ASSERT_TRUE(response != NULL);
EXPECT_TRUE(CheckBasicServerAuth(response->auth_challenge.get()));
TestCompletionCallback callback2;
rv = trans->RestartWithAuth(
AuthCredentials(kFoo, kBar), callback2.callback());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback2.WaitForResult();
EXPECT_EQ(OK, rv);
response = trans->GetResponseInfo();
ASSERT_TRUE(response != NULL);
EXPECT_TRUE(response->auth_challenge.get() == NULL);
EXPECT_EQ(5, response->headers->GetContentLength());
}
// Test the request-challenge-retry sequence for basic auth, over a keep-alive
// connection, but the server gets impatient and closes the connection.
TEST_F(HttpNetworkTransactionSpdy3Test, BasicAuthKeepAliveImpatientServer) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.google.com/");
request.load_flags = 0;
SpdySessionDependencies session_deps;
scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps));
MockWrite data_writes1[] = {
MockWrite("GET / HTTP/1.1\r\n"
"Host: www.google.com\r\n"
"Connection: keep-alive\r\n\r\n"),
// This simulates the seemingly successful write to a closed connection
// if the bug is not fixed.
MockWrite("GET / HTTP/1.1\r\n"
"Host: www.google.com\r\n"
"Connection: keep-alive\r\n"
"Authorization: Basic Zm9vOmJhcg==\r\n\r\n"),
};
MockRead data_reads1[] = {
MockRead("HTTP/1.1 401 Unauthorized\r\n"),
MockRead("WWW-Authenticate: Basic realm=\"MyRealm1\"\r\n"),
MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"),
MockRead("Content-Length: 14\r\n\r\n"),
// Tell MockTCPClientSocket to simulate the server closing the connection.
MockRead(SYNCHRONOUS, ERR_TEST_PEER_CLOSE_AFTER_NEXT_MOCK_READ),
MockRead("Unauthorized\r\n"),
MockRead(SYNCHRONOUS, OK), // The server closes the connection.
};
// After calling trans->RestartWithAuth(), this is the request we should
// be issuing -- the final header line contains the credentials.
MockWrite data_writes2[] = {
MockWrite("GET / HTTP/1.1\r\n"
"Host: www.google.com\r\n"
"Connection: keep-alive\r\n"
"Authorization: Basic Zm9vOmJhcg==\r\n\r\n"),
};
// Lastly, the server responds with the actual content.
MockRead data_reads2[] = {
MockRead("HTTP/1.1 200 OK\r\n"),
MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"),
MockRead("Content-Length: 5\r\n\r\n"),
MockRead("hello"),
};
StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
data_writes1, arraysize(data_writes1));
StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2),
data_writes2, arraysize(data_writes2));
session_deps.socket_factory->AddSocketDataProvider(&data1);
session_deps.socket_factory->AddSocketDataProvider(&data2);
TestCompletionCallback callback1;
scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session));
int rv = trans->Start(&request, callback1.callback(), BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback1.WaitForResult();
EXPECT_EQ(OK, rv);
const HttpResponseInfo* response = trans->GetResponseInfo();
ASSERT_TRUE(response != NULL);
EXPECT_TRUE(CheckBasicServerAuth(response->auth_challenge.get()));
TestCompletionCallback callback2;
rv = trans->RestartWithAuth(
AuthCredentials(kFoo, kBar), callback2.callback());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback2.WaitForResult();
EXPECT_EQ(OK, rv);
response = trans->GetResponseInfo();
ASSERT_TRUE(response != NULL);
EXPECT_TRUE(response->auth_challenge.get() == NULL);
EXPECT_EQ(5, response->headers->GetContentLength());
}
// Test the request-challenge-retry sequence for basic auth, over a connection
// that requires a restart when setting up an SSL tunnel.
TEST_F(HttpNetworkTransactionSpdy3Test, BasicAuthProxyNoKeepAlive) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("https://www.google.com/");
// when the no authentication data flag is set.
request.load_flags = net::LOAD_DO_NOT_SEND_AUTH_DATA;
// Configure against proxy server "myproxy:70".
SpdySessionDependencies session_deps(ProxyService::CreateFixed("myproxy:70"));
CapturingBoundNetLog log;
session_deps.net_log = log.bound().net_log();
scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps));
// Since we have proxy, should try to establish tunnel.
MockWrite data_writes1[] = {
MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n"
"Host: www.google.com\r\n"
"Proxy-Connection: keep-alive\r\n\r\n"),
// After calling trans->RestartWithAuth(), this is the request we should
// be issuing -- the final header line contains the credentials.
MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n"
"Host: www.google.com\r\n"
"Proxy-Connection: keep-alive\r\n"
"Proxy-Authorization: Basic Zm9vOmJhcg==\r\n\r\n"),
MockWrite("GET / HTTP/1.1\r\n"
"Host: www.google.com\r\n"
"Connection: keep-alive\r\n\r\n"),
};
// The proxy responds to the connect with a 407, using a persistent
// connection.
MockRead data_reads1[] = {
// No credentials.
MockRead("HTTP/1.1 407 Proxy Authentication Required\r\n"),
MockRead("Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n"),
MockRead("Proxy-Connection: close\r\n\r\n"),
MockRead("HTTP/1.1 200 Connection Established\r\n\r\n"),
MockRead("HTTP/1.1 200 OK\r\n"),
MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"),
MockRead("Content-Length: 5\r\n\r\n"),
MockRead(SYNCHRONOUS, "hello"),
};
StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
data_writes1, arraysize(data_writes1));
session_deps.socket_factory->AddSocketDataProvider(&data1);
SSLSocketDataProvider ssl(ASYNC, OK);
session_deps.socket_factory->AddSSLSocketDataProvider(&ssl);
TestCompletionCallback callback1;
scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session));
int rv = trans->Start(&request, callback1.callback(), log.bound());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback1.WaitForResult();
EXPECT_EQ(OK, rv);
CapturingNetLog::CapturedEntryList entries;
log.GetEntries(&entries);
size_t pos = ExpectLogContainsSomewhere(
entries, 0, NetLog::TYPE_HTTP_TRANSACTION_SEND_TUNNEL_HEADERS,
NetLog::PHASE_NONE);
ExpectLogContainsSomewhere(
entries, pos,
NetLog::TYPE_HTTP_TRANSACTION_READ_TUNNEL_RESPONSE_HEADERS,
NetLog::PHASE_NONE);
const HttpResponseInfo* response = trans->GetResponseInfo();
ASSERT_TRUE(response != NULL);
ASSERT_FALSE(response->headers == NULL);
EXPECT_EQ(407, response->headers->response_code());
EXPECT_TRUE(HttpVersion(1, 1) == response->headers->GetHttpVersion());
EXPECT_TRUE(CheckBasicProxyAuth(response->auth_challenge.get()));
TestCompletionCallback callback2;
rv = trans->RestartWithAuth(
AuthCredentials(kFoo, kBar), callback2.callback());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback2.WaitForResult();
EXPECT_EQ(OK, rv);
response = trans->GetResponseInfo();
ASSERT_TRUE(response != NULL);
EXPECT_TRUE(response->headers->IsKeepAlive());
EXPECT_EQ(200, response->headers->response_code());
EXPECT_EQ(5, response->headers->GetContentLength());
EXPECT_TRUE(HttpVersion(1, 1) == response->headers->GetHttpVersion());
// The password prompt info should not be set.
EXPECT_TRUE(response->auth_challenge.get() == NULL);
trans.reset();
session->CloseAllConnections();
}
// Test the request-challenge-retry sequence for basic auth, over a keep-alive
// proxy connection, when setting up an SSL tunnel.
TEST_F(HttpNetworkTransactionSpdy3Test, BasicAuthProxyKeepAlive) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("https://www.google.com/");
// Ensure that proxy authentication is attempted even
// when the no authentication data flag is set.
request.load_flags = net::LOAD_DO_NOT_SEND_AUTH_DATA;
// Configure against proxy server "myproxy:70".
SpdySessionDependencies session_deps(ProxyService::CreateFixed("myproxy:70"));
CapturingBoundNetLog log;
session_deps.net_log = log.bound().net_log();
scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps));
scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session));
// Since we have proxy, should try to establish tunnel.
MockWrite data_writes1[] = {
MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n"
"Host: www.google.com\r\n"
"Proxy-Connection: keep-alive\r\n\r\n"),
// After calling trans->RestartWithAuth(), this is the request we should
// be issuing -- the final header line contains the credentials.
MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n"
"Host: www.google.com\r\n"
"Proxy-Connection: keep-alive\r\n"
"Proxy-Authorization: Basic Zm9vOmJheg==\r\n\r\n"),
};
// The proxy responds to the connect with a 407, using a persistent
// connection.
MockRead data_reads1[] = {
// No credentials.
MockRead("HTTP/1.1 407 Proxy Authentication Required\r\n"),
MockRead("Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n"),
MockRead("Content-Length: 10\r\n\r\n"),
MockRead("0123456789"),
// Wrong credentials (wrong password).
MockRead("HTTP/1.1 407 Proxy Authentication Required\r\n"),
MockRead("Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n"),
MockRead("Content-Length: 10\r\n\r\n"),
// No response body because the test stops reading here.
MockRead(SYNCHRONOUS, ERR_UNEXPECTED), // Should not be reached.
};
StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
data_writes1, arraysize(data_writes1));
session_deps.socket_factory->AddSocketDataProvider(&data1);
TestCompletionCallback callback1;
int rv = trans->Start(&request, callback1.callback(), log.bound());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback1.WaitForResult();
EXPECT_EQ(OK, rv);
CapturingNetLog::CapturedEntryList entries;
log.GetEntries(&entries);
size_t pos = ExpectLogContainsSomewhere(
entries, 0, NetLog::TYPE_HTTP_TRANSACTION_SEND_TUNNEL_HEADERS,
NetLog::PHASE_NONE);
ExpectLogContainsSomewhere(
entries, pos,
NetLog::TYPE_HTTP_TRANSACTION_READ_TUNNEL_RESPONSE_HEADERS,
NetLog::PHASE_NONE);
const HttpResponseInfo* response = trans->GetResponseInfo();
ASSERT_TRUE(response != NULL);
ASSERT_FALSE(response->headers == NULL);
EXPECT_TRUE(response->headers->IsKeepAlive());
EXPECT_EQ(407, response->headers->response_code());
EXPECT_EQ(10, response->headers->GetContentLength());
EXPECT_TRUE(HttpVersion(1, 1) == response->headers->GetHttpVersion());
EXPECT_TRUE(CheckBasicProxyAuth(response->auth_challenge.get()));
TestCompletionCallback callback2;
// Wrong password (should be "bar").
rv = trans->RestartWithAuth(
AuthCredentials(kFoo, kBaz), callback2.callback());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback2.WaitForResult();
EXPECT_EQ(OK, rv);
response = trans->GetResponseInfo();
ASSERT_TRUE(response != NULL);
ASSERT_FALSE(response->headers == NULL);
EXPECT_TRUE(response->headers->IsKeepAlive());
EXPECT_EQ(407, response->headers->response_code());
EXPECT_EQ(10, response->headers->GetContentLength());
EXPECT_TRUE(HttpVersion(1, 1) == response->headers->GetHttpVersion());
EXPECT_TRUE(CheckBasicProxyAuth(response->auth_challenge.get()));
// Flush the idle socket before the NetLog and HttpNetworkTransaction go
// out of scope.
session->CloseAllConnections();
}
// Test that we don't read the response body when we fail to establish a tunnel,
// even if the user cancels the proxy's auth attempt.
TEST_F(HttpNetworkTransactionSpdy3Test, BasicAuthProxyCancelTunnel) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("https://www.google.com/");
request.load_flags = 0;
// Configure against proxy server "myproxy:70".
SpdySessionDependencies session_deps(ProxyService::CreateFixed("myproxy:70"));
scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps));
scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session));
// Since we have proxy, should try to establish tunnel.
MockWrite data_writes[] = {
MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n"
"Host: www.google.com\r\n"
"Proxy-Connection: keep-alive\r\n\r\n"),
};
// The proxy responds to the connect with a 407.
MockRead data_reads[] = {
MockRead("HTTP/1.1 407 Proxy Authentication Required\r\n"),
MockRead("Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n"),
MockRead("Content-Length: 10\r\n\r\n"),
MockRead(SYNCHRONOUS, ERR_UNEXPECTED), // Should not be reached.
};
StaticSocketDataProvider data(data_reads, arraysize(data_reads),
data_writes, arraysize(data_writes));
session_deps.socket_factory->AddSocketDataProvider(&data);
TestCompletionCallback callback;
int rv = trans->Start(&request, callback.callback(), BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback.WaitForResult();
EXPECT_EQ(OK, rv);
const HttpResponseInfo* response = trans->GetResponseInfo();
ASSERT_TRUE(response != NULL);
EXPECT_TRUE(response->headers->IsKeepAlive());
EXPECT_EQ(407, response->headers->response_code());
EXPECT_EQ(10, response->headers->GetContentLength());
EXPECT_TRUE(HttpVersion(1, 1) == response->headers->GetHttpVersion());
std::string response_data;
rv = ReadTransaction(trans.get(), &response_data);
EXPECT_EQ(ERR_TUNNEL_CONNECTION_FAILED, rv);
// Flush the idle socket before the HttpNetworkTransaction goes out of scope.
session->CloseAllConnections();
}
// Test when a server (non-proxy) returns a 407 (proxy-authenticate).
// The request should fail with ERR_UNEXPECTED_PROXY_AUTH.
TEST_F(HttpNetworkTransactionSpdy3Test, UnexpectedProxyAuth) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.google.com/");
request.load_flags = 0;
// We are using a DIRECT connection (i.e. no proxy) for this session.
SpdySessionDependencies session_deps;
scoped_ptr<HttpTransaction> trans(
new HttpNetworkTransaction(CreateSession(&session_deps)));
MockWrite data_writes1[] = {
MockWrite("GET / HTTP/1.1\r\n"
"Host: www.google.com\r\n"
"Connection: keep-alive\r\n\r\n"),
};
MockRead data_reads1[] = {
MockRead("HTTP/1.0 407 Proxy Auth required\r\n"),
MockRead("Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n"),
// Large content-length -- won't matter, as connection will be reset.
MockRead("Content-Length: 10000\r\n\r\n"),
MockRead(SYNCHRONOUS, ERR_FAILED),
};
StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
data_writes1, arraysize(data_writes1));
session_deps.socket_factory->AddSocketDataProvider(&data1);
TestCompletionCallback callback;
int rv = trans->Start(&request, callback.callback(), BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback.WaitForResult();
EXPECT_EQ(ERR_UNEXPECTED_PROXY_AUTH, rv);
}
// Tests when an HTTPS server (non-proxy) returns a 407 (proxy-authentication)
// through a non-authenticating proxy. The request should fail with
// ERR_UNEXPECTED_PROXY_AUTH.
// Note that it is impossible to detect if an HTTP server returns a 407 through
// a non-authenticating proxy - there is nothing to indicate whether the
// response came from the proxy or the server, so it is treated as if the proxy
// issued the challenge.
TEST_F(HttpNetworkTransactionSpdy3Test,
HttpsServerRequestsProxyAuthThroughProxy) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("https://www.google.com/");
SpdySessionDependencies session_deps(ProxyService::CreateFixed("myproxy:70"));
CapturingBoundNetLog log;
session_deps.net_log = log.bound().net_log();
scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps));
// Since we have proxy, should try to establish tunnel.
MockWrite data_writes1[] = {
MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n"
"Host: www.google.com\r\n"
"Proxy-Connection: keep-alive\r\n\r\n"),
MockWrite("GET / HTTP/1.1\r\n"
"Host: www.google.com\r\n"
"Connection: keep-alive\r\n\r\n"),
};
MockRead data_reads1[] = {
MockRead("HTTP/1.1 200 Connection Established\r\n\r\n"),
MockRead("HTTP/1.1 407 Unauthorized\r\n"),
MockRead("Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n"),
MockRead("\r\n"),
MockRead(SYNCHRONOUS, OK),
};
StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
data_writes1, arraysize(data_writes1));
session_deps.socket_factory->AddSocketDataProvider(&data1);
SSLSocketDataProvider ssl(ASYNC, OK);
session_deps.socket_factory->AddSSLSocketDataProvider(&ssl);
TestCompletionCallback callback1;
scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session));
int rv = trans->Start(&request, callback1.callback(), log.bound());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback1.WaitForResult();
EXPECT_EQ(ERR_UNEXPECTED_PROXY_AUTH, rv);
CapturingNetLog::CapturedEntryList entries;
log.GetEntries(&entries);
size_t pos = ExpectLogContainsSomewhere(
entries, 0, NetLog::TYPE_HTTP_TRANSACTION_SEND_TUNNEL_HEADERS,
NetLog::PHASE_NONE);
ExpectLogContainsSomewhere(
entries, pos,
NetLog::TYPE_HTTP_TRANSACTION_READ_TUNNEL_RESPONSE_HEADERS,
NetLog::PHASE_NONE);
}
// Test a simple get through an HTTPS Proxy.
TEST_F(HttpNetworkTransactionSpdy3Test, HttpsProxyGet) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.google.com/");
// Configure against https proxy server "proxy:70".
SpdySessionDependencies session_deps(ProxyService::CreateFixed(
"https://proxy:70"));
CapturingBoundNetLog log;
session_deps.net_log = log.bound().net_log();
scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps));
// Since we have proxy, should use full url
MockWrite data_writes1[] = {
MockWrite("GET http://www.google.com/ HTTP/1.1\r\n"
"Host: www.google.com\r\n"
"Proxy-Connection: keep-alive\r\n\r\n"),
};
MockRead data_reads1[] = {
MockRead("HTTP/1.1 200 OK\r\n"),
MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"),
MockRead("Content-Length: 100\r\n\r\n"),
MockRead(SYNCHRONOUS, OK),
};
StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
data_writes1, arraysize(data_writes1));
session_deps.socket_factory->AddSocketDataProvider(&data1);
SSLSocketDataProvider ssl(ASYNC, OK);
session_deps.socket_factory->AddSSLSocketDataProvider(&ssl);
TestCompletionCallback callback1;
scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session));
int rv = trans->Start(&request, callback1.callback(), log.bound());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback1.WaitForResult();
EXPECT_EQ(OK, rv);
const HttpResponseInfo* response = trans->GetResponseInfo();
ASSERT_TRUE(response != NULL);
EXPECT_TRUE(response->headers->IsKeepAlive());
EXPECT_EQ(200, response->headers->response_code());
EXPECT_EQ(100, response->headers->GetContentLength());
EXPECT_TRUE(HttpVersion(1, 1) == response->headers->GetHttpVersion());
// The password prompt info should not be set.
EXPECT_TRUE(response->auth_challenge.get() == NULL);
}
// Test a SPDY get through an HTTPS Proxy.
TEST_F(HttpNetworkTransactionSpdy3Test, HttpsProxySpdyGet) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.google.com/");
request.load_flags = 0;
// Configure against https proxy server "proxy:70".
SpdySessionDependencies session_deps(ProxyService::CreateFixed(
"https://proxy:70"));
CapturingBoundNetLog log;
session_deps.net_log = log.bound().net_log();
scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps));
// fetch http://www.google.com/ via SPDY
scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST,
false));
MockWrite spdy_writes[] = { CreateMockWrite(*req) };
scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
scoped_ptr<SpdyFrame> data(ConstructSpdyBodyFrame(1, true));
MockRead spdy_reads[] = {
CreateMockRead(*resp),
CreateMockRead(*data),
MockRead(ASYNC, 0, 0),
};
DelayedSocketData spdy_data(
1, // wait for one write to finish before reading.
spdy_reads, arraysize(spdy_reads),
spdy_writes, arraysize(spdy_writes));
session_deps.socket_factory->AddSocketDataProvider(&spdy_data);
SSLSocketDataProvider ssl(ASYNC, OK);
ssl.SetNextProto(kProtoSPDY3);
session_deps.socket_factory->AddSSLSocketDataProvider(&ssl);
TestCompletionCallback callback1;
scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session));
int rv = trans->Start(&request, callback1.callback(), log.bound());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback1.WaitForResult();
EXPECT_EQ(OK, rv);
const HttpResponseInfo* response = trans->GetResponseInfo();
ASSERT_TRUE(response != NULL);
ASSERT_TRUE(response->headers != NULL);
EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
std::string response_data;
ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data));
EXPECT_EQ(kUploadData, response_data);
}
// Test a SPDY get through an HTTPS Proxy.
TEST_F(HttpNetworkTransactionSpdy3Test, HttpsProxySpdyGetWithProxyAuth) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.google.com/");
request.load_flags = 0;
// Configure against https proxy server "myproxy:70".
SpdySessionDependencies session_deps(
ProxyService::CreateFixed("https://myproxy:70"));
CapturingBoundNetLog log;
session_deps.net_log = log.bound().net_log();
scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps));
// The first request will be a bare GET, the second request will be a
// GET with a Proxy-Authorization header.
scoped_ptr<SpdyFrame> req_get(
ConstructSpdyGet(NULL, 0, false, 1, LOWEST, false));
const char* const kExtraAuthorizationHeaders[] = {
"proxy-authorization",
"Basic Zm9vOmJhcg==",
};
scoped_ptr<SpdyFrame> req_get_authorization(
ConstructSpdyGet(
kExtraAuthorizationHeaders, arraysize(kExtraAuthorizationHeaders)/2,
false, 3, LOWEST, false));
MockWrite spdy_writes[] = {
CreateMockWrite(*req_get, 1),
CreateMockWrite(*req_get_authorization, 4),
};
// The first response is a 407 proxy authentication challenge, and the second
// response will be a 200 response since the second request includes a valid
// Authorization header.
const char* const kExtraAuthenticationHeaders[] = {
"proxy-authenticate",
"Basic realm=\"MyRealm1\""
};
scoped_ptr<SpdyFrame> resp_authentication(
ConstructSpdySynReplyError(
"407 Proxy Authentication Required",
kExtraAuthenticationHeaders, arraysize(kExtraAuthenticationHeaders)/2,
1));
scoped_ptr<SpdyFrame> body_authentication(
ConstructSpdyBodyFrame(1, true));
scoped_ptr<SpdyFrame> resp_data(ConstructSpdyGetSynReply(NULL, 0, 3));
scoped_ptr<SpdyFrame> body_data(ConstructSpdyBodyFrame(3, true));
MockRead spdy_reads[] = {
CreateMockRead(*resp_authentication, 2),
CreateMockRead(*body_authentication, 3),
CreateMockRead(*resp_data, 5),
CreateMockRead(*body_data, 6),
MockRead(ASYNC, 0, 7),
};
OrderedSocketData data(
spdy_reads, arraysize(spdy_reads),
spdy_writes, arraysize(spdy_writes));
session_deps.socket_factory->AddSocketDataProvider(&data);
SSLSocketDataProvider ssl(ASYNC, OK);
ssl.SetNextProto(kProtoSPDY3);
session_deps.socket_factory->AddSSLSocketDataProvider(&ssl);
TestCompletionCallback callback1;
scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session));
int rv = trans->Start(&request, callback1.callback(), log.bound());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback1.WaitForResult();
EXPECT_EQ(OK, rv);
const HttpResponseInfo* const response = trans->GetResponseInfo();
ASSERT_TRUE(response != NULL);
ASSERT_TRUE(response->headers != NULL);
EXPECT_EQ(407, response->headers->response_code());
EXPECT_TRUE(response->was_fetched_via_spdy);
EXPECT_TRUE(CheckBasicProxyAuth(response->auth_challenge.get()));
TestCompletionCallback callback2;
rv = trans->RestartWithAuth(
AuthCredentials(kFoo, kBar), callback2.callback());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback2.WaitForResult();
EXPECT_EQ(OK, rv);
const HttpResponseInfo* const response_restart = trans->GetResponseInfo();
ASSERT_TRUE(response_restart != NULL);
ASSERT_TRUE(response_restart->headers != NULL);
EXPECT_EQ(200, response_restart->headers->response_code());
// The password prompt info should not be set.
EXPECT_TRUE(response_restart->auth_challenge.get() == NULL);
}
// Test a SPDY CONNECT through an HTTPS Proxy to an HTTPS (non-SPDY) Server.
TEST_F(HttpNetworkTransactionSpdy3Test, HttpsProxySpdyConnectHttps) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("https://www.google.com/");
request.load_flags = 0;
// Configure against https proxy server "proxy:70".
SpdySessionDependencies session_deps(ProxyService::CreateFixed(
"https://proxy:70"));
CapturingBoundNetLog log;
session_deps.net_log = log.bound().net_log();
scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps));
scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session));
// CONNECT to www.google.com:443 via SPDY
scoped_ptr<SpdyFrame> connect(ConstructSpdyConnect(NULL, 0, 1));
// fetch https://www.google.com/ via HTTP
const char get[] = "GET / HTTP/1.1\r\n"
"Host: www.google.com\r\n"
"Connection: keep-alive\r\n\r\n";
scoped_ptr<SpdyFrame> wrapped_get(
ConstructSpdyBodyFrame(1, get, strlen(get), false));
scoped_ptr<SpdyFrame> conn_resp(ConstructSpdyGetSynReply(NULL, 0, 1));
const char resp[] = "HTTP/1.1 200 OK\r\n"
"Content-Length: 10\r\n\r\n";
scoped_ptr<SpdyFrame> wrapped_get_resp(
ConstructSpdyBodyFrame(1, resp, strlen(resp), false));
scoped_ptr<SpdyFrame> wrapped_body(
ConstructSpdyBodyFrame(1, "1234567890", 10, false));
scoped_ptr<SpdyFrame> window_update(
ConstructSpdyWindowUpdate(1, wrapped_get_resp->length()));
MockWrite spdy_writes[] = {
CreateMockWrite(*connect, 1),
CreateMockWrite(*wrapped_get, 3),
CreateMockWrite(*window_update, 5)
};
MockRead spdy_reads[] = {
CreateMockRead(*conn_resp, 2, ASYNC),
CreateMockRead(*wrapped_get_resp, 4, ASYNC),
CreateMockRead(*wrapped_body, 6, ASYNC),
CreateMockRead(*wrapped_body, 7, ASYNC),
MockRead(ASYNC, 0, 8),
};
OrderedSocketData spdy_data(
spdy_reads, arraysize(spdy_reads),
spdy_writes, arraysize(spdy_writes));
session_deps.socket_factory->AddSocketDataProvider(&spdy_data);
SSLSocketDataProvider ssl(ASYNC, OK);
ssl.SetNextProto(kProtoSPDY3);
session_deps.socket_factory->AddSSLSocketDataProvider(&ssl);
SSLSocketDataProvider ssl2(ASYNC, OK);
ssl2.was_npn_negotiated = false;
ssl2.protocol_negotiated = kProtoUnknown;
session_deps.socket_factory->AddSSLSocketDataProvider(&ssl2);
TestCompletionCallback callback1;
int rv = trans->Start(&request, callback1.callback(), log.bound());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback1.WaitForResult();
EXPECT_EQ(OK, rv);
const HttpResponseInfo* response = trans->GetResponseInfo();
ASSERT_TRUE(response != NULL);
ASSERT_TRUE(response->headers != NULL);
EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
std::string response_data;
ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data));
EXPECT_EQ("1234567890", response_data);
}
// Test a SPDY CONNECT through an HTTPS Proxy to a SPDY server.
TEST_F(HttpNetworkTransactionSpdy3Test, HttpsProxySpdyConnectSpdy) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("https://www.google.com/");
request.load_flags = 0;
// Configure against https proxy server "proxy:70".
SpdySessionDependencies session_deps(ProxyService::CreateFixed(
"https://proxy:70"));
CapturingBoundNetLog log;
session_deps.net_log = log.bound().net_log();
scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps));
scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session));
// CONNECT to www.google.com:443 via SPDY
scoped_ptr<SpdyFrame> connect(ConstructSpdyConnect(NULL, 0, 1));
// fetch https://www.google.com/ via SPDY
const char* const kMyUrl = "https://www.google.com/";
scoped_ptr<SpdyFrame> get(ConstructSpdyGet(kMyUrl, false, 1, LOWEST));
scoped_ptr<SpdyFrame> wrapped_get(ConstructWrappedSpdyFrame(get, 1));
scoped_ptr<SpdyFrame> conn_resp(ConstructSpdyGetSynReply(NULL, 0, 1));
scoped_ptr<SpdyFrame> get_resp(ConstructSpdyGetSynReply(NULL, 0, 1));
scoped_ptr<SpdyFrame> wrapped_get_resp(
ConstructWrappedSpdyFrame(get_resp, 1));
scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
scoped_ptr<SpdyFrame> wrapped_body(ConstructWrappedSpdyFrame(body, 1));
scoped_ptr<SpdyFrame> window_update_get_resp(
ConstructSpdyWindowUpdate(1, wrapped_get_resp->length()));
scoped_ptr<SpdyFrame> window_update_body(
ConstructSpdyWindowUpdate(1, wrapped_body->length()));
MockWrite spdy_writes[] = {
CreateMockWrite(*connect, 1),
CreateMockWrite(*wrapped_get, 3),
CreateMockWrite(*window_update_get_resp, 5),
CreateMockWrite(*window_update_body, 7),
};
MockRead spdy_reads[] = {
CreateMockRead(*conn_resp, 2, ASYNC),
CreateMockRead(*wrapped_get_resp, 4, ASYNC),
CreateMockRead(*wrapped_body, 6, ASYNC),
MockRead(ASYNC, 0, 8),
};
OrderedSocketData spdy_data(
spdy_reads, arraysize(spdy_reads),
spdy_writes, arraysize(spdy_writes));
session_deps.socket_factory->AddSocketDataProvider(&spdy_data);
SSLSocketDataProvider ssl(ASYNC, OK);
ssl.SetNextProto(kProtoSPDY3);
session_deps.socket_factory->AddSSLSocketDataProvider(&ssl);
SSLSocketDataProvider ssl2(ASYNC, OK);
ssl2.SetNextProto(kProtoSPDY3);
ssl2.protocol_negotiated = kProtoSPDY3;
session_deps.socket_factory->AddSSLSocketDataProvider(&ssl2);
TestCompletionCallback callback1;
int rv = trans->Start(&request, callback1.callback(), log.bound());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback1.WaitForResult();
EXPECT_EQ(OK, rv);
const HttpResponseInfo* response = trans->GetResponseInfo();
ASSERT_TRUE(response != NULL);
ASSERT_TRUE(response->headers != NULL);
EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
std::string response_data;
ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data));
EXPECT_EQ(kUploadData, response_data);
}
// Test a SPDY CONNECT failure through an HTTPS Proxy.
TEST_F(HttpNetworkTransactionSpdy3Test, HttpsProxySpdyConnectFailure) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("https://www.google.com/");
request.load_flags = 0;
// Configure against https proxy server "proxy:70".
SpdySessionDependencies session_deps(ProxyService::CreateFixed(
"https://proxy:70"));
CapturingBoundNetLog log;
session_deps.net_log = log.bound().net_log();
scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps));
scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session));
// CONNECT to www.google.com:443 via SPDY
scoped_ptr<SpdyFrame> connect(ConstructSpdyConnect(NULL, 0, 1));
scoped_ptr<SpdyFrame> get(ConstructSpdyRstStream(1, CANCEL));
MockWrite spdy_writes[] = {
CreateMockWrite(*connect, 1),
CreateMockWrite(*get, 3),
};
scoped_ptr<SpdyFrame> resp(ConstructSpdySynReplyError(1));
scoped_ptr<SpdyFrame> data(ConstructSpdyBodyFrame(1, true));
MockRead spdy_reads[] = {
CreateMockRead(*resp, 2, ASYNC),
MockRead(ASYNC, 0, 4),
};
OrderedSocketData spdy_data(
spdy_reads, arraysize(spdy_reads),
spdy_writes, arraysize(spdy_writes));
session_deps.socket_factory->AddSocketDataProvider(&spdy_data);
SSLSocketDataProvider ssl(ASYNC, OK);
ssl.SetNextProto(kProtoSPDY3);
session_deps.socket_factory->AddSSLSocketDataProvider(&ssl);
SSLSocketDataProvider ssl2(ASYNC, OK);
ssl2.SetNextProto(kProtoSPDY3);
session_deps.socket_factory->AddSSLSocketDataProvider(&ssl2);
TestCompletionCallback callback1;
int rv = trans->Start(&request, callback1.callback(), log.bound());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback1.WaitForResult();
EXPECT_EQ(ERR_TUNNEL_CONNECTION_FAILED, rv);
// TODO(ttuttle): Anything else to check here?
}
// Test the challenge-response-retry sequence through an HTTPS Proxy
TEST_F(HttpNetworkTransactionSpdy3Test, HttpsProxyAuthRetry) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.google.com/");
// when the no authentication data flag is set.
request.load_flags = net::LOAD_DO_NOT_SEND_AUTH_DATA;
// Configure against https proxy server "myproxy:70".
SpdySessionDependencies session_deps(
ProxyService::CreateFixed("https://myproxy:70"));
CapturingBoundNetLog log;
session_deps.net_log = log.bound().net_log();
scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps));
// Since we have proxy, should use full url
MockWrite data_writes1[] = {
MockWrite("GET http://www.google.com/ HTTP/1.1\r\n"
"Host: www.google.com\r\n"
"Proxy-Connection: keep-alive\r\n\r\n"),
// After calling trans->RestartWithAuth(), this is the request we should
// be issuing -- the final header line contains the credentials.
MockWrite("GET http://www.google.com/ HTTP/1.1\r\n"
"Host: www.google.com\r\n"
"Proxy-Connection: keep-alive\r\n"
"Proxy-Authorization: Basic Zm9vOmJhcg==\r\n\r\n"),
};
// The proxy responds to the GET with a 407, using a persistent
// connection.
MockRead data_reads1[] = {
// No credentials.
MockRead("HTTP/1.1 407 Proxy Authentication Required\r\n"),
MockRead("Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n"),
MockRead("Proxy-Connection: keep-alive\r\n"),
MockRead("Content-Length: 0\r\n\r\n"),
MockRead("HTTP/1.1 200 OK\r\n"),
MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"),
MockRead("Content-Length: 100\r\n\r\n"),
MockRead(SYNCHRONOUS, OK),
};
StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
data_writes1, arraysize(data_writes1));
session_deps.socket_factory->AddSocketDataProvider(&data1);
SSLSocketDataProvider ssl(ASYNC, OK);
session_deps.socket_factory->AddSSLSocketDataProvider(&ssl);
TestCompletionCallback callback1;
scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session));
int rv = trans->Start(&request, callback1.callback(), log.bound());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback1.WaitForResult();
EXPECT_EQ(OK, rv);
const HttpResponseInfo* response = trans->GetResponseInfo();
ASSERT_TRUE(response != NULL);
ASSERT_FALSE(response->headers == NULL);
EXPECT_EQ(407, response->headers->response_code());
EXPECT_TRUE(HttpVersion(1, 1) == response->headers->GetHttpVersion());
EXPECT_TRUE(CheckBasicProxyAuth(response->auth_challenge.get()));
TestCompletionCallback callback2;
rv = trans->RestartWithAuth(
AuthCredentials(kFoo, kBar), callback2.callback());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback2.WaitForResult();
EXPECT_EQ(OK, rv);
response = trans->GetResponseInfo();
ASSERT_TRUE(response != NULL);
EXPECT_TRUE(response->headers->IsKeepAlive());
EXPECT_EQ(200, response->headers->response_code());
EXPECT_EQ(100, response->headers->GetContentLength());
EXPECT_TRUE(HttpVersion(1, 1) == response->headers->GetHttpVersion());
// The password prompt info should not be set.
EXPECT_TRUE(response->auth_challenge.get() == NULL);
}
void HttpNetworkTransactionSpdy3Test::ConnectStatusHelperWithExpectedStatus(
const MockRead& status, int expected_status) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("https://www.google.com/");
request.load_flags = 0;
// Configure against proxy server "myproxy:70".
SpdySessionDependencies session_deps(ProxyService::CreateFixed("myproxy:70"));
scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps));
// Since we have proxy, should try to establish tunnel.
MockWrite data_writes[] = {
MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n"
"Host: www.google.com\r\n"
"Proxy-Connection: keep-alive\r\n\r\n"),
};
MockRead data_reads[] = {
status,
MockRead("Content-Length: 10\r\n\r\n"),
// No response body because the test stops reading here.
MockRead(SYNCHRONOUS, ERR_UNEXPECTED), // Should not be reached.
};
StaticSocketDataProvider data(data_reads, arraysize(data_reads),
data_writes, arraysize(data_writes));
session_deps.socket_factory->AddSocketDataProvider(&data);
TestCompletionCallback callback;
scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session));
int rv = trans->Start(&request, callback.callback(), BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback.WaitForResult();
EXPECT_EQ(expected_status, rv);
}
void HttpNetworkTransactionSpdy3Test::ConnectStatusHelper(
const MockRead& status) {
ConnectStatusHelperWithExpectedStatus(
status, ERR_TUNNEL_CONNECTION_FAILED);
}
TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus100) {
ConnectStatusHelper(MockRead("HTTP/1.1 100 Continue\r\n"));
}
TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus101) {
ConnectStatusHelper(MockRead("HTTP/1.1 101 Switching Protocols\r\n"));
}
TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus201) {
ConnectStatusHelper(MockRead("HTTP/1.1 201 Created\r\n"));
}
TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus202) {
ConnectStatusHelper(MockRead("HTTP/1.1 202 Accepted\r\n"));
}
TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus203) {
ConnectStatusHelper(
MockRead("HTTP/1.1 203 Non-Authoritative Information\r\n"));
}
TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus204) {
ConnectStatusHelper(MockRead("HTTP/1.1 204 No Content\r\n"));
}
TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus205) {
ConnectStatusHelper(MockRead("HTTP/1.1 205 Reset Content\r\n"));
}
TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus206) {
ConnectStatusHelper(MockRead("HTTP/1.1 206 Partial Content\r\n"));
}
TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus300) {
ConnectStatusHelper(MockRead("HTTP/1.1 300 Multiple Choices\r\n"));
}
TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus301) {
ConnectStatusHelper(MockRead("HTTP/1.1 301 Moved Permanently\r\n"));
}
TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus302) {
ConnectStatusHelper(MockRead("HTTP/1.1 302 Found\r\n"));
}
TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus303) {
ConnectStatusHelper(MockRead("HTTP/1.1 303 See Other\r\n"));
}
TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus304) {
ConnectStatusHelper(MockRead("HTTP/1.1 304 Not Modified\r\n"));
}
TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus305) {
ConnectStatusHelper(MockRead("HTTP/1.1 305 Use Proxy\r\n"));
}
TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus306) {
ConnectStatusHelper(MockRead("HTTP/1.1 306\r\n"));
}
TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus307) {
ConnectStatusHelper(MockRead("HTTP/1.1 307 Temporary Redirect\r\n"));
}
TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus400) {
ConnectStatusHelper(MockRead("HTTP/1.1 400 Bad Request\r\n"));
}
TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus401) {
ConnectStatusHelper(MockRead("HTTP/1.1 401 Unauthorized\r\n"));
}
TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus402) {
ConnectStatusHelper(MockRead("HTTP/1.1 402 Payment Required\r\n"));
}
TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus403) {
ConnectStatusHelper(MockRead("HTTP/1.1 403 Forbidden\r\n"));
}
TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus404) {
ConnectStatusHelper(MockRead("HTTP/1.1 404 Not Found\r\n"));
}
TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus405) {
ConnectStatusHelper(MockRead("HTTP/1.1 405 Method Not Allowed\r\n"));
}
TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus406) {
ConnectStatusHelper(MockRead("HTTP/1.1 406 Not Acceptable\r\n"));
}
TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus407) {
ConnectStatusHelperWithExpectedStatus(
MockRead("HTTP/1.1 407 Proxy Authentication Required\r\n"),
ERR_PROXY_AUTH_UNSUPPORTED);
}
TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus408) {
ConnectStatusHelper(MockRead("HTTP/1.1 408 Request Timeout\r\n"));
}
TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus409) {
ConnectStatusHelper(MockRead("HTTP/1.1 409 Conflict\r\n"));
}
TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus410) {
ConnectStatusHelper(MockRead("HTTP/1.1 410 Gone\r\n"));
}
TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus411) {
ConnectStatusHelper(MockRead("HTTP/1.1 411 Length Required\r\n"));
}
TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus412) {
ConnectStatusHelper(MockRead("HTTP/1.1 412 Precondition Failed\r\n"));
}
TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus413) {
ConnectStatusHelper(MockRead("HTTP/1.1 413 Request Entity Too Large\r\n"));
}
TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus414) {
ConnectStatusHelper(MockRead("HTTP/1.1 414 Request-URI Too Long\r\n"));
}
TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus415) {
ConnectStatusHelper(MockRead("HTTP/1.1 415 Unsupported Media Type\r\n"));
}
TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus416) {
ConnectStatusHelper(
MockRead("HTTP/1.1 416 Requested Range Not Satisfiable\r\n"));
}
TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus417) {
ConnectStatusHelper(MockRead("HTTP/1.1 417 Expectation Failed\r\n"));
}
TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus500) {
ConnectStatusHelper(MockRead("HTTP/1.1 500 Internal Server Error\r\n"));
}
TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus501) {
ConnectStatusHelper(MockRead("HTTP/1.1 501 Not Implemented\r\n"));
}
TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus502) {
ConnectStatusHelper(MockRead("HTTP/1.1 502 Bad Gateway\r\n"));
}
TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus503) {
ConnectStatusHelper(MockRead("HTTP/1.1 503 Service Unavailable\r\n"));
}
TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus504) {
ConnectStatusHelper(MockRead("HTTP/1.1 504 Gateway Timeout\r\n"));
}
TEST_F(HttpNetworkTransactionSpdy3Test, ConnectStatus505) {
ConnectStatusHelper(MockRead("HTTP/1.1 505 HTTP Version Not Supported\r\n"));
}
// Test the flow when both the proxy server AND origin server require
// authentication. Again, this uses basic auth for both since that is
// the simplest to mock.
TEST_F(HttpNetworkTransactionSpdy3Test, BasicAuthProxyThenServer) {
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.google.com/");
request.load_flags = 0;
SpdySessionDependencies session_deps(ProxyService::CreateFixed("myproxy:70"));
// Configure against proxy server "myproxy:70".
scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(
CreateSession(&session_deps)));
MockWrite data_writes1[] = {
MockWrite("GET http://www.google.com/ HTTP/1.1\r\n"
"Host: www.google.com\r\n"
"Proxy-Connection: keep-alive\r\n\r\n"),
};
MockRead data_reads1[] = {
MockRead("HTTP/1.0 407 Unauthorized\r\n"),
// Give a couple authenticate options (only the middle one is actually
// supported).
MockRead("Proxy-Authenticate: Basic invalid\r\n"), // Malformed.
MockRead("Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n"),
MockRead("Proxy-Authenticate: UNSUPPORTED realm=\"FOO\"\r\n"),
MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"),
// Large content-length -- won't matter, as connection will be reset.
MockRead("Content-Length: 10000\r\n\r\n"),
MockRead(SYNCHRONOUS, ERR_FAILED),
};
// After calling trans->RestartWithAuth() the first time, this is the
// request we should be issuing -- the final header line contains the
// proxy's credentials.
MockWrite data_writes2[] = {
MockWrite("GET http://www.google.com/ HTTP/1.1\r\n"
"Host: www.google.com\r\n"
"Proxy-Connection: keep-alive\r\n"
"Proxy-Authorization: Basic Zm9vOmJhcg==\r\n\r\n"),
};
// Now the proxy server lets the request pass through to origin server.
// The origin server responds with a 401.
MockRead data_reads2[] = {
MockRead("HTTP/1.0 401 Unauthorized\r\n"),
// Note: We are using the same realm-name as the proxy server. This is
// completely valid, as realms are unique across hosts.
MockRead("WWW-Authenticate: Basic realm=\"MyRealm1\"\r\n"),
MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"),
MockRead("Content-Length: 2000\r\n\r\n"),
MockRead(SYNCHRONOUS, ERR_FAILED), // Won't be reached.
};
// After calling trans->RestartWithAuth() the second time, we should send
// the credentials for both the proxy and origin server.
MockWrite data_writes3[] = {
MockWrite("GET http://www.google.com/ HTTP/1.1\r\n"
"Host: www.google.com\r\n"
"Proxy-Connection: keep-alive\r\n"
"Proxy-Authorization: Basic Zm9vOmJhcg==\r\n"
"Authorization: Basic Zm9vMjpiYXIy\r\n\r\n"),
};
// Lastly we get the desired content.
MockRead data_reads3[] = {
MockRead("HTTP/1.0 200 OK\r\n"),
MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"),
MockRead("Content-Length: 100\r\n\r\n"),
MockRead(SYNCHRONOUS, OK),
};
StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
data_writes1, arraysize(data_writes1));
StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2),
data_writes2, arraysize(data_writes2));
StaticSocketDataProvider data3(data_reads3, arraysize(data_reads3),
data_writes3, arraysize(data_writes3));
session_deps.socket_factory->AddSocketDataProvider(&data1);
session_deps.socket_factory->AddSocketDataProvider(&data2);
session_deps.socket_factory->AddSocketDataProvider(&data3);
TestCompletionCallback callback1;
int rv = trans->Start(&request, callback1.callback(), BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback1