blob: 7977d5d5493f9ac28ddb59c983c4e470fce116fb [file] [log] [blame]
// Copyright 2010-2015, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "net/proxy_manager.h"
#include <string>
#ifdef OS_MACOSX
#include <CoreServices/CoreServices.h>
#include <SystemConfiguration/SystemConfiguration.h>
#include "base/scoped_cftyperef.h"
#include "base/mac_util.h"
#endif
#include "base/port.h"
#include "base/logging.h"
#include "base/singleton.h"
namespace mozc {
#ifdef OS_MACOSX
namespace {
// Helper functions for proxy configuration for Mac.
// Callback for CFNetworkExecuteProxyAutoConfigurationURL. client is a
// pointer to a CFTypeRef. This stashes either error or proxies in that
// location.
// The implementation is inspired by
// http://developer.apple.com/samplecode/CFProxySupportTool/
static void PACResultCallback(
void * client, CFArrayRef proxies, CFErrorRef error) {
DCHECK((proxies == NULL && error != NULL) ||
(proxies != NULL && error == NULL));
CFTypeRef *result = static_cast<CFTypeRef *>(client);
if (result != NULL && *result == NULL) {
if (error != NULL) {
*result = CFRetain(error);
} else {
*result = CFRetain(proxies);
}
}
CFRunLoopStop(CFRunLoopGetCurrent());
}
// If the specified proxy is PAC, it fetches the PAC file using
// CFNetworkExecuteProxyAutoConfigurationURL() and apply it to cfurl,
// and then returns the actual proxy configuration dictionary. Otherwise,
// it just retains the specified proxy and then returns it.
// The implementation is inspired by
// http://developer.apple.com/samplecode/CFProxySupportTool/
CFDictionaryRef RetainOrExpandPacFile(CFURLRef cfurl, CFDictionaryRef proxy) {
CFDictionaryRef final_proxy = NULL;
CFStringRef proxy_type = reinterpret_cast<CFStringRef>(
CFDictionaryGetValue(proxy, kCFProxyTypeKey));
// PAC file case. Currently it always call
// CFNetworkExecuteProxyAutoConfigurationURL() so it always ask to
// the pac URL server. However, it seems that the function seems to
// take care of caching in memory. So there are no additional
// latency problems here.
if (proxy_type != NULL && CFGetTypeID(proxy_type) == CFStringGetTypeID() &&
CFEqual(proxy_type, kCFProxyTypeAutoConfigurationURL)) {
CFURLRef script_url = reinterpret_cast<CFURLRef>(
CFDictionaryGetValue(proxy, kCFProxyAutoConfigurationURLKey));
if (script_url != NULL && CFGetTypeID(script_url) == CFURLGetTypeID()) {
CFTypeRef result = NULL;
CFStreamClientContext context = {0, &result, NULL, NULL, NULL};
scoped_cftyperef<CFRunLoopSourceRef> runloop_source(
CFNetworkExecuteProxyAutoConfigurationURL(
script_url, cfurl, PACResultCallback, &context));
const string label = MacUtil::GetLabelForSuffix("ProxyResolverMac");
scoped_cftyperef<CFStringRef> private_runloop_mode(
CFStringCreateWithBytes(
NULL, reinterpret_cast<const UInt8 *>(label.data()),
label.size(), kCFStringEncodingUTF8, NULL));
CFRunLoopAddSource(
CFRunLoopGetCurrent(), runloop_source.get(),
private_runloop_mode.get());
CFRunLoopRunInMode(private_runloop_mode.get(), 1.0e10, false);
CFRunLoopRemoveSource(
CFRunLoopGetCurrent(), runloop_source.get(),
private_runloop_mode.get());
// resolving PAC succeeds
if (result != NULL && CFGetTypeID(result) == CFArrayGetTypeID() &&
CFArrayGetCount(reinterpret_cast<CFArrayRef>(result)) > 0) {
final_proxy = reinterpret_cast<CFDictionaryRef>(
CFArrayGetValueAtIndex(reinterpret_cast<CFArrayRef>(result), 0));
CFRetain(final_proxy);
} else {
LOG(WARNING) << "Failed to resolve PAC file. "
<< "Possibly wrong PAC file is specified.";
}
if (result != NULL) {
CFRelease(result);
}
}
}
// If configuration isn't PAC or resolving PAC fails, just returns
// the retained proxy.
if (final_proxy == NULL) {
final_proxy = reinterpret_cast<CFDictionaryRef>(CFRetain(proxy));
}
return final_proxy;
}
} // anonymous namespace
// MacProxyManager is a proxy manager for Mac OSX. It uses
// CoreService API and SystemConfiguration API to obtain the current
// network configuration.
class MacProxyManager : public ProxyManagerInterface {
public:
bool GetProxyData(const string &url, string *hostdata, string *authdata) {
DCHECK(hostdata);
DCHECK(authdata);
scoped_cftyperef<CFDictionaryRef> proxy_settings(
SCDynamicStoreCopyProxies(NULL));
if (!proxy_settings.Verify(CFDictionaryGetTypeID())) {
LOG(ERROR) << "Failed to create proxy setting";
return false;
}
scoped_cftyperef<CFURLRef> cfurl(CFURLCreateWithBytes(
NULL, reinterpret_cast<const UInt8 *>(url.data()), url.size(),
kCFStringEncodingUTF8, NULL));
if (!cfurl.Verify(CFURLGetTypeID())) {
LOG(ERROR) << "Failed to create URL object from the specified URL";
return false;
}
scoped_cftyperef<CFArrayRef> proxies(
CFNetworkCopyProxiesForURL(cfurl.get(), proxy_settings.get()));
if (!proxies.Verify(CFArrayGetTypeID())) {
LOG(ERROR) << "Failed to get the proxies from the URL / proxy settings";
return false;
}
bool proxy_available = false;
if (CFArrayGetCount(proxies.get()) > 0) {
scoped_cftyperef<CFDictionaryRef> proxy(
RetainOrExpandPacFile(cfurl.get(), reinterpret_cast<CFDictionaryRef>(
CFArrayGetValueAtIndex(proxies.get(), 0))));
CFStringRef proxy_type = static_cast<CFStringRef>(
CFDictionaryGetValue(proxy.get(), kCFProxyTypeKey));
if (proxy.Verify(CFDictionaryGetTypeID()) &&
CFEqual(proxy_type, kCFProxyTypeHTTP)) {
scoped_cftyperef<CFTypeRef> host(
CFDictionaryGetValue(proxy.get(), kCFProxyHostNameKey), true);
scoped_cftyperef<CFTypeRef> port(
CFDictionaryGetValue(proxy.get(), kCFProxyPortNumberKey), true);
scoped_cftyperef<CFTypeRef> username(
CFDictionaryGetValue(proxy.get(), kCFProxyUsernameKey), true);
scoped_cftyperef<CFTypeRef> password(
CFDictionaryGetValue(proxy.get(), kCFProxyPasswordKey), true);
if (host.Verify(CFStringGetTypeID())) {
scoped_cftyperef<CFStringRef> host_desc;
if (port.Verify(CFNumberGetTypeID())) {
host_desc.reset(CFStringCreateWithFormat(
NULL, NULL, CFSTR("%@:%@"), host.get(), port.get()));
} else {
host_desc.reset(reinterpret_cast<CFStringRef>(host.get()));
CFRetain(host.get());
}
if (host_desc.Verify(CFStringGetTypeID())) {
const char *hostdata_ptr =
CFStringGetCStringPtr(host_desc.get(), kCFStringEncodingUTF8);
if (hostdata_ptr != NULL) {
hostdata->assign(hostdata_ptr);
proxy_available = true;
} else {
proxy_available = false;
}
} else {
LOG(ERROR) << "Invalid proxy spec: no host is specified";
}
}
if (proxy_available &&
username.Verify(CFStringGetTypeID()) &&
password.Verify(CFStringGetTypeID())) {
scoped_cftyperef<CFStringRef> auth_desc(CFStringCreateWithFormat(
NULL, NULL, CFSTR("%@:%@"), username.get(), password.get()));
if (auth_desc.Verify(CFStringGetTypeID())) {
const char *authdata_ptr =
CFStringGetCStringPtr(auth_desc.get(), kCFStringEncodingUTF8);
if (authdata_ptr != NULL) {
authdata->assign(authdata_ptr);
}
}
}
}
}
return proxy_available;
}
};
#endif // OS_MACOSX
bool DummyProxyManager::GetProxyData(
const string &url, string *hostdata, string *authdata) {
return false;
}
namespace {
// This is only used for dependency injection
ProxyManagerInterface *g_proxy_manager = NULL;
ProxyManagerInterface *GetProxyManager() {
if (g_proxy_manager == NULL) {
#ifdef OS_MACOSX
return Singleton<MacProxyManager>::get();
#else
return Singleton<DummyProxyManager>::get();
#endif
} else {
return g_proxy_manager;
}
}
} // anonymous namespace
void ProxyManager::SetProxyManager(
ProxyManagerInterface *proxy_manager) {
g_proxy_manager = proxy_manager;
}
bool ProxyManager::GetProxyData(
const string &url, string *hostdata, string *authdata) {
return GetProxyManager()->GetProxyData(url, hostdata, authdata);
}
ProxyManagerInterface::ProxyManagerInterface() {
}
ProxyManagerInterface::~ProxyManagerInterface() {
}
} // namespace mozc