1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the QtNetwork module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39 
40 #include "qnetworkproxy.h"
41 
42 #ifndef QT_NO_NETWORKPROXY
43 
44 #include <qmutex.h>
45 #include <qstringlist.h>
46 #include <qregexp.h>
47 #include <qurl.h>
48 #include <private/qsystemlibrary_p.h>
49 #include <qnetworkinterface.h>
50 #include <qdebug.h>
51 
52 #include <string.h>
53 #include <qt_windows.h>
54 #include <wininet.h>
55 #include <lmcons.h>
56 
57 /*
58  * Information on the WinHTTP DLL:
59  *  http://msdn.microsoft.com/en-us/library/aa384122(VS.85).aspx example for WPAD
60  *
61  *  http://msdn.microsoft.com/en-us/library/aa384097(VS.85).aspx WinHttpGetProxyForUrl
62  *  http://msdn.microsoft.com/en-us/library/aa384096(VS.85).aspx WinHttpGetIEProxyConfigForCurrentUs
63  *  http://msdn.microsoft.com/en-us/library/aa384095(VS.85).aspx WinHttpGetDefaultProxyConfiguration
64  */
65 
66 // We don't want to include winhttp.h because that's not
67 // present in some Windows SDKs (I don't know why)
68 // So, instead, copy the definitions here
69 
70 typedef struct {
71   DWORD dwFlags;
72   DWORD dwAutoDetectFlags;
73   LPCWSTR lpszAutoConfigUrl;
74   LPVOID lpvReserved;
75   DWORD dwReserved;
76   BOOL fAutoLogonIfChallenged;
77 } WINHTTP_AUTOPROXY_OPTIONS;
78 
79 typedef struct {
80   DWORD dwAccessType;
81   LPWSTR lpszProxy;
82   LPWSTR lpszProxyBypass;
83 } WINHTTP_PROXY_INFO;
84 
85 typedef struct {
86   BOOL fAutoDetect;
87   LPWSTR lpszAutoConfigUrl;
88   LPWSTR lpszProxy;
89   LPWSTR lpszProxyBypass;
90 } WINHTTP_CURRENT_USER_IE_PROXY_CONFIG;
91 
92 #define WINHTTP_AUTOPROXY_AUTO_DETECT           0x00000001
93 #define WINHTTP_AUTOPROXY_CONFIG_URL            0x00000002
94 
95 #define WINHTTP_AUTO_DETECT_TYPE_DHCP           0x00000001
96 #define WINHTTP_AUTO_DETECT_TYPE_DNS_A          0x00000002
97 
98 #define WINHTTP_ACCESS_TYPE_DEFAULT_PROXY               0
99 #define WINHTTP_ACCESS_TYPE_NO_PROXY                    1
100 #define WINHTTP_ACCESS_TYPE_NAMED_PROXY                 3
101 
102 #define WINHTTP_NO_PROXY_NAME     NULL
103 #define WINHTTP_NO_PROXY_BYPASS   NULL
104 
105 #define WINHTTP_ERROR_BASE                      12000
106 #define ERROR_WINHTTP_LOGIN_FAILURE             (WINHTTP_ERROR_BASE + 15)
107 #define ERROR_WINHTTP_UNABLE_TO_DOWNLOAD_SCRIPT (WINHTTP_ERROR_BASE + 167)
108 #define ERROR_WINHTTP_AUTODETECTION_FAILED      (WINHTTP_ERROR_BASE + 180)
109 
110 QT_BEGIN_NAMESPACE
111 
112 typedef BOOL (WINAPI * PtrWinHttpGetProxyForUrl)(HINTERNET, LPCWSTR, WINHTTP_AUTOPROXY_OPTIONS*, WINHTTP_PROXY_INFO*);
113 typedef HINTERNET (WINAPI * PtrWinHttpOpen)(LPCWSTR, DWORD, LPCWSTR, LPCWSTR,DWORD);
114 typedef BOOL (WINAPI * PtrWinHttpGetDefaultProxyConfiguration)(WINHTTP_PROXY_INFO*);
115 typedef BOOL (WINAPI * PtrWinHttpGetIEProxyConfigForCurrentUser)(WINHTTP_CURRENT_USER_IE_PROXY_CONFIG*);
116 typedef BOOL (WINAPI * PtrWinHttpCloseHandle)(HINTERNET);
117 typedef BOOL (WINAPI * PtrCloseServiceHandle)(SC_HANDLE hSCObject);
118 static PtrWinHttpGetProxyForUrl ptrWinHttpGetProxyForUrl = 0;
119 static PtrWinHttpOpen ptrWinHttpOpen = 0;
120 static PtrWinHttpGetDefaultProxyConfiguration ptrWinHttpGetDefaultProxyConfiguration = 0;
121 static PtrWinHttpGetIEProxyConfigForCurrentUser ptrWinHttpGetIEProxyConfigForCurrentUser = 0;
122 static PtrWinHttpCloseHandle ptrWinHttpCloseHandle = 0;
123 
124 
currentProcessIsService()125 static bool currentProcessIsService()
126 {
127     typedef BOOL (WINAPI *PtrGetUserName)(LPTSTR lpBuffer, LPDWORD lpnSize);
128     typedef BOOL (WINAPI *PtrLookupAccountName)(LPCTSTR lpSystemName, LPCTSTR lpAccountName, PSID Sid,
129                                   LPDWORD cbSid, LPTSTR ReferencedDomainName, LPDWORD cchReferencedDomainName, PSID_NAME_USE peUse);
130     static PtrGetUserName ptrGetUserName = (PtrGetUserName)QSystemLibrary::resolve(QLatin1String("Advapi32"), "GetUserNameW");
131     static PtrLookupAccountName ptrLookupAccountName = (PtrLookupAccountName)QSystemLibrary::resolve(QLatin1String("Advapi32"), "LookupAccountNameW");
132 
133     if (ptrGetUserName && ptrLookupAccountName) {
134         wchar_t userName[UNLEN + 1] = L"";
135         DWORD size = UNLEN;
136         if (ptrGetUserName(userName, &size)) {
137             SID_NAME_USE type = SidTypeUser;
138             DWORD sidSize = 0;
139             DWORD domainSize = 0;
140             // first call is to get the correct size
141             bool bRet = ptrLookupAccountName(NULL, userName, NULL, &sidSize, NULL, &domainSize, &type);
142             if (bRet == FALSE && ERROR_INSUFFICIENT_BUFFER != GetLastError())
143                 return false;
144             QVarLengthArray<BYTE, 68> buff(sidSize);
145             QVarLengthArray<wchar_t, MAX_PATH> domainName(domainSize);
146             // second call to LookupAccountNameW actually gets the SID
147             // both the pointer to the buffer and the pointer to the domain name should not be NULL
148             if (ptrLookupAccountName(NULL, userName, buff.data(), &sidSize, domainName.data(), &domainSize, &type))
149                 return type != SidTypeUser; //returns true if the current user is not a user
150         }
151     }
152     return false;
153 }
154 
splitSpaceSemicolon(const QString & source)155 static QStringList splitSpaceSemicolon(const QString &source)
156 {
157     QStringList list;
158     int start = 0;
159     int end;
160     while (true) {
161         int space = source.indexOf(QLatin1Char(' '), start);
162         int semicolon = source.indexOf(QLatin1Char(';'), start);
163         end = space;
164         if (semicolon != -1 && (end == -1 || semicolon < end))
165             end = semicolon;
166 
167         if (end == -1) {
168             if (start != source.length())
169                 list.append(source.mid(start));
170             return list;
171         }
172         if (start != end)
173             list.append(source.mid(start, end - start));
174         start = end + 1;
175     }
176     return list;
177 }
178 
isBypassed(const QString & host,const QStringList & bypassList)179 static bool isBypassed(const QString &host, const QStringList &bypassList)
180 {
181     if (host.isEmpty())
182         return false;
183 
184     bool isSimple = !host.contains(QLatin1Char('.')) && !host.contains(QLatin1Char(':'));
185 
186     QHostAddress ipAddress;
187     bool isIpAddress = ipAddress.setAddress(host);
188 
189     // always exclude loopback
190     if (isIpAddress && ipAddress.isLoopback())
191         return true;
192 
193     // does it match the list of exclusions?
194     for (const QString &entry : bypassList) {
195         if (entry == QLatin1String("<local>")) {
196             if (isSimple)
197                 return true;
198             if (isIpAddress) {
199                 //exclude all local subnets
200                 const auto ifaces = QNetworkInterface::allInterfaces();
201                 for (const QNetworkInterface &iface : ifaces) {
202                     const auto netaddrs = iface.addressEntries();
203                     for (const QNetworkAddressEntry &netaddr : netaddrs) {
204                         if (ipAddress.isInSubnet(netaddr.ip(), netaddr.prefixLength())) {
205                             return true;
206                         }
207                     }
208                 }
209             }
210         }
211         if (isIpAddress && ipAddress.isInSubnet(QHostAddress::parseSubnet(entry))) {
212             return true;        // excluded
213         } else {
214             // do wildcard matching
215             QRegExp rx(entry, Qt::CaseInsensitive, QRegExp::Wildcard);
216             if (rx.exactMatch(host))
217                 return true;
218         }
219     }
220 
221     // host was not excluded
222     return false;
223 }
224 
filterProxyListByCapabilities(const QList<QNetworkProxy> & proxyList,const QNetworkProxyQuery & query)225 static QList<QNetworkProxy> filterProxyListByCapabilities(const QList<QNetworkProxy> &proxyList, const QNetworkProxyQuery &query)
226 {
227     QNetworkProxy::Capabilities requiredCaps;
228     switch (query.queryType()) {
229     case QNetworkProxyQuery::TcpSocket:
230         requiredCaps = QNetworkProxy::TunnelingCapability;
231         break;
232     case QNetworkProxyQuery::UdpSocket:
233         requiredCaps = QNetworkProxy::UdpTunnelingCapability;
234         break;
235     case QNetworkProxyQuery::SctpSocket:
236         requiredCaps = QNetworkProxy::SctpTunnelingCapability;
237         break;
238     case QNetworkProxyQuery::TcpServer:
239         requiredCaps = QNetworkProxy::ListeningCapability;
240         break;
241     case QNetworkProxyQuery::SctpServer:
242         requiredCaps = QNetworkProxy::SctpListeningCapability;
243         break;
244     default:
245         return proxyList;
246         break;
247     }
248     QList<QNetworkProxy> result;
249     for (const QNetworkProxy &proxy : proxyList) {
250         if (proxy.capabilities() & requiredCaps)
251             result.append(proxy);
252     }
253     return result;
254 }
255 
removeDuplicateProxies(const QList<QNetworkProxy> & proxyList)256 static QList<QNetworkProxy> removeDuplicateProxies(const QList<QNetworkProxy> &proxyList)
257 {
258     QList<QNetworkProxy> result;
259     for (const QNetworkProxy &proxy : proxyList) {
260          bool append = true;
261          for (int i=0; i < result.count(); i++) {
262              if (proxy.hostName() == result.at(i).hostName()
263                  && proxy.port() == result.at(i).port()) {
264                      append = false;
265                      // HttpProxy trumps FtpCachingProxy or HttpCachingProxy on the same host/port
266                      if (proxy.type() == QNetworkProxy::HttpProxy)
267                          result[i] = proxy;
268              }
269          }
270          if (append)
271              result.append(proxy);
272      }
273      return result;
274 }
275 
parseServerList(const QNetworkProxyQuery & query,const QStringList & proxyList)276 static QList<QNetworkProxy> parseServerList(const QNetworkProxyQuery &query, const QStringList &proxyList)
277 {
278     // Reference documentation from Microsoft:
279     // http://msdn.microsoft.com/en-us/library/aa383912(VS.85).aspx
280     //
281     // According to the website, the proxy server list is
282     // one or more of the space- or semicolon-separated strings in the format:
283     //   ([<scheme>=][<scheme>"://"]<server>[":"<port>])
284     // The first scheme relates to the protocol tag
285     // The second scheme, if present, overrides the proxy type
286 
287     QList<QNetworkProxy> result;
288     QHash<QString, QNetworkProxy> taggedProxies;
289     const QString requiredTag = query.protocolTag();
290     // windows tags are only for clients
291     bool checkTags = !requiredTag.isEmpty()
292             && query.queryType() != QNetworkProxyQuery::TcpServer
293             && query.queryType() != QNetworkProxyQuery::SctpServer;
294     for (const QString &entry : proxyList) {
295         int server = 0;
296 
297         QNetworkProxy::ProxyType proxyType = QNetworkProxy::HttpProxy;
298         quint16 port = 8080;
299 
300         int pos = entry.indexOf(QLatin1Char('='));
301         QStringRef scheme;
302         QStringRef protocolTag;
303         if (pos != -1) {
304             scheme = protocolTag = entry.leftRef(pos);
305             server = pos + 1;
306         }
307         pos = entry.indexOf(QLatin1String("://"), server);
308         if (pos != -1) {
309             scheme = entry.midRef(server, pos - server);
310             server = pos + 3;
311         }
312 
313         if (!scheme.isEmpty()) {
314             if (scheme == QLatin1String("http") || scheme == QLatin1String("https")) {
315                 // no-op
316                 // defaults are above
317             } else if (scheme == QLatin1String("socks") || scheme == QLatin1String("socks5")) {
318                 proxyType = QNetworkProxy::Socks5Proxy;
319                 port = 1080;
320             } else if (scheme == QLatin1String("ftp")) {
321                 proxyType = QNetworkProxy::FtpCachingProxy;
322                 port = 2121;
323             } else {
324                 // unknown proxy type
325                 continue;
326             }
327         }
328 
329         pos = entry.indexOf(QLatin1Char(':'), server);
330         if (pos != -1) {
331             bool ok;
332             uint value = entry.midRef(pos + 1).toUInt(&ok);
333             if (!ok || value > 65535)
334                 continue;       // invalid port number
335 
336             port = value;
337         } else {
338             pos = entry.length();
339         }
340 
341         result << QNetworkProxy(proxyType, entry.mid(server, pos - server), port);
342         if (!protocolTag.isEmpty())
343             taggedProxies.insert(protocolTag.toString(), result.constLast());
344     }
345 
346     if (checkTags && taggedProxies.contains(requiredTag)) {
347         if (query.queryType() == QNetworkProxyQuery::UrlRequest) {
348             result.clear();
349             result.append(taggedProxies.value(requiredTag));
350             return result;
351         } else {
352             result.prepend(taggedProxies.value(requiredTag));
353         }
354     }
355     if (!checkTags || requiredTag != QLatin1String("http")) {
356         // if there are different http proxies for http and https, prefer the https one (more likely to be capable of CONNECT)
357         QNetworkProxy httpProxy = taggedProxies.value(QLatin1String("http"));
358         QNetworkProxy httpsProxy = taggedProxies.value(QLatin1String("http"));
359         if (httpProxy != httpsProxy && httpProxy.type() == QNetworkProxy::HttpProxy && httpsProxy.type() == QNetworkProxy::HttpProxy) {
360             for (int i = 0; i < result.count(); i++) {
361                 if (httpProxy == result.at(i))
362                     result[i].setType(QNetworkProxy::HttpCachingProxy);
363             }
364         }
365     }
366     result = filterProxyListByCapabilities(result, query);
367     return removeDuplicateProxies(result);
368 }
369 
370 #if !defined(Q_OS_WINRT)
371 namespace {
372 class QRegistryWatcher {
373     Q_DISABLE_COPY_MOVE(QRegistryWatcher)
374 public:
375     QRegistryWatcher() = default;
376 
addLocation(HKEY hive,const QString & path)377     void addLocation(HKEY hive, const QString& path)
378     {
379         HKEY openedKey;
380         if (RegOpenKeyEx(hive, reinterpret_cast<const wchar_t*>(path.utf16()), 0, KEY_READ, &openedKey) != ERROR_SUCCESS)
381             return;
382 
383         const DWORD filter = REG_NOTIFY_CHANGE_NAME | REG_NOTIFY_CHANGE_ATTRIBUTES |
384                 REG_NOTIFY_CHANGE_LAST_SET | REG_NOTIFY_CHANGE_SECURITY;
385 
386         // Watch the registry key for a change of value.
387         HANDLE handle = CreateEvent(NULL, true, false, NULL);
388         if (RegNotifyChangeKeyValue(openedKey, true, filter, handle, true) != ERROR_SUCCESS) {
389             CloseHandle(handle);
390             return;
391         }
392         m_watchEvents.append(handle);
393         m_registryHandles.append(openedKey);
394     }
395 
hasChanged() const396     bool hasChanged() const {
397         return !isEmpty() &&
398                WaitForMultipleObjects(m_watchEvents.size(), m_watchEvents.data(), false, 0) < WAIT_OBJECT_0 + m_watchEvents.size();
399     }
400 
isEmpty() const401     bool isEmpty() const {
402         return m_watchEvents.isEmpty();
403     }
404 
clear()405     void clear() {
406         for (HANDLE event : qAsConst(m_watchEvents))
407             CloseHandle(event);
408         for (HKEY key : qAsConst(m_registryHandles))
409             RegCloseKey(key);
410 
411         m_watchEvents.clear();
412         m_registryHandles.clear();
413     }
414 
~QRegistryWatcher()415     ~QRegistryWatcher() {
416         clear();
417     }
418 
419 private:
420     QVector<HANDLE> m_watchEvents;
421     QVector<HKEY> m_registryHandles;
422 };
423 } // namespace
424 #endif // !defined(Q_OS_WINRT)
425 
426 class QWindowsSystemProxy
427 {
428     Q_DISABLE_COPY_MOVE(QWindowsSystemProxy)
429 public:
430     QWindowsSystemProxy();
431     ~QWindowsSystemProxy();
432     void init();
433     void reset();
434 
435     QMutex mutex;
436 
437     HINTERNET hHttpSession;
438     WINHTTP_AUTOPROXY_OPTIONS autoProxyOptions;
439 
440     QString autoConfigUrl;
441     QStringList proxyServerList;
442     QStringList proxyBypass;
443     QList<QNetworkProxy> defaultResult;
444 #if !defined(Q_OS_WINRT)
445     QRegistryWatcher proxySettingsWatcher;
446 #endif
447     bool initialized;
448     bool functional;
449     bool isAutoConfig;
450 };
451 
Q_GLOBAL_STATIC(QWindowsSystemProxy,systemProxy)452 Q_GLOBAL_STATIC(QWindowsSystemProxy, systemProxy)
453 
454 QWindowsSystemProxy::QWindowsSystemProxy()
455     : hHttpSession(0), initialized(false), functional(false), isAutoConfig(false)
456 {
457     defaultResult << QNetworkProxy::NoProxy;
458 }
459 
~QWindowsSystemProxy()460 QWindowsSystemProxy::~QWindowsSystemProxy()
461 {
462     if (hHttpSession)
463         ptrWinHttpCloseHandle(hHttpSession);
464 }
465 
reset()466 void QWindowsSystemProxy::reset()
467 {
468     autoConfigUrl.clear();
469     proxyServerList.clear();
470     proxyBypass.clear();
471     defaultResult.clear();
472     defaultResult << QNetworkProxy::NoProxy;
473     functional = false;
474     isAutoConfig = false;
475 }
476 
init()477 void QWindowsSystemProxy::init()
478 {
479     bool proxySettingsChanged = false;
480 #if !defined(Q_OS_WINRT)
481     proxySettingsChanged = proxySettingsWatcher.hasChanged();
482 #endif
483 
484     if (initialized && !proxySettingsChanged)
485         return;
486     initialized = true;
487 
488     reset();
489 
490 #if !defined(Q_OS_WINRT)
491     proxySettingsWatcher.clear(); // needs reset to trigger a new detection
492     proxySettingsWatcher.addLocation(HKEY_CURRENT_USER,  QStringLiteral("Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings"));
493     proxySettingsWatcher.addLocation(HKEY_LOCAL_MACHINE, QStringLiteral("Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings"));
494     proxySettingsWatcher.addLocation(HKEY_LOCAL_MACHINE, QStringLiteral("Software\\Policies\\Microsoft\\Windows\\CurrentVersion\\Internet Settings"));
495 #endif
496 
497     // load the winhttp.dll library
498     QSystemLibrary lib(L"winhttp");
499     if (!lib.load())
500         return;                 // failed to load
501 
502     ptrWinHttpOpen = (PtrWinHttpOpen)lib.resolve("WinHttpOpen");
503     ptrWinHttpCloseHandle = (PtrWinHttpCloseHandle)lib.resolve("WinHttpCloseHandle");
504     ptrWinHttpGetProxyForUrl = (PtrWinHttpGetProxyForUrl)lib.resolve("WinHttpGetProxyForUrl");
505     ptrWinHttpGetDefaultProxyConfiguration = (PtrWinHttpGetDefaultProxyConfiguration)lib.resolve("WinHttpGetDefaultProxyConfiguration");
506     ptrWinHttpGetIEProxyConfigForCurrentUser = (PtrWinHttpGetIEProxyConfigForCurrentUser)lib.resolve("WinHttpGetIEProxyConfigForCurrentUser");
507 
508     // Try to obtain the Internet Explorer configuration.
509     WINHTTP_CURRENT_USER_IE_PROXY_CONFIG ieProxyConfig;
510     const bool hasIEConfig = ptrWinHttpGetIEProxyConfigForCurrentUser(&ieProxyConfig);
511     if (hasIEConfig) {
512         if (ieProxyConfig.lpszAutoConfigUrl) {
513             autoConfigUrl = QString::fromWCharArray(ieProxyConfig.lpszAutoConfigUrl);
514             GlobalFree(ieProxyConfig.lpszAutoConfigUrl);
515         }
516         if (ieProxyConfig.lpszProxy) {
517             // http://msdn.microsoft.com/en-us/library/aa384250%28VS.85%29.aspx speaks only about a "proxy URL",
518             // not multiple URLs. However we tested this and it can return multiple URLs. So we use splitSpaceSemicolon
519             // on it.
520             proxyServerList = splitSpaceSemicolon(QString::fromWCharArray(ieProxyConfig.lpszProxy));
521             GlobalFree(ieProxyConfig.lpszProxy);
522         }
523         if (ieProxyConfig.lpszProxyBypass) {
524             proxyBypass = splitSpaceSemicolon(QString::fromWCharArray(ieProxyConfig.lpszProxyBypass));
525             GlobalFree(ieProxyConfig.lpszProxyBypass);
526         }
527     }
528 
529     if (!hasIEConfig ||
530         (currentProcessIsService() && proxyServerList.isEmpty() && proxyBypass.isEmpty())) {
531         // no user configuration
532         // attempt to get the default configuration instead
533         // that config will serve as default if WPAD fails
534         WINHTTP_PROXY_INFO proxyInfo;
535         if (ptrWinHttpGetDefaultProxyConfiguration(&proxyInfo) &&
536             proxyInfo.dwAccessType == WINHTTP_ACCESS_TYPE_NAMED_PROXY) {
537             // we got information from the registry
538             // overwrite the IE configuration, if any
539 
540             proxyBypass = splitSpaceSemicolon(QString::fromWCharArray(proxyInfo.lpszProxyBypass));
541             proxyServerList = splitSpaceSemicolon(QString::fromWCharArray(proxyInfo.lpszProxy));
542         }
543 
544         if (proxyInfo.lpszProxy)
545             GlobalFree(proxyInfo.lpszProxy);
546         if (proxyInfo.lpszProxyBypass)
547             GlobalFree(proxyInfo.lpszProxyBypass);
548     }
549 
550     hHttpSession = NULL;
551     if (ieProxyConfig.fAutoDetect || !autoConfigUrl.isEmpty()) {
552         // open the handle and obtain the options
553         hHttpSession = ptrWinHttpOpen(L"Qt System Proxy access/1.0",
554                                       WINHTTP_ACCESS_TYPE_NO_PROXY,
555                                       WINHTTP_NO_PROXY_NAME,
556                                       WINHTTP_NO_PROXY_BYPASS,
557                                       0);
558         if (!hHttpSession)
559             return;
560 
561         isAutoConfig = true;
562         memset(&autoProxyOptions, 0, sizeof autoProxyOptions);
563         autoProxyOptions.fAutoLogonIfChallenged = false;
564         //Although it is possible to specify dwFlags = WINHTTP_AUTOPROXY_AUTO_DETECT | WINHTTP_AUTOPROXY_CONFIG_URL
565         //this has poor performance (WPAD is attempted for every url, taking 2.5 seconds per interface,
566         //before the configured pac file is used)
567         if (ieProxyConfig.fAutoDetect) {
568             autoProxyOptions.dwFlags = WINHTTP_AUTOPROXY_AUTO_DETECT;
569             autoProxyOptions.dwAutoDetectFlags = WINHTTP_AUTO_DETECT_TYPE_DHCP |
570                                                  WINHTTP_AUTO_DETECT_TYPE_DNS_A;
571         } else {
572             autoProxyOptions.dwFlags = WINHTTP_AUTOPROXY_CONFIG_URL;
573             autoProxyOptions.lpszAutoConfigUrl = reinterpret_cast<LPCWSTR>(autoConfigUrl.utf16());
574         }
575     }
576 
577     functional = isAutoConfig || !proxyServerList.isEmpty();
578 }
579 
systemProxyForQuery(const QNetworkProxyQuery & query)580 QList<QNetworkProxy> QNetworkProxyFactory::systemProxyForQuery(const QNetworkProxyQuery &query)
581 {
582     QWindowsSystemProxy *sp = systemProxy();
583     if (!sp)
584         return QList<QNetworkProxy>() << QNetworkProxy();
585 
586     QMutexLocker locker(&sp->mutex);
587     sp->init();
588     if (!sp->functional)
589         return sp->defaultResult;
590 
591     if (sp->isAutoConfig) {
592         WINHTTP_PROXY_INFO proxyInfo;
593 
594         // try to get the proxy config for the URL
595         QUrl url = query.url();
596         // url could be empty, e.g. from QNetworkProxy::applicationProxy(), that's fine,
597         // we'll still ask for the proxy.
598         // But for a file url, we know we don't need one.
599         if (url.scheme() == QLatin1String("file") || url.scheme() == QLatin1String("qrc"))
600             return sp->defaultResult;
601         if (query.queryType() != QNetworkProxyQuery::UrlRequest) {
602             // change the scheme to https, maybe it'll work
603             url.setScheme(QLatin1String("https"));
604         }
605 
606         QString urlQueryString = url.toString();
607         if (urlQueryString.size() > 2083) {
608             // calls to WinHttpGetProxyForUrl with urls longer than 2083 characters
609             // fail with error code ERROR_INVALID_PARAMETER(87), so we truncate it
610             qWarning("Proxy query URL too long for windows API, try with truncated URL");
611             urlQueryString = url.toString().left(2083);
612         }
613 
614         bool getProxySucceeded = ptrWinHttpGetProxyForUrl(sp->hHttpSession,
615                                                 reinterpret_cast<LPCWSTR>(urlQueryString.utf16()),
616                                                 &sp->autoProxyOptions,
617                                                 &proxyInfo);
618         DWORD getProxyError = GetLastError();
619 
620         if (!getProxySucceeded
621             && (ERROR_WINHTTP_AUTODETECTION_FAILED == getProxyError)) {
622             // WPAD failed
623             if (sp->autoConfigUrl.isEmpty()) {
624                 //No config file could be retrieved on the network.
625                 //Don't search for it next time again.
626                 sp->isAutoConfig = false;
627             } else {
628                 //pac file URL is specified as well, try using that
629                 sp->autoProxyOptions.dwFlags = WINHTTP_AUTOPROXY_CONFIG_URL;
630                 sp->autoProxyOptions.lpszAutoConfigUrl =
631                     reinterpret_cast<LPCWSTR>(sp->autoConfigUrl.utf16());
632                 getProxySucceeded = ptrWinHttpGetProxyForUrl(sp->hHttpSession,
633                                                 reinterpret_cast<LPCWSTR>(urlQueryString.utf16()),
634                                                 &sp->autoProxyOptions,
635                                                 &proxyInfo);
636                 getProxyError = GetLastError();
637             }
638         }
639 
640         if (!getProxySucceeded
641             && (ERROR_WINHTTP_LOGIN_FAILURE == getProxyError)) {
642             // We first tried without AutoLogon, because this might prevent caching the result.
643             // But now we've to enable it (http://msdn.microsoft.com/en-us/library/aa383153%28v=VS.85%29.aspx)
644             sp->autoProxyOptions.fAutoLogonIfChallenged = TRUE;
645             getProxySucceeded = ptrWinHttpGetProxyForUrl(sp->hHttpSession,
646                                                 reinterpret_cast<LPCWSTR>(urlQueryString.utf16()),
647                                                 &sp->autoProxyOptions,
648                                                 &proxyInfo);
649             getProxyError = GetLastError();
650         }
651 
652         if (!getProxySucceeded
653             && (ERROR_WINHTTP_UNABLE_TO_DOWNLOAD_SCRIPT == getProxyError)) {
654             // PAC file url is not connectable, or server returned error (e.g. http 404)
655             //Don't search for it next time again.
656             sp->isAutoConfig = false;
657         }
658 
659         if (getProxySucceeded) {
660             // yes, we got a config for this URL
661             QString proxyBypass = QString::fromWCharArray(proxyInfo.lpszProxyBypass);
662             QStringList proxyServerList = splitSpaceSemicolon(QString::fromWCharArray(proxyInfo.lpszProxy));
663             if (proxyInfo.lpszProxy)
664                 GlobalFree(proxyInfo.lpszProxy);
665             if (proxyInfo.lpszProxyBypass)
666                 GlobalFree(proxyInfo.lpszProxyBypass);
667 
668             if (proxyInfo.dwAccessType == WINHTTP_ACCESS_TYPE_NO_PROXY)
669                 return sp->defaultResult; //i.e. the PAC file result was "DIRECT"
670             if (isBypassed(query.peerHostName(), splitSpaceSemicolon(proxyBypass)))
671                 return sp->defaultResult;
672             return parseServerList(query, proxyServerList);
673         }
674 
675         // GetProxyForUrl failed, fall back to static configuration
676     }
677 
678     // static configuration
679     if (isBypassed(query.peerHostName(), sp->proxyBypass))
680         return sp->defaultResult;
681 
682     QList<QNetworkProxy> result = parseServerList(query, sp->proxyServerList);
683     // In some cases, this was empty. See SF task 00062670
684     if (result.isEmpty())
685         return sp->defaultResult;
686 
687     return result;
688 }
689 
690 QT_END_NAMESPACE
691 
692 #endif
693