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