mirror of
https://github.com/klzgrad/naiveproxy.git
synced 2024-12-01 01:36:09 +03:00
366 lines
14 KiB
C++
366 lines
14 KiB
C++
// 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_resolution/proxy_resolver_mac.h"
|
|
|
|
#include <CoreFoundation/CoreFoundation.h>
|
|
|
|
#include "base/lazy_instance.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 "base/synchronization/lock.h"
|
|
#include "base/threading/thread_checker.h"
|
|
#include "net/base/net_errors.h"
|
|
#include "net/base/proxy_server.h"
|
|
#include "net/proxy_resolution/proxy_info.h"
|
|
#include "net/proxy_resolution/proxy_resolver.h"
|
|
#include "url/gurl.h"
|
|
|
|
#if defined(OS_IOS)
|
|
#include <CFNetwork/CFProxySupport.h>
|
|
#else
|
|
#include <CoreServices/CoreServices.h>
|
|
#endif
|
|
|
|
namespace net {
|
|
|
|
namespace {
|
|
|
|
// A lock shared by all ProxyResolverMac instances. It is used to synchronize
|
|
// the events of multiple CFNetworkExecuteProxyAutoConfigurationURL run loop
|
|
// sources. These events are:
|
|
// 1. Adding the source to the run loop.
|
|
// 2. Handling the source result.
|
|
// 3. Removing the source from the run loop.
|
|
static base::LazyInstance<base::Lock>::Leaky g_cfnetwork_pac_runloop_lock =
|
|
LAZY_INSTANCE_INITIALIZER;
|
|
|
|
// Forward declaration of the callback function used by the
|
|
// SynchronizedRunLoopObserver class.
|
|
void RunLoopObserverCallBackFunc(CFRunLoopObserverRef observer,
|
|
CFRunLoopActivity activity,
|
|
void* info);
|
|
|
|
// Utility function to map a CFProxyType to a ProxyServer::Scheme.
|
|
// If the type is unknown, returns ProxyServer::SCHEME_INVALID.
|
|
ProxyServer::Scheme GetProxyServerScheme(CFStringRef proxy_type) {
|
|
if (CFEqual(proxy_type, kCFProxyTypeNone))
|
|
return ProxyServer::SCHEME_DIRECT;
|
|
if (CFEqual(proxy_type, kCFProxyTypeHTTP))
|
|
return 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 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 ProxyServer::SCHEME_SOCKS5;
|
|
}
|
|
return 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());
|
|
}
|
|
|
|
#pragma mark - SynchronizedRunLoopObserver
|
|
// A run loop observer that guarantees that no two run loop sources protected
|
|
// by the same lock will be fired concurrently in different threads.
|
|
// The observer does not prevent the parallel execution of the sources but only
|
|
// synchronizes the run loop events associated with the sources. In the context
|
|
// of proxy resolver, the observer is used to synchronize the execution of the
|
|
// callbacks function that handles the result of
|
|
// CFNetworkExecuteProxyAutoConfigurationURL execution.
|
|
class SynchronizedRunLoopObserver final {
|
|
public:
|
|
// Creates the instance of an observer that will synchronize the sources
|
|
// using a given |lock|.
|
|
SynchronizedRunLoopObserver(base::Lock& lock);
|
|
// Destructor.
|
|
~SynchronizedRunLoopObserver();
|
|
// Adds the observer to the current run loop for a given run loop mode.
|
|
// This method should always be paired with |RemoveFromCurrentRunLoop|.
|
|
void AddToCurrentRunLoop(const CFStringRef mode);
|
|
// Removes the observer from the current run loop for a given run loop mode.
|
|
// This method should always be paired with |AddToCurrentRunLoop|.
|
|
void RemoveFromCurrentRunLoop(const CFStringRef mode);
|
|
// Callback function that is called when an observable run loop event occurs.
|
|
void RunLoopObserverCallBack(CFRunLoopObserverRef observer,
|
|
CFRunLoopActivity activity);
|
|
|
|
private:
|
|
// Lock to use to synchronize the run loop sources.
|
|
base::Lock& lock_;
|
|
// Indicates whether the current observer holds the lock. It is used to
|
|
// avoid double locking and releasing.
|
|
bool lock_acquired_;
|
|
// The underlying CFRunLoopObserverRef structure wrapped by this instance.
|
|
base::ScopedCFTypeRef<CFRunLoopObserverRef> observer_;
|
|
// Validates that all methods of this class are executed on the same thread.
|
|
base::ThreadChecker thread_checker_;
|
|
DISALLOW_COPY_AND_ASSIGN(SynchronizedRunLoopObserver);
|
|
};
|
|
|
|
SynchronizedRunLoopObserver::SynchronizedRunLoopObserver(base::Lock& lock)
|
|
: lock_(lock), lock_acquired_(false) {
|
|
CFRunLoopObserverContext observer_context = {0, this, NULL, NULL, NULL};
|
|
observer_.reset(CFRunLoopObserverCreate(
|
|
kCFAllocatorDefault,
|
|
kCFRunLoopBeforeSources | kCFRunLoopBeforeWaiting | kCFRunLoopExit, true,
|
|
0, RunLoopObserverCallBackFunc, &observer_context));
|
|
}
|
|
|
|
SynchronizedRunLoopObserver::~SynchronizedRunLoopObserver() {
|
|
DCHECK(thread_checker_.CalledOnValidThread());
|
|
DCHECK(!lock_acquired_);
|
|
}
|
|
|
|
void SynchronizedRunLoopObserver::AddToCurrentRunLoop(const CFStringRef mode) {
|
|
DCHECK(thread_checker_.CalledOnValidThread());
|
|
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer_.get(), mode);
|
|
}
|
|
|
|
void SynchronizedRunLoopObserver::RemoveFromCurrentRunLoop(
|
|
const CFStringRef mode) {
|
|
DCHECK(thread_checker_.CalledOnValidThread());
|
|
CFRunLoopRemoveObserver(CFRunLoopGetCurrent(), observer_.get(), mode);
|
|
}
|
|
|
|
void SynchronizedRunLoopObserver::RunLoopObserverCallBack(
|
|
CFRunLoopObserverRef observer,
|
|
CFRunLoopActivity activity) {
|
|
DCHECK(thread_checker_.CalledOnValidThread());
|
|
// Acquire the lock when a source has been signaled and going to be fired.
|
|
// In the context of the proxy resolver that happens when the proxy for a
|
|
// given URL has been resolved and the callback function that handles the
|
|
// result is going to be fired.
|
|
// Release the lock when all source events have been handled.
|
|
switch (activity) {
|
|
case kCFRunLoopBeforeSources:
|
|
if (!lock_acquired_) {
|
|
lock_.Acquire();
|
|
lock_acquired_ = true;
|
|
}
|
|
break;
|
|
case kCFRunLoopBeforeWaiting:
|
|
case kCFRunLoopExit:
|
|
if (lock_acquired_) {
|
|
lock_acquired_ = false;
|
|
lock_.Release();
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
void RunLoopObserverCallBackFunc(CFRunLoopObserverRef observer,
|
|
CFRunLoopActivity activity,
|
|
void* info) {
|
|
// Forward the call to the instance of SynchronizedRunLoopObserver
|
|
// that is associated with the current CF run loop observer.
|
|
SynchronizedRunLoopObserver* observerInstance =
|
|
(SynchronizedRunLoopObserver*)info;
|
|
observerInstance->RunLoopObserverCallBack(observer, activity);
|
|
}
|
|
|
|
#pragma mark - ProxyResolverMac
|
|
class ProxyResolverMac : public ProxyResolver {
|
|
public:
|
|
explicit ProxyResolverMac(const scoped_refptr<PacFileData>& script_data);
|
|
~ProxyResolverMac() override;
|
|
|
|
// ProxyResolver methods:
|
|
int GetProxyForURL(const GURL& url,
|
|
ProxyInfo* results,
|
|
CompletionOnceCallback callback,
|
|
std::unique_ptr<Request>* request,
|
|
const NetLogWithSource& net_log) override;
|
|
|
|
private:
|
|
const scoped_refptr<PacFileData> script_data_;
|
|
};
|
|
|
|
ProxyResolverMac::ProxyResolverMac(
|
|
const scoped_refptr<PacFileData>& script_data)
|
|
: script_data_(script_data) {}
|
|
|
|
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,
|
|
CompletionOnceCallback /*callback*/,
|
|
std::unique_ptr<Request>* /*request*/,
|
|
const NetLogWithSource& net_log) {
|
|
// OS X's system resolver does not support WebSocket URLs in proxy.pac, as of
|
|
// version 10.13.5. See https://crbug.com/862121.
|
|
GURL mutable_query_url = query_url;
|
|
if (query_url.SchemeIsWSOrWSS()) {
|
|
GURL::Replacements replacements;
|
|
replacements.SetSchemeStr(query_url.SchemeIsCryptographic() ? "https"
|
|
: "http");
|
|
mutable_query_url = query_url.ReplaceComponents(replacements);
|
|
}
|
|
|
|
base::ScopedCFTypeRef<CFStringRef> query_ref(
|
|
base::SysUTF8ToCFStringRef(mutable_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() == PacFileData::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.
|
|
|
|
base::ScopedCFTypeRef<CFDictionaryRef> empty_dictionary(
|
|
CFDictionaryCreate(NULL, NULL, NULL, 0, NULL, NULL));
|
|
CFArrayRef dummy_result =
|
|
CFNetworkCopyProxiesForURL(query_url_ref.get(), empty_dictionary);
|
|
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");
|
|
|
|
// Add the run loop observer to synchronize events of
|
|
// CFNetworkExecuteProxyAutoConfigurationURL sources. See the definition of
|
|
// |g_cfnetwork_pac_runloop_lock|.
|
|
SynchronizedRunLoopObserver observer(g_cfnetwork_pac_runloop_lock.Get());
|
|
observer.AddToCurrentRunLoop(private_runloop_mode);
|
|
|
|
// Make sure that no CFNetworkExecuteProxyAutoConfigurationURL sources
|
|
// are added to the run loop concurrently.
|
|
{
|
|
base::AutoLock lock(g_cfnetwork_pac_runloop_lock.Get());
|
|
CFRunLoopAddSource(CFRunLoopGetCurrent(), runloop_source.get(),
|
|
private_runloop_mode);
|
|
}
|
|
|
|
CFRunLoopRunInMode(private_runloop_mode, DBL_MAX, false);
|
|
|
|
// Make sure that no CFNetworkExecuteProxyAutoConfigurationURL sources
|
|
// are removed from the run loop concurrently.
|
|
{
|
|
base::AutoLock lock(g_cfnetwork_pac_runloop_lock.Get());
|
|
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), runloop_source.get(),
|
|
private_runloop_mode);
|
|
}
|
|
observer.RemoveFromCurrentRunLoop(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;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
ProxyResolverFactoryMac::ProxyResolverFactoryMac()
|
|
: ProxyResolverFactory(false /*expects_pac_bytes*/) {
|
|
}
|
|
|
|
int ProxyResolverFactoryMac::CreateProxyResolver(
|
|
const scoped_refptr<PacFileData>& pac_script,
|
|
std::unique_ptr<ProxyResolver>* resolver,
|
|
CompletionOnceCallback callback,
|
|
std::unique_ptr<Request>* request) {
|
|
resolver->reset(new ProxyResolverMac(pac_script));
|
|
return OK;
|
|
}
|
|
|
|
} // namespace net
|