// Copyright (c) 2010 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 <algorithm> // min #include "base/logging.h" #include "base/string_util.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_net_log_params.h" #include "net/http/http_proxy_utils.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, HttpAuthCache* auth_cache, HttpAuthHandlerFactory* auth_handler_factory) : ALLOW_THIS_IN_INITIALIZER_LIST( io_callback_(this, &SpdyProxyClientSocket::OnIOComplete)), next_state_(STATE_DISCONNECTED), spdy_stream_(spdy_stream), read_callback_(NULL), write_callback_(NULL), endpoint_(endpoint), auth_( new HttpAuthController(HttpAuth::AUTH_PROXY, GURL("http://" + proxy_server.ToString()), auth_cache, auth_handler_factory)), user_buffer_(NULL), write_buffer_len_(0), write_bytes_outstanding_(0), eof_has_been_read_(false), net_log_(spdy_stream->net_log()) { request_.method = "CONNECT"; request_.url = url; if (!user_agent.empty()) request_.extra_headers.SetHeader(HttpRequestHeaders::kUserAgent, user_agent); spdy_stream_->SetDelegate(this); was_ever_used_ = spdy_stream_->WasEverUsed(); } SpdyProxyClientSocket::~SpdyProxyClientSocket() { Disconnect(); } const HttpResponseInfo* SpdyProxyClientSocket::GetConnectResponseInfo() const { return response_.headers ? &response_ : NULL; } 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. #ifdef ANDROID // TODO(kristianm): handle the case when wait_for_connect is true // (sync requests) #endif int SpdyProxyClientSocket::Connect(CompletionCallback* callback #ifdef ANDROID , bool wait_for_connect , bool valid_uid , uid_t calling_uid #endif ) { DCHECK(!read_callback_); 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_ = NULL; write_buffer_len_ = 0; write_bytes_outstanding_ = 0; write_callback_ = NULL; 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(); } bool SpdyProxyClientSocket::IsConnected() const { return next_state_ == STATE_OPEN || next_state_ == STATE_CLOSED; } bool SpdyProxyClientSocket::IsConnectedAndIdle() const { return IsConnected() && !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; } int SpdyProxyClientSocket::Read(IOBuffer* buf, int buf_len, CompletionCallback* callback) { DCHECK(!read_callback_); DCHECK(!user_buffer_); if (next_state_ == STATE_DISCONNECTED) return ERR_SOCKET_NOT_CONNECTED; if (!spdy_stream_ && read_buffer_.empty()) { if (eof_has_been_read_) return ERR_CONNECTION_CLOSED; eof_has_been_read_ = true; 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); read_callback_ = callback; return ERR_IO_PENDING; } user_buffer_ = NULL; return result; } int SpdyProxyClientSocket::PopulateUserReadBuffer() { if (!user_buffer_) return ERR_IO_PENDING; while (!read_buffer_.empty() && user_buffer_->BytesRemaining() > 0) { scoped_refptr<DrainableIOBuffer> 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); if (data->BytesRemaining() == bytes_to_copy) { // Consumed all data from this buffer read_buffer_.pop_front(); } else { data->DidConsume(bytes_to_copy); } } return user_buffer_->BytesConsumed(); } int SpdyProxyClientSocket::Write(IOBuffer* buf, int buf_len, CompletionCallback* callback) { DCHECK(!write_callback_); if (next_state_ == STATE_DISCONNECTED) return ERR_SOCKET_NOT_CONNECTED; if (!spdy_stream_) return ERR_CONNECTION_CLOSED; write_bytes_outstanding_= buf_len; if (buf_len <= kMaxSpdyFrameChunkSize) { int rv = spdy_stream_->WriteStreamData(buf, buf_len, spdy::DATA_FLAG_NONE); if (rv == ERR_IO_PENDING) { write_callback_ = callback; write_buffer_len_ = buf_len; } return rv; } // 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<DrainableIOBuffer> iobuf(new DrainableIOBuffer(buf, i + len)); iobuf->SetOffset(i); int rv = spdy_stream_->WriteStreamData(iobuf, len, spdy::DATA_FLAG_NONE); if (rv > 0) { write_bytes_outstanding_ -= rv; } else if (rv != ERR_IO_PENDING) { return rv; } } 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 ClientSocket 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 ClientSocket sits on top of a shared SpdySession, it // is not safe for callers to set change this underlying socket. return false; } int SpdyProxyClientSocket::GetPeerAddress(AddressList* 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::OnIOComplete(int result) { DCHECK_NE(STATE_DISCONNECTED, next_state_); int rv = DoLoop(result); if (rv != ERR_IO_PENDING) { CompletionCallback* c = read_callback_; read_callback_ = NULL; 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, NULL); rv = DoSendRequest(); break; case STATE_SEND_REQUEST_COMPLETE: net_log_.EndEventWithNetErrorCode( NetLog::TYPE_HTTP_TRANSACTION_TUNNEL_SEND_REQUEST, rv); rv = DoSendRequestComplete(rv); 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_, &io_callback_, 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); if (net_log_.IsLoggingAllEvents()) { net_log_.AddEvent( NetLog::TYPE_HTTP_TRANSACTION_SEND_TUNNEL_HEADERS, make_scoped_refptr(new NetLogHttpRequestParameter( request_line, request_headers))); } request_.extra_headers.MergeFrom(request_headers); linked_ptr<spdy::SpdyHeaderBlock> headers(new spdy::SpdyHeaderBlock()); CreateSpdyHeadersFromHttpRequest(request_, request_headers, headers.get(), true); // Reset the URL to be the endpoint of the connection (*headers)["url"] = endpoint_.ToString(); headers->erase("scheme"); spdy_stream_->set_spdy_headers(headers); 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; next_state_ = STATE_OPEN; if (net_log_.IsLoggingAllEvents()) { net_log_.AddEvent( NetLog::TYPE_HTTP_TRANSACTION_READ_TUNNEL_RESPONSE_HEADERS, make_scoped_refptr(new NetLogHttpResponseParameter(response_.headers))); } if (response_.headers->response_code() == 200) { return OK; } else if (response_.headers->response_code() == 407) { return ERR_TUNNEL_CONNECTION_FAILED; } else { // 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; } } // 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 spdy::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 int rv = SpdyHeadersToHttpResponse(response, &response_); if (rv == ERR_INCOMPLETE_SPDY_HEADERS) return rv; // More headers are coming. OnIOComplete(status); return OK; } // Called when data is received. void SpdyProxyClientSocket::OnDataReceived(const char* data, int length) { if (length > 0) { // Save the received data. scoped_refptr<IOBuffer> 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_) { int rv = PopulateUserReadBuffer(); CompletionCallback* c = read_callback_; read_callback_ = NULL; user_buffer_ = NULL; c->Run(rv); } } void SpdyProxyClientSocket::OnDataSent(int length) { DCHECK(write_callback_); 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_ = NULL; 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; CompletionCallback* write_callback = write_callback_; write_callback_ = NULL; 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_); CompletionCallback* read_callback = read_callback_; read_callback_ = NULL; read_callback->Run(status); } else if (read_callback_) { // If we have a read_callback, the we need to make sure we call it back OnDataReceived(NULL, 0); } if (write_callback) write_callback->Run(ERR_CONNECTION_CLOSED); } void SpdyProxyClientSocket::set_chunk_callback(ChunkCallback* /*callback*/) { } } // namespace net