#include "net/proxy_resolution/proxy_resolver_apple.h"
#include <CFNetwork/CFProxySupport.h>
#include <CoreFoundation/CoreFoundation.h>
#include <memory>
#include "base/apple/foundation_util.h"
#include "base/apple/scoped_cftyperef.h"
#include "base/check.h"
#include "base/memory/raw_ref.h"
#include "base/no_destructor.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 "build/build_config.h"
#include "net/base/net_errors.h"
#include "net/proxy_resolution/proxy_chain_util_apple.h"
#include "net/proxy_resolution/proxy_info.h"
#include "net/proxy_resolution/proxy_list.h"
#include "net/proxy_resolution/proxy_resolver.h"
#include "url/gurl.h"
#if BUILDFLAG(IS_IOS)
#include <CFNetwork/CFProxySupport.h>
#else
#include <CoreServices/CoreServices.h>
#endif
#if LEAK_SANITIZER
#include <sanitizer/lsan_interface.h>
#endif
namespace net {
class NetworkAnonymizationKey;
namespace {
base::Lock& GetCFNetworkPacRunloopLock() {
static base::NoDestructor<base::Lock> lock;
return *lock;
}
void RunLoopObserverCallBackFunc(CFRunLoopObserverRef observer,
CFRunLoopActivity activity,
void* info);
void ResultCallback(void* client, CFArrayRef proxies, CFErrorRef error) {
DCHECK((proxies != nullptr) == (error == nullptr));
CFTypeRef* result_ptr = reinterpret_cast<CFTypeRef*>(client);
DCHECK(result_ptr != nullptr);
DCHECK(*result_ptr == nullptr);
if (error != nullptr) {
*result_ptr = CFRetain(error);
} else {
*result_ptr = CFRetain(proxies);
}
CFRunLoopStop(CFRunLoopGetCurrent());
}
#pragma mark - SynchronizedRunLoopObserver
class SynchronizedRunLoopObserver final {
public:
SynchronizedRunLoopObserver(base::Lock& lock);
SynchronizedRunLoopObserver(const SynchronizedRunLoopObserver&) = delete;
SynchronizedRunLoopObserver& operator=(const SynchronizedRunLoopObserver&) =
delete;
~SynchronizedRunLoopObserver();
void AddToCurrentRunLoop(const CFStringRef mode);
void RemoveFromCurrentRunLoop(const CFStringRef mode);
void RunLoopObserverCallBack(CFRunLoopObserverRef observer,
CFRunLoopActivity activity);
private:
const raw_ref<base::Lock> lock_;
bool lock_acquired_ = false;
base::apple::ScopedCFTypeRef<CFRunLoopObserverRef> observer_;
base::ThreadChecker thread_checker_;
};
SynchronizedRunLoopObserver::SynchronizedRunLoopObserver(base::Lock& lock)
: lock_(lock) {
CFRunLoopObserverContext observer_context = {0, this, nullptr, nullptr,
nullptr};
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) NO_THREAD_SAFETY_ANALYSIS {
DCHECK(thread_checker_.CalledOnValidThread());
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) {
SynchronizedRunLoopObserver* observerInstance =
(SynchronizedRunLoopObserver*)info;
observerInstance->RunLoopObserverCallBack(observer, activity);
}
#pragma mark - ProxyResolverApple
class ProxyResolverApple : public ProxyResolver {
public:
explicit ProxyResolverApple(const scoped_refptr<PacFileData>& script_data);
~ProxyResolverApple() override;
int GetProxyForURL(const GURL& url,
const NetworkAnonymizationKey& network_anonymization_key,
ProxyInfo* results,
CompletionOnceCallback callback,
std::unique_ptr<Request>* request,
const NetLogWithSource& net_log) override;
private:
const scoped_refptr<PacFileData> script_data_;
};
ProxyResolverApple::ProxyResolverApple(
const scoped_refptr<PacFileData>& script_data)
: script_data_(script_data) {}
ProxyResolverApple::~ProxyResolverApple() = default;
int ProxyResolverApple::GetProxyForURL(
const GURL& query_url,
const NetworkAnonymizationKey& network_anonymization_key,
ProxyInfo* results,
CompletionOnceCallback ,
std::unique_ptr<Request>* ,
const NetLogWithSource& net_log) {
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::apple::ScopedCFTypeRef<CFStringRef> query_ref(
base::SysUTF8ToCFStringRef(mutable_query_url.spec()));
base::apple::ScopedCFTypeRef<CFURLRef> query_url_ref(
CFURLCreateWithString(kCFAllocatorDefault, query_ref.get(), nullptr));
if (!query_url_ref.get())
return ERR_FAILED;
base::apple::ScopedCFTypeRef<CFStringRef> pac_ref(base::SysUTF8ToCFStringRef(
script_data_->type() == PacFileData::TYPE_AUTO_DETECT
? std::string()
: script_data_->url().spec()));
base::apple::ScopedCFTypeRef<CFURLRef> pac_url_ref(
CFURLCreateWithString(kCFAllocatorDefault, pac_ref.get(), nullptr));
if (!pac_url_ref.get())
return ERR_FAILED;
base::apple::ScopedCFTypeRef<CFDictionaryRef> empty_dictionary(
CFDictionaryCreate(nullptr, nullptr, nullptr, 0, nullptr, nullptr));
base::apple::ScopedCFTypeRef<CFArrayRef> dummy_result(
CFNetworkCopyProxiesForURL(query_url_ref.get(), empty_dictionary.get()));
CFTypeRef result = nullptr;
CFStreamClientContext context = {0, &result, nullptr, nullptr, nullptr};
base::apple::ScopedCFTypeRef<CFRunLoopSourceRef> runloop_source(
CFNetworkExecuteProxyAutoConfigurationURL(
pac_url_ref.get(), query_url_ref.get(), ResultCallback, &context));
#if LEAK_SANITIZER
__lsan_ignore_object(runloop_source.get());
#endif
if (!runloop_source)
return ERR_FAILED;
const CFStringRef private_runloop_mode =
CFSTR("org.chromium.ProxyResolverApple");
SynchronizedRunLoopObserver observer(GetCFNetworkPacRunloopLock());
observer.AddToCurrentRunLoop(private_runloop_mode);
{
base::AutoLock lock(GetCFNetworkPacRunloopLock());
CFRunLoopAddSource(CFRunLoopGetCurrent(), runloop_source.get(),
private_runloop_mode);
}
CFRunLoopRunInMode(private_runloop_mode, DBL_MAX, false);
{
base::AutoLock lock(GetCFNetworkPacRunloopLock());
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), runloop_source.get(),
private_runloop_mode);
}
observer.RemoveFromCurrentRunLoop(private_runloop_mode);
DCHECK(result);
if (CFGetTypeID(result) == CFErrorGetTypeID()) {
CFRelease(result);
return ERR_FAILED;
}
base::apple::ScopedCFTypeRef<CFArrayRef> proxy_array_ref(
base::apple::CFCastStrict<CFArrayRef>(result));
DCHECK(proxy_array_ref);
ProxyList proxy_list;
CFIndex proxy_array_count = CFArrayGetCount(proxy_array_ref.get());
for (CFIndex i = 0; i < proxy_array_count; ++i) {
CFDictionaryRef proxy_dictionary =
base::apple::CFCastStrict<CFDictionaryRef>(
CFArrayGetValueAtIndex(proxy_array_ref.get(), i));
DCHECK(proxy_dictionary);
CFStringRef proxy_type = base::apple::GetValueFromDictionary<CFStringRef>(
proxy_dictionary, kCFProxyTypeKey);
ProxyChain proxy_chain =
ProxyDictionaryToProxyChain(proxy_type, proxy_dictionary,
kCFProxyHostNameKey, kCFProxyPortNumberKey);
proxy_list.AddProxyChain(proxy_chain);
}
if (!proxy_list.IsEmpty())
results->UseProxyList(proxy_list);
return OK;
}
}
ProxyResolverFactoryApple::ProxyResolverFactoryApple()
: ProxyResolverFactory(false ) {
}
int ProxyResolverFactoryApple::CreateProxyResolver(
const scoped_refptr<PacFileData>& pac_script,
std::unique_ptr<ProxyResolver>* resolver,
CompletionOnceCallback callback,
std::unique_ptr<Request>* request) {
*resolver = std::make_unique<ProxyResolverApple>(pac_script);
return OK;
}
}