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 <CFNetwork/CFProxySupport.h>
8 #include <CoreFoundation/CoreFoundation.h>
9 #include <SystemConfiguration/SystemConfiguration.h>
10
11 #include <memory>
12
13 #include "base/apple/foundation_util.h"
14 #include "base/apple/scoped_cftyperef.h"
15 #include "base/functional/bind.h"
16 #include "base/logging.h"
17 #include "base/memory/raw_ptr.h"
18 #include "base/strings/sys_string_conversions.h"
19 #include "base/task/sequenced_task_runner.h"
20 #include "net/base/net_errors.h"
21 #include "net/proxy_resolution/proxy_chain_util_apple.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 =
34 base::apple::GetValueFromDictionary<CFNumberRef>(dict, 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::apple::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::apple::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(), kSCPropNetProxiesFTPEnable,
75 false)) {
76 ProxyChain proxy_chain = ProxyDictionaryToProxyChain(
77 kCFProxyTypeHTTP, config_dict.get(), kSCPropNetProxiesFTPProxy,
78 kSCPropNetProxiesFTPPort);
79 if (proxy_chain.IsValid()) {
80 proxy_config.proxy_rules().type =
81 ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME;
82 proxy_config.proxy_rules().proxies_for_ftp.SetSingleProxyChain(
83 proxy_chain);
84 }
85 }
86 if (GetBoolFromDictionary(config_dict.get(), kSCPropNetProxiesHTTPEnable,
87 false)) {
88 ProxyChain proxy_chain = ProxyDictionaryToProxyChain(
89 kCFProxyTypeHTTP, config_dict.get(), kSCPropNetProxiesHTTPProxy,
90 kSCPropNetProxiesHTTPPort);
91 if (proxy_chain.IsValid()) {
92 proxy_config.proxy_rules().type =
93 ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME;
94 proxy_config.proxy_rules().proxies_for_http.SetSingleProxyChain(
95 proxy_chain);
96 }
97 }
98 if (GetBoolFromDictionary(config_dict.get(), kSCPropNetProxiesHTTPSEnable,
99 false)) {
100 ProxyChain proxy_chain = ProxyDictionaryToProxyChain(
101 kCFProxyTypeHTTPS, config_dict.get(), kSCPropNetProxiesHTTPSProxy,
102 kSCPropNetProxiesHTTPSPort);
103 if (proxy_chain.IsValid()) {
104 proxy_config.proxy_rules().type =
105 ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME;
106 proxy_config.proxy_rules().proxies_for_https.SetSingleProxyChain(
107 proxy_chain);
108 }
109 }
110 if (GetBoolFromDictionary(config_dict.get(), kSCPropNetProxiesSOCKSEnable,
111 false)) {
112 ProxyChain proxy_chain = ProxyDictionaryToProxyChain(
113 kCFProxyTypeSOCKS, config_dict.get(), kSCPropNetProxiesSOCKSProxy,
114 kSCPropNetProxiesSOCKSPort);
115 if (proxy_chain.IsValid()) {
116 proxy_config.proxy_rules().type =
117 ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME;
118 proxy_config.proxy_rules().fallback_proxies.SetSingleProxyChain(
119 proxy_chain);
120 }
121 }
122
123 // proxy bypass list
124
125 CFArrayRef bypass_array_ref = base::apple::GetValueFromDictionary<CFArrayRef>(
126 config_dict.get(), kSCPropNetProxiesExceptionsList);
127 if (bypass_array_ref) {
128 CFIndex bypass_array_count = CFArrayGetCount(bypass_array_ref);
129 for (CFIndex i = 0; i < bypass_array_count; ++i) {
130 CFStringRef bypass_item_ref = base::apple::CFCast<CFStringRef>(
131 CFArrayGetValueAtIndex(bypass_array_ref, i));
132 if (!bypass_item_ref) {
133 LOG(WARNING) << "Expected value for item " << i
134 << " in the kSCPropNetProxiesExceptionsList"
135 " to be a CFStringRef but it was not";
136
137 } else {
138 proxy_config.proxy_rules().bypass_rules.AddRuleFromString(
139 base::SysCFStringRefToUTF8(bypass_item_ref));
140 }
141 }
142 }
143
144 // proxy bypass boolean
145
146 if (GetBoolFromDictionary(config_dict.get(),
147 kSCPropNetProxiesExcludeSimpleHostnames,
148 false)) {
149 proxy_config.proxy_rules()
150 .bypass_rules.PrependRuleToBypassSimpleHostnames();
151 }
152
153 *config = ProxyConfigWithAnnotation(proxy_config, traffic_annotation);
154 }
155
156 } // namespace
157
158 // Reference-counted helper for posting a task to
159 // ProxyConfigServiceMac::OnProxyConfigChanged between the notifier and IO
160 // thread. This helper object may outlive the ProxyConfigServiceMac.
161 class ProxyConfigServiceMac::Helper
162 : public base::RefCountedThreadSafe<ProxyConfigServiceMac::Helper> {
163 public:
Helper(ProxyConfigServiceMac * parent)164 explicit Helper(ProxyConfigServiceMac* parent) : parent_(parent) {
165 DCHECK(parent);
166 }
167
168 // Called when the parent is destroyed.
Orphan()169 void Orphan() { parent_ = nullptr; }
170
OnProxyConfigChanged(const ProxyConfigWithAnnotation & new_config)171 void OnProxyConfigChanged(const ProxyConfigWithAnnotation& new_config) {
172 if (parent_)
173 parent_->OnProxyConfigChanged(new_config);
174 }
175
176 private:
177 friend class base::RefCountedThreadSafe<Helper>;
178 ~Helper() = default;
179
180 raw_ptr<ProxyConfigServiceMac> parent_;
181 };
182
SetDynamicStoreNotificationKeys(base::apple::ScopedCFTypeRef<SCDynamicStoreRef> store)183 void ProxyConfigServiceMac::Forwarder::SetDynamicStoreNotificationKeys(
184 base::apple::ScopedCFTypeRef<SCDynamicStoreRef> store) {
185 proxy_config_service_->SetDynamicStoreNotificationKeys(std::move(store));
186 }
187
OnNetworkConfigChange(CFArrayRef changed_keys)188 void ProxyConfigServiceMac::Forwarder::OnNetworkConfigChange(
189 CFArrayRef changed_keys) {
190 proxy_config_service_->OnNetworkConfigChange(changed_keys);
191 }
192
ProxyConfigServiceMac(const scoped_refptr<base::SequencedTaskRunner> & sequenced_task_runner,const NetworkTrafficAnnotationTag & traffic_annotation)193 ProxyConfigServiceMac::ProxyConfigServiceMac(
194 const scoped_refptr<base::SequencedTaskRunner>& sequenced_task_runner,
195 const NetworkTrafficAnnotationTag& traffic_annotation)
196 : forwarder_(this),
197 helper_(base::MakeRefCounted<Helper>(this)),
198 sequenced_task_runner_(sequenced_task_runner),
199 traffic_annotation_(traffic_annotation) {
200 DCHECK(sequenced_task_runner_.get());
201 config_watcher_ = std::make_unique<NetworkConfigWatcherApple>(&forwarder_);
202 }
203
~ProxyConfigServiceMac()204 ProxyConfigServiceMac::~ProxyConfigServiceMac() {
205 DCHECK(sequenced_task_runner_->RunsTasksInCurrentSequence());
206 // Delete the config_watcher_ to ensure the notifier thread finishes before
207 // this object is destroyed.
208 config_watcher_.reset();
209 helper_->Orphan();
210 }
211
AddObserver(Observer * observer)212 void ProxyConfigServiceMac::AddObserver(Observer* observer) {
213 DCHECK(sequenced_task_runner_->RunsTasksInCurrentSequence());
214 observers_.AddObserver(observer);
215 }
216
RemoveObserver(Observer * observer)217 void ProxyConfigServiceMac::RemoveObserver(Observer* observer) {
218 DCHECK(sequenced_task_runner_->RunsTasksInCurrentSequence());
219 observers_.RemoveObserver(observer);
220 }
221
222 ProxyConfigService::ConfigAvailability
GetLatestProxyConfig(ProxyConfigWithAnnotation * config)223 ProxyConfigServiceMac::GetLatestProxyConfig(ProxyConfigWithAnnotation* config) {
224 DCHECK(sequenced_task_runner_->RunsTasksInCurrentSequence());
225
226 // Lazy-initialize by fetching the proxy setting from this thread.
227 if (!has_fetched_config_) {
228 GetCurrentProxyConfig(traffic_annotation_, &last_config_fetched_);
229 has_fetched_config_ = true;
230 }
231
232 *config = last_config_fetched_;
233 return has_fetched_config_ ? CONFIG_VALID : CONFIG_PENDING;
234 }
235
SetDynamicStoreNotificationKeys(base::apple::ScopedCFTypeRef<SCDynamicStoreRef> store)236 void ProxyConfigServiceMac::SetDynamicStoreNotificationKeys(
237 base::apple::ScopedCFTypeRef<SCDynamicStoreRef> store) {
238 // Called on notifier thread.
239
240 base::apple::ScopedCFTypeRef<CFStringRef> proxies_key(
241 SCDynamicStoreKeyCreateProxies(nullptr));
242 base::apple::ScopedCFTypeRef<CFArrayRef> key_array(CFArrayCreate(
243 nullptr, (const void**)(&proxies_key), 1, &kCFTypeArrayCallBacks));
244
245 bool ret = SCDynamicStoreSetNotificationKeys(store.get(), key_array.get(),
246 /*patterns=*/nullptr);
247 // TODO(willchan): Figure out a proper way to handle this rather than crash.
248 CHECK(ret);
249 }
250
OnNetworkConfigChange(CFArrayRef changed_keys)251 void ProxyConfigServiceMac::OnNetworkConfigChange(CFArrayRef changed_keys) {
252 // Called on notifier thread.
253
254 // Fetch the new system proxy configuration.
255 ProxyConfigWithAnnotation new_config;
256 GetCurrentProxyConfig(traffic_annotation_, &new_config);
257
258 // Call OnProxyConfigChanged() on the TakeRunner to notify our observers.
259 sequenced_task_runner_->PostTask(
260 FROM_HERE,
261 base::BindOnce(&Helper::OnProxyConfigChanged, helper_.get(), new_config));
262 }
263
OnProxyConfigChanged(const ProxyConfigWithAnnotation & new_config)264 void ProxyConfigServiceMac::OnProxyConfigChanged(
265 const ProxyConfigWithAnnotation& new_config) {
266 DCHECK(sequenced_task_runner_->RunsTasksInCurrentSequence());
267
268 // Keep track of the last value we have seen.
269 has_fetched_config_ = true;
270 last_config_fetched_ = new_config;
271
272 // Notify all the observers.
273 for (auto& observer : observers_)
274 observer.OnProxyConfigChanged(new_config, CONFIG_VALID);
275 }
276
277 } // namespace net
278