| // 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. |
| |
| #include "chrome/browser/chromeos/proxy_config_service_impl.h" |
| |
| #include <ostream> |
| |
| #include "base/logging.h" |
| #include "base/string_util.h" |
| #include "base/task.h" |
| #include "chrome/browser/chromeos/cros/cros_library.h" |
| #include "chrome/browser/chromeos/cros_settings_names.h" |
| #include "chrome/browser/policy/proto/chrome_device_policy.pb.h" |
| #include "chrome/browser/prefs/proxy_prefs.h" |
| #include "content/browser/browser_thread.h" |
| |
| namespace em = enterprise_management; |
| |
| namespace chromeos { |
| |
| namespace { |
| |
| const char* SourceToString(ProxyConfigServiceImpl::ProxyConfig::Source source) { |
| switch (source) { |
| case ProxyConfigServiceImpl::ProxyConfig::SOURCE_NONE: |
| return "SOURCE_NONE"; |
| case ProxyConfigServiceImpl::ProxyConfig::SOURCE_POLICY: |
| return "SOURCE_POLICY"; |
| case ProxyConfigServiceImpl::ProxyConfig::SOURCE_OWNER: |
| return "SOURCE_OWNER"; |
| } |
| NOTREACHED() << "Unrecognized source type"; |
| return ""; |
| } |
| |
| std::ostream& operator<<(std::ostream& out, |
| const ProxyConfigServiceImpl::ProxyConfig::ManualProxy& proxy) { |
| out << " " << SourceToString(proxy.source) << "\n" |
| << " server: " << (proxy.server.is_valid() ? proxy.server.ToURI() : "") |
| << "\n"; |
| return out; |
| } |
| |
| std::ostream& operator<<(std::ostream& out, |
| const ProxyConfigServiceImpl::ProxyConfig& config) { |
| switch (config.mode) { |
| case ProxyConfigServiceImpl::ProxyConfig::MODE_DIRECT: |
| out << "Direct connection:\n " |
| << SourceToString(config.automatic_proxy.source) << "\n"; |
| break; |
| case ProxyConfigServiceImpl::ProxyConfig::MODE_AUTO_DETECT: |
| out << "Auto detection:\n " |
| << SourceToString(config.automatic_proxy.source) << "\n"; |
| break; |
| case ProxyConfigServiceImpl::ProxyConfig::MODE_PAC_SCRIPT: |
| out << "Custom PAC script:\n " |
| << SourceToString(config.automatic_proxy.source) |
| << "\n PAC: " << config.automatic_proxy.pac_url << "\n"; |
| break; |
| case ProxyConfigServiceImpl::ProxyConfig::MODE_SINGLE_PROXY: |
| out << "Single proxy:\n" << config.single_proxy; |
| break; |
| case ProxyConfigServiceImpl::ProxyConfig::MODE_PROXY_PER_SCHEME: |
| out << "HTTP proxy: " << config.http_proxy; |
| out << "HTTPS proxy: " << config.https_proxy; |
| out << "FTP proxy: " << config.ftp_proxy; |
| out << "SOCKS proxy: " << config.socks_proxy; |
| break; |
| default: |
| NOTREACHED() << "Unrecognized proxy config mode"; |
| break; |
| } |
| if (config.mode == ProxyConfigServiceImpl::ProxyConfig::MODE_SINGLE_PROXY || |
| config.mode == |
| ProxyConfigServiceImpl::ProxyConfig::MODE_PROXY_PER_SCHEME) { |
| out << "Bypass list: "; |
| if (config.bypass_rules.rules().empty()) { |
| out << "[None]"; |
| } else { |
| const net::ProxyBypassRules& bypass_rules = config.bypass_rules; |
| net::ProxyBypassRules::RuleList::const_iterator it; |
| for (it = bypass_rules.rules().begin(); |
| it != bypass_rules.rules().end(); ++it) { |
| out << "\n " << (*it)->ToString(); |
| } |
| } |
| } |
| return out; |
| } |
| |
| std::string ProxyConfigToString( |
| const ProxyConfigServiceImpl::ProxyConfig& proxy_config) { |
| std::ostringstream stream; |
| stream << proxy_config; |
| return stream.str(); |
| } |
| |
| } // namespace |
| |
| //---------- ProxyConfigServiceImpl::ProxyConfig::Setting methods -------------- |
| |
| bool ProxyConfigServiceImpl::ProxyConfig::Setting::CanBeWrittenByUser( |
| bool user_is_owner) { |
| // Setting can only be written by user if user is owner and setting is not |
| // from policy. |
| return user_is_owner && source != ProxyConfig::SOURCE_POLICY; |
| } |
| |
| //----------- ProxyConfigServiceImpl::ProxyConfig: public methods -------------- |
| |
| void ProxyConfigServiceImpl::ProxyConfig::ToNetProxyConfig( |
| net::ProxyConfig* net_config) { |
| switch (mode) { |
| case MODE_DIRECT: |
| *net_config = net::ProxyConfig::CreateDirect(); |
| break; |
| case MODE_AUTO_DETECT: |
| *net_config = net::ProxyConfig::CreateAutoDetect(); |
| break; |
| case MODE_PAC_SCRIPT: |
| *net_config = net::ProxyConfig::CreateFromCustomPacURL( |
| automatic_proxy.pac_url); |
| break; |
| case MODE_SINGLE_PROXY: |
| *net_config = net::ProxyConfig(); |
| net_config->proxy_rules().type = |
| net::ProxyConfig::ProxyRules::TYPE_SINGLE_PROXY; |
| net_config->proxy_rules().single_proxy = single_proxy.server; |
| net_config->proxy_rules().bypass_rules = bypass_rules; |
| break; |
| case MODE_PROXY_PER_SCHEME: |
| *net_config = net::ProxyConfig(); |
| net_config->proxy_rules().type = |
| net::ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME; |
| net_config->proxy_rules().proxy_for_http = http_proxy.server; |
| net_config->proxy_rules().proxy_for_https = https_proxy.server; |
| net_config->proxy_rules().proxy_for_ftp = ftp_proxy.server; |
| net_config->proxy_rules().fallback_proxy = socks_proxy.server; |
| net_config->proxy_rules().bypass_rules = bypass_rules; |
| break; |
| default: |
| NOTREACHED() << "Unrecognized proxy config mode"; |
| break; |
| } |
| } |
| |
| bool ProxyConfigServiceImpl::ProxyConfig::CanBeWrittenByUser( |
| bool user_is_owner, const std::string& scheme) { |
| // Setting can only be written by user if user is owner and setting is not |
| // from policy. |
| Setting* setting = NULL; |
| switch (mode) { |
| case MODE_DIRECT: |
| case MODE_AUTO_DETECT: |
| case MODE_PAC_SCRIPT: |
| setting = &automatic_proxy; |
| break; |
| case MODE_SINGLE_PROXY: |
| setting = &single_proxy; |
| break; |
| case MODE_PROXY_PER_SCHEME: |
| setting = MapSchemeToProxy(scheme); |
| break; |
| default: |
| break; |
| } |
| if (!setting) { |
| NOTREACHED() << "Unrecognized proxy config mode"; |
| return false; |
| } |
| return setting->CanBeWrittenByUser(user_is_owner); |
| } |
| |
| ProxyConfigServiceImpl::ProxyConfig::ManualProxy* |
| ProxyConfigServiceImpl::ProxyConfig::MapSchemeToProxy( |
| const std::string& scheme) { |
| if (scheme == "http") |
| return &http_proxy; |
| if (scheme == "https") |
| return &https_proxy; |
| if (scheme == "ftp") |
| return &ftp_proxy; |
| if (scheme == "socks") |
| return &socks_proxy; |
| NOTREACHED() << "Invalid scheme: " << scheme; |
| return NULL; |
| } |
| |
| bool ProxyConfigServiceImpl::ProxyConfig::Serialize(std::string* output) { |
| em::DeviceProxySettingsProto proxy_proto; |
| switch (mode) { |
| case MODE_DIRECT: { |
| proxy_proto.set_proxy_mode(ProxyPrefs::kDirectProxyModeName); |
| break; |
| } |
| case MODE_AUTO_DETECT: { |
| proxy_proto.set_proxy_mode(ProxyPrefs::kAutoDetectProxyModeName); |
| break; |
| } |
| case MODE_PAC_SCRIPT: { |
| proxy_proto.set_proxy_mode(ProxyPrefs::kPacScriptProxyModeName); |
| if (!automatic_proxy.pac_url.is_empty()) |
| proxy_proto.set_proxy_pac_url(automatic_proxy.pac_url.spec()); |
| break; |
| } |
| case MODE_SINGLE_PROXY: { |
| proxy_proto.set_proxy_mode(ProxyPrefs::kFixedServersProxyModeName); |
| if (single_proxy.server.is_valid()) |
| proxy_proto.set_proxy_server(single_proxy.server.ToURI()); |
| break; |
| } |
| case MODE_PROXY_PER_SCHEME: { |
| proxy_proto.set_proxy_mode(ProxyPrefs::kFixedServersProxyModeName); |
| std::string spec; |
| EncodeAndAppendProxyServer("http", http_proxy.server, &spec); |
| EncodeAndAppendProxyServer("https", https_proxy.server, &spec); |
| EncodeAndAppendProxyServer("ftp", ftp_proxy.server, &spec); |
| EncodeAndAppendProxyServer("socks", socks_proxy.server, &spec); |
| if (!spec.empty()) |
| proxy_proto.set_proxy_server(spec); |
| break; |
| } |
| default: { |
| NOTREACHED() << "Unrecognized proxy config mode"; |
| break; |
| } |
| } |
| proxy_proto.set_proxy_bypass_list(bypass_rules.ToString()); |
| return proxy_proto.SerializeToString(output); |
| } |
| |
| bool ProxyConfigServiceImpl::ProxyConfig::Deserialize( |
| const std::string& input) { |
| em::DeviceProxySettingsProto proxy_proto; |
| if (!proxy_proto.ParseFromString(input)) |
| return false; |
| |
| const std::string& mode_string(proxy_proto.proxy_mode()); |
| if (mode_string == ProxyPrefs::kDirectProxyModeName) { |
| mode = MODE_DIRECT; |
| } else if (mode_string == ProxyPrefs::kAutoDetectProxyModeName) { |
| mode = MODE_AUTO_DETECT; |
| } else if (mode_string == ProxyPrefs::kPacScriptProxyModeName) { |
| mode = MODE_PAC_SCRIPT; |
| if (proxy_proto.has_proxy_pac_url()) |
| automatic_proxy.pac_url = GURL(proxy_proto.proxy_pac_url()); |
| } else if (mode_string == ProxyPrefs::kFixedServersProxyModeName) { |
| net::ProxyConfig::ProxyRules rules; |
| rules.ParseFromString(proxy_proto.proxy_server()); |
| switch (rules.type) { |
| case net::ProxyConfig::ProxyRules::TYPE_NO_RULES: |
| return false; |
| case net::ProxyConfig::ProxyRules::TYPE_SINGLE_PROXY: |
| if (!rules.single_proxy.is_valid()) |
| return false; |
| mode = MODE_SINGLE_PROXY; |
| single_proxy.server = rules.single_proxy; |
| break; |
| case net::ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME: |
| // Make sure we have valid server for at least one of the protocols. |
| if (!rules.proxy_for_http.is_valid() && |
| !rules.proxy_for_https.is_valid() && |
| !rules.proxy_for_ftp.is_valid() && |
| !rules.fallback_proxy.is_valid()) { |
| return false; |
| } |
| mode = MODE_PROXY_PER_SCHEME; |
| if (rules.proxy_for_http.is_valid()) |
| http_proxy.server = rules.proxy_for_http; |
| if (rules.proxy_for_https.is_valid()) |
| https_proxy.server = rules.proxy_for_https; |
| if (rules.proxy_for_ftp.is_valid()) |
| ftp_proxy.server = rules.proxy_for_ftp; |
| if (rules.fallback_proxy.is_valid()) |
| socks_proxy.server = rules.fallback_proxy; |
| break; |
| } |
| } else { |
| NOTREACHED() << "Unrecognized proxy config mode"; |
| return false; |
| } |
| |
| if (proxy_proto.has_proxy_bypass_list()) |
| bypass_rules.ParseFromString(proxy_proto.proxy_bypass_list()); |
| |
| return true; |
| } |
| |
| std::string ProxyConfigServiceImpl::ProxyConfig::ToString() const { |
| return ProxyConfigToString(*this); |
| } |
| |
| //----------- ProxyConfigServiceImpl::ProxyConfig: private methods ------------- |
| |
| // static |
| void ProxyConfigServiceImpl::ProxyConfig::EncodeAndAppendProxyServer( |
| const std::string& scheme, |
| const net::ProxyServer& server, |
| std::string* spec) { |
| if (!server.is_valid()) |
| return; |
| |
| if (!spec->empty()) |
| *spec += ';'; |
| |
| if (!scheme.empty()) { |
| *spec += scheme; |
| *spec += "="; |
| } |
| *spec += server.ToURI(); |
| } |
| |
| //------------------- ProxyConfigServiceImpl: public methods ------------------- |
| |
| ProxyConfigServiceImpl::ProxyConfigServiceImpl() |
| : can_post_task_(false), |
| config_availability_(net::ProxyConfigService::CONFIG_PENDING), |
| persist_to_device_(true), |
| persist_to_device_pending_(false) { |
| // Start async fetch of proxy config from settings persisted on device. |
| // TODO(kuan): retrieve config from policy and owner and merge them |
| bool use_default = true; |
| if (CrosLibrary::Get()->EnsureLoaded()) { |
| retrieve_property_op_ = SignedSettings::CreateRetrievePropertyOp( |
| kSettingProxyEverywhere, this); |
| if (retrieve_property_op_) { |
| retrieve_property_op_->Execute(); |
| VLOG(1) << "Start retrieving proxy setting from device"; |
| use_default = false; |
| } else { |
| VLOG(1) << "Fail to retrieve proxy setting from device"; |
| } |
| } |
| if (use_default) |
| config_availability_ = net::ProxyConfigService::CONFIG_UNSET; |
| can_post_task_ = true; |
| } |
| |
| ProxyConfigServiceImpl::ProxyConfigServiceImpl(const ProxyConfig& init_config) |
| : can_post_task_(true), |
| config_availability_(net::ProxyConfigService::CONFIG_VALID), |
| persist_to_device_(false), |
| persist_to_device_pending_(false) { |
| reference_config_ = init_config; |
| // Update the IO-accessible copy in |cached_config_| as well. |
| cached_config_ = reference_config_; |
| } |
| |
| ProxyConfigServiceImpl::~ProxyConfigServiceImpl() { |
| } |
| |
| void ProxyConfigServiceImpl::UIGetProxyConfig(ProxyConfig* config) { |
| // Should be called from UI thread. |
| CheckCurrentlyOnUIThread(); |
| // Simply returns the copy on the UI thread. |
| *config = reference_config_; |
| } |
| |
| bool ProxyConfigServiceImpl::UISetProxyConfigToDirect() { |
| // Should be called from UI thread. |
| CheckCurrentlyOnUIThread(); |
| reference_config_.mode = ProxyConfig::MODE_DIRECT; |
| OnUISetProxyConfig(persist_to_device_); |
| return true; |
| } |
| |
| bool ProxyConfigServiceImpl::UISetProxyConfigToAutoDetect() { |
| // Should be called from UI thread. |
| CheckCurrentlyOnUIThread(); |
| reference_config_.mode = ProxyConfig::MODE_AUTO_DETECT; |
| OnUISetProxyConfig(persist_to_device_); |
| return true; |
| } |
| |
| bool ProxyConfigServiceImpl::UISetProxyConfigToPACScript(const GURL& pac_url) { |
| // Should be called from UI thread. |
| CheckCurrentlyOnUIThread(); |
| reference_config_.mode = ProxyConfig::MODE_PAC_SCRIPT; |
| reference_config_.automatic_proxy.pac_url = pac_url; |
| OnUISetProxyConfig(persist_to_device_); |
| return true; |
| } |
| |
| bool ProxyConfigServiceImpl::UISetProxyConfigToSingleProxy( |
| const net::ProxyServer& server) { |
| // Should be called from UI thread. |
| CheckCurrentlyOnUIThread(); |
| reference_config_.mode = ProxyConfig::MODE_SINGLE_PROXY; |
| reference_config_.single_proxy.server = server; |
| OnUISetProxyConfig(persist_to_device_); |
| return true; |
| } |
| |
| bool ProxyConfigServiceImpl::UISetProxyConfigToProxyPerScheme( |
| const std::string& scheme, const net::ProxyServer& server) { |
| // Should be called from UI thread. |
| CheckCurrentlyOnUIThread(); |
| ProxyConfig::ManualProxy* proxy = reference_config_.MapSchemeToProxy(scheme); |
| if (!proxy) { |
| NOTREACHED() << "Cannot set proxy: invalid scheme [" << scheme << "]"; |
| return false; |
| } |
| reference_config_.mode = ProxyConfig::MODE_PROXY_PER_SCHEME; |
| proxy->server = server; |
| OnUISetProxyConfig(persist_to_device_); |
| return true; |
| } |
| |
| bool ProxyConfigServiceImpl::UISetProxyConfigBypassRules( |
| const net::ProxyBypassRules& bypass_rules) { |
| // Should be called from UI thread. |
| CheckCurrentlyOnUIThread(); |
| DCHECK(reference_config_.mode == ProxyConfig::MODE_SINGLE_PROXY || |
| reference_config_.mode == ProxyConfig::MODE_PROXY_PER_SCHEME); |
| if (reference_config_.mode != ProxyConfig::MODE_SINGLE_PROXY && |
| reference_config_.mode != ProxyConfig::MODE_PROXY_PER_SCHEME) { |
| VLOG(1) << "Cannot set bypass rules for proxy mode [" |
| << reference_config_.mode << "]"; |
| return false; |
| } |
| reference_config_.bypass_rules = bypass_rules; |
| OnUISetProxyConfig(persist_to_device_); |
| return true; |
| } |
| |
| void ProxyConfigServiceImpl::AddObserver( |
| net::ProxyConfigService::Observer* observer) { |
| // Should be called from IO thread. |
| CheckCurrentlyOnIOThread(); |
| observers_.AddObserver(observer); |
| } |
| |
| void ProxyConfigServiceImpl::RemoveObserver( |
| net::ProxyConfigService::Observer* observer) { |
| // Should be called from IO thread. |
| CheckCurrentlyOnIOThread(); |
| observers_.RemoveObserver(observer); |
| } |
| |
| net::ProxyConfigService::ConfigAvailability |
| ProxyConfigServiceImpl::IOGetProxyConfig(net::ProxyConfig* net_config) { |
| // Should be called from IO thread. |
| CheckCurrentlyOnIOThread(); |
| if (config_availability_ == net::ProxyConfigService::CONFIG_VALID) |
| cached_config_.ToNetProxyConfig(net_config); |
| |
| return config_availability_; |
| } |
| |
| void ProxyConfigServiceImpl::OnSettingsOpCompleted( |
| SignedSettings::ReturnCode code, |
| bool value) { |
| if (SignedSettings::SUCCESS == code) |
| VLOG(1) << "Stored proxy setting to device"; |
| else |
| LOG(WARNING) << "Error storing proxy setting to device"; |
| store_property_op_ = NULL; |
| if (persist_to_device_pending_) |
| PersistConfigToDevice(); |
| } |
| |
| void ProxyConfigServiceImpl::OnSettingsOpCompleted( |
| SignedSettings::ReturnCode code, |
| std::string value) { |
| retrieve_property_op_ = NULL; |
| if (SignedSettings::SUCCESS == code) { |
| VLOG(1) << "Retrieved proxy setting from device, value=[" << value << "]"; |
| if (reference_config_.Deserialize(value)) { |
| IOSetProxyConfig(reference_config_, |
| net::ProxyConfigService::CONFIG_VALID); |
| return; |
| } else { |
| LOG(WARNING) << "Error deserializing device's proxy setting"; |
| } |
| } else { |
| LOG(WARNING) << "Error retrieving proxy setting from device"; |
| } |
| |
| // Update the configuration state on the IO thread. |
| IOSetProxyConfig(reference_config_, net::ProxyConfigService::CONFIG_UNSET); |
| } |
| |
| //------------------ ProxyConfigServiceImpl: private methods ------------------- |
| |
| void ProxyConfigServiceImpl::PersistConfigToDevice() { |
| DCHECK(!store_property_op_); |
| persist_to_device_pending_ = false; |
| std::string value; |
| if (!reference_config_.Serialize(&value)) { |
| LOG(WARNING) << "Error serializing proxy config"; |
| return; |
| } |
| store_property_op_ = SignedSettings::CreateStorePropertyOp( |
| kSettingProxyEverywhere, value, this); |
| store_property_op_->Execute(); |
| VLOG(1) << "Start storing proxy setting to device, value=" << value; |
| } |
| |
| void ProxyConfigServiceImpl::OnUISetProxyConfig(bool persist_to_device) { |
| IOSetProxyConfig(reference_config_, net::ProxyConfigService::CONFIG_VALID); |
| if (persist_to_device && CrosLibrary::Get()->EnsureLoaded()) { |
| if (store_property_op_) { |
| persist_to_device_pending_ = true; |
| VLOG(1) << "Pending persisting proxy setting to device"; |
| } else { |
| PersistConfigToDevice(); |
| } |
| } |
| } |
| |
| void ProxyConfigServiceImpl::IOSetProxyConfig( |
| const ProxyConfig& new_config, |
| net::ProxyConfigService::ConfigAvailability new_availability) { |
| if (!BrowserThread::CurrentlyOn(BrowserThread::IO) && can_post_task_) { |
| // Posts a task to IO thread with the new config, so it can update |
| // |cached_config_|. |
| Task* task = NewRunnableMethod(this, |
| &ProxyConfigServiceImpl::IOSetProxyConfig, |
| new_config, |
| new_availability); |
| if (!BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, task)) |
| VLOG(1) << "Couldn't post task to IO thread to set new proxy config"; |
| return; |
| } |
| |
| // Now guaranteed to be on the correct thread. |
| VLOG(1) << "Proxy configuration changed"; |
| cached_config_ = new_config; |
| config_availability_ = new_availability; |
| // Notify observers of new proxy config. |
| net::ProxyConfig net_config; |
| cached_config_.ToNetProxyConfig(&net_config); |
| FOR_EACH_OBSERVER(net::ProxyConfigService::Observer, observers_, |
| OnProxyConfigChanged(net_config, config_availability_)); |
| } |
| |
| void ProxyConfigServiceImpl::CheckCurrentlyOnIOThread() { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| } |
| |
| void ProxyConfigServiceImpl::CheckCurrentlyOnUIThread() { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| } |
| |
| } // namespace chromeos |