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
19 namespace net {
20
21 namespace {
22
23 const 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.
GetBoolFromDictionary(CFDictionaryRef dict,CFStringRef key,bool default_value)27 bool 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
GetCurrentProxyConfig(ProxyConfig * config)42 void 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.
167 class ProxyConfigServiceMac::Helper
168 : public base::RefCountedThreadSafe<ProxyConfigServiceMac::Helper> {
169 public:
Helper(ProxyConfigServiceMac * parent)170 explicit Helper(ProxyConfigServiceMac* parent) : parent_(parent) {
171 DCHECK(parent);
172 }
173
174 // Called when the parent is destroyed.
Orphan()175 void Orphan() {
176 parent_ = NULL;
177 }
178
OnProxyConfigChanged(const ProxyConfig & new_config)179 void OnProxyConfigChanged(const ProxyConfig& new_config) {
180 if (parent_)
181 parent_->OnProxyConfigChanged(new_config);
182 }
183
184 private:
185 ProxyConfigServiceMac* parent_;
186 };
187
ProxyConfigServiceMac(MessageLoop * io_loop)188 ProxyConfigServiceMac::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
~ProxyConfigServiceMac()197 ProxyConfigServiceMac::~ProxyConfigServiceMac() {
198 DCHECK_EQ(io_loop_, MessageLoop::current());
199 helper_->Orphan();
200 io_loop_ = NULL;
201 }
202
AddObserver(Observer * observer)203 void ProxyConfigServiceMac::AddObserver(Observer* observer) {
204 DCHECK_EQ(io_loop_, MessageLoop::current());
205 observers_.AddObserver(observer);
206 }
207
RemoveObserver(Observer * observer)208 void ProxyConfigServiceMac::RemoveObserver(Observer* observer) {
209 DCHECK_EQ(io_loop_, MessageLoop::current());
210 observers_.RemoveObserver(observer);
211 }
212
213 net::ProxyConfigService::ConfigAvailability
GetLatestProxyConfig(ProxyConfig * config)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
SetDynamicStoreNotificationKeys(SCDynamicStoreRef store)227 void 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
OnNetworkConfigChange(CFArrayRef changed_keys)243 void 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
OnProxyConfigChanged(const ProxyConfig & new_config)257 void 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