proxy_config_service_impl.cc revision 513209b27ff55e2841eac0e4120199c23acce758
1// Copyright (c) 2010 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#include "chrome/browser/chromeos/proxy_config_service_impl.h"
6
7#include <ostream>
8
9#include "base/logging.h"
10#include "base/string_util.h"
11#include "base/task.h"
12#include "chrome/browser/browser_thread.h"
13#include "chrome/browser/chromeos/cros/cros_library.h"
14#include "chrome/common/json_value_serializer.h"
15
16namespace chromeos {
17
18namespace {
19
20const char* SourceToString(ProxyConfigServiceImpl::ProxyConfig::Source source) {
21  switch (source) {
22    case ProxyConfigServiceImpl::ProxyConfig::SOURCE_NONE:
23      return "SOURCE_NONE";
24    case ProxyConfigServiceImpl::ProxyConfig::SOURCE_POLICY:
25      return "SOURCE_POLICY";
26    case ProxyConfigServiceImpl::ProxyConfig::SOURCE_OWNER:
27      return "SOURCE_OWNER";
28  }
29  NOTREACHED() << "Unrecognized source type";
30  return "";
31}
32
33std::ostream& operator<<(std::ostream& out,
34    const ProxyConfigServiceImpl::ProxyConfig::ManualProxy& proxy) {
35  out << "  " << SourceToString(proxy.source) << "\n"
36      << "  server: " << (proxy.server.is_valid() ? proxy.server.ToURI() : "")
37      << "\n";
38  return out;
39}
40
41std::ostream& operator<<(std::ostream& out,
42    const ProxyConfigServiceImpl::ProxyConfig& config) {
43  switch (config.mode) {
44    case ProxyConfigServiceImpl::ProxyConfig::MODE_DIRECT:
45      out << "Direct connection:\n  "
46          << SourceToString(config.automatic_proxy.source) << "\n";
47      break;
48    case ProxyConfigServiceImpl::ProxyConfig::MODE_AUTO_DETECT:
49      out << "Auto detection:\n  "
50          << SourceToString(config.automatic_proxy.source) << "\n";
51      break;
52    case ProxyConfigServiceImpl::ProxyConfig::MODE_PAC_SCRIPT:
53      out << "Custom PAC script:\n  "
54          << SourceToString(config.automatic_proxy.source)
55          << "\n  PAC: " << config.automatic_proxy.pac_url << "\n";
56      break;
57    case ProxyConfigServiceImpl::ProxyConfig::MODE_SINGLE_PROXY:
58      out << "Single proxy:\n" << config.single_proxy;
59      break;
60    case ProxyConfigServiceImpl::ProxyConfig::MODE_PROXY_PER_SCHEME:
61      out << "HTTP proxy: " << config.http_proxy;
62      out << "HTTPS proxy: " << config.https_proxy;
63      out << "FTP proxy: " << config.ftp_proxy;
64      out << "SOCKS proxy: " << config.socks_proxy;
65      break;
66    default:
67      NOTREACHED() << "Unrecognized proxy config mode";
68      break;
69  }
70  if (config.mode == ProxyConfigServiceImpl::ProxyConfig::MODE_SINGLE_PROXY ||
71      config.mode ==
72          ProxyConfigServiceImpl::ProxyConfig::MODE_PROXY_PER_SCHEME) {
73    out << "Bypass list: ";
74    if (config.bypass_rules.rules().empty()) {
75      out << "[None]";
76    } else {
77      const net::ProxyBypassRules& bypass_rules = config.bypass_rules;
78      net::ProxyBypassRules::RuleList::const_iterator it;
79      for (it = bypass_rules.rules().begin();
80           it != bypass_rules.rules().end(); ++it) {
81        out << "\n    " << (*it)->ToString();
82      }
83    }
84  }
85  return out;
86}
87
88std::string ProxyConfigToString(
89    const ProxyConfigServiceImpl::ProxyConfig& proxy_config) {
90  std::ostringstream stream;
91  stream << proxy_config;
92  return stream.str();
93}
94
95// Name of signed setting persisted on device, writeable only by owner.
96const char* kSettingProxyEverywhere = "cros.proxy.everywhere";
97
98// Names used for dictionary values to serialize chromeos::ProxyConfig.
99const char* kMode = "mode";
100const char* kSource = "src";
101const char* kAutomaticProxy = "auto";
102const char* kSingleProxy = "single";
103const char* kHttpProxy = "http";
104const char* kHttpsProxy = "https";
105const char* kFtpProxy = "ftp";
106const char* kSocksProxy = "socks";
107const char* kPACUrl = "pac";
108const char* kServer = "server";
109const char* kBypassRules = "bypass_rules";
110const char* kRulesNum = "num";
111const char* kRulesList = "list";
112
113}  // namespace
114
115//---------- ProxyConfigServiceImpl::ProxyConfig::Setting methods --------------
116
117bool ProxyConfigServiceImpl::ProxyConfig::Setting::CanBeWrittenByUser(
118    bool user_is_owner) {
119  // Setting can only be written by user if user is owner and setting is not
120  // from policy.
121  return user_is_owner && source != ProxyConfig::SOURCE_POLICY;
122}
123
124DictionaryValue* ProxyConfigServiceImpl::ProxyConfig::Setting::Encode() const {
125  DictionaryValue* dict = new DictionaryValue;
126  dict->SetInteger(kSource, source);
127  return dict;
128}
129
130bool ProxyConfigServiceImpl::ProxyConfig::Setting::Decode(
131    DictionaryValue* dict) {
132  int int_source;
133  if (!dict->GetInteger(kSource, &int_source))
134    return false;
135  source = static_cast<Source>(int_source);
136  return true;
137}
138
139//------- ProxyConfigServiceImpl::ProxyConfig::AutomaticProxy methods ----------
140
141DictionaryValue*
142    ProxyConfigServiceImpl::ProxyConfig::AutomaticProxy::Encode() const {
143  DictionaryValue* dict = Setting::Encode();
144  if (!pac_url.is_empty())
145    dict->SetString(kPACUrl, pac_url.spec());
146  return dict;
147}
148
149bool ProxyConfigServiceImpl::ProxyConfig::AutomaticProxy::Decode(
150    DictionaryValue* dict, Mode mode) {
151  if (!Setting::Decode(dict))
152    return false;
153  if (mode == MODE_PAC_SCRIPT) {
154    std::string value;
155    if (!dict->GetString(kPACUrl, &value))
156      return false;
157    pac_url = GURL(value);
158  }
159  return true;
160}
161
162//--------- ProxyConfigServiceImpl::ProxyConfig::ManualProxy methods -----------
163
164DictionaryValue*
165    ProxyConfigServiceImpl::ProxyConfig::ManualProxy::Encode() const {
166  DictionaryValue* dict = Setting::Encode();
167  dict->SetString(kServer, server.ToURI());
168  return dict;
169}
170
171bool ProxyConfigServiceImpl::ProxyConfig::ManualProxy::Decode(
172    DictionaryValue* dict, net::ProxyServer::Scheme scheme) {
173  if (!Setting::Decode(dict))
174    return false;
175  std::string value;
176  if (!dict->GetString(kServer, &value))
177    return false;
178  server = net::ProxyServer::FromURI(value, scheme);
179  return true;
180}
181
182//----------- ProxyConfigServiceImpl::ProxyConfig: public methods --------------
183
184void ProxyConfigServiceImpl::ProxyConfig::ToNetProxyConfig(
185    net::ProxyConfig* net_config) {
186  switch (mode) {
187    case MODE_DIRECT:
188      *net_config = net::ProxyConfig::CreateDirect();
189      break;
190    case MODE_AUTO_DETECT:
191      *net_config = net::ProxyConfig::CreateAutoDetect();
192      break;
193    case MODE_PAC_SCRIPT:
194      *net_config = net::ProxyConfig::CreateFromCustomPacURL(
195          automatic_proxy.pac_url);
196      break;
197    case MODE_SINGLE_PROXY:
198      *net_config = net::ProxyConfig();
199      net_config->proxy_rules().type =
200             net::ProxyConfig::ProxyRules::TYPE_SINGLE_PROXY;
201      net_config->proxy_rules().single_proxy = single_proxy.server;
202      net_config->proxy_rules().bypass_rules = bypass_rules;
203      break;
204    case MODE_PROXY_PER_SCHEME:
205      *net_config = net::ProxyConfig();
206      net_config->proxy_rules().type =
207          net::ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME;
208      net_config->proxy_rules().proxy_for_http = http_proxy.server;
209      net_config->proxy_rules().proxy_for_https = https_proxy.server;
210      net_config->proxy_rules().proxy_for_ftp = ftp_proxy.server;
211      net_config->proxy_rules().fallback_proxy = socks_proxy.server;
212      net_config->proxy_rules().bypass_rules = bypass_rules;
213      break;
214    default:
215      NOTREACHED() << "Unrecognized proxy config mode";
216      break;
217  }
218}
219
220bool ProxyConfigServiceImpl::ProxyConfig::Serialize(std::string* output) {
221  scoped_ptr<DictionaryValue> dict(new DictionaryValue);
222  dict->SetInteger(kMode, mode);
223  DictionaryValue* proxy_dict;
224  switch (mode) {
225    case MODE_DIRECT:
226    case MODE_AUTO_DETECT:
227    case MODE_PAC_SCRIPT:
228      proxy_dict = automatic_proxy.Encode();
229      dict->Set(kAutomaticProxy, proxy_dict);
230      break;
231    case MODE_SINGLE_PROXY:
232      EncodeManualProxy(single_proxy, dict.get(), kSingleProxy);
233      break;
234    case MODE_PROXY_PER_SCHEME:
235      EncodeManualProxy(http_proxy, dict.get(), kHttpProxy);
236      EncodeManualProxy(https_proxy, dict.get(), kHttpsProxy);
237      EncodeManualProxy(ftp_proxy, dict.get(), kFtpProxy);
238      EncodeManualProxy(socks_proxy, dict.get(), kSocksProxy);
239      break;
240    default:
241      NOTREACHED() << "Unrecognized proxy config mode";
242      break;
243  }
244  net::ProxyBypassRules::RuleList rules = bypass_rules.rules();
245  if (!rules.empty()) {
246    DictionaryValue* bypass_dict = new DictionaryValue;
247    bypass_dict->SetInteger(kRulesNum, rules.size());
248    ListValue* list = new ListValue;
249    for (size_t i = 0; i < rules.size(); ++i) {
250      list->Append(Value::CreateStringValue(rules[i]->ToString()));
251    }
252    bypass_dict->Set(kRulesList, list);
253    dict->Set(kBypassRules, bypass_dict);
254  }
255  JSONStringValueSerializer serializer(output);
256  return serializer.Serialize(*dict.get());
257}
258
259bool ProxyConfigServiceImpl::ProxyConfig::Deserialize(
260    const std::string& input) {
261  JSONStringValueSerializer serializer(input);
262  scoped_ptr<Value> value(serializer.Deserialize(NULL, NULL));
263  if (!value.get() || value->GetType() != Value::TYPE_DICTIONARY)
264    return false;
265  DictionaryValue* dict = static_cast<DictionaryValue*>(value.get());
266  int int_mode;
267  if (!dict->GetInteger(kMode, &int_mode))
268    return false;
269  mode = static_cast<Mode>(int_mode);
270  DictionaryValue* proxy_dict = NULL;
271  switch (mode) {
272    case MODE_DIRECT:
273    case MODE_AUTO_DETECT:
274    case MODE_PAC_SCRIPT:
275      if (!dict->GetDictionary(kAutomaticProxy, &proxy_dict) ||
276          !automatic_proxy.Decode(proxy_dict, mode))
277        return false;
278      break;
279    case MODE_SINGLE_PROXY:
280      if (!DecodeManualProxy(dict, kSingleProxy, false,
281                             net::ProxyServer::SCHEME_HTTP, &single_proxy))
282        return false;
283      break;
284    case MODE_PROXY_PER_SCHEME:
285      if (!DecodeManualProxy(dict, kHttpProxy, true,
286                             net::ProxyServer::SCHEME_HTTP, &http_proxy))
287        return false;
288      if (!DecodeManualProxy(dict, kHttpsProxy, true,
289                             net::ProxyServer::SCHEME_HTTPS, &https_proxy))
290        return false;
291      if (!DecodeManualProxy(dict, kFtpProxy, true,
292                             net::ProxyServer::SCHEME_HTTP, &ftp_proxy))
293        return false;
294      if (!DecodeManualProxy(dict, kSocksProxy, true,
295                             net::ProxyServer::SCHEME_SOCKS4, &socks_proxy))
296        return false;
297      // Make sure we have valid server for at least one of the protocols.
298      if (!(http_proxy.server.is_valid() || https_proxy.server.is_valid() ||
299            ftp_proxy.server.is_valid() || socks_proxy.server.is_valid()))
300        return false;
301      break;
302    default:
303      NOTREACHED() << "Unrecognized proxy config mode";
304      break;
305  }
306  DictionaryValue* bypass_dict = NULL;
307  if (dict->GetDictionary(kBypassRules, &bypass_dict)) {
308    int num_rules = 0;
309    if (bypass_dict->GetInteger(kRulesNum, &num_rules) && num_rules > 0) {
310      ListValue* list;
311      if (!bypass_dict->GetList(kRulesList, &list))
312        return false;
313      for (size_t i = 0; i < list->GetSize(); ++i) {
314        std::string rule;
315        if (!list->GetString(i, &rule))
316          return false;
317        bypass_rules.AddRuleFromString(rule);
318      }
319    }
320  }
321  return true;
322}
323
324std::string ProxyConfigServiceImpl::ProxyConfig::ToString() const {
325  return ProxyConfigToString(*this);
326}
327
328//----------- ProxyConfigServiceImpl::ProxyConfig: private methods -------------
329
330void ProxyConfigServiceImpl::ProxyConfig::EncodeManualProxy(
331    const ManualProxy& manual_proxy, DictionaryValue* dict,
332    const char* key_name) {
333  if (!manual_proxy.server.is_valid())
334    return;
335  DictionaryValue* proxy_dict = manual_proxy.Encode();
336  dict->Set(key_name, proxy_dict);
337}
338
339bool ProxyConfigServiceImpl::ProxyConfig::DecodeManualProxy(
340    DictionaryValue* dict, const char* key_name, bool ok_if_absent,
341    net::ProxyServer::Scheme scheme, ManualProxy* manual_proxy) {
342  DictionaryValue* proxy_dict;
343  if (!dict->GetDictionary(key_name, &proxy_dict))
344    return ok_if_absent;
345  return manual_proxy->Decode(proxy_dict, scheme);
346}
347
348//------------------- ProxyConfigServiceImpl: public methods -------------------
349
350ProxyConfigServiceImpl::ProxyConfigServiceImpl()
351    : can_post_task_(false),
352      has_config_(false),
353      persist_to_device_pending_(false) {
354  // Start async fetch of proxy config from settings persisted on device.
355  // TODO(kuan): retrieve config from policy and owner and merge them
356  bool use_default = true;
357  if (CrosLibrary::Get()->EnsureLoaded()) {
358    retrieve_property_op_ = SignedSettings::CreateRetrievePropertyOp(
359        kSettingProxyEverywhere, this);
360    if (retrieve_property_op_ && retrieve_property_op_->Execute()) {
361      VLOG(1) << "Start retrieving proxy setting from device";
362      use_default = false;
363    } else {
364      VLOG(1) << "Fail to retrieve proxy setting from device";
365    }
366  }
367  if (use_default)
368    InitConfigToDefault(false);
369  can_post_task_ = true;
370}
371
372ProxyConfigServiceImpl::ProxyConfigServiceImpl(const ProxyConfig& init_config)
373    : can_post_task_(true),
374      has_config_(true),
375      persist_to_device_pending_(false) {
376  reference_config_ = init_config;
377  // Update the IO-accessible copy in |cached_config_| as well.
378  cached_config_ = reference_config_;
379}
380
381ProxyConfigServiceImpl::~ProxyConfigServiceImpl() {
382}
383
384void ProxyConfigServiceImpl::UIGetProxyConfig(ProxyConfig* config) {
385  // Should be called from UI thread.
386  CheckCurrentlyOnUIThread();
387  // Simply returns the copy on the UI thread.
388  *config = reference_config_;
389}
390
391bool ProxyConfigServiceImpl::UISetProxyConfigToDirect() {
392  // Should be called from UI thread.
393  CheckCurrentlyOnUIThread();
394  reference_config_.mode = ProxyConfig::MODE_DIRECT;
395  OnUISetProxyConfig(true);
396  return true;
397}
398
399bool ProxyConfigServiceImpl::UISetProxyConfigToAutoDetect() {
400  // Should be called from UI thread.
401  CheckCurrentlyOnUIThread();
402  reference_config_.mode = ProxyConfig::MODE_AUTO_DETECT;
403  OnUISetProxyConfig(true);
404  return true;
405}
406
407bool ProxyConfigServiceImpl::UISetProxyConfigToPACScript(const GURL& pac_url) {
408  // Should be called from UI thread.
409  CheckCurrentlyOnUIThread();
410  reference_config_.mode = ProxyConfig::MODE_PAC_SCRIPT;
411  reference_config_.automatic_proxy.pac_url = pac_url;
412  OnUISetProxyConfig(true);
413  return true;
414}
415
416bool ProxyConfigServiceImpl::UISetProxyConfigToSingleProxy(
417    const net::ProxyServer& server) {
418  // Should be called from UI thread.
419  CheckCurrentlyOnUIThread();
420  reference_config_.mode = ProxyConfig::MODE_SINGLE_PROXY;
421  reference_config_.single_proxy.server = server;
422  OnUISetProxyConfig(true);
423  return true;
424}
425
426bool ProxyConfigServiceImpl::UISetProxyConfigToProxyPerScheme(
427    const std::string& scheme, const net::ProxyServer& server) {
428  // Should be called from UI thread.
429  CheckCurrentlyOnUIThread();
430  ProxyConfig::ManualProxy* proxy = NULL;
431  if (scheme == "http")
432    proxy = &reference_config_.http_proxy;
433  else if (scheme == "https")
434    proxy = &reference_config_.https_proxy;
435  else if (scheme == "ftp")
436    proxy = &reference_config_.ftp_proxy;
437  else if (scheme == "socks")
438    proxy = &reference_config_.socks_proxy;
439  if (!proxy) {
440    NOTREACHED() << "Cannot set proxy: invalid scheme [" << scheme << "]";
441    return false;
442  }
443  reference_config_.mode = ProxyConfig::MODE_PROXY_PER_SCHEME;
444  proxy->server = server;
445  OnUISetProxyConfig(true);
446  return true;
447}
448
449bool ProxyConfigServiceImpl::UISetProxyConfigBypassRules(
450    const net::ProxyBypassRules& bypass_rules) {
451  // Should be called from UI thread.
452  CheckCurrentlyOnUIThread();
453  DCHECK(reference_config_.mode == ProxyConfig::MODE_SINGLE_PROXY ||
454         reference_config_.mode == ProxyConfig::MODE_PROXY_PER_SCHEME);
455  if (reference_config_.mode != ProxyConfig::MODE_SINGLE_PROXY &&
456      reference_config_.mode != ProxyConfig::MODE_PROXY_PER_SCHEME) {
457    VLOG(1) << "Cannot set bypass rules for proxy mode ["
458             << reference_config_.mode << "]";
459    return false;
460  }
461  reference_config_.bypass_rules = bypass_rules;
462  OnUISetProxyConfig(true);
463  return true;
464}
465
466void ProxyConfigServiceImpl::AddObserver(
467    net::ProxyConfigService::Observer* observer) {
468  // Should be called from IO thread.
469  CheckCurrentlyOnIOThread();
470  observers_.AddObserver(observer);
471}
472
473void ProxyConfigServiceImpl::RemoveObserver(
474    net::ProxyConfigService::Observer* observer) {
475  // Should be called from IO thread.
476  CheckCurrentlyOnIOThread();
477  observers_.RemoveObserver(observer);
478}
479
480bool ProxyConfigServiceImpl::IOGetProxyConfig(net::ProxyConfig* net_config) {
481  // Should be called from IO thread.
482  CheckCurrentlyOnIOThread();
483  if (has_config_) {
484    // Simply return the last cached proxy configuration.
485    cached_config_.ToNetProxyConfig(net_config);
486    return true;
487  }
488  return false;
489}
490
491void ProxyConfigServiceImpl::OnSettingsOpSucceeded(bool value) {
492  VLOG(1) << "Stored proxy setting to device";
493  store_property_op_ = NULL;
494  if (persist_to_device_pending_)
495    PersistConfigToDevice();
496}
497
498void ProxyConfigServiceImpl::OnSettingsOpSucceeded(std::string value) {
499  VLOG(1) << "Retrieved proxy setting from device, value=[" << value << "]";
500  if (reference_config_.Deserialize(value)) {
501    OnUISetProxyConfig(false);
502  } else {
503    LOG(WARNING) << "Error deserializing device's proxy setting";
504    InitConfigToDefault(true);
505  }
506  retrieve_property_op_ = NULL;
507}
508
509void ProxyConfigServiceImpl::OnSettingsOpFailed() {
510  if (retrieve_property_op_) {
511    LOG(WARNING) << "Error retrieving proxy setting from device";
512    InitConfigToDefault(true);
513    retrieve_property_op_ = NULL;
514  } else {
515    LOG(WARNING) << "Error storing proxy setting to device";
516    store_property_op_ = NULL;
517    if (persist_to_device_pending_)
518      PersistConfigToDevice();
519  }
520}
521
522//------------------ ProxyConfigServiceImpl: private methods -------------------
523
524void ProxyConfigServiceImpl::InitConfigToDefault(bool post_to_io_thread) {
525  VLOG(1) << "Using default proxy config: auto-detect";
526  reference_config_.mode = ProxyConfig::MODE_AUTO_DETECT;
527  reference_config_.automatic_proxy.source = ProxyConfig::SOURCE_OWNER;
528  if (post_to_io_thread && can_post_task_) {
529    OnUISetProxyConfig(false);
530  } else {
531    // Update the IO-accessible copy in |cached_config_| as well.
532    cached_config_ = reference_config_;
533    has_config_ = true;
534  }
535}
536
537void ProxyConfigServiceImpl::PersistConfigToDevice() {
538  DCHECK(!store_property_op_);
539  persist_to_device_pending_ = false;
540  std::string value;
541  if (!reference_config_.Serialize(&value)) {
542    VLOG(1) << "Error serializing proxy config";
543    return;
544  }
545  store_property_op_ = SignedSettings::CreateStorePropertyOp(
546      kSettingProxyEverywhere, value, this);
547  bool rc = store_property_op_->Execute();
548  VLOG(1) << "Start storing proxy setting to device, value=" << value << ", rc="
549          << rc;
550}
551
552void ProxyConfigServiceImpl::OnUISetProxyConfig(bool persist_to_device) {
553  // Posts a task to IO thread with the new config, so it can update
554  // |cached_config_|.
555  Task* task = NewRunnableMethod(this,
556      &ProxyConfigServiceImpl::IOSetProxyConfig, reference_config_);
557  if (!BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, task)) {
558    VLOG(1) << "Couldn't post task to IO thread to set new proxy config";
559    delete task;
560  }
561
562  if (persist_to_device && CrosLibrary::Get()->EnsureLoaded()) {
563    if (store_property_op_) {
564      persist_to_device_pending_ = true;
565      VLOG(1) << "Pending persisting proxy setting to device";
566    } else {
567      PersistConfigToDevice();
568    }
569  }
570}
571
572void ProxyConfigServiceImpl::CheckCurrentlyOnIOThread() {
573  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
574}
575
576void ProxyConfigServiceImpl::CheckCurrentlyOnUIThread() {
577  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
578}
579
580void ProxyConfigServiceImpl::IOSetProxyConfig(const ProxyConfig& new_config) {
581  // This is called on the IO thread (posted from UI thread).
582  CheckCurrentlyOnIOThread();
583  VLOG(1) << "Proxy configuration changed";
584  has_config_ = true;
585  cached_config_ = new_config;
586  // Notify observers of new proxy config.
587  net::ProxyConfig net_config;
588  cached_config_.ToNetProxyConfig(&net_config);
589  FOR_EACH_OBSERVER(net::ProxyConfigService::Observer, observers_,
590                    OnProxyConfigChanged(net_config));
591}
592
593}  // namespace chromeos
594