| // 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 |