|  | // Copyright (c) 2012 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/bind.h" | 
|  | #include "base/bind_helpers.h" | 
|  | #include "base/strings/string_util.h" | 
|  | #include "base/strings/stringprintf.h" | 
|  | #include "net/base/auth.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/base/proxy_delegate.h" | 
|  | #include "net/http/http_basic_stream.h" | 
|  | #include "net/http/http_network_session.h" | 
|  | #include "net/http/http_request_info.h" | 
|  | #include "net/http/http_response_headers.h" | 
|  | #include "net/http/http_stream_parser.h" | 
|  | #include "net/http/proxy_connect_redirect_http_stream.h" | 
|  | #include "net/socket/client_socket_handle.h" | 
|  | #include "url/gurl.h" | 
|  |  | 
|  | namespace net { | 
|  |  | 
|  | HttpProxyClientSocket::HttpProxyClientSocket( | 
|  | ClientSocketHandle* transport_socket, | 
|  | const GURL& request_url, | 
|  | const std::string& user_agent, | 
|  | const HostPortPair& endpoint, | 
|  | const HostPortPair& proxy_server, | 
|  | HttpAuthCache* http_auth_cache, | 
|  | HttpAuthHandlerFactory* http_auth_handler_factory, | 
|  | bool tunnel, | 
|  | bool using_spdy, | 
|  | NextProto protocol_negotiated, | 
|  | ProxyDelegate* proxy_delegate, | 
|  | bool is_https_proxy) | 
|  | : io_callback_(base::Bind(&HttpProxyClientSocket::OnIOComplete, | 
|  | base::Unretained(this))), | 
|  | next_state_(STATE_NONE), | 
|  | transport_(transport_socket), | 
|  | endpoint_(endpoint), | 
|  | auth_(tunnel ? | 
|  | new HttpAuthController(HttpAuth::AUTH_PROXY, | 
|  | GURL((is_https_proxy ? "https://" : "http://") | 
|  | + proxy_server.ToString()), | 
|  | http_auth_cache, | 
|  | http_auth_handler_factory) | 
|  | : NULL), | 
|  | tunnel_(tunnel), | 
|  | using_spdy_(using_spdy), | 
|  | protocol_negotiated_(protocol_negotiated), | 
|  | is_https_proxy_(is_https_proxy), | 
|  | redirect_has_load_timing_info_(false), | 
|  | proxy_server_(proxy_server), | 
|  | proxy_delegate_(proxy_delegate), | 
|  | net_log_(transport_socket->socket()->NetLog()) { | 
|  | // Synthesize the bits of a request that we actually use. | 
|  | request_.url = request_url; | 
|  | request_.method = "GET"; | 
|  | if (!user_agent.empty()) | 
|  | request_.extra_headers.SetHeader(HttpRequestHeaders::kUserAgent, | 
|  | user_agent); | 
|  | } | 
|  |  | 
|  | HttpProxyClientSocket::~HttpProxyClientSocket() { | 
|  | Disconnect(); | 
|  | } | 
|  |  | 
|  | int HttpProxyClientSocket::RestartWithAuth(const CompletionCallback& callback) { | 
|  | DCHECK_EQ(STATE_NONE, next_state_); | 
|  | DCHECK(user_callback_.is_null()); | 
|  |  | 
|  | int rv = PrepareForAuthRestart(); | 
|  | if (rv != OK) | 
|  | return rv; | 
|  |  | 
|  | rv = DoLoop(OK); | 
|  | if (rv == ERR_IO_PENDING) { | 
|  | if (!callback.is_null()) | 
|  | user_callback_ =  callback; | 
|  | } | 
|  |  | 
|  | return rv; | 
|  | } | 
|  |  | 
|  | const scoped_refptr<HttpAuthController>& | 
|  | HttpProxyClientSocket::GetAuthController() const { | 
|  | return auth_; | 
|  | } | 
|  |  | 
|  | bool HttpProxyClientSocket::IsUsingSpdy() const { | 
|  | return using_spdy_; | 
|  | } | 
|  |  | 
|  | NextProto HttpProxyClientSocket::GetProtocolNegotiated() const { | 
|  | return protocol_negotiated_; | 
|  | } | 
|  |  | 
|  | const HttpResponseInfo* HttpProxyClientSocket::GetConnectResponseInfo() const { | 
|  | return response_.headers.get() ? &response_ : NULL; | 
|  | } | 
|  |  | 
|  | HttpStream* HttpProxyClientSocket::CreateConnectResponseStream() { | 
|  | return new ProxyConnectRedirectHttpStream( | 
|  | redirect_has_load_timing_info_ ? &redirect_load_timing_info_ : NULL); | 
|  | } | 
|  |  | 
|  |  | 
|  | int HttpProxyClientSocket::Connect(const CompletionCallback& callback) { | 
|  | DCHECK(transport_.get()); | 
|  | DCHECK(transport_->socket()); | 
|  | DCHECK(user_callback_.is_null()); | 
|  |  | 
|  | // TODO(rch): figure out the right way to set up a tunnel with SPDY. | 
|  | // This approach sends the complete HTTPS request to the proxy | 
|  | // which allows the proxy to see "private" data.  Instead, we should | 
|  | // create an SSL tunnel to the origin server using the CONNECT method | 
|  | // inside a single SPDY stream. | 
|  | if (using_spdy_ || !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; | 
|  | } | 
|  |  | 
|  | void HttpProxyClientSocket::Disconnect() { | 
|  | if (transport_.get()) | 
|  | 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_.Reset(); | 
|  | } | 
|  |  | 
|  | bool HttpProxyClientSocket::IsConnected() const { | 
|  | return next_state_ == STATE_DONE && transport_->socket()->IsConnected(); | 
|  | } | 
|  |  | 
|  | bool HttpProxyClientSocket::IsConnectedAndIdle() const { | 
|  | return next_state_ == STATE_DONE && | 
|  | transport_->socket()->IsConnectedAndIdle(); | 
|  | } | 
|  |  | 
|  | const BoundNetLog& HttpProxyClientSocket::NetLog() const { | 
|  | return net_log_; | 
|  | } | 
|  |  | 
|  | void HttpProxyClientSocket::SetSubresourceSpeculation() { | 
|  | if (transport_.get() && transport_->socket()) { | 
|  | transport_->socket()->SetSubresourceSpeculation(); | 
|  | } else { | 
|  | NOTREACHED(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void HttpProxyClientSocket::SetOmniboxSpeculation() { | 
|  | if (transport_.get() && transport_->socket()) { | 
|  | transport_->socket()->SetOmniboxSpeculation(); | 
|  | } else { | 
|  | NOTREACHED(); | 
|  | } | 
|  | } | 
|  |  | 
|  | bool HttpProxyClientSocket::WasEverUsed() const { | 
|  | if (transport_.get() && transport_->socket()) { | 
|  | return transport_->socket()->WasEverUsed(); | 
|  | } | 
|  | NOTREACHED(); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | bool HttpProxyClientSocket::UsingTCPFastOpen() const { | 
|  | if (transport_.get() && transport_->socket()) { | 
|  | return transport_->socket()->UsingTCPFastOpen(); | 
|  | } | 
|  | NOTREACHED(); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | bool HttpProxyClientSocket::WasNpnNegotiated() const { | 
|  | if (transport_.get() && transport_->socket()) { | 
|  | return transport_->socket()->WasNpnNegotiated(); | 
|  | } | 
|  | NOTREACHED(); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | NextProto HttpProxyClientSocket::GetNegotiatedProtocol() const { | 
|  | if (transport_.get() && transport_->socket()) { | 
|  | return transport_->socket()->GetNegotiatedProtocol(); | 
|  | } | 
|  | NOTREACHED(); | 
|  | return kProtoUnknown; | 
|  | } | 
|  |  | 
|  | bool HttpProxyClientSocket::GetSSLInfo(SSLInfo* ssl_info) { | 
|  | if (transport_.get() && transport_->socket()) { | 
|  | return transport_->socket()->GetSSLInfo(ssl_info); | 
|  | } | 
|  | NOTREACHED(); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | int HttpProxyClientSocket::Read(IOBuffer* buf, int buf_len, | 
|  | const CompletionCallback& callback) { | 
|  | DCHECK(user_callback_.is_null()); | 
|  | 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(); | 
|  |  | 
|  | return ERR_TUNNEL_CONNECTION_FAILED; | 
|  | } | 
|  |  | 
|  | return transport_->socket()->Read(buf, buf_len, callback); | 
|  | } | 
|  |  | 
|  | int HttpProxyClientSocket::Write(IOBuffer* buf, int buf_len, | 
|  | const CompletionCallback& callback) { | 
|  | DCHECK_EQ(STATE_DONE, next_state_); | 
|  | DCHECK(user_callback_.is_null()); | 
|  |  | 
|  | return transport_->socket()->Write(buf, buf_len, callback); | 
|  | } | 
|  |  | 
|  | int HttpProxyClientSocket::SetReceiveBufferSize(int32 size) { | 
|  | return transport_->socket()->SetReceiveBufferSize(size); | 
|  | } | 
|  |  | 
|  | int HttpProxyClientSocket::SetSendBufferSize(int32 size) { | 
|  | return transport_->socket()->SetSendBufferSize(size); | 
|  | } | 
|  |  | 
|  | int HttpProxyClientSocket::GetPeerAddress(IPEndPoint* address) const { | 
|  | return transport_->socket()->GetPeerAddress(address); | 
|  | } | 
|  |  | 
|  | int HttpProxyClientSocket::GetLocalAddress(IPEndPoint* address) const { | 
|  | return transport_->socket()->GetLocalAddress(address); | 
|  | } | 
|  |  | 
|  | int HttpProxyClientSocket::PrepareForAuthRestart() { | 
|  | if (!response_.headers.get()) | 
|  | return ERR_CONNECTION_RESET; | 
|  |  | 
|  | bool keep_alive = false; | 
|  | if (response_.headers->IsKeepAlive() && | 
|  | http_stream_parser_->CanFindEndOfResponse()) { | 
|  | if (!http_stream_parser_->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_reuse_type(ClientSocketHandle::REUSED_IDLE); | 
|  | } else { | 
|  | // This assumes that the underlying transport socket is a TCP socket, | 
|  | // since only TCP sockets are restartable. | 
|  | next_state_ = STATE_TCP_RESTART; | 
|  | transport_->socket()->Disconnect(); | 
|  | } | 
|  |  | 
|  | // Reset the other member variables. | 
|  | drain_buf_ = NULL; | 
|  | parser_buf_ = NULL; | 
|  | http_stream_parser_.reset(); | 
|  | request_line_.clear(); | 
|  | request_headers_.Clear(); | 
|  | response_ = HttpResponseInfo(); | 
|  | return OK; | 
|  | } | 
|  |  | 
|  | void HttpProxyClientSocket::LogBlockedTunnelResponse() const { | 
|  | ProxyClientSocket::LogBlockedTunnelResponse( | 
|  | response_.headers->response_code(), | 
|  | request_.url, | 
|  | is_https_proxy_); | 
|  | } | 
|  |  | 
|  | void HttpProxyClientSocket::DoCallback(int result) { | 
|  | DCHECK_NE(ERR_IO_PENDING, result); | 
|  | DCHECK(!user_callback_.is_null()); | 
|  |  | 
|  | // Since Run() may result in Read being called, | 
|  | // clear user_callback_ up front. | 
|  | CompletionCallback c = user_callback_; | 
|  | user_callback_.Reset(); | 
|  | 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); | 
|  | rv = DoSendRequest(); | 
|  | break; | 
|  | case STATE_SEND_REQUEST_COMPLETE: | 
|  | rv = DoSendRequestComplete(rv); | 
|  | net_log_.EndEventWithNetErrorCode( | 
|  | NetLog::TYPE_HTTP_TRANSACTION_TUNNEL_SEND_REQUEST, rv); | 
|  | break; | 
|  | case STATE_READ_HEADERS: | 
|  | DCHECK_EQ(OK, rv); | 
|  | net_log_.BeginEvent( | 
|  | NetLog::TYPE_HTTP_TRANSACTION_TUNNEL_READ_HEADERS); | 
|  | rv = DoReadHeaders(); | 
|  | break; | 
|  | case STATE_READ_HEADERS_COMPLETE: | 
|  | rv = DoReadHeadersComplete(rv); | 
|  | net_log_.EndEventWithNetErrorCode( | 
|  | NetLog::TYPE_HTTP_TRANSACTION_TUNNEL_READ_HEADERS, rv); | 
|  | break; | 
|  | case STATE_DRAIN_BODY: | 
|  | DCHECK_EQ(OK, rv); | 
|  | rv = DoDrainBody(); | 
|  | break; | 
|  | case STATE_DRAIN_BODY_COMPLETE: | 
|  | rv = DoDrainBodyComplete(rv); | 
|  | break; | 
|  | case STATE_TCP_RESTART: | 
|  | DCHECK_EQ(OK, rv); | 
|  | rv = DoTCPRestart(); | 
|  | break; | 
|  | case STATE_TCP_RESTART_COMPLETE: | 
|  | rv = DoTCPRestartComplete(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_, net_log_); | 
|  | } | 
|  |  | 
|  | 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_line_.empty()) { | 
|  | DCHECK(request_headers_.IsEmpty()); | 
|  | HttpRequestHeaders authorization_headers; | 
|  | if (auth_->HaveAuth()) | 
|  | auth_->AddAuthorizationHeader(&authorization_headers); | 
|  | if (proxy_delegate_) { | 
|  | proxy_delegate_->OnBeforeTunnelRequest(proxy_server_, | 
|  | &authorization_headers); | 
|  | } | 
|  | BuildTunnelRequest(request_, authorization_headers, endpoint_, | 
|  | &request_line_, &request_headers_); | 
|  |  | 
|  | net_log_.AddEvent( | 
|  | NetLog::TYPE_HTTP_TRANSACTION_SEND_TUNNEL_HEADERS, | 
|  | base::Bind(&HttpRequestHeaders::NetLogCallback, | 
|  | base::Unretained(&request_headers_), | 
|  | &request_line_)); | 
|  | } | 
|  |  | 
|  | parser_buf_ = new GrowableIOBuffer(); | 
|  | http_stream_parser_.reset(new HttpStreamParser( | 
|  | transport_.get(), &request_, parser_buf_.get(), net_log_)); | 
|  | return http_stream_parser_->SendRequest( | 
|  | request_line_, request_headers_, &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_parser_->ReadResponseHeaders(io_callback_); | 
|  | } | 
|  |  | 
|  | int HttpProxyClientSocket::DoReadHeadersComplete(int result) { | 
|  | if (result < 0) | 
|  | return result; | 
|  |  | 
|  | // Require the "HTTP/1.x" status line for SSL CONNECT. | 
|  | if (response_.headers->GetParsedHttpVersion() < HttpVersion(1, 0)) | 
|  | return ERR_TUNNEL_CONNECTION_FAILED; | 
|  |  | 
|  | net_log_.AddEvent( | 
|  | NetLog::TYPE_HTTP_TRANSACTION_READ_TUNNEL_RESPONSE_HEADERS, | 
|  | base::Bind(&HttpResponseHeaders::NetLogCallback, response_.headers)); | 
|  |  | 
|  | if (proxy_delegate_) { | 
|  | proxy_delegate_->OnTunnelHeadersReceived( | 
|  | HostPortPair::FromURL(request_.url), | 
|  | proxy_server_, | 
|  | *response_.headers); | 
|  | } | 
|  |  | 
|  | switch (response_.headers->response_code()) { | 
|  | case 200:  // OK | 
|  | if (http_stream_parser_->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 302:  // Found / Moved Temporarily | 
|  | // Attempt to follow redirects from HTTPS proxies, but only if we can | 
|  | // sanitize the response.  This still allows a rogue HTTPS proxy to | 
|  | // redirect an HTTPS site load to a similar-looking site, but no longer | 
|  | // allows it to impersonate the site the user requested. | 
|  | if (is_https_proxy_ && SanitizeProxyRedirect(&response_, request_.url)) { | 
|  | bool is_connection_reused = http_stream_parser_->IsConnectionReused(); | 
|  | redirect_has_load_timing_info_ = | 
|  | transport_->GetLoadTimingInfo( | 
|  | is_connection_reused, &redirect_load_timing_info_); | 
|  | transport_.reset(); | 
|  | http_stream_parser_.reset(); | 
|  | return ERR_HTTPS_PROXY_TUNNEL_RESPONSE; | 
|  | } | 
|  |  | 
|  | // We're not using an HTTPS proxy, or we couldn't sanitize the redirect. | 
|  | LogBlockedTunnelResponse(); | 
|  | return ERR_TUNNEL_CONNECTION_FAILED; | 
|  |  | 
|  | 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 HandleProxyAuthChallenge(auth_.get(), &response_, net_log_); | 
|  |  | 
|  | default: | 
|  | // Ignore response to avoid letting the proxy impersonate the target | 
|  | // server.  (See http://crbug.com/137891.) | 
|  | // 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(); | 
|  | return ERR_TUNNEL_CONNECTION_FAILED; | 
|  | } | 
|  | } | 
|  |  | 
|  | int HttpProxyClientSocket::DoDrainBody() { | 
|  | DCHECK(drain_buf_.get()); | 
|  | DCHECK(transport_->is_initialized()); | 
|  | next_state_ = STATE_DRAIN_BODY_COMPLETE; | 
|  | return http_stream_parser_->ReadResponseBody( | 
|  | drain_buf_.get(), kDrainBodyBufferSize, io_callback_); | 
|  | } | 
|  |  | 
|  | int HttpProxyClientSocket::DoDrainBodyComplete(int result) { | 
|  | if (result < 0) | 
|  | return result; | 
|  |  | 
|  | if (http_stream_parser_->IsResponseBodyComplete()) | 
|  | return DidDrainBodyForAuthRestart(true); | 
|  |  | 
|  | // Keep draining. | 
|  | next_state_ = STATE_DRAIN_BODY; | 
|  | return OK; | 
|  | } | 
|  |  | 
|  | int HttpProxyClientSocket::DoTCPRestart() { | 
|  | next_state_ = STATE_TCP_RESTART_COMPLETE; | 
|  | return transport_->socket()->Connect( | 
|  | base::Bind(&HttpProxyClientSocket::OnIOComplete, base::Unretained(this))); | 
|  | } | 
|  |  | 
|  | int HttpProxyClientSocket::DoTCPRestartComplete(int result) { | 
|  | if (result != OK) | 
|  | return result; | 
|  |  | 
|  | next_state_ = STATE_GENERATE_AUTH_TOKEN; | 
|  | return result; | 
|  | } | 
|  |  | 
|  | }  // namespace net |