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