shill: Add http_proxy class
The http_proxy adds a device/connection based proxy that guarantees
to the caller that its HTTP request will go out a particular device's
connection. DNS requests occur through a bound socket to this device
and goes to DNS servers configured on this connection. HTTP requests
will also be bound to this interface. This facility will be used by
a number of peripheral bits including portal detection, activation and
cashew.
BUG=chromium-os:21664
TEST=New unit test. New (disabled) functional test, against which I
can run "curl -x" and Chrome with manual proxy settings.
Change-Id: I0d59bf0ae27d3538ef359f786742f5c2f1d6fef9
Reviewed-on: https://gerrit.chromium.org/gerrit/10165
Reviewed-by: Thieu Le <thieule@chromium.org>
Tested-by: Paul Stewart <pstew@chromium.org>
Commit-Ready: Paul Stewart <pstew@chromium.org>
diff --git a/http_proxy_unittest.cc b/http_proxy_unittest.cc
new file mode 100644
index 0000000..07e9cb7
--- /dev/null
+++ b/http_proxy_unittest.cc
@@ -0,0 +1,738 @@
+// Copyright (c) 2011 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/stringprintf.h>
+#include <gtest/gtest.h>
+
+#include "shill/ip_address.h"
+#include "shill/mock_async_connection.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::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 kBadHeader[] = "BLAH\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 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;
+} // namespace {}
+
+MATCHER_P(IsIPAddress, address, "") {
+ IPAddress ip_address(IPAddress::kFamilyIPv4);
+ EXPECT_TRUE(ip_address.SetAddressFromString(address));
+ return ip_address.Equals(arg);
+}
+
+class HTTPProxyTest : public Test {
+ public:
+ HTTPProxyTest()
+ : server_async_connection_(NULL),
+ dns_servers_(kDNSServers, kDNSServers + 2),
+ dns_client_(NULL),
+ proxy_(kInterfaceName, dns_servers_) { }
+ protected:
+ virtual void TearDown() {
+ if (proxy_.sockets_) {
+ ExpectStop();
+ }
+ }
+ 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_FALSE(proxy_.idle_timeout_);
+ 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());
+ }
+ void ExpectReset() {
+ EXPECT_FALSE(proxy_.accept_handler_.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,
+ proxy_.accept_callback_.get()))
+ .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));
+ }
+ }
+ 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, proxy_.read_client_callback_.get()))
+ .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 ExpectClientError(int code, const string &error) {
+ EXPECT_EQ(HTTPProxy::kStateFlushResponse, GetProxyState());
+ string status_line = StringPrintf("HTTP/1.1 %d ERROR", code);
+ string server_data(reinterpret_cast<char *>(proxy_.server_data_.GetData()),
+ proxy_.server_data_.GetLength());
+ EXPECT_NE(string::npos, server_data.find(status_line));
+ EXPECT_NE(string::npos, server_data.find(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 ExpectDNSFailure(const string &error) {
+ EXPECT_CALL(*dns_client_, error())
+ .WillOnce(ReturnRef(error));
+ }
+ 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 ExpectClientResult() {
+ EXPECT_CALL(dispatcher(),
+ CreateReadyHandler(kClientFD,
+ IOHandler::kModeOutput,
+ proxy_.write_client_callback_.get()))
+ .WillOnce(ReturnNew<IOHandler>());
+ ExpectInputTimeout();
+ }
+ void ExpectServerInput() {
+ EXPECT_CALL(dispatcher(),
+ CreateInputHandler(kServerFD,
+ proxy_.read_server_callback_.get()))
+ .WillOnce(ReturnNew<IOHandler>());
+ ExpectInputTimeout();
+ }
+ void ExpectServerOutput() {
+ EXPECT_CALL(dispatcher(),
+ CreateReadyHandler(kServerFD,
+ IOHandler::kModeOutput,
+ proxy_.write_server_callback_.get()))
+ .WillOnce(ReturnNew<IOHandler>());
+ ExpectInputTimeout();
+ }
+ void ExpectRepeatedServerOutput() {
+ EXPECT_CALL(dispatcher(),
+ CreateReadyHandler(kServerFD,
+ IOHandler::kModeOutput,
+ proxy_.write_server_callback_.get()))
+ .WillOnce(ReturnNew<IOHandler>());
+ ExpectRepeatedInputTimeout();
+ }
+
+ void ExpectTunnelClose() {
+ EXPECT_CALL(sockets(), Close(kClientFD))
+ .WillOnce(Return(0));
+ EXPECT_CALL(sockets(), Close(kServerFD))
+ .WillOnce(Return(0));
+ ExpectStop();
+ }
+
+ // 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 GetDNSResult(bool result) {
+ proxy_.GetDNSResult(result);
+ }
+ 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);
+ ReadFromClient(CreateRequest(url, http_version, extra_lines));
+ IPAddress addr(IPAddress::kFamilyIPv4);
+ EXPECT_TRUE(addr.SetAddressFromString(kServerAddress));
+ EXPECT_CALL(*dns_client_, address())
+ .WillOnce(ReturnRef(addr));;
+ GetDNSResult(true);
+ }
+ 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());
+ }
+
+ private:
+ // 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_;
+ HTTPProxy proxy_;
+ StrictMock<MockSockets> sockets_;
+};
+
+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, ReadBadFirstLine) {
+ SetupClient();
+ ExpectClientResult();
+ ReadFromClient(kBadHeader);
+ 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, DNSRequestFailure) {
+ SetupClient();
+ 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();
+ ExpectDNSRequest("www.chromium.org", true);
+ ReadFromClient(CreateRequest("/", "1.1", "Host: www.chromium.org:40506"));
+ ExpectClientResult();
+ const std::string not_found_error(DNSClient::kErrorNotFound);
+ ExpectDNSFailure(not_found_error);
+ GetDNSResult(false);
+ ExpectClientError(502, string("Could not resolve hostname: ") +
+ not_found_error);
+}
+
+TEST_F(HTTPProxyTest, TrailingClientData) {
+ SetupClient();
+ 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();
+ 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();
+ 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();
+ ReadFromClient(CreateRequest("/", "1.1",
+ StringPrintf("Host: %s:999", kServerAddress)));
+ EXPECT_EQ(HTTPProxy::kStateTunnelData, GetProxyState());
+}
+
+TEST_F(HTTPProxyTest, ConnectAsyncConnectSuccess) {
+ SetupConnectComplete();
+}
+
+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, 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));
+ StopClient();
+ ExpectClientReset();
+ EXPECT_EQ(HTTPProxy::kStateWaitConnection, GetProxyState());
+}
+
+} // namespace shill