Home | History | Annotate | Download | only in proxy
      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 
     24 namespace {
     25 
     26 // Utility function to map a CFProxyType to a ProxyServer::Scheme.
     27 // If the type is unknown, returns ProxyServer::SCHEME_INVALID.
     28 net::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.
     48 void 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 
     65 namespace net {
     66 
     67 ProxyResolverMac::ProxyResolverMac()
     68     : ProxyResolver(false /*expects_pac_bytes*/) {
     69 }
     70 
     71 ProxyResolverMac::~ProxyResolverMac() {}
     72 
     73 // Gets the proxy information for a query URL from a PAC. Implementation
     74 // inspired by http://developer.apple.com/samplecode/CFProxySupportTool/
     75 int 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 
    184 void ProxyResolverMac::CancelRequest(RequestHandle request) {
    185   NOTREACHED();
    186 }
    187 
    188 LoadState ProxyResolverMac::GetLoadState(RequestHandle request) const {
    189   NOTREACHED();
    190   return LOAD_STATE_IDLE;
    191 }
    192 
    193 void ProxyResolverMac::CancelSetPacScript() {
    194   NOTREACHED();
    195 }
    196 
    197 int 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