| // 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. |
| |
| // Implementation of helper functions for the Chrome Extensions Proxy Settings |
| // API. |
| // |
| // Throughout this code, we report errors to the user by setting an |error| |
| // parameter, if and only if these errors can be cause by invalid input |
| // from the extension and we cannot expect that the extensions API has |
| // caught this error before. In all other cases we are dealing with internal |
| // errors and log to LOG(ERROR). |
| |
| #include "chrome/browser/extensions/extension_proxy_api_helpers.h" |
| |
| #include "base/base64.h" |
| #include "base/basictypes.h" |
| #include "base/string_tokenizer.h" |
| #include "base/string_util.h" |
| #include "base/utf_string_conversions.h" |
| #include "base/values.h" |
| #include "chrome/browser/extensions/extension_proxy_api_constants.h" |
| #include "chrome/browser/prefs/proxy_config_dictionary.h" |
| #include "chrome/common/extensions/extension_error_utils.h" |
| #include "net/proxy/proxy_config.h" |
| |
| namespace keys = extension_proxy_api_constants; |
| |
| namespace extension_proxy_api_helpers { |
| |
| bool CreateDataURLFromPACScript(const std::string& pac_script, |
| std::string* pac_script_url_base64_encoded) { |
| // Encode pac_script in base64. |
| std::string pac_script_base64_encoded; |
| if (!base::Base64Encode(pac_script, &pac_script_base64_encoded)) |
| return false; |
| |
| // Make it a correct data url. |
| *pac_script_url_base64_encoded = |
| std::string(keys::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(keys::kPACDataUrlPrefix) != 0) |
| return false; |
| |
| // Strip constant data-url prefix. |
| std::string pac_script_base64_encoded = |
| pac_script_url_base64_encoded.substr(strlen(keys::kPACDataUrlPrefix)); |
| |
| // The rest is a base64 encoded PAC script. |
| return base::Base64Decode(pac_script_base64_encoded, pac_script); |
| } |
| |
| // Extension Pref -> Browser Pref conversion. |
| |
| bool GetProxyModeFromExtensionPref(const DictionaryValue* proxy_config, |
| ProxyPrefs::ProxyMode* out, |
| std::string* error) { |
| 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(keys::kProxyConfigMode, &proxy_mode); |
| if (!ProxyPrefs::StringToProxyMode(proxy_mode, out)) { |
| LOG(ERROR) << "Invalid mode for proxy settings: " << proxy_mode; |
| return false; |
| } |
| return true; |
| } |
| |
| bool GetPacUrlFromExtensionPref(const DictionaryValue* proxy_config, |
| std::string* out, |
| std::string* error) { |
| DictionaryValue* pac_dict = NULL; |
| proxy_config->GetDictionary(keys::kProxyConfigPacScript, &pac_dict); |
| if (!pac_dict) |
| return true; |
| |
| // TODO(battre): Handle UTF-8 URLs (http://crbug.com/72692). |
| string16 pac_url16; |
| if (pac_dict->HasKey(keys::kProxyConfigPacScriptUrl) && |
| !pac_dict->GetString(keys::kProxyConfigPacScriptUrl, &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; |
| } |
| *out = UTF16ToASCII(pac_url16); |
| return true; |
| } |
| |
| bool GetPacDataFromExtensionPref(const DictionaryValue* proxy_config, |
| std::string* out, |
| std::string* error) { |
| DictionaryValue* pac_dict = NULL; |
| proxy_config->GetDictionary(keys::kProxyConfigPacScript, &pac_dict); |
| if (!pac_dict) |
| return true; |
| |
| string16 pac_data16; |
| if (pac_dict->HasKey(keys::kProxyConfigPacScriptData) && |
| !pac_dict->GetString(keys::kProxyConfigPacScriptData, &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; |
| } |
| *out = UTF16ToASCII(pac_data16); |
| return true; |
| } |
| |
| bool GetProxyServer(const DictionaryValue* proxy_server, |
| net::ProxyServer::Scheme default_scheme, |
| net::ProxyServer* out, |
| std::string* error) { |
| std::string scheme_string; // optional. |
| |
| // We can safely assume that this is ASCII due to the allowed enumeration |
| // values specified in extension_api.json. |
| proxy_server->GetStringASCII(keys::kProxyConfigRuleScheme, &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 (!proxy_server->GetString(keys::kProxyConfigRuleHost, &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 (!proxy_server->GetInteger(keys::kProxyConfigRulePort, &port)) |
| port = net::ProxyServer::GetDefaultPortForScheme(scheme); |
| |
| *out = net::ProxyServer(scheme, net::HostPortPair(host, port)); |
| |
| return true; |
| } |
| |
| bool GetProxyRulesStringFromExtensionPref(const DictionaryValue* proxy_config, |
| std::string* out, |
| std::string* error) { |
| DictionaryValue* proxy_rules = NULL; |
| proxy_config->GetDictionary(keys::kProxyConfigRules, &proxy_rules); |
| if (!proxy_rules) |
| return true; |
| |
| // Local data into which the parameters will be parsed. has_proxy describes |
| // whether a setting was found for the scheme; proxy_server holds the |
| // respective ProxyServer objects containing those descriptions. |
| bool has_proxy[keys::SCHEME_MAX + 1]; |
| net::ProxyServer proxy_server[keys::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 <= keys::SCHEME_MAX; ++i) { |
| DictionaryValue* proxy_dict = NULL; |
| has_proxy[i] = proxy_rules->GetDictionary(keys::field_name[i], |
| &proxy_dict); |
| if (has_proxy[i]) { |
| net::ProxyServer::Scheme default_scheme = net::ProxyServer::SCHEME_HTTP; |
| if (!GetProxyServer(proxy_dict, default_scheme, |
| &proxy_server[i], error)) { |
| // Don't set |error| here, as GetProxyServer takes care of that. |
| return false; |
| } |
| } |
| } |
| |
| COMPILE_ASSERT(keys::SCHEME_ALL == 0, singleProxy_must_be_first_option); |
| |
| // Handle case that only singleProxy is specified. |
| if (has_proxy[keys::SCHEME_ALL]) { |
| for (size_t i = 1; i <= keys::SCHEME_MAX; ++i) { |
| if (has_proxy[i]) { |
| *error = ExtensionErrorUtils::FormatErrorMessage( |
| "Proxy rule for * and * cannot be set at the same time.", |
| keys::field_name[keys::SCHEME_ALL], keys::field_name[i]); |
| return false; |
| } |
| } |
| *out = proxy_server[keys::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 <= keys::SCHEME_MAX; ++i) { |
| if (has_proxy[i]) { |
| // http=foopy:4010;ftp=socks5://foopy2:80 |
| if (!proxy_pref.empty()) |
| proxy_pref.append(";"); |
| proxy_pref.append(keys::scheme_name[i]); |
| proxy_pref.append("="); |
| proxy_pref.append(proxy_server[i].ToURI()); |
| } |
| } |
| |
| *out = proxy_pref; |
| return true; |
| } |
| |
| bool JoinUrlList(ListValue* list, |
| const std::string& joiner, |
| std::string* out, |
| std::string* error) { |
| 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 GetBypassListFromExtensionPref(const DictionaryValue* proxy_config, |
| std::string *out, |
| std::string* error) { |
| DictionaryValue* proxy_rules = NULL; |
| proxy_config->GetDictionary(keys::kProxyConfigRules, &proxy_rules); |
| if (!proxy_rules) |
| return true; |
| |
| if (!proxy_rules->HasKey(keys::kProxyConfigBypassList)) { |
| *out = ""; |
| return true; |
| } |
| ListValue* bypass_list = NULL; |
| if (!proxy_rules->GetList(keys::kProxyConfigBypassList, &bypass_list)) { |
| LOG(ERROR) << "'rules.bypassList' not be parsed."; |
| return false; |
| } |
| |
| return JoinUrlList(bypass_list, ",", out, error); |
| } |
| |
| DictionaryValue* CreateProxyConfigDict(ProxyPrefs::ProxyMode mode_enum, |
| const std::string& pac_url, |
| const std::string& pac_data, |
| const std::string& proxy_rules_string, |
| const std::string& bypass_list, |
| std::string* error) { |
| 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: { |
| 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 NULL; |
| } |
| } else { |
| *error = "Proxy mode 'pac_script' requires a 'pacScript' field with " |
| "either a 'url' field or a 'data' field."; |
| return NULL; |
| } |
| result_proxy_config = ProxyConfigDictionary::CreatePacScript(url); |
| break; |
| } |
| case ProxyPrefs::MODE_FIXED_SERVERS: { |
| if (proxy_rules_string.empty()) { |
| *error = "Proxy mode 'fixed_servers' requires a 'rules' field."; |
| return NULL; |
| } |
| 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(); |
| } |
| return result_proxy_config; |
| } |
| |
| DictionaryValue* CreateProxyRulesDict( |
| const ProxyConfigDictionary& proxy_config) { |
| ProxyPrefs::ProxyMode mode; |
| CHECK(proxy_config.GetMode(&mode) && mode == ProxyPrefs::MODE_FIXED_SERVERS); |
| |
| scoped_ptr<DictionaryValue> extension_proxy_rules(new DictionaryValue); |
| |
| std::string proxy_servers; |
| if (!proxy_config.GetProxyServer(&proxy_servers)) { |
| LOG(ERROR) << "Missing proxy servers in configuration."; |
| return NULL; |
| } |
| |
| net::ProxyConfig::ProxyRules rules; |
| rules.ParseFromString(proxy_servers); |
| |
| switch (rules.type) { |
| case net::ProxyConfig::ProxyRules::TYPE_NO_RULES: |
| return NULL; |
| case net::ProxyConfig::ProxyRules::TYPE_SINGLE_PROXY: |
| if (rules.single_proxy.is_valid()) { |
| extension_proxy_rules->Set(keys::field_name[keys::SCHEME_ALL], |
| CreateProxyServerDict(rules.single_proxy)); |
| } |
| break; |
| case net::ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME: |
| if (rules.proxy_for_http.is_valid()) { |
| extension_proxy_rules->Set(keys::field_name[keys::SCHEME_HTTP], |
| CreateProxyServerDict(rules.proxy_for_http)); |
| } |
| if (rules.proxy_for_https.is_valid()) { |
| extension_proxy_rules->Set( |
| keys::field_name[keys::SCHEME_HTTPS], |
| CreateProxyServerDict(rules.proxy_for_https)); |
| } |
| if (rules.proxy_for_ftp.is_valid()) { |
| extension_proxy_rules->Set(keys::field_name[keys::SCHEME_FTP], |
| CreateProxyServerDict(rules.proxy_for_ftp)); |
| } |
| if (rules.fallback_proxy.is_valid()) { |
| extension_proxy_rules->Set(keys::field_name[keys::SCHEME_FALLBACK], |
| CreateProxyServerDict(rules.fallback_proxy)); |
| } |
| break; |
| } |
| |
| // If we add a new scheme some time, we need to also store a new dictionary |
| // representing this scheme in the code above. |
| COMPILE_ASSERT(keys::SCHEME_MAX == 4, SCHEME_FORGOTTEN); |
| |
| if (proxy_config.HasBypassList()) { |
| std::string bypass_list_string; |
| if (!proxy_config.GetBypassList(&bypass_list_string)) { |
| LOG(ERROR) << "Invalid bypassList in configuration."; |
| return NULL; |
| } |
| ListValue* bypass_list = TokenizeToStringList(bypass_list_string, ",;"); |
| extension_proxy_rules->Set(keys::kProxyConfigBypassList, bypass_list); |
| } |
| |
| return extension_proxy_rules.release(); |
| } |
| |
| DictionaryValue* CreateProxyServerDict(const net::ProxyServer& proxy) { |
| scoped_ptr<DictionaryValue> out(new DictionaryValue); |
| switch (proxy.scheme()) { |
| case net::ProxyServer::SCHEME_HTTP: |
| out->SetString(keys::kProxyConfigRuleScheme, "http"); |
| break; |
| case net::ProxyServer::SCHEME_HTTPS: |
| out->SetString(keys::kProxyConfigRuleScheme, "https"); |
| break; |
| case net::ProxyServer::SCHEME_SOCKS4: |
| out->SetString(keys::kProxyConfigRuleScheme, "socks4"); |
| break; |
| case net::ProxyServer::SCHEME_SOCKS5: |
| out->SetString(keys::kProxyConfigRuleScheme, "socks5"); |
| break; |
| case net::ProxyServer::SCHEME_DIRECT: |
| case net::ProxyServer::SCHEME_INVALID: |
| NOTREACHED(); |
| return NULL; |
| } |
| out->SetString(keys::kProxyConfigRuleHost, proxy.host_port_pair().host()); |
| out->SetInteger(keys::kProxyConfigRulePort, proxy.host_port_pair().port()); |
| return out.release(); |
| } |
| |
| DictionaryValue* CreatePacScriptDict( |
| const ProxyConfigDictionary& proxy_config) { |
| ProxyPrefs::ProxyMode mode; |
| CHECK(proxy_config.GetMode(&mode) && mode == ProxyPrefs::MODE_PAC_SCRIPT); |
| |
| scoped_ptr<DictionaryValue> pac_script_dict(new DictionaryValue); |
| std::string pac_url; |
| if (!proxy_config.GetPacUrl(&pac_url)) { |
| LOG(ERROR) << "Invalid proxy configuration. Missing PAC URL."; |
| return NULL; |
| } |
| |
| if (pac_url.find("data") == 0) { |
| std::string pac_data; |
| if (!CreatePACScriptFromDataURL(pac_url, &pac_data)) { |
| LOG(ERROR) << "Cannot decode base64-encoded PAC data URL."; |
| return NULL; |
| } |
| pac_script_dict->SetString(keys::kProxyConfigPacScriptData, pac_data); |
| } else { |
| pac_script_dict->SetString(keys::kProxyConfigPacScriptUrl, pac_url); |
| } |
| return pac_script_dict.release(); |
| } |
| |
| ListValue* TokenizeToStringList(const std::string& in, |
| const std::string& delims) { |
| ListValue* out = new ListValue; |
| StringTokenizer entries(in, delims); |
| while (entries.GetNext()) |
| out->Append(Value::CreateStringValue(entries.token())); |
| return out; |
| } |
| |
| } // namespace extension_proxy_api_helpers |