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_win.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 <qmutex.h>
10#include <qstringlist.h>
11#include <qregularexpression.h>
12#include <qurl.h>
13#include <qnetworkinterface.h>
14#include <qdebug.h>
15#include <qvarlengtharray.h>
16#include <qhash.h>
17
18#include <string.h>
19#include <qt_windows.h>
20#include <lmcons.h>
21#include <winhttp.h>
22
24
25using namespace Qt::StringLiterals;
26
28{
29 wchar_t userName[UNLEN + 1] = L"";
30 DWORD size = UNLEN;
31 if (GetUserNameW(userName, &size)) {
32 SID_NAME_USE type = SidTypeUser;
33 DWORD sidSize = 0;
34 DWORD domainSize = 0;
35 // first call is to get the correct size
36 bool bRet = LookupAccountNameW(NULL, userName, NULL, &sidSize, NULL, &domainSize, &type);
37 if (bRet == FALSE && ERROR_INSUFFICIENT_BUFFER != GetLastError())
38 return false;
39 QVarLengthArray<BYTE, 68> buff(sidSize);
40 QVarLengthArray<wchar_t, MAX_PATH> domainName(domainSize);
41 // second call to LookupAccountNameW actually gets the SID
42 // both the pointer to the buffer and the pointer to the domain name should not be NULL
43 if (LookupAccountNameW(NULL, userName, buff.data(), &sidSize, domainName.data(), &domainSize, &type))
44 return type != SidTypeUser; //returns true if the current user is not a user
45 }
46 return false;
47}
48
49static QStringList splitSpaceSemicolon(const QString &source)
50{
51 QStringList list;
52 qsizetype start = 0;
53 qsizetype end;
54 while (true) {
55 qsizetype space = source.indexOf(u' ', start);
56 qsizetype semicolon = source.indexOf(u';', start);
57 end = space;
58 if (semicolon != -1 && (end == -1 || semicolon < end))
59 end = semicolon;
60
61 if (end == -1) {
62 if (start != source.length())
63 list.append(source.mid(start));
64 return list;
65 }
66 if (start != end)
67 list.append(source.mid(start, end - start));
68 start = end + 1;
69 }
70 return list;
71}
72
73static bool isBypassed(const QString &host, const QStringList &bypassList)
74{
75 if (host.isEmpty())
76 return false;
77
78 bool isSimple = !host.contains(u'.') && !host.contains(u':');
79
80 QHostAddress ipAddress;
81 bool isIpAddress = ipAddress.setAddress(host);
82
83 // always exclude loopback
84 if (isIpAddress && ipAddress.isLoopback())
85 return true;
86
87 // does it match the list of exclusions?
88 for (const QString &entry : bypassList) {
89 if (entry == "<local>"_L1) {
90 if (isSimple)
91 return true;
92 if (isIpAddress) {
93 //exclude all local subnets
94 const auto ifaces = QNetworkInterface::allInterfaces();
95 for (const QNetworkInterface &iface : ifaces) {
96 const auto netaddrs = iface.addressEntries();
97 for (const QNetworkAddressEntry &netaddr : netaddrs) {
98 if (ipAddress.isInSubnet(netaddr.ip(), netaddr.prefixLength())) {
99 return true;
100 }
101 }
102 }
103 }
104 }
105 if (isIpAddress && ipAddress.isInSubnet(QHostAddress::parseSubnet(entry))) {
106 return true; // excluded
107 } else {
108 // do wildcard matching
109 auto rx = QRegularExpression::fromWildcard(entry, Qt::CaseInsensitive);
110 if (rx.match(host).hasMatch())
111 return true;
112 }
113 }
114
115 // host was not excluded
116 return false;
117}
118
119static QList<QNetworkProxy> filterProxyListByCapabilities(const QList<QNetworkProxy> &proxyList, const QNetworkProxyQuery &query)
120{
121 QNetworkProxy::Capabilities requiredCaps;
122 switch (query.queryType()) {
123 case QNetworkProxyQuery::TcpSocket:
124 requiredCaps = QNetworkProxy::TunnelingCapability;
125 break;
126 case QNetworkProxyQuery::UdpSocket:
127 requiredCaps = QNetworkProxy::UdpTunnelingCapability;
128 break;
129 case QNetworkProxyQuery::SctpSocket:
130 requiredCaps = QNetworkProxy::SctpTunnelingCapability;
131 break;
132 case QNetworkProxyQuery::TcpServer:
133 requiredCaps = QNetworkProxy::ListeningCapability;
134 break;
135 case QNetworkProxyQuery::SctpServer:
136 requiredCaps = QNetworkProxy::SctpListeningCapability;
137 break;
138 default:
139 return proxyList;
140 break;
141 }
142 QList<QNetworkProxy> result;
143 for (const QNetworkProxy &proxy : proxyList) {
144 if (proxy.capabilities() & requiredCaps)
145 result.append(proxy);
146 }
147 return result;
148}
149
150static QList<QNetworkProxy> removeDuplicateProxies(const QList<QNetworkProxy> &proxyList)
151{
152 QList<QNetworkProxy> result;
153 for (const QNetworkProxy &proxy : proxyList) {
154 bool append = true;
155 for (int i=0; i < result.count(); i++) {
156 if (proxy.hostName() == result.at(i).hostName()
157 && proxy.port() == result.at(i).port()) {
158 append = false;
159 // HttpProxy trumps FtpCachingProxy or HttpCachingProxy on the same host/port
160 if (proxy.type() == QNetworkProxy::HttpProxy)
161 result[i] = proxy;
162 }
163 }
164 if (append)
165 result.append(proxy);
166 }
167 return result;
168}
169
170static QList<QNetworkProxy> parseServerList(const QNetworkProxyQuery &query, const QStringList &proxyList)
171{
172 // Reference documentation from Microsoft:
173 // http://msdn.microsoft.com/en-us/library/aa383912(VS.85).aspx
174 //
175 // According to the website, the proxy server list is
176 // one or more of the space- or semicolon-separated strings in the format:
177 // ([<scheme>=][<scheme>"://"]<server>[":"<port>])
178 // The first scheme relates to the protocol tag
179 // The second scheme, if present, overrides the proxy type
180
181 QList<QNetworkProxy> result;
182 QHash<QString, QNetworkProxy> taggedProxies;
183 const QString requiredTag = query.protocolTag();
184 // windows tags are only for clients
185 bool checkTags = !requiredTag.isEmpty()
186 && query.queryType() != QNetworkProxyQuery::TcpServer
187 && query.queryType() != QNetworkProxyQuery::SctpServer;
188 for (const QString &entry : proxyList) {
189 qsizetype server = 0;
190
191 QNetworkProxy::ProxyType proxyType = QNetworkProxy::HttpProxy;
192 quint16 port = 8080;
193
194 qsizetype pos = entry.indexOf(u'=');
195 QStringView scheme;
196 QStringView protocolTag;
197 if (pos != -1) {
198 scheme = protocolTag = QStringView{entry}.left(pos);
199 server = pos + 1;
200 }
201 pos = entry.indexOf("://"_L1, server);
202 if (pos != -1) {
203 scheme = QStringView{entry}.mid(server, pos - server);
204 server = pos + 3;
205 }
206
207 if (!scheme.isEmpty()) {
208 if (scheme == "http"_L1 || scheme == "https"_L1) {
209 // no-op
210 // defaults are above
211 } else if (scheme == "socks"_L1 || scheme == "socks5"_L1) {
212 proxyType = QNetworkProxy::Socks5Proxy;
213 port = 1080;
214 } else if (scheme == "ftp"_L1) {
215 proxyType = QNetworkProxy::FtpCachingProxy;
216 port = 2121;
217 } else {
218 // unknown proxy type
219 continue;
220 }
221 }
222
223 pos = entry.indexOf(u':', server);
224 if (pos != -1) {
225 bool ok;
226 uint value = QStringView{entry}.mid(pos + 1).toUInt(&ok);
227 if (!ok || value > 65535)
228 continue; // invalid port number
229
230 port = value;
231 } else {
232 pos = entry.length();
233 }
234
235 result << QNetworkProxy(proxyType, entry.mid(server, pos - server), port);
236 if (!protocolTag.isEmpty())
237 taggedProxies.insert(protocolTag.toString(), result.constLast());
238 }
239
240 if (checkTags && taggedProxies.contains(requiredTag)) {
241 if (query.queryType() == QNetworkProxyQuery::UrlRequest) {
242 result.clear();
243 result.append(taggedProxies.value(requiredTag));
244 return result;
245 } else {
246 result.prepend(taggedProxies.value(requiredTag));
247 }
248 }
249 if (!checkTags || requiredTag != "http"_L1) {
250 // if there are different http proxies for http and https, prefer the https one (more likely to be capable of CONNECT)
251 QNetworkProxy httpProxy = taggedProxies.value("http"_L1);
252 QNetworkProxy httpsProxy = taggedProxies.value("http"_L1);
253 if (httpProxy != httpsProxy && httpProxy.type() == QNetworkProxy::HttpProxy && httpsProxy.type() == QNetworkProxy::HttpProxy) {
254 for (int i = 0; i < result.count(); i++) {
255 if (httpProxy == result.at(i))
256 result[i].setType(QNetworkProxy::HttpCachingProxy);
257 }
258 }
259 }
260 result = filterProxyListByCapabilities(result, query);
261 return removeDuplicateProxies(result);
262}
263
264namespace {
265class QRegistryWatcher {
266 Q_DISABLE_COPY_MOVE(QRegistryWatcher)
267public:
268 QRegistryWatcher() = default;
269
270 void addLocation(HKEY hive, const QString& path)
271 {
272 HKEY openedKey;
273 if (RegOpenKeyEx(hive, reinterpret_cast<const wchar_t*>(path.utf16()), 0, KEY_READ, &openedKey) != ERROR_SUCCESS)
274 return;
275
276 const DWORD filter = REG_NOTIFY_CHANGE_NAME | REG_NOTIFY_CHANGE_ATTRIBUTES |
277 REG_NOTIFY_CHANGE_LAST_SET | REG_NOTIFY_CHANGE_SECURITY;
278
279 // Watch the registry key for a change of value.
280 HANDLE handle = CreateEvent(NULL, true, false, NULL);
281 if (RegNotifyChangeKeyValue(openedKey, true, filter, handle, true) != ERROR_SUCCESS) {
282 CloseHandle(handle);
283 return;
284 }
285 m_watchEvents.append(handle);
286 m_registryHandles.append(openedKey);
287 }
288
289 bool hasChanged() const {
290 return !isEmpty() &&
291 WaitForMultipleObjects(m_watchEvents.size(), m_watchEvents.data(), false, 0) < WAIT_OBJECT_0 + m_watchEvents.size();
292 }
293
294 bool isEmpty() const {
295 return m_watchEvents.isEmpty();
296 }
297
298 void clear() {
299 for (HANDLE event : std::as_const(m_watchEvents))
300 CloseHandle(event);
301 for (HKEY key : std::as_const(m_registryHandles))
302 RegCloseKey(key);
303
304 m_watchEvents.clear();
305 m_registryHandles.clear();
306 }
307
308 ~QRegistryWatcher() {
309 clear();
310 }
311
312private:
313 QList<HANDLE> m_watchEvents;
314 QList<HKEY> m_registryHandles;
315};
316} // namespace
317
341
342Q_GLOBAL_STATIC(QWindowsSystemProxy, systemProxy)
343
344QWindowsSystemProxy::QWindowsSystemProxy()
345 : hHttpSession(0), initialized(false), functional(false), isAutoConfig(false)
346{
347 defaultResult << QNetworkProxy::NoProxy;
348}
349
351{
352 if (hHttpSession)
353 WinHttpCloseHandle(hHttpSession);
354}
355
357{
358 autoConfigUrl.clear();
359 proxyServerList.clear();
360 proxyBypass.clear();
361 defaultResult.clear();
362 defaultResult << QNetworkProxy::NoProxy;
363 functional = false;
364 isAutoConfig = false;
365}
366
368{
369 bool proxySettingsChanged = false;
370 proxySettingsChanged = proxySettingsWatcher.hasChanged();
371
372 if (initialized && !proxySettingsChanged)
373 return;
374 initialized = true;
375
376 reset();
377
378 proxySettingsWatcher.clear(); // needs reset to trigger a new detection
379 proxySettingsWatcher.addLocation(HKEY_CURRENT_USER, QStringLiteral("Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings"));
380 proxySettingsWatcher.addLocation(HKEY_LOCAL_MACHINE, QStringLiteral("Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings"));
381 proxySettingsWatcher.addLocation(HKEY_LOCAL_MACHINE, QStringLiteral("Software\\Policies\\Microsoft\\Windows\\CurrentVersion\\Internet Settings"));
382
383 // Try to obtain the Internet Explorer configuration.
384 WINHTTP_CURRENT_USER_IE_PROXY_CONFIG ieProxyConfig;
385 const bool hasIEConfig = WinHttpGetIEProxyConfigForCurrentUser(&ieProxyConfig);
386 if (hasIEConfig) {
387 if (ieProxyConfig.lpszAutoConfigUrl) {
388 autoConfigUrl = QString::fromWCharArray(ieProxyConfig.lpszAutoConfigUrl);
389 GlobalFree(ieProxyConfig.lpszAutoConfigUrl);
390 }
391 if (ieProxyConfig.lpszProxy) {
392 // http://msdn.microsoft.com/en-us/library/aa384250%28VS.85%29.aspx speaks only about a "proxy URL",
393 // not multiple URLs. However we tested this and it can return multiple URLs. So we use splitSpaceSemicolon
394 // on it.
395 proxyServerList = splitSpaceSemicolon(QString::fromWCharArray(ieProxyConfig.lpszProxy));
396 GlobalFree(ieProxyConfig.lpszProxy);
397 }
398 if (ieProxyConfig.lpszProxyBypass) {
399 proxyBypass = splitSpaceSemicolon(QString::fromWCharArray(ieProxyConfig.lpszProxyBypass));
400 GlobalFree(ieProxyConfig.lpszProxyBypass);
401 }
402 }
403
404 if (!hasIEConfig ||
405 (currentProcessIsService() && proxyServerList.isEmpty() && proxyBypass.isEmpty())) {
406 // no user configuration
407 // attempt to get the default configuration instead
408 // that config will serve as default if WPAD fails
409 WINHTTP_PROXY_INFO proxyInfo;
410 if (WinHttpGetDefaultProxyConfiguration(&proxyInfo) &&
411 proxyInfo.dwAccessType == WINHTTP_ACCESS_TYPE_NAMED_PROXY) {
412 // we got information from the registry
413 // overwrite the IE configuration, if any
414
415 proxyBypass = splitSpaceSemicolon(QString::fromWCharArray(proxyInfo.lpszProxyBypass));
416 proxyServerList = splitSpaceSemicolon(QString::fromWCharArray(proxyInfo.lpszProxy));
417 }
418
419 if (proxyInfo.lpszProxy)
420 GlobalFree(proxyInfo.lpszProxy);
421 if (proxyInfo.lpszProxyBypass)
422 GlobalFree(proxyInfo.lpszProxyBypass);
423 }
424
425 hHttpSession = NULL;
426 if (ieProxyConfig.fAutoDetect || !autoConfigUrl.isEmpty()) {
427 // open the handle and obtain the options
428 hHttpSession = WinHttpOpen(L"Qt System Proxy access/1.0",
429 WINHTTP_ACCESS_TYPE_NO_PROXY,
430 WINHTTP_NO_PROXY_NAME,
431 WINHTTP_NO_PROXY_BYPASS,
432 0);
433 if (!hHttpSession)
434 return;
435
436 isAutoConfig = true;
437 memset(&autoProxyOptions, 0, sizeof autoProxyOptions);
438 autoProxyOptions.fAutoLogonIfChallenged = false;
439 //Although it is possible to specify dwFlags = WINHTTP_AUTOPROXY_AUTO_DETECT | WINHTTP_AUTOPROXY_CONFIG_URL
440 //this has poor performance (WPAD is attempted for every url, taking 2.5 seconds per interface,
441 //before the configured pac file is used)
442 if (ieProxyConfig.fAutoDetect) {
443 autoProxyOptions.dwFlags = WINHTTP_AUTOPROXY_AUTO_DETECT;
444 autoProxyOptions.dwAutoDetectFlags = WINHTTP_AUTO_DETECT_TYPE_DHCP |
445 WINHTTP_AUTO_DETECT_TYPE_DNS_A;
446 } else {
447 autoProxyOptions.dwFlags = WINHTTP_AUTOPROXY_CONFIG_URL;
448 autoProxyOptions.lpszAutoConfigUrl = reinterpret_cast<LPCWSTR>(autoConfigUrl.utf16());
449 }
450 }
451
452 functional = isAutoConfig || !proxyServerList.isEmpty();
453}
454
455QList<QNetworkProxy> QNetworkProxyFactory::systemProxyForQuery(const QNetworkProxyQuery &query)
456{
457 QWindowsSystemProxy *sp = systemProxy();
458 if (!sp)
459 return QList<QNetworkProxy>() << QNetworkProxy();
460
461 QMutexLocker locker(&sp->mutex);
462 sp->init();
463 if (!sp->functional)
464 return sp->defaultResult;
465
466 if (sp->isAutoConfig) {
467 WINHTTP_PROXY_INFO proxyInfo;
468
469 // try to get the proxy config for the URL
470 QUrl url = query.url();
471 // url could be empty, e.g. from QNetworkProxy::applicationProxy(), that's fine,
472 // we'll still ask for the proxy.
473 // But for a file url, we know we don't need one.
474 if (url.scheme() == "file"_L1 || url.scheme() == "qrc"_L1)
475 return sp->defaultResult;
476 if (query.queryType() != QNetworkProxyQuery::UrlRequest) {
477 // change the scheme to https, maybe it'll work
478 url.setScheme("https"_L1);
479 }
480
481 QString urlQueryString = url.toString();
482 if (urlQueryString.size() > 2083) {
483 // calls to WinHttpGetProxyForUrl with urls longer than 2083 characters
484 // fail with error code ERROR_INVALID_PARAMETER(87), so we truncate it
485 qWarning("Proxy query URL too long for windows API, try with truncated URL");
486 urlQueryString = url.toString().left(2083);
487 }
488
489 bool getProxySucceeded = WinHttpGetProxyForUrl(sp->hHttpSession,
490 reinterpret_cast<LPCWSTR>(urlQueryString.utf16()),
491 &sp->autoProxyOptions,
492 &proxyInfo);
493 DWORD getProxyError = GetLastError();
494
495 if (!getProxySucceeded
496 && (ERROR_WINHTTP_AUTODETECTION_FAILED == getProxyError)) {
497 // WPAD failed
498 if (sp->autoConfigUrl.isEmpty()) {
499 //No config file could be retrieved on the network.
500 //Don't search for it next time again.
501 sp->isAutoConfig = false;
502 } else {
503 //pac file URL is specified as well, try using that
504 sp->autoProxyOptions.dwFlags = WINHTTP_AUTOPROXY_CONFIG_URL;
505 sp->autoProxyOptions.lpszAutoConfigUrl =
506 reinterpret_cast<LPCWSTR>(sp->autoConfigUrl.utf16());
507 getProxySucceeded = WinHttpGetProxyForUrl(sp->hHttpSession,
508 reinterpret_cast<LPCWSTR>(urlQueryString.utf16()),
509 &sp->autoProxyOptions,
510 &proxyInfo);
511 getProxyError = GetLastError();
512 }
513 }
514
515 if (!getProxySucceeded
516 && (ERROR_WINHTTP_LOGIN_FAILURE == getProxyError)) {
517 // We first tried without AutoLogon, because this might prevent caching the result.
518 // But now we've to enable it (http://msdn.microsoft.com/en-us/library/aa383153%28v=VS.85%29.aspx)
519 sp->autoProxyOptions.fAutoLogonIfChallenged = TRUE;
520 getProxySucceeded = WinHttpGetProxyForUrl(sp->hHttpSession,
521 reinterpret_cast<LPCWSTR>(urlQueryString.utf16()),
522 &sp->autoProxyOptions,
523 &proxyInfo);
524 getProxyError = GetLastError();
525 }
526
527 if (!getProxySucceeded
528 && (ERROR_WINHTTP_UNABLE_TO_DOWNLOAD_SCRIPT == getProxyError)) {
529 // PAC file url is not connectable, or server returned error (e.g. http 404)
530 //Don't search for it next time again.
531 sp->isAutoConfig = false;
532 }
533
534 if (getProxySucceeded) {
535 // yes, we got a config for this URL
536 QString proxyBypass = QString::fromWCharArray(proxyInfo.lpszProxyBypass);
537 QStringList proxyServerList = splitSpaceSemicolon(QString::fromWCharArray(proxyInfo.lpszProxy));
538 if (proxyInfo.lpszProxy)
539 GlobalFree(proxyInfo.lpszProxy);
540 if (proxyInfo.lpszProxyBypass)
541 GlobalFree(proxyInfo.lpszProxyBypass);
542
543 if (proxyInfo.dwAccessType == WINHTTP_ACCESS_TYPE_NO_PROXY)
544 return sp->defaultResult; //i.e. the PAC file result was "DIRECT"
545 if (isBypassed(query.peerHostName(), splitSpaceSemicolon(proxyBypass)))
546 return sp->defaultResult;
547 return parseServerList(query, proxyServerList);
548 }
549
550 // GetProxyForUrl failed, fall back to static configuration
551 }
552
553 // static configuration
554 if (isBypassed(query.peerHostName(), sp->proxyBypass))
555 return sp->defaultResult;
556
557 QList<QNetworkProxy> result = parseServerList(query, sp->proxyServerList);
558 // In some cases, this was empty. See SF task 00062670
559 if (result.isEmpty())
560 return sp->defaultResult;
561
562 return result;
563}
564
565QT_END_NAMESPACE
566
567#endif
The QNetworkProxy class provides a network layer proxy.
QList< QNetworkProxy > defaultResult
WINHTTP_AUTOPROXY_OPTIONS autoProxyOptions
QRegistryWatcher proxySettingsWatcher
static QStringList splitSpaceSemicolon(const QString &source)
static QList< QNetworkProxy > removeDuplicateProxies(const QList< QNetworkProxy > &proxyList)
static bool isBypassed(const QString &host, const QStringList &bypassList)
static QList< QNetworkProxy > filterProxyListByCapabilities(const QList< QNetworkProxy > &proxyList, const QNetworkProxyQuery &query)
static QList< QNetworkProxy > parseServerList(const QNetworkProxyQuery &query, const QStringList &proxyList)
static bool currentProcessIsService()