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