| // 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/string_util.h" |
| #include "base/stringprintf.h" |
| #include "googleurl/src/gurl.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/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/socket/client_socket_handle.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, |
| bool is_https_proxy) |
| : ALLOW_THIS_IN_INITIALIZER_LIST(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), |
| 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 ? &response_ : NULL; |
| } |
| |
| HttpStream* HttpProxyClientSocket::CreateConnectResponseStream() { |
| return new HttpBasicStream(transport_.release(), |
| http_stream_parser_.release(), false); |
| } |
| |
| |
| 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; |
| } |
| |
| int64 HttpProxyClientSocket::NumBytesRead() const { |
| if (transport_.get() && transport_->socket()) { |
| return transport_->socket()->NumBytesRead(); |
| } |
| NOTREACHED(); |
| return -1; |
| } |
| |
| base::TimeDelta HttpProxyClientSocket::GetConnectTimeMicros() const { |
| if (transport_.get() && transport_->socket()) { |
| return transport_->socket()->GetConnectTimeMicros(); |
| } |
| NOTREACHED(); |
| return base::TimeDelta::FromMicroseconds(-1); |
| } |
| |
| 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); |
| } |
| |
| bool HttpProxyClientSocket::SetReceiveBufferSize(int32 size) { |
| return transport_->socket()->SetReceiveBufferSize(size); |
| } |
| |
| bool 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_is_reused(true); |
| } 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); |
| 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_, 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)); |
| |
| 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)) |
| 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_, &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_); |
| DCHECK(transport_->is_initialized()); |
| next_state_ = STATE_DRAIN_BODY_COMPLETE; |
| return http_stream_parser_->ReadResponseBody(drain_buf_, 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 |