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 27namespace keys = extension_proxy_api_constants; 28 29namespace extension_proxy_api_helpers { 30 31bool 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 44bool 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 60bool 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 75bool 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 99bool 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 122bool 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 161bool 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 227bool 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 253bool 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 274DictionaryValue* 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 323DictionaryValue* 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 386DictionaryValue* 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 411DictionaryValue* 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 436ListValue* 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