// 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/logging.h" #include "base/string_util.h" #include "base/values.h" #include "googleurl/src/gurl.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/spdy/spdy_http_utils.h" namespace net { SpdyProxyClientSocket::SpdyProxyClientSocket( SpdyStream* 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_(NULL), write_buffer_len_(0), write_bytes_outstanding_(0), ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)), net_log_(BoundNetLog::Make(spdy_stream->net_log().net_log(), NetLog::SOURCE_PROXY_CLIENT_SOCKET)) { 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 ? &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() { DCHECK(response_stream_.get()); return response_stream_.release(); } // 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_.clear(); user_buffer_ = NULL; read_callback_.Reset(); write_buffer_len_ = 0; write_bytes_outstanding_ = 0; write_callback_.Reset(); next_state_ = STATE_DISCONNECTED; if (spdy_stream_) { // 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_.empty() && spdy_stream_->is_idle(); } 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_ && 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_); if (next_state_ == STATE_DISCONNECTED) return ERR_SOCKET_NOT_CONNECTED; if (next_state_ == STATE_CLOSED && read_buffer_.empty()) { return 0; } DCHECK(next_state_ == STATE_OPEN || next_state_ == STATE_CLOSED); DCHECK(buf); user_buffer_ = new DrainableIOBuffer(buf, buf_len); int result = PopulateUserReadBuffer(); if (result == 0) { DCHECK(!callback.is_null()); read_callback_ = callback; return ERR_IO_PENDING; } user_buffer_ = NULL; return result; } int SpdyProxyClientSocket::PopulateUserReadBuffer() { if (!user_buffer_) return ERR_IO_PENDING; int bytes_read = 0; while (!read_buffer_.empty() && user_buffer_->BytesRemaining() > 0) { scoped_refptr data = read_buffer_.front(); const int bytes_to_copy = std::min(user_buffer_->BytesRemaining(), data->BytesRemaining()); memcpy(user_buffer_->data(), data->data(), bytes_to_copy); user_buffer_->DidConsume(bytes_to_copy); bytes_read += bytes_to_copy; if (data->BytesRemaining() == bytes_to_copy) { // Consumed all data from this buffer read_buffer_.pop_front(); } else { data->DidConsume(bytes_to_copy); } } if (bytes_read > 0 && spdy_stream_) spdy_stream_->IncreaseRecvWindowSize(bytes_read); return user_buffer_->BytesConsumed(); } 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_); write_bytes_outstanding_= buf_len; if (buf_len <= kMaxSpdyFrameChunkSize) { spdy_stream_->QueueStreamData(buf, buf_len, DATA_FLAG_NONE); net_log_.AddByteTransferEvent(NetLog::TYPE_SOCKET_BYTES_SENT, buf_len, buf->data()); write_callback_ = callback; write_buffer_len_ = buf_len; return ERR_IO_PENDING; } // Since a SPDY Data frame can only include kMaxSpdyFrameChunkSize bytes // we need to send multiple data frames for (int i = 0; i < buf_len; i += kMaxSpdyFrameChunkSize) { int len = std::min(kMaxSpdyFrameChunkSize, buf_len - i); scoped_refptr iobuf(new DrainableIOBuffer(buf, i + len)); iobuf->SetOffset(i); spdy_stream_->QueueStreamData(iobuf, len, DATA_FLAG_NONE); net_log_.AddByteTransferEvent(NetLog::TYPE_SOCKET_BYTES_SENT, len, buf->data()); } if (write_bytes_outstanding_ > 0) { write_callback_ = callback; write_buffer_len_ = buf_len; return ERR_IO_PENDING; } else { return buf_len; } } bool SpdyProxyClientSocket::SetReceiveBufferSize(int32 size) { // Since this StreamSocket sits on top of a shared SpdySession, it // is not safe for callers to set change this underlying socket. return false; } bool SpdyProxyClientSocket::SetSendBufferSize(int32 size) { // Since this StreamSocket sits on top of a shared SpdySession, it // is not safe for callers to set change this underlying socket. return false; } 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"); } spdy_stream_->set_spdy_headers(headers.Pass()); return spdy_stream_->SendRequest(true); } 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)) { // Immediately hand off our SpdyStream to a newly created // SpdyHttpStream so that any subsequent SpdyFrames are processed in // the context of the HttpStream, not the socket. DCHECK(spdy_stream_); SpdyStream* stream = spdy_stream_; spdy_stream_ = NULL; response_stream_.reset(new SpdyHttpStream(NULL, false)); response_stream_->InitializeWithExistingStream(stream); 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_, &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. bool SpdyProxyClientSocket::OnSendHeadersComplete(int status) { DCHECK_EQ(next_state_, STATE_SEND_REQUEST_COMPLETE); OnIOComplete(status); // We return true here so that we send |spdy_stream_| into // STATE_OPEN (ala WebSockets). return true; } int SpdyProxyClientSocket::OnSendBody() { // Because we use |spdy_stream_| via STATE_OPEN (ala WebSockets) // OnSendBody() should never be called. NOTREACHED(); return ERR_UNEXPECTED; } int SpdyProxyClientSocket::OnSendBodyComplete(int /*status*/, bool* /*eof*/) { // Because we use |spdy_stream_| via STATE_OPEN (ala WebSockets) // OnSendBodyComplete() should never be called. NOTREACHED(); return ERR_UNEXPECTED; } int SpdyProxyClientSocket::OnResponseReceived( const SpdyHeaderBlock& response, base::Time response_time, int status) { // 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 OK; // Save the response if (!SpdyHeadersToHttpResponse( response, spdy_stream_->GetProtocolVersion(), &response_)) return ERR_INCOMPLETE_SPDY_HEADERS; OnIOComplete(status); return OK; } void SpdyProxyClientSocket::OnHeadersSent() { // Proxy client sockets don't send any HEADERS frame. NOTREACHED(); } // Called when data is received. int SpdyProxyClientSocket::OnDataReceived(const char* data, int length) { net_log_.AddByteTransferEvent(NetLog::TYPE_SOCKET_BYTES_RECEIVED, length, data); if (length > 0) { // Save the received data. scoped_refptr io_buffer(new IOBuffer(length)); memcpy(io_buffer->data(), data, length); read_buffer_.push_back( make_scoped_refptr(new DrainableIOBuffer(io_buffer, length))); } if (!read_callback_.is_null()) { int rv = PopulateUserReadBuffer(); CompletionCallback c = read_callback_; read_callback_.Reset(); user_buffer_ = NULL; c.Run(rv); } return OK; } void SpdyProxyClientSocket::OnDataSent(int length) { DCHECK(!write_callback_.is_null()); write_bytes_outstanding_ -= length; DCHECK_GE(write_bytes_outstanding_, 0); if (write_bytes_outstanding_ == 0) { int rv = write_buffer_len_; write_buffer_len_ = 0; write_bytes_outstanding_ = 0; CompletionCallback c = write_callback_; write_callback_.Reset(); c.Run(rv); } } void SpdyProxyClientSocket::OnClose(int status) { DCHECK(spdy_stream_); was_ever_used_ = spdy_stream_->WasEverUsed(); spdy_stream_ = NULL; 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; write_bytes_outstanding_ = 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(NULL, 0); } // This may have been deleted by read_callback_, so check first. if (weak_ptr && !write_callback.is_null()) write_callback.Run(ERR_CONNECTION_CLOSED); } } // namespace net