1 // Copyright (c) 2008 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.
4
5 #include "net/proxy/proxy_resolver_mac.h"
6
7 #include <CoreFoundation/CoreFoundation.h>
8
9 #include "base/logging.h"
10 #include "base/mac/mac_util.h"
11 #include "base/mac/scoped_cftyperef.h"
12 #include "base/string_util.h"
13 #include "base/sys_string_conversions.h"
14 #include "net/base/net_errors.h"
15 #include "net/proxy/proxy_info.h"
16 #include "net/proxy/proxy_server.h"
17
18 namespace {
19
20 // Utility function to map a CFProxyType to a ProxyServer::Scheme.
21 // If the type is unknown, returns ProxyServer::SCHEME_INVALID.
GetProxyServerScheme(CFStringRef proxy_type)22 net::ProxyServer::Scheme GetProxyServerScheme(CFStringRef proxy_type) {
23 if (CFEqual(proxy_type, kCFProxyTypeNone))
24 return net::ProxyServer::SCHEME_DIRECT;
25 if (CFEqual(proxy_type, kCFProxyTypeHTTP))
26 return net::ProxyServer::SCHEME_HTTP;
27 if (CFEqual(proxy_type, kCFProxyTypeSOCKS)) {
28 // We can't tell whether this was v4 or v5. We will assume it is
29 // v5 since that is the only version OS X supports.
30 return net::ProxyServer::SCHEME_SOCKS5;
31 }
32 return net::ProxyServer::SCHEME_INVALID;
33 }
34
35 // Callback for CFNetworkExecuteProxyAutoConfigurationURL. |client| is a pointer
36 // to a CFTypeRef. This stashes either |error| or |proxies| in that location.
ResultCallback(void * client,CFArrayRef proxies,CFErrorRef error)37 void ResultCallback(void* client, CFArrayRef proxies, CFErrorRef error) {
38 DCHECK((proxies != NULL) == (error == NULL));
39
40 CFTypeRef* result_ptr = reinterpret_cast<CFTypeRef*>(client);
41 DCHECK(result_ptr != NULL);
42 DCHECK(*result_ptr == NULL);
43
44 if (error != NULL) {
45 *result_ptr = CFRetain(error);
46 } else {
47 *result_ptr = CFRetain(proxies);
48 }
49 CFRunLoopStop(CFRunLoopGetCurrent());
50 }
51
52 } // namespace
53
54 namespace net {
55
ProxyResolverMac()56 ProxyResolverMac::ProxyResolverMac()
57 : ProxyResolver(false /*expects_pac_bytes*/) {
58 }
59
~ProxyResolverMac()60 ProxyResolverMac::~ProxyResolverMac() {}
61
62 // Gets the proxy information for a query URL from a PAC. Implementation
63 // inspired by http://developer.apple.com/samplecode/CFProxySupportTool/
GetProxyForURL(const GURL & query_url,ProxyInfo * results,CompletionCallback *,RequestHandle *,const BoundNetLog & net_log)64 int ProxyResolverMac::GetProxyForURL(const GURL& query_url,
65 ProxyInfo* results,
66 CompletionCallback* /*callback*/,
67 RequestHandle* /*request*/,
68 const BoundNetLog& net_log) {
69 base::mac::ScopedCFTypeRef<CFStringRef> query_ref(
70 base::SysUTF8ToCFStringRef(query_url.spec()));
71 base::mac::ScopedCFTypeRef<CFURLRef> query_url_ref(
72 CFURLCreateWithString(kCFAllocatorDefault,
73 query_ref.get(),
74 NULL));
75 if (!query_url_ref.get())
76 return ERR_FAILED;
77 base::mac::ScopedCFTypeRef<CFStringRef> pac_ref(
78 base::SysUTF8ToCFStringRef(
79 script_data_->type() == ProxyResolverScriptData::TYPE_AUTO_DETECT ?
80 std::string() : script_data_->url().spec()));
81 base::mac::ScopedCFTypeRef<CFURLRef> pac_url_ref(
82 CFURLCreateWithString(kCFAllocatorDefault,
83 pac_ref.get(),
84 NULL));
85 if (!pac_url_ref.get())
86 return ERR_FAILED;
87
88 // Work around <rdar://problem/5530166>. This dummy call to
89 // CFNetworkCopyProxiesForURL initializes some state within CFNetwork that is
90 // required by CFNetworkExecuteProxyAutoConfigurationURL.
91
92 CFArrayRef dummy_result = CFNetworkCopyProxiesForURL(query_url_ref.get(),
93 NULL);
94 if (dummy_result)
95 CFRelease(dummy_result);
96
97 // We cheat here. We need to act as if we were synchronous, so we pump the
98 // runloop ourselves. Our caller moved us to a new thread anyway, so this is
99 // OK to do. (BTW, CFNetworkExecuteProxyAutoConfigurationURL returns a
100 // runloop source we need to release despite its name.)
101
102 CFTypeRef result = NULL;
103 CFStreamClientContext context = { 0, &result, NULL, NULL, NULL };
104 base::mac::ScopedCFTypeRef<CFRunLoopSourceRef> runloop_source(
105 CFNetworkExecuteProxyAutoConfigurationURL(pac_url_ref.get(),
106 query_url_ref.get(),
107 ResultCallback,
108 &context));
109 if (!runloop_source)
110 return ERR_FAILED;
111
112 const CFStringRef private_runloop_mode =
113 CFSTR("org.chromium.ProxyResolverMac");
114
115 CFRunLoopAddSource(CFRunLoopGetCurrent(), runloop_source.get(),
116 private_runloop_mode);
117 CFRunLoopRunInMode(private_runloop_mode, DBL_MAX, false);
118 CFRunLoopRemoveSource(CFRunLoopGetCurrent(), runloop_source.get(),
119 private_runloop_mode);
120 DCHECK(result != NULL);
121
122 if (CFGetTypeID(result) == CFErrorGetTypeID()) {
123 // TODO(avi): do something better than this
124 CFRelease(result);
125 return ERR_FAILED;
126 }
127 DCHECK(CFGetTypeID(result) == CFArrayGetTypeID());
128 base::mac::ScopedCFTypeRef<CFArrayRef> proxy_array_ref((CFArrayRef)result);
129
130 // This string will be an ordered list of <proxy-uri> entries, separated by
131 // semi-colons. It is the format that ProxyInfo::UseNamedProxy() expects.
132 // proxy-uri = [<proxy-scheme>"://"]<proxy-host>":"<proxy-port>
133 // (This also includes entries for direct connection, as "direct://").
134 std::string proxy_uri_list;
135
136 CFIndex proxy_array_count = CFArrayGetCount(proxy_array_ref.get());
137 for (CFIndex i = 0; i < proxy_array_count; ++i) {
138 CFDictionaryRef proxy_dictionary =
139 (CFDictionaryRef)CFArrayGetValueAtIndex(proxy_array_ref.get(), i);
140 DCHECK(CFGetTypeID(proxy_dictionary) == CFDictionaryGetTypeID());
141
142 // The dictionary may have the following keys:
143 // - kCFProxyTypeKey : The type of the proxy
144 // - kCFProxyHostNameKey
145 // - kCFProxyPortNumberKey : The meat we're after.
146 // - kCFProxyUsernameKey
147 // - kCFProxyPasswordKey : Despite the existence of these keys in the
148 // documentation, they're never populated. Even if a
149 // username/password were to be set in the network
150 // proxy system preferences, we'd need to fetch it
151 // from the Keychain ourselves. CFProxy is such a
152 // tease.
153 // - kCFProxyAutoConfigurationURLKey : If the PAC file specifies another
154 // PAC file, I'm going home.
155
156 CFStringRef proxy_type =
157 (CFStringRef)base::mac::GetValueFromDictionary(proxy_dictionary,
158 kCFProxyTypeKey,
159 CFStringGetTypeID());
160 ProxyServer proxy_server = ProxyServer::FromDictionary(
161 GetProxyServerScheme(proxy_type),
162 proxy_dictionary,
163 kCFProxyHostNameKey,
164 kCFProxyPortNumberKey);
165 if (!proxy_server.is_valid())
166 continue;
167
168 if (!proxy_uri_list.empty())
169 proxy_uri_list += ";";
170 proxy_uri_list += proxy_server.ToURI();
171 }
172
173 if (!proxy_uri_list.empty())
174 results->UseNamedProxy(proxy_uri_list);
175 // Else do nothing (results is already guaranteed to be in the default state).
176
177 return OK;
178 }
179
CancelRequest(RequestHandle request)180 void ProxyResolverMac::CancelRequest(RequestHandle request) {
181 NOTREACHED();
182 }
183
CancelSetPacScript()184 void ProxyResolverMac::CancelSetPacScript() {
185 NOTREACHED();
186 }
187
SetPacScript(const scoped_refptr<ProxyResolverScriptData> & script_data,CompletionCallback *)188 int ProxyResolverMac::SetPacScript(
189 const scoped_refptr<ProxyResolverScriptData>& script_data,
190 CompletionCallback* /*callback*/) {
191 script_data_ = script_data;
192 return OK;
193 }
194
195 } // namespace net
196