summaryrefslogtreecommitdiffstats
path: root/src/network/kernel/qnetworkproxy_darwin.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/network/kernel/qnetworkproxy_darwin.cpp')
-rw-r--r--src/network/kernel/qnetworkproxy_darwin.cpp376
1 files changed, 376 insertions, 0 deletions
diff --git a/src/network/kernel/qnetworkproxy_darwin.cpp b/src/network/kernel/qnetworkproxy_darwin.cpp
new file mode 100644
index 0000000000..d2bd4958dd
--- /dev/null
+++ b/src/network/kernel/qnetworkproxy_darwin.cpp
@@ -0,0 +1,376 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qnetworkproxy.h"
+
+#ifndef QT_NO_NETWORKPROXY
+
+#include <CFNetwork/CFNetwork.h>
+#include <CoreFoundation/CoreFoundation.h>
+#include <SystemConfiguration/SystemConfiguration.h>
+
+#include <QtCore/QRegularExpression>
+#include <QtCore/QStringList>
+#include <QtCore/QUrl>
+#include <QtCore/qendian.h>
+#include <QtCore/qstringlist.h>
+#include <QtCore/qsystemdetection.h>
+#include "private/qcore_mac_p.h"
+
+/*
+ * MacOS X has a proxy configuration module in System Preferences (on
+ * MacOS X 10.5, it's in Network, Advanced), where one can set the
+ * proxy settings for:
+ *
+ * \list
+ * \li FTP proxy
+ * \li Web Proxy (HTTP)
+ * \li Secure Web Proxy (HTTPS)
+ * \li Streaming Proxy (RTSP)
+ * \li SOCKS Proxy
+ * \li Gopher Proxy
+ * \li URL for Automatic Proxy Configuration (PAC scripts)
+ * \li Bypass list (by default: *.local, 169.254/16)
+ * \endlist
+ *
+ * The matching configuration can be obtained by calling CFNetworkCopySystemProxySettings()
+ * (from <CFNetwork/CFProxySupport.h>). See
+ * Apple's documentation:
+ *
+ * https://developer.apple.com/documentation/cfnetwork/1426754-cfnetworkcopysystemproxysettings?language=objc
+ *
+ */
+
+QT_BEGIN_NAMESPACE
+
+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, kCFNetworkProxiesExcludeSimpleHostnames))) {
+ int enabled;
+ if (CFNumberGetValue(excludeSimples, kCFNumberIntType, &enabled) && enabled)
+ return true;
+ }
+
+ QHostAddress ipAddress;
+ bool isIpAddress = ipAddress.setAddress(host);
+
+ // not a simple host name
+ // does it match the list of exclusions?
+ CFArrayRef exclusionList = (CFArrayRef)CFDictionaryGetValue(dict, kCFNetworkProxiesExceptionsList);
+ if (!exclusionList)
+ return false;
+
+ CFIndex size = CFArrayGetCount(exclusionList);
+ for (CFIndex i = 0; i < size; ++i) {
+ CFStringRef cfentry = (CFStringRef)CFArrayGetValueAtIndex(exclusionList, i);
+ QString entry = QString::fromCFString(cfentry);
+
+ if (isIpAddress && ipAddress.isInSubnet(QHostAddress::parseSubnet(entry))) {
+ return true; // excluded
+ } else {
+ // do wildcard matching
+ auto rx = QRegularExpression::fromWildcard(entry, Qt::CaseInsensitive);
+ if (rx.match(host).hasMatch())
+ return true;
+ }
+ }
+#else
+ Q_UNUSED(dict);
+#endif // Q_OS_IOS
+ // host was not excluded
+ return false;
+}
+
+static QNetworkProxy proxyFromDictionary(CFDictionaryRef dict, QNetworkProxy::ProxyType type,
+ CFStringRef enableKey, CFStringRef hostKey,
+ CFStringRef portKey)
+{
+ CFNumberRef protoEnabled;
+ CFNumberRef protoPort;
+ CFStringRef protoHost;
+ if (enableKey
+ && (protoEnabled = (CFNumberRef)CFDictionaryGetValue(dict, enableKey))
+ && (protoHost = (CFStringRef)CFDictionaryGetValue(dict, hostKey))
+ && (protoPort = (CFNumberRef)CFDictionaryGetValue(dict, portKey))) {
+ int enabled;
+ if (CFNumberGetValue(protoEnabled, kCFNumberIntType, &enabled) && enabled) {
+ QString host = QString::fromCFString(protoHost);
+
+ int port;
+ CFNumberGetValue(protoPort, kCFNumberIntType, &port);
+
+ return QNetworkProxy(type, host, port);
+ }
+ }
+
+ // proxy not enabled
+ return QNetworkProxy();
+}
+
+static QNetworkProxy proxyFromDictionary(CFDictionaryRef dict)
+{
+ QNetworkProxy::ProxyType proxyType = QNetworkProxy::DefaultProxy;
+ QString hostName;
+ quint16 port = 0;
+ QString user;
+ QString password;
+
+ CFStringRef cfProxyType = (CFStringRef)CFDictionaryGetValue(dict, kCFProxyTypeKey);
+ if (CFStringCompare(cfProxyType, kCFProxyTypeNone, 0) == kCFCompareEqualTo) {
+ proxyType = QNetworkProxy::NoProxy;
+ } else if (CFStringCompare(cfProxyType, kCFProxyTypeFTP, 0) == kCFCompareEqualTo) {
+ proxyType = QNetworkProxy::FtpCachingProxy;
+ } else if (CFStringCompare(cfProxyType, kCFProxyTypeHTTP, 0) == kCFCompareEqualTo) {
+ proxyType = QNetworkProxy::HttpProxy;
+ } else if (CFStringCompare(cfProxyType, kCFProxyTypeHTTPS, 0) == kCFCompareEqualTo) {
+ proxyType = QNetworkProxy::HttpProxy;
+ } else if (CFStringCompare(cfProxyType, kCFProxyTypeSOCKS, 0) == kCFCompareEqualTo) {
+ proxyType = QNetworkProxy::Socks5Proxy;
+ }
+
+ hostName = QString::fromCFString((CFStringRef)CFDictionaryGetValue(dict, kCFProxyHostNameKey));
+ user = QString::fromCFString((CFStringRef)CFDictionaryGetValue(dict, kCFProxyUsernameKey));
+ password = QString::fromCFString((CFStringRef)CFDictionaryGetValue(dict, kCFProxyPasswordKey));
+
+ CFNumberRef portNumber = (CFNumberRef)CFDictionaryGetValue(dict, kCFProxyPortNumberKey);
+ if (portNumber) {
+ CFNumberGetValue(portNumber, kCFNumberSInt16Type, &port);
+ }
+
+ return QNetworkProxy(proxyType, hostName, port, user, password);
+}
+
+namespace {
+struct PACInfo {
+ QCFType<CFArrayRef> proxies;
+ QCFType<CFErrorRef> error;
+ bool done = false;
+};
+
+void proxyAutoConfigCallback(void *client, CFArrayRef proxylist, CFErrorRef error)
+{
+ Q_ASSERT(client);
+
+ PACInfo *info = static_cast<PACInfo *>(client);
+ info->done = true;
+
+ if (error) {
+ CFRetain(error);
+ info->error = error;
+ }
+ if (proxylist) {
+ CFRetain(proxylist);
+ info->proxies = proxylist;
+ }
+}
+
+QCFType<CFStringRef> stringByAddingPercentEscapes(CFStringRef originalPath)
+{
+ Q_ASSERT(originalPath);
+ const auto qtPath = QString::fromCFString(originalPath);
+ const auto escaped = QString::fromUtf8(QUrl(qtPath).toEncoded());
+ return escaped.toCFString();
+}
+
+#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 = CFNetworkCopySystemProxySettings();
+ if (!dict) {
+ qWarning("QNetworkProxyFactory::systemProxyForQuery: CFNetworkCopySystemProxySettings returned nullptr");
+ return result; // failed
+ }
+
+ if (isHostExcluded(dict, query.peerHostName()))
+ return result; // no proxy for this host
+
+ // is there a PAC enabled? If so, use it first.
+ CFNumberRef pacEnabled;
+ 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, kCFNetworkProxiesProxyAutoConfigURLString);
+ auto cfPacLocation = stringByAddingPercentEscapes(pacLocationSetting);
+ QCFType<CFDataRef> pacData;
+ QCFType<CFURLRef> pacUrl = CFURLCreateWithString(kCFAllocatorDefault, cfPacLocation, NULL);
+ if (!pacUrl) {
+ qWarning("Invalid PAC URL \"%s\"", qPrintable(QString::fromCFString(cfPacLocation)));
+ return result;
+ }
+
+ QByteArray encodedURL = query.url().toEncoded(); // converted to UTF-8
+ if (encodedURL.isEmpty()) {
+ return result; // Invalid URL, abort
+ }
+
+ QCFType<CFURLRef> targetURL = CFURLCreateWithBytes(kCFAllocatorDefault, (UInt8*)encodedURL.data(), encodedURL.size(), kCFStringEncodingUTF8, NULL);
+ if (!targetURL) {
+ return result; // URL creation problem, abort
+ }
+
+ CFStreamClientContext pacCtx;
+ pacCtx.version = 0;
+ PACInfo pacInfo;
+ pacCtx.info = &pacInfo;
+ pacCtx.retain = NULL;
+ pacCtx.release = NULL;
+ pacCtx.copyDescription = NULL;
+
+ static CFStringRef pacRunLoopMode = CFSTR("qtPACRunLoopMode");
+
+ QCFType<CFRunLoopSourceRef> pacRunLoopSource = CFNetworkExecuteProxyAutoConfigurationURL(pacUrl, targetURL, &proxyAutoConfigCallback, &pacCtx);
+ CFRunLoopAddSource(CFRunLoopGetCurrent(), pacRunLoopSource, pacRunLoopMode);
+ while (!pacInfo.done)
+ CFRunLoopRunInMode(pacRunLoopMode, 1000, /*returnAfterSourceHandled*/ true);
+
+ if (!pacInfo.proxies) {
+ QString pacLocation = QString::fromCFString(cfPacLocation);
+ QCFType<CFStringRef> pacErrorDescription = CFErrorCopyDescription(pacInfo.error);
+ qWarning("Execution of PAC script at \"%s\" failed: %s", qPrintable(pacLocation), qPrintable(QString::fromCFString(pacErrorDescription)));
+ return result;
+ }
+
+ CFIndex size = CFArrayGetCount(pacInfo.proxies);
+ for (CFIndex i = 0; i < size; ++i) {
+ CFDictionaryRef proxy = (CFDictionaryRef)CFArrayGetValueAtIndex(pacInfo.proxies, i);
+ result << proxyFromDictionary(proxy);
+ }
+ return result;
+ }
+ }
+
+ // No PAC, decide which proxy we're looking for based on the query
+ // try the protocol-specific proxy
+ const QString protocol = query.protocolTag();
+ QNetworkProxy protocolSpecificProxy;
+ if (protocol.compare("http"_L1, Qt::CaseInsensitive) == 0) {
+ 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.compare("ftp"_L1, Qt::CaseInsensitive) == 0) {
+ protocolSpecificProxy =
+ proxyFromDictionary(dict, QNetworkProxy::FtpCachingProxy,
+ kCFNetworkProxiesFTPEnable,
+ kCFNetworkProxiesFTPProxy,
+ kCFNetworkProxiesFTPPort);
+ } else if (protocol.compare("https"_L1, Qt::CaseInsensitive) == 0) {
+ isHttps = true;
+ protocolSpecificProxy =
+ proxyFromDictionary(dict, QNetworkProxy::HttpProxy,
+ kCFNetworkProxiesHTTPSEnable,
+ kCFNetworkProxiesHTTPSProxy,
+ kCFNetworkProxiesHTTPSPort);
+ }
+
+ if (protocolSpecificProxy.type() != QNetworkProxy::DefaultProxy)
+ result << protocolSpecificProxy;
+
+ // let's add SOCKSv5 if present too
+ QNetworkProxy socks5 = proxyFromDictionary(dict, QNetworkProxy::Socks5Proxy,
+ 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;
+ https = proxyFromDictionary(dict, QNetworkProxy::HttpProxy,
+ kCFNetworkProxiesHTTPSEnable,
+ kCFNetworkProxiesHTTPSProxy,
+ kCFNetworkProxiesHTTPSPort);
+
+
+ if (https.type() != QNetworkProxy::DefaultProxy && https != protocolSpecificProxy)
+ result << https;
+ }
+#endif // !Q_OS_IOS
+
+ return result;
+}
+
+QList<QNetworkProxy> QNetworkProxyFactory::systemProxyForQuery(const QNetworkProxyQuery &query)
+{
+ QList<QNetworkProxy> result = macQueryInternal(query);
+ if (result.isEmpty())
+ result << QNetworkProxy::NoProxy;
+
+ return result;
+}
+
+#endif
+
+QT_END_NAMESPACE