• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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