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