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