// Copyright (c) 2006-2008 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/proxy/proxy_service.h" #include #include "base/compiler_specific.h" #include "base/logging.h" #include "base/string_util.h" #include "googleurl/src/gurl.h" #include "net/base/net_errors.h" #include "net/proxy/proxy_config_service_fixed.h" #include "net/proxy/proxy_script_fetcher.h" #if defined(OS_WIN) #include "net/proxy/proxy_config_service_win.h" #include "net/proxy/proxy_resolver_winhttp.h" #elif defined(OS_MACOSX) #include "net/proxy/proxy_resolver_mac.h" #elif defined(OS_LINUX) #include "net/proxy/proxy_config_service_linux.h" #endif #include "net/proxy/proxy_resolver.h" #include "net/proxy/proxy_resolver_v8.h" using base::TimeDelta; using base::TimeTicks; namespace net { // Config getter that fails every time. class ProxyConfigServiceNull : public ProxyConfigService { public: virtual int GetProxyConfig(ProxyConfig* config) { return ERR_NOT_IMPLEMENTED; } }; // Strip away any reference fragments and the username/password, as they // are not relevant to proxy resolution. static GURL SanitizeURLForProxyResolver(const GURL& url) { // TODO(eroman): The following duplicates logic from // HttpUtil::SpecForRequest. Should probably live in net_util.h GURL::Replacements replacements; replacements.ClearUsername(); replacements.ClearPassword(); replacements.ClearRef(); return url.ReplaceComponents(replacements); } // Runs on the PAC thread to notify the proxy resolver of the fetched PAC // script contents. This task shouldn't outlive ProxyService, since // |resolver| is owned by ProxyService. class NotifyFetchCompletionTask : public Task { public: NotifyFetchCompletionTask(ProxyResolver* resolver, const std::string& bytes) : resolver_(resolver), bytes_(bytes) {} virtual void Run() { resolver_->SetPacScript(bytes_); } private: ProxyResolver* resolver_; std::string bytes_; }; // ProxyService::PacRequest --------------------------------------------------- // We rely on the fact that the origin thread (and its message loop) will not // be destroyed until after the PAC thread is destroyed. class ProxyService::PacRequest : public base::RefCountedThreadSafe { public: // |service| -- the ProxyService that owns this request. // |url| -- the url of the query. // |results| -- the structure to fill with proxy resolve results. PacRequest(ProxyService* service, const GURL& url, ProxyInfo* results, CompletionCallback* callback) : service_(service), callback_(callback), results_(results), url_(url), is_started_(false), origin_loop_(MessageLoop::current()) { DCHECK(callback); } // Start the resolve proxy request on the PAC thread. void Query() { is_started_ = true; AddRef(); // balanced in QueryComplete GURL query_url = SanitizeURLForProxyResolver(url_); const GURL& pac_url = service_->config_.pac_url; results_->config_id_ = service_->config_.id(); service_->pac_thread()->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(this, &ProxyService::PacRequest::DoQuery, service_->resolver(), query_url, pac_url)); } // Run the request's callback on the current message loop. void PostCallback(int result_code) { AddRef(); // balanced in DoCallback MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod(this, &ProxyService::PacRequest::DoCallback, result_code)); } void Cancel() { // Clear these to inform QueryComplete that it should not try to // access them. service_ = NULL; callback_ = NULL; results_ = NULL; } // Returns true if Cancel() has been called. bool was_cancelled() const { return callback_ == NULL; } private: friend class ProxyService; // Runs on the PAC thread. void DoQuery(ProxyResolver* resolver, const GURL& query_url, const GURL& pac_url) { int rv = resolver->GetProxyForURL(query_url, pac_url, &results_buf_); origin_loop_->PostTask(FROM_HERE, NewRunnableMethod(this, &PacRequest::QueryComplete, rv)); } // Runs the completion callback on the origin thread. void QueryComplete(int result_code) { // The PacRequest may have been cancelled after it was started. if (!was_cancelled()) { service_->DidCompletePacRequest(results_->config_id_, result_code); if (result_code == OK) { results_->Use(results_buf_); results_->RemoveBadProxies(service_->proxy_retry_info_); } callback_->Run(result_code); // We check for cancellation once again, in case the callback deleted // the owning ProxyService (whose destructor will in turn cancel us). if (!was_cancelled()) service_->RemoveFrontOfRequestQueue(this); } Release(); // balances the AddRef in Query. we may get deleted after // we return. } // Runs the completion callback on the origin thread. void DoCallback(int result_code) { if (!was_cancelled()) { callback_->Run(result_code); } Release(); // balances the AddRef in PostCallback. } // Must only be used on the "origin" thread. ProxyService* service_; CompletionCallback* callback_; ProxyInfo* results_; GURL url_; bool is_started_; // Usable from within DoQuery on the PAC thread. ProxyInfo results_buf_; MessageLoop* origin_loop_; }; // ProxyService --------------------------------------------------------------- ProxyService::ProxyService(ProxyConfigService* config_service, ProxyResolver* resolver) : config_service_(config_service), resolver_(resolver), config_is_bad_(false), config_has_been_updated_(false), ALLOW_THIS_IN_INITIALIZER_LIST(proxy_script_fetcher_callback_( this, &ProxyService::OnScriptFetchCompletion)), fetched_pac_config_id_(ProxyConfig::INVALID_ID), fetched_pac_error_(OK), in_progress_fetch_config_id_(ProxyConfig::INVALID_ID) { } // static ProxyService* ProxyService::Create(const ProxyInfo* pi) { if (pi) { // The ProxyResolver is set to NULL, since it should never be called // (because the configuration will never require PAC). return new ProxyService(new ProxyConfigServiceFixed(*pi), NULL); } #if defined(OS_WIN) return new ProxyService(new ProxyConfigServiceWin(), new ProxyResolverWinHttp()); #elif defined(OS_MACOSX) return new ProxyService(new ProxyConfigServiceMac(), new ProxyResolverMac()); #elif defined(OS_LINUX) // On Linux we use the V8Resolver, no fallback implementation. return CreateNull(); #else return CreateNull(); #endif } // static ProxyService* ProxyService::CreateUsingV8Resolver( const ProxyInfo* pi, URLRequestContext* url_request_context) { if (pi) { // The ProxyResolver is set to NULL, since it should never be called // (because the configuration will never require PAC). return new ProxyService(new ProxyConfigServiceFixed(*pi), NULL); } // Choose the system configuration service appropriate for each platform. ProxyConfigService* config_service; #if defined(OS_WIN) config_service = new ProxyConfigServiceWin(); #elif defined(OS_MACOSX) config_service = new ProxyConfigServiceMac(); #elif defined(OS_LINUX) config_service = new ProxyConfigServiceLinux(); #else return CreateNull(); #endif // Create a ProxyService that uses V8 to evaluate PAC scripts. ProxyService* proxy_service = new ProxyService( config_service, new ProxyResolverV8()); // Configure PAC script downloads to be issued using |url_request_context|. proxy_service->SetProxyScriptFetcher( ProxyScriptFetcher::Create(url_request_context)); return proxy_service; } // static ProxyService* ProxyService::CreateNull() { // The ProxyResolver is set to NULL, since it should never be called // (because the configuration will never require PAC). return new ProxyService(new ProxyConfigServiceNull, NULL); } int ProxyService::ResolveProxy(const GURL& url, ProxyInfo* result, CompletionCallback* callback, PacRequest** pac_request) { DCHECK(callback); // Check if the request can be completed right away. This is the case when // using a direct connection, or when the config is bad. UpdateConfigIfOld(); int rv = TryToCompleteSynchronously(url, result); if (rv != ERR_IO_PENDING) return rv; // Otherwise, push the request into the work queue. scoped_refptr req = new PacRequest(this, url, result, callback); pending_requests_.push_back(req); ProcessPendingRequests(req.get()); // Completion will be notifed through |callback|, unless the caller cancels // the request using |pac_request|. if (pac_request) *pac_request = req.get(); return rv; // ERR_IO_PENDING } int ProxyService::TryToCompleteSynchronously(const GURL& url, ProxyInfo* result) { result->config_id_ = config_.id(); DCHECK(config_.id() != ProxyConfig::INVALID_ID); // Fallback to a "direct" (no proxy) connection if the current configuration // is known to be bad. if (config_is_bad_) { // Reset this flag to false in case the ProxyInfo object is being // re-used by the caller. result->config_was_tried_ = false; } else { // Remember that we are trying to use the current proxy configuration. result->config_was_tried_ = true; if (!config_.proxy_rules.empty()) { ApplyProxyRules(url, config_.proxy_rules, result); return OK; } if (config_.pac_url.is_valid() || config_.auto_detect) { // If we failed to download the PAC script, return the network error // from the failed download. This is only going to happen for the first // request after the failed download -- after that |config_is_bad_| will // be set to true, so we short-cuircuit sooner. if (fetched_pac_error_ != OK && !IsFetchingPacScript()) { DidCompletePacRequest(fetched_pac_config_id_, fetched_pac_error_); return fetched_pac_error_; } return ERR_IO_PENDING; } } // otherwise, we have no proxy config result->UseDirect(); return OK; } void ProxyService::ApplyProxyRules(const GURL& url, const ProxyConfig::ProxyRules& proxy_rules, ProxyInfo* result) { DCHECK(!proxy_rules.empty()); if (ShouldBypassProxyForURL(url)) { result->UseDirect(); return; } switch (proxy_rules.type) { case ProxyConfig::ProxyRules::TYPE_SINGLE_PROXY: result->UseProxyServer(proxy_rules.single_proxy); break; case ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME: { const ProxyServer* entry = proxy_rules.MapSchemeToProxy(url.scheme()); if (entry) { result->UseProxyServer(*entry); } else { // We failed to find a matching proxy server for the current URL // scheme. Default to direct. result->UseDirect(); } break; } default: result->UseDirect(); NOTREACHED(); break; } } void ProxyService::InitPacThread() { if (!pac_thread_.get()) { pac_thread_.reset(new base::Thread("pac-thread")); pac_thread_->Start(); } } ProxyService::~ProxyService() { // Cancel the inprogress request (if any), and free the rest. for (PendingRequestsQueue::iterator it = pending_requests_.begin(); it != pending_requests_.end(); ++it) { (*it)->Cancel(); } } void ProxyService::ProcessPendingRequests(PacRequest* recent_req) { if (pending_requests_.empty()) return; // While the PAC script is being downloaded, requests are blocked. if (IsFetchingPacScript()) return; // Get the next request to process (FIFO). PacRequest* req = pending_requests_.front().get(); if (req->is_started_) return; // The configuration may have changed since |req| was added to the // queue. It could be this request now completes synchronously. if (req != recent_req) { UpdateConfigIfOld(); int rv = TryToCompleteSynchronously(req->url_, req->results_); if (rv != ERR_IO_PENDING) { req->PostCallback(rv); RemoveFrontOfRequestQueue(req); return; } } // Check if a new PAC script needs to be downloaded. DCHECK(config_.id() != ProxyConfig::INVALID_ID); if (!resolver_->does_fetch() && config_.id() != fetched_pac_config_id_) { // For auto-detect we use the well known WPAD url. GURL pac_url = config_.auto_detect ? GURL("http://wpad/wpad.dat") : config_.pac_url; in_progress_fetch_config_id_ = config_.id(); LOG(INFO) << "Starting fetch of PAC script " << pac_url << " for config_id=" << in_progress_fetch_config_id_; proxy_script_fetcher_->Fetch( pac_url, &in_progress_fetch_bytes_, &proxy_script_fetcher_callback_); return; } // The only choice left now is to actually run the ProxyResolver on // the PAC thread. InitPacThread(); req->Query(); } void ProxyService::RemoveFrontOfRequestQueue(PacRequest* expected_req) { DCHECK(pending_requests_.front().get() == expected_req); pending_requests_.pop_front(); // Start next work item. ProcessPendingRequests(NULL); } void ProxyService::OnScriptFetchCompletion(int result) { DCHECK(IsFetchingPacScript()); DCHECK(!resolver_->does_fetch()); LOG(INFO) << "Completed PAC script fetch for config_id=" << in_progress_fetch_config_id_ << " with error " << ErrorToString(result) << ". Fetched a total of " << in_progress_fetch_bytes_.size() << " bytes"; // Notify the ProxyResolver of the new script data (will be empty string if // result != OK). InitPacThread(); pac_thread()->message_loop()->PostTask(FROM_HERE, new NotifyFetchCompletionTask( resolver_.get(), in_progress_fetch_bytes_)); fetched_pac_config_id_ = in_progress_fetch_config_id_; fetched_pac_error_ = result; in_progress_fetch_config_id_ = ProxyConfig::INVALID_ID; in_progress_fetch_bytes_.clear(); // Start a pending request if any. ProcessPendingRequests(NULL); } int ProxyService::ReconsiderProxyAfterError(const GURL& url, ProxyInfo* result, CompletionCallback* callback, PacRequest** pac_request) { // Check to see if we have a new config since ResolveProxy was called. We // want to re-run ResolveProxy in two cases: 1) we have a new config, or 2) a // direct connection failed and we never tried the current config. bool re_resolve = result->config_id_ != config_.id(); if (!re_resolve) { UpdateConfig(); if (result->config_id_ != config_.id()) { // A new configuration! re_resolve = true; } else if (!result->config_was_tried_) { // We never tried the proxy configuration since we thought it was bad, // but because we failed to establish a connection, let's try the proxy // configuration again to see if it will work now. config_is_bad_ = false; re_resolve = true; } } if (re_resolve) { // If we have a new config or the config was never tried, we delete the // list of bad proxies and we try again. proxy_retry_info_.clear(); return ResolveProxy(url, result, callback, pac_request); } // We don't have new proxy settings to try, fallback to the next proxy // in the list. bool was_direct = result->is_direct(); if (!was_direct && result->Fallback(&proxy_retry_info_)) return OK; if (!config_.auto_detect && !config_.proxy_rules.empty()) { // If auto detect is on, then we should try a DIRECT connection // as the attempt to reach the proxy failed. return ERR_FAILED; } // If we already tried a direct connection, then just give up. if (was_direct) return ERR_FAILED; // Try going direct. result->UseDirect(); return OK; } // There are four states of the request we need to handle: // (1) Not started (just sitting in the queue). // (2) Executing PacRequest::DoQuery in the PAC thread. // (3) Waiting for PacRequest::QueryComplete to be run on the origin thread. // (4) Waiting for PacRequest::DoCallback to be run on the origin thread. void ProxyService::CancelPacRequest(PacRequest* req) { DCHECK(req); bool is_active_request = req->is_started_ && !pending_requests_.empty() && pending_requests_.front().get() == req; req->Cancel(); if (is_active_request) { RemoveFrontOfRequestQueue(req); return; } // Otherwise just delete the request from the queue. PendingRequestsQueue::iterator it = std::find( pending_requests_.begin(), pending_requests_.end(), req); if (it != pending_requests_.end()) { pending_requests_.erase(it); } } void ProxyService::SetProxyScriptFetcher( ProxyScriptFetcher* proxy_script_fetcher) { proxy_script_fetcher_.reset(proxy_script_fetcher); } void ProxyService::DidCompletePacRequest(int config_id, int result_code) { // If we get an error that indicates a bad PAC config, then we should // remember that, and not try the PAC config again for a while. // Our config may have already changed. if (result_code == OK || config_id != config_.id()) return; // Remember that this configuration doesn't work. config_is_bad_ = true; } void ProxyService::UpdateConfig() { config_has_been_updated_ = true; ProxyConfig latest; if (config_service_->GetProxyConfig(&latest) != OK) return; config_last_update_time_ = TimeTicks::Now(); if (latest.Equals(config_)) return; LOG(INFO) << "New proxy configuration was loaded:\n" << latest; config_ = latest; config_is_bad_ = false; // We have a new config, we should clear the list of bad proxies. proxy_retry_info_.clear(); } void ProxyService::UpdateConfigIfOld() { // The overhead of calling ProxyConfigService::GetProxyConfig is very low. const TimeDelta kProxyConfigMaxAge = TimeDelta::FromSeconds(5); // Periodically check for a new config. if (!config_has_been_updated_ || (TimeTicks::Now() - config_last_update_time_) > kProxyConfigMaxAge) UpdateConfig(); } bool ProxyService::ShouldBypassProxyForURL(const GURL& url) { std::string url_domain = url.scheme(); if (!url_domain.empty()) url_domain += "://"; url_domain += url.host(); // This isn't superfluous; GURL case canonicalization doesn't hit the embedded // percent-encoded characters. StringToLowerASCII(&url_domain); std::string url_domain_and_port = url_domain + ":" + IntToString(url.EffectiveIntPort()); if (config_.proxy_bypass_local_names) { if (url.host().find('.') == std::string::npos) return true; } for(std::vector::const_iterator i = config_.proxy_bypass.begin(); i != config_.proxy_bypass.end(); ++i) { std::string bypass_url_domain = *i; // The proxy server bypass list can contain entities with http/https // If no scheme is specified then it indicates that all schemes are // allowed for the current entry. For matching this we just use // the protocol scheme of the url passed in. size_t scheme_colon = bypass_url_domain.find("://"); if (scheme_colon == std::string::npos) { std::string bypass_url_domain_with_scheme = url.scheme(); scheme_colon = bypass_url_domain_with_scheme.length(); bypass_url_domain_with_scheme += "://"; bypass_url_domain_with_scheme += bypass_url_domain; bypass_url_domain = bypass_url_domain_with_scheme; } std::string* url_compare_reference = &url_domain; size_t port_colon = bypass_url_domain.rfind(":"); if (port_colon > scheme_colon) { // If our match pattern includes a colon followed by a digit, // and either it's preceded by ']' (IPv6 with port) // or has no other colon (IPv4), // then match against :. // TODO(sdoyon): straighten this out, in particular the IPv6 brackets, // and do the parsing in ProxyConfig when we do the CIDR matching // mentioned below. std::string::const_iterator domain_begin = bypass_url_domain.begin() + scheme_colon + 3; // after :// std::string::const_iterator port_iter = bypass_url_domain.begin() + port_colon; std::string::const_iterator end = bypass_url_domain.end(); if ((port_iter + 1) < end && IsAsciiDigit(*(port_iter + 1)) && (*(port_iter - 1) == ']' || std::find(domain_begin, port_iter, ':') == port_iter)) url_compare_reference = &url_domain_and_port; } StringToLowerASCII(&bypass_url_domain); if (MatchPattern(*url_compare_reference, bypass_url_domain)) return true; // Some systems (the Mac, for example) allow CIDR-style specification of // proxy bypass for IP-specified hosts (e.g. "10.0.0.0/8"; see // http://www.tcd.ie/iss/internet/osx_proxy.php for a real-world example). // That's kinda cool so we'll provide that for everyone. // TODO(avi): implement here. See: http://crbug.com/9835. // IP addresses ought to be canonicalized for comparison (whether // with CIDR, port, or IP address alone). } return false; } SyncProxyServiceHelper::SyncProxyServiceHelper(MessageLoop* io_message_loop, ProxyService* proxy_service) : io_message_loop_(io_message_loop), proxy_service_(proxy_service), event_(false, false), ALLOW_THIS_IN_INITIALIZER_LIST(callback_( this, &SyncProxyServiceHelper::OnCompletion)) { DCHECK(io_message_loop_ != MessageLoop::current()); } int SyncProxyServiceHelper::ResolveProxy(const GURL& url, ProxyInfo* proxy_info) { DCHECK(io_message_loop_ != MessageLoop::current()); io_message_loop_->PostTask(FROM_HERE, NewRunnableMethod( this, &SyncProxyServiceHelper::StartAsyncResolve, url)); event_.Wait(); if (result_ == net::OK) { *proxy_info = proxy_info_; } return result_; } int SyncProxyServiceHelper::ReconsiderProxyAfterError(const GURL& url, ProxyInfo* proxy_info) { DCHECK(io_message_loop_ != MessageLoop::current()); io_message_loop_->PostTask(FROM_HERE, NewRunnableMethod( this, &SyncProxyServiceHelper::StartAsyncReconsider, url)); event_.Wait(); if (result_ == net::OK) { *proxy_info = proxy_info_; } return result_; } void SyncProxyServiceHelper::StartAsyncResolve(const GURL& url) { result_ = proxy_service_->ResolveProxy(url, &proxy_info_, &callback_, NULL); if (result_ != net::ERR_IO_PENDING) { OnCompletion(result_); } } void SyncProxyServiceHelper::StartAsyncReconsider(const GURL& url) { result_ = proxy_service_->ReconsiderProxyAfterError( url, &proxy_info_, &callback_, NULL); if (result_ != net::ERR_IO_PENDING) { OnCompletion(result_); } } void SyncProxyServiceHelper::OnCompletion(int rv) { result_ = rv; event_.Signal(); } } // namespace net