// 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/spdy/spdy_proxy_client_socket.h" #include // min #include "base/bind.h" #include "base/bind_helpers.h" #include "base/callback_helpers.h" #include "base/logging.h" #include "base/strings/string_util.h" #include "base/values.h" #include "net/base/auth.h" #include "net/base/io_buffer.h" #include "net/base/net_util.h" #include "net/http/http_auth_cache.h" #include "net/http/http_auth_handler_factory.h" #include "net/http/http_response_headers.h" #include "net/http/proxy_connect_redirect_http_stream.h" #include "net/spdy/spdy_http_utils.h" #include "url/gurl.h" namespace net { SpdyProxyClientSocket::SpdyProxyClientSocket( const base::WeakPtr& spdy_stream, const std::string& user_agent, const HostPortPair& endpoint, const GURL& url, const HostPortPair& proxy_server, const BoundNetLog& source_net_log, HttpAuthCache* auth_cache, HttpAuthHandlerFactory* auth_handler_factory) : next_state_(STATE_DISCONNECTED), spdy_stream_(spdy_stream), endpoint_(endpoint), auth_( new HttpAuthController(HttpAuth::AUTH_PROXY, GURL("https://" + proxy_server.ToString()), auth_cache, auth_handler_factory)), user_buffer_len_(0), write_buffer_len_(0), was_ever_used_(false), redirect_has_load_timing_info_(false), net_log_(BoundNetLog::Make(spdy_stream->net_log().net_log(), NetLog::SOURCE_PROXY_CLIENT_SOCKET)), weak_factory_(this) { request_.method = "CONNECT"; request_.url = url; if (!user_agent.empty()) request_.extra_headers.SetHeader(HttpRequestHeaders::kUserAgent, user_agent); net_log_.BeginEvent(NetLog::TYPE_SOCKET_ALIVE, source_net_log.source().ToEventParametersCallback()); net_log_.AddEvent( NetLog::TYPE_SPDY_PROXY_CLIENT_SESSION, spdy_stream->net_log().source().ToEventParametersCallback()); spdy_stream_->SetDelegate(this); was_ever_used_ = spdy_stream_->WasEverUsed(); } SpdyProxyClientSocket::~SpdyProxyClientSocket() { Disconnect(); net_log_.EndEvent(NetLog::TYPE_SOCKET_ALIVE); } const HttpResponseInfo* SpdyProxyClientSocket::GetConnectResponseInfo() const { return response_.headers.get() ? &response_ : NULL; } const scoped_refptr& SpdyProxyClientSocket::GetAuthController() const { return auth_; } int SpdyProxyClientSocket::RestartWithAuth(const CompletionCallback& callback) { // A SPDY Stream can only handle a single request, so the underlying // stream may not be reused and a new SpdyProxyClientSocket must be // created (possibly on top of the same SPDY Session). next_state_ = STATE_DISCONNECTED; return OK; } bool SpdyProxyClientSocket::IsUsingSpdy() const { return true; } NextProto SpdyProxyClientSocket::GetProtocolNegotiated() const { // Save the negotiated protocol SSLInfo ssl_info; bool was_npn_negotiated; NextProto protocol_negotiated; spdy_stream_->GetSSLInfo(&ssl_info, &was_npn_negotiated, &protocol_negotiated); return protocol_negotiated; } HttpStream* SpdyProxyClientSocket::CreateConnectResponseStream() { return new ProxyConnectRedirectHttpStream( redirect_has_load_timing_info_ ? &redirect_load_timing_info_ : NULL); } // Sends a SYN_STREAM frame to the proxy with a CONNECT request // for the specified endpoint. Waits for the server to send back // a SYN_REPLY frame. OK will be returned if the status is 200. // ERR_TUNNEL_CONNECTION_FAILED will be returned for any other status. // In any of these cases, Read() may be called to retrieve the HTTP // response body. Any other return values should be considered fatal. // TODO(rch): handle 407 proxy auth requested correctly, perhaps // by creating a new stream for the subsequent request. // TODO(rch): create a more appropriate error code to disambiguate // the HTTPS Proxy tunnel failure from an HTTP Proxy tunnel failure. int SpdyProxyClientSocket::Connect(const CompletionCallback& callback) { DCHECK(read_callback_.is_null()); if (next_state_ == STATE_OPEN) return OK; DCHECK_EQ(STATE_DISCONNECTED, next_state_); next_state_ = STATE_GENERATE_AUTH_TOKEN; int rv = DoLoop(OK); if (rv == ERR_IO_PENDING) read_callback_ = callback; return rv; } void SpdyProxyClientSocket::Disconnect() { read_buffer_queue_.Clear(); user_buffer_ = NULL; user_buffer_len_ = 0; read_callback_.Reset(); write_buffer_len_ = 0; write_callback_.Reset(); next_state_ = STATE_DISCONNECTED; if (spdy_stream_.get()) { // This will cause OnClose to be invoked, which takes care of // cleaning up all the internal state. spdy_stream_->Cancel(); DCHECK(!spdy_stream_.get()); } } bool SpdyProxyClientSocket::IsConnected() const { return next_state_ == STATE_OPEN; } bool SpdyProxyClientSocket::IsConnectedAndIdle() const { return IsConnected() && read_buffer_queue_.IsEmpty() && spdy_stream_->IsOpen(); } const BoundNetLog& SpdyProxyClientSocket::NetLog() const { return net_log_; } void SpdyProxyClientSocket::SetSubresourceSpeculation() { // TODO(rch): what should this implementation be? } void SpdyProxyClientSocket::SetOmniboxSpeculation() { // TODO(rch): what should this implementation be? } bool SpdyProxyClientSocket::WasEverUsed() const { return was_ever_used_ || (spdy_stream_.get() && spdy_stream_->WasEverUsed()); } bool SpdyProxyClientSocket::UsingTCPFastOpen() const { return false; } bool SpdyProxyClientSocket::WasNpnNegotiated() const { return false; } NextProto SpdyProxyClientSocket::GetNegotiatedProtocol() const { return kProtoUnknown; } bool SpdyProxyClientSocket::GetSSLInfo(SSLInfo* ssl_info) { bool was_npn_negotiated; NextProto protocol_negotiated; return spdy_stream_->GetSSLInfo(ssl_info, &was_npn_negotiated, &protocol_negotiated); } int SpdyProxyClientSocket::Read(IOBuffer* buf, int buf_len, const CompletionCallback& callback) { DCHECK(read_callback_.is_null()); DCHECK(!user_buffer_.get()); if (next_state_ == STATE_DISCONNECTED) return ERR_SOCKET_NOT_CONNECTED; if (next_state_ == STATE_CLOSED && read_buffer_queue_.IsEmpty()) { return 0; } DCHECK(next_state_ == STATE_OPEN || next_state_ == STATE_CLOSED); DCHECK(buf); size_t result = PopulateUserReadBuffer(buf->data(), buf_len); if (result == 0) { user_buffer_ = buf; user_buffer_len_ = static_cast(buf_len); DCHECK(!callback.is_null()); read_callback_ = callback; return ERR_IO_PENDING; } user_buffer_ = NULL; return result; } size_t SpdyProxyClientSocket::PopulateUserReadBuffer(char* data, size_t len) { return read_buffer_queue_.Dequeue(data, len); } int SpdyProxyClientSocket::Write(IOBuffer* buf, int buf_len, const CompletionCallback& callback) { DCHECK(write_callback_.is_null()); if (next_state_ != STATE_OPEN) return ERR_SOCKET_NOT_CONNECTED; DCHECK(spdy_stream_.get()); spdy_stream_->SendData(buf, buf_len, MORE_DATA_TO_SEND); net_log_.AddByteTransferEvent(NetLog::TYPE_SOCKET_BYTES_SENT, buf_len, buf->data()); write_callback_ = callback; write_buffer_len_ = buf_len; return ERR_IO_PENDING; } int SpdyProxyClientSocket::SetReceiveBufferSize(int32 size) { // Since this StreamSocket sits on top of a shared SpdySession, it // is not safe for callers to change this underlying socket. return ERR_NOT_IMPLEMENTED; } int SpdyProxyClientSocket::SetSendBufferSize(int32 size) { // Since this StreamSocket sits on top of a shared SpdySession, it // is not safe for callers to change this underlying socket. return ERR_NOT_IMPLEMENTED; } int SpdyProxyClientSocket::GetPeerAddress(IPEndPoint* address) const { if (!IsConnected()) return ERR_SOCKET_NOT_CONNECTED; return spdy_stream_->GetPeerAddress(address); } int SpdyProxyClientSocket::GetLocalAddress(IPEndPoint* address) const { if (!IsConnected()) return ERR_SOCKET_NOT_CONNECTED; return spdy_stream_->GetLocalAddress(address); } void SpdyProxyClientSocket::LogBlockedTunnelResponse() const { ProxyClientSocket::LogBlockedTunnelResponse( response_.headers->response_code(), request_.url, /* is_https_proxy = */ true); } void SpdyProxyClientSocket::OnIOComplete(int result) { DCHECK_NE(STATE_DISCONNECTED, next_state_); int rv = DoLoop(result); if (rv != ERR_IO_PENDING) { CompletionCallback c = read_callback_; read_callback_.Reset(); c.Run(rv); } } int SpdyProxyClientSocket::DoLoop(int last_io_result) { DCHECK_NE(next_state_, STATE_DISCONNECTED); int rv = last_io_result; do { State state = next_state_; next_state_ = STATE_DISCONNECTED; 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: net_log_.EndEventWithNetErrorCode( NetLog::TYPE_HTTP_TRANSACTION_TUNNEL_SEND_REQUEST, rv); rv = DoSendRequestComplete(rv); if (rv >= 0 || rv == ERR_IO_PENDING) { // Emit extra event so can use the same events as // HttpProxyClientSocket. net_log_.BeginEvent( NetLog::TYPE_HTTP_TRANSACTION_TUNNEL_READ_HEADERS); } break; case STATE_READ_REPLY_COMPLETE: rv = DoReadReplyComplete(rv); net_log_.EndEventWithNetErrorCode( NetLog::TYPE_HTTP_TRANSACTION_TUNNEL_READ_HEADERS, rv); break; default: NOTREACHED() << "bad state"; rv = ERR_UNEXPECTED; break; } } while (rv != ERR_IO_PENDING && next_state_ != STATE_DISCONNECTED && next_state_ != STATE_OPEN); return rv; } int SpdyProxyClientSocket::DoGenerateAuthToken() { next_state_ = STATE_GENERATE_AUTH_TOKEN_COMPLETE; return auth_->MaybeGenerateAuthToken( &request_, base::Bind(&SpdyProxyClientSocket::OnIOComplete, weak_factory_.GetWeakPtr()), net_log_); } int SpdyProxyClientSocket::DoGenerateAuthTokenComplete(int result) { DCHECK_NE(ERR_IO_PENDING, result); if (result == OK) next_state_ = STATE_SEND_REQUEST; return result; } int SpdyProxyClientSocket::DoSendRequest() { next_state_ = STATE_SEND_REQUEST_COMPLETE; // Add Proxy-Authentication header if necessary. 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); net_log_.AddEvent( NetLog::TYPE_HTTP_TRANSACTION_SEND_TUNNEL_HEADERS, base::Bind(&HttpRequestHeaders::NetLogCallback, base::Unretained(&request_headers), &request_line)); request_.extra_headers.MergeFrom(request_headers); scoped_ptr headers(new SpdyHeaderBlock()); CreateSpdyHeadersFromHttpRequest(request_, request_headers, headers.get(), spdy_stream_->GetProtocolVersion(), true); // Reset the URL to be the endpoint of the connection if (spdy_stream_->GetProtocolVersion() > 2) { (*headers)[":path"] = endpoint_.ToString(); headers->erase(":scheme"); } else { (*headers)["url"] = endpoint_.ToString(); headers->erase("scheme"); } return spdy_stream_->SendRequestHeaders(headers.Pass(), MORE_DATA_TO_SEND); } int SpdyProxyClientSocket::DoSendRequestComplete(int result) { if (result < 0) return result; // Wait for SYN_REPLY frame from the server next_state_ = STATE_READ_REPLY_COMPLETE; return ERR_IO_PENDING; } int SpdyProxyClientSocket::DoReadReplyComplete(int result) { // We enter this method directly from DoSendRequestComplete, since // we are notified by a callback when the SYN_REPLY frame arrives 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 next_state_ = STATE_OPEN; return OK; case 302: // Found / Moved Temporarily // Try to return a sanitized response so we can follow auth redirects. // If we can't, fail the tunnel connection. if (SanitizeProxyRedirect(&response_, request_.url)) { redirect_has_load_timing_info_ = spdy_stream_->GetLoadTimingInfo(&redirect_load_timing_info_); spdy_stream_->DetachDelegate(); next_state_ = STATE_DISCONNECTED; return ERR_HTTPS_PROXY_TUNNEL_RESPONSE; } else { LogBlockedTunnelResponse(); return ERR_TUNNEL_CONNECTION_FAILED; } case 407: // Proxy Authentication Required next_state_ = STATE_OPEN; return HandleProxyAuthChallenge(auth_.get(), &response_, net_log_); default: // Ignore response to avoid letting the proxy impersonate the target // server. (See http://crbug.com/137891.) LogBlockedTunnelResponse(); return ERR_TUNNEL_CONNECTION_FAILED; } } // SpdyStream::Delegate methods: // Called when SYN frame has been sent. // Returns true if no more data to be sent after SYN frame. void SpdyProxyClientSocket::OnRequestHeadersSent() { DCHECK_EQ(next_state_, STATE_SEND_REQUEST_COMPLETE); OnIOComplete(OK); } SpdyResponseHeadersStatus SpdyProxyClientSocket::OnResponseHeadersUpdated( const SpdyHeaderBlock& response_headers) { // If we've already received the reply, existing headers are too late. // TODO(mbelshe): figure out a way to make HEADERS frames useful after the // initial response. if (next_state_ != STATE_READ_REPLY_COMPLETE) return RESPONSE_HEADERS_ARE_COMPLETE; // Save the response if (!SpdyHeadersToHttpResponse( response_headers, spdy_stream_->GetProtocolVersion(), &response_)) return RESPONSE_HEADERS_ARE_INCOMPLETE; OnIOComplete(OK); return RESPONSE_HEADERS_ARE_COMPLETE; } // Called when data is received or on EOF (if |buffer| is NULL). void SpdyProxyClientSocket::OnDataReceived(scoped_ptr buffer) { if (buffer) { net_log_.AddByteTransferEvent(NetLog::TYPE_SOCKET_BYTES_RECEIVED, buffer->GetRemainingSize(), buffer->GetRemainingData()); read_buffer_queue_.Enqueue(buffer.Pass()); } else { net_log_.AddByteTransferEvent(NetLog::TYPE_SOCKET_BYTES_RECEIVED, 0, NULL); } if (!read_callback_.is_null()) { int rv = PopulateUserReadBuffer(user_buffer_->data(), user_buffer_len_); CompletionCallback c = read_callback_; read_callback_.Reset(); user_buffer_ = NULL; user_buffer_len_ = 0; c.Run(rv); } } void SpdyProxyClientSocket::OnDataSent() { DCHECK(!write_callback_.is_null()); int rv = write_buffer_len_; write_buffer_len_ = 0; // Proxy write callbacks result in deep callback chains. Post to allow the // stream's write callback chain to unwind (see crbug.com/355511). base::MessageLoop::current()->PostTask( FROM_HERE, base::Bind(ResetAndReturn(&write_callback_), rv)); } void SpdyProxyClientSocket::OnClose(int status) { was_ever_used_ = spdy_stream_->WasEverUsed(); spdy_stream_.reset(); bool connecting = next_state_ != STATE_DISCONNECTED && next_state_ < STATE_OPEN; if (next_state_ == STATE_OPEN) next_state_ = STATE_CLOSED; else next_state_ = STATE_DISCONNECTED; base::WeakPtr weak_ptr = weak_factory_.GetWeakPtr(); CompletionCallback write_callback = write_callback_; write_callback_.Reset(); write_buffer_len_ = 0; // If we're in the middle of connecting, we need to make sure // we invoke the connect callback. if (connecting) { DCHECK(!read_callback_.is_null()); CompletionCallback read_callback = read_callback_; read_callback_.Reset(); read_callback.Run(status); } else if (!read_callback_.is_null()) { // If we have a read_callback_, the we need to make sure we call it back. OnDataReceived(scoped_ptr()); } // This may have been deleted by read_callback_, so check first. if (weak_ptr.get() && !write_callback.is_null()) write_callback.Run(ERR_CONNECTION_CLOSED); } } // namespace net