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.
5 #include "net/proxy/proxy_config_service_mac.h"
7 #include <CoreFoundation/CoreFoundation.h>
8 #include <SystemConfiguration/SystemConfiguration.h>
10 #include "base/bind.h"
11 #include "base/logging.h"
12 #include "base/mac/foundation_util.h"
13 #include "base/mac/scoped_cftyperef.h"
14 #include "base/message_loop/message_loop.h"
15 #include "base/strings/sys_string_conversions.h"
16 #include "net/base/net_errors.h"
17 #include "net/proxy/proxy_config.h"
18 #include "net/proxy/proxy_info.h"
19 #include "net/proxy/proxy_server.h"
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.
27 bool GetBoolFromDictionary(CFDictionaryRef dict
,
30 CFNumberRef number
= base::mac::GetValueFromDictionary
<CFNumberRef
>(dict
,
36 if (CFNumberGetValue(number
, kCFNumberIntType
, &int_value
))
42 void GetCurrentProxyConfig(ProxyConfig
* config
) {
43 base::ScopedCFTypeRef
<CFDictionaryRef
> config_dict(
44 SCDynamicStoreCopyProxies(NULL
));
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
,
59 if (GetBoolFromDictionary(config_dict
.get(),
60 kSCPropNetProxiesProxyAutoConfigEnable
,
62 CFStringRef pac_url_ref
= base::mac::GetValueFromDictionary
<CFStringRef
>(
63 config_dict
.get(), kSCPropNetProxiesProxyAutoConfigURLString
);
65 config
->set_pac_url(GURL(base::SysCFStringRefToUTF8(pac_url_ref
)));
68 // proxies (for now ftp, http, https, and SOCKS)
70 if (GetBoolFromDictionary(config_dict
.get(),
71 kSCPropNetProxiesFTPEnable
,
73 ProxyServer proxy_server
=
74 ProxyServer::FromDictionary(ProxyServer::SCHEME_HTTP
,
76 kSCPropNetProxiesFTPProxy
,
77 kSCPropNetProxiesFTPPort
);
78 if (proxy_server
.is_valid()) {
79 config
->proxy_rules().type
=
80 ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME
;
81 config
->proxy_rules().proxies_for_ftp
.SetSingleProxyServer(proxy_server
);
84 if (GetBoolFromDictionary(config_dict
.get(),
85 kSCPropNetProxiesHTTPEnable
,
87 ProxyServer proxy_server
=
88 ProxyServer::FromDictionary(ProxyServer::SCHEME_HTTP
,
90 kSCPropNetProxiesHTTPProxy
,
91 kSCPropNetProxiesHTTPPort
);
92 if (proxy_server
.is_valid()) {
93 config
->proxy_rules().type
=
94 ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME
;
95 config
->proxy_rules().proxies_for_http
.SetSingleProxyServer(proxy_server
);
98 if (GetBoolFromDictionary(config_dict
.get(),
99 kSCPropNetProxiesHTTPSEnable
,
101 ProxyServer proxy_server
=
102 ProxyServer::FromDictionary(ProxyServer::SCHEME_HTTP
,
104 kSCPropNetProxiesHTTPSProxy
,
105 kSCPropNetProxiesHTTPSPort
);
106 if (proxy_server
.is_valid()) {
107 config
->proxy_rules().type
=
108 ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME
;
109 config
->proxy_rules().proxies_for_https
.
110 SetSingleProxyServer(proxy_server
);
113 if (GetBoolFromDictionary(config_dict
.get(),
114 kSCPropNetProxiesSOCKSEnable
,
116 ProxyServer proxy_server
=
117 ProxyServer::FromDictionary(ProxyServer::SCHEME_SOCKS5
,
119 kSCPropNetProxiesSOCKSProxy
,
120 kSCPropNetProxiesSOCKSPort
);
121 if (proxy_server
.is_valid()) {
122 config
->proxy_rules().type
=
123 ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME
;
124 config
->proxy_rules().fallback_proxies
.SetSingleProxyServer(proxy_server
);
130 CFArrayRef bypass_array_ref
= base::mac::GetValueFromDictionary
<CFArrayRef
>(
131 config_dict
.get(), kSCPropNetProxiesExceptionsList
);
132 if (bypass_array_ref
) {
133 CFIndex bypass_array_count
= CFArrayGetCount(bypass_array_ref
);
134 for (CFIndex i
= 0; i
< bypass_array_count
; ++i
) {
135 CFStringRef bypass_item_ref
= base::mac::CFCast
<CFStringRef
>(
136 CFArrayGetValueAtIndex(bypass_array_ref
, i
));
137 if (!bypass_item_ref
) {
138 LOG(WARNING
) << "Expected value for item " << i
139 << " in the kSCPropNetProxiesExceptionsList"
140 " to be a CFStringRef but it was not";
143 config
->proxy_rules().bypass_rules
.AddRuleFromString(
144 base::SysCFStringRefToUTF8(bypass_item_ref
));
149 // proxy bypass boolean
151 if (GetBoolFromDictionary(config_dict
.get(),
152 kSCPropNetProxiesExcludeSimpleHostnames
,
154 config
->proxy_rules().bypass_rules
.AddRuleToBypassLocal();
158 config
->set_source(PROXY_CONFIG_SOURCE_SYSTEM
);
163 // Reference-counted helper for posting a task to
164 // ProxyConfigServiceMac::OnProxyConfigChanged between the notifier and IO
165 // thread. This helper object may outlive the ProxyConfigServiceMac.
166 class ProxyConfigServiceMac::Helper
167 : public base::RefCountedThreadSafe
<ProxyConfigServiceMac::Helper
> {
169 explicit Helper(ProxyConfigServiceMac
* parent
) : parent_(parent
) {
173 // Called when the parent is destroyed.
178 void OnProxyConfigChanged(const ProxyConfig
& new_config
) {
180 parent_
->OnProxyConfigChanged(new_config
);
184 friend class base::RefCountedThreadSafe
<Helper
>;
187 ProxyConfigServiceMac
* parent_
;
190 void ProxyConfigServiceMac::Forwarder::SetDynamicStoreNotificationKeys(
191 SCDynamicStoreRef store
) {
192 proxy_config_service_
->SetDynamicStoreNotificationKeys(store
);
195 void ProxyConfigServiceMac::Forwarder::OnNetworkConfigChange(
196 CFArrayRef changed_keys
) {
197 proxy_config_service_
->OnNetworkConfigChange(changed_keys
);
200 ProxyConfigServiceMac::ProxyConfigServiceMac(
201 const scoped_refptr
<base::SingleThreadTaskRunner
>& io_thread_task_runner
)
203 has_fetched_config_(false),
204 helper_(new Helper(this)),
205 io_thread_task_runner_(io_thread_task_runner
) {
206 DCHECK(io_thread_task_runner_
.get());
207 config_watcher_
.reset(new NetworkConfigWatcherMac(&forwarder_
));
210 ProxyConfigServiceMac::~ProxyConfigServiceMac() {
211 DCHECK(io_thread_task_runner_
->BelongsToCurrentThread());
212 // Delete the config_watcher_ to ensure the notifier thread finishes before
213 // this object is destroyed.
214 config_watcher_
.reset();
218 void ProxyConfigServiceMac::AddObserver(Observer
* observer
) {
219 DCHECK(io_thread_task_runner_
->BelongsToCurrentThread());
220 observers_
.AddObserver(observer
);
223 void ProxyConfigServiceMac::RemoveObserver(Observer
* observer
) {
224 DCHECK(io_thread_task_runner_
->BelongsToCurrentThread());
225 observers_
.RemoveObserver(observer
);
228 ProxyConfigService::ConfigAvailability
229 ProxyConfigServiceMac::GetLatestProxyConfig(ProxyConfig
* config
) {
230 DCHECK(io_thread_task_runner_
->BelongsToCurrentThread());
232 // Lazy-initialize by fetching the proxy setting from this thread.
233 if (!has_fetched_config_
) {
234 GetCurrentProxyConfig(&last_config_fetched_
);
235 has_fetched_config_
= true;
238 *config
= last_config_fetched_
;
239 return has_fetched_config_
? CONFIG_VALID
: CONFIG_PENDING
;
242 void ProxyConfigServiceMac::SetDynamicStoreNotificationKeys(
243 SCDynamicStoreRef store
) {
244 // Called on notifier thread.
246 CFStringRef proxies_key
= SCDynamicStoreKeyCreateProxies(NULL
);
247 CFArrayRef key_array
= CFArrayCreate(
248 NULL
, (const void **)(&proxies_key
), 1, &kCFTypeArrayCallBacks
);
250 bool ret
= SCDynamicStoreSetNotificationKeys(store
, key_array
, NULL
);
251 // TODO(willchan): Figure out a proper way to handle this rather than crash.
254 CFRelease(key_array
);
255 CFRelease(proxies_key
);
258 void ProxyConfigServiceMac::OnNetworkConfigChange(CFArrayRef changed_keys
) {
259 // Called on notifier thread.
261 // Fetch the new system proxy configuration.
262 ProxyConfig new_config
;
263 GetCurrentProxyConfig(&new_config
);
265 // Call OnProxyConfigChanged() on the IO thread to notify our observers.
266 io_thread_task_runner_
->PostTask(
268 base::Bind(&Helper::OnProxyConfigChanged
, helper_
.get(), new_config
));
271 void ProxyConfigServiceMac::OnProxyConfigChanged(
272 const ProxyConfig
& new_config
) {
273 DCHECK(io_thread_task_runner_
->BelongsToCurrentThread());
275 // Keep track of the last value we have seen.
276 has_fetched_config_
= true;
277 last_config_fetched_
= new_config
;
279 // Notify all the observers.
280 FOR_EACH_OBSERVER(Observer
, observers_
,
281 OnProxyConfigChanged(new_config
, CONFIG_VALID
));