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