// Copyright (c) 2011 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. // // TODO(ukai): code is similar with http_network_transaction.cc. We should // think about ways to share code, if possible. #include "net/socket_stream/socket_stream.h" #include <set> #include <string> #include "base/compiler_specific.h" #include "base/logging.h" #include "base/message_loop.h" #include "base/string_util.h" #include "base/stringprintf.h" #include "base/utf_string_conversions.h" #include "net/base/auth.h" #include "net/base/host_resolver.h" #include "net/base/io_buffer.h" #include "net/base/net_errors.h" #include "net/base/net_util.h" #include "net/http/http_auth_handler_factory.h" #include "net/http/http_request_info.h" #include "net/http/http_response_headers.h" #include "net/http/http_util.h" #include "net/socket/client_socket_factory.h" #include "net/socket/socks5_client_socket.h" #include "net/socket/socks_client_socket.h" #include "net/socket/ssl_client_socket.h" #include "net/socket/tcp_client_socket.h" #include "net/socket_stream/socket_stream_metrics.h" #include "net/url_request/url_request.h" static const int kMaxPendingSendAllowed = 32768; // 32 kilobytes. static const int kReadBufferSize = 4096; namespace net { SocketStream::ResponseHeaders::ResponseHeaders() : IOBuffer() {} void SocketStream::ResponseHeaders::Realloc(size_t new_size) { headers_.reset(static_cast<char*>(realloc(headers_.release(), new_size))); } SocketStream::ResponseHeaders::~ResponseHeaders() { data_ = NULL; } SocketStream::SocketStream(const GURL& url, Delegate* delegate) : delegate_(delegate), url_(url), max_pending_send_allowed_(kMaxPendingSendAllowed), next_state_(STATE_NONE), host_resolver_(NULL), cert_verifier_(NULL), http_auth_handler_factory_(NULL), factory_(ClientSocketFactory::GetDefaultFactory()), proxy_mode_(kDirectConnection), proxy_url_(url), pac_request_(NULL), ALLOW_THIS_IN_INITIALIZER_LIST( io_callback_(this, &SocketStream::OnIOCompleted)), ALLOW_THIS_IN_INITIALIZER_LIST( read_callback_(this, &SocketStream::OnReadCompleted)), ALLOW_THIS_IN_INITIALIZER_LIST( write_callback_(this, &SocketStream::OnWriteCompleted)), read_buf_(NULL), write_buf_(NULL), current_write_buf_(NULL), write_buf_offset_(0), write_buf_size_(0), closing_(false), server_closed_(false), metrics_(new SocketStreamMetrics(url)) { DCHECK(MessageLoop::current()) << "The current MessageLoop must exist"; DCHECK_EQ(MessageLoop::TYPE_IO, MessageLoop::current()->type()) << "The current MessageLoop must be TYPE_IO"; DCHECK(delegate_); } SocketStream::UserData* SocketStream::GetUserData( const void* key) const { UserDataMap::const_iterator found = user_data_.find(key); if (found != user_data_.end()) return found->second.get(); return NULL; } void SocketStream::SetUserData(const void* key, UserData* data) { user_data_[key] = linked_ptr<UserData>(data); } bool SocketStream::is_secure() const { return url_.SchemeIs("wss"); } void SocketStream::set_context(URLRequestContext* context) { scoped_refptr<URLRequestContext> prev_context = context_; context_ = context; if (prev_context != context) { if (prev_context && pac_request_) { prev_context->proxy_service()->CancelPacRequest(pac_request_); pac_request_ = NULL; } net_log_.EndEvent(NetLog::TYPE_REQUEST_ALIVE, NULL); net_log_ = BoundNetLog(); if (context) { net_log_ = BoundNetLog::Make( context->net_log(), NetLog::SOURCE_SOCKET_STREAM); net_log_.BeginEvent(NetLog::TYPE_REQUEST_ALIVE, NULL); } } if (context_) { host_resolver_ = context_->host_resolver(); cert_verifier_ = context_->cert_verifier(); http_auth_handler_factory_ = context_->http_auth_handler_factory(); } } void SocketStream::Connect() { DCHECK(MessageLoop::current()) << "The current MessageLoop must exist"; DCHECK_EQ(MessageLoop::TYPE_IO, MessageLoop::current()->type()) << "The current MessageLoop must be TYPE_IO"; if (context_) ssl_config_service()->GetSSLConfig(&ssl_config_); DCHECK_EQ(next_state_, STATE_NONE); AddRef(); // Released in Finish() // Open a connection asynchronously, so that delegate won't be called // back before returning Connect(). next_state_ = STATE_RESOLVE_PROXY; net_log_.BeginEvent( NetLog::TYPE_SOCKET_STREAM_CONNECT, make_scoped_refptr( new NetLogStringParameter("url", url_.possibly_invalid_spec()))); MessageLoop::current()->PostTask( FROM_HERE, NewRunnableMethod(this, &SocketStream::DoLoop, OK)); } bool SocketStream::SendData(const char* data, int len) { DCHECK(MessageLoop::current()) << "The current MessageLoop must exist"; DCHECK_EQ(MessageLoop::TYPE_IO, MessageLoop::current()->type()) << "The current MessageLoop must be TYPE_IO"; if (!socket_.get() || !socket_->IsConnected() || next_state_ == STATE_NONE) return false; if (write_buf_) { int current_amount_send = write_buf_size_ - write_buf_offset_; for (PendingDataQueue::const_iterator iter = pending_write_bufs_.begin(); iter != pending_write_bufs_.end(); ++iter) current_amount_send += (*iter)->size(); current_amount_send += len; if (current_amount_send > max_pending_send_allowed_) return false; pending_write_bufs_.push_back(make_scoped_refptr( new IOBufferWithSize(len))); memcpy(pending_write_bufs_.back()->data(), data, len); return true; } DCHECK(!current_write_buf_); write_buf_ = new IOBuffer(len); memcpy(write_buf_->data(), data, len); write_buf_size_ = len; write_buf_offset_ = 0; // Send pending data asynchronously, so that delegate won't be called // back before returning SendData(). MessageLoop::current()->PostTask( FROM_HERE, NewRunnableMethod(this, &SocketStream::DoLoop, OK)); return true; } void SocketStream::Close() { DCHECK(MessageLoop::current()) << "The current MessageLoop must exist"; DCHECK_EQ(MessageLoop::TYPE_IO, MessageLoop::current()->type()) << "The current MessageLoop must be TYPE_IO"; // If next_state_ is STATE_NONE, the socket was not opened, or already // closed. So, return immediately. // Otherwise, it might call Finish() more than once, so breaks balance // of AddRef() and Release() in Connect() and Finish(), respectively. if (next_state_ == STATE_NONE) return; MessageLoop::current()->PostTask( FROM_HERE, NewRunnableMethod(this, &SocketStream::DoClose)); } void SocketStream::RestartWithAuth( const string16& username, const string16& password) { DCHECK(MessageLoop::current()) << "The current MessageLoop must exist"; DCHECK_EQ(MessageLoop::TYPE_IO, MessageLoop::current()->type()) << "The current MessageLoop must be TYPE_IO"; DCHECK(auth_handler_.get()); if (!socket_.get()) { LOG(ERROR) << "Socket is closed before restarting with auth."; return; } if (auth_identity_.invalid) { // Update the username/password. auth_identity_.source = HttpAuth::IDENT_SRC_EXTERNAL; auth_identity_.invalid = false; auth_identity_.username = username; auth_identity_.password = password; } MessageLoop::current()->PostTask( FROM_HERE, NewRunnableMethod(this, &SocketStream::DoRestartWithAuth)); } void SocketStream::DetachDelegate() { if (!delegate_) return; delegate_ = NULL; net_log_.AddEvent(NetLog::TYPE_CANCELLED, NULL); // We don't need to send pending data when client detach the delegate. pending_write_bufs_.clear(); Close(); } void SocketStream::SetHostResolver(HostResolver* host_resolver) { DCHECK(host_resolver); host_resolver_ = host_resolver; } void SocketStream::SetClientSocketFactory( ClientSocketFactory* factory) { DCHECK(factory); factory_ = factory; } SocketStream::~SocketStream() { set_context(NULL); DCHECK(!delegate_); DCHECK(!pac_request_); } void SocketStream::CopyAddrInfo(struct addrinfo* head) { addresses_.Copy(head, true); } void SocketStream::DoClose() { closing_ = true; // If next_state_ is STATE_TCP_CONNECT, it's waiting other socket establishing // connection. If next_state_ is STATE_AUTH_REQUIRED, it's waiting for // restarting. In these states, we'll close the SocketStream now. if (next_state_ == STATE_TCP_CONNECT || next_state_ == STATE_AUTH_REQUIRED) { DoLoop(ERR_ABORTED); return; } // If next_state_ is STATE_READ_WRITE, we'll run DoLoop and close // the SocketStream. // If it's writing now, we should defer the closing after the current // writing is completed. if (next_state_ == STATE_READ_WRITE && !current_write_buf_) DoLoop(ERR_ABORTED); // In other next_state_, we'll wait for callback of other APIs, such as // ResolveProxy(). } void SocketStream::Finish(int result) { DCHECK(MessageLoop::current()) << "The current MessageLoop must exist"; DCHECK_EQ(MessageLoop::TYPE_IO, MessageLoop::current()->type()) << "The current MessageLoop must be TYPE_IO"; DCHECK_LE(result, OK); if (result == OK) result = ERR_CONNECTION_CLOSED; DCHECK_EQ(next_state_, STATE_NONE); DVLOG(1) << "Finish result=" << ErrorToString(result); if (delegate_) delegate_->OnError(this, result); metrics_->OnClose(); Delegate* delegate = delegate_; delegate_ = NULL; if (delegate) { delegate->OnClose(this); } Release(); } int SocketStream::DidEstablishConnection() { if (!socket_.get() || !socket_->IsConnected()) { next_state_ = STATE_CLOSE; return ERR_CONNECTION_FAILED; } next_state_ = STATE_READ_WRITE; metrics_->OnConnected(); net_log_.EndEvent(NetLog::TYPE_SOCKET_STREAM_CONNECT, NULL); if (delegate_) delegate_->OnConnected(this, max_pending_send_allowed_); return OK; } int SocketStream::DidReceiveData(int result) { DCHECK(read_buf_); DCHECK_GT(result, 0); net_log_.AddEvent(NetLog::TYPE_SOCKET_STREAM_RECEIVED, NULL); int len = result; metrics_->OnRead(len); if (delegate_) { // Notify recevied data to delegate. delegate_->OnReceivedData(this, read_buf_->data(), len); } read_buf_ = NULL; return OK; } int SocketStream::DidSendData(int result) { DCHECK_GT(result, 0); net_log_.AddEvent(NetLog::TYPE_SOCKET_STREAM_SENT, NULL); int len = result; metrics_->OnWrite(len); current_write_buf_ = NULL; if (delegate_) delegate_->OnSentData(this, len); int remaining_size = write_buf_size_ - write_buf_offset_ - len; if (remaining_size == 0) { if (!pending_write_bufs_.empty()) { write_buf_size_ = pending_write_bufs_.front()->size(); write_buf_ = pending_write_bufs_.front(); pending_write_bufs_.pop_front(); } else { write_buf_size_ = 0; write_buf_ = NULL; } write_buf_offset_ = 0; } else { write_buf_offset_ += len; } return OK; } void SocketStream::OnIOCompleted(int result) { DoLoop(result); } void SocketStream::OnReadCompleted(int result) { if (result == 0) { // 0 indicates end-of-file, so socket was closed. // Don't close the socket if it's still writing. server_closed_ = true; } else if (result > 0 && read_buf_) { result = DidReceiveData(result); } DoLoop(result); } void SocketStream::OnWriteCompleted(int result) { if (result >= 0 && write_buf_) { result = DidSendData(result); } DoLoop(result); } void SocketStream::DoLoop(int result) { // If context was not set, close immediately. if (!context_) next_state_ = STATE_CLOSE; if (next_state_ == STATE_NONE) return; do { State state = next_state_; next_state_ = STATE_NONE; switch (state) { case STATE_RESOLVE_PROXY: DCHECK_EQ(OK, result); result = DoResolveProxy(); break; case STATE_RESOLVE_PROXY_COMPLETE: result = DoResolveProxyComplete(result); break; case STATE_RESOLVE_HOST: DCHECK_EQ(OK, result); result = DoResolveHost(); break; case STATE_RESOLVE_HOST_COMPLETE: result = DoResolveHostComplete(result); break; case STATE_TCP_CONNECT: result = DoTcpConnect(result); break; case STATE_TCP_CONNECT_COMPLETE: result = DoTcpConnectComplete(result); break; case STATE_WRITE_TUNNEL_HEADERS: DCHECK_EQ(OK, result); result = DoWriteTunnelHeaders(); break; case STATE_WRITE_TUNNEL_HEADERS_COMPLETE: result = DoWriteTunnelHeadersComplete(result); break; case STATE_READ_TUNNEL_HEADERS: DCHECK_EQ(OK, result); result = DoReadTunnelHeaders(); break; case STATE_READ_TUNNEL_HEADERS_COMPLETE: result = DoReadTunnelHeadersComplete(result); break; case STATE_SOCKS_CONNECT: DCHECK_EQ(OK, result); result = DoSOCKSConnect(); break; case STATE_SOCKS_CONNECT_COMPLETE: result = DoSOCKSConnectComplete(result); break; case STATE_SSL_CONNECT: DCHECK_EQ(OK, result); result = DoSSLConnect(); break; case STATE_SSL_CONNECT_COMPLETE: result = DoSSLConnectComplete(result); break; case STATE_READ_WRITE: result = DoReadWrite(result); break; case STATE_AUTH_REQUIRED: // It might be called when DoClose is called while waiting in // STATE_AUTH_REQUIRED. Finish(result); return; case STATE_CLOSE: DCHECK_LE(result, OK); Finish(result); return; default: NOTREACHED() << "bad state " << state; Finish(result); return; } // If the connection is not established yet and had actual errors, // close the connection. if (state != STATE_READ_WRITE && result < ERR_IO_PENDING) { DCHECK_EQ(next_state_, STATE_CLOSE); net_log_.EndEventWithNetErrorCode( NetLog::TYPE_SOCKET_STREAM_CONNECT, result); } } while (result != ERR_IO_PENDING); } int SocketStream::DoResolveProxy() { DCHECK(!pac_request_); next_state_ = STATE_RESOLVE_PROXY_COMPLETE; if (!proxy_url_.is_valid()) { next_state_ = STATE_CLOSE; return ERR_INVALID_ARGUMENT; } return proxy_service()->ResolveProxy( proxy_url_, &proxy_info_, &io_callback_, &pac_request_, net_log_); } int SocketStream::DoResolveProxyComplete(int result) { pac_request_ = NULL; if (result != OK) { LOG(ERROR) << "Failed to resolve proxy: " << result; if (delegate_) delegate_->OnError(this, result); proxy_info_.UseDirect(); } if (proxy_info_.is_direct()) { // If proxy was not found for original URL (i.e. websocket URL), // try again with https URL, like Safari implementation. // Note that we don't want to use http proxy, because we'll use tunnel // proxy using CONNECT method, which is used by https proxy. if (!proxy_url_.SchemeIs("https")) { const std::string scheme = "https"; GURL::Replacements repl; repl.SetSchemeStr(scheme); proxy_url_ = url_.ReplaceComponents(repl); DVLOG(1) << "Try https proxy: " << proxy_url_; next_state_ = STATE_RESOLVE_PROXY; return OK; } } if (proxy_info_.is_empty()) { // No proxies/direct to choose from. This happens when we don't support any // of the proxies in the returned list. return ERR_NO_SUPPORTED_PROXIES; } next_state_ = STATE_RESOLVE_HOST; return OK; } int SocketStream::DoResolveHost() { next_state_ = STATE_RESOLVE_HOST_COMPLETE; DCHECK(!proxy_info_.is_empty()); if (proxy_info_.is_direct()) proxy_mode_ = kDirectConnection; else if (proxy_info_.proxy_server().is_socks()) proxy_mode_ = kSOCKSProxy; else proxy_mode_ = kTunnelProxy; // Determine the host and port to connect to. HostPortPair host_port_pair; if (proxy_mode_ != kDirectConnection) { host_port_pair = proxy_info_.proxy_server().host_port_pair(); } else { host_port_pair = HostPortPair::FromURL(url_); } HostResolver::RequestInfo resolve_info(host_port_pair); DCHECK(host_resolver_); resolver_.reset(new SingleRequestHostResolver(host_resolver_)); return resolver_->Resolve(resolve_info, &addresses_, &io_callback_, net_log_); } int SocketStream::DoResolveHostComplete(int result) { if (result == OK && delegate_) { next_state_ = STATE_TCP_CONNECT; result = delegate_->OnStartOpenConnection(this, &io_callback_); if (result == ERR_IO_PENDING) metrics_->OnWaitConnection(); } else { next_state_ = STATE_CLOSE; } // TODO(ukai): if error occured, reconsider proxy after error. return result; } int SocketStream::DoTcpConnect(int result) { if (result != OK) { next_state_ = STATE_CLOSE; return result; } next_state_ = STATE_TCP_CONNECT_COMPLETE; DCHECK(factory_); socket_.reset(factory_->CreateTransportClientSocket(addresses_, net_log_.net_log(), net_log_.source())); metrics_->OnStartConnection(); return socket_->Connect(&io_callback_); } int SocketStream::DoTcpConnectComplete(int result) { // TODO(ukai): if error occured, reconsider proxy after error. if (result != OK) { next_state_ = STATE_CLOSE; return result; } if (proxy_mode_ == kTunnelProxy) next_state_ = STATE_WRITE_TUNNEL_HEADERS; else if (proxy_mode_ == kSOCKSProxy) next_state_ = STATE_SOCKS_CONNECT; else if (is_secure()) { next_state_ = STATE_SSL_CONNECT; } else { result = DidEstablishConnection(); } return result; } int SocketStream::DoWriteTunnelHeaders() { DCHECK_EQ(kTunnelProxy, proxy_mode_); next_state_ = STATE_WRITE_TUNNEL_HEADERS_COMPLETE; if (!tunnel_request_headers_.get()) { metrics_->OnTunnelProxy(); tunnel_request_headers_ = new RequestHeaders(); tunnel_request_headers_bytes_sent_ = 0; } if (tunnel_request_headers_->headers_.empty()) { std::string authorization_headers; if (!auth_handler_.get()) { // Do preemptive authentication. HttpAuthCache::Entry* entry = auth_cache_.LookupByPath( ProxyAuthOrigin(), std::string()); if (entry) { scoped_ptr<HttpAuthHandler> handler_preemptive; int rv_create = http_auth_handler_factory_-> CreatePreemptiveAuthHandlerFromString( entry->auth_challenge(), HttpAuth::AUTH_PROXY, ProxyAuthOrigin(), entry->IncrementNonceCount(), net_log_, &handler_preemptive); if (rv_create == OK) { auth_identity_.source = HttpAuth::IDENT_SRC_PATH_LOOKUP; auth_identity_.invalid = false; auth_identity_.username = entry->username(); auth_identity_.password = entry->password(); auth_handler_.swap(handler_preemptive); } } } // Support basic authentication scheme only, because we don't have // HttpRequestInfo. // TODO(ukai): Add support other authentication scheme. if (auth_handler_.get() && auth_handler_->auth_scheme() == HttpAuth::AUTH_SCHEME_BASIC) { HttpRequestInfo request_info; std::string auth_token; int rv = auth_handler_->GenerateAuthToken( &auth_identity_.username, &auth_identity_.password, &request_info, NULL, &auth_token); // TODO(cbentzel): Support async auth handlers. DCHECK_NE(ERR_IO_PENDING, rv); if (rv != OK) return rv; authorization_headers.append( HttpAuth::GetAuthorizationHeaderName(HttpAuth::AUTH_PROXY) + ": " + auth_token + "\r\n"); } tunnel_request_headers_->headers_ = base::StringPrintf( "CONNECT %s HTTP/1.1\r\n" "Host: %s\r\n" "Proxy-Connection: keep-alive\r\n", GetHostAndPort(url_).c_str(), GetHostAndOptionalPort(url_).c_str()); if (!authorization_headers.empty()) tunnel_request_headers_->headers_ += authorization_headers; tunnel_request_headers_->headers_ += "\r\n"; } tunnel_request_headers_->SetDataOffset(tunnel_request_headers_bytes_sent_); int buf_len = static_cast<int>(tunnel_request_headers_->headers_.size() - tunnel_request_headers_bytes_sent_); DCHECK_GT(buf_len, 0); return socket_->Write(tunnel_request_headers_, buf_len, &io_callback_); } int SocketStream::DoWriteTunnelHeadersComplete(int result) { DCHECK_EQ(kTunnelProxy, proxy_mode_); if (result < 0) { next_state_ = STATE_CLOSE; return result; } tunnel_request_headers_bytes_sent_ += result; if (tunnel_request_headers_bytes_sent_ < tunnel_request_headers_->headers_.size()) next_state_ = STATE_WRITE_TUNNEL_HEADERS; else next_state_ = STATE_READ_TUNNEL_HEADERS; return OK; } int SocketStream::DoReadTunnelHeaders() { DCHECK_EQ(kTunnelProxy, proxy_mode_); next_state_ = STATE_READ_TUNNEL_HEADERS_COMPLETE; if (!tunnel_response_headers_.get()) { tunnel_response_headers_ = new ResponseHeaders(); tunnel_response_headers_capacity_ = kMaxTunnelResponseHeadersSize; tunnel_response_headers_->Realloc(tunnel_response_headers_capacity_); tunnel_response_headers_len_ = 0; } int buf_len = tunnel_response_headers_capacity_ - tunnel_response_headers_len_; tunnel_response_headers_->SetDataOffset(tunnel_response_headers_len_); CHECK(tunnel_response_headers_->data()); return socket_->Read(tunnel_response_headers_, buf_len, &io_callback_); } int SocketStream::DoReadTunnelHeadersComplete(int result) { DCHECK_EQ(kTunnelProxy, proxy_mode_); if (result < 0) { next_state_ = STATE_CLOSE; return result; } if (result == 0) { // 0 indicates end-of-file, so socket was closed. next_state_ = STATE_CLOSE; return ERR_CONNECTION_CLOSED; } tunnel_response_headers_len_ += result; DCHECK(tunnel_response_headers_len_ <= tunnel_response_headers_capacity_); int eoh = HttpUtil::LocateEndOfHeaders( tunnel_response_headers_->headers(), tunnel_response_headers_len_, 0); if (eoh == -1) { if (tunnel_response_headers_len_ >= kMaxTunnelResponseHeadersSize) { next_state_ = STATE_CLOSE; return ERR_RESPONSE_HEADERS_TOO_BIG; } next_state_ = STATE_READ_TUNNEL_HEADERS; return OK; } // DidReadResponseHeaders scoped_refptr<HttpResponseHeaders> headers; headers = new HttpResponseHeaders( HttpUtil::AssembleRawHeaders(tunnel_response_headers_->headers(), eoh)); if (headers->GetParsedHttpVersion() < HttpVersion(1, 0)) { // Require the "HTTP/1.x" status line. next_state_ = STATE_CLOSE; return ERR_TUNNEL_CONNECTION_FAILED; } switch (headers->response_code()) { case 200: // OK if (is_secure()) { DCHECK_EQ(eoh, tunnel_response_headers_len_); next_state_ = STATE_SSL_CONNECT; } else { result = DidEstablishConnection(); if (result < 0) { next_state_ = STATE_CLOSE; return result; } if ((eoh < tunnel_response_headers_len_) && delegate_) delegate_->OnReceivedData( this, tunnel_response_headers_->headers() + eoh, tunnel_response_headers_len_ - eoh); } return OK; case 407: // Proxy Authentication Required. result = HandleAuthChallenge(headers.get()); if (result == ERR_PROXY_AUTH_UNSUPPORTED && auth_handler_.get() && delegate_) { DCHECK(!proxy_info_.is_empty()); auth_info_ = new AuthChallengeInfo; auth_info_->is_proxy = true; auth_info_->host_and_port = ASCIIToWide(proxy_info_.proxy_server().host_port_pair().ToString()); auth_info_->scheme = ASCIIToWide( HttpAuth::SchemeToString(auth_handler_->auth_scheme())); auth_info_->realm = ASCIIToWide(auth_handler_->realm()); // Wait until RestartWithAuth or Close is called. MessageLoop::current()->PostTask( FROM_HERE, NewRunnableMethod(this, &SocketStream::DoAuthRequired)); next_state_ = STATE_AUTH_REQUIRED; return ERR_IO_PENDING; } default: break; } next_state_ = STATE_CLOSE; return ERR_TUNNEL_CONNECTION_FAILED; } int SocketStream::DoSOCKSConnect() { DCHECK_EQ(kSOCKSProxy, proxy_mode_); next_state_ = STATE_SOCKS_CONNECT_COMPLETE; ClientSocket* s = socket_.release(); HostResolver::RequestInfo req_info(HostPortPair::FromURL(url_)); DCHECK(!proxy_info_.is_empty()); if (proxy_info_.proxy_server().scheme() == ProxyServer::SCHEME_SOCKS5) s = new SOCKS5ClientSocket(s, req_info); else s = new SOCKSClientSocket(s, req_info, host_resolver_); socket_.reset(s); metrics_->OnSOCKSProxy(); return socket_->Connect(&io_callback_); } int SocketStream::DoSOCKSConnectComplete(int result) { DCHECK_EQ(kSOCKSProxy, proxy_mode_); if (result == OK) { if (is_secure()) next_state_ = STATE_SSL_CONNECT; else result = DidEstablishConnection(); } else { next_state_ = STATE_CLOSE; } return result; } int SocketStream::DoSSLConnect() { DCHECK(factory_); // TODO(agl): look into plumbing SSLHostInfo here. socket_.reset(factory_->CreateSSLClientSocket(socket_.release(), HostPortPair::FromURL(url_), ssl_config_, NULL /* ssl_host_info */, cert_verifier_)); next_state_ = STATE_SSL_CONNECT_COMPLETE; metrics_->OnSSLConnection(); return socket_->Connect(&io_callback_); } int SocketStream::DoSSLConnectComplete(int result) { if (IsCertificateError(result)) { if (socket_->IsConnectedAndIdle()) { result = HandleCertificateError(result); } else { // SSLClientSocket for Mac will report socket is not connected, // if it returns cert verification error. It didn't perform // SSLHandshake yet. // So, we should restart establishing connection with the // certificate in allowed bad certificates in |ssl_config_|. // See also net/http/http_network_transaction.cc // HandleCertificateError() and RestartIgnoringLastError(). SSLClientSocket* ssl_socket = reinterpret_cast<SSLClientSocket*>(socket_.get()); SSLInfo ssl_info; ssl_socket->GetSSLInfo(&ssl_info); if (ssl_config_.IsAllowedBadCert(ssl_info.cert)) { // If we already have the certificate in the set of allowed bad // certificates, we did try it and failed again, so we should not // retry again: the connection should fail at last. next_state_ = STATE_CLOSE; return result; } // Add the bad certificate to the set of allowed certificates in the // SSL config object. SSLConfig::CertAndStatus bad_cert; bad_cert.cert = ssl_info.cert; bad_cert.cert_status = ssl_info.cert_status; ssl_config_.allowed_bad_certs.push_back(bad_cert); // Restart connection ignoring the bad certificate. socket_->Disconnect(); socket_.reset(); next_state_ = STATE_TCP_CONNECT; return OK; } } if (result == OK) result = DidEstablishConnection(); else next_state_ = STATE_CLOSE; return result; } int SocketStream::DoReadWrite(int result) { if (result < OK) { next_state_ = STATE_CLOSE; return result; } if (!socket_.get() || !socket_->IsConnected()) { next_state_ = STATE_CLOSE; return ERR_CONNECTION_CLOSED; } // If client has requested close(), and there's nothing to write, then // let's close the socket. // We don't care about receiving data after the socket is closed. if (closing_ && !write_buf_ && pending_write_bufs_.empty()) { socket_->Disconnect(); next_state_ = STATE_CLOSE; return OK; } next_state_ = STATE_READ_WRITE; // If server already closed the socket, we don't try to read. if (!server_closed_) { if (!read_buf_) { // No read pending and server didn't close the socket. read_buf_ = new IOBuffer(kReadBufferSize); result = socket_->Read(read_buf_, kReadBufferSize, &read_callback_); if (result > 0) { return DidReceiveData(result); } else if (result == 0) { // 0 indicates end-of-file, so socket was closed. next_state_ = STATE_CLOSE; server_closed_ = true; return ERR_CONNECTION_CLOSED; } // If read is pending, try write as well. // Otherwise, return the result and do next loop (to close the // connection). if (result != ERR_IO_PENDING) { next_state_ = STATE_CLOSE; server_closed_ = true; return result; } } // Read is pending. DCHECK(read_buf_); } if (write_buf_ && !current_write_buf_) { // No write pending. current_write_buf_ = new DrainableIOBuffer(write_buf_, write_buf_size_); current_write_buf_->SetOffset(write_buf_offset_); result = socket_->Write(current_write_buf_, current_write_buf_->BytesRemaining(), &write_callback_); if (result > 0) { return DidSendData(result); } // If write is not pending, return the result and do next loop (to close // the connection). if (result != 0 && result != ERR_IO_PENDING) { next_state_ = STATE_CLOSE; return result; } return result; } // We arrived here when both operation is pending. return ERR_IO_PENDING; } GURL SocketStream::ProxyAuthOrigin() const { DCHECK(!proxy_info_.is_empty()); return GURL("http://" + proxy_info_.proxy_server().host_port_pair().ToString()); } int SocketStream::HandleAuthChallenge(const HttpResponseHeaders* headers) { GURL auth_origin(ProxyAuthOrigin()); VLOG(1) << "The proxy " << auth_origin << " requested auth"; // TODO(cbentzel): Since SocketStream only suppports basic authentication // right now, another challenge is always treated as a rejection. // Ultimately this should be converted to use HttpAuthController like the // HttpNetworkTransaction has. if (auth_handler_.get() && !auth_identity_.invalid) { if (auth_identity_.source != HttpAuth::IDENT_SRC_PATH_LOOKUP) auth_cache_.Remove(auth_origin, auth_handler_->realm(), auth_handler_->auth_scheme(), auth_identity_.username, auth_identity_.password); auth_handler_.reset(); auth_identity_ = HttpAuth::Identity(); } auth_identity_.invalid = true; std::set<HttpAuth::Scheme> disabled_schemes; HttpAuth::ChooseBestChallenge(http_auth_handler_factory_, headers, HttpAuth::AUTH_PROXY, auth_origin, disabled_schemes, net_log_, &auth_handler_); if (!auth_handler_.get()) { LOG(ERROR) << "Can't perform auth to the proxy " << auth_origin; return ERR_TUNNEL_CONNECTION_FAILED; } if (auth_handler_->NeedsIdentity()) { // We only support basic authentication scheme now. // TODO(ukai): Support other authentication scheme. HttpAuthCache::Entry* entry = auth_cache_.Lookup( auth_origin, auth_handler_->realm(), HttpAuth::AUTH_SCHEME_BASIC); if (entry) { auth_identity_.source = HttpAuth::IDENT_SRC_REALM_LOOKUP; auth_identity_.invalid = false; auth_identity_.username = entry->username(); auth_identity_.password = entry->password(); // Restart with auth info. } return ERR_PROXY_AUTH_UNSUPPORTED; } else { auth_identity_.invalid = false; } return ERR_TUNNEL_CONNECTION_FAILED; } void SocketStream::DoAuthRequired() { if (delegate_ && auth_info_.get()) delegate_->OnAuthRequired(this, auth_info_.get()); else DoLoop(ERR_UNEXPECTED); } void SocketStream::DoRestartWithAuth() { DCHECK_EQ(next_state_, STATE_AUTH_REQUIRED); auth_cache_.Add(ProxyAuthOrigin(), auth_handler_->realm(), auth_handler_->auth_scheme(), auth_handler_->challenge(), auth_identity_.username, auth_identity_.password, std::string()); tunnel_request_headers_ = NULL; tunnel_request_headers_bytes_sent_ = 0; tunnel_response_headers_ = NULL; tunnel_response_headers_capacity_ = 0; tunnel_response_headers_len_ = 0; next_state_ = STATE_TCP_CONNECT; DoLoop(OK); } int SocketStream::HandleCertificateError(int result) { // TODO(ukai): handle cert error properly. switch (result) { case ERR_CERT_COMMON_NAME_INVALID: case ERR_CERT_DATE_INVALID: case ERR_CERT_AUTHORITY_INVALID: result = OK; break; default: break; } return result; } SSLConfigService* SocketStream::ssl_config_service() const { return context_->ssl_config_service(); } ProxyService* SocketStream::proxy_service() const { return context_->proxy_service(); } } // namespace net