1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "net/proxy/proxy_resolver_mac.h"
7 #include <CoreFoundation/CoreFoundation.h>
9 #include "base/logging.h"
10 #include "base/mac/foundation_util.h"
11 #include "base/mac/scoped_cftyperef.h"
12 #include "base/strings/string_util.h"
13 #include "base/strings/sys_string_conversions.h"
14 #include "net/base/net_errors.h"
15 #include "net/proxy/proxy_info.h"
16 #include "net/proxy/proxy_resolver.h"
17 #include "net/proxy/proxy_server.h"
20 #include <CFNetwork/CFProxySupport.h>
22 #include <CoreServices/CoreServices.h>
29 // Utility function to map a CFProxyType to a ProxyServer::Scheme.
30 // If the type is unknown, returns ProxyServer::SCHEME_INVALID.
31 ProxyServer::Scheme
GetProxyServerScheme(CFStringRef proxy_type
) {
32 if (CFEqual(proxy_type
, kCFProxyTypeNone
))
33 return ProxyServer::SCHEME_DIRECT
;
34 if (CFEqual(proxy_type
, kCFProxyTypeHTTP
))
35 return ProxyServer::SCHEME_HTTP
;
36 if (CFEqual(proxy_type
, kCFProxyTypeHTTPS
)) {
37 // The "HTTPS" on the Mac side here means "proxy applies to https://" URLs;
38 // the proxy itself is still expected to be an HTTP proxy.
39 return ProxyServer::SCHEME_HTTP
;
41 if (CFEqual(proxy_type
, kCFProxyTypeSOCKS
)) {
42 // We can't tell whether this was v4 or v5. We will assume it is
43 // v5 since that is the only version OS X supports.
44 return ProxyServer::SCHEME_SOCKS5
;
46 return ProxyServer::SCHEME_INVALID
;
49 // Callback for CFNetworkExecuteProxyAutoConfigurationURL. |client| is a pointer
50 // to a CFTypeRef. This stashes either |error| or |proxies| in that location.
51 void ResultCallback(void* client
, CFArrayRef proxies
, CFErrorRef error
) {
52 DCHECK((proxies
!= NULL
) == (error
== NULL
));
54 CFTypeRef
* result_ptr
= reinterpret_cast<CFTypeRef
*>(client
);
55 DCHECK(result_ptr
!= NULL
);
56 DCHECK(*result_ptr
== NULL
);
59 *result_ptr
= CFRetain(error
);
61 *result_ptr
= CFRetain(proxies
);
63 CFRunLoopStop(CFRunLoopGetCurrent());
66 class ProxyResolverMac
: public ProxyResolver
{
68 explicit ProxyResolverMac(
69 const scoped_refptr
<ProxyResolverScriptData
>& script_data
);
70 ~ProxyResolverMac() override
;
72 // ProxyResolver methods:
73 int GetProxyForURL(const GURL
& url
,
75 const CompletionCallback
& callback
,
76 RequestHandle
* request
,
77 const BoundNetLog
& net_log
) override
;
79 void CancelRequest(RequestHandle request
) override
;
81 LoadState
GetLoadState(RequestHandle request
) const override
;
84 const scoped_refptr
<ProxyResolverScriptData
> script_data_
;
87 ProxyResolverMac::ProxyResolverMac(
88 const scoped_refptr
<ProxyResolverScriptData
>& script_data
)
89 : script_data_(script_data
) {
92 ProxyResolverMac::~ProxyResolverMac() {}
94 // Gets the proxy information for a query URL from a PAC. Implementation
95 // inspired by http://developer.apple.com/samplecode/CFProxySupportTool/
96 int ProxyResolverMac::GetProxyForURL(const GURL
& query_url
,
98 const CompletionCallback
& /*callback*/,
99 RequestHandle
* /*request*/,
100 const BoundNetLog
& net_log
) {
101 base::ScopedCFTypeRef
<CFStringRef
> query_ref(
102 base::SysUTF8ToCFStringRef(query_url
.spec()));
103 base::ScopedCFTypeRef
<CFURLRef
> query_url_ref(
104 CFURLCreateWithString(kCFAllocatorDefault
, query_ref
.get(), NULL
));
105 if (!query_url_ref
.get())
107 base::ScopedCFTypeRef
<CFStringRef
> pac_ref(base::SysUTF8ToCFStringRef(
108 script_data_
->type() == ProxyResolverScriptData::TYPE_AUTO_DETECT
110 : script_data_
->url().spec()));
111 base::ScopedCFTypeRef
<CFURLRef
> pac_url_ref(
112 CFURLCreateWithString(kCFAllocatorDefault
, pac_ref
.get(), NULL
));
113 if (!pac_url_ref
.get())
116 // Work around <rdar://problem/5530166>. This dummy call to
117 // CFNetworkCopyProxiesForURL initializes some state within CFNetwork that is
118 // required by CFNetworkExecuteProxyAutoConfigurationURL.
120 base::ScopedCFTypeRef
<CFDictionaryRef
> empty_dictionary(
121 CFDictionaryCreate(NULL
, NULL
, NULL
, 0, NULL
, NULL
));
122 CFArrayRef dummy_result
=
123 CFNetworkCopyProxiesForURL(query_url_ref
.get(), empty_dictionary
);
125 CFRelease(dummy_result
);
127 // We cheat here. We need to act as if we were synchronous, so we pump the
128 // runloop ourselves. Our caller moved us to a new thread anyway, so this is
129 // OK to do. (BTW, CFNetworkExecuteProxyAutoConfigurationURL returns a
130 // runloop source we need to release despite its name.)
132 CFTypeRef result
= NULL
;
133 CFStreamClientContext context
= { 0, &result
, NULL
, NULL
, NULL
};
134 base::ScopedCFTypeRef
<CFRunLoopSourceRef
> runloop_source(
135 CFNetworkExecuteProxyAutoConfigurationURL(
136 pac_url_ref
.get(), query_url_ref
.get(), ResultCallback
, &context
));
140 const CFStringRef private_runloop_mode
=
141 CFSTR("org.chromium.ProxyResolverMac");
143 CFRunLoopAddSource(CFRunLoopGetCurrent(), runloop_source
.get(),
144 private_runloop_mode
);
145 CFRunLoopRunInMode(private_runloop_mode
, DBL_MAX
, false);
146 CFRunLoopSourceInvalidate(runloop_source
.get());
147 DCHECK(result
!= NULL
);
149 if (CFGetTypeID(result
) == CFErrorGetTypeID()) {
150 // TODO(avi): do something better than this
154 base::ScopedCFTypeRef
<CFArrayRef
> proxy_array_ref(
155 base::mac::CFCastStrict
<CFArrayRef
>(result
));
156 DCHECK(proxy_array_ref
!= NULL
);
158 // This string will be an ordered list of <proxy-uri> entries, separated by
159 // semi-colons. It is the format that ProxyInfo::UseNamedProxy() expects.
160 // proxy-uri = [<proxy-scheme>"://"]<proxy-host>":"<proxy-port>
161 // (This also includes entries for direct connection, as "direct://").
162 std::string proxy_uri_list
;
164 CFIndex proxy_array_count
= CFArrayGetCount(proxy_array_ref
.get());
165 for (CFIndex i
= 0; i
< proxy_array_count
; ++i
) {
166 CFDictionaryRef proxy_dictionary
= base::mac::CFCastStrict
<CFDictionaryRef
>(
167 CFArrayGetValueAtIndex(proxy_array_ref
.get(), i
));
168 DCHECK(proxy_dictionary
!= NULL
);
170 // The dictionary may have the following keys:
171 // - kCFProxyTypeKey : The type of the proxy
172 // - kCFProxyHostNameKey
173 // - kCFProxyPortNumberKey : The meat we're after.
174 // - kCFProxyUsernameKey
175 // - kCFProxyPasswordKey : Despite the existence of these keys in the
176 // documentation, they're never populated. Even if a
177 // username/password were to be set in the network
178 // proxy system preferences, we'd need to fetch it
179 // from the Keychain ourselves. CFProxy is such a
181 // - kCFProxyAutoConfigurationURLKey : If the PAC file specifies another
182 // PAC file, I'm going home.
184 CFStringRef proxy_type
= base::mac::GetValueFromDictionary
<CFStringRef
>(
185 proxy_dictionary
, kCFProxyTypeKey
);
186 ProxyServer proxy_server
= ProxyServer::FromDictionary(
187 GetProxyServerScheme(proxy_type
),
190 kCFProxyPortNumberKey
);
191 if (!proxy_server
.is_valid())
194 if (!proxy_uri_list
.empty())
195 proxy_uri_list
+= ";";
196 proxy_uri_list
+= proxy_server
.ToURI();
199 if (!proxy_uri_list
.empty())
200 results
->UseNamedProxy(proxy_uri_list
);
201 // Else do nothing (results is already guaranteed to be in the default state).
206 void ProxyResolverMac::CancelRequest(RequestHandle request
) {
210 LoadState
ProxyResolverMac::GetLoadState(RequestHandle request
) const {
212 return LOAD_STATE_IDLE
;
217 ProxyResolverFactoryMac::ProxyResolverFactoryMac()
218 : ProxyResolverFactory(false /*expects_pac_bytes*/) {
221 int ProxyResolverFactoryMac::CreateProxyResolver(
222 const scoped_refptr
<ProxyResolverScriptData
>& pac_script
,
223 scoped_ptr
<ProxyResolver
>* resolver
,
224 const CompletionCallback
& callback
,
225 scoped_ptr
<Request
>* request
) {
226 resolver
->reset(new ProxyResolverMac(pac_script
));