proxy_api_helpers.cc revision a1401311d1ab56c4ed0a474bd38c108f75cb0cd9
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
28namespace extensions {
29
30namespace keys = proxy_api_constants;
31
32namespace proxy_api_helpers {
33
34bool 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
46bool 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
60bool 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
77bool 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
98bool 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 (!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
124bool 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 (!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
149bool 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 (!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
190bool 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
258bool 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 (!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
286bool 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
309base::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
361base::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
430base::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
458base::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
490base::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