Home | History | Annotate | Download | only in extensions
      1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 // Implementation of helper functions for the Chrome Extensions Proxy Settings
      6 // API.
      7 //
      8 // Throughout this code, we report errors to the user by setting an |error|
      9 // parameter, if and only if these errors can be cause by invalid input
     10 // from the extension and we cannot expect that the extensions API has
     11 // caught this error before. In all other cases we are dealing with internal
     12 // errors and log to LOG(ERROR).
     13 
     14 #include "chrome/browser/extensions/extension_proxy_api_helpers.h"
     15 
     16 #include "base/base64.h"
     17 #include "base/basictypes.h"
     18 #include "base/string_tokenizer.h"
     19 #include "base/string_util.h"
     20 #include "base/utf_string_conversions.h"
     21 #include "base/values.h"
     22 #include "chrome/browser/extensions/extension_proxy_api_constants.h"
     23 #include "chrome/browser/prefs/proxy_config_dictionary.h"
     24 #include "chrome/common/extensions/extension_error_utils.h"
     25 #include "net/proxy/proxy_config.h"
     26 
     27 namespace keys = extension_proxy_api_constants;
     28 
     29 namespace extension_proxy_api_helpers {
     30 
     31 bool CreateDataURLFromPACScript(const std::string& pac_script,
     32                                 std::string* pac_script_url_base64_encoded) {
     33   // Encode pac_script in base64.
     34   std::string pac_script_base64_encoded;
     35   if (!base::Base64Encode(pac_script, &pac_script_base64_encoded))
     36     return false;
     37 
     38   // Make it a correct data url.
     39   *pac_script_url_base64_encoded =
     40       std::string(keys::kPACDataUrlPrefix) + pac_script_base64_encoded;
     41   return true;
     42 }
     43 
     44 bool CreatePACScriptFromDataURL(
     45     const std::string& pac_script_url_base64_encoded,
     46     std::string* pac_script) {
     47   if (pac_script_url_base64_encoded.find(keys::kPACDataUrlPrefix) != 0)
     48     return false;
     49 
     50   // Strip constant data-url prefix.
     51   std::string pac_script_base64_encoded =
     52       pac_script_url_base64_encoded.substr(strlen(keys::kPACDataUrlPrefix));
     53 
     54   // The rest is a base64 encoded PAC script.
     55   return base::Base64Decode(pac_script_base64_encoded, pac_script);
     56 }
     57 
     58 // Extension Pref -> Browser Pref conversion.
     59 
     60 bool GetProxyModeFromExtensionPref(const DictionaryValue* proxy_config,
     61                                    ProxyPrefs::ProxyMode* out,
     62                                    std::string* error) {
     63   std::string proxy_mode;
     64 
     65   // We can safely assume that this is ASCII due to the allowed enumeration
     66   // values specified in extension_api.json.
     67   proxy_config->GetStringASCII(keys::kProxyConfigMode, &proxy_mode);
     68   if (!ProxyPrefs::StringToProxyMode(proxy_mode, out)) {
     69     LOG(ERROR) << "Invalid mode for proxy settings: " << proxy_mode;
     70     return false;
     71   }
     72   return true;
     73 }
     74 
     75 bool GetPacUrlFromExtensionPref(const DictionaryValue* proxy_config,
     76                                 std::string* out,
     77                                 std::string* error) {
     78   DictionaryValue* pac_dict = NULL;
     79   proxy_config->GetDictionary(keys::kProxyConfigPacScript, &pac_dict);
     80   if (!pac_dict)
     81     return true;
     82 
     83   // TODO(battre): Handle UTF-8 URLs (http://crbug.com/72692).
     84   string16 pac_url16;
     85   if (pac_dict->HasKey(keys::kProxyConfigPacScriptUrl) &&
     86       !pac_dict->GetString(keys::kProxyConfigPacScriptUrl, &pac_url16)) {
     87     LOG(ERROR) << "'pacScript.url' could not be parsed.";
     88     return false;
     89   }
     90   if (!IsStringASCII(pac_url16)) {
     91     *error = "'pacScript.url' supports only ASCII URLs "
     92              "(encode URLs in Punycode format).";
     93     return false;
     94   }
     95   *out = UTF16ToASCII(pac_url16);
     96   return true;
     97 }
     98 
     99 bool GetPacDataFromExtensionPref(const DictionaryValue* proxy_config,
    100                                  std::string* out,
    101                                  std::string* error) {
    102   DictionaryValue* pac_dict = NULL;
    103   proxy_config->GetDictionary(keys::kProxyConfigPacScript, &pac_dict);
    104   if (!pac_dict)
    105     return true;
    106 
    107   string16 pac_data16;
    108   if (pac_dict->HasKey(keys::kProxyConfigPacScriptData) &&
    109       !pac_dict->GetString(keys::kProxyConfigPacScriptData, &pac_data16)) {
    110     LOG(ERROR) << "'pacScript.data' could not be parsed.";
    111     return false;
    112   }
    113   if (!IsStringASCII(pac_data16)) {
    114     *error = "'pacScript.data' supports only ASCII code"
    115              "(encode URLs in Punycode format).";
    116     return false;
    117   }
    118   *out = UTF16ToASCII(pac_data16);
    119   return true;
    120 }
    121 
    122 bool GetProxyServer(const DictionaryValue* proxy_server,
    123                     net::ProxyServer::Scheme default_scheme,
    124                     net::ProxyServer* out,
    125                     std::string* error) {
    126   std::string scheme_string;  // optional.
    127 
    128   // We can safely assume that this is ASCII due to the allowed enumeration
    129   // values specified in extension_api.json.
    130   proxy_server->GetStringASCII(keys::kProxyConfigRuleScheme, &scheme_string);
    131 
    132   net::ProxyServer::Scheme scheme =
    133       net::ProxyServer::GetSchemeFromURI(scheme_string);
    134   if (scheme == net::ProxyServer::SCHEME_INVALID)
    135     scheme = default_scheme;
    136 
    137   // TODO(battre): handle UTF-8 in hostnames (http://crbug.com/72692).
    138   string16 host16;
    139   if (!proxy_server->GetString(keys::kProxyConfigRuleHost, &host16)) {
    140     LOG(ERROR) << "Could not parse a 'rules.*.host' entry.";
    141     return false;
    142   }
    143   if (!IsStringASCII(host16)) {
    144     *error = ExtensionErrorUtils::FormatErrorMessage(
    145         "Invalid 'rules.???.host' entry '*'. 'host' field supports only ASCII "
    146         "URLs (encode URLs in Punycode format).",
    147         UTF16ToUTF8(host16));
    148     return false;
    149   }
    150   std::string host = UTF16ToASCII(host16);
    151 
    152   int port;  // optional.
    153   if (!proxy_server->GetInteger(keys::kProxyConfigRulePort, &port))
    154     port = net::ProxyServer::GetDefaultPortForScheme(scheme);
    155 
    156   *out = net::ProxyServer(scheme, net::HostPortPair(host, port));
    157 
    158   return true;
    159 }
    160 
    161 bool GetProxyRulesStringFromExtensionPref(const DictionaryValue* proxy_config,
    162                                           std::string* out,
    163                                           std::string* error) {
    164   DictionaryValue* proxy_rules = NULL;
    165   proxy_config->GetDictionary(keys::kProxyConfigRules, &proxy_rules);
    166   if (!proxy_rules)
    167     return true;
    168 
    169   // Local data into which the parameters will be parsed. has_proxy describes
    170   // whether a setting was found for the scheme; proxy_server holds the
    171   // respective ProxyServer objects containing those descriptions.
    172   bool has_proxy[keys::SCHEME_MAX + 1];
    173   net::ProxyServer proxy_server[keys::SCHEME_MAX + 1];
    174 
    175   // Looking for all possible proxy types is inefficient if we have a
    176   // singleProxy that will supersede per-URL proxies, but it's worth it to keep
    177   // the code simple and extensible.
    178   for (size_t i = 0; i <= keys::SCHEME_MAX; ++i) {
    179     DictionaryValue* proxy_dict = NULL;
    180     has_proxy[i] = proxy_rules->GetDictionary(keys::field_name[i],
    181                                               &proxy_dict);
    182     if (has_proxy[i]) {
    183       net::ProxyServer::Scheme default_scheme = net::ProxyServer::SCHEME_HTTP;
    184       if (!GetProxyServer(proxy_dict, default_scheme,
    185                           &proxy_server[i], error)) {
    186         // Don't set |error| here, as GetProxyServer takes care of that.
    187         return false;
    188       }
    189     }
    190   }
    191 
    192   COMPILE_ASSERT(keys::SCHEME_ALL == 0, singleProxy_must_be_first_option);
    193 
    194   // Handle case that only singleProxy is specified.
    195   if (has_proxy[keys::SCHEME_ALL]) {
    196     for (size_t i = 1; i <= keys::SCHEME_MAX; ++i) {
    197       if (has_proxy[i]) {
    198         *error = ExtensionErrorUtils::FormatErrorMessage(
    199             "Proxy rule for * and * cannot be set at the same time.",
    200             keys::field_name[keys::SCHEME_ALL], keys::field_name[i]);
    201         return false;
    202       }
    203     }
    204     *out = proxy_server[keys::SCHEME_ALL].ToURI();
    205     return true;
    206   }
    207 
    208   // Handle case that anything but singleProxy is specified.
    209 
    210   // Build the proxy preference string.
    211   std::string proxy_pref;
    212   for (size_t i = 1; i <= keys::SCHEME_MAX; ++i) {
    213     if (has_proxy[i]) {
    214       // http=foopy:4010;ftp=socks5://foopy2:80
    215       if (!proxy_pref.empty())
    216         proxy_pref.append(";");
    217       proxy_pref.append(keys::scheme_name[i]);
    218       proxy_pref.append("=");
    219       proxy_pref.append(proxy_server[i].ToURI());
    220     }
    221   }
    222 
    223   *out = proxy_pref;
    224   return true;
    225 }
    226 
    227 bool JoinUrlList(ListValue* list,
    228                  const std::string& joiner,
    229                  std::string* out,
    230                  std::string* error) {
    231   std::string result;
    232   for (size_t i = 0; i < list->GetSize(); ++i) {
    233     if (!result.empty())
    234       result.append(joiner);
    235 
    236     // TODO(battre): handle UTF-8 (http://crbug.com/72692).
    237     string16 entry;
    238     if (!list->GetString(i, &entry)) {
    239       LOG(ERROR) << "'rules.bypassList' could not be parsed.";
    240       return false;
    241     }
    242     if (!IsStringASCII(entry)) {
    243       *error = "'rules.bypassList' supports only ASCII URLs "
    244                "(encode URLs in Punycode format).";
    245       return false;
    246     }
    247     result.append(UTF16ToASCII(entry));
    248   }
    249   *out = result;
    250   return true;
    251 }
    252 
    253 bool GetBypassListFromExtensionPref(const DictionaryValue* proxy_config,
    254                                     std::string *out,
    255                                     std::string* error) {
    256   DictionaryValue* proxy_rules = NULL;
    257   proxy_config->GetDictionary(keys::kProxyConfigRules, &proxy_rules);
    258   if (!proxy_rules)
    259     return true;
    260 
    261   if (!proxy_rules->HasKey(keys::kProxyConfigBypassList)) {
    262     *out = "";
    263     return true;
    264   }
    265   ListValue* bypass_list = NULL;
    266   if (!proxy_rules->GetList(keys::kProxyConfigBypassList, &bypass_list)) {
    267     LOG(ERROR) << "'rules.bypassList' not be parsed.";
    268     return false;
    269   }
    270 
    271   return JoinUrlList(bypass_list, ",", out, error);
    272 }
    273 
    274 DictionaryValue* CreateProxyConfigDict(ProxyPrefs::ProxyMode mode_enum,
    275                                        const std::string& pac_url,
    276                                        const std::string& pac_data,
    277                                        const std::string& proxy_rules_string,
    278                                        const std::string& bypass_list,
    279                                        std::string* error) {
    280   DictionaryValue* result_proxy_config = NULL;
    281   switch (mode_enum) {
    282     case ProxyPrefs::MODE_DIRECT:
    283       result_proxy_config = ProxyConfigDictionary::CreateDirect();
    284       break;
    285     case ProxyPrefs::MODE_AUTO_DETECT:
    286       result_proxy_config = ProxyConfigDictionary::CreateAutoDetect();
    287       break;
    288     case ProxyPrefs::MODE_PAC_SCRIPT: {
    289       std::string url;
    290       if (!pac_url.empty()) {
    291         url = pac_url;
    292       } else if (!pac_data.empty()) {
    293         if (!CreateDataURLFromPACScript(pac_data, &url)) {
    294           *error = "Internal error, at base64 encoding of 'pacScript.data'.";
    295           return NULL;
    296         }
    297       } else {
    298         *error = "Proxy mode 'pac_script' requires a 'pacScript' field with "
    299                  "either a 'url' field or a 'data' field.";
    300         return NULL;
    301       }
    302       result_proxy_config = ProxyConfigDictionary::CreatePacScript(url);
    303       break;
    304     }
    305     case ProxyPrefs::MODE_FIXED_SERVERS: {
    306       if (proxy_rules_string.empty()) {
    307         *error = "Proxy mode 'fixed_servers' requires a 'rules' field.";
    308         return NULL;
    309       }
    310       result_proxy_config = ProxyConfigDictionary::CreateFixedServers(
    311           proxy_rules_string, bypass_list);
    312       break;
    313     }
    314     case ProxyPrefs::MODE_SYSTEM:
    315       result_proxy_config = ProxyConfigDictionary::CreateSystem();
    316       break;
    317     case ProxyPrefs::kModeCount:
    318       NOTREACHED();
    319   }
    320   return result_proxy_config;
    321 }
    322 
    323 DictionaryValue* CreateProxyRulesDict(
    324     const ProxyConfigDictionary& proxy_config) {
    325   ProxyPrefs::ProxyMode mode;
    326   CHECK(proxy_config.GetMode(&mode) && mode == ProxyPrefs::MODE_FIXED_SERVERS);
    327 
    328   scoped_ptr<DictionaryValue> extension_proxy_rules(new DictionaryValue);
    329 
    330   std::string proxy_servers;
    331   if (!proxy_config.GetProxyServer(&proxy_servers)) {
    332     LOG(ERROR) << "Missing proxy servers in configuration.";
    333     return NULL;
    334   }
    335 
    336   net::ProxyConfig::ProxyRules rules;
    337   rules.ParseFromString(proxy_servers);
    338 
    339   switch (rules.type) {
    340     case net::ProxyConfig::ProxyRules::TYPE_NO_RULES:
    341       return NULL;
    342     case net::ProxyConfig::ProxyRules::TYPE_SINGLE_PROXY:
    343       if (rules.single_proxy.is_valid()) {
    344         extension_proxy_rules->Set(keys::field_name[keys::SCHEME_ALL],
    345                                    CreateProxyServerDict(rules.single_proxy));
    346       }
    347       break;
    348     case net::ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME:
    349       if (rules.proxy_for_http.is_valid()) {
    350         extension_proxy_rules->Set(keys::field_name[keys::SCHEME_HTTP],
    351                                    CreateProxyServerDict(rules.proxy_for_http));
    352       }
    353       if (rules.proxy_for_https.is_valid()) {
    354         extension_proxy_rules->Set(
    355             keys::field_name[keys::SCHEME_HTTPS],
    356             CreateProxyServerDict(rules.proxy_for_https));
    357       }
    358       if (rules.proxy_for_ftp.is_valid()) {
    359         extension_proxy_rules->Set(keys::field_name[keys::SCHEME_FTP],
    360                                    CreateProxyServerDict(rules.proxy_for_ftp));
    361       }
    362       if (rules.fallback_proxy.is_valid()) {
    363         extension_proxy_rules->Set(keys::field_name[keys::SCHEME_FALLBACK],
    364                                    CreateProxyServerDict(rules.fallback_proxy));
    365       }
    366       break;
    367   }
    368 
    369   // If we add a new scheme some time, we need to also store a new dictionary
    370   // representing this scheme in the code above.
    371   COMPILE_ASSERT(keys::SCHEME_MAX == 4, SCHEME_FORGOTTEN);
    372 
    373   if (proxy_config.HasBypassList()) {
    374     std::string bypass_list_string;
    375     if (!proxy_config.GetBypassList(&bypass_list_string)) {
    376       LOG(ERROR) << "Invalid bypassList in configuration.";
    377       return NULL;
    378     }
    379     ListValue* bypass_list = TokenizeToStringList(bypass_list_string, ",;");
    380     extension_proxy_rules->Set(keys::kProxyConfigBypassList, bypass_list);
    381   }
    382 
    383   return extension_proxy_rules.release();
    384 }
    385 
    386 DictionaryValue* CreateProxyServerDict(const net::ProxyServer& proxy) {
    387   scoped_ptr<DictionaryValue> out(new DictionaryValue);
    388   switch (proxy.scheme()) {
    389     case net::ProxyServer::SCHEME_HTTP:
    390       out->SetString(keys::kProxyConfigRuleScheme, "http");
    391       break;
    392     case net::ProxyServer::SCHEME_HTTPS:
    393       out->SetString(keys::kProxyConfigRuleScheme, "https");
    394       break;
    395     case net::ProxyServer::SCHEME_SOCKS4:
    396       out->SetString(keys::kProxyConfigRuleScheme, "socks4");
    397       break;
    398     case net::ProxyServer::SCHEME_SOCKS5:
    399       out->SetString(keys::kProxyConfigRuleScheme, "socks5");
    400       break;
    401     case net::ProxyServer::SCHEME_DIRECT:
    402     case net::ProxyServer::SCHEME_INVALID:
    403       NOTREACHED();
    404       return NULL;
    405   }
    406   out->SetString(keys::kProxyConfigRuleHost, proxy.host_port_pair().host());
    407   out->SetInteger(keys::kProxyConfigRulePort, proxy.host_port_pair().port());
    408   return out.release();
    409 }
    410 
    411 DictionaryValue* CreatePacScriptDict(
    412     const ProxyConfigDictionary& proxy_config) {
    413   ProxyPrefs::ProxyMode mode;
    414   CHECK(proxy_config.GetMode(&mode) && mode == ProxyPrefs::MODE_PAC_SCRIPT);
    415 
    416   scoped_ptr<DictionaryValue> pac_script_dict(new DictionaryValue);
    417   std::string pac_url;
    418   if (!proxy_config.GetPacUrl(&pac_url)) {
    419     LOG(ERROR) << "Invalid proxy configuration. Missing PAC URL.";
    420     return NULL;
    421   }
    422 
    423   if (pac_url.find("data") == 0) {
    424     std::string pac_data;
    425     if (!CreatePACScriptFromDataURL(pac_url, &pac_data)) {
    426       LOG(ERROR) << "Cannot decode base64-encoded PAC data URL.";
    427       return NULL;
    428     }
    429     pac_script_dict->SetString(keys::kProxyConfigPacScriptData, pac_data);
    430   } else {
    431     pac_script_dict->SetString(keys::kProxyConfigPacScriptUrl, pac_url);
    432   }
    433   return pac_script_dict.release();
    434 }
    435 
    436 ListValue* TokenizeToStringList(const std::string& in,
    437                                 const std::string& delims) {
    438   ListValue* out = new ListValue;
    439   StringTokenizer entries(in, delims);
    440   while (entries.GetNext())
    441     out->Append(Value::CreateStringValue(entries.token()));
    442   return out;
    443 }
    444 
    445 }  // namespace extension_proxy_api_helpers
    446