xref: /aosp_15_r20/external/cronet/net/proxy_resolution/proxy_config_service_mac.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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