summaryrefslogtreecommitdiffstats
path: root/net/http/http_proxy_client_socket.cc
diff options
context:
space:
mode:
authorvandebo@chromium.org <vandebo@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-07-12 17:54:54 +0000
committervandebo@chromium.org <vandebo@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-07-12 17:54:54 +0000
commita7ea883a61fc3c41addab9a6d6aa3f9f0e95b994 (patch)
tree06ef27350a861cdf70f049d03600e3af64fbe949 /net/http/http_proxy_client_socket.cc
parent3d2f01d1cbe644706fa04a92f0bd815b3b868d4d (diff)
downloadchromium_src-a7ea883a61fc3c41addab9a6d6aa3f9f0e95b994.zip
chromium_src-a7ea883a61fc3c41addab9a6d6aa3f9f0e95b994.tar.gz
chromium_src-a7ea883a61fc3c41addab9a6d6aa3f9f0e95b994.tar.bz2
Implement HttpProxyClientSocket: Http proxie setup is now done in it's own class (refactor).
BUG=42795 TEST=existing unit tests Review URL: http://codereview.chromium.org/2799036 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@52100 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net/http/http_proxy_client_socket.cc')
-rw-r--r--net/http/http_proxy_client_socket.cc421
1 files changed, 421 insertions, 0 deletions
diff --git a/net/http/http_proxy_client_socket.cc b/net/http/http_proxy_client_socket.cc
new file mode 100644
index 0000000..cdfa183
--- /dev/null
+++ b/net/http/http_proxy_client_socket.cc
@@ -0,0 +1,421 @@
+// Copyright (c) 2010 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_proxy_client_socket.h"
+
+#include "base/string_util.h"
+#include "googleurl/src/gurl.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_log.h"
+#include "net/base/net_util.h"
+#include "net/http/http_basic_stream.h"
+#include "net/http/http_net_log_params.h"
+#include "net/http/http_network_session.h"
+#include "net/http/http_request_info.h"
+#include "net/socket/client_socket_handle.h"
+
+namespace net {
+
+namespace {
+
+// The HTTP CONNECT method for establishing a tunnel connection is documented
+// in draft-luotonen-web-proxy-tunneling-01.txt and RFC 2817, Sections 5.2 and
+// 5.3.
+void BuildTunnelRequest(const HttpRequestInfo* request_info,
+ const HttpRequestHeaders& authorization_headers,
+ const HostPortPair& endpoint,
+ std::string* request_line,
+ HttpRequestHeaders* request_headers) {
+ // RFC 2616 Section 9 says the Host request-header field MUST accompany all
+ // HTTP/1.1 requests. Add "Proxy-Connection: keep-alive" for compat with
+ // HTTP/1.0 proxies such as Squid (required for NTLM authentication).
+ *request_line = StringPrintf(
+ "CONNECT %s HTTP/1.1\r\n", endpoint.ToString().c_str());
+ request_headers->SetHeader(HttpRequestHeaders::kHost,
+ GetHostAndOptionalPort(request_info->url));
+ request_headers->SetHeader(HttpRequestHeaders::kProxyConnection,
+ "keep-alive");
+
+ std::string user_agent;
+ if (request_info->extra_headers.GetHeader(HttpRequestHeaders::kUserAgent,
+ &user_agent))
+ request_headers->SetHeader(HttpRequestHeaders::kUserAgent, user_agent);
+
+ request_headers->MergeFrom(authorization_headers);
+}
+
+} // namespace
+
+HttpProxyClientSocket::HttpProxyClientSocket(
+ ClientSocketHandle* transport_socket, const GURL& request_url,
+ const HostPortPair& endpoint, HttpAuthController* auth, bool tunnel)
+ : ALLOW_THIS_IN_INITIALIZER_LIST(
+ io_callback_(this, &HttpProxyClientSocket::OnIOComplete)),
+ next_state_(STATE_NONE),
+ user_callback_(NULL),
+ transport_(transport_socket),
+ tunnel_(tunnel),
+ auth_(auth),
+ endpoint_(endpoint),
+ net_log_(transport_socket->socket()->NetLog()) {
+ DCHECK_EQ(tunnel, auth != NULL);
+ if (tunnel)
+ auth->set_net_log(net_log_);
+ // Synthesize the bits of a request that we actually use.
+ request_.url = request_url;
+ request_.method = "GET";
+}
+
+HttpProxyClientSocket::~HttpProxyClientSocket() {
+ Disconnect();
+}
+
+int HttpProxyClientSocket::Connect(CompletionCallback* callback) {
+ DCHECK(transport_.get());
+ DCHECK(transport_->socket());
+ DCHECK(transport_->socket()->IsConnected());
+ DCHECK(!user_callback_);
+
+ if (!tunnel_)
+ next_state_ = STATE_DONE;
+ if (next_state_ == STATE_DONE)
+ return OK;
+
+ DCHECK_EQ(STATE_NONE, next_state_);
+ next_state_ = STATE_GENERATE_AUTH_TOKEN;
+
+ int rv = DoLoop(OK);
+ if (rv == ERR_IO_PENDING)
+ user_callback_ = callback;
+ return rv;
+}
+
+int HttpProxyClientSocket::RestartWithAuth(const std::wstring& username,
+ const std::wstring& password,
+ CompletionCallback* callback) {
+ DCHECK_EQ(STATE_NONE, next_state_);
+ DCHECK(!user_callback_);
+
+ auth_->ResetAuth(username, password);
+
+ int rv = PrepareForAuthRestart();
+ if (rv != OK)
+ return rv;
+
+ rv = DoLoop(OK);
+ if (rv == ERR_IO_PENDING)
+ user_callback_ = callback;
+ return rv;
+}
+
+int HttpProxyClientSocket::PrepareForAuthRestart() {
+ bool keep_alive = false;
+ if (response_.headers->IsKeepAlive() &&
+ http_stream_->CanFindEndOfResponse()) {
+ if (!http_stream_->IsResponseBodyComplete()) {
+ next_state_ = STATE_DRAIN_BODY;
+ drain_buf_ = new IOBuffer(kDrainBodyBufferSize);
+ return OK;
+ }
+ keep_alive = true;
+ }
+
+ // We don't need to drain the response body, so we act as if we had drained
+ // the response body.
+ return DidDrainBodyForAuthRestart(keep_alive);
+}
+
+int HttpProxyClientSocket::DidDrainBodyForAuthRestart(bool keep_alive) {
+ if (keep_alive && transport_->socket()->IsConnectedAndIdle()) {
+ next_state_ = STATE_GENERATE_AUTH_TOKEN;
+ transport_->set_is_reused(true);
+ } else {
+ transport_->socket()->Disconnect();
+ return ERR_RETRY_CONNECTION;
+ }
+
+ // Reset the other member variables.
+ drain_buf_ = NULL;
+ http_stream_.reset();
+ request_headers_.clear();
+ response_ = HttpResponseInfo();
+ return OK;
+}
+
+void HttpProxyClientSocket::LogBlockedTunnelResponse(int response_code) const {
+ LOG(WARNING) << "Blocked proxy response with status " << response_code
+ << " to CONNECT request for "
+ << GetHostAndPort(request_.url) << ".";
+}
+
+void HttpProxyClientSocket::Disconnect() {
+ transport_->socket()->Disconnect();
+
+ // Reset other states to make sure they aren't mistakenly used later.
+ // These are the states initialized by Connect().
+ next_state_ = STATE_NONE;
+ user_callback_ = NULL;
+}
+
+bool HttpProxyClientSocket::IsConnected() const {
+ return next_state_ == STATE_DONE && transport_->socket()->IsConnected();
+}
+
+bool HttpProxyClientSocket::IsConnectedAndIdle() const {
+ return next_state_ == STATE_DONE
+ && transport_->socket()->IsConnectedAndIdle();
+}
+
+int HttpProxyClientSocket::Read(IOBuffer* buf, int buf_len,
+ CompletionCallback* callback) {
+ DCHECK(!user_callback_);
+ if (next_state_ != STATE_DONE) {
+ // We're trying to read the body of the response but we're still trying
+ // to establish an SSL tunnel through the proxy. We can't read these
+ // bytes when establishing a tunnel because they might be controlled by
+ // an active network attacker. We don't worry about this for HTTP
+ // because an active network attacker can already control HTTP sessions.
+ // We reach this case when the user cancels a 407 proxy auth prompt.
+ // See http://crbug.com/8473.
+ DCHECK_EQ(407, response_.headers->response_code());
+ LogBlockedTunnelResponse(response_.headers->response_code());
+
+ return ERR_TUNNEL_CONNECTION_FAILED;
+ }
+
+ return transport_->socket()->Read(buf, buf_len, callback);
+}
+
+int HttpProxyClientSocket::Write(IOBuffer* buf, int buf_len,
+ CompletionCallback* callback) {
+ DCHECK_EQ(STATE_DONE, next_state_);
+ DCHECK(!user_callback_);
+
+ return transport_->socket()->Write(buf, buf_len, callback);
+}
+
+bool HttpProxyClientSocket::SetReceiveBufferSize(int32 size) {
+ return transport_->socket()->SetReceiveBufferSize(size);
+}
+
+bool HttpProxyClientSocket::SetSendBufferSize(int32 size) {
+ return transport_->socket()->SetSendBufferSize(size);
+}
+
+int HttpProxyClientSocket::GetPeerAddress(AddressList* address) const {
+ return transport_->socket()->GetPeerAddress(address);
+}
+
+void HttpProxyClientSocket::DoCallback(int result) {
+ DCHECK_NE(ERR_IO_PENDING, result);
+ DCHECK(user_callback_);
+
+ // Since Run() may result in Read being called,
+ // clear user_callback_ up front.
+ CompletionCallback* c = user_callback_;
+ user_callback_ = NULL;
+ c->Run(result);
+}
+
+void HttpProxyClientSocket::OnIOComplete(int result) {
+ DCHECK_NE(STATE_NONE, next_state_);
+ DCHECK_NE(STATE_DONE, next_state_);
+ int rv = DoLoop(result);
+ if (rv != ERR_IO_PENDING)
+ DoCallback(rv);
+}
+
+int HttpProxyClientSocket::DoLoop(int last_io_result) {
+ DCHECK_NE(next_state_, STATE_NONE);
+ DCHECK_NE(next_state_, STATE_DONE);
+ int rv = last_io_result;
+ do {
+ State state = next_state_;
+ next_state_ = STATE_NONE;
+ switch (state) {
+ case STATE_GENERATE_AUTH_TOKEN:
+ DCHECK_EQ(OK, rv);
+ rv = DoGenerateAuthToken();
+ break;
+ case STATE_GENERATE_AUTH_TOKEN_COMPLETE:
+ rv = DoGenerateAuthTokenComplete(rv);
+ break;
+ case STATE_SEND_REQUEST:
+ DCHECK_EQ(OK, rv);
+ net_log_.BeginEvent(NetLog::TYPE_HTTP_TRANSACTION_TUNNEL_SEND_REQUEST,
+ NULL);
+ rv = DoSendRequest();
+ break;
+ case STATE_SEND_REQUEST_COMPLETE:
+ rv = DoSendRequestComplete(rv);
+ net_log_.EndEvent(NetLog::TYPE_HTTP_TRANSACTION_TUNNEL_SEND_REQUEST,
+ NULL);
+ break;
+ case STATE_READ_HEADERS:
+ DCHECK_EQ(OK, rv);
+ net_log_.BeginEvent(NetLog::TYPE_HTTP_TRANSACTION_TUNNEL_READ_HEADERS,
+ NULL);
+ rv = DoReadHeaders();
+ break;
+ case STATE_READ_HEADERS_COMPLETE:
+ rv = DoReadHeadersComplete(rv);
+ net_log_.EndEvent(NetLog::TYPE_HTTP_TRANSACTION_TUNNEL_READ_HEADERS,
+ NULL);
+ break;
+ case STATE_DRAIN_BODY:
+ DCHECK_EQ(OK, rv);
+ rv = DoDrainBody();
+ break;
+ case STATE_DRAIN_BODY_COMPLETE:
+ rv = DoDrainBodyComplete(rv);
+ break;
+ case STATE_DONE:
+ break;
+ default:
+ NOTREACHED() << "bad state";
+ rv = ERR_UNEXPECTED;
+ break;
+ }
+ } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE
+ && next_state_ != STATE_DONE);
+ return rv;
+}
+
+int HttpProxyClientSocket::DoGenerateAuthToken() {
+ next_state_ = STATE_GENERATE_AUTH_TOKEN_COMPLETE;
+ return auth_->MaybeGenerateAuthToken(&request_, &io_callback_);
+}
+
+int HttpProxyClientSocket::DoGenerateAuthTokenComplete(int result) {
+ DCHECK_NE(ERR_IO_PENDING, result);
+ if (result == OK)
+ next_state_ = STATE_SEND_REQUEST;
+ return result;
+}
+
+int HttpProxyClientSocket::DoSendRequest() {
+ next_state_ = STATE_SEND_REQUEST_COMPLETE;
+
+ // This is constructed lazily (instead of within our Start method), so that
+ // we have proxy info available.
+ if (request_headers_.empty()) {
+ HttpRequestHeaders authorization_headers;
+ if (auth_->HaveAuth())
+ auth_->AddAuthorizationHeader(&authorization_headers);
+ std::string request_line;
+ HttpRequestHeaders request_headers;
+ BuildTunnelRequest(&request_, authorization_headers, endpoint_,
+ &request_line, &request_headers);
+ if (net_log_.HasListener()) {
+ net_log_.AddEvent(
+ NetLog::TYPE_HTTP_TRANSACTION_SEND_TUNNEL_HEADERS,
+ new NetLogHttpRequestParameter(
+ request_line, request_headers));
+ }
+ request_headers_ = request_line + request_headers.ToString();
+ }
+
+ http_stream_.reset(new HttpBasicStream(transport_.get(), net_log_));
+
+ return http_stream_->SendRequest(&request_, request_headers_, NULL,
+ &response_, &io_callback_);
+}
+
+int HttpProxyClientSocket::DoSendRequestComplete(int result) {
+ if (result < 0)
+ return result;
+
+ next_state_ = STATE_READ_HEADERS;
+ return OK;
+}
+
+int HttpProxyClientSocket::DoReadHeaders() {
+ next_state_ = STATE_READ_HEADERS_COMPLETE;
+ return http_stream_->ReadResponseHeaders(&io_callback_);
+}
+
+int HttpProxyClientSocket::DoReadHeadersComplete(int result) {
+ if (result < 0) {
+ if (result == ERR_CONNECTION_CLOSED)
+ result = ERR_TUNNEL_CONNECTION_FAILED;
+ return result;
+ }
+
+ // Require the "HTTP/1.x" status line for SSL CONNECT.
+ if (response_.headers->GetParsedHttpVersion() < HttpVersion(1, 0))
+ return ERR_TUNNEL_CONNECTION_FAILED;
+
+ if (net_log_.HasListener()) {
+ net_log_.AddEvent(
+ NetLog::TYPE_HTTP_TRANSACTION_READ_TUNNEL_RESPONSE_HEADERS,
+ new NetLogHttpResponseParameter(response_.headers));
+ }
+
+ switch (response_.headers->response_code()) {
+ case 200: // OK
+ if (http_stream_->IsMoreDataBuffered())
+ // The proxy sent extraneous data after the headers.
+ return ERR_TUNNEL_CONNECTION_FAILED;
+
+ next_state_ = STATE_DONE;
+ return OK;
+
+ // We aren't able to CONNECT to the remote host through the proxy. We
+ // need to be very suspicious about the response because an active network
+ // attacker can force us into this state by masquerading as the proxy.
+ // The only safe thing to do here is to fail the connection because our
+ // client is expecting an SSL protected response.
+ // See http://crbug.com/7338.
+ case 407: // Proxy Authentication Required
+ // We need this status code to allow proxy authentication. Our
+ // authentication code is smart enough to avoid being tricked by an
+ // active network attacker.
+ // The next state is intentionally not set as it should be STATE_NONE;
+ return HandleAuthChallenge();
+
+ default:
+ // For all other status codes, we conservatively fail the CONNECT
+ // request.
+ // We lose something by doing this. We have seen proxy 403, 404, and
+ // 501 response bodies that contain a useful error message. For
+ // example, Squid uses a 404 response to report the DNS error: "The
+ // domain name does not exist."
+ LogBlockedTunnelResponse(response_.headers->response_code());
+ return ERR_TUNNEL_CONNECTION_FAILED;
+ }
+}
+
+int HttpProxyClientSocket::DoDrainBody() {
+ DCHECK(drain_buf_);
+ DCHECK(transport_->is_initialized());
+ next_state_ = STATE_DRAIN_BODY_COMPLETE;
+ return http_stream_->ReadResponseBody(drain_buf_, kDrainBodyBufferSize,
+ &io_callback_);
+}
+
+int HttpProxyClientSocket::DoDrainBodyComplete(int result) {
+ if (result < 0)
+ return result;
+
+ if (http_stream_->IsResponseBodyComplete())
+ return DidDrainBodyForAuthRestart(true);
+
+ // Keep draining.
+ next_state_ = STATE_DRAIN_BODY;
+ return OK;
+}
+
+int HttpProxyClientSocket::HandleAuthChallenge() {
+ DCHECK(response_.headers);
+
+ int rv = auth_->HandleAuthChallenge(response_.headers, false, true);
+ response_.auth_challenge = auth_->auth_info();
+ if (rv == OK)
+ return ERR_PROXY_AUTH_REQUESTED;
+
+ return rv;
+}
+
+} // namespace net