xref: /aosp_15_r20/external/cronet/net/proxy_resolution/proxy_resolver_apple.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 // Copyright 2011 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_resolver_apple.h"
6 
7 #include <CFNetwork/CFProxySupport.h>
8 #include <CoreFoundation/CoreFoundation.h>
9 
10 #include <memory>
11 
12 #include "base/apple/foundation_util.h"
13 #include "base/apple/scoped_cftyperef.h"
14 #include "base/check.h"
15 #include "base/lazy_instance.h"
16 #include "base/memory/raw_ref.h"
17 #include "base/strings/string_util.h"
18 #include "base/strings/sys_string_conversions.h"
19 #include "base/synchronization/lock.h"
20 #include "base/threading/thread_checker.h"
21 #include "build/build_config.h"
22 #include "net/base/net_errors.h"
23 #include "net/proxy_resolution/proxy_chain_util_apple.h"
24 #include "net/proxy_resolution/proxy_info.h"
25 #include "net/proxy_resolution/proxy_list.h"
26 #include "net/proxy_resolution/proxy_resolver.h"
27 #include "url/gurl.h"
28 
29 #if BUILDFLAG(IS_IOS)
30 #include <CFNetwork/CFProxySupport.h>
31 #else
32 #include <CoreServices/CoreServices.h>
33 #endif
34 
35 #if LEAK_SANITIZER
36 #include <sanitizer/lsan_interface.h>
37 #endif
38 
39 namespace net {
40 
41 class NetworkAnonymizationKey;
42 
43 namespace {
44 
45 // A lock shared by all ProxyResolverApple instances. It is used to synchronize
46 // the events of multiple CFNetworkExecuteProxyAutoConfigurationURL run loop
47 // sources. These events are:
48 // 1. Adding the source to the run loop.
49 // 2. Handling the source result.
50 // 3. Removing the source from the run loop.
51 static base::LazyInstance<base::Lock>::Leaky g_cfnetwork_pac_runloop_lock =
52     LAZY_INSTANCE_INITIALIZER;
53 
54 // Forward declaration of the callback function used by the
55 // SynchronizedRunLoopObserver class.
56 void RunLoopObserverCallBackFunc(CFRunLoopObserverRef observer,
57                                  CFRunLoopActivity activity,
58                                  void* info);
59 
60 // Callback for CFNetworkExecuteProxyAutoConfigurationURL. |client| is a pointer
61 // to a CFTypeRef.  This stashes either |error| or |proxies| in that location.
ResultCallback(void * client,CFArrayRef proxies,CFErrorRef error)62 void ResultCallback(void* client, CFArrayRef proxies, CFErrorRef error) {
63   DCHECK((proxies != nullptr) == (error == nullptr));
64 
65   CFTypeRef* result_ptr = reinterpret_cast<CFTypeRef*>(client);
66   DCHECK(result_ptr != nullptr);
67   DCHECK(*result_ptr == nullptr);
68 
69   if (error != nullptr) {
70     *result_ptr = CFRetain(error);
71   } else {
72     *result_ptr = CFRetain(proxies);
73   }
74   CFRunLoopStop(CFRunLoopGetCurrent());
75 }
76 
77 #pragma mark - SynchronizedRunLoopObserver
78 // A run loop observer that guarantees that no two run loop sources protected
79 // by the same lock will be fired concurrently in different threads.
80 // The observer does not prevent the parallel execution of the sources but only
81 // synchronizes the run loop events associated with the sources. In the context
82 // of proxy resolver, the observer is used to synchronize the execution of the
83 // callbacks function that handles the result of
84 // CFNetworkExecuteProxyAutoConfigurationURL execution.
85 class SynchronizedRunLoopObserver final {
86  public:
87   // Creates the instance of an observer that will synchronize the sources
88   // using a given |lock|.
89   SynchronizedRunLoopObserver(base::Lock& lock);
90 
91   SynchronizedRunLoopObserver(const SynchronizedRunLoopObserver&) = delete;
92   SynchronizedRunLoopObserver& operator=(const SynchronizedRunLoopObserver&) =
93       delete;
94 
95   // Destructor.
96   ~SynchronizedRunLoopObserver();
97   // Adds the observer to the current run loop for a given run loop mode.
98   // This method should always be paired with |RemoveFromCurrentRunLoop|.
99   void AddToCurrentRunLoop(const CFStringRef mode);
100   // Removes the observer from the current run loop for a given run loop mode.
101   // This method should always be paired with |AddToCurrentRunLoop|.
102   void RemoveFromCurrentRunLoop(const CFStringRef mode);
103   // Callback function that is called when an observable run loop event occurs.
104   void RunLoopObserverCallBack(CFRunLoopObserverRef observer,
105                                CFRunLoopActivity activity);
106 
107  private:
108   // Lock to use to synchronize the run loop sources.
109   const raw_ref<base::Lock> lock_;
110   // Indicates whether the current observer holds the lock. It is used to
111   // avoid double locking and releasing.
112   bool lock_acquired_ = false;
113   // The underlying CFRunLoopObserverRef structure wrapped by this instance.
114   base::apple::ScopedCFTypeRef<CFRunLoopObserverRef> observer_;
115   // Validates that all methods of this class are executed on the same thread.
116   base::ThreadChecker thread_checker_;
117 };
118 
SynchronizedRunLoopObserver(base::Lock & lock)119 SynchronizedRunLoopObserver::SynchronizedRunLoopObserver(base::Lock& lock)
120     : lock_(lock) {
121   CFRunLoopObserverContext observer_context = {0, this, nullptr, nullptr,
122                                                nullptr};
123   observer_.reset(CFRunLoopObserverCreate(
124       kCFAllocatorDefault,
125       kCFRunLoopBeforeSources | kCFRunLoopBeforeWaiting | kCFRunLoopExit, true,
126       0, RunLoopObserverCallBackFunc, &observer_context));
127 }
128 
~SynchronizedRunLoopObserver()129 SynchronizedRunLoopObserver::~SynchronizedRunLoopObserver() {
130   DCHECK(thread_checker_.CalledOnValidThread());
131   DCHECK(!lock_acquired_);
132 }
133 
AddToCurrentRunLoop(const CFStringRef mode)134 void SynchronizedRunLoopObserver::AddToCurrentRunLoop(const CFStringRef mode) {
135   DCHECK(thread_checker_.CalledOnValidThread());
136   CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer_.get(), mode);
137 }
138 
RemoveFromCurrentRunLoop(const CFStringRef mode)139 void SynchronizedRunLoopObserver::RemoveFromCurrentRunLoop(
140     const CFStringRef mode) {
141   DCHECK(thread_checker_.CalledOnValidThread());
142   CFRunLoopRemoveObserver(CFRunLoopGetCurrent(), observer_.get(), mode);
143 }
144 
RunLoopObserverCallBack(CFRunLoopObserverRef observer,CFRunLoopActivity activity)145 void SynchronizedRunLoopObserver::RunLoopObserverCallBack(
146     CFRunLoopObserverRef observer,
147     CFRunLoopActivity activity) NO_THREAD_SAFETY_ANALYSIS {
148   DCHECK(thread_checker_.CalledOnValidThread());
149   // Acquire the lock when a source has been signaled and going to be fired.
150   // In the context of the proxy resolver that happens when the proxy for a
151   // given URL has been resolved and the callback function that handles the
152   // result is going to be fired.
153   // Release the lock when all source events have been handled.
154   //
155   // NO_THREAD_SAFETY_ANALYSIS: Runtime dependent locking.
156   switch (activity) {
157     case kCFRunLoopBeforeSources:
158       if (!lock_acquired_) {
159         lock_->Acquire();
160         lock_acquired_ = true;
161       }
162       break;
163     case kCFRunLoopBeforeWaiting:
164     case kCFRunLoopExit:
165       if (lock_acquired_) {
166         lock_acquired_ = false;
167         lock_->Release();
168       }
169       break;
170   }
171 }
172 
RunLoopObserverCallBackFunc(CFRunLoopObserverRef observer,CFRunLoopActivity activity,void * info)173 void RunLoopObserverCallBackFunc(CFRunLoopObserverRef observer,
174                                  CFRunLoopActivity activity,
175                                  void* info) {
176   // Forward the call to the instance of SynchronizedRunLoopObserver
177   // that is associated with the current CF run loop observer.
178   SynchronizedRunLoopObserver* observerInstance =
179       (SynchronizedRunLoopObserver*)info;
180   observerInstance->RunLoopObserverCallBack(observer, activity);
181 }
182 
183 #pragma mark - ProxyResolverApple
184 class ProxyResolverApple : public ProxyResolver {
185  public:
186   explicit ProxyResolverApple(const scoped_refptr<PacFileData>& script_data);
187   ~ProxyResolverApple() override;
188 
189   // ProxyResolver methods:
190   int GetProxyForURL(const GURL& url,
191                      const NetworkAnonymizationKey& network_anonymization_key,
192                      ProxyInfo* results,
193                      CompletionOnceCallback callback,
194                      std::unique_ptr<Request>* request,
195                      const NetLogWithSource& net_log) override;
196 
197  private:
198   const scoped_refptr<PacFileData> script_data_;
199 };
200 
ProxyResolverApple(const scoped_refptr<PacFileData> & script_data)201 ProxyResolverApple::ProxyResolverApple(
202     const scoped_refptr<PacFileData>& script_data)
203     : script_data_(script_data) {}
204 
205 ProxyResolverApple::~ProxyResolverApple() = default;
206 
207 // Gets the proxy information for a query URL from a PAC. Implementation
208 // inspired by http://developer.apple.com/samplecode/CFProxySupportTool/
GetProxyForURL(const GURL & query_url,const NetworkAnonymizationKey & network_anonymization_key,ProxyInfo * results,CompletionOnceCallback,std::unique_ptr<Request> *,const NetLogWithSource & net_log)209 int ProxyResolverApple::GetProxyForURL(
210     const GURL& query_url,
211     const NetworkAnonymizationKey& network_anonymization_key,
212     ProxyInfo* results,
213     CompletionOnceCallback /*callback*/,
214     std::unique_ptr<Request>* /*request*/,
215     const NetLogWithSource& net_log) {
216   // OS X's system resolver does not support WebSocket URLs in proxy.pac, as of
217   // version 10.13.5. See https://crbug.com/862121.
218   GURL mutable_query_url = query_url;
219   if (query_url.SchemeIsWSOrWSS()) {
220     GURL::Replacements replacements;
221     replacements.SetSchemeStr(query_url.SchemeIsCryptographic() ? "https"
222                                                                 : "http");
223     mutable_query_url = query_url.ReplaceComponents(replacements);
224   }
225 
226   base::apple::ScopedCFTypeRef<CFStringRef> query_ref(
227       base::SysUTF8ToCFStringRef(mutable_query_url.spec()));
228   base::apple::ScopedCFTypeRef<CFURLRef> query_url_ref(
229       CFURLCreateWithString(kCFAllocatorDefault, query_ref.get(), nullptr));
230   if (!query_url_ref.get())
231     return ERR_FAILED;
232   base::apple::ScopedCFTypeRef<CFStringRef> pac_ref(base::SysUTF8ToCFStringRef(
233       script_data_->type() == PacFileData::TYPE_AUTO_DETECT
234           ? std::string()
235           : script_data_->url().spec()));
236   base::apple::ScopedCFTypeRef<CFURLRef> pac_url_ref(
237       CFURLCreateWithString(kCFAllocatorDefault, pac_ref.get(), nullptr));
238   if (!pac_url_ref.get())
239     return ERR_FAILED;
240 
241   // Work around <rdar://problem/5530166>. This dummy call to
242   // CFNetworkCopyProxiesForURL initializes some state within CFNetwork that is
243   // required by CFNetworkExecuteProxyAutoConfigurationURL.
244 
245   base::apple::ScopedCFTypeRef<CFDictionaryRef> empty_dictionary(
246       CFDictionaryCreate(nullptr, nullptr, nullptr, 0, nullptr, nullptr));
247   base::apple::ScopedCFTypeRef<CFArrayRef> dummy_result(
248       CFNetworkCopyProxiesForURL(query_url_ref.get(), empty_dictionary.get()));
249 
250   // We cheat here. We need to act as if we were synchronous, so we pump the
251   // runloop ourselves. Our caller moved us to a new thread anyway, so this is
252   // OK to do. (BTW, CFNetworkExecuteProxyAutoConfigurationURL returns a
253   // runloop source we need to release despite its name.)
254 
255   CFTypeRef result = nullptr;
256   CFStreamClientContext context = {0, &result, nullptr, nullptr, nullptr};
257   base::apple::ScopedCFTypeRef<CFRunLoopSourceRef> runloop_source(
258       CFNetworkExecuteProxyAutoConfigurationURL(
259           pac_url_ref.get(), query_url_ref.get(), ResultCallback, &context));
260 #if LEAK_SANITIZER
261   // CFNetworkExecuteProxyAutoConfigurationURL leaks the returned
262   // CFRunLoopSourceRef. Filed as FB12170226.
263   __lsan_ignore_object(runloop_source.get());
264 #endif
265   if (!runloop_source)
266     return ERR_FAILED;
267 
268   const CFStringRef private_runloop_mode =
269       CFSTR("org.chromium.ProxyResolverApple");
270 
271   // Add the run loop observer to synchronize events of
272   // CFNetworkExecuteProxyAutoConfigurationURL sources. See the definition of
273   // |g_cfnetwork_pac_runloop_lock|.
274   SynchronizedRunLoopObserver observer(g_cfnetwork_pac_runloop_lock.Get());
275   observer.AddToCurrentRunLoop(private_runloop_mode);
276 
277   // Make sure that no CFNetworkExecuteProxyAutoConfigurationURL sources
278   // are added to the run loop concurrently.
279   {
280     base::AutoLock lock(g_cfnetwork_pac_runloop_lock.Get());
281     CFRunLoopAddSource(CFRunLoopGetCurrent(), runloop_source.get(),
282                        private_runloop_mode);
283   }
284 
285   CFRunLoopRunInMode(private_runloop_mode, DBL_MAX, false);
286 
287   // Make sure that no CFNetworkExecuteProxyAutoConfigurationURL sources
288   // are removed from the run loop concurrently.
289   {
290     base::AutoLock lock(g_cfnetwork_pac_runloop_lock.Get());
291     CFRunLoopRemoveSource(CFRunLoopGetCurrent(), runloop_source.get(),
292                           private_runloop_mode);
293   }
294   observer.RemoveFromCurrentRunLoop(private_runloop_mode);
295 
296   DCHECK(result);
297 
298   if (CFGetTypeID(result) == CFErrorGetTypeID()) {
299     // TODO(avi): do something better than this
300     CFRelease(result);
301     return ERR_FAILED;
302   }
303   base::apple::ScopedCFTypeRef<CFArrayRef> proxy_array_ref(
304       base::apple::CFCastStrict<CFArrayRef>(result));
305   DCHECK(proxy_array_ref);
306 
307   ProxyList proxy_list;
308 
309   CFIndex proxy_array_count = CFArrayGetCount(proxy_array_ref.get());
310   for (CFIndex i = 0; i < proxy_array_count; ++i) {
311     CFDictionaryRef proxy_dictionary =
312         base::apple::CFCastStrict<CFDictionaryRef>(
313             CFArrayGetValueAtIndex(proxy_array_ref.get(), i));
314     DCHECK(proxy_dictionary);
315 
316     // The dictionary may have the following keys:
317     // - kCFProxyTypeKey : The type of the proxy
318     // - kCFProxyHostNameKey
319     // - kCFProxyPortNumberKey : The meat we're after.
320     // - kCFProxyUsernameKey
321     // - kCFProxyPasswordKey : Despite the existence of these keys in the
322     //                         documentation, they're never populated. Even if a
323     //                         username/password were to be set in the network
324     //                         proxy system preferences, we'd need to fetch it
325     //                         from the Keychain ourselves. CFProxy is such a
326     //                         tease.
327     // - kCFProxyAutoConfigurationURLKey : If the PAC file specifies another
328     //                                     PAC file, I'm going home.
329 
330     CFStringRef proxy_type = base::apple::GetValueFromDictionary<CFStringRef>(
331         proxy_dictionary, kCFProxyTypeKey);
332     ProxyChain proxy_chain =
333         ProxyDictionaryToProxyChain(proxy_type, proxy_dictionary,
334                                     kCFProxyHostNameKey, kCFProxyPortNumberKey);
335     if (!proxy_chain.IsValid()) {
336       continue;
337     }
338 
339     proxy_list.AddProxyChain(proxy_chain);
340   }
341 
342   if (!proxy_list.IsEmpty())
343     results->UseProxyList(proxy_list);
344   // Else do nothing (results is already guaranteed to be in the default state).
345 
346   return OK;
347 }
348 
349 }  // namespace
350 
ProxyResolverFactoryApple()351 ProxyResolverFactoryApple::ProxyResolverFactoryApple()
352     : ProxyResolverFactory(false /*expects_pac_bytes*/) {
353 }
354 
CreateProxyResolver(const scoped_refptr<PacFileData> & pac_script,std::unique_ptr<ProxyResolver> * resolver,CompletionOnceCallback callback,std::unique_ptr<Request> * request)355 int ProxyResolverFactoryApple::CreateProxyResolver(
356     const scoped_refptr<PacFileData>& pac_script,
357     std::unique_ptr<ProxyResolver>* resolver,
358     CompletionOnceCallback callback,
359     std::unique_ptr<Request>* request) {
360   *resolver = std::make_unique<ProxyResolverApple>(pac_script);
361   return OK;
362 }
363 
364 }  // namespace net
365