// Copyright (c) 2009 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_util.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 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(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 pref_branch; pref_service->ReadUserPrefs(nsnull); 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 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 += 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 proxy_settings_value(BuildProxyValueSet()); JSONStringValueSerializer serializer(output); return serializer.Serialize(*static_cast(proxy_settings_value.get())); }