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.
5 // Implementation of helper functions for the Chrome Extensions Proxy Settings
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).
14 #include "chrome/browser/extensions/api/proxy/proxy_api_helpers.h"
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 "components/proxy_config/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"
28 namespace extensions
{
30 namespace keys
= proxy_api_constants
;
32 namespace proxy_api_helpers
{
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
);
40 // Make it a correct data url.
41 *pac_script_url_base64_encoded
=
42 std::string(keys::kPACDataUrlPrefix
) + pac_script_base64_encoded
;
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
);
53 std::string mime_type
;
55 return net::DataURL::Parse(url
, &mime_type
, &charset
, pac_script
);
58 // Extension Pref -> Browser Pref conversion.
60 bool GetProxyModeFromExtensionPref(const base::DictionaryValue
* proxy_config
,
61 ProxyPrefs::ProxyMode
* out
,
64 std::string proxy_mode
;
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
;
77 bool GetPacMandatoryFromExtensionPref(const base::DictionaryValue
* proxy_config
,
81 const base::DictionaryValue
* pac_dict
= NULL
;
82 proxy_config
->GetDictionary(keys::kProxyConfigPacScript
, &pac_dict
);
86 bool mandatory_pac
= false;
87 if (pac_dict
->HasKey(keys::kProxyConfigPacScriptMandatory
) &&
88 !pac_dict
->GetBoolean(keys::kProxyConfigPacScriptMandatory
,
90 LOG(ERROR
) << "'pacScript.mandatory' could not be parsed.";
98 bool GetPacUrlFromExtensionPref(const base::DictionaryValue
* proxy_config
,
102 const base::DictionaryValue
* pac_dict
= NULL
;
103 proxy_config
->GetDictionary(keys::kProxyConfigPacScript
, &pac_dict
);
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.";
115 if (!base::IsStringASCII(pac_url16
)) {
116 *error
= "'pacScript.url' supports only ASCII URLs "
117 "(encode URLs in Punycode format).";
120 *out
= base::UTF16ToASCII(pac_url16
);
124 bool GetPacDataFromExtensionPref(const base::DictionaryValue
* proxy_config
,
128 const base::DictionaryValue
* pac_dict
= NULL
;
129 proxy_config
->GetDictionary(keys::kProxyConfigPacScript
, &pac_dict
);
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.";
140 if (!base::IsStringASCII(pac_data16
)) {
141 *error
= "'pacScript.data' supports only ASCII code"
142 "(encode URLs in Punycode format).";
145 *out
= base::UTF16ToASCII(pac_data16
);
149 bool GetProxyServer(const base::DictionaryValue
* proxy_server
,
150 net::ProxyServer::Scheme default_scheme
,
151 net::ProxyServer
* out
,
154 std::string scheme_string
; // optional.
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
);
160 net::ProxyServer::Scheme scheme
=
161 net::ProxyServer::GetSchemeFromURI(scheme_string
);
162 if (scheme
== net::ProxyServer::SCHEME_INVALID
)
163 scheme
= default_scheme
;
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.";
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
));
179 std::string host
= base::UTF16ToASCII(host16
);
181 int port
; // optional.
182 if (!proxy_server
->GetInteger(keys::kProxyConfigRulePort
, &port
))
183 port
= net::ProxyServer::GetDefaultPortForScheme(scheme
);
185 *out
= net::ProxyServer(scheme
, net::HostPortPair(host
, port
));
190 bool GetProxyRulesStringFromExtensionPref(
191 const base::DictionaryValue
* proxy_config
,
195 const base::DictionaryValue
* proxy_rules
= NULL
;
196 proxy_config
->GetDictionary(keys::kProxyConfigRules
, &proxy_rules
);
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];
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
],
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.
223 static_assert(keys::SCHEME_ALL
== 0, "SCHEME_ALL must be the first value");
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
) {
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
]);
235 *out
= proxy_server
[keys::SCHEME_ALL
].ToURI();
239 // Handle case that anything but singleProxy is specified.
241 // Build the proxy preference string.
242 std::string proxy_pref
;
243 for (size_t i
= 1; i
<= keys::SCHEME_MAX
; ++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());
258 bool JoinUrlList(const base::ListValue
* list
,
259 const std::string
& joiner
,
264 for (size_t i
= 0; i
< list
->GetSize(); ++i
) {
266 result
.append(joiner
);
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.";
275 if (!base::IsStringASCII(entry
)) {
276 *error
= "'rules.bypassList' supports only ASCII URLs "
277 "(encode URLs in Punycode format).";
280 result
.append(base::UTF16ToASCII(entry
));
286 bool GetBypassListFromExtensionPref(const base::DictionaryValue
* proxy_config
,
290 const base::DictionaryValue
* proxy_rules
= NULL
;
291 proxy_config
->GetDictionary(keys::kProxyConfigRules
, &proxy_rules
);
295 if (!proxy_rules
->HasKey(keys::kProxyConfigBypassList
)) {
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.";
306 return JoinUrlList(bypass_list
, ",", out
, error
, bad_message
);
309 base::DictionaryValue
* CreateProxyConfigDict(
310 ProxyPrefs::ProxyMode mode_enum
,
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
;
319 case ProxyPrefs::MODE_DIRECT
:
320 result_proxy_config
= ProxyConfigDictionary::CreateDirect();
322 case ProxyPrefs::MODE_AUTO_DETECT
:
323 result_proxy_config
= ProxyConfigDictionary::CreateAutoDetect();
325 case ProxyPrefs::MODE_PAC_SCRIPT
: {
327 if (!pac_url
.empty()) {
329 } else if (!pac_data
.empty()) {
330 if (!CreateDataURLFromPACScript(pac_data
, &url
)) {
331 *error
= "Internal error, at base64 encoding of 'pacScript.data'.";
335 *error
= "Proxy mode 'pac_script' requires a 'pacScript' field with "
336 "either a 'url' field or a 'data' field.";
339 result_proxy_config
=
340 ProxyConfigDictionary::CreatePacScript(url
, pac_mandatory
);
343 case ProxyPrefs::MODE_FIXED_SERVERS
: {
344 if (proxy_rules_string
.empty()) {
345 *error
= "Proxy mode 'fixed_servers' requires a 'rules' field.";
348 result_proxy_config
= ProxyConfigDictionary::CreateFixedServers(
349 proxy_rules_string
, bypass_list
);
352 case ProxyPrefs::MODE_SYSTEM
:
353 result_proxy_config
= ProxyConfigDictionary::CreateSystem();
355 case ProxyPrefs::kModeCount
:
358 return result_proxy_config
;
361 base::DictionaryValue
* CreateProxyRulesDict(
362 const ProxyConfigDictionary
& proxy_config
) {
363 ProxyPrefs::ProxyMode mode
;
364 CHECK(proxy_config
.GetMode(&mode
) && mode
== ProxyPrefs::MODE_FIXED_SERVERS
);
366 scoped_ptr
<base::DictionaryValue
> extension_proxy_rules(
367 new base::DictionaryValue
);
369 std::string proxy_servers
;
370 if (!proxy_config
.GetProxyServer(&proxy_servers
)) {
371 LOG(ERROR
) << "Missing proxy servers in configuration.";
375 net::ProxyConfig::ProxyRules rules
;
376 rules
.ParseFromString(proxy_servers
);
378 switch (rules
.type
) {
379 case net::ProxyConfig::ProxyRules::TYPE_NO_RULES
:
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()));
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()));
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()));
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()));
404 if (!rules
.fallback_proxies
.IsEmpty()) {
405 extension_proxy_rules
->Set(
406 keys::field_name
[keys::SCHEME_FALLBACK
],
407 CreateProxyServerDict(rules
.fallback_proxies
.Get()));
412 // If we add a new scheme sometime, we need to also store a new dictionary
413 // representing this scheme in the code above.
414 static_assert(keys::SCHEME_MAX
== 4,
415 "rules need to be updated along with schemes");
417 if (proxy_config
.HasBypassList()) {
418 std::string bypass_list_string
;
419 if (!proxy_config
.GetBypassList(&bypass_list_string
)) {
420 LOG(ERROR
) << "Invalid bypassList in configuration.";
423 base::ListValue
* bypass_list
=
424 TokenizeToStringList(bypass_list_string
, ",;");
425 extension_proxy_rules
->Set(keys::kProxyConfigBypassList
, bypass_list
);
428 return extension_proxy_rules
.release();
431 base::DictionaryValue
* CreateProxyServerDict(const net::ProxyServer
& proxy
) {
432 scoped_ptr
<base::DictionaryValue
> out(new base::DictionaryValue
);
433 switch (proxy
.scheme()) {
434 case net::ProxyServer::SCHEME_HTTP
:
435 out
->SetString(keys::kProxyConfigRuleScheme
, "http");
437 case net::ProxyServer::SCHEME_HTTPS
:
438 out
->SetString(keys::kProxyConfigRuleScheme
, "https");
440 case net::ProxyServer::SCHEME_QUIC
:
441 out
->SetString(keys::kProxyConfigRuleScheme
, "quic");
443 case net::ProxyServer::SCHEME_SOCKS4
:
444 out
->SetString(keys::kProxyConfigRuleScheme
, "socks4");
446 case net::ProxyServer::SCHEME_SOCKS5
:
447 out
->SetString(keys::kProxyConfigRuleScheme
, "socks5");
449 case net::ProxyServer::SCHEME_DIRECT
:
450 case net::ProxyServer::SCHEME_INVALID
:
454 out
->SetString(keys::kProxyConfigRuleHost
, proxy
.host_port_pair().host());
455 out
->SetInteger(keys::kProxyConfigRulePort
, proxy
.host_port_pair().port());
456 return out
.release();
459 base::DictionaryValue
* CreatePacScriptDict(
460 const ProxyConfigDictionary
& proxy_config
) {
461 ProxyPrefs::ProxyMode mode
;
462 CHECK(proxy_config
.GetMode(&mode
) && mode
== ProxyPrefs::MODE_PAC_SCRIPT
);
464 scoped_ptr
<base::DictionaryValue
> pac_script_dict(new base::DictionaryValue
);
466 if (!proxy_config
.GetPacUrl(&pac_url
)) {
467 LOG(ERROR
) << "Invalid proxy configuration. Missing PAC URL.";
470 bool pac_mandatory
= false;
471 if (!proxy_config
.GetPacMandatory(&pac_mandatory
)) {
472 LOG(ERROR
) << "Invalid proxy configuration. Missing PAC mandatory field.";
476 if (pac_url
.find("data") == 0) {
477 std::string pac_data
;
478 if (!CreatePACScriptFromDataURL(pac_url
, &pac_data
)) {
479 LOG(ERROR
) << "Cannot decode base64-encoded PAC data URL: " << pac_url
;
482 pac_script_dict
->SetString(keys::kProxyConfigPacScriptData
, pac_data
);
484 pac_script_dict
->SetString(keys::kProxyConfigPacScriptUrl
, pac_url
);
486 pac_script_dict
->SetBoolean(keys::kProxyConfigPacScriptMandatory
,
488 return pac_script_dict
.release();
491 base::ListValue
* TokenizeToStringList(const std::string
& in
,
492 const std::string
& delims
) {
493 base::ListValue
* out
= new base::ListValue
;
494 base::StringTokenizer
entries(in
, delims
);
495 while (entries
.GetNext())
496 out
->Append(new base::StringValue(entries
.token()));
500 } // namespace proxy_api_helpers
501 } // namespace extensions