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