| // Copyright 2012 The Chromium Authors |
| // 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/api/proxy/proxy_api_helpers.h" |
| |
| #include <stddef.h> |
| |
| #include <array> |
| #include <iterator> |
| #include <utility> |
| |
| #include "base/base64.h" |
| #include "base/notreached.h" |
| #include "base/strings/string_tokenizer.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/values.h" |
| #include "chrome/browser/extensions/api/proxy/proxy_api_constants.h" |
| #include "components/proxy_config/proxy_config_dictionary.h" |
| #include "extensions/common/error_utils.h" |
| #include "net/base/data_url.h" |
| #include "net/base/proxy_server.h" |
| #include "net/base/proxy_string_util.h" |
| #include "net/proxy_resolution/proxy_config.h" |
| |
| namespace extensions { |
| |
| namespace proxy_api_helpers { |
| |
| |
| std::string CreateDataURLFromPACScript(const std::string& pac_script) { |
| // Prefix that identifies PAC-script encoding urls. |
| static constexpr char kPACDataUrlPrefix[] = |
| "data:application/x-ns-proxy-autoconfig;base64,"; |
| |
| // Encode pac_script in base64. |
| std::string pac_script_base64_encoded = base::Base64Encode(pac_script); |
| |
| // Make it a correct data url. |
| return kPACDataUrlPrefix + pac_script_base64_encoded; |
| } |
| |
| bool CreatePACScriptFromDataURL( |
| const std::string& pac_script_url_base64_encoded, |
| std::string* pac_script) { |
| GURL url(pac_script_url_base64_encoded); |
| if (!url.is_valid()) |
| return false; |
| |
| std::string mime_type; |
| std::string charset; |
| return net::DataURL::Parse(url, &mime_type, &charset, pac_script); |
| } |
| |
| // Extension Pref -> Browser Pref conversion. |
| |
| bool GetProxyModeFromExtensionPref(const base::Value::Dict& proxy_config, |
| ProxyPrefs::ProxyMode* out, |
| std::string* error, |
| bool* bad_message) { |
| std::string proxy_mode; |
| |
| // We can safely assume that this is ASCII due to the allowed enumeration |
| // values specified in the extension API JSON. |
| if (const std::string* proxy_mode_ptr = |
| proxy_config.FindString(proxy_api_constants::kProxyConfigMode)) { |
| proxy_mode = *proxy_mode_ptr; |
| DCHECK(base::IsStringASCII(proxy_mode)); |
| } |
| if (!ProxyPrefs::StringToProxyMode(proxy_mode, out)) { |
| LOG(ERROR) << "Invalid mode for proxy settings: " << proxy_mode; |
| *bad_message = true; |
| return false; |
| } |
| return true; |
| } |
| |
| bool GetPacMandatoryFromExtensionPref(const base::Value::Dict& proxy_config, |
| bool* out, |
| std::string* error, |
| bool* bad_message) { |
| const base::Value::Dict* pac_dict = |
| proxy_config.FindDict(proxy_api_constants::kProxyConfigPacScript); |
| if (!pac_dict) { |
| *out = false; |
| return true; |
| } |
| |
| const base::Value* mandatory_pac = |
| pac_dict->Find(proxy_api_constants::kProxyConfigPacScriptMandatory); |
| if (mandatory_pac) { |
| if (!mandatory_pac->is_bool()) { |
| LOG(ERROR) << "'pacScript.mandatory' could not be parsed."; |
| *bad_message = true; |
| return false; |
| } |
| } |
| *out = mandatory_pac ? mandatory_pac->GetBool() : false; |
| return true; |
| } |
| |
| bool GetPacUrlFromExtensionPref(const base::Value::Dict& proxy_config, |
| std::string* out, |
| std::string* error, |
| bool* bad_message) { |
| const base::Value::Dict* pac_dict = |
| proxy_config.FindDict(proxy_api_constants::kProxyConfigPacScript); |
| if (!pac_dict) |
| return true; |
| |
| // TODO(battre): Handle UTF-8 URLs (http://crbug.com/72692). |
| std::string pac_url; |
| const base::Value* pac_url_val = |
| pac_dict->Find(proxy_api_constants::kProxyConfigPacScriptUrl); |
| if (pac_url_val) { |
| if (!pac_url_val->is_string()) { |
| LOG(ERROR) << "'pacScript.url' could not be parsed."; |
| *bad_message = true; |
| return false; |
| } |
| pac_url = pac_url_val->GetString(); |
| } |
| if (!base::IsStringASCII(pac_url)) { |
| *error = "'pacScript.url' supports only ASCII URLs " |
| "(encode URLs in Punycode format)."; |
| return false; |
| } |
| *out = std::move(pac_url); |
| return true; |
| } |
| |
| bool GetPacDataFromExtensionPref(const base::Value::Dict& proxy_config, |
| std::string* out, |
| std::string* error, |
| bool* bad_message) { |
| const base::Value::Dict* pac_dict = |
| proxy_config.FindDict(proxy_api_constants::kProxyConfigPacScript); |
| if (!pac_dict) |
| return true; |
| |
| std::string pac_data; |
| const base::Value* pac_val = |
| pac_dict->Find(proxy_api_constants::kProxyConfigPacScriptData); |
| if (pac_val) { |
| if (!pac_val->is_string()) { |
| LOG(ERROR) << "'pacScript.data' could not be parsed."; |
| *bad_message = true; |
| return false; |
| } |
| pac_data = pac_val->GetString(); |
| } |
| |
| if (!base::IsStringASCII(pac_data)) { |
| *error = "'pacScript.data' supports only ASCII code" |
| "(encode URLs in Punycode format)."; |
| return false; |
| } |
| *out = std::move(pac_data); |
| return true; |
| } |
| |
| bool GetProxyServer(const base::Value::Dict& proxy_server, |
| net::ProxyServer::Scheme default_scheme, |
| net::ProxyServer* out, |
| std::string* error, |
| bool* bad_message) { |
| std::string scheme_string; // optional. |
| |
| // We can safely assume that this is ASCII due to the allowed enumeration |
| // values specified in the extension API JSON. |
| if (const std::string* scheme_string_ptr = proxy_server.FindString( |
| proxy_api_constants::kProxyConfigRuleScheme)) { |
| scheme_string = *scheme_string_ptr; |
| DCHECK(base::IsStringASCII(scheme_string)); |
| } |
| |
| net::ProxyServer::Scheme scheme = net::GetSchemeFromUriScheme(scheme_string); |
| if (scheme == net::ProxyServer::SCHEME_INVALID) |
| scheme = default_scheme; |
| |
| // TODO(battre): handle UTF-8 in hostnames (http://crbug.com/72692). |
| std::u16string host16; |
| if (const std::string* ptr = |
| proxy_server.FindString(proxy_api_constants::kProxyConfigRuleHost)) { |
| host16 = base::UTF8ToUTF16(*ptr); |
| } else { |
| LOG(ERROR) << "Could not parse a 'rules.*.host' entry."; |
| *bad_message = true; |
| return false; |
| } |
| if (!base::IsStringASCII(host16)) { |
| *error = ErrorUtils::FormatErrorMessage( |
| "Invalid 'rules.???.host' entry '*'. 'host' field supports only ASCII " |
| "URLs (encode URLs in Punycode format).", |
| base::UTF16ToUTF8(host16)); |
| return false; |
| } |
| std::string host = base::UTF16ToASCII(host16); |
| |
| if (host.empty()) { |
| *error = "Invalid 'rules.???.host' entry. Hostname cannot be empty."; |
| return false; |
| } |
| |
| // optional. |
| int port = proxy_server.FindInt(proxy_api_constants::kProxyConfigRulePort) |
| .value_or(net::ProxyServer::GetDefaultPortForScheme(scheme)); |
| |
| *out = net::ProxyServer(scheme, net::HostPortPair(host, port)); |
| |
| return true; |
| } |
| |
| bool GetProxyRulesStringFromExtensionPref(const base::Value::Dict& proxy_config, |
| std::string* out, |
| std::string* error, |
| bool* bad_message) { |
| const base::Value::Dict* proxy_rules = |
| proxy_config.FindDict(proxy_api_constants::kProxyConfigRules); |
| 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. |
| std::array<bool, SCHEME_MAX + 1> has_proxy; |
| std::array<net::ProxyServer, SCHEME_MAX + 1> proxy_server; |
| |
| // 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) { |
| const base::Value::Dict* proxy_dict = |
| proxy_rules->FindDictByDottedPath(kFieldNames[i]); |
| has_proxy[i] = proxy_dict != nullptr; |
| if (has_proxy[i]) { |
| net::ProxyServer::Scheme default_scheme = net::ProxyServer::SCHEME_HTTP; |
| if (!GetProxyServer(*proxy_dict, default_scheme, &proxy_server[i], error, |
| bad_message)) { |
| // 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 = ErrorUtils::FormatErrorMessage( |
| "Proxy rule for * and * cannot be set at the same time.", |
| kFieldNames[SCHEME_ALL], kFieldNames[i]); |
| return false; |
| } |
| } |
| *out = net::ProxyServerToProxyUri(proxy_server[SCHEME_ALL]); |
| 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(kSchemeNames[i]); |
| proxy_pref.append("="); |
| proxy_pref.append(net::ProxyServerToProxyUri(proxy_server[i])); |
| } |
| } |
| |
| *out = proxy_pref; |
| return true; |
| } |
| |
| bool JoinUrlList(const base::Value::List& list, |
| const std::string& joiner, |
| std::string* out, |
| std::string* error, |
| bool* bad_message) { |
| std::string result; |
| for (const auto& val : list) { |
| if (!result.empty()) |
| result.append(joiner); |
| |
| // TODO(battre): handle UTF-8 (http://crbug.com/72692). |
| const std::string* entry = val.GetIfString(); |
| if (!entry) { |
| LOG(ERROR) << "'rules.bypassList' could not be parsed."; |
| *bad_message = true; |
| return false; |
| } |
| if (!base::IsStringASCII(*entry)) { |
| *error = "'rules.bypassList' supports only ASCII URLs " |
| "(encode URLs in Punycode format)."; |
| return false; |
| } |
| result.append(*entry); |
| } |
| *out = result; |
| return true; |
| } |
| |
| bool GetBypassListFromExtensionPref(const base::Value::Dict& proxy_config, |
| std::string* out, |
| std::string* error, |
| bool* bad_message) { |
| const base::Value::Dict* proxy_rules = |
| proxy_config.FindDict(proxy_api_constants::kProxyConfigRules); |
| if (!proxy_rules) |
| return true; |
| |
| const base::Value* bypass_list = |
| proxy_rules->Find(proxy_api_constants::kProxyConfigBypassList); |
| if (!bypass_list) { |
| *out = ""; |
| return true; |
| } |
| |
| if (!bypass_list->is_list()) { |
| LOG(ERROR) << "'rules.bypassList' could not be parsed."; |
| *bad_message = true; |
| return false; |
| } |
| |
| return JoinUrlList(bypass_list->GetList(), ",", out, error, bad_message); |
| } |
| |
| std::optional<base::Value::Dict> CreateProxyConfigDict( |
| ProxyPrefs::ProxyMode mode_enum, |
| bool pac_mandatory, |
| const std::string& pac_url, |
| const std::string& pac_data, |
| const std::string& proxy_rules_string, |
| const std::string& bypass_list, |
| std::string* error) { |
| switch (mode_enum) { |
| case ProxyPrefs::MODE_DIRECT: |
| return ProxyConfigDictionary::CreateDirect(); |
| case ProxyPrefs::MODE_AUTO_DETECT: |
| return ProxyConfigDictionary::CreateAutoDetect(); |
| case ProxyPrefs::MODE_PAC_SCRIPT: { |
| std::string url; |
| if (!pac_url.empty()) { |
| url = pac_url; |
| } else if (!pac_data.empty()) { |
| url = CreateDataURLFromPACScript(pac_data); |
| } else { |
| *error = |
| "Proxy mode 'pac_script' requires a 'pacScript' field with " |
| "either a 'url' field or a 'data' field."; |
| return std::nullopt; |
| } |
| return ProxyConfigDictionary::CreatePacScript(url, pac_mandatory); |
| } |
| case ProxyPrefs::MODE_FIXED_SERVERS: { |
| if (proxy_rules_string.empty()) { |
| *error = "Proxy mode 'fixed_servers' requires a 'rules' field."; |
| return std::nullopt; |
| } |
| return ProxyConfigDictionary::CreateFixedServers(proxy_rules_string, |
| bypass_list); |
| } |
| case ProxyPrefs::MODE_SYSTEM: |
| return ProxyConfigDictionary::CreateSystem(); |
| case ProxyPrefs::kModeCount: |
| NOTREACHED(); |
| } |
| return std::nullopt; |
| } |
| |
| std::optional<base::Value::Dict> CreateProxyRulesDict( |
| const ProxyConfigDictionary& proxy_config) { |
| ProxyPrefs::ProxyMode mode; |
| CHECK(proxy_config.GetMode(&mode) && mode == ProxyPrefs::MODE_FIXED_SERVERS); |
| |
| std::string proxy_servers; |
| if (!proxy_config.GetProxyServer(&proxy_servers)) { |
| LOG(ERROR) << "Missing proxy servers in configuration."; |
| return std::nullopt; |
| } |
| |
| base::Value::Dict extension_proxy_rules; |
| |
| net::ProxyConfig::ProxyRules rules; |
| rules.ParseFromString(proxy_servers); |
| |
| switch (rules.type) { |
| case net::ProxyConfig::ProxyRules::Type::EMPTY: |
| return std::nullopt; |
| case net::ProxyConfig::ProxyRules::Type::PROXY_LIST: |
| if (!rules.single_proxies.IsEmpty()) { |
| extension_proxy_rules.Set( |
| kFieldNames[SCHEME_ALL], |
| CreateProxyServerDict(rules.single_proxies.First())); |
| } |
| break; |
| case net::ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME: |
| if (!rules.proxies_for_http.IsEmpty()) { |
| extension_proxy_rules.Set( |
| kFieldNames[SCHEME_HTTP], |
| CreateProxyServerDict(rules.proxies_for_http.First())); |
| } |
| if (!rules.proxies_for_https.IsEmpty()) { |
| extension_proxy_rules.Set( |
| kFieldNames[SCHEME_HTTPS], |
| CreateProxyServerDict(rules.proxies_for_https.First())); |
| } |
| if (!rules.proxies_for_ftp.IsEmpty()) { |
| extension_proxy_rules.Set( |
| kFieldNames[SCHEME_FTP], |
| CreateProxyServerDict(rules.proxies_for_ftp.First())); |
| } |
| if (!rules.fallback_proxies.IsEmpty()) { |
| extension_proxy_rules.Set( |
| kFieldNames[SCHEME_FALLBACK], |
| CreateProxyServerDict(rules.fallback_proxies.First())); |
| } |
| break; |
| } |
| |
| // If we add a new scheme sometime, we need to also store a new dictionary |
| // representing this scheme in the code above. |
| static_assert(SCHEME_MAX == 4, "rules need to be updated along with schemes"); |
| |
| if (proxy_config.HasBypassList()) { |
| std::string bypass_list_string; |
| if (!proxy_config.GetBypassList(&bypass_list_string)) { |
| LOG(ERROR) << "Invalid bypassList in configuration."; |
| return std::nullopt; |
| } |
| base::Value::List bypass_list = |
| TokenizeToStringList(bypass_list_string, ",;"); |
| extension_proxy_rules.Set(proxy_api_constants::kProxyConfigBypassList, |
| std::move(bypass_list)); |
| } |
| |
| return extension_proxy_rules; |
| } |
| |
| base::Value::Dict CreateProxyServerDict(const net::ProxyChain& proxy_chain) { |
| base::Value::Dict out; |
| const char* scheme = nullptr; |
| CHECK(proxy_chain.is_single_proxy()); |
| const net::ProxyServer& proxy = proxy_chain.First(); |
| switch (proxy.scheme()) { |
| case net::ProxyServer::SCHEME_HTTP: |
| scheme = "http"; |
| break; |
| case net::ProxyServer::SCHEME_HTTPS: |
| scheme = "https"; |
| break; |
| case net::ProxyServer::SCHEME_QUIC: |
| scheme = "quic"; |
| break; |
| case net::ProxyServer::SCHEME_SOCKS4: |
| scheme = "socks4"; |
| break; |
| case net::ProxyServer::SCHEME_SOCKS5: |
| scheme = "socks5"; |
| break; |
| case net::ProxyServer::SCHEME_INVALID: |
| NOTREACHED(); |
| } |
| out.Set(proxy_api_constants::kProxyConfigRuleScheme, scheme); |
| out.Set(proxy_api_constants::kProxyConfigRuleHost, |
| proxy.host_port_pair().host()); |
| out.Set(proxy_api_constants::kProxyConfigRulePort, |
| proxy.host_port_pair().port()); |
| return out; |
| } |
| |
| std::optional<base::Value::Dict> CreatePacScriptDict( |
| const ProxyConfigDictionary& proxy_config) { |
| ProxyPrefs::ProxyMode mode; |
| CHECK(proxy_config.GetMode(&mode) && mode == ProxyPrefs::MODE_PAC_SCRIPT); |
| |
| std::string pac_url; |
| if (!proxy_config.GetPacUrl(&pac_url)) { |
| LOG(ERROR) << "Invalid proxy configuration. Missing PAC URL."; |
| return std::nullopt; |
| } |
| bool pac_mandatory = false; |
| if (!proxy_config.GetPacMandatory(&pac_mandatory)) { |
| LOG(ERROR) << "Invalid proxy configuration. Missing PAC mandatory field."; |
| return std::nullopt; |
| } |
| |
| base::Value::Dict pac_script_dict; |
| if (base::StartsWith(pac_url, "data", base::CompareCase::SENSITIVE)) { |
| std::string pac_data; |
| if (!CreatePACScriptFromDataURL(pac_url, &pac_data)) { |
| LOG(ERROR) << "Cannot decode base64-encoded PAC data URL: " << pac_url; |
| return std::nullopt; |
| } |
| pac_script_dict.Set(proxy_api_constants::kProxyConfigPacScriptData, |
| pac_data); |
| } else { |
| pac_script_dict.Set(proxy_api_constants::kProxyConfigPacScriptUrl, pac_url); |
| } |
| pac_script_dict.Set(proxy_api_constants::kProxyConfigPacScriptMandatory, |
| pac_mandatory); |
| return std::make_optional(std::move(pac_script_dict)); |
| } |
| |
| base::Value::List TokenizeToStringList(const std::string& in, |
| const std::string& delims) { |
| base::Value::List out; |
| base::StringTokenizer entries(in, delims); |
| while (entries.GetNext()) |
| out.Append(entries.token_piece()); |
| return out; |
| } |
| |
| } // namespace proxy_api_helpers |
| } // namespace extensions |