// 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