qnetworkproxy_mac: use API available both on iOS and macOS

To extract system proxies, the one we used previously, was not available
on iOS and thus we could not obtain system proxies there. Support is
limited - no such things, as SOCKS/FTP/etc. proxies, only PAC (auto
configuration), and HTTP/HTTPS. There are no keys to extract info
about HTTPS, so instead we'll use CFNetworkCopyProxiesForURL (
looks like this enables exclusion lists (which are hidden)
functionality and apparently from the system point of view HTTP/HTTPS
are the same.

Fixes: QTBUG-39869
Change-Id: I73af719a2e2b5cded706e6b3faa4b8eaa879352b
Reviewed-by: Mårten Nordheim <marten.nordheim@qt.io>
This commit is contained in:
Timur Pocheptsov 2023-01-18 12:51:23 +01:00
parent ade96ff644
commit f932be58d5
4 changed files with 110 additions and 41 deletions

View File

@ -4,6 +4,7 @@
macro(qt_find_apple_system_frameworks)
if(APPLE)
qt_internal_find_apple_system_framework(FWAppKit AppKit)
qt_internal_find_apple_system_framework(FWCFNetwork CFNetwork)
qt_internal_find_apple_system_framework(FWAssetsLibrary AssetsLibrary)
qt_internal_find_apple_system_framework(FWAudioToolbox AudioToolbox)
qt_internal_find_apple_system_framework(FWApplicationServices ApplicationServices)

View File

@ -226,6 +226,18 @@ qt_internal_extend_target(Network CONDITION APPLE AND NOT UIKIT
${FWSystemConfiguration}
)
qt_internal_extend_target(Network CONDITION APPLE AND NOT UIKIT
LIBRARIES
${FWCoreServices}
${FWSystemConfiguration}
)
qt_internal_extend_target(Network CONDITION APPLE
LIBRARIES
${FWCFNetwork}
)
qt_internal_extend_target(Network CONDITION IOS OR MACOS
SOURCES
kernel/qnetconmonitor_darwin.mm
@ -253,9 +265,9 @@ qt_internal_extend_target(Network CONDITION UIKIT
kernel/qnetworkinterface_uikit_p.h
)
qt_internal_extend_target(Network CONDITION MACOS
qt_internal_extend_target(Network CONDITION APPLE
SOURCES
kernel/qnetworkproxy_mac.cpp
kernel/qnetworkproxy_darwin.cpp
)
qt_internal_extend_target(Network CONDITION QT_FEATURE_libproxy AND UNIX AND NOT MACOS

View File

@ -1466,7 +1466,7 @@ void QNetworkProxyFactory::setApplicationProxyFactory(QNetworkProxyFactory *fact
Internet Explorer's settings and use them.
On \macos, this function will obtain the proxy settings using the
SystemConfiguration framework from Apple. It will apply the FTP,
CFNetwork framework from Apple. It will apply the FTP,
HTTP and HTTPS proxy configurations for queries that contain the
protocol tag "ftp", "http" and "https", respectively. If the SOCKS
proxy is enabled in that configuration, this function will use the
@ -1489,9 +1489,6 @@ void QNetworkProxyFactory::setApplicationProxyFactory(QNetworkProxyFactory *fact
listed here.
\list
\li On \macos, this function will ignore the Proxy Auto Configuration
settings, since it cannot execute the associated ECMAScript code.
\li On Windows platforms, this function may take several seconds to
execute depending on the configuration of the user's system.
\endlist

View File

@ -14,6 +14,7 @@
#include <QtCore/QUrl>
#include <QtCore/qendian.h>
#include <QtCore/qstringlist.h>
#include <QtCore/qsystemdetection.h>
#include "private/qcore_mac_p.h"
/*
@ -32,11 +33,11 @@
* \li Bypass list (by default: *.local, 169.254/16)
* \endlist
*
* The matching configuration can be obtained by calling SCDynamicStoreCopyProxies
* (from <SystemConfiguration/SCDynamicStoreCopySpecific.h>). See
* The matching configuration can be obtained by calling CFNetworkCopySystemProxySettings()
* (from <CFNetwork/CFProxySupport.h>). See
* Apple's documentation:
*
* http://developer.apple.com/DOCUMENTATION/Networking/Reference/SysConfig/SCDynamicStoreCopySpecific/CompositePage.html#//apple_ref/c/func/SCDynamicStoreCopyProxies
* https://developer.apple.com/documentation/cfnetwork/1426754-cfnetworkcopysystemproxysettings?language=objc
*
*/
@ -46,13 +47,19 @@ using namespace Qt::StringLiterals;
static bool isHostExcluded(CFDictionaryRef dict, const QString &host)
{
Q_ASSERT(dict);
if (host.isEmpty())
return true;
#ifndef Q_OS_IOS
// On iOS all those keys are not available, and worse so - entries
// for HTTPS are not in the dictionary, but instead in some nested dictionary
// with undocumented keys/object types.
bool isSimple = !host.contains(u'.') && !host.contains(u':');
CFNumberRef excludeSimples;
if (isSimple &&
(excludeSimples = (CFNumberRef)CFDictionaryGetValue(dict, kSCPropNetProxiesExcludeSimpleHostnames))) {
(excludeSimples = (CFNumberRef)CFDictionaryGetValue(dict, kCFNetworkProxiesExcludeSimpleHostnames))) {
int enabled;
if (CFNumberGetValue(excludeSimples, kCFNumberIntType, &enabled) && enabled)
return true;
@ -63,7 +70,7 @@ static bool isHostExcluded(CFDictionaryRef dict, const QString &host)
// not a simple host name
// does it match the list of exclusions?
CFArrayRef exclusionList = (CFArrayRef)CFDictionaryGetValue(dict, kSCPropNetProxiesExceptionsList);
CFArrayRef exclusionList = (CFArrayRef)CFDictionaryGetValue(dict, kCFNetworkProxiesExceptionsList);
if (!exclusionList)
return false;
@ -81,7 +88,9 @@ static bool isHostExcluded(CFDictionaryRef dict, const QString &host)
return true;
}
}
#else
Q_UNUSED(dict);
#endif // Q_OS_IOS
// host was not excluded
return false;
}
@ -112,7 +121,6 @@ static QNetworkProxy proxyFromDictionary(CFDictionaryRef dict, QNetworkProxy::Pr
return QNetworkProxy();
}
static QNetworkProxy proxyFromDictionary(CFDictionaryRef dict)
{
QNetworkProxy::ProxyType proxyType = QNetworkProxy::DefaultProxy;
@ -178,16 +186,43 @@ QCFType<CFStringRef> stringByAddingPercentEscapes(CFStringRef originalPath)
return escaped.toCFString();
}
} // anon namespace
#ifdef Q_OS_IOS
QList<QNetworkProxy> proxiesForQueryUrl(CFDictionaryRef dict, const QUrl &url)
{
Q_ASSERT(dict);
const QCFType<CFURLRef> cfUrl = url.toCFURL();
const QCFType<CFArrayRef> proxies = CFNetworkCopyProxiesForURL(cfUrl, dict);
Q_ASSERT(proxies);
QList<QNetworkProxy> result;
const auto count = CFArrayGetCount(proxies);
if (!count) // Could be no proper proxy or host excluded.
return result;
for (CFIndex i = 0; i < count; ++i) {
const void *obj = CFArrayGetValueAtIndex(proxies, i);
if (CFGetTypeID(obj) != CFDictionaryGetTypeID())
continue;
const QNetworkProxy proxy = proxyFromDictionary(static_cast<CFDictionaryRef>(obj));
if (proxy.type() == QNetworkProxy::NoProxy || proxy.type() == QNetworkProxy::DefaultProxy)
continue;
result << proxy;
}
return result;
}
#endif // Q_OS_IOS
} // unnamed namespace.
QList<QNetworkProxy> macQueryInternal(const QNetworkProxyQuery &query)
{
QList<QNetworkProxy> result;
// obtain a dictionary to the proxy settings:
const QCFType<CFDictionaryRef> dict = SCDynamicStoreCopyProxies(NULL);
const QCFType<CFDictionaryRef> dict = CFNetworkCopySystemProxySettings();
if (!dict) {
qWarning("QNetworkProxyFactory::systemProxyForQuery: SCDynamicStoreCopyProxies returned NULL");
qWarning("QNetworkProxyFactory::systemProxyForQuery: CFNetworkCopySystemProxySettings returned nullptr");
return result; // failed
}
@ -196,13 +231,13 @@ QList<QNetworkProxy> macQueryInternal(const QNetworkProxyQuery &query)
// is there a PAC enabled? If so, use it first.
CFNumberRef pacEnabled;
if ((pacEnabled = (CFNumberRef)CFDictionaryGetValue(dict, kSCPropNetProxiesProxyAutoConfigEnable))) {
if ((pacEnabled = (CFNumberRef)CFDictionaryGetValue(dict, kCFNetworkProxiesProxyAutoConfigEnable))) {
int enabled;
if (CFNumberGetValue(pacEnabled, kCFNumberIntType, &enabled) && enabled) {
// PAC is enabled
// kSCPropNetProxiesProxyAutoConfigURLString returns the URL string
// as entered in the system proxy configuration dialog
CFStringRef pacLocationSetting = (CFStringRef)CFDictionaryGetValue(dict, kSCPropNetProxiesProxyAutoConfigURLString);
CFStringRef pacLocationSetting = (CFStringRef)CFDictionaryGetValue(dict, kCFNetworkProxiesProxyAutoConfigURLString);
auto cfPacLocation = stringByAddingPercentEscapes(pacLocationSetting);
QCFType<CFDataRef> pacData;
QCFType<CFURLRef> pacUrl = CFURLCreateWithString(kCFAllocatorDefault, cfPacLocation, NULL);
@ -252,53 +287,77 @@ QList<QNetworkProxy> macQueryInternal(const QNetworkProxyQuery &query)
}
}
// no PAC, decide which proxy we're looking for based on the query
bool isHttps = false;
QString protocol = query.protocolTag().toLower();
// No PAC, decide which proxy we're looking for based on the query
// try the protocol-specific proxy
QString protocol = query.protocolTag().toLower();
QNetworkProxy protocolSpecificProxy;
if (protocol == "http"_L1) {
protocolSpecificProxy =
proxyFromDictionary(dict, QNetworkProxy::HttpProxy,
kCFNetworkProxiesHTTPEnable,
kCFNetworkProxiesHTTPProxy,
kCFNetworkProxiesHTTPPort);
}
#ifdef Q_OS_IOS
if (protocolSpecificProxy.type() != QNetworkProxy::DefaultProxy
&& protocolSpecificProxy.type() != QNetworkProxy::DefaultProxy) {
// HTTP proxy is enabled (on iOS there is no separate HTTPS, though
// 'dict' contains deeply buried entries which are the same as HTTP.
result << protocolSpecificProxy;
}
// TODO: check query.queryType()? It's possible, the exclude list
// did exclude it but above we added a proxy because HTTP proxy
// is found. We'll deal with such a situation later, since now NMI.
const auto proxiesForUrl = proxiesForQueryUrl(dict, query.url());
for (const auto &proxy : proxiesForUrl) {
if (!result.contains(proxy))
result << proxy;
}
#else
bool isHttps = false;
if (protocol == "ftp"_L1) {
protocolSpecificProxy =
proxyFromDictionary(dict, QNetworkProxy::FtpCachingProxy,
kSCPropNetProxiesFTPEnable,
kSCPropNetProxiesFTPProxy,
kSCPropNetProxiesFTPPort);
} else if (protocol == "http"_L1) {
protocolSpecificProxy =
proxyFromDictionary(dict, QNetworkProxy::HttpProxy,
kSCPropNetProxiesHTTPEnable,
kSCPropNetProxiesHTTPProxy,
kSCPropNetProxiesHTTPPort);
kCFNetworkProxiesFTPEnable,
kCFNetworkProxiesFTPProxy,
kCFNetworkProxiesFTPPort);
} else if (protocol == "https"_L1) {
isHttps = true;
protocolSpecificProxy =
proxyFromDictionary(dict, QNetworkProxy::HttpProxy,
kSCPropNetProxiesHTTPSEnable,
kSCPropNetProxiesHTTPSProxy,
kSCPropNetProxiesHTTPSPort);
kCFNetworkProxiesHTTPSEnable,
kCFNetworkProxiesHTTPSProxy,
kCFNetworkProxiesHTTPSPort);
}
if (protocolSpecificProxy.type() != QNetworkProxy::DefaultProxy)
result << protocolSpecificProxy;
// let's add SOCKSv5 if present too
QNetworkProxy socks5 = proxyFromDictionary(dict, QNetworkProxy::Socks5Proxy,
kSCPropNetProxiesSOCKSEnable,
kSCPropNetProxiesSOCKSProxy,
kSCPropNetProxiesSOCKSPort);
kCFNetworkProxiesSOCKSEnable,
kCFNetworkProxiesSOCKSProxy,
kCFNetworkProxiesSOCKSPort);
if (socks5.type() != QNetworkProxy::DefaultProxy)
result << socks5;
// let's add the HTTPS proxy if present (and if we haven't added
// yet)
if (!isHttps) {
QNetworkProxy https = proxyFromDictionary(dict, QNetworkProxy::HttpProxy,
kSCPropNetProxiesHTTPSEnable,
kSCPropNetProxiesHTTPSProxy,
kSCPropNetProxiesHTTPSPort);
QNetworkProxy https;
https = proxyFromDictionary(dict, QNetworkProxy::HttpProxy,
kCFNetworkProxiesHTTPSEnable,
kCFNetworkProxiesHTTPSProxy,
kCFNetworkProxiesHTTPSPort);
if (https.type() != QNetworkProxy::DefaultProxy && https != protocolSpecificProxy)
result << https;
}
#endif // !Q_OS_IOS
return result;
}