Home | History | Annotate | Download | only in proxy
      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.
     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.
     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 
     56 ProxyResolverMac::ProxyResolverMac()
     57     : ProxyResolver(false /*expects_pac_bytes*/) {
     58 }
     59 
     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/
     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 
    180 void ProxyResolverMac::CancelRequest(RequestHandle request) {
    181   NOTREACHED();
    182 }
    183 
    184 void ProxyResolverMac::CancelSetPacScript() {
    185   NOTREACHED();
    186 }
    187 
    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