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