blob: 26811bea689ef2ac365a1fbac69029ddf8091216 [file] [log] [blame]
// 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