blob: b3f00b716f4d318830e4f2eb5c251d8082cde402 [file] [log] [blame]
// Copyright (c) 2012 The Chromium OS 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 "shill/http_proxy.h"
#include <netinet/in.h>
#include <string>
#include <vector>
#include <base/strings/stringprintf.h>
#include <gtest/gtest.h>
#include "shill/ip_address.h"
#include "shill/mock_async_connection.h"
#include "shill/mock_connection.h"
#include "shill/mock_control.h"
#include "shill/mock_device_info.h"
#include "shill/mock_dns_client.h"
#include "shill/mock_event_dispatcher.h"
#include "shill/mock_sockets.h"
using base::StringPrintf;
using std::string;
using std::vector;
using ::testing::_;
using ::testing::AtLeast;
using ::testing::DoAll;
using ::testing::Invoke;
using ::testing::NiceMock;
using ::testing::Return;
using ::testing::ReturnArg;
using ::testing::ReturnNew;
using ::testing::ReturnRef;
using ::testing::SetArgumentPointee;
using ::testing::StrEq;
using ::testing::StrictMock;
using ::testing::Test;
namespace shill {
namespace {
const char kBadHeaderMissingURL[] = "BLAH\r\n";
const char kBadHeaderMissingVersion[] = "BLAH http://hostname\r\n";
const char kBadHostnameLine[] = "GET HTTP/1.1 http://hostname\r\n";
const char kBasicGetHeader[] = "GET / HTTP/1.1\r\n";
const char kBasicGetHeaderWithURL[] =
"GET http://www.chromium.org/ HTTP/1.1\r\n";
const char kBasicGetHeaderWithURLNoTrailingSlash[] =
"GET http://www.chromium.org HTTP/1.1\r\n";
const char kConnectQuery[] =
"CONNECT 10.10.10.10:443 HTTP/1.1\r\n"
"Host: 10.10.10.10:443\r\n\r\n";
const char kQueryTemplate[] = "GET %s HTTP/%s\r\n%s"
"User-Agent: Mozilla/5.0 (X11; CrOS i686 1299.0.2011) "
"AppleWebKit/535.8 (KHTML, like Gecko) Chrome/17.0.936.0 Safari/535.8\r\n"
"Accept: text/html,application/xhtml+xml,application/xml;"
"q=0.9,*/*;q=0.8\r\n"
"Accept-Encoding: gzip,deflate,sdch\r\n"
"Accept-Language: en-US,en;q=0.8,ja;q=0.6\r\n"
"Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3\r\n"
"Cookie: PREF=ID=xxxxxxxxxxxxxxxx:U=xxxxxxxxxxxxxxxx:FF=0:"
"TM=1317340083:LM=1317390705:GM=1:S=_xxxxxxxxxxxxxxx; "
"NID=52=xxxxxxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx_xxxxxxxxxxxxxxxxxxxxxxx; "
"HSID=xxxxxxxxxxxx-xxxx; APISID=xxxxxxxxxxxxxxxx/xxxxxxxxxxxxxxxxx; "
"SID=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx_xxxxxxxxxxx"
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxx"
"xxx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxxxx"
"_xxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxx-xx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
"xxxxxxxxxxxxxxxx\r\n\r\n";
const char kInterfaceName[] = "int0";
const char kDNSServer0[] = "8.8.8.8";
const char kDNSServer1[] = "8.8.4.4";
const char kServerAddress[] = "10.10.10.10";
const char *kDNSServers[] = { kDNSServer0, kDNSServer1 };
const int kProxyFD = 10203;
const int kServerFD = 10204;
const int kClientFD = 10205;
const int kServerPort = 40506;
const int kConnectPort = 443;
} // namespace
MATCHER_P(IsIPAddress, address, "") {
IPAddress ip_address(IPAddress::kFamilyIPv4);
EXPECT_TRUE(ip_address.SetAddressFromString(address));
return ip_address.Equals(arg);
}
MATCHER_P(CallbackEq, callback, "") {
return arg.Equals(callback);
}
class HTTPProxyTest : public Test {
public:
HTTPProxyTest()
: interface_name_(kInterfaceName),
server_async_connection_(NULL),
dns_servers_(kDNSServers, kDNSServers + 2),
dns_client_(NULL),
device_info_(new NiceMock<MockDeviceInfo>(
&control_,
reinterpret_cast<EventDispatcher*>(NULL),
reinterpret_cast<Metrics*>(NULL),
reinterpret_cast<Manager*>(NULL))),
connection_(new StrictMock<MockConnection>(device_info_.get())),
proxy_(connection_) { }
protected:
virtual void SetUp() {
EXPECT_CALL(*connection_.get(), interface_name())
.WillRepeatedly(ReturnRef(interface_name_));
EXPECT_CALL(*connection_.get(), dns_servers())
.WillRepeatedly(ReturnRef(dns_servers_));
}
virtual void TearDown() {
if (proxy_.sockets_) {
ExpectStop();
}
const int proxy_fds[] = {
proxy_.client_socket_,
proxy_.server_socket_,
proxy_.proxy_socket_
};
for (const int fd : proxy_fds) {
if (fd != -1) {
EXPECT_CALL(sockets_, Close(fd));
}
}
}
string CreateRequest(const string &url, const string &http_version,
const string &extra_lines) {
string append_lines(extra_lines);
if (append_lines.size()) {
append_lines.append("\r\n");
}
return StringPrintf(kQueryTemplate, url.c_str(), http_version.c_str(),
append_lines.c_str());
}
int InvokeGetSockName(int fd, struct sockaddr *addr_out,
socklen_t *sockaddr_size) {
struct sockaddr_in addr;
EXPECT_EQ(kProxyFD, fd);
EXPECT_GE(sizeof(sockaddr_in), *sockaddr_size);
addr.sin_addr.s_addr = 0;
addr.sin_port = kServerPort;
memcpy(addr_out, &addr, sizeof(addr));
*sockaddr_size = sizeof(sockaddr_in);
return 0;
}
void InvokeSyncConnect(const IPAddress &/*address*/, int /*port*/) {
proxy_.OnConnectCompletion(true, kServerFD);
}
size_t FindInRequest(const string &find_string) {
const ByteString &request_data = GetClientData();
string request_string(
reinterpret_cast<const char *>(request_data.GetConstData()),
request_data.GetLength());
return request_string.find(find_string);
}
// Accessors
const ByteString &GetClientData() {
return proxy_.client_data_;
}
HTTPProxy *proxy() { return &proxy_; }
HTTPProxy::State GetProxyState() {
return proxy_.state_;
}
const ByteString &GetServerData() {
return proxy_.server_data_;
}
MockSockets &sockets() { return sockets_; }
MockEventDispatcher &dispatcher() { return dispatcher_; }
// Expectations
void ExpectClientReset() {
EXPECT_EQ(-1, proxy_.client_socket_);
EXPECT_TRUE(proxy_.client_version_.empty());
EXPECT_EQ(HTTPProxy::kDefaultServerPort, proxy_.server_port_);
EXPECT_EQ(-1, proxy_.server_socket_);
EXPECT_TRUE(proxy_.idle_timeout_.IsCancelled());
EXPECT_TRUE(proxy_.client_headers_.empty());
EXPECT_TRUE(proxy_.server_hostname_.empty());
EXPECT_TRUE(proxy_.client_data_.IsEmpty());
EXPECT_TRUE(proxy_.server_data_.IsEmpty());
EXPECT_FALSE(proxy_.read_client_handler_.get());
EXPECT_FALSE(proxy_.write_client_handler_.get());
EXPECT_FALSE(proxy_.read_server_handler_.get());
EXPECT_FALSE(proxy_.write_server_handler_.get());
EXPECT_FALSE(proxy_.is_route_requested_);
}
void ExpectReset() {
EXPECT_FALSE(proxy_.accept_handler_.get());
EXPECT_EQ(proxy_.connection_.get(), connection_.get());
EXPECT_FALSE(proxy_.dispatcher_);
EXPECT_FALSE(proxy_.dns_client_.get());
EXPECT_EQ(-1, proxy_.proxy_port_);
EXPECT_EQ(-1, proxy_.proxy_socket_);
EXPECT_FALSE(proxy_.server_async_connection_.get());
EXPECT_FALSE(proxy_.sockets_);
EXPECT_EQ(HTTPProxy::kStateIdle, proxy_.state_);
ExpectClientReset();
}
void ExpectStart() {
EXPECT_CALL(sockets(), Socket(_, _, _))
.WillOnce(Return(kProxyFD));
EXPECT_CALL(sockets(), Bind(kProxyFD, _, _))
.WillOnce(Return(0));
EXPECT_CALL(sockets(), GetSockName(kProxyFD, _, _))
.WillOnce(Invoke(this, &HTTPProxyTest::InvokeGetSockName));
EXPECT_CALL(sockets(), SetNonBlocking(kProxyFD))
.WillOnce(Return(0));
EXPECT_CALL(sockets(), Listen(kProxyFD, _))
.WillOnce(Return(0));
EXPECT_CALL(dispatcher_,
CreateReadyHandler(kProxyFD,
IOHandler::kModeInput,
CallbackEq(proxy_.accept_callback_)))
.WillOnce(ReturnNew<IOHandler>());
}
void ExpectStop() {
if (dns_client_) {
EXPECT_CALL(*dns_client_, Stop())
.Times(AtLeast(1));
}
if (server_async_connection_) {
EXPECT_CALL(*server_async_connection_, Stop())
.Times(AtLeast(1));
}
if (proxy_.is_route_requested_) {
EXPECT_CALL(*connection_.get(), ReleaseRouting());
}
}
void ExpectClientInput(int fd) {
EXPECT_CALL(sockets(), Accept(kProxyFD, _, _))
.WillOnce(Return(fd));
EXPECT_CALL(sockets(), SetNonBlocking(fd))
.WillOnce(Return(0));
EXPECT_CALL(dispatcher(),
CreateInputHandler(fd,
CallbackEq(proxy_.read_client_callback_), _))
.WillOnce(ReturnNew<IOHandler>());
ExpectTransactionTimeout();
ExpectClientHeaderTimeout();
}
void ExpectTimeout(int timeout) {
EXPECT_CALL(dispatcher_, PostDelayedTask(_, timeout * 1000))
.WillOnce(Return(true));
}
void ExpectClientHeaderTimeout() {
ExpectTimeout(HTTPProxy::kClientHeaderTimeoutSeconds);
}
void ExpectConnectTimeout() {
ExpectTimeout(HTTPProxy::kConnectTimeoutSeconds);
}
void ExpectInputTimeout() {
ExpectTimeout(HTTPProxy::kInputTimeoutSeconds);
}
void ExpectRepeatedInputTimeout() {
EXPECT_CALL(dispatcher_,
PostDelayedTask(_, HTTPProxy::kInputTimeoutSeconds * 1000))
.WillRepeatedly(Return(true));
}
void ExpectTransactionTimeout() {
ExpectTimeout(HTTPProxy::kTransactionTimeoutSeconds);
}
void ExpectInClientResponse(const string &response_data) {
string server_data(reinterpret_cast<char *>(proxy_.server_data_.GetData()),
proxy_.server_data_.GetLength());
EXPECT_NE(string::npos, server_data.find(response_data));
}
void ExpectClientError(int code, const string &error) {
EXPECT_EQ(HTTPProxy::kStateFlushResponse, GetProxyState());
string status_line = StringPrintf("HTTP/1.1 %d ERROR", code);
ExpectInClientResponse(status_line);
ExpectInClientResponse(error);
}
void ExpectClientInternalError() {
ExpectClientError(500, HTTPProxy::kInternalErrorMsg);
}
void ExpectClientVersion(const string &version) {
EXPECT_EQ(version, proxy_.client_version_);
}
void ExpectServerHostname(const string &hostname) {
EXPECT_EQ(hostname, proxy_.server_hostname_);
}
void ExpectFirstLine(const string &line) {
EXPECT_EQ(line, proxy_.client_headers_[0] + "\r\n");
}
void ExpectDNSRequest(const string &host, bool return_value) {
EXPECT_CALL(*dns_client_, Start(StrEq(host), _))
.WillOnce(Return(return_value));
}
void ExpectAsyncConnect(const string &address, int port,
bool return_value) {
EXPECT_CALL(*server_async_connection_, Start(IsIPAddress(address), port))
.WillOnce(Return(return_value));
}
void ExpectSyncConnect(const string &address, int port) {
EXPECT_CALL(*server_async_connection_, Start(IsIPAddress(address), port))
.WillOnce(DoAll(Invoke(this, &HTTPProxyTest::InvokeSyncConnect),
Return(true)));
}
void ExpectClientData() {
EXPECT_CALL(dispatcher(),
CreateReadyHandler(kClientFD,
IOHandler::kModeOutput,
CallbackEq(proxy_.write_client_callback_)))
.WillOnce(ReturnNew<IOHandler>());
}
void ExpectClientResult() {
ExpectClientData();
ExpectInputTimeout();
}
void ExpectServerInput() {
EXPECT_CALL(dispatcher(),
CreateInputHandler(kServerFD,
CallbackEq(proxy_.read_server_callback_), _))
.WillOnce(ReturnNew<IOHandler>());
ExpectInputTimeout();
}
void ExpectServerOutput() {
EXPECT_CALL(dispatcher(),
CreateReadyHandler(kServerFD,
IOHandler::kModeOutput,
CallbackEq(proxy_.write_server_callback_)))
.WillOnce(ReturnNew<IOHandler>());
ExpectInputTimeout();
}
void ExpectRepeatedServerOutput() {
EXPECT_CALL(dispatcher(),
CreateReadyHandler(kServerFD, IOHandler::kModeOutput,
CallbackEq(proxy_.write_server_callback_)))
.WillOnce(ReturnNew<IOHandler>());
ExpectRepeatedInputTimeout();
}
void ExpectTunnelClose() {
EXPECT_CALL(sockets(), Close(kClientFD))
.WillOnce(Return(0));
EXPECT_CALL(sockets(), Close(kServerFD))
.WillOnce(Return(0));
ExpectStop();
}
void ExpectRouteRequest() {
EXPECT_CALL(*connection_.get(), RequestRouting());
}
void ExpectRouteRelease() {
EXPECT_CALL(*connection_.get(), ReleaseRouting());
}
// Callers for various private routines in the proxy
bool StartProxy() {
bool ret = proxy_.Start(&dispatcher_, &sockets_);
if (ret) {
dns_client_ = new StrictMock<MockDNSClient>();
// Passes ownership.
proxy_.dns_client_.reset(dns_client_);
server_async_connection_ = new StrictMock<MockAsyncConnection>();
// Passes ownership.
proxy_.server_async_connection_.reset(server_async_connection_);
}
return ret;
}
void AcceptClient(int fd) {
proxy_.AcceptClient(fd);
}
void GetDNSResultFailure(const string &error_msg) {
Error error(Error::kOperationFailed, error_msg);
IPAddress address(IPAddress::kFamilyUnknown);
proxy_.GetDNSResult(error, address);
}
void GetDNSResultSuccess(const IPAddress &address) {
Error error;
proxy_.GetDNSResult(error, address);
}
void OnConnectCompletion(bool result, int sockfd) {
proxy_.OnConnectCompletion(result, sockfd);
}
void ReadFromClient(const string &data) {
const unsigned char *ptr =
reinterpret_cast<const unsigned char *>(data.c_str());
vector<unsigned char> data_bytes(ptr, ptr + data.length());
InputData proxy_data(data_bytes.data(), data_bytes.size());
proxy_.ReadFromClient(&proxy_data);
}
void ReadFromServer(const string &data) {
const unsigned char *ptr =
reinterpret_cast<const unsigned char *>(data.c_str());
vector<unsigned char> data_bytes(ptr, ptr + data.length());
InputData proxy_data(data_bytes.data(), data_bytes.size());
proxy_.ReadFromServer(&proxy_data);
}
void SendClientError(int code, const string &error) {
proxy_.SendClientError(code, error);
EXPECT_FALSE(proxy_.server_data_.IsEmpty());
}
void StopClient() {
EXPECT_CALL(*dns_client_, Stop());
EXPECT_CALL(*server_async_connection_, Stop());
proxy_.StopClient();
}
void StopProxy() {
ExpectStop();
proxy_.Stop();
server_async_connection_ = NULL;
dns_client_ = NULL;
ExpectReset();
}
void WriteToClient(int fd) {
proxy_.WriteToClient(fd);
}
void WriteToServer(int fd) {
proxy_.WriteToServer(fd);
}
void SetupClient() {
ExpectStart();
ASSERT_TRUE(StartProxy());
ExpectClientInput(kClientFD);
AcceptClient(kProxyFD);
EXPECT_EQ(HTTPProxy::kStateReadClientHeader, GetProxyState());
}
void SetupConnectWithRequest(const string &url, const string &http_version,
const string &extra_lines) {
ExpectDNSRequest("www.chromium.org", true);
ExpectRouteRequest();
ReadFromClient(CreateRequest(url, http_version, extra_lines));
IPAddress addr(IPAddress::kFamilyIPv4);
EXPECT_TRUE(addr.SetAddressFromString(kServerAddress));
GetDNSResultSuccess(addr);
}
void SetupConnect() {
SetupConnectWithRequest("/", "1.1", "Host: www.chromium.org:40506");
}
void SetupConnectAsync() {
SetupClient();
ExpectAsyncConnect(kServerAddress, kServerPort, true);
ExpectConnectTimeout();
SetupConnect();
}
void SetupConnectComplete() {
SetupConnectAsync();
ExpectServerOutput();
OnConnectCompletion(true, kServerFD);
EXPECT_EQ(HTTPProxy::kStateTunnelData, GetProxyState());
}
void CauseReadError() {
proxy_.OnReadError(Error());
}
private:
const string interface_name_;
// Owned by the HTTPProxy, but tracked here for EXPECT().
StrictMock<MockAsyncConnection> *server_async_connection_;
vector<string> dns_servers_;
// Owned by the HTTPProxy, but tracked here for EXPECT().
StrictMock<MockDNSClient> *dns_client_;
MockEventDispatcher dispatcher_;
MockControl control_;
scoped_ptr<MockDeviceInfo> device_info_;
scoped_refptr<MockConnection> connection_;
StrictMock<MockSockets> sockets_;
HTTPProxy proxy_; // Destroy first, before anything it references.
};
TEST_F(HTTPProxyTest, StartFailSocket) {
EXPECT_CALL(sockets(), Socket(_, _, _))
.WillOnce(Return(-1));
EXPECT_FALSE(StartProxy());
ExpectReset();
}
TEST_F(HTTPProxyTest, StartFailBind) {
EXPECT_CALL(sockets(), Socket(_, _, _))
.WillOnce(Return(kProxyFD));
EXPECT_CALL(sockets(), Bind(kProxyFD, _, _))
.WillOnce(Return(-1));
EXPECT_CALL(sockets(), Close(kProxyFD))
.WillOnce(Return(0));
EXPECT_FALSE(StartProxy());
ExpectReset();
}
TEST_F(HTTPProxyTest, StartFailGetSockName) {
EXPECT_CALL(sockets(), Socket(_, _, _))
.WillOnce(Return(kProxyFD));
EXPECT_CALL(sockets(), Bind(kProxyFD, _, _))
.WillOnce(Return(0));
EXPECT_CALL(sockets(), GetSockName(kProxyFD, _, _))
.WillOnce(Return(-1));
EXPECT_CALL(sockets(), Close(kProxyFD))
.WillOnce(Return(0));
EXPECT_FALSE(StartProxy());
ExpectReset();
}
TEST_F(HTTPProxyTest, StartFailSetNonBlocking) {
EXPECT_CALL(sockets(), Socket(_, _, _))
.WillOnce(Return(kProxyFD));
EXPECT_CALL(sockets(), Bind(kProxyFD, _, _))
.WillOnce(Return(0));
EXPECT_CALL(sockets(), GetSockName(kProxyFD, _, _))
.WillOnce(Return(0));
EXPECT_CALL(sockets(), SetNonBlocking(kProxyFD))
.WillOnce(Return(-1));
EXPECT_CALL(sockets(), Close(kProxyFD))
.WillOnce(Return(0));
EXPECT_FALSE(StartProxy());
ExpectReset();
}
TEST_F(HTTPProxyTest, StartFailListen) {
EXPECT_CALL(sockets(), Socket(_, _, _))
.WillOnce(Return(kProxyFD));
EXPECT_CALL(sockets(), Bind(kProxyFD, _, _))
.WillOnce(Return(0));
EXPECT_CALL(sockets(), GetSockName(kProxyFD, _, _))
.WillOnce(Return(0));
EXPECT_CALL(sockets(), SetNonBlocking(kProxyFD))
.WillOnce(Return(0));
EXPECT_CALL(sockets(), Listen(kProxyFD, _))
.WillOnce(Return(-1));
EXPECT_CALL(sockets(), Close(kProxyFD))
.WillOnce(Return(0));
EXPECT_FALSE(StartProxy());
ExpectReset();
}
TEST_F(HTTPProxyTest, StartSuccess) {
ExpectStart();
EXPECT_TRUE(StartProxy());
}
TEST_F(HTTPProxyTest, SendClientError) {
SetupClient();
ExpectClientResult();
SendClientError(500, "This is an error");
ExpectClientError(500, "This is an error");
// We succeed in sending all but one byte of the client response.
int buf_len = GetServerData().GetLength();
EXPECT_CALL(sockets(), Send(kClientFD, _, buf_len, 0))
.WillOnce(Return(buf_len - 1));
ExpectInputTimeout();
WriteToClient(kClientFD);
EXPECT_EQ(1, GetServerData().GetLength());
EXPECT_EQ(HTTPProxy::kStateFlushResponse, GetProxyState());
// When we are able to send the last byte, we close the connection.
EXPECT_CALL(sockets(), Send(kClientFD, _, 1, 0))
.WillOnce(Return(1));
EXPECT_CALL(sockets(), Close(kClientFD))
.WillOnce(Return(0));
ExpectStop();
WriteToClient(kClientFD);
EXPECT_EQ(HTTPProxy::kStateWaitConnection, GetProxyState());
}
TEST_F(HTTPProxyTest, ReadMissingURL) {
SetupClient();
ExpectClientResult();
ReadFromClient(kBadHeaderMissingURL);
ExpectClientError(501, "Server could not parse HTTP method");
}
TEST_F(HTTPProxyTest, ReadMissingVersion) {
SetupClient();
ExpectClientResult();
ReadFromClient(kBadHeaderMissingVersion);
ExpectClientError(501, "Server only accepts HTTP/1.x requests");
}
TEST_F(HTTPProxyTest, ReadBadHostname) {
SetupClient();
ExpectClientResult();
ReadFromClient(kBadHostnameLine);
ExpectClientInternalError();
}
TEST_F(HTTPProxyTest, GoodFirstLineWithoutURL) {
SetupClient();
ExpectClientHeaderTimeout();
ReadFromClient(kBasicGetHeader);
ExpectClientVersion("1.1");
ExpectServerHostname("");
ExpectFirstLine(kBasicGetHeader);
}
TEST_F(HTTPProxyTest, GoodFirstLineWithURL) {
SetupClient();
ExpectClientHeaderTimeout();
ReadFromClient(kBasicGetHeaderWithURL);
ExpectClientVersion("1.1");
ExpectServerHostname("www.chromium.org");
ExpectFirstLine(kBasicGetHeader);
}
TEST_F(HTTPProxyTest, GoodFirstLineWithURLNoSlash) {
SetupClient();
ExpectClientHeaderTimeout();
ReadFromClient(kBasicGetHeaderWithURLNoTrailingSlash);
ExpectClientVersion("1.1");
ExpectServerHostname("www.chromium.org");
ExpectFirstLine(kBasicGetHeader);
}
TEST_F(HTTPProxyTest, NoHostInRequest) {
SetupClient();
ExpectClientResult();
ReadFromClient(CreateRequest("/", "1.1", ""));
ExpectClientError(400, "I don't know what host you want me to connect to");
}
TEST_F(HTTPProxyTest, TooManyColonsInHost) {
SetupClient();
ExpectClientResult();
ReadFromClient(CreateRequest("/", "1.1", "Host: www.chromium.org:80:40506"));
ExpectClientError(400, "Too many colons in hostname");
}
TEST_F(HTTPProxyTest, ClientReadError) {
SetupClient();
EXPECT_CALL(sockets(), Close(kClientFD))
.WillOnce(Return(0));
ExpectStop();
CauseReadError();
ExpectClientReset();
}
TEST_F(HTTPProxyTest, DNSRequestFailure) {
SetupClient();
ExpectRouteRequest();
ExpectDNSRequest("www.chromium.org", false);
ExpectClientResult();
ReadFromClient(CreateRequest("/", "1.1", "Host: www.chromium.org:40506"));
ExpectClientError(502, "Could not resolve hostname");
}
TEST_F(HTTPProxyTest, DNSRequestDelayedFailure) {
SetupClient();
ExpectRouteRequest();
ExpectDNSRequest("www.chromium.org", true);
ReadFromClient(CreateRequest("/", "1.1", "Host: www.chromium.org:40506"));
ExpectClientResult();
const std::string not_found_error(DNSClient::kErrorNotFound);
GetDNSResultFailure(not_found_error);
ExpectClientError(502, string("Could not resolve hostname: ") +
not_found_error);
}
TEST_F(HTTPProxyTest, TrailingClientData) {
SetupClient();
ExpectRouteRequest();
ExpectDNSRequest("www.chromium.org", true);
const string trailing_data("Trailing client data");
ReadFromClient(CreateRequest("/", "1.1", "Host: www.chromium.org:40506") +
trailing_data);
EXPECT_EQ(GetClientData().GetLength() - trailing_data.length(),
FindInRequest(trailing_data));
EXPECT_EQ(HTTPProxy::kStateLookupServer, GetProxyState());
}
TEST_F(HTTPProxyTest, LineContinuation) {
SetupClient();
ExpectRouteRequest();
ExpectDNSRequest("www.chromium.org", true);
string text_to_keep("X-Long-Header: this is one line\r\n"
"\tand this is another");
ReadFromClient(CreateRequest("http://www.chromium.org/", "1.1",
text_to_keep));
EXPECT_NE(string::npos, FindInRequest(text_to_keep));
}
// NB: This tests two different things:
// 1) That the system replaces the value for "Proxy-Connection" headers.
// 2) That when it replaces a header, it also removes the text in the line
// continuation.
TEST_F(HTTPProxyTest, LineContinuationRemoval) {
SetupClient();
ExpectRouteRequest();
ExpectDNSRequest("www.chromium.org", true);
string text_to_remove("remove this text please");
ReadFromClient(CreateRequest("http://www.chromium.org/", "1.1",
string("Proxy-Connection: stuff\r\n\t") +
text_to_remove));
EXPECT_EQ(string::npos, FindInRequest(text_to_remove));
EXPECT_NE(string::npos, FindInRequest("Proxy-Connection: close\r\n"));
}
TEST_F(HTTPProxyTest, ConnectSynchronousFailure) {
SetupClient();
ExpectAsyncConnect(kServerAddress, kServerPort, false);
ExpectClientResult();
SetupConnect();
ExpectClientError(500, "Could not create socket to connect to server");
}
TEST_F(HTTPProxyTest, ConnectAsyncConnectFailure) {
SetupConnectAsync();
ExpectClientResult();
OnConnectCompletion(false, -1);
ExpectClientError(500, "Socket connection delayed failure");
}
TEST_F(HTTPProxyTest, ConnectSynchronousSuccess) {
SetupClient();
ExpectSyncConnect(kServerAddress, 999);
ExpectRepeatedServerOutput();
SetupConnectWithRequest("/", "1.1", "Host: www.chromium.org:999");
EXPECT_EQ(HTTPProxy::kStateTunnelData, GetProxyState());
}
TEST_F(HTTPProxyTest, ConnectIPAddresss) {
SetupClient();
ExpectSyncConnect(kServerAddress, 999);
ExpectRepeatedServerOutput();
ExpectRouteRequest();
ReadFromClient(CreateRequest("/", "1.1",
StringPrintf("Host: %s:999", kServerAddress)));
EXPECT_EQ(HTTPProxy::kStateTunnelData, GetProxyState());
}
TEST_F(HTTPProxyTest, ConnectAsyncConnectSuccess) {
SetupConnectComplete();
}
TEST_F(HTTPProxyTest, HTTPConnectMethod) {
SetupClient();
ExpectAsyncConnect(kServerAddress, kConnectPort, true);
ExpectConnectTimeout();
ExpectRouteRequest();
ReadFromClient(kConnectQuery);
ExpectRepeatedInputTimeout();
ExpectClientData();
OnConnectCompletion(true, kServerFD);
ExpectInClientResponse("HTTP/1.1 200 OK\r\n\r\n");
}
TEST_F(HTTPProxyTest, TunnelData) {
SetupConnectComplete();
// The proxy is waiting for the server to be ready to accept data.
EXPECT_CALL(sockets(), Send(kServerFD, _, _, 0))
.WillOnce(Return(10));
ExpectServerInput();
WriteToServer(kServerFD);
EXPECT_CALL(sockets(), Send(kServerFD, _, _, 0))
.WillOnce(ReturnArg<2>());
ExpectInputTimeout();
WriteToServer(kServerFD);
EXPECT_EQ(HTTPProxy::kStateTunnelData, GetProxyState());
// Tunnel a reply back to the client.
const string server_result("200 OK ... and so on");
ExpectClientResult();
ReadFromServer(server_result);
EXPECT_EQ(server_result,
string(reinterpret_cast<const char *>(
GetServerData().GetConstData()),
GetServerData().GetLength()));
// Allow part of the result string to be sent to the client.
const int part = server_result.length() / 2;
EXPECT_CALL(sockets(), Send(kClientFD, _, server_result.length(), 0))
.WillOnce(Return(part));
ExpectInputTimeout();
WriteToClient(kClientFD);
EXPECT_EQ(HTTPProxy::kStateTunnelData, GetProxyState());
// The Server closes the connection while the client is still reading.
ExpectInputTimeout();
ReadFromServer("");
EXPECT_EQ(HTTPProxy::kStateFlushResponse, GetProxyState());
// When the last part of the response is written to the client, we close
// all connections.
EXPECT_CALL(sockets(), Send(kClientFD, _, server_result.length() - part, 0))
.WillOnce(ReturnArg<2>());
ExpectTunnelClose();
WriteToClient(kClientFD);
EXPECT_EQ(HTTPProxy::kStateWaitConnection, GetProxyState());
}
TEST_F(HTTPProxyTest, TunnelDataFailWriteClient) {
SetupConnectComplete();
EXPECT_CALL(sockets(), Send(kClientFD, _, _, 0))
.WillOnce(Return(-1));
ExpectTunnelClose();
WriteToClient(kClientFD);
ExpectClientReset();
EXPECT_EQ(HTTPProxy::kStateWaitConnection, GetProxyState());
}
TEST_F(HTTPProxyTest, TunnelDataFailWriteServer) {
SetupConnectComplete();
EXPECT_CALL(sockets(), Send(kServerFD, _, _, 0))
.WillOnce(Return(-1));
ExpectTunnelClose();
WriteToServer(kServerFD);
ExpectClientReset();
EXPECT_EQ(HTTPProxy::kStateWaitConnection, GetProxyState());
}
TEST_F(HTTPProxyTest, TunnelDataFailReadServer) {
SetupConnectComplete();
EXPECT_CALL(sockets(), Send(kServerFD, _, _, 0))
.WillOnce(Return(10));
ExpectServerInput();
WriteToServer(kServerFD);
ExpectTunnelClose();
CauseReadError();
ExpectClientReset();
EXPECT_EQ(HTTPProxy::kStateWaitConnection, GetProxyState());
}
TEST_F(HTTPProxyTest, TunnelDataFailClientClose) {
SetupConnectComplete();
ExpectTunnelClose();
ReadFromClient("");
ExpectClientReset();
EXPECT_EQ(HTTPProxy::kStateWaitConnection, GetProxyState());
}
TEST_F(HTTPProxyTest, TunnelDataFailServerClose) {
SetupConnectComplete();
ExpectTunnelClose();
ReadFromServer("");
ExpectClientReset();
EXPECT_EQ(HTTPProxy::kStateWaitConnection, GetProxyState());
}
TEST_F(HTTPProxyTest, StopClient) {
SetupConnectComplete();
EXPECT_CALL(sockets(), Close(kClientFD))
.WillOnce(Return(0));
EXPECT_CALL(sockets(), Close(kServerFD))
.WillOnce(Return(0));
ExpectRouteRelease();
StopClient();
ExpectClientReset();
EXPECT_EQ(HTTPProxy::kStateWaitConnection, GetProxyState());
}
} // namespace shill