// 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/strings/string_util.h"
#include "base/strings/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::ScopedCFTypeRef<CFStringRef> query_ref(
base::SysUTF8ToCFStringRef(query_url.spec()));
base::ScopedCFTypeRef<CFURLRef> query_url_ref(
CFURLCreateWithString(kCFAllocatorDefault, query_ref.get(), NULL));
if (!query_url_ref.get())
return ERR_FAILED;
base::ScopedCFTypeRef<CFStringRef> pac_ref(base::SysUTF8ToCFStringRef(
script_data_->type() == ProxyResolverScriptData::TYPE_AUTO_DETECT
? std::string()
: script_data_->url().spec()));
base::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::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::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