| // 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/extensions/extension_proxy_api.h" |
| |
| #include "base/base64.h" |
| #include "base/json/json_writer.h" |
| #include "base/string_util.h" |
| #include "base/string_tokenizer.h" |
| #include "base/utf_string_conversions.h" |
| #include "base/values.h" |
| #include "chrome/browser/prefs/proxy_config_dictionary.h" |
| #include "chrome/browser/extensions/extension_event_router_forwarder.h" |
| #include "chrome/browser/extensions/extension_service.h" |
| #include "chrome/common/extensions/extension_error_utils.h" |
| #include "chrome/common/pref_names.h" |
| #include "net/base/net_errors.h" |
| #include "net/proxy/proxy_config.h" |
| |
| namespace { |
| |
| // The scheme for which to use a manually specified proxy, not of the proxy URI |
| // itself. |
| enum { |
| SCHEME_ALL = 0, |
| SCHEME_HTTP, |
| SCHEME_HTTPS, |
| SCHEME_FTP, |
| SCHEME_FALLBACK, |
| SCHEME_MAX = SCHEME_FALLBACK // Keep this value up to date. |
| }; |
| |
| // The names of the JavaScript properties to extract from the proxy_rules. |
| // These must be kept in sync with the SCHEME_* constants. |
| const char* field_name[] = { "singleProxy", |
| "proxyForHttp", |
| "proxyForHttps", |
| "proxyForFtp", |
| "fallbackProxy" }; |
| |
| // The names of the schemes to be used to build the preference value string |
| // for manual proxy settings. These must be kept in sync with the SCHEME_* |
| // constants. |
| const char* scheme_name[] = { "*error*", |
| "http", |
| "https", |
| "ftp", |
| "socks" }; |
| |
| // String literals in dictionaries used to communicate with extension. |
| const char kProxyCfgMode[] = "mode"; |
| const char kProxyCfgPacScript[] = "pacScript"; |
| const char kProxyCfgPacScriptUrl[] = "url"; |
| const char kProxyCfgPacScriptData[] = "data"; |
| const char kProxyCfgRules[] = "rules"; |
| const char kProxyCfgRuleHost[] = "host"; |
| const char kProxyCfgRulePort[] = "port"; |
| const char kProxyCfgBypassList[] = "bypassList"; |
| const char kProxyCfgScheme[] = "scheme"; |
| const char kProxyCfgValue[] = "value"; |
| |
| const char kProxyEventFatal[] = "fatal"; |
| const char kProxyEventError[] = "error"; |
| const char kProxyEventDetails[] = "details"; |
| const char kProxyEventOnProxyError[] = "experimental.proxy.onProxyError"; |
| |
| |
| const char kPACDataUrlPrefix[] = |
| "data:application/x-ns-proxy-autoconfig;base64,"; |
| |
| COMPILE_ASSERT(SCHEME_MAX == SCHEME_FALLBACK, |
| SCHEME_MAX_must_equal_SCHEME_FALLBACK); |
| COMPILE_ASSERT(arraysize(field_name) == SCHEME_MAX + 1, |
| field_name_array_is_wrong_size); |
| COMPILE_ASSERT(arraysize(scheme_name) == SCHEME_MAX + 1, |
| scheme_name_array_is_wrong_size); |
| COMPILE_ASSERT(SCHEME_ALL == 0, singleProxy_must_be_first_option); |
| |
| bool TokenizeToStringList( |
| const std::string& in, const std::string& delims, ListValue** out) { |
| scoped_ptr<ListValue> result(new ListValue); |
| StringTokenizer entries(in, delims); |
| while (entries.GetNext()) { |
| result->Append(Value::CreateStringValue(entries.token())); |
| } |
| *out = result.release(); |
| return true; |
| } |
| |
| bool CreateDataURLFromPACScript(const std::string& pac_script, |
| std::string* pac_script_url_base64_encoded) { |
| std::string pac_script_base64_encoded; |
| if (!base::Base64Encode(pac_script, &pac_script_base64_encoded)) |
| return false; |
| *pac_script_url_base64_encoded = |
| std::string(kPACDataUrlPrefix) + pac_script_base64_encoded; |
| return true; |
| } |
| |
| bool CreatePACScriptFromDataURL( |
| const std::string& pac_script_url_base64_encoded, std::string* pac_script) { |
| if (pac_script_url_base64_encoded.find(kPACDataUrlPrefix) != 0) { |
| return false; |
| } |
| std::string pac_script_base64_encoded = |
| pac_script_url_base64_encoded.substr(strlen(kPACDataUrlPrefix)); |
| return base::Base64Decode(pac_script_base64_encoded, pac_script); |
| } |
| |
| } // namespace |
| |
| // static |
| ExtensionProxyEventRouter* ExtensionProxyEventRouter::GetInstance() { |
| return Singleton<ExtensionProxyEventRouter>::get(); |
| } |
| |
| ExtensionProxyEventRouter::ExtensionProxyEventRouter() { |
| } |
| |
| ExtensionProxyEventRouter::~ExtensionProxyEventRouter() { |
| } |
| |
| void ExtensionProxyEventRouter::OnProxyError( |
| ExtensionEventRouterForwarder* event_router, |
| ProfileId profile_id, |
| int error_code) { |
| ListValue args; |
| DictionaryValue* dict = new DictionaryValue(); |
| dict->SetBoolean(kProxyEventFatal, true); |
| dict->SetString(kProxyEventError, net::ErrorToString(error_code)); |
| dict->SetString(kProxyEventDetails, ""); |
| args.Append(dict); |
| |
| std::string json_args; |
| base::JSONWriter::Write(&args, false, &json_args); |
| |
| if (profile_id != Profile::kInvalidProfileId) { |
| event_router->DispatchEventToRenderers( |
| kProxyEventOnProxyError, json_args, profile_id, true, GURL()); |
| } else { |
| event_router->BroadcastEventToRenderers( |
| kProxyEventOnProxyError, json_args, GURL()); |
| } |
| } |
| |
| bool SetProxySettingsFunction::GetProxyServer( |
| const DictionaryValue* dict, |
| net::ProxyServer::Scheme default_scheme, |
| net::ProxyServer* proxy_server) { |
| std::string scheme_string; // optional. |
| // We can safely assume that this is ASCII due to the allowed enumeration |
| // values specified in extension_api.json. |
| dict->GetStringASCII(kProxyCfgScheme, &scheme_string); |
| |
| net::ProxyServer::Scheme scheme = |
| net::ProxyServer::GetSchemeFromURI(scheme_string); |
| if (scheme == net::ProxyServer::SCHEME_INVALID) |
| scheme = default_scheme; |
| |
| // TODO(battre): handle UTF-8 in hostnames (http://crbug.com/72692) |
| string16 host16; |
| if (!dict->GetString(kProxyCfgRuleHost, &host16)) { |
| LOG(ERROR) << "Could not parse a 'rules.*.host' entry."; |
| return false; |
| } |
| if (!IsStringASCII(host16)) { |
| error_ = ExtensionErrorUtils::FormatErrorMessage( |
| "Invalid 'rules.???.host' entry '*'. 'host' field supports only ASCII " |
| "URLs (encode URLs in Punycode format).", |
| UTF16ToUTF8(host16)); |
| return false; |
| } |
| std::string host = UTF16ToASCII(host16); |
| |
| int port; // optional. |
| if (!dict->GetInteger(kProxyCfgRulePort, &port)) |
| port = net::ProxyServer::GetDefaultPortForScheme(scheme); |
| |
| *proxy_server = net::ProxyServer(scheme, net::HostPortPair(host, port)); |
| |
| return true; |
| } |
| |
| bool SetProxySettingsFunction::GetProxyRules(DictionaryValue* proxy_rules, |
| std::string* out) { |
| if (!proxy_rules) |
| return false; |
| |
| // Local data into which the parameters will be parsed. has_proxy describes |
| // whether a setting was found for the scheme; proxy_dict holds the |
| // DictionaryValues which in turn contain proxy server descriptions, and |
| // proxy_server holds ProxyServer structs containing those descriptions. |
| bool has_proxy[SCHEME_MAX + 1]; |
| DictionaryValue* proxy_dict[SCHEME_MAX + 1]; |
| net::ProxyServer proxy_server[SCHEME_MAX + 1]; |
| |
| // Looking for all possible proxy types is inefficient if we have a |
| // singleProxy that will supersede per-URL proxies, but it's worth it to keep |
| // the code simple and extensible. |
| for (size_t i = 0; i <= SCHEME_MAX; ++i) { |
| has_proxy[i] = proxy_rules->GetDictionary(field_name[i], &proxy_dict[i]); |
| if (has_proxy[i]) { |
| net::ProxyServer::Scheme default_scheme = net::ProxyServer::SCHEME_HTTP; |
| if (!GetProxyServer(proxy_dict[i], default_scheme, &proxy_server[i])) { |
| // Don't set |error_| here, as GetProxyServer takes care of that. |
| return false; |
| } |
| } |
| } |
| |
| // Handle case that only singleProxy is specified. |
| if (has_proxy[SCHEME_ALL]) { |
| for (size_t i = 1; i <= SCHEME_MAX; ++i) { |
| if (has_proxy[i]) { |
| error_ = ExtensionErrorUtils::FormatErrorMessage( |
| "Proxy rule for * and * cannot be set at the same time.", |
| field_name[SCHEME_ALL], field_name[i]); |
| return false; |
| } |
| } |
| *out = proxy_server[SCHEME_ALL].ToURI(); |
| return true; |
| } |
| |
| // Handle case that anything but singleProxy is specified. |
| |
| // Build the proxy preference string. |
| std::string proxy_pref; |
| for (size_t i = 1; i <= SCHEME_MAX; ++i) { |
| if (has_proxy[i]) { |
| // http=foopy:4010;ftp=socks5://foopy2:80 |
| if (!proxy_pref.empty()) |
| proxy_pref.append(";"); |
| proxy_pref.append(scheme_name[i]); |
| proxy_pref.append("="); |
| proxy_pref.append(proxy_server[i].ToURI()); |
| } |
| } |
| |
| *out = proxy_pref; |
| return true; |
| } |
| |
| bool SetProxySettingsFunction::JoinUrlList( |
| ListValue* list, const std::string& joiner, std::string* out) { |
| std::string result; |
| for (size_t i = 0; i < list->GetSize(); ++i) { |
| if (!result.empty()) |
| result.append(joiner); |
| // TODO(battre): handle UTF-8 (http://crbug.com/72692) |
| string16 entry; |
| if (!list->GetString(i, &entry)) { |
| LOG(ERROR) << "'rules.bypassList' could not be parsed."; |
| return false; |
| } |
| if (!IsStringASCII(entry)) { |
| error_ = "'rules.bypassList' supports only ASCII URLs " |
| "(encode URLs in Punycode format)."; |
| return false; |
| } |
| result.append(UTF16ToASCII(entry)); |
| } |
| *out = result; |
| return true; |
| } |
| |
| bool SetProxySettingsFunction::GetBypassList(DictionaryValue* proxy_rules, |
| std::string* out) { |
| if (!proxy_rules) |
| return false; |
| |
| ListValue* bypass_list; |
| if (!proxy_rules->HasKey(kProxyCfgBypassList)) { |
| *out = ""; |
| return true; |
| } |
| if (!proxy_rules->GetList(kProxyCfgBypassList, &bypass_list)) { |
| LOG(ERROR) << "'rules.bypassList' not be parsed."; |
| return false; |
| } |
| |
| return JoinUrlList(bypass_list, ",", out); |
| } |
| |
| bool SetProxySettingsFunction::RunImpl() { |
| DictionaryValue* details = NULL; |
| EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(1, &details)); |
| |
| DictionaryValue* proxy_config = NULL; |
| EXTENSION_FUNCTION_VALIDATE(details->GetDictionary("value", &proxy_config)); |
| |
| std::string proxy_mode; |
| // We can safely assume that this is ASCII due to the allowed enumeration |
| // values specified in extension_api.json. |
| proxy_config->GetStringASCII(kProxyCfgMode, &proxy_mode); |
| ProxyPrefs::ProxyMode mode_enum; |
| if (!ProxyPrefs::StringToProxyMode(proxy_mode, &mode_enum)) { |
| LOG(ERROR) << "Invalid mode for proxy settings: " << proxy_mode; |
| return false; |
| } |
| |
| DictionaryValue* pac_dict = NULL; |
| proxy_config->GetDictionary(kProxyCfgPacScript, &pac_dict); |
| |
| // TODO(battre): Handle UTF-8 URLs (http://crbug.com/72692) |
| string16 pac_url16; |
| if (pac_dict && |
| pac_dict->HasKey(kProxyCfgPacScriptUrl) && |
| !pac_dict->GetString(kProxyCfgPacScriptUrl, &pac_url16)) { |
| LOG(ERROR) << "'pacScript.url' could not be parsed."; |
| return false; |
| } |
| if (!IsStringASCII(pac_url16)) { |
| error_ = "'pacScript.url' supports only ASCII URLs " |
| "(encode URLs in Punycode format)."; |
| return false; |
| } |
| std::string pac_url = UTF16ToASCII(pac_url16); |
| |
| string16 pac_data16; |
| if (pac_dict && |
| pac_dict->HasKey(kProxyCfgPacScriptData) && |
| !pac_dict->GetString(kProxyCfgPacScriptData, &pac_data16)) { |
| LOG(ERROR) << "'pacScript.data' could not be parsed."; |
| return false; |
| } |
| if (!IsStringASCII(pac_data16)) { |
| error_ = "'pacScript.data' supports only ASCII code" |
| "(encode URLs in Punycode format)."; |
| return false; |
| } |
| std::string pac_data = UTF16ToASCII(pac_data16); |
| |
| DictionaryValue* proxy_rules = NULL; |
| proxy_config->GetDictionary(kProxyCfgRules, &proxy_rules); |
| std::string proxy_rules_string; |
| if (proxy_rules && !GetProxyRules(proxy_rules, &proxy_rules_string)) { |
| // Do not set error message as GetProxyRules does that. |
| return false; |
| } |
| std::string bypass_list; |
| if (proxy_rules && !GetBypassList(proxy_rules, &bypass_list)) { |
| LOG(ERROR) << "Invalid 'bypassList' specified."; |
| return false; |
| } |
| |
| DictionaryValue* result_proxy_config = NULL; |
| switch (mode_enum) { |
| case ProxyPrefs::MODE_DIRECT: |
| result_proxy_config = ProxyConfigDictionary::CreateDirect(); |
| break; |
| case ProxyPrefs::MODE_AUTO_DETECT: |
| result_proxy_config = ProxyConfigDictionary::CreateAutoDetect(); |
| break; |
| case ProxyPrefs::MODE_PAC_SCRIPT: { |
| if (!pac_dict) { |
| error_ = "Proxy mode 'pac_script' requires a 'pacScript' field."; |
| return false; |
| } |
| std::string url; |
| if (!pac_url.empty()) { |
| url = pac_url; |
| } else if (!pac_data.empty()) { |
| if (!CreateDataURLFromPACScript(pac_data, &url)) { |
| error_ = "Internal error, at base64 encoding of 'pacScript.data'."; |
| return false; |
| } |
| } else { |
| error_ = "Proxy mode 'pac_script' requires a 'pacScript' field with " |
| "either a 'url' field or a 'data' field."; |
| return false; |
| } |
| result_proxy_config = ProxyConfigDictionary::CreatePacScript(url); |
| break; |
| } |
| case ProxyPrefs::MODE_FIXED_SERVERS: { |
| if (!proxy_rules) { |
| error_ = "Proxy mode 'fixed_servers' requires a 'rules' field."; |
| return false; |
| } |
| result_proxy_config = ProxyConfigDictionary::CreateFixedServers( |
| proxy_rules_string, bypass_list); |
| break; |
| } |
| case ProxyPrefs::MODE_SYSTEM: |
| result_proxy_config = ProxyConfigDictionary::CreateSystem(); |
| break; |
| case ProxyPrefs::kModeCount: |
| NOTREACHED(); |
| } |
| if (!result_proxy_config) |
| return false; |
| |
| details->Set("value", result_proxy_config); |
| return SetPreferenceFunction::RunImpl(); |
| } |
| |
| bool GetProxySettingsFunction::RunImpl() { |
| if (!GetPreferenceFunction::RunImpl()) |
| return false; |
| |
| DCHECK(result_->IsType(Value::TYPE_DICTIONARY)); |
| |
| DictionaryValue* result_dict_ = static_cast<DictionaryValue*>(result_.get()); |
| |
| // This is how it is stored in the PrefStores: |
| DictionaryValue* proxy_prefs = NULL; |
| if (!result_dict_->GetDictionary(kProxyCfgValue, &proxy_prefs)) { |
| LOG(ERROR) << "Received invalid configuration."; |
| return false; |
| } |
| |
| // This is how it is presented to the API caller: |
| scoped_ptr<DictionaryValue> out(new DictionaryValue); |
| |
| if (!ConvertToApiFormat(proxy_prefs, out.get())) { |
| // Do not set error message as ConvertToApiFormat does that. |
| return false; |
| } |
| |
| result_dict_->Set(kProxyCfgValue, out.release()); |
| return true; |
| } |
| |
| bool GetProxySettingsFunction::ConvertToApiFormat( |
| const DictionaryValue* proxy_prefs, |
| DictionaryValue* api_proxy_config) { |
| ProxyConfigDictionary dict(proxy_prefs); |
| |
| ProxyPrefs::ProxyMode mode; |
| if (!dict.GetMode(&mode)) { |
| LOG(ERROR) << "Cannot determine proxy mode."; |
| return false; |
| } |
| api_proxy_config->SetString(kProxyCfgMode, |
| ProxyPrefs::ProxyModeToString(mode)); |
| |
| switch (mode) { |
| case ProxyPrefs::MODE_DIRECT: |
| case ProxyPrefs::MODE_AUTO_DETECT: |
| case ProxyPrefs::MODE_SYSTEM: |
| // These modes have no further parameters. |
| break; |
| case ProxyPrefs::MODE_PAC_SCRIPT: { |
| std::string pac_url; |
| if (!dict.GetPacUrl(&pac_url)) { |
| error_ = "Invalid proxy configuration. Missing PAC URL."; |
| return false; |
| } |
| DictionaryValue* pac_dict = new DictionaryValue; |
| if (pac_url.find("data") == 0) { |
| std::string pac_data; |
| if (!CreatePACScriptFromDataURL(pac_url, &pac_data)) { |
| error_ = "Cannot decode base64-encoded PAC data URL."; |
| return false; |
| } |
| pac_dict->SetString(kProxyCfgPacScriptData, pac_data); |
| } else { |
| pac_dict->SetString(kProxyCfgPacScriptUrl, pac_url); |
| } |
| api_proxy_config->Set(kProxyCfgPacScript, pac_dict); |
| break; |
| } |
| case ProxyPrefs::MODE_FIXED_SERVERS: { |
| scoped_ptr<DictionaryValue> rules_dict(new DictionaryValue); |
| |
| std::string proxy_servers; |
| if (!dict.GetProxyServer(&proxy_servers)) { |
| error_ = "Missing proxy servers in configuration."; |
| return false; |
| } |
| if (!ParseRules(proxy_servers, rules_dict.get())) { |
| error_ = "Could not parse proxy rules."; |
| return false; |
| } |
| |
| bool hasBypassList = dict.HasBypassList(); |
| if (hasBypassList) { |
| std::string bypass_list_string; |
| if (!dict.GetBypassList(&bypass_list_string)) { |
| error_ = "Invalid bypassList in configuration."; |
| return false; |
| } |
| ListValue* bypass_list = NULL; |
| if (TokenizeToStringList(bypass_list_string, ",;", &bypass_list)) { |
| rules_dict->Set(kProxyCfgBypassList, bypass_list); |
| } else { |
| error_ = "Error parsing bypassList " + bypass_list_string; |
| return false; |
| } |
| } |
| api_proxy_config->Set(kProxyCfgRules, rules_dict.release()); |
| break; |
| } |
| case ProxyPrefs::kModeCount: |
| NOTREACHED(); |
| } |
| return true; |
| } |
| |
| bool GetProxySettingsFunction::ParseRules(const std::string& rules, |
| DictionaryValue* out) const { |
| net::ProxyConfig::ProxyRules config; |
| config.ParseFromString(rules); |
| switch (config.type) { |
| case net::ProxyConfig::ProxyRules::TYPE_NO_RULES: |
| return false; |
| case net::ProxyConfig::ProxyRules::TYPE_SINGLE_PROXY: |
| if (config.single_proxy.is_valid()) { |
| out->Set(field_name[SCHEME_ALL], |
| ConvertToDictionary(config.single_proxy)); |
| } |
| break; |
| case net::ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME: |
| if (config.proxy_for_http.is_valid()) { |
| out->Set(field_name[SCHEME_HTTP], |
| ConvertToDictionary(config.proxy_for_http)); |
| } |
| if (config.proxy_for_https.is_valid()) { |
| out->Set(field_name[SCHEME_HTTPS], |
| ConvertToDictionary(config.proxy_for_https)); |
| } |
| if (config.proxy_for_ftp.is_valid()) { |
| out->Set(field_name[SCHEME_FTP], |
| ConvertToDictionary(config.proxy_for_ftp)); |
| } |
| if (config.fallback_proxy.is_valid()) { |
| out->Set(field_name[SCHEME_FALLBACK], |
| ConvertToDictionary(config.fallback_proxy)); |
| } |
| COMPILE_ASSERT(SCHEME_MAX == 4, SCHEME_FORGOTTEN); |
| break; |
| } |
| return true; |
| } |
| |
| DictionaryValue* GetProxySettingsFunction::ConvertToDictionary( |
| const net::ProxyServer& proxy) const { |
| DictionaryValue* out = new DictionaryValue; |
| switch (proxy.scheme()) { |
| case net::ProxyServer::SCHEME_HTTP: |
| out->SetString(kProxyCfgScheme, "http"); |
| break; |
| case net::ProxyServer::SCHEME_HTTPS: |
| out->SetString(kProxyCfgScheme, "https"); |
| break; |
| case net::ProxyServer::SCHEME_SOCKS4: |
| out->SetString(kProxyCfgScheme, "socks4"); |
| break; |
| case net::ProxyServer::SCHEME_SOCKS5: |
| out->SetString(kProxyCfgScheme, "socks5"); |
| break; |
| case net::ProxyServer::SCHEME_DIRECT: |
| case net::ProxyServer::SCHEME_INVALID: |
| NOTREACHED(); |
| return out; |
| } |
| out->SetString(kProxyCfgRuleHost, proxy.host_port_pair().host()); |
| out->SetInteger(kProxyCfgRulePort, proxy.host_port_pair().port()); |
| return out; |
| } |