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