diff options
author | vandebo@chromium.org <vandebo@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-07-12 17:54:54 +0000 |
---|---|---|
committer | vandebo@chromium.org <vandebo@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-07-12 17:54:54 +0000 |
commit | a7ea883a61fc3c41addab9a6d6aa3f9f0e95b994 (patch) | |
tree | 06ef27350a861cdf70f049d03600e3af64fbe949 /net/http | |
parent | 3d2f01d1cbe644706fa04a92f0bd815b3b868d4d (diff) | |
download | chromium_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')
-rw-r--r-- | net/http/http_auth_controller.cc | 7 | ||||
-rw-r--r-- | net/http/http_auth_controller.h | 7 | ||||
-rw-r--r-- | net/http/http_net_log_params.h | 84 | ||||
-rw-r--r-- | net/http/http_network_transaction.cc | 338 | ||||
-rw-r--r-- | net/http/http_network_transaction.h | 29 | ||||
-rw-r--r-- | net/http/http_network_transaction_unittest.cc | 14 | ||||
-rw-r--r-- | net/http/http_proxy_client_socket.cc | 421 | ||||
-rw-r--r-- | net/http/http_proxy_client_socket.h | 146 |
8 files changed, 766 insertions, 280 deletions
diff --git a/net/http/http_auth_controller.cc b/net/http/http_auth_controller.cc index a40b5d9..cb899cb 100644 --- a/net/http/http_auth_controller.cc +++ b/net/http/http_auth_controller.cc @@ -126,7 +126,7 @@ void HttpAuthController::AddAuthorizationHeader( int HttpAuthController::HandleAuthChallenge( scoped_refptr<HttpResponseHeaders> headers, - int load_flags, + bool do_not_send_server_auth, bool establishing_tunnel) { DCHECK(headers); DCHECK(auth_origin_.is_valid()); @@ -150,8 +150,7 @@ int HttpAuthController::HandleAuthChallenge( identity_.invalid = true; - if (target_ != HttpAuth::AUTH_SERVER || - !(load_flags & LOAD_DO_NOT_SEND_AUTH_DATA)) { + if (target_ != HttpAuth::AUTH_SERVER || !do_not_send_server_auth) { // Find the best authentication challenge that we support. HttpAuth::ChooseBestChallenge(session_->http_auth_handler_factory(), headers, target_, auth_origin_, net_log_, @@ -169,7 +168,7 @@ int HttpAuthController::HandleAuthChallenge( // active network attacker could control its contents. Instead, we just // fail to establish the tunnel. DCHECK(target_ == HttpAuth::AUTH_PROXY); - return ERR_PROXY_AUTH_REQUESTED; + return ERR_PROXY_AUTH_UNSUPPORTED; } // We found no supported challenge -- let the transaction continue // so we end up displaying the error page. diff --git a/net/http/http_auth_controller.h b/net/http/http_auth_controller.h index 6a74dea..016f88e 100644 --- a/net/http/http_auth_controller.h +++ b/net/http/http_auth_controller.h @@ -46,7 +46,8 @@ class HttpAuthController { // |HandleAuthChallenge()| returns OK on success, or a network error code // otherwise. It may also populate |auth_info_|. int HandleAuthChallenge(scoped_refptr<HttpResponseHeaders> headers, - int load_flags, bool establishing_tunnel); + bool do_not_send_server_auth, + bool establishing_tunnel); // Store the supplied credentials and prepare to restart the auth. void ResetAuth(const std::wstring& username, const std::wstring& password); @@ -63,6 +64,10 @@ class HttpAuthController { return auth_info_; } + void set_net_log(const BoundNetLog& net_log) { + net_log_ = net_log; + } + private: // Searches the auth cache for an entry that encompasses the request's path. // If such an entry is found, updates |identity_| and |handler_| with the diff --git a/net/http/http_net_log_params.h b/net/http/http_net_log_params.h new file mode 100644 index 0000000..563c799 --- /dev/null +++ b/net/http/http_net_log_params.h @@ -0,0 +1,84 @@ +// 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. + +#ifndef NET_HTTP_HTTP_NET_LOG_PARAMS_H_ +#define NET_HTTP_HTTP_NET_LOG_PARAMS_H_ + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/ref_counted.h" +#include "base/values.h" +#include "net/base/net_log.h" +#include "net/http/http_request_headers.h" +#include "net/http/http_response_headers.h" + +namespace net { + +class NetLogHttpRequestParameter : public NetLog::EventParameters { + public: + NetLogHttpRequestParameter(const std::string& line, + const HttpRequestHeaders& headers) + : line_(line) { + headers_.CopyFrom(headers); + } + + Value* ToValue() const { + DictionaryValue* dict = new DictionaryValue(); + dict->SetString(L"line", line_); + ListValue* headers = new ListValue(); + HttpRequestHeaders::Iterator iterator(headers_); + while (iterator.GetNext()) { + headers->Append( + new StringValue(StringPrintf("%s: %s", + iterator.name().c_str(), + iterator.value().c_str()))); + } + dict->Set(L"headers", headers); + return dict; + } + + private: + ~NetLogHttpRequestParameter() {} + + const std::string line_; + HttpRequestHeaders headers_; + + DISALLOW_COPY_AND_ASSIGN(NetLogHttpRequestParameter); +}; + +class NetLogHttpResponseParameter : public NetLog::EventParameters { + public: + explicit NetLogHttpResponseParameter( + const scoped_refptr<HttpResponseHeaders>& headers) + : headers_(headers) {} + + Value* ToValue() const { + DictionaryValue* dict = new DictionaryValue(); + ListValue* headers = new ListValue(); + headers->Append(new StringValue(headers_->GetStatusLine())); + void* iterator = NULL; + std::string name; + std::string value; + while (headers_->EnumerateHeaderLines(&iterator, &name, &value)) { + headers->Append( + new StringValue(StringPrintf("%s: %s", name.c_str(), value.c_str()))); + } + dict->Set(L"headers", headers); + return dict; + } + + private: + ~NetLogHttpResponseParameter() {} + + const scoped_refptr<HttpResponseHeaders> headers_; + + DISALLOW_COPY_AND_ASSIGN(NetLogHttpResponseParameter); +}; + +} // namespace net + +#endif // NET_HTTP_HTTP_NET_LOG_PARAMS_H_ + diff --git a/net/http/http_network_transaction.cc b/net/http/http_network_transaction.cc index 1d58863..c0fe07b 100644 --- a/net/http/http_network_transaction.cc +++ b/net/http/http_network_transaction.cc @@ -12,7 +12,6 @@ #include "base/stats_counters.h" #include "base/stl_util-inl.h" #include "base/string_util.h" -#include "base/values.h" #include "build/build_config.h" #include "googleurl/src/gurl.h" #include "net/base/connection_type_histograms.h" @@ -29,7 +28,9 @@ #include "net/http/http_auth_handler_factory.h" #include "net/http/http_basic_stream.h" #include "net/http/http_chunked_decoder.h" +#include "net/http/http_net_log_params.h" #include "net/http/http_network_session.h" +#include "net/http/http_proxy_client_socket.h" #include "net/http/http_request_headers.h" #include "net/http/http_request_info.h" #include "net/http/http_response_headers.h" @@ -127,32 +128,6 @@ void BuildRequestHeaders(const HttpRequestInfo* request_info, request_headers->MergeFrom(stripped_extra_headers); } -// 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); -} - void ProcessAlternateProtocol(const HttpResponseHeaders& headers, const HostPortPair& http_host_port_pair, HttpAlternateProtocols* alternate_protocols) { @@ -208,67 +183,6 @@ void ProcessAlternateProtocol(const HttpResponseHeaders& headers, host_port, port, HttpAlternateProtocols::NPN_SPDY_1); } -class NetLogHttpRequestParameter : public NetLog::EventParameters { - public: - NetLogHttpRequestParameter(const std::string& line, - const HttpRequestHeaders& headers) - : line_(line) { - headers_.CopyFrom(headers); - } - - Value* ToValue() const { - DictionaryValue* dict = new DictionaryValue(); - dict->SetString(L"line", line_); - ListValue* headers = new ListValue(); - HttpRequestHeaders::Iterator iterator(headers_); - while (iterator.GetNext()) { - headers->Append( - new StringValue(StringPrintf("%s: %s", - iterator.name().c_str(), - iterator.value().c_str()))); - } - dict->Set(L"headers", headers); - return dict; - } - - private: - ~NetLogHttpRequestParameter() {} - - const std::string line_; - HttpRequestHeaders headers_; - - DISALLOW_COPY_AND_ASSIGN(NetLogHttpRequestParameter); -}; - -class NetLogHttpResponseParameter : public NetLog::EventParameters { - public: - explicit NetLogHttpResponseParameter( - const scoped_refptr<HttpResponseHeaders>& headers) - : headers_(headers) {} - - Value* ToValue() const { - DictionaryValue* dict = new DictionaryValue(); - ListValue* headers = new ListValue(); - headers->Append(new StringValue(headers_->GetStatusLine())); - void* iterator = NULL; - std::string name; - std::string value; - while (headers_->EnumerateHeaderLines(&iterator, &name, &value)) { - headers->Append( - new StringValue(StringPrintf("%s: %s", name.c_str(), value.c_str()))); - } - dict->Set(L"headers", headers); - return dict; - } - - private: - ~NetLogHttpResponseParameter() {} - - const scoped_refptr<HttpResponseHeaders> headers_; - - DISALLOW_COPY_AND_ASSIGN(NetLogHttpResponseParameter); -}; - } // namespace //----------------------------------------------------------------------------- @@ -294,7 +208,8 @@ HttpNetworkTransaction::HttpNetworkTransaction(HttpNetworkSession* session) g_use_alternate_protocols ? kUnspecified : kDoNotUseAlternateProtocol), read_buf_len_(0), - next_state_(STATE_NONE) { + next_state_(STATE_NONE), + establishing_tunnel_(false) { session->ssl_config_service()->GetSSLConfig(&ssl_config_); if (g_next_protos) ssl_config_.next_protos = *g_next_protos; @@ -394,9 +309,17 @@ int HttpNetworkTransaction::RestartWithAuth( } pending_auth_target_ = HttpAuth::AUTH_NONE; - auth_controllers_[target]->ResetAuth(username, password); - - PrepareForAuthRestart(target); + if (target == HttpAuth::AUTH_PROXY && using_ssl_ && proxy_info_.is_http()) { + DCHECK(establishing_tunnel_); + ResetStateForRestart(); + tunnel_credentials_.username = username; + tunnel_credentials_.password = password; + tunnel_credentials_.invalid = false; + next_state_ = STATE_TUNNEL_RESTART_WITH_AUTH; + } else { + auth_controllers_[target]->ResetAuth(username, password); + PrepareForAuthRestart(target); + } DCHECK(user_callback_ == NULL); int rv = DoLoop(OK); @@ -408,6 +331,7 @@ int HttpNetworkTransaction::RestartWithAuth( void HttpNetworkTransaction::PrepareForAuthRestart(HttpAuth::Target target) { DCHECK(HaveAuth(target)); + DCHECK(!establishing_tunnel_); bool keep_alive = false; // Even if the server says the connection is keep-alive, we have to be // able to find the end of each response in order to reuse the connection. @@ -430,14 +354,11 @@ void HttpNetworkTransaction::PrepareForAuthRestart(HttpAuth::Target target) { } void HttpNetworkTransaction::DidDrainBodyForAuthRestart(bool keep_alive) { + DCHECK(!establishing_tunnel_); if (keep_alive && connection_->socket()->IsConnectedAndIdle()) { // We should call connection_->set_idle_time(), but this doesn't occur // often enough to be worth the trouble. - if (using_ssl_ && proxy_info_.is_http() && - ssl_connect_start_time_.is_null()) - next_state_ = STATE_TUNNEL_GENERATE_AUTH_TOKEN; - else - next_state_ = STATE_GENERATE_PROXY_AUTH_TOKEN; + next_state_ = STATE_GENERATE_PROXY_AUTH_TOKEN; connection_->set_is_reused(true); reused_socket_ = true; } else { @@ -472,7 +393,7 @@ int HttpNetworkTransaction::Read(IOBuffer* buf, int buf_len, scoped_refptr<HttpResponseHeaders> headers = GetResponseHeaders(); DCHECK(headers.get()); - if (headers->response_code() == 407) { + if (establishing_tunnel_) { // 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 @@ -481,7 +402,10 @@ int HttpNetworkTransaction::Read(IOBuffer* buf, int buf_len, // We reach this case when the user cancels a 407 proxy auth prompt. // See http://crbug.com/8473. DCHECK(proxy_info_.is_http()); - LogBlockedTunnelResponse(headers->response_code()); + DCHECK_EQ(headers->response_code(), 407); + LOG(WARNING) << "Blocked proxy response with status " + << headers->response_code() << " to CONNECT request for " + << GetHostAndPort(request_->url) << "."; return ERR_TUNNEL_CONNECTION_FAILED; } @@ -508,9 +432,7 @@ LoadState HttpNetworkTransaction::GetLoadState() const { return LOAD_STATE_RESOLVING_PROXY_FOR_URL; case STATE_INIT_CONNECTION_COMPLETE: return connection_->GetLoadState(); - case STATE_TUNNEL_GENERATE_AUTH_TOKEN_COMPLETE: - case STATE_TUNNEL_SEND_REQUEST_COMPLETE: - case STATE_TUNNEL_READ_HEADERS_COMPLETE: + case STATE_TUNNEL_CONNECT_COMPLETE: return LOAD_STATE_ESTABLISHING_PROXY_TUNNEL; case STATE_SSL_CONNECT_COMPLETE: return LOAD_STATE_SSL_HANDSHAKE; @@ -598,34 +520,16 @@ int HttpNetworkTransaction::DoLoop(int result) { case STATE_INIT_CONNECTION_COMPLETE: rv = DoInitConnectionComplete(rv); break; - case STATE_TUNNEL_GENERATE_AUTH_TOKEN: - DCHECK_EQ(OK, rv); - rv = DoTunnelGenerateAuthToken(); - break; - case STATE_TUNNEL_GENERATE_AUTH_TOKEN_COMPLETE: - rv = DoTunnelGenerateAuthTokenComplete(rv); - break; - case STATE_TUNNEL_SEND_REQUEST: + case STATE_TUNNEL_CONNECT: DCHECK_EQ(OK, rv); - net_log_.BeginEvent(NetLog::TYPE_HTTP_TRANSACTION_TUNNEL_SEND_REQUEST, - NULL); - rv = DoTunnelSendRequest(); + rv = DoTunnelConnect(); break; - case STATE_TUNNEL_SEND_REQUEST_COMPLETE: - rv = DoTunnelSendRequestComplete(rv); - net_log_.EndEvent(NetLog::TYPE_HTTP_TRANSACTION_TUNNEL_SEND_REQUEST, - NULL); + case STATE_TUNNEL_CONNECT_COMPLETE: + rv = DoTunnelConnectComplete(rv); break; - case STATE_TUNNEL_READ_HEADERS: + case STATE_TUNNEL_RESTART_WITH_AUTH: DCHECK_EQ(OK, rv); - net_log_.BeginEvent(NetLog::TYPE_HTTP_TRANSACTION_TUNNEL_READ_HEADERS, - NULL); - rv = DoTunnelReadHeaders(); - break; - case STATE_TUNNEL_READ_HEADERS_COMPLETE: - rv = DoTunnelReadHeadersComplete(rv); - net_log_.EndEvent(NetLog::TYPE_HTTP_TRANSACTION_TUNNEL_READ_HEADERS, - NULL); + rv = DoTunnelRestartWithAuth(); break; case STATE_SSL_CONNECT: DCHECK_EQ(OK, rv); @@ -948,7 +852,7 @@ int HttpNetworkTransaction::DoInitConnectionComplete(int result) { if (proxy_info_.is_direct() || proxy_info_.is_socks()) next_state_ = STATE_SSL_CONNECT; else - next_state_ = STATE_TUNNEL_GENERATE_AUTH_TOKEN; + next_state_ = STATE_TUNNEL_CONNECT; } else { next_state_ = STATE_GENERATE_PROXY_AUTH_TOKEN; } @@ -957,126 +861,61 @@ int HttpNetworkTransaction::DoInitConnectionComplete(int result) { return OK; } -void HttpNetworkTransaction::ClearTunnelState() { - http_stream_.reset(); - request_headers_.clear(); - response_ = HttpResponseInfo(); - headers_valid_ = false; -} - -int HttpNetworkTransaction::DoTunnelGenerateAuthToken() { - next_state_ = STATE_TUNNEL_GENERATE_AUTH_TOKEN_COMPLETE; - return auth_controllers_[HttpAuth::AUTH_PROXY]->MaybeGenerateAuthToken( - request_, &io_callback_); -} +int HttpNetworkTransaction::DoTunnelConnect() { + next_state_ = STATE_TUNNEL_CONNECT_COMPLETE; + establishing_tunnel_ = true; -int HttpNetworkTransaction::DoTunnelGenerateAuthTokenComplete(int rv) { - DCHECK_NE(ERR_IO_PENDING, rv); - if (rv == OK) - next_state_ = STATE_TUNNEL_SEND_REQUEST; - return rv; -} - -int HttpNetworkTransaction::DoTunnelSendRequest() { - next_state_ = STATE_TUNNEL_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 (HaveAuth(HttpAuth::AUTH_PROXY)) - auth_controllers_[HttpAuth::AUTH_PROXY]->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(connection_.get(), net_log_)); - - return http_stream_->SendRequest(request_, request_headers_, NULL, &response_, - &io_callback_); -} - -int HttpNetworkTransaction::DoTunnelSendRequestComplete(int result) { - if (result < 0) - return result; - - next_state_ = STATE_TUNNEL_READ_HEADERS; - return OK; -} - -int HttpNetworkTransaction::DoTunnelReadHeaders() { - next_state_ = STATE_TUNNEL_READ_HEADERS_COMPLETE; - - return http_stream_->ReadResponseHeaders(&io_callback_); + // Add a tunnel socket on top of our existing transport socket. + ClientSocket* socket = connection_->release_socket(); + ClientSocketHandle* transport_socket_handle = new ClientSocketHandle(); + transport_socket_handle->set_socket(socket); + socket = new HttpProxyClientSocket(transport_socket_handle, request_->url, + endpoint_, auth_controllers_[HttpAuth::AUTH_PROXY].release(), true); + connection_->set_socket(socket); + return connection_->socket()->Connect(&io_callback_); } -int HttpNetworkTransaction::DoTunnelReadHeadersComplete(int result) { - if (result < 0) { - if (result == ERR_CONNECTION_CLOSED) - result = ERR_TUNNEL_CONNECTION_FAILED; - ClearTunnelState(); - return result; +int HttpNetworkTransaction::DoTunnelConnectComplete(int result) { + if (result == OK) { + next_state_ = STATE_SSL_CONNECT; + establishing_tunnel_ = false; } - // Require the "HTTP/1.x" status line for SSL CONNECT. - if (response_.headers->GetParsedHttpVersion() < HttpVersion(1, 0)) { - ClearTunnelState(); - return ERR_TUNNEL_CONNECTION_FAILED; - } + if (result == ERR_RETRY_CONNECTION) { + HttpProxyClientSocket* tunnel_socket = + reinterpret_cast<HttpProxyClientSocket*>(connection_->socket()); + auth_controllers_[HttpAuth::AUTH_PROXY].reset( + tunnel_socket->TakeAuthController()); + next_state_ = STATE_INIT_CONNECTION; + connection_->socket()->Disconnect(); + connection_->Reset(); + result = OK; + } else if (result == ERR_PROXY_AUTH_REQUESTED) { + HttpProxyClientSocket* tunnel_socket = + reinterpret_cast<HttpProxyClientSocket*>(connection_->socket()); + const HttpResponseInfo* auth_response = tunnel_socket->GetResponseInfo(); - if (net_log_.HasListener()) { - net_log_.AddEvent( - NetLog::TYPE_HTTP_TRANSACTION_READ_TUNNEL_RESPONSE_HEADERS, - new NetLogHttpResponseParameter(response_.headers)); + response_.headers = auth_response->headers; + headers_valid_ = true; + response_.auth_challenge = auth_response->auth_challenge; + pending_auth_target_ = HttpAuth::AUTH_PROXY; + result = OK; } + return result; +} - int rv = result; - switch (response_.headers->response_code()) { - case 200: // OK - if (http_stream_->IsMoreDataBuffered()) { - // The proxy sent extraneous data after the headers. - rv = ERR_TUNNEL_CONNECTION_FAILED; - } else { - next_state_ = STATE_SSL_CONNECT; - rv = OK; - } - break; +int HttpNetworkTransaction::DoTunnelRestartWithAuth() { + DCHECK(establishing_tunnel_); + DCHECK(!tunnel_credentials_.invalid); + next_state_ = STATE_TUNNEL_CONNECT_COMPLETE; - // 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. - headers_valid_ = true; - return HandleAuthChallenge(true); + HttpProxyClientSocket* tunnel_socket = + reinterpret_cast<HttpProxyClientSocket*>(connection_->socket()); + tunnel_credentials_.invalid = true; - 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()); - rv = ERR_TUNNEL_CONNECTION_FAILED; - } - ClearTunnelState(); - return rv; + return tunnel_socket->RestartWithAuth(tunnel_credentials_.username, + tunnel_credentials_.password, + &io_callback_); } int HttpNetworkTransaction::DoSSLConnect() { @@ -1253,8 +1092,7 @@ int HttpNetworkTransaction::DoSendRequest() { if (net_log_.HasListener()) { net_log_.AddEvent( NetLog::TYPE_HTTP_TRANSACTION_SEND_REQUEST_HEADERS, - new NetLogHttpRequestParameter( - request_line, request_headers)); + new NetLogHttpRequestParameter(request_line, request_headers)); } request_headers_ = request_line + request_headers.ToString(); @@ -1718,13 +1556,6 @@ void HttpNetworkTransaction::LogTransactionMetrics() const { } } -void HttpNetworkTransaction::LogBlockedTunnelResponse( - int response_code) const { - LOG(WARNING) << "Blocked proxy response with status " << response_code - << " to CONNECT request for " - << GetHostAndPort(request_->url) << "."; -} - int HttpNetworkTransaction::HandleCertificateError(int error) { DCHECK(using_ssl_); DCHECK(IsCertificateError(error)); @@ -1976,9 +1807,9 @@ int HttpNetworkTransaction::HandleAuthChallenge(bool establishing_tunnel) { if (target == HttpAuth::AUTH_PROXY && proxy_info_.is_direct()) return ERR_UNEXPECTED_PROXY_AUTH; - int rv = auth_controllers_[target]->HandleAuthChallenge(headers, - request_->load_flags, - establishing_tunnel); + int rv = auth_controllers_[target]->HandleAuthChallenge( + headers, (request_->load_flags & LOAD_DO_NOT_SEND_AUTH_DATA) != 0, + establishing_tunnel); if (auth_controllers_[target]->HaveAuthHandler()) pending_auth_target_ = target; @@ -2037,12 +1868,9 @@ std::string HttpNetworkTransaction::DescribeState(State state) { STATE_CASE(STATE_RESOLVE_PROXY_COMPLETE); STATE_CASE(STATE_INIT_CONNECTION); STATE_CASE(STATE_INIT_CONNECTION_COMPLETE); - STATE_CASE(STATE_TUNNEL_GENERATE_AUTH_TOKEN); - STATE_CASE(STATE_TUNNEL_GENERATE_AUTH_TOKEN_COMPLETE); - STATE_CASE(STATE_TUNNEL_SEND_REQUEST); - STATE_CASE(STATE_TUNNEL_SEND_REQUEST_COMPLETE); - STATE_CASE(STATE_TUNNEL_READ_HEADERS); - STATE_CASE(STATE_TUNNEL_READ_HEADERS_COMPLETE); + STATE_CASE(STATE_TUNNEL_CONNECT); + STATE_CASE(STATE_TUNNEL_CONNECT_COMPLETE); + STATE_CASE(STATE_TUNNEL_RESTART_WITH_AUTH); STATE_CASE(STATE_SSL_CONNECT); STATE_CASE(STATE_SSL_CONNECT_COMPLETE); STATE_CASE(STATE_GENERATE_PROXY_AUTH_TOKEN); diff --git a/net/http/http_network_transaction.h b/net/http/http_network_transaction.h index 2a33663..0c8d029 100644 --- a/net/http/http_network_transaction.h +++ b/net/http/http_network_transaction.h @@ -83,12 +83,9 @@ class HttpNetworkTransaction : public HttpTransaction { STATE_RESOLVE_PROXY_COMPLETE, STATE_INIT_CONNECTION, STATE_INIT_CONNECTION_COMPLETE, - STATE_TUNNEL_GENERATE_AUTH_TOKEN, - STATE_TUNNEL_GENERATE_AUTH_TOKEN_COMPLETE, - STATE_TUNNEL_SEND_REQUEST, - STATE_TUNNEL_SEND_REQUEST_COMPLETE, - STATE_TUNNEL_READ_HEADERS, - STATE_TUNNEL_READ_HEADERS_COMPLETE, + STATE_TUNNEL_CONNECT, + STATE_TUNNEL_CONNECT_COMPLETE, + STATE_TUNNEL_RESTART_WITH_AUTH, STATE_SSL_CONNECT, STATE_SSL_CONNECT_COMPLETE, STATE_GENERATE_PROXY_AUTH_TOKEN, @@ -132,12 +129,9 @@ class HttpNetworkTransaction : public HttpTransaction { int DoResolveProxyComplete(int result); int DoInitConnection(); int DoInitConnectionComplete(int result); - int DoTunnelGenerateAuthToken(); - int DoTunnelGenerateAuthTokenComplete(int result); - int DoTunnelSendRequest(); - int DoTunnelSendRequestComplete(int result); - int DoTunnelReadHeaders(); - int DoTunnelReadHeadersComplete(int result); + int DoTunnelConnect(); + int DoTunnelConnectComplete(int result); + int DoTunnelRestartWithAuth(); int DoSSLConnect(); int DoSSLConnectComplete(int result); int DoGenerateProxyAuthToken(); @@ -227,9 +221,6 @@ class HttpNetworkTransaction : public HttpTransaction { // Resets the members of the transaction so it can be restarted. void ResetStateForRestart(); - // Clear the state used to setup the tunnel. - void ClearTunnelState(); - // Returns true if we should try to add a Proxy-Authorization header bool ShouldApplyProxyAuth() const; @@ -327,6 +318,14 @@ class HttpNetworkTransaction : public HttpTransaction { // specified by the URL, due to Alternate-Protocol or fixed testing ports. HostPortPair endpoint_; + // Stores login and password between |RestartWithAuth| + // and |DoTunnelRestartWithAuth|. + HttpAuth::Identity tunnel_credentials_; + + // True when the tunnel is in the process of being established - we can't + // read from the socket until the tunnel is done. + bool establishing_tunnel_; + DISALLOW_COPY_AND_ASSIGN(HttpNetworkTransaction); }; diff --git a/net/http/http_network_transaction_unittest.cc b/net/http/http_network_transaction_unittest.cc index 945450c..36b3ce2 100644 --- a/net/http/http_network_transaction_unittest.cc +++ b/net/http/http_network_transaction_unittest.cc @@ -83,7 +83,8 @@ struct SessionDependencies { proxy_service(ProxyService::CreateNull()), ssl_config_service(new SSLConfigServiceDefaults), http_auth_handler_factory(HttpAuthHandlerFactory::CreateDefault()), - spdy_session_pool(new SpdySessionPool()) {} + spdy_session_pool(new SpdySessionPool()), + net_log(NULL) {} // Custom proxy service dependency. explicit SessionDependencies(ProxyService* proxy_service) @@ -91,7 +92,8 @@ struct SessionDependencies { proxy_service(proxy_service), ssl_config_service(new SSLConfigServiceDefaults), http_auth_handler_factory(HttpAuthHandlerFactory::CreateDefault()), - spdy_session_pool(new SpdySessionPool()) {} + spdy_session_pool(new SpdySessionPool()), + net_log(NULL) {} scoped_refptr<MockHostResolverBase> host_resolver; scoped_refptr<ProxyService> proxy_service; @@ -99,6 +101,7 @@ struct SessionDependencies { MockClientSocketFactory socket_factory; scoped_ptr<HttpAuthHandlerFactory> http_auth_handler_factory; scoped_refptr<SpdySessionPool> spdy_session_pool; + NetLog* net_log; }; ProxyService* CreateFixedProxyService(const std::string& proxy) { @@ -115,7 +118,7 @@ HttpNetworkSession* CreateSession(SessionDependencies* session_deps) { session_deps->spdy_session_pool, session_deps->http_auth_handler_factory.get(), NULL, - NULL); + session_deps->net_log); } class HttpNetworkTransactionTest : public PlatformTest { @@ -1297,6 +1300,8 @@ TEST_F(HttpNetworkTransactionTest, BasicAuthKeepAliveImpatientServer) { TEST_F(HttpNetworkTransactionTest, BasicAuthProxyKeepAlive) { // Configure against proxy server "myproxy:70". SessionDependencies session_deps(CreateFixedProxyService("myproxy:70")); + CapturingBoundNetLog log(CapturingNetLog::kUnbounded); + session_deps.net_log = log.bound().net_log(); scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); @@ -1345,7 +1350,6 @@ TEST_F(HttpNetworkTransactionTest, BasicAuthProxyKeepAlive) { TestCompletionCallback callback1; - CapturingBoundNetLog log(CapturingNetLog::kUnbounded); int rv = trans->Start(&request, &callback1, log.bound()); EXPECT_EQ(ERR_IO_PENDING, rv); @@ -1636,7 +1640,7 @@ TEST_F(HttpNetworkTransactionTest, ConnectStatus406) { TEST_F(HttpNetworkTransactionTest, ConnectStatus407) { ConnectStatusHelperWithExpectedStatus( MockRead("HTTP/1.1 407 Proxy Authentication Required\r\n"), - ERR_PROXY_AUTH_REQUESTED); + ERR_PROXY_AUTH_UNSUPPORTED); } TEST_F(HttpNetworkTransactionTest, ConnectStatus408) { 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 diff --git a/net/http/http_proxy_client_socket.h b/net/http/http_proxy_client_socket.h new file mode 100644 index 0000000..6ced031 --- /dev/null +++ b/net/http/http_proxy_client_socket.h @@ -0,0 +1,146 @@ +// 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. + +#ifndef NET_HTTP_HTTP_PROXY_CLIENT_SOCKET_H_ +#define NET_HTTP_HTTP_PROXY_CLIENT_SOCKET_H_ + +#include <string> + +#include "base/basictypes.h" +#include "base/ref_counted.h" +#include "net/base/completion_callback.h" +#include "net/base/host_port_pair.h" +#include "net/base/net_log.h" +#include "net/http/http_auth_controller.h" +#include "net/http/http_request_headers.h" +#include "net/http/http_request_info.h" +#include "net/http/http_response_info.h" +#include "net/socket/client_socket.h" + +class GURL; + +namespace net { + +class AddressList; +class ClientSocketHandle; +class HttpStream; +class IOBuffer;; + +class HttpProxyClientSocket : public ClientSocket { + public: + // Takes ownership of |auth| and the |transport_socket|, which should + // already be connected by the time Connect() is called. If tunnel is true + // then on Connect() this socket will establish an Http tunnel. + HttpProxyClientSocket(ClientSocketHandle* transport_socket, + const GURL& request_url, const HostPortPair& endpoint, + HttpAuthController* auth, bool tunnel); + + // On destruction Disconnect() is called. + virtual ~HttpProxyClientSocket(); + + // If Connect (or its callback) returns PROXY_AUTH_REQUESTED, then + // credentials can be provided by calling RestartWithAuth. + int RestartWithAuth(const std::wstring& username, + const std::wstring& password, + CompletionCallback* callback); + + const HttpResponseInfo* GetResponseInfo() const { + return response_.headers ? &response_ : NULL; + } + + HttpAuthController* TakeAuthController() { + return auth_.release(); + } + + // ClientSocket methods: + + // Authenticates to the Http Proxy and then passes data freely. + virtual int Connect(CompletionCallback* callback); + virtual void Disconnect(); + virtual bool IsConnected() const; + virtual bool IsConnectedAndIdle() const; + virtual const BoundNetLog& NetLog() const { return net_log_; } + + // Socket methods: + virtual int Read(IOBuffer* buf, int buf_len, CompletionCallback* callback); + virtual int Write(IOBuffer* buf, int buf_len, CompletionCallback* callback); + + virtual bool SetReceiveBufferSize(int32 size); + virtual bool SetSendBufferSize(int32 size); + + virtual int GetPeerAddress(AddressList* address) const; + + private: + enum State { + STATE_NONE, + STATE_GENERATE_AUTH_TOKEN, + STATE_GENERATE_AUTH_TOKEN_COMPLETE, + STATE_SEND_REQUEST, + STATE_SEND_REQUEST_COMPLETE, + STATE_READ_HEADERS, + STATE_READ_HEADERS_COMPLETE, + STATE_RESOLVE_CANONICAL_NAME, + STATE_RESOLVE_CANONICAL_NAME_COMPLETE, + STATE_DRAIN_BODY, + STATE_DRAIN_BODY_COMPLETE, + STATE_DONE, + }; + + // The size in bytes of the buffer we use to drain the response body that + // we want to throw away. The response body is typically a small error + // page just a few hundred bytes long. + enum { kDrainBodyBufferSize = 1024 }; + + int PrepareForAuthRestart(); + int DidDrainBodyForAuthRestart(bool keep_alive); + + int HandleAuthChallenge(); + + void LogBlockedTunnelResponse(int response_code) const; + + void DoCallback(int result); + void OnIOComplete(int result); + + int DoLoop(int last_io_result); + int DoGenerateAuthToken(); + int DoGenerateAuthTokenComplete(int result); + int DoSendRequest(); + int DoSendRequestComplete(int result); + int DoReadHeaders(); + int DoReadHeadersComplete(int result); + int DoDrainBody(); + int DoDrainBodyComplete(int result); + + CompletionCallbackImpl<HttpProxyClientSocket> io_callback_; + State next_state_; + + // Stores the callback to the layer above, called on completing Connect(). + CompletionCallback* user_callback_; + + // Stores the underlying socket. + scoped_ptr<ClientSocketHandle> transport_; + + bool tunnel_; + + std::string request_headers_; + + scoped_ptr<HttpStream> http_stream_; + HttpRequestInfo request_; + HttpResponseInfo response_; + scoped_ptr<HttpAuthController> auth_; + + // The hostname and port of the endpoint. This is not necessarily the one + // specified by the URL, due to Alternate-Protocol or fixed testing ports. + HostPortPair endpoint_; + + scoped_refptr<IOBuffer> drain_buf_; + + BoundNetLog net_log_; + + DISALLOW_COPY_AND_ASSIGN(HttpProxyClientSocket); +}; + +} // namespace net + +#endif // NET_HTTP_HTTP_PROXY_CLIENT_SOCKET_H_ |