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 "net/proxy/proxy_config_service_mac.h"
6
7#include <CoreFoundation/CoreFoundation.h>
8#include <SystemConfiguration/SystemConfiguration.h>
9
10#include "base/logging.h"
11#include "base/mac/mac_util.h"
12#include "base/mac/scoped_cftyperef.h"
13#include "base/sys_string_conversions.h"
14#include "net/base/net_errors.h"
15#include "net/proxy/proxy_config.h"
16#include "net/proxy/proxy_info.h"
17#include "net/proxy/proxy_server.h"
18
19namespace net {
20
21namespace {
22
23const int kPollIntervalSec = 5;
24
25// Utility function to pull out a boolean value from a dictionary and return it,
26// returning a default value if the key is not present.
27bool GetBoolFromDictionary(CFDictionaryRef dict,
28                           CFStringRef key,
29                           bool default_value) {
30  CFNumberRef number = (CFNumberRef)base::mac::GetValueFromDictionary(
31      dict, key, CFNumberGetTypeID());
32  if (!number)
33    return default_value;
34
35  int int_value;
36  if (CFNumberGetValue(number, kCFNumberIntType, &int_value))
37    return int_value;
38  else
39    return default_value;
40}
41
42void GetCurrentProxyConfig(ProxyConfig* config) {
43  base::mac::ScopedCFTypeRef<CFDictionaryRef> config_dict(
44      SCDynamicStoreCopyProxies(NULL));
45  DCHECK(config_dict);
46
47  // auto-detect
48
49  // There appears to be no UI for this configuration option, and we're not sure
50  // if Apple's proxy code even takes it into account. But the constant is in
51  // the header file so we'll use it.
52  config->set_auto_detect(
53      GetBoolFromDictionary(config_dict.get(),
54                            kSCPropNetProxiesProxyAutoDiscoveryEnable,
55                            false));
56
57  // PAC file
58
59  if (GetBoolFromDictionary(config_dict.get(),
60                            kSCPropNetProxiesProxyAutoConfigEnable,
61                            false)) {
62    CFStringRef pac_url_ref = (CFStringRef)base::mac::GetValueFromDictionary(
63        config_dict.get(),
64        kSCPropNetProxiesProxyAutoConfigURLString,
65        CFStringGetTypeID());
66    if (pac_url_ref)
67      config->set_pac_url(GURL(base::SysCFStringRefToUTF8(pac_url_ref)));
68  }
69
70  // proxies (for now ftp, http, https, and SOCKS)
71
72  if (GetBoolFromDictionary(config_dict.get(),
73                            kSCPropNetProxiesFTPEnable,
74                            false)) {
75    ProxyServer proxy_server =
76        ProxyServer::FromDictionary(ProxyServer::SCHEME_HTTP,
77                                    config_dict.get(),
78                                    kSCPropNetProxiesFTPProxy,
79                                    kSCPropNetProxiesFTPPort);
80    if (proxy_server.is_valid()) {
81      config->proxy_rules().type =
82          ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME;
83      config->proxy_rules().proxy_for_ftp = proxy_server;
84    }
85  }
86  if (GetBoolFromDictionary(config_dict.get(),
87                            kSCPropNetProxiesHTTPEnable,
88                            false)) {
89    ProxyServer proxy_server =
90        ProxyServer::FromDictionary(ProxyServer::SCHEME_HTTP,
91                                    config_dict.get(),
92                                    kSCPropNetProxiesHTTPProxy,
93                                    kSCPropNetProxiesHTTPPort);
94    if (proxy_server.is_valid()) {
95      config->proxy_rules().type =
96          ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME;
97      config->proxy_rules().proxy_for_http = proxy_server;
98    }
99  }
100  if (GetBoolFromDictionary(config_dict.get(),
101                            kSCPropNetProxiesHTTPSEnable,
102                            false)) {
103    ProxyServer proxy_server =
104        ProxyServer::FromDictionary(ProxyServer::SCHEME_HTTP,
105                                    config_dict.get(),
106                                    kSCPropNetProxiesHTTPSProxy,
107                                    kSCPropNetProxiesHTTPSPort);
108    if (proxy_server.is_valid()) {
109      config->proxy_rules().type =
110          ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME;
111      config->proxy_rules().proxy_for_https = proxy_server;
112    }
113  }
114  if (GetBoolFromDictionary(config_dict.get(),
115                            kSCPropNetProxiesSOCKSEnable,
116                            false)) {
117    ProxyServer proxy_server =
118        ProxyServer::FromDictionary(ProxyServer::SCHEME_SOCKS5,
119                                    config_dict.get(),
120                                    kSCPropNetProxiesSOCKSProxy,
121                                    kSCPropNetProxiesSOCKSPort);
122    if (proxy_server.is_valid()) {
123      config->proxy_rules().type =
124          ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME;
125      config->proxy_rules().fallback_proxy = proxy_server;
126    }
127  }
128
129  // proxy bypass list
130
131  CFArrayRef bypass_array_ref =
132      (CFArrayRef)base::mac::GetValueFromDictionary(
133          config_dict.get(),
134          kSCPropNetProxiesExceptionsList,
135          CFArrayGetTypeID());
136  if (bypass_array_ref) {
137    CFIndex bypass_array_count = CFArrayGetCount(bypass_array_ref);
138    for (CFIndex i = 0; i < bypass_array_count; ++i) {
139      CFStringRef bypass_item_ref =
140          (CFStringRef)CFArrayGetValueAtIndex(bypass_array_ref, i);
141      if (CFGetTypeID(bypass_item_ref) != CFStringGetTypeID()) {
142        LOG(WARNING) << "Expected value for item " << i
143                     << " in the kSCPropNetProxiesExceptionsList"
144                        " to be a CFStringRef but it was not";
145
146      } else {
147        config->proxy_rules().bypass_rules.AddRuleFromString(
148            base::SysCFStringRefToUTF8(bypass_item_ref));
149      }
150    }
151  }
152
153  // proxy bypass boolean
154
155  if (GetBoolFromDictionary(config_dict.get(),
156                            kSCPropNetProxiesExcludeSimpleHostnames,
157                            false)) {
158    config->proxy_rules().bypass_rules.AddRuleToBypassLocal();
159  }
160}
161
162}  // namespace
163
164// Reference-counted helper for posting a task to
165// ProxyConfigServiceMac::OnProxyConfigChanged between the notifier and IO
166// thread. This helper object may outlive the ProxyConfigServiceMac.
167class ProxyConfigServiceMac::Helper
168    : public base::RefCountedThreadSafe<ProxyConfigServiceMac::Helper> {
169 public:
170  explicit Helper(ProxyConfigServiceMac* parent) : parent_(parent) {
171    DCHECK(parent);
172  }
173
174  // Called when the parent is destroyed.
175  void Orphan() {
176    parent_ = NULL;
177  }
178
179  void OnProxyConfigChanged(const ProxyConfig& new_config) {
180    if (parent_)
181      parent_->OnProxyConfigChanged(new_config);
182  }
183
184 private:
185  ProxyConfigServiceMac* parent_;
186};
187
188ProxyConfigServiceMac::ProxyConfigServiceMac(MessageLoop* io_loop)
189    : forwarder_(this),
190      config_watcher_(&forwarder_),
191      has_fetched_config_(false),
192      helper_(new Helper(this)),
193      io_loop_(io_loop) {
194  DCHECK(io_loop);
195}
196
197ProxyConfigServiceMac::~ProxyConfigServiceMac() {
198  DCHECK_EQ(io_loop_, MessageLoop::current());
199  helper_->Orphan();
200  io_loop_ = NULL;
201}
202
203void ProxyConfigServiceMac::AddObserver(Observer* observer) {
204  DCHECK_EQ(io_loop_, MessageLoop::current());
205  observers_.AddObserver(observer);
206}
207
208void ProxyConfigServiceMac::RemoveObserver(Observer* observer) {
209  DCHECK_EQ(io_loop_, MessageLoop::current());
210  observers_.RemoveObserver(observer);
211}
212
213net::ProxyConfigService::ConfigAvailability
214    ProxyConfigServiceMac::GetLatestProxyConfig(ProxyConfig* config) {
215  DCHECK_EQ(io_loop_, MessageLoop::current());
216
217  // Lazy-initialize by fetching the proxy setting from this thread.
218  if (!has_fetched_config_) {
219    GetCurrentProxyConfig(&last_config_fetched_);
220    has_fetched_config_ = true;
221  }
222
223  *config = last_config_fetched_;
224  return has_fetched_config_ ? CONFIG_VALID : CONFIG_PENDING;
225}
226
227void ProxyConfigServiceMac::SetDynamicStoreNotificationKeys(
228    SCDynamicStoreRef store) {
229  // Called on notifier thread.
230
231  CFStringRef proxies_key = SCDynamicStoreKeyCreateProxies(NULL);
232  CFArrayRef key_array = CFArrayCreate(
233      NULL, (const void **)(&proxies_key), 1, &kCFTypeArrayCallBacks);
234
235  bool ret = SCDynamicStoreSetNotificationKeys(store, key_array, NULL);
236  // TODO(willchan): Figure out a proper way to handle this rather than crash.
237  CHECK(ret);
238
239  CFRelease(key_array);
240  CFRelease(proxies_key);
241}
242
243void ProxyConfigServiceMac::OnNetworkConfigChange(CFArrayRef changed_keys) {
244  // Called on notifier thread.
245
246  // Fetch the new system proxy configuration.
247  ProxyConfig new_config;
248  GetCurrentProxyConfig(&new_config);
249
250  // Call OnProxyConfigChanged() on the IO thread to notify our observers.
251  io_loop_->PostTask(
252      FROM_HERE,
253      NewRunnableMethod(
254          helper_.get(), &Helper::OnProxyConfigChanged, new_config));
255}
256
257void ProxyConfigServiceMac::OnProxyConfigChanged(
258    const ProxyConfig& new_config) {
259  DCHECK_EQ(io_loop_, MessageLoop::current());
260
261  // Keep track of the last value we have seen.
262  has_fetched_config_ = true;
263  last_config_fetched_ = new_config;
264
265  // Notify all the observers.
266  FOR_EACH_OBSERVER(Observer, observers_,
267                    OnProxyConfigChanged(new_config, CONFIG_VALID));
268}
269
270}  // namespace net
271