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