text: // Copyright (c) 2011 The Chromium Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include "net/proxy/proxy_resolver_mac.h"
  
  #include <CoreFoundation/CoreFoundation.h>
  
  #include "base/logging.h"
  #include "base/mac/foundation_util.h"
  #include "base/mac/scoped_cftyperef.h"
  #include "base/string_util.h"
  #include "base/sys_string_conversions.h"
  #include "net/base/net_errors.h"
  #include "net/proxy/proxy_info.h"
  #include "net/proxy/proxy_server.h"
  
  #if defined(OS_IOS)
  #include <CFNetwork/CFProxySupport.h>
  #else
  #include <CoreServices/CoreServices.h>
  #endif
  
  namespace {
  
  // Utility function to map a CFProxyType to a ProxyServer::Scheme.
  // If the type is unknown, returns ProxyServer::SCHEME_INVALID.
  net::ProxyServer::Scheme GetProxyServerScheme(CFStringRef proxy_type) {
    if (CFEqual(proxy_type, kCFProxyTypeNone))
      return net::ProxyServer::SCHEME_DIRECT;
    if (CFEqual(proxy_type, kCFProxyTypeHTTP))
      return net::ProxyServer::SCHEME_HTTP;
    if (CFEqual(proxy_type, kCFProxyTypeHTTPS)) {
      // The "HTTPS" on the Mac side here means "proxy applies to https://" URLs;
      // the proxy itself is still expected to be an HTTP proxy.
      return net::ProxyServer::SCHEME_HTTP;
    }
    if (CFEqual(proxy_type, kCFProxyTypeSOCKS)) {
      // We can't tell whether this was v4 or v5. We will assume it is
      // v5 since that is the only version OS X supports.
      return net::ProxyServer::SCHEME_SOCKS5;
    }
    return net::ProxyServer::SCHEME_INVALID;
  }
  
  // Callback for CFNetworkExecuteProxyAutoConfigurationURL. |client| is a pointer
  // to a CFTypeRef.  This stashes either |error| or |proxies| in that location.
  void ResultCallback(void* client, CFArrayRef proxies, CFErrorRef error) {
    DCHECK((proxies != NULL) == (error == NULL));
  
    CFTypeRef* result_ptr = reinterpret_cast<CFTypeRef*>(client);
    DCHECK(result_ptr != NULL);
    DCHECK(*result_ptr == NULL);
  
    if (error != NULL) {
      *result_ptr = CFRetain(error);
    } else {
      *result_ptr = CFRetain(proxies);
    }
    CFRunLoopStop(CFRunLoopGetCurrent());
  }
  
  }  // namespace
  
  namespace net {
  
  ProxyResolverMac::ProxyResolverMac()
      : ProxyResolver(false /*expects_pac_bytes*/) {
  }
  
  ProxyResolverMac::~ProxyResolverMac() {}
  
  // Gets the proxy information for a query URL from a PAC. Implementation
  // inspired by http://developer.apple.com/samplecode/CFProxySupportTool/
  int ProxyResolverMac::GetProxyForURL(const GURL& query_url,
                                       ProxyInfo* results,
                                       const CompletionCallback& /*callback*/,
                                       RequestHandle* /*request*/,
                                       const BoundNetLog& net_log) {
    base::mac::ScopedCFTypeRef<CFStringRef> query_ref(
        base::SysUTF8ToCFStringRef(query_url.spec()));
    base::mac::ScopedCFTypeRef<CFURLRef> query_url_ref(
        CFURLCreateWithString(kCFAllocatorDefault,
                              query_ref.get(),
                              NULL));
    if (!query_url_ref.get())
      return ERR_FAILED;
    base::mac::ScopedCFTypeRef<CFStringRef> pac_ref(
        base::SysUTF8ToCFStringRef(
            script_data_->type() == ProxyResolverScriptData::TYPE_AUTO_DETECT ?
                std::string() : script_data_->url().spec()));
    base::mac::ScopedCFTypeRef<CFURLRef> pac_url_ref(
        CFURLCreateWithString(kCFAllocatorDefault,
                              pac_ref.get(),
                              NULL));
    if (!pac_url_ref.get())
      return ERR_FAILED;
  
    // Work around <rdar://problem/5530166>. This dummy call to
    // CFNetworkCopyProxiesForURL initializes some state within CFNetwork that is
    // required by CFNetworkExecuteProxyAutoConfigurationURL.
  
    CFArrayRef dummy_result = CFNetworkCopyProxiesForURL(query_url_ref.get(),
                                                         NULL);
    if (dummy_result)
      CFRelease(dummy_result);
  
    // We cheat here. We need to act as if we were synchronous, so we pump the
    // runloop ourselves. Our caller moved us to a new thread anyway, so this is
    // OK to do. (BTW, CFNetworkExecuteProxyAutoConfigurationURL returns a
    // runloop source we need to release despite its name.)
  
    CFTypeRef result = NULL;
    CFStreamClientContext context = { 0, &result, NULL, NULL, NULL };
    base::mac::ScopedCFTypeRef<CFRunLoopSourceRef> runloop_source(
        CFNetworkExecuteProxyAutoConfigurationURL(pac_url_ref.get(),
                                                  query_url_ref.get(),
                                                  ResultCallback,
                                                  &context));
    if (!runloop_source)
      return ERR_FAILED;
  
    const CFStringRef private_runloop_mode =
        CFSTR("org.chromium.ProxyResolverMac");
  
    CFRunLoopAddSource(CFRunLoopGetCurrent(), runloop_source.get(),
                       private_runloop_mode);
    CFRunLoopRunInMode(private_runloop_mode, DBL_MAX, false);
    CFRunLoopRemoveSource(CFRunLoopGetCurrent(), runloop_source.get(),
                          private_runloop_mode);
    DCHECK(result != NULL);
  
    if (CFGetTypeID(result) == CFErrorGetTypeID()) {
      // TODO(avi): do something better than this
      CFRelease(result);
      return ERR_FAILED;
    }
    base::mac::ScopedCFTypeRef<CFArrayRef> proxy_array_ref(
        base::mac::CFCastStrict<CFArrayRef>(result));
    DCHECK(proxy_array_ref != NULL);
  
    // This string will be an ordered list of <proxy-uri> entries, separated by
    // semi-colons. It is the format that ProxyInfo::UseNamedProxy() expects.
    //    proxy-uri = [<proxy-scheme>"://"]<proxy-host>":"<proxy-port>
    // (This also includes entries for direct connection, as "direct://").
    std::string proxy_uri_list;
  
    CFIndex proxy_array_count = CFArrayGetCount(proxy_array_ref.get());
    for (CFIndex i = 0; i < proxy_array_count; ++i) {
      CFDictionaryRef proxy_dictionary = base::mac::CFCastStrict<CFDictionaryRef>(
          CFArrayGetValueAtIndex(proxy_array_ref.get(), i));
      DCHECK(proxy_dictionary != NULL);
  
      // The dictionary may have the following keys:
      // - kCFProxyTypeKey : The type of the proxy
      // - kCFProxyHostNameKey
      // - kCFProxyPortNumberKey : The meat we're after.
      // - kCFProxyUsernameKey
      // - kCFProxyPasswordKey : Despite the existence of these keys in the
      //                         documentation, they're never populated. Even if a
      //                         username/password were to be set in the network
      //                         proxy system preferences, we'd need to fetch it
      //                         from the Keychain ourselves. CFProxy is such a
      //                         tease.
      // - kCFProxyAutoConfigurationURLKey : If the PAC file specifies another
      //                                     PAC file, I'm going home.
  
      CFStringRef proxy_type = base::mac::GetValueFromDictionary<CFStringRef>(
          proxy_dictionary, kCFProxyTypeKey);
      ProxyServer proxy_server = ProxyServer::FromDictionary(
          GetProxyServerScheme(proxy_type),
          proxy_dictionary,
          kCFProxyHostNameKey,
          kCFProxyPortNumberKey);
      if (!proxy_server.is_valid())
        continue;
  
      if (!proxy_uri_list.empty())
        proxy_uri_list += ";";
      proxy_uri_list += proxy_server.ToURI();
    }
  
    if (!proxy_uri_list.empty())
      results->UseNamedProxy(proxy_uri_list);
    // Else do nothing (results is already guaranteed to be in the default state).
  
    return OK;
  }
  
  void ProxyResolverMac::CancelRequest(RequestHandle request) {
    NOTREACHED();
  }
  
  LoadState ProxyResolverMac::GetLoadState(RequestHandle request) const {
    NOTREACHED();
    return LOAD_STATE_IDLE;
  }
  
  void ProxyResolverMac::CancelSetPacScript() {
    NOTREACHED();
  }
  
  int ProxyResolverMac::SetPacScript(
      const scoped_refptr<ProxyResolverScriptData>& script_data,
      const CompletionCallback& /*callback*/) {
    script_data_ = script_data;
    return OK;
  }
  
  }  // namespace net
  