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
CreateDataURLFromPACScript(const std::string & pac_script,std::string * pac_script_url_base64_encoded)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
CreatePACScriptFromDataURL(const std::string & pac_script_url_base64_encoded,std::string * pac_script)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
GetProxyModeFromExtensionPref(const base::DictionaryValue * proxy_config,ProxyPrefs::ProxyMode * out,std::string * error,bool * bad_message)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
GetPacMandatoryFromExtensionPref(const base::DictionaryValue * proxy_config,bool * out,std::string * error,bool * bad_message)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
GetPacUrlFromExtensionPref(const base::DictionaryValue * proxy_config,std::string * out,std::string * error,bool * bad_message)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
GetPacDataFromExtensionPref(const base::DictionaryValue * proxy_config,std::string * out,std::string * error,bool * bad_message)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
GetProxyServer(const base::DictionaryValue * proxy_server,net::ProxyServer::Scheme default_scheme,net::ProxyServer * out,std::string * error,bool * bad_message)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
GetProxyRulesStringFromExtensionPref(const base::DictionaryValue * proxy_config,std::string * out,std::string * error,bool * bad_message)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
JoinUrlList(const base::ListValue * list,const std::string & joiner,std::string * out,std::string * error,bool * bad_message)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
GetBypassListFromExtensionPref(const base::DictionaryValue * proxy_config,std::string * out,std::string * error,bool * bad_message)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
CreateProxyConfigDict(ProxyPrefs::ProxyMode mode_enum,bool pac_mandatory,const std::string & pac_url,const std::string & pac_data,const std::string & proxy_rules_string,const std::string & bypass_list,std::string * error)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
CreateProxyRulesDict(const ProxyConfigDictionary & 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);
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
CreateProxyServerDict(const net::ProxyServer & proxy)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
CreatePacScriptDict(const ProxyConfigDictionary & proxy_config)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
TokenizeToStringList(const std::string & in,const std::string & delims)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