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
18namespace {
19
20// Utility function to map a CFProxyType to a ProxyServer::Scheme.
21// If the type is unknown, returns ProxyServer::SCHEME_INVALID.
22net::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.
37void 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
54namespace net {
55
56ProxyResolverMac::ProxyResolverMac()
57    : ProxyResolver(false /*expects_pac_bytes*/) {
58}
59
60ProxyResolverMac::~ProxyResolverMac() {}
61
62// Gets the proxy information for a query URL from a PAC. Implementation
63// inspired by http://developer.apple.com/samplecode/CFProxySupportTool/
64int 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
180void ProxyResolverMac::CancelRequest(RequestHandle request) {
181  NOTREACHED();
182}
183
184void ProxyResolverMac::CancelSetPacScript() {
185  NOTREACHED();
186}
187
188int 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