Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qnetworkproxy_darwin.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:trusted-data
4
6
7#ifndef QT_NO_NETWORKPROXY
8
9#include <CFNetwork/CFNetwork.h>
10#include <CoreFoundation/CoreFoundation.h>
11#include <SystemConfiguration/SystemConfiguration.h>
12
13#include <QtCore/QRegularExpression>
14#include <QtCore/QStringList>
15#include <QtCore/QUrl>
16#include <QtCore/qendian.h>
17#include <QtCore/qstringlist.h>
18#include <QtCore/qsystemdetection.h>
19#include "private/qcore_mac_p.h"
20
21/*
22 * MacOS X has a proxy configuration module in System Preferences (on
23 * MacOS X 10.5, it's in Network, Advanced), where one can set the
24 * proxy settings for:
25 *
26 * \list
27 * \li FTP proxy
28 * \li Web Proxy (HTTP)
29 * \li Secure Web Proxy (HTTPS)
30 * \li Streaming Proxy (RTSP)
31 * \li SOCKS Proxy
32 * \li Gopher Proxy
33 * \li URL for Automatic Proxy Configuration (PAC scripts)
34 * \li Bypass list (by default: *.local, 169.254/16)
35 * \endlist
36 *
37 * The matching configuration can be obtained by calling CFNetworkCopySystemProxySettings()
38 * (from <CFNetwork/CFProxySupport.h>). See
39 * Apple's documentation:
40 *
41 * https://developer.apple.com/documentation/cfnetwork/1426754-cfnetworkcopysystemproxysettings?language=objc
42 *
43 */
44
45QT_BEGIN_NAMESPACE
46
47using namespace Qt::StringLiterals;
48
49static bool isHostExcluded(CFDictionaryRef dict, const QString &host)
50{
51 Q_ASSERT(dict);
52
53 if (host.isEmpty())
54 return true;
55
56#ifndef Q_OS_IOS
57 // On iOS all those keys are not available, and worse so - entries
58 // for HTTPS are not in the dictionary, but instead in some nested dictionary
59 // with undocumented keys/object types.
60 bool isSimple = !host.contains(u'.') && !host.contains(u':');
61 CFNumberRef excludeSimples;
62 if (isSimple &&
63 (excludeSimples = (CFNumberRef)CFDictionaryGetValue(dict, kCFNetworkProxiesExcludeSimpleHostnames))) {
64 int enabled;
65 if (CFNumberGetValue(excludeSimples, kCFNumberIntType, &enabled) && enabled)
66 return true;
67 }
68
69 QHostAddress ipAddress;
70 bool isIpAddress = ipAddress.setAddress(host);
71
72 // not a simple host name
73 // does it match the list of exclusions?
74 CFArrayRef exclusionList = (CFArrayRef)CFDictionaryGetValue(dict, kCFNetworkProxiesExceptionsList);
75 if (!exclusionList)
76 return false;
77
78 CFIndex size = CFArrayGetCount(exclusionList);
79 for (CFIndex i = 0; i < size; ++i) {
80 CFStringRef cfentry = (CFStringRef)CFArrayGetValueAtIndex(exclusionList, i);
81 QString entry = QString::fromCFString(cfentry);
82
83 if (isIpAddress && ipAddress.isInSubnet(QHostAddress::parseSubnet(entry))) {
84 return true; // excluded
85 } else {
86 // do wildcard matching
87 auto rx = QRegularExpression::fromWildcard(entry, Qt::CaseInsensitive);
88 if (rx.match(host).hasMatch())
89 return true;
90 }
91 }
92#else
93 Q_UNUSED(dict);
94#endif // Q_OS_IOS
95 // host was not excluded
96 return false;
97}
98
99static QNetworkProxy proxyFromDictionary(CFDictionaryRef dict, QNetworkProxy::ProxyType type,
100 CFStringRef enableKey, CFStringRef hostKey,
101 CFStringRef portKey)
102{
103 CFNumberRef protoEnabled;
104 CFNumberRef protoPort;
105 CFStringRef protoHost;
106 if (enableKey
107 && (protoEnabled = (CFNumberRef)CFDictionaryGetValue(dict, enableKey))
108 && (protoHost = (CFStringRef)CFDictionaryGetValue(dict, hostKey))
109 && (protoPort = (CFNumberRef)CFDictionaryGetValue(dict, portKey))) {
110 int enabled;
111 if (CFNumberGetValue(protoEnabled, kCFNumberIntType, &enabled) && enabled) {
112 QString host = QString::fromCFString(protoHost);
113
114 int port;
115 CFNumberGetValue(protoPort, kCFNumberIntType, &port);
116
117 return QNetworkProxy(type, host, port);
118 }
119 }
120
121 // proxy not enabled
122 return QNetworkProxy();
123}
124
125static QNetworkProxy proxyFromDictionary(CFDictionaryRef dict)
126{
127 QNetworkProxy::ProxyType proxyType = QNetworkProxy::DefaultProxy;
128 QString hostName;
129 quint16 port = 0;
130 QString user;
131 QString password;
132
133 CFStringRef cfProxyType = (CFStringRef)CFDictionaryGetValue(dict, kCFProxyTypeKey);
134 if (CFStringCompare(cfProxyType, kCFProxyTypeNone, 0) == kCFCompareEqualTo) {
135 proxyType = QNetworkProxy::NoProxy;
136 } else if (CFStringCompare(cfProxyType, kCFProxyTypeFTP, 0) == kCFCompareEqualTo) {
137 proxyType = QNetworkProxy::FtpCachingProxy;
138 } else if (CFStringCompare(cfProxyType, kCFProxyTypeHTTP, 0) == kCFCompareEqualTo) {
139 proxyType = QNetworkProxy::HttpProxy;
140 } else if (CFStringCompare(cfProxyType, kCFProxyTypeHTTPS, 0) == kCFCompareEqualTo) {
141 proxyType = QNetworkProxy::HttpProxy;
142 } else if (CFStringCompare(cfProxyType, kCFProxyTypeSOCKS, 0) == kCFCompareEqualTo) {
143 proxyType = QNetworkProxy::Socks5Proxy;
144 }
145
146 hostName = QString::fromCFString((CFStringRef)CFDictionaryGetValue(dict, kCFProxyHostNameKey));
147 user = QString::fromCFString((CFStringRef)CFDictionaryGetValue(dict, kCFProxyUsernameKey));
148 password = QString::fromCFString((CFStringRef)CFDictionaryGetValue(dict, kCFProxyPasswordKey));
149
150 CFNumberRef portNumber = (CFNumberRef)CFDictionaryGetValue(dict, kCFProxyPortNumberKey);
151 if (portNumber) {
152 CFNumberGetValue(portNumber, kCFNumberSInt16Type, &port);
153 }
154
155 return QNetworkProxy(proxyType, hostName, port, user, password);
156}
157
158namespace {
159struct PACInfo {
160 QCFType<CFArrayRef> proxies;
161 QCFType<CFErrorRef> error;
162 bool done = false;
163};
164
165void proxyAutoConfigCallback(void *client, CFArrayRef proxylist, CFErrorRef error)
166{
167 Q_ASSERT(client);
168
169 PACInfo *info = static_cast<PACInfo *>(client);
170 info->done = true;
171
172 if (error) {
173 CFRetain(error);
174 info->error = error;
175 }
176 if (proxylist) {
177 CFRetain(proxylist);
178 info->proxies = proxylist;
179 }
180}
181
182QCFType<CFStringRef> stringByAddingPercentEscapes(CFStringRef originalPath)
183{
184 Q_ASSERT(originalPath);
185 const auto qtPath = QString::fromCFString(originalPath);
186 const auto escaped = QString::fromUtf8(QUrl(qtPath).toEncoded());
187 return escaped.toCFString();
188}
189
190#ifdef Q_OS_IOS
191QList<QNetworkProxy> proxiesForQueryUrl(CFDictionaryRef dict, const QUrl &url)
192{
193 Q_ASSERT(dict);
194
195 const QCFType<CFURLRef> cfUrl = url.toCFURL();
196 const QCFType<CFArrayRef> proxies = CFNetworkCopyProxiesForURL(cfUrl, dict);
197 Q_ASSERT(proxies);
198
199 QList<QNetworkProxy> result;
200 const auto count = CFArrayGetCount(proxies);
201 if (!count) // Could be no proper proxy or host excluded.
202 return result;
203
204 for (CFIndex i = 0; i < count; ++i) {
205 const void *obj = CFArrayGetValueAtIndex(proxies, i);
206 if (CFGetTypeID(obj) != CFDictionaryGetTypeID())
207 continue;
208 const QNetworkProxy proxy = proxyFromDictionary(static_cast<CFDictionaryRef>(obj));
209 if (proxy.type() == QNetworkProxy::NoProxy || proxy.type() == QNetworkProxy::DefaultProxy)
210 continue;
211 result << proxy;
212 }
213
214 return result;
215}
216#endif // Q_OS_IOS
217} // unnamed namespace.
218
219QList<QNetworkProxy> macQueryInternal(const QNetworkProxyQuery &query)
220{
221 QList<QNetworkProxy> result;
222
223 // obtain a dictionary to the proxy settings:
224 const QCFType<CFDictionaryRef> dict = CFNetworkCopySystemProxySettings();
225 if (!dict) {
226 qWarning("QNetworkProxyFactory::systemProxyForQuery: CFNetworkCopySystemProxySettings returned nullptr");
227 return result; // failed
228 }
229
230 if (isHostExcluded(dict, query.peerHostName()))
231 return result; // no proxy for this host
232
233 // is there a PAC enabled? If so, use it first.
234 CFNumberRef pacEnabled;
235 if ((pacEnabled = (CFNumberRef)CFDictionaryGetValue(dict, kCFNetworkProxiesProxyAutoConfigEnable))) {
236 int enabled;
237 if (CFNumberGetValue(pacEnabled, kCFNumberIntType, &enabled) && enabled) {
238 // PAC is enabled
239 // kSCPropNetProxiesProxyAutoConfigURLString returns the URL string
240 // as entered in the system proxy configuration dialog
241 CFStringRef pacLocationSetting = (CFStringRef)CFDictionaryGetValue(dict, kCFNetworkProxiesProxyAutoConfigURLString);
242 auto cfPacLocation = stringByAddingPercentEscapes(pacLocationSetting);
243 QCFType<CFDataRef> pacData;
244 QCFType<CFURLRef> pacUrl = CFURLCreateWithString(kCFAllocatorDefault, cfPacLocation, NULL);
245 if (!pacUrl) {
246 qWarning("Invalid PAC URL \"%s\"", qPrintable(QString::fromCFString(cfPacLocation)));
247 return result;
248 }
249
250 QByteArray encodedURL = query.url().toEncoded(); // converted to UTF-8
251 if (encodedURL.isEmpty()) {
252 return result; // Invalid URL, abort
253 }
254
255 QCFType<CFURLRef> targetURL = CFURLCreateWithBytes(kCFAllocatorDefault, (UInt8*)encodedURL.data(), encodedURL.size(), kCFStringEncodingUTF8, NULL);
256 if (!targetURL) {
257 return result; // URL creation problem, abort
258 }
259
260 CFStreamClientContext pacCtx;
261 pacCtx.version = 0;
262 PACInfo pacInfo;
263 pacCtx.info = &pacInfo;
264 pacCtx.retain = NULL;
265 pacCtx.release = NULL;
266 pacCtx.copyDescription = NULL;
267
268 static CFStringRef pacRunLoopMode = CFSTR("qtPACRunLoopMode");
269
270 QCFType<CFRunLoopSourceRef> pacRunLoopSource = CFNetworkExecuteProxyAutoConfigurationURL(pacUrl, targetURL, &proxyAutoConfigCallback, &pacCtx);
271 CFRunLoopAddSource(CFRunLoopGetCurrent(), pacRunLoopSource, pacRunLoopMode);
272 while (!pacInfo.done)
273 CFRunLoopRunInMode(pacRunLoopMode, 1000, /*returnAfterSourceHandled*/ true);
274
275 if (!pacInfo.proxies) {
276 QString pacLocation = QString::fromCFString(cfPacLocation);
277 QCFType<CFStringRef> pacErrorDescription = CFErrorCopyDescription(pacInfo.error);
278 qWarning("Execution of PAC script at \"%s\" failed: %s", qPrintable(pacLocation), qPrintable(QString::fromCFString(pacErrorDescription)));
279 return result;
280 }
281
282 CFIndex size = CFArrayGetCount(pacInfo.proxies);
283 for (CFIndex i = 0; i < size; ++i) {
284 CFDictionaryRef proxy = (CFDictionaryRef)CFArrayGetValueAtIndex(pacInfo.proxies, i);
285 result << proxyFromDictionary(proxy);
286 }
287 return result;
288 }
289 }
290
291 // No PAC, decide which proxy we're looking for based on the query
292 // try the protocol-specific proxy
293 const QString protocol = query.protocolTag();
294 QNetworkProxy protocolSpecificProxy;
295 if (protocol.compare("http"_L1, Qt::CaseInsensitive) == 0) {
296 protocolSpecificProxy =
297 proxyFromDictionary(dict, QNetworkProxy::HttpProxy,
298 kCFNetworkProxiesHTTPEnable,
299 kCFNetworkProxiesHTTPProxy,
300 kCFNetworkProxiesHTTPPort);
301 }
302
303
304#ifdef Q_OS_IOS
305 if (protocolSpecificProxy.type() != QNetworkProxy::DefaultProxy
306 && protocolSpecificProxy.type() != QNetworkProxy::DefaultProxy) {
307 // HTTP proxy is enabled (on iOS there is no separate HTTPS, though
308 // 'dict' contains deeply buried entries which are the same as HTTP.
309 result << protocolSpecificProxy;
310 }
311
312 // TODO: check query.queryType()? It's possible, the exclude list
313 // did exclude it but above we added a proxy because HTTP proxy
314 // is found. We'll deal with such a situation later, since now NMI.
315 const auto proxiesForUrl = proxiesForQueryUrl(dict, query.url());
316 for (const auto &proxy : proxiesForUrl) {
317 if (!result.contains(proxy))
318 result << proxy;
319 }
320#else
321 bool isHttps = false;
322 if (protocol.compare("ftp"_L1, Qt::CaseInsensitive) == 0) {
323 protocolSpecificProxy =
324 proxyFromDictionary(dict, QNetworkProxy::FtpCachingProxy,
325 kCFNetworkProxiesFTPEnable,
326 kCFNetworkProxiesFTPProxy,
327 kCFNetworkProxiesFTPPort);
328 } else if (protocol.compare("https"_L1, Qt::CaseInsensitive) == 0) {
329 isHttps = true;
330 protocolSpecificProxy =
331 proxyFromDictionary(dict, QNetworkProxy::HttpProxy,
332 kCFNetworkProxiesHTTPSEnable,
333 kCFNetworkProxiesHTTPSProxy,
334 kCFNetworkProxiesHTTPSPort);
335 }
336
337 if (protocolSpecificProxy.type() != QNetworkProxy::DefaultProxy)
338 result << protocolSpecificProxy;
339
340 // let's add SOCKSv5 if present too
341 QNetworkProxy socks5 = proxyFromDictionary(dict, QNetworkProxy::Socks5Proxy,
342 kCFNetworkProxiesSOCKSEnable,
343 kCFNetworkProxiesSOCKSProxy,
344 kCFNetworkProxiesSOCKSPort);
345 if (socks5.type() != QNetworkProxy::DefaultProxy)
346 result << socks5;
347
348 // let's add the HTTPS proxy if present (and if we haven't added
349 // yet)
350 if (!isHttps) {
351 QNetworkProxy https;
352 https = proxyFromDictionary(dict, QNetworkProxy::HttpProxy,
353 kCFNetworkProxiesHTTPSEnable,
354 kCFNetworkProxiesHTTPSProxy,
355 kCFNetworkProxiesHTTPSPort);
356
357
358 if (https.type() != QNetworkProxy::DefaultProxy && https != protocolSpecificProxy)
359 result << https;
360 }
361#endif // !Q_OS_IOS
362
363 return result;
364}
365
366QList<QNetworkProxy> QNetworkProxyFactory::systemProxyForQuery(const QNetworkProxyQuery &query)
367{
368 QList<QNetworkProxy> result = macQueryInternal(query);
369 if (result.isEmpty())
370 result << QNetworkProxy::NoProxy;
371
372 return result;
373}
374
375QT_END_NAMESPACE
376
377#endif // QT_NO_NETWORKPROXY
The QNetworkProxy class provides a network layer proxy.
static QNetworkProxy proxyFromDictionary(CFDictionaryRef dict)
static bool isHostExcluded(CFDictionaryRef dict, const QString &host)
static QNetworkProxy proxyFromDictionary(CFDictionaryRef dict, QNetworkProxy::ProxyType type, CFStringRef enableKey, CFStringRef hostKey, CFStringRef portKey)
QList< QNetworkProxy > macQueryInternal(const QNetworkProxyQuery &query)