|  | // Copyright 2015 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_wrapper.h" | 
|  |  | 
|  | #include <utility> | 
|  |  | 
|  | #include "base/bind.h" | 
|  | #include "base/bind_helpers.h" | 
|  | #include "base/callback_helpers.h" | 
|  | #include "base/memory/weak_ptr.h" | 
|  | #include "base/metrics/histogram_macros.h" | 
|  | #include "base/values.h" | 
|  | #include "net/http/http_proxy_client_socket.h" | 
|  | #include "net/http/http_response_info.h" | 
|  | #include "net/log/net_log_event_type.h" | 
|  | #include "net/log/net_log_source.h" | 
|  | #include "net/log/net_log_source_type.h" | 
|  | #include "net/quic/quic_http_utils.h" | 
|  | #include "net/quic/quic_proxy_client_socket.h" | 
|  | #include "net/socket/client_socket_factory.h" | 
|  | #include "net/socket/client_socket_handle.h" | 
|  | #include "net/socket/socket_tag.h" | 
|  | #include "net/spdy/spdy_proxy_client_socket.h" | 
|  | #include "net/spdy/spdy_session.h" | 
|  | #include "net/spdy/spdy_session_pool.h" | 
|  | #include "net/spdy/spdy_stream.h" | 
|  | #include "net/ssl/ssl_cert_request_info.h" | 
|  | #include "net/traffic_annotation/network_traffic_annotation.h" | 
|  | #include "url/gurl.h" | 
|  |  | 
|  | namespace net { | 
|  |  | 
|  | HttpProxyClientSocketWrapper::HttpProxyClientSocketWrapper( | 
|  | const std::string& group_name, | 
|  | RequestPriority priority, | 
|  | const SocketTag& socket_tag, | 
|  | ClientSocketPool::RespectLimits respect_limits, | 
|  | base::TimeDelta connect_timeout_duration, | 
|  | base::TimeDelta proxy_negotiation_timeout_duration, | 
|  | TransportClientSocketPool* transport_pool, | 
|  | SSLClientSocketPool* ssl_pool, | 
|  | const scoped_refptr<TransportSocketParams>& transport_params, | 
|  | const scoped_refptr<SSLSocketParams>& ssl_params, | 
|  | quic::QuicTransportVersion quic_version, | 
|  | const std::string& user_agent, | 
|  | const HostPortPair& endpoint, | 
|  | HttpAuthCache* http_auth_cache, | 
|  | HttpAuthHandlerFactory* http_auth_handler_factory, | 
|  | SpdySessionPool* spdy_session_pool, | 
|  | #if !defined(QUIC_DISABLED_FOR_STARBOARD) | 
|  | QuicStreamFactory* quic_stream_factory, | 
|  | #endif | 
|  | bool is_trusted_proxy, | 
|  | bool tunnel, | 
|  | const NetworkTrafficAnnotationTag& traffic_annotation, | 
|  | const NetLogWithSource& net_log) | 
|  | : next_state_(STATE_NONE), | 
|  | group_name_(group_name), | 
|  | priority_(priority), | 
|  | initial_socket_tag_(socket_tag), | 
|  | respect_limits_(respect_limits), | 
|  | connect_timeout_duration_(connect_timeout_duration), | 
|  | proxy_negotiation_timeout_duration_(proxy_negotiation_timeout_duration), | 
|  | transport_pool_(transport_pool), | 
|  | ssl_pool_(ssl_pool), | 
|  | transport_params_(transport_params), | 
|  | ssl_params_(ssl_params), | 
|  | quic_version_(quic_version), | 
|  | user_agent_(user_agent), | 
|  | endpoint_(endpoint), | 
|  | spdy_session_pool_(spdy_session_pool), | 
|  | has_restarted_(false), | 
|  | tunnel_(tunnel), | 
|  | using_spdy_(false), | 
|  | is_trusted_proxy_(is_trusted_proxy), | 
|  | #if !defined(QUIC_DISABLED_FOR_STARBOARD) | 
|  | quic_stream_request_(quic_stream_factory), | 
|  | #endif | 
|  | http_auth_controller_( | 
|  | tunnel ? new HttpAuthController( | 
|  | HttpAuth::AUTH_PROXY, | 
|  | GURL((ssl_params_.get() ? "https://" : "http://") + | 
|  | GetDestination().host_port_pair().ToString()), | 
|  | http_auth_cache, | 
|  | http_auth_handler_factory) | 
|  | : nullptr), | 
|  | net_log_(NetLogWithSource::Make( | 
|  | net_log.net_log(), | 
|  | NetLogSourceType::PROXY_CLIENT_SOCKET_WRAPPER)), | 
|  | traffic_annotation_(traffic_annotation) { | 
|  | net_log_.BeginEvent(NetLogEventType::SOCKET_ALIVE, | 
|  | net_log.source().ToEventParametersCallback()); | 
|  | // If doing a QUIC proxy, |quic_version| must not be | 
|  | // quic::QUIC_VERSION_UNSUPPORTED, and |ssl_params| must be valid while | 
|  | // |transport_params| is null. Otherwise, |quic_version| must be | 
|  | // quic::QUIC_VERSION_UNSUPPORTED, and exactly one of |transport_params| or | 
|  | // |ssl_params| must be set. | 
|  | #if !defined(QUIC_DISABLED_FOR_STARBOARD) | 
|  | DCHECK(quic_version_ == quic::QUIC_VERSION_UNSUPPORTED | 
|  | ? (bool)transport_params != (bool)ssl_params | 
|  | : !transport_params && ssl_params); | 
|  | #endif | 
|  | } | 
|  |  | 
|  | HttpProxyClientSocketWrapper::~HttpProxyClientSocketWrapper() { | 
|  | // Make sure no sockets are returned to the lower level socket pools. | 
|  | Disconnect(); | 
|  |  | 
|  | net_log_.EndEvent(NetLogEventType::SOCKET_ALIVE); | 
|  | } | 
|  |  | 
|  | LoadState HttpProxyClientSocketWrapper::GetConnectLoadState() const { | 
|  | switch (next_state_) { | 
|  | case STATE_BEGIN_CONNECT: | 
|  | case STATE_TCP_CONNECT: | 
|  | case STATE_TCP_CONNECT_COMPLETE: | 
|  | case STATE_SSL_CONNECT: | 
|  | case STATE_SSL_CONNECT_COMPLETE: | 
|  | return transport_socket_handle_->GetLoadState(); | 
|  | case STATE_HTTP_PROXY_CONNECT: | 
|  | case STATE_HTTP_PROXY_CONNECT_COMPLETE: | 
|  | case STATE_SPDY_PROXY_CREATE_STREAM: | 
|  | case STATE_SPDY_PROXY_CREATE_STREAM_COMPLETE: | 
|  | case STATE_QUIC_PROXY_CREATE_SESSION: | 
|  | case STATE_QUIC_PROXY_CREATE_STREAM: | 
|  | case STATE_QUIC_PROXY_CREATE_STREAM_COMPLETE: | 
|  | case STATE_RESTART_WITH_AUTH: | 
|  | case STATE_RESTART_WITH_AUTH_COMPLETE: | 
|  | return LOAD_STATE_ESTABLISHING_PROXY_TUNNEL; | 
|  | case STATE_NONE: | 
|  | // May be possible for this method to be called after an error, shouldn't | 
|  | // be called after a successful connect. | 
|  | break; | 
|  | } | 
|  | return LOAD_STATE_IDLE; | 
|  | } | 
|  |  | 
|  | std::unique_ptr<HttpResponseInfo> | 
|  | HttpProxyClientSocketWrapper::GetAdditionalErrorState() { | 
|  | return std::move(error_response_info_); | 
|  | } | 
|  |  | 
|  | const HttpResponseInfo* HttpProxyClientSocketWrapper::GetConnectResponseInfo() | 
|  | const { | 
|  | if (transport_socket_) | 
|  | return transport_socket_->GetConnectResponseInfo(); | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | std::unique_ptr<HttpStream> | 
|  | HttpProxyClientSocketWrapper::CreateConnectResponseStream() { | 
|  | if (transport_socket_) | 
|  | return transport_socket_->CreateConnectResponseStream(); | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | int HttpProxyClientSocketWrapper::RestartWithAuth( | 
|  | CompletionOnceCallback callback) { | 
|  | DCHECK(!callback.is_null()); | 
|  | DCHECK(connect_callback_.is_null()); | 
|  | DCHECK(transport_socket_); | 
|  | DCHECK_EQ(STATE_NONE, next_state_); | 
|  |  | 
|  | connect_callback_ = std::move(callback); | 
|  | next_state_ = STATE_RESTART_WITH_AUTH; | 
|  | return DoLoop(OK); | 
|  | } | 
|  |  | 
|  | const scoped_refptr<HttpAuthController>& | 
|  | HttpProxyClientSocketWrapper::GetAuthController() const { | 
|  | return http_auth_controller_; | 
|  | } | 
|  |  | 
|  | bool HttpProxyClientSocketWrapper::IsUsingSpdy() const { | 
|  | if (transport_socket_) | 
|  | return transport_socket_->IsUsingSpdy(); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | NextProto HttpProxyClientSocketWrapper::GetProxyNegotiatedProtocol() const { | 
|  | if (transport_socket_) | 
|  | return transport_socket_->GetProxyNegotiatedProtocol(); | 
|  | return kProtoUnknown; | 
|  | } | 
|  |  | 
|  | int HttpProxyClientSocketWrapper::Connect(CompletionOnceCallback callback) { | 
|  | DCHECK(!callback.is_null()); | 
|  | DCHECK(connect_callback_.is_null()); | 
|  |  | 
|  | // If connecting or previously connected and not disconnected, return OK, to | 
|  | // match TCPClientSocket's behavior. | 
|  | if (next_state_ != STATE_NONE || transport_socket_) | 
|  | return OK; | 
|  |  | 
|  | next_state_ = STATE_BEGIN_CONNECT; | 
|  | int rv = DoLoop(OK); | 
|  | if (rv == ERR_IO_PENDING) { | 
|  | connect_callback_ = std::move(callback); | 
|  | } else { | 
|  | connect_timer_.Stop(); | 
|  | } | 
|  |  | 
|  | return rv; | 
|  | } | 
|  |  | 
|  | void HttpProxyClientSocketWrapper::Disconnect() { | 
|  | connect_callback_.Reset(); | 
|  | connect_timer_.Stop(); | 
|  | next_state_ = STATE_NONE; | 
|  | spdy_stream_request_.CancelRequest(); | 
|  | if (transport_socket_handle_) { | 
|  | if (transport_socket_handle_->socket()) | 
|  | transport_socket_handle_->socket()->Disconnect(); | 
|  | transport_socket_handle_->Reset(); | 
|  | transport_socket_handle_.reset(); | 
|  | } | 
|  |  | 
|  | if (transport_socket_) | 
|  | transport_socket_->Disconnect(); | 
|  | } | 
|  |  | 
|  | bool HttpProxyClientSocketWrapper::IsConnected() const { | 
|  | if (transport_socket_) | 
|  | return transport_socket_->IsConnected(); | 
|  | // Don't return true if still connecting.  Shouldn't really matter, either | 
|  | // way. | 
|  | return false; | 
|  | } | 
|  |  | 
|  | bool HttpProxyClientSocketWrapper::IsConnectedAndIdle() const { | 
|  | if (transport_socket_) | 
|  | return transport_socket_->IsConnectedAndIdle(); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | const NetLogWithSource& HttpProxyClientSocketWrapper::NetLog() const { | 
|  | return net_log_; | 
|  | } | 
|  |  | 
|  | bool HttpProxyClientSocketWrapper::WasEverUsed() const { | 
|  | // TODO(mmenke):  This is a little weird.  Figure out if something else should | 
|  | // be done. | 
|  | if (transport_socket_) | 
|  | return transport_socket_->WasEverUsed(); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | bool HttpProxyClientSocketWrapper::WasAlpnNegotiated() const { | 
|  | if (transport_socket_) | 
|  | return transport_socket_->WasAlpnNegotiated(); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | NextProto HttpProxyClientSocketWrapper::GetNegotiatedProtocol() const { | 
|  | if (transport_socket_) | 
|  | return transport_socket_->GetNegotiatedProtocol(); | 
|  | return kProtoUnknown; | 
|  | } | 
|  |  | 
|  | bool HttpProxyClientSocketWrapper::GetSSLInfo(SSLInfo* ssl_info) { | 
|  | if (transport_socket_) | 
|  | return transport_socket_->GetSSLInfo(ssl_info); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | void HttpProxyClientSocketWrapper::GetConnectionAttempts( | 
|  | ConnectionAttempts* out) const { | 
|  | // TODO(mmenke): Not clear how reconnecting for auth fits into things. | 
|  | if (transport_socket_) { | 
|  | transport_socket_->GetConnectionAttempts(out); | 
|  | } else { | 
|  | out->clear(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void HttpProxyClientSocketWrapper::ClearConnectionAttempts() { | 
|  | if (transport_socket_) | 
|  | transport_socket_->ClearConnectionAttempts(); | 
|  | } | 
|  |  | 
|  | void HttpProxyClientSocketWrapper::AddConnectionAttempts( | 
|  | const ConnectionAttempts& attempts) { | 
|  | if (transport_socket_) | 
|  | transport_socket_->AddConnectionAttempts(attempts); | 
|  | } | 
|  |  | 
|  | int64_t HttpProxyClientSocketWrapper::GetTotalReceivedBytes() const { | 
|  | return transport_socket_->GetTotalReceivedBytes(); | 
|  | } | 
|  |  | 
|  | void HttpProxyClientSocketWrapper::ApplySocketTag(const SocketTag& tag) { | 
|  | // HttpProxyClientSocketPool only tags once connected, when transport_socket_ | 
|  | // is set. Socket tagging is not supported with tunneling. Socket tagging is | 
|  | // also not supported with proxy auth so ApplySocketTag() won't be called with | 
|  | // a specific (non-default) tag when transport_socket_ is cleared by | 
|  | // RestartWithAuth(). | 
|  | if (tunnel_ || !transport_socket_) { | 
|  | CHECK(tag == SocketTag()); | 
|  | } else { | 
|  | transport_socket_->ApplySocketTag(tag); | 
|  | } | 
|  | } | 
|  |  | 
|  | int HttpProxyClientSocketWrapper::Read(IOBuffer* buf, | 
|  | int buf_len, | 
|  | CompletionOnceCallback callback) { | 
|  | if (transport_socket_) | 
|  | return transport_socket_->Read(buf, buf_len, std::move(callback)); | 
|  | return ERR_SOCKET_NOT_CONNECTED; | 
|  | } | 
|  |  | 
|  | int HttpProxyClientSocketWrapper::ReadIfReady(IOBuffer* buf, | 
|  | int buf_len, | 
|  | CompletionOnceCallback callback) { | 
|  | if (transport_socket_) | 
|  | return transport_socket_->ReadIfReady(buf, buf_len, std::move(callback)); | 
|  | return ERR_SOCKET_NOT_CONNECTED; | 
|  | } | 
|  |  | 
|  | int HttpProxyClientSocketWrapper::CancelReadIfReady() { | 
|  | if (transport_socket_) | 
|  | return transport_socket_->CancelReadIfReady(); | 
|  | return OK; | 
|  | } | 
|  |  | 
|  | int HttpProxyClientSocketWrapper::Write( | 
|  | IOBuffer* buf, | 
|  | int buf_len, | 
|  | CompletionOnceCallback callback, | 
|  | const NetworkTrafficAnnotationTag& traffic_annotation) { | 
|  | if (transport_socket_) { | 
|  | return transport_socket_->Write(buf, buf_len, std::move(callback), | 
|  | traffic_annotation); | 
|  | } | 
|  | return ERR_SOCKET_NOT_CONNECTED; | 
|  | } | 
|  |  | 
|  | int HttpProxyClientSocketWrapper::SetReceiveBufferSize(int32_t size) { | 
|  | // TODO(mmenke):  Should this persist across reconnects?  Seems a little | 
|  | //     weird, and not done for normal reconnects. | 
|  | if (transport_socket_) | 
|  | return transport_socket_->SetReceiveBufferSize(size); | 
|  | return ERR_SOCKET_NOT_CONNECTED; | 
|  | } | 
|  |  | 
|  | int HttpProxyClientSocketWrapper::SetSendBufferSize(int32_t size) { | 
|  | if (transport_socket_) | 
|  | return transport_socket_->SetSendBufferSize(size); | 
|  | return ERR_SOCKET_NOT_CONNECTED; | 
|  | } | 
|  |  | 
|  | int HttpProxyClientSocketWrapper::GetPeerAddress(IPEndPoint* address) const { | 
|  | if (transport_socket_) | 
|  | return transport_socket_->GetPeerAddress(address); | 
|  | return ERR_SOCKET_NOT_CONNECTED; | 
|  | } | 
|  |  | 
|  | int HttpProxyClientSocketWrapper::GetLocalAddress(IPEndPoint* address) const { | 
|  | if (transport_socket_) | 
|  | return transport_socket_->GetLocalAddress(address); | 
|  | return ERR_SOCKET_NOT_CONNECTED; | 
|  | } | 
|  |  | 
|  | void HttpProxyClientSocketWrapper::OnIOComplete(int result) { | 
|  | int rv = DoLoop(result); | 
|  | if (rv != ERR_IO_PENDING) { | 
|  | connect_timer_.Stop(); | 
|  | // May delete |this|. | 
|  | std::move(connect_callback_).Run(rv); | 
|  | } | 
|  | } | 
|  |  | 
|  | int HttpProxyClientSocketWrapper::DoLoop(int result) { | 
|  | DCHECK_NE(next_state_, STATE_NONE); | 
|  |  | 
|  | int rv = result; | 
|  | do { | 
|  | State state = next_state_; | 
|  | next_state_ = STATE_NONE; | 
|  | switch (state) { | 
|  | case STATE_BEGIN_CONNECT: | 
|  | DCHECK_EQ(OK, rv); | 
|  | rv = DoBeginConnect(); | 
|  | break; | 
|  | case STATE_TCP_CONNECT: | 
|  | DCHECK_EQ(OK, rv); | 
|  | rv = DoTransportConnect(); | 
|  | break; | 
|  | case STATE_TCP_CONNECT_COMPLETE: | 
|  | rv = DoTransportConnectComplete(rv); | 
|  | break; | 
|  | case STATE_SSL_CONNECT: | 
|  | DCHECK_EQ(OK, rv); | 
|  | rv = DoSSLConnect(); | 
|  | break; | 
|  | case STATE_SSL_CONNECT_COMPLETE: | 
|  | rv = DoSSLConnectComplete(rv); | 
|  | break; | 
|  | case STATE_HTTP_PROXY_CONNECT: | 
|  | DCHECK_EQ(OK, rv); | 
|  | rv = DoHttpProxyConnect(); | 
|  | break; | 
|  | case STATE_HTTP_PROXY_CONNECT_COMPLETE: | 
|  | rv = DoHttpProxyConnectComplete(rv); | 
|  | break; | 
|  | case STATE_SPDY_PROXY_CREATE_STREAM: | 
|  | DCHECK_EQ(OK, rv); | 
|  | rv = DoSpdyProxyCreateStream(); | 
|  | break; | 
|  | case STATE_SPDY_PROXY_CREATE_STREAM_COMPLETE: | 
|  | rv = DoSpdyProxyCreateStreamComplete(rv); | 
|  | break; | 
|  | case STATE_QUIC_PROXY_CREATE_SESSION: | 
|  | DCHECK_EQ(OK, rv); | 
|  | #if !defined(QUIC_DISABLED_FOR_STARBOARD) | 
|  | rv = DoQuicProxyCreateSession(); | 
|  | #else | 
|  | NOTREACHED(); | 
|  | #endif | 
|  | break; | 
|  | case STATE_QUIC_PROXY_CREATE_STREAM: | 
|  | #if !defined(QUIC_DISABLED_FOR_STARBOARD) | 
|  | rv = DoQuicProxyCreateStream(rv); | 
|  | #else | 
|  | NOTREACHED(); | 
|  | #endif | 
|  | break; | 
|  | case STATE_QUIC_PROXY_CREATE_STREAM_COMPLETE: | 
|  | #if !defined(QUIC_DISABLED_FOR_STARBOARD) | 
|  | rv = DoQuicProxyCreateStreamComplete(rv); | 
|  | #else | 
|  | NOTREACHED(); | 
|  | #endif | 
|  | break; | 
|  | case STATE_RESTART_WITH_AUTH: | 
|  | DCHECK_EQ(OK, rv); | 
|  | rv = DoRestartWithAuth(); | 
|  | break; | 
|  | case STATE_RESTART_WITH_AUTH_COMPLETE: | 
|  | rv = DoRestartWithAuthComplete(rv); | 
|  | break; | 
|  | default: | 
|  | NOTREACHED() << "bad state"; | 
|  | rv = ERR_FAILED; | 
|  | break; | 
|  | } | 
|  | } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE); | 
|  |  | 
|  | return rv; | 
|  | } | 
|  |  | 
|  | int HttpProxyClientSocketWrapper::DoBeginConnect() { | 
|  | connect_start_time_ = base::TimeTicks::Now(); | 
|  | SetConnectTimer(connect_timeout_duration_); | 
|  | #if !defined(QUIC_DISABLED_FOR_STARBOARD) | 
|  | if (quic_version_ != quic::QUIC_VERSION_UNSUPPORTED) { | 
|  | #else | 
|  | if (false) { | 
|  | #endif | 
|  | next_state_ = STATE_QUIC_PROXY_CREATE_SESSION; | 
|  | } else if (transport_params_) { | 
|  | next_state_ = STATE_TCP_CONNECT; | 
|  | } else { | 
|  | next_state_ = STATE_SSL_CONNECT; | 
|  | } | 
|  | return OK; | 
|  | } | 
|  |  | 
|  | int HttpProxyClientSocketWrapper::DoTransportConnect() { | 
|  | next_state_ = STATE_TCP_CONNECT_COMPLETE; | 
|  | transport_socket_handle_.reset(new ClientSocketHandle()); | 
|  | return transport_socket_handle_->Init( | 
|  | group_name_, transport_params_, priority_, initial_socket_tag_, | 
|  | respect_limits_, | 
|  | base::Bind(&HttpProxyClientSocketWrapper::OnIOComplete, | 
|  | base::Unretained(this)), | 
|  | transport_pool_, net_log_); | 
|  | } | 
|  |  | 
|  | int HttpProxyClientSocketWrapper::DoTransportConnectComplete(int result) { | 
|  | if (result != OK) { | 
|  | UMA_HISTOGRAM_MEDIUM_TIMES("Net.HttpProxy.ConnectLatency.Insecure.Error", | 
|  | base::TimeTicks::Now() - connect_start_time_); | 
|  | return ERR_PROXY_CONNECTION_FAILED; | 
|  | } | 
|  |  | 
|  | // Reset the timer to just the length of time allowed for HttpProxy handshake | 
|  | // so that a fast TCP connection plus a slow HttpProxy failure doesn't take | 
|  | // longer to timeout than it should. | 
|  | SetConnectTimer(proxy_negotiation_timeout_duration_); | 
|  |  | 
|  | next_state_ = STATE_HTTP_PROXY_CONNECT; | 
|  | return result; | 
|  | } | 
|  |  | 
|  | int HttpProxyClientSocketWrapper::DoSSLConnect() { | 
|  | DCHECK(ssl_params_); | 
|  | if (tunnel_) { | 
|  | SpdySessionKey key(ssl_params_->GetDirectConnectionParams() | 
|  | ->destination() | 
|  | .host_port_pair(), | 
|  | ProxyServer::Direct(), PRIVACY_MODE_DISABLED, | 
|  | initial_socket_tag_); | 
|  | if (spdy_session_pool_->FindAvailableSession( | 
|  | key, /* enable_ip_based_pooling = */ true, | 
|  | /* is_websocket = */ false, net_log_)) { | 
|  | using_spdy_ = true; | 
|  | next_state_ = STATE_SPDY_PROXY_CREATE_STREAM; | 
|  | return OK; | 
|  | } | 
|  | } | 
|  | next_state_ = STATE_SSL_CONNECT_COMPLETE; | 
|  | transport_socket_handle_.reset(new ClientSocketHandle()); | 
|  | return transport_socket_handle_->Init( | 
|  | group_name_, ssl_params_, priority_, initial_socket_tag_, respect_limits_, | 
|  | base::Bind(&HttpProxyClientSocketWrapper::OnIOComplete, | 
|  | base::Unretained(this)), | 
|  | ssl_pool_, net_log_); | 
|  | } | 
|  |  | 
|  | int HttpProxyClientSocketWrapper::DoSSLConnectComplete(int result) { | 
|  | if (result == ERR_SSL_CLIENT_AUTH_CERT_NEEDED) { | 
|  | DCHECK( | 
|  | transport_socket_handle_->ssl_error_response_info().cert_request_info); | 
|  | UMA_HISTOGRAM_MEDIUM_TIMES("Net.HttpProxy.ConnectLatency.Secure.Error", | 
|  | base::TimeTicks::Now() - connect_start_time_); | 
|  | error_response_info_.reset(new HttpResponseInfo( | 
|  | transport_socket_handle_->ssl_error_response_info())); | 
|  | error_response_info_->cert_request_info->is_proxy = true; | 
|  | return result; | 
|  | } | 
|  |  | 
|  | if (IsCertificateError(result)) { | 
|  | UMA_HISTOGRAM_MEDIUM_TIMES("Net.HttpProxy.ConnectLatency.Secure.Error", | 
|  | base::TimeTicks::Now() - connect_start_time_); | 
|  | if (ssl_params_->load_flags() & LOAD_IGNORE_ALL_CERT_ERRORS) { | 
|  | result = OK; | 
|  | } else { | 
|  | // TODO(rch): allow the user to deal with proxy cert errors in the | 
|  | // same way as server cert errors. | 
|  | transport_socket_handle_->socket()->Disconnect(); | 
|  | return ERR_PROXY_CERTIFICATE_INVALID; | 
|  | } | 
|  | } | 
|  | // A SPDY session to the proxy completed prior to resolving the proxy | 
|  | // hostname. Surface this error, and allow the delegate to retry. | 
|  | // See crbug.com/334413. | 
|  | if (result == ERR_SPDY_SESSION_ALREADY_EXISTS) { | 
|  | DCHECK(!transport_socket_handle_->socket()); | 
|  | return ERR_SPDY_SESSION_ALREADY_EXISTS; | 
|  | } | 
|  | if (result < 0) { | 
|  | UMA_HISTOGRAM_MEDIUM_TIMES("Net.HttpProxy.ConnectLatency.Secure.Error", | 
|  | base::TimeTicks::Now() - connect_start_time_); | 
|  | if (transport_socket_handle_->socket()) | 
|  | transport_socket_handle_->socket()->Disconnect(); | 
|  | return ERR_PROXY_CONNECTION_FAILED; | 
|  | } | 
|  |  | 
|  | negotiated_protocol_ = | 
|  | transport_socket_handle_->socket()->GetNegotiatedProtocol(); | 
|  | using_spdy_ = negotiated_protocol_ == kProtoHTTP2; | 
|  |  | 
|  | // Reset the timer to just the length of time allowed for HttpProxy handshake | 
|  | // so that a fast SSL connection plus a slow HttpProxy failure doesn't take | 
|  | // longer to timeout than it should. | 
|  | SetConnectTimer(proxy_negotiation_timeout_duration_); | 
|  |  | 
|  | // TODO(rch): If we ever decide to implement a "trusted" SPDY proxy | 
|  | // (one that we speak SPDY over SSL to, but to which we send HTTPS | 
|  | // request directly instead of through CONNECT tunnels, then we | 
|  | // need to add a predicate to this if statement so we fall through | 
|  | // to the else case. (HttpProxyClientSocket currently acts as | 
|  | // a "trusted" SPDY proxy). | 
|  | if (using_spdy_ && tunnel_) { | 
|  | next_state_ = STATE_SPDY_PROXY_CREATE_STREAM; | 
|  | } else { | 
|  | next_state_ = STATE_HTTP_PROXY_CONNECT; | 
|  | } | 
|  | return result; | 
|  | } | 
|  |  | 
|  | int HttpProxyClientSocketWrapper::DoHttpProxyConnect() { | 
|  | next_state_ = STATE_HTTP_PROXY_CONNECT_COMPLETE; | 
|  |  | 
|  | if (transport_params_) { | 
|  | UMA_HISTOGRAM_MEDIUM_TIMES("Net.HttpProxy.ConnectLatency.Insecure.Success", | 
|  | base::TimeTicks::Now() - connect_start_time_); | 
|  | } else { | 
|  | UMA_HISTOGRAM_MEDIUM_TIMES("Net.HttpProxy.ConnectLatency.Secure.Success", | 
|  | base::TimeTicks::Now() - connect_start_time_); | 
|  | } | 
|  |  | 
|  | // Add a HttpProxy connection on top of the tcp socket. | 
|  | transport_socket_ = | 
|  | transport_pool_->client_socket_factory()->CreateProxyClientSocket( | 
|  | std::move(transport_socket_handle_), user_agent_, endpoint_, | 
|  | http_auth_controller_.get(), tunnel_, using_spdy_, | 
|  | negotiated_protocol_, ssl_params_.get() != nullptr, | 
|  | traffic_annotation_); | 
|  | return transport_socket_->Connect(base::Bind( | 
|  | &HttpProxyClientSocketWrapper::OnIOComplete, base::Unretained(this))); | 
|  | } | 
|  |  | 
|  | int HttpProxyClientSocketWrapper::DoHttpProxyConnectComplete(int result) { | 
|  | if (result == ERR_HTTP_1_1_REQUIRED) | 
|  | return ERR_PROXY_HTTP_1_1_REQUIRED; | 
|  |  | 
|  | return result; | 
|  | } | 
|  |  | 
|  | int HttpProxyClientSocketWrapper::DoSpdyProxyCreateStream() { | 
|  | DCHECK(using_spdy_); | 
|  | DCHECK(tunnel_); | 
|  | DCHECK(ssl_params_); | 
|  | SpdySessionKey key( | 
|  | ssl_params_->GetDirectConnectionParams()->destination().host_port_pair(), | 
|  | ProxyServer::Direct(), PRIVACY_MODE_DISABLED, initial_socket_tag_); | 
|  | base::WeakPtr<SpdySession> spdy_session = | 
|  | spdy_session_pool_->FindAvailableSession( | 
|  | key, /* enable_ip_based_pooling = */ true, | 
|  | /* is_websocket = */ false, net_log_); | 
|  | // It's possible that a session to the proxy has recently been created | 
|  | if (spdy_session) { | 
|  | if (transport_socket_handle_.get()) { | 
|  | if (transport_socket_handle_->socket()) | 
|  | transport_socket_handle_->socket()->Disconnect(); | 
|  | transport_socket_handle_->Reset(); | 
|  | } | 
|  | } else { | 
|  | // Create a session direct to the proxy itself | 
|  | spdy_session = spdy_session_pool_->CreateAvailableSessionFromSocket( | 
|  | key, is_trusted_proxy_, std::move(transport_socket_handle_), net_log_); | 
|  | DCHECK(spdy_session); | 
|  | } | 
|  |  | 
|  | next_state_ = STATE_SPDY_PROXY_CREATE_STREAM_COMPLETE; | 
|  | return spdy_stream_request_.StartRequest( | 
|  | SPDY_BIDIRECTIONAL_STREAM, spdy_session, | 
|  | GURL("https://" + endpoint_.ToString()), priority_, initial_socket_tag_, | 
|  | spdy_session->net_log(), | 
|  | base::Bind(&HttpProxyClientSocketWrapper::OnIOComplete, | 
|  | base::Unretained(this)), | 
|  | traffic_annotation_); | 
|  | } | 
|  |  | 
|  | int HttpProxyClientSocketWrapper::DoSpdyProxyCreateStreamComplete(int result) { | 
|  | if (result < 0) | 
|  | return result; | 
|  |  | 
|  | next_state_ = STATE_HTTP_PROXY_CONNECT_COMPLETE; | 
|  | base::WeakPtr<SpdyStream> stream = spdy_stream_request_.ReleaseStream(); | 
|  | DCHECK(stream.get()); | 
|  | // |transport_socket_| will set itself as |stream|'s delegate. | 
|  | transport_socket_.reset(new SpdyProxyClientSocket( | 
|  | stream, user_agent_, endpoint_, net_log_, http_auth_controller_.get())); | 
|  | return transport_socket_->Connect(base::Bind( | 
|  | &HttpProxyClientSocketWrapper::OnIOComplete, base::Unretained(this))); | 
|  | } | 
|  |  | 
|  | #if !defined(QUIC_DISABLED_FOR_STARBOARD) | 
|  | int HttpProxyClientSocketWrapper::DoQuicProxyCreateSession() { | 
|  | DCHECK(ssl_params_); | 
|  | DCHECK(tunnel_); | 
|  | next_state_ = STATE_QUIC_PROXY_CREATE_STREAM; | 
|  | const HostPortPair& proxy_server = | 
|  | ssl_params_->GetDirectConnectionParams()->destination().host_port_pair(); | 
|  | return quic_stream_request_.Request( | 
|  | proxy_server, quic_version_, ssl_params_->privacy_mode(), priority_, | 
|  | initial_socket_tag_, ssl_params_->ssl_config().GetCertVerifyFlags(), | 
|  | GURL("https://" + proxy_server.ToString()), net_log_, | 
|  | &quic_net_error_details_, | 
|  | /*failed_on_default_network_callback=*/CompletionOnceCallback(), | 
|  | base::Bind(&HttpProxyClientSocketWrapper::OnIOComplete, | 
|  | base::Unretained(this))); | 
|  | } | 
|  |  | 
|  | int HttpProxyClientSocketWrapper::DoQuicProxyCreateStream(int result) { | 
|  | if (result < 0) | 
|  | return result; | 
|  |  | 
|  | next_state_ = STATE_QUIC_PROXY_CREATE_STREAM_COMPLETE; | 
|  |  | 
|  | #if !defined(QUIC_DISABLED_FOR_STARBOARD) | 
|  | quic_session_ = quic_stream_request_.ReleaseSessionHandle(); | 
|  | return quic_session_->RequestStream( | 
|  | false, | 
|  | base::Bind(&HttpProxyClientSocketWrapper::OnIOComplete, | 
|  | base::Unretained(this)), | 
|  | traffic_annotation_); | 
|  | #else | 
|  | return ERR_NOT_IMPLEMENTED; | 
|  | #endif | 
|  | } | 
|  |  | 
|  | int HttpProxyClientSocketWrapper::DoQuicProxyCreateStreamComplete(int result) { | 
|  | if (result < 0) | 
|  | return result; | 
|  |  | 
|  | next_state_ = STATE_HTTP_PROXY_CONNECT_COMPLETE; | 
|  |  | 
|  | #if !defined(QUIC_DISABLED_FOR_STARBOARD) | 
|  | std::unique_ptr<QuicChromiumClientStream::Handle> quic_stream = | 
|  | quic_session_->ReleaseStream(); | 
|  |  | 
|  | spdy::SpdyPriority spdy_priority = | 
|  | ConvertRequestPriorityToQuicPriority(priority_); | 
|  | quic_stream->SetPriority(spdy_priority); | 
|  |  | 
|  | transport_socket_.reset(new QuicProxyClientSocket( | 
|  | std::move(quic_stream), std::move(quic_session_), user_agent_, endpoint_, | 
|  | net_log_, http_auth_controller_.get())); | 
|  | return transport_socket_->Connect(base::Bind( | 
|  | &HttpProxyClientSocketWrapper::OnIOComplete, base::Unretained(this))); | 
|  | #else | 
|  | return ERR_NOT_IMPLEMENTED; | 
|  | #endif | 
|  | } | 
|  | #endif | 
|  |  | 
|  | int HttpProxyClientSocketWrapper::DoRestartWithAuth() { | 
|  | DCHECK(transport_socket_); | 
|  |  | 
|  | next_state_ = STATE_RESTART_WITH_AUTH_COMPLETE; | 
|  | return transport_socket_->RestartWithAuth(base::BindOnce( | 
|  | &HttpProxyClientSocketWrapper::OnIOComplete, base::Unretained(this))); | 
|  | } | 
|  |  | 
|  | int HttpProxyClientSocketWrapper::DoRestartWithAuthComplete(int result) { | 
|  | DCHECK_NE(ERR_IO_PENDING, result); | 
|  |  | 
|  | // If the connection could not be reused to attempt to send proxy auth | 
|  | // credentials, try reconnecting. Do not reset the HttpAuthController in this | 
|  | // case; the server may, for instance, send "Proxy-Connection: close" and | 
|  | // expect that each leg of the authentication progress on separate | 
|  | // connections. | 
|  | bool reconnect = result == ERR_UNABLE_TO_REUSE_CONNECTION_FOR_PROXY_AUTH; | 
|  |  | 
|  | // If auth credentials were sent but the connection was closed, the server may | 
|  | // have timed out while the user was selecting credentials. Retry once. | 
|  | if (!has_restarted_ && | 
|  | (result == ERR_CONNECTION_CLOSED || result == ERR_CONNECTION_RESET || | 
|  | result == ERR_CONNECTION_ABORTED || | 
|  | result == ERR_SOCKET_NOT_CONNECTED)) { | 
|  | reconnect = true; | 
|  | has_restarted_ = true; | 
|  |  | 
|  | // Release any auth state bound to the connection. The new connection will | 
|  | // start the current scheme from scratch. | 
|  | if (http_auth_controller_) | 
|  | http_auth_controller_->OnConnectionClosed(); | 
|  | } | 
|  |  | 
|  | if (reconnect) { | 
|  | // Attempt to create a new one. | 
|  | transport_socket_.reset(); | 
|  |  | 
|  | // Reconnect with HIGHEST priority to get in front of other requests that | 
|  | // don't yet have the information |http_auth_controller_| does. | 
|  | // TODO(mmenke): This may still result in waiting in line, if there are | 
|  | //               other HIGHEST priority requests. Consider a workaround for | 
|  | //               that. Starting the new request before releasing the old | 
|  | //               socket and using RespectLimits::Disabled would work, | 
|  | //               without exceding the the socket pool limits (Since the old | 
|  | //               socket would free up the extra socket slot when destroyed). | 
|  | priority_ = HIGHEST; | 
|  | next_state_ = STATE_BEGIN_CONNECT; | 
|  | return OK; | 
|  | } | 
|  |  | 
|  | return result; | 
|  | } | 
|  |  | 
|  | void HttpProxyClientSocketWrapper::SetConnectTimer(base::TimeDelta delay) { | 
|  | connect_timer_.Stop(); | 
|  | connect_timer_.Start(FROM_HERE, delay, this, | 
|  | &HttpProxyClientSocketWrapper::ConnectTimeout); | 
|  | } | 
|  |  | 
|  | void HttpProxyClientSocketWrapper::ConnectTimeout() { | 
|  | // Timer shouldn't be running if next_state_ is STATE_NONE. | 
|  | DCHECK_NE(STATE_NONE, next_state_); | 
|  | DCHECK(!connect_callback_.is_null()); | 
|  |  | 
|  | if (next_state_ == STATE_TCP_CONNECT_COMPLETE || | 
|  | next_state_ == STATE_SSL_CONNECT_COMPLETE) { | 
|  | if (transport_params_) { | 
|  | UMA_HISTOGRAM_MEDIUM_TIMES( | 
|  | "Net.HttpProxy.ConnectLatency.Insecure.TimedOut", | 
|  | base::TimeTicks::Now() - connect_start_time_); | 
|  | } else { | 
|  | UMA_HISTOGRAM_MEDIUM_TIMES("Net.HttpProxy.ConnectLatency.Secure.TimedOut", | 
|  | base::TimeTicks::Now() - connect_start_time_); | 
|  | } | 
|  | } | 
|  |  | 
|  | CompletionOnceCallback callback = std::move(connect_callback_); | 
|  | Disconnect(); | 
|  | std::move(callback).Run(ERR_CONNECTION_TIMED_OUT); | 
|  | } | 
|  |  | 
|  | const HostResolver::RequestInfo& | 
|  | HttpProxyClientSocketWrapper::GetDestination() { | 
|  | if (transport_params_) { | 
|  | return transport_params_->destination(); | 
|  | } else { | 
|  | return ssl_params_->GetDirectConnectionParams()->destination(); | 
|  | } | 
|  | } | 
|  |  | 
|  | }  // namespace net |