blob: b1eadde81f241e0c5748b47e0276404345343bab [file] [log] [blame]
// Copyright (c) 2010 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 "base/string_number_conversions.h"
#include "chrome/common/automation_constants.h"
#include "chrome/common/json_value_serializer.h"
#include "chrome_frame/np_proxy_service.h"
#include "chrome_frame/np_browser_functions.h"
#include "net/proxy/proxy_config.h"
#include "third_party/xulrunner-sdk/win/include/xpcom/nsXPCOM.h"
#include "third_party/xulrunner-sdk/win/include/xpcom/nsIObserverService.h"
#include "third_party/xulrunner-sdk/win/include/xpcom/nsISupportsUtils.h"
#include "third_party/xulrunner-sdk/win/include/xpcom/nsStringAPI.h"
ASSOCIATE_IID(NS_IOBSERVERSERVICE_IID_STR, nsIObserverService);
ASSOCIATE_IID(NS_IPREFBRANCH_IID_STR, nsIPrefBranch);
// Firefox preference names.
const char* kProxyObserverRoot = "network.";
const char* kProxyObserverBranch = "proxy.";
const char* kProxyType = "proxy.type";
const char* kProxyAutoconfigUrl = "proxy.autoconfig_url";
const char* kProxyBypassList = "proxy.no_proxies_on";
const int kInvalidIntPref = -1;
// These are the proxy schemes that Chrome knows about at the moment.
// SOCKS is a notable ommission here, this will need to be updated when
// Chrome supports SOCKS proxies.
const NpProxyService::ProxyNames NpProxyService::kProxyInfo[] = {
{"http", "proxy.http", "proxy.http_port"},
{"https", "proxy.ssl", "proxy.ssl_port"},
{"ftp", "proxy.ftp", "proxy.ftp_port"} };
NpProxyService::NpProxyService(void)
: type_(PROXY_CONFIG_LAST), auto_detect_(false), no_proxy_(false),
system_config_(false), automation_client_(NULL) {
}
NpProxyService::~NpProxyService(void) {
}
bool NpProxyService::Initialize(NPP instance,
ChromeFrameAutomationClient* automation_client) {
DCHECK(automation_client);
automation_client_ = automation_client;
// Get the pref service
bool result = false;
ScopedNsPtr<nsISupports> service_manager_base;
npapi::GetValue(instance, NPNVserviceManager, service_manager_base.Receive());
if (service_manager_base != NULL) {
service_manager_.QueryFrom(service_manager_base);
if (service_manager_.get() == NULL) {
DLOG(ERROR) << "Failed to create ServiceManager. This only works in FF.";
} else {
service_manager_->GetServiceByContractID(
NS_PREFSERVICE_CONTRACTID, NS_GET_IID(nsIPrefService),
reinterpret_cast<void**>(pref_service_.Receive()));
if (!pref_service_) {
DLOG(ERROR) << "Failed to create PreferencesService";
} else {
result = InitializePrefBranch(pref_service_);
}
}
}
return result;
}
bool NpProxyService::InitializePrefBranch(nsIPrefService* pref_service) {
DCHECK(pref_service);
// Note that we cannot persist a reference to the pref branch because we
// also act as an observer of changes to the branch. As per
// nsIPrefBranch2.h, this would result in a circular reference between us
// and the pref branch, which can impede cleanup. There are workarounds,
// but let's try just not caching the branch reference for now.
bool result = false;
ScopedNsPtr<nsIPrefBranch> pref_branch;
pref_service->GetBranch(kProxyObserverRoot, pref_branch.Receive());
if (!pref_branch) {
DLOG(ERROR) << "Failed to get nsIPrefBranch";
} else {
if (!ReadProxySettings(pref_branch.get())) {
DLOG(ERROR) << "Could not read proxy settings.";
} else {
observer_pref_branch_.QueryFrom(pref_branch);
if (!observer_pref_branch_) {
DLOG(ERROR) << "Failed to get observer nsIPrefBranch2";
} else {
nsresult res = observer_pref_branch_->AddObserver(kProxyObserverBranch,
this, PR_FALSE);
result = NS_SUCCEEDED(res);
}
}
}
return result;
}
bool NpProxyService::UnInitialize() {
// Fail early if this was never created - we may not be running on FF.
if (!pref_service_)
return false;
// Unhook ourselves as an observer.
nsresult res = NS_ERROR_FAILURE;
if (observer_pref_branch_)
res = observer_pref_branch_->RemoveObserver(kProxyObserverBranch, this);
return NS_SUCCEEDED(res);
}
NS_IMETHODIMP NpProxyService::Observe(nsISupports* subject, const char* topic,
const PRUnichar* data) {
if (!subject || !topic) {
NOTREACHED();
return NS_ERROR_UNEXPECTED;
}
std::string topic_str(topic);
nsresult res = NS_OK;
if (topic_str == NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) {
// Looks like our proxy settings changed. We need to reload!
// I have observed some extremely strange behaviour here. Specifically,
// we are supposed to be able to QI |subject| and get from it an
// nsIPrefBranch from which we can query new values. This has erratic
// behaviour, specifically subject starts returning null on all member
// queries. So I am using the cached nsIPrefBranch2 (that we used to add
// the observer) to do the querying.
if (NS_SUCCEEDED(res)) {
if (!ReadProxySettings(observer_pref_branch_)) {
res = NS_ERROR_UNEXPECTED;
} else {
std::string proxy_settings;
if (GetProxyValueJSONString(&proxy_settings))
automation_client_->SetProxySettings(proxy_settings);
}
}
} else {
NOTREACHED();
}
return res;
}
std::string NpProxyService::GetStringPref(nsIPrefBranch* pref_branch,
const char* pref_name) {
nsCString pref_string;
std::string result;
nsresult rv = pref_branch->GetCharPref(pref_name, getter_Copies(pref_string));
if (SUCCEEDED(rv) && pref_string.get()) {
result = pref_string.get();
}
return result;
}
int NpProxyService::GetIntPref(nsIPrefBranch* pref_branch,
const char* pref_name) {
PRInt32 pref_int;
int result = kInvalidIntPref;
nsresult rv = pref_branch->GetIntPref(pref_name, &pref_int);
if (SUCCEEDED(rv)) {
result = pref_int;
}
return result;
}
bool NpProxyService::GetBoolPref(nsIPrefBranch* pref_branch,
const char* pref_name) {
PRBool pref_bool;
bool result = false;
nsresult rv = pref_branch->GetBoolPref(pref_name, &pref_bool);
if (SUCCEEDED(rv)) {
result = pref_bool == PR_TRUE;
}
return result;
}
void NpProxyService::Reset() {
type_ = PROXY_CONFIG_LAST;
auto_detect_ = false;
no_proxy_ = false;
system_config_ = false;
manual_proxies_.clear();
pac_url_.clear();
proxy_bypass_list_.clear();
}
bool NpProxyService::ReadProxySettings(nsIPrefBranch* pref_branch) {
DCHECK(pref_branch);
// Clear our current settings.
Reset();
type_ = GetIntPref(pref_branch, kProxyType);
if (type_ == kInvalidIntPref) {
NOTREACHED();
return false;
}
switch (type_) {
case PROXY_CONFIG_DIRECT:
case PROXY_CONFIG_DIRECT4X:
no_proxy_ = true;
break;
case PROXY_CONFIG_SYSTEM:
// _SYSTEM is documented as "Use system settings if available, otherwise
// DIRECT". It isn't clear under what circumstances system settings would
// be unavailable, but I'll special-case this nonetheless and have
// GetProxyValueJSONString() return empty if we get this proxy type.
DLOG(WARNING) << "Received PROXY_CONFIG_SYSTEM proxy type.";
system_config_ = true;
break;
case PROXY_CONFIG_WPAD:
auto_detect_ = true;
break;
case PROXY_CONFIG_PAC:
pac_url_ = GetStringPref(pref_branch, kProxyAutoconfigUrl);
break;
case PROXY_CONFIG_MANUAL:
// Read in the values for each of the known schemes.
for (int i = 0; i < arraysize(kProxyInfo); i++) {
ManualProxyEntry entry;
entry.url = GetStringPref(pref_branch, kProxyInfo[i].pref_name);
entry.port = GetIntPref(pref_branch, kProxyInfo[i].port_pref_name);
if (!entry.url.empty() && entry.port != kInvalidIntPref) {
entry.scheme = kProxyInfo[i].chrome_scheme;
manual_proxies_.push_back(entry);
}
}
// Also pick up the list of URLs we bypass proxies for.
proxy_bypass_list_ = GetStringPref(pref_branch, kProxyBypassList);
break;
default:
NOTREACHED();
return false;
}
return true;
}
DictionaryValue* NpProxyService::BuildProxyValueSet() {
scoped_ptr<DictionaryValue> proxy_settings_value(new DictionaryValue);
if (auto_detect_) {
proxy_settings_value->SetBoolean(automation::kJSONProxyAutoconfig,
auto_detect_);
}
if (no_proxy_) {
proxy_settings_value->SetBoolean(automation::kJSONProxyNoProxy, no_proxy_);
}
if (!pac_url_.empty()) {
proxy_settings_value->SetString(automation::kJSONProxyPacUrl, pac_url_);
}
if (!proxy_bypass_list_.empty()) {
proxy_settings_value->SetString(automation::kJSONProxyBypassList,
proxy_bypass_list_);
}
// Fill in the manual proxy settings. Build a string representation that
// corresponds to the format of the input parameter to
// ProxyConfig::ProxyRules::ParseFromString.
std::string manual_proxy_settings;
ManualProxyList::const_iterator iter(manual_proxies_.begin());
for (; iter != manual_proxies_.end(); iter++) {
DCHECK(!iter->scheme.empty());
DCHECK(!iter->url.empty());
DCHECK(iter->port != kInvalidIntPref);
manual_proxy_settings += iter->scheme;
manual_proxy_settings += "=";
manual_proxy_settings += iter->url;
manual_proxy_settings += ":";
manual_proxy_settings += base::IntToString(iter->port);
manual_proxy_settings += ";";
}
if (!manual_proxy_settings.empty()) {
proxy_settings_value->SetString(automation::kJSONProxyServer,
manual_proxy_settings);
}
return proxy_settings_value.release();
}
bool NpProxyService::GetProxyValueJSONString(std::string* output) {
DCHECK(output);
output->empty();
// If we detected a PROXY_CONFIG_SYSTEM config type or failed to obtain the
// pref service then return false here to make Chrome continue using its
// default proxy settings.
if (system_config_ || !pref_service_)
return false;
scoped_ptr<DictionaryValue> proxy_settings_value(BuildProxyValueSet());
JSONStringValueSerializer serializer(output);
return serializer.Serialize(*static_cast<Value*>(proxy_settings_value.get()));
}