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 "qnetworkaccessauthenticationmanager_p.h"
5#include "qnetworkaccessmanager.h"
6#include "qnetworkaccessmanager_p.h"
7
8#include "QtCore/qbuffer.h"
9#include "QtCore/qlist.h"
10#include "QtCore/qurl.h"
11#include "QtCore/QMutexLocker"
12#include "QtNetwork/qauthenticator.h"
13
14#include <algorithm>
15
16QT_BEGIN_NAMESPACE
17
18using namespace Qt::StringLiterals;
19
20class QNetworkAuthenticationCache : private QList<QNetworkAuthenticationCredential>,
21 public QNetworkAccessCache::CacheableObject
22{
23public:
24 QNetworkAuthenticationCache()
25 {
26 setExpires(false);
27 setShareable(true);
28 reserve(asize: 1);
29 }
30
31 using QList<QNetworkAuthenticationCredential>::begin;
32 using QList<QNetworkAuthenticationCredential>::end;
33
34 iterator findClosestMatch(const QString &domain)
35 {
36 iterator it = std::lower_bound(first: begin(), last: end(), val: domain);
37 if (it == end() && !isEmpty())
38 --it;
39 if (it == end() || !domain.startsWith(s: it->domain))
40 return end();
41 return it;
42 }
43
44 void insert(const QString &domain, const QString &user, const QString &password)
45 {
46 iterator closestMatch = findClosestMatch(domain);
47 if (closestMatch != end() && closestMatch->domain == domain) {
48 // we're overriding the current credentials
49 closestMatch->user = user;
50 closestMatch->password = password;
51 } else {
52 QNetworkAuthenticationCredential newCredential;
53 newCredential.domain = domain;
54 newCredential.user = user;
55 newCredential.password = password;
56
57 if (closestMatch != end())
58 QList<QNetworkAuthenticationCredential>::insert(before: ++closestMatch, t: newCredential);
59 else
60 QList<QNetworkAuthenticationCredential>::insert(before: end(), t: newCredential);
61 }
62 }
63
64 virtual void dispose() override { delete this; }
65};
66
67#ifndef QT_NO_NETWORKPROXY
68static QByteArray proxyAuthenticationKey(const QNetworkProxy &proxy, const QString &realm)
69{
70 QUrl key;
71
72 switch (proxy.type()) {
73 case QNetworkProxy::Socks5Proxy:
74 key.setScheme("proxy-socks5"_L1);
75 break;
76
77 case QNetworkProxy::HttpProxy:
78 case QNetworkProxy::HttpCachingProxy:
79 key.setScheme("proxy-http"_L1);
80 break;
81
82 case QNetworkProxy::FtpCachingProxy:
83 key.setScheme("proxy-ftp"_L1);
84 break;
85
86 case QNetworkProxy::DefaultProxy:
87 case QNetworkProxy::NoProxy:
88 // shouldn't happen
89 return QByteArray();
90
91 // no default:
92 // let there be errors if a new proxy type is added in the future
93 }
94
95 if (key.scheme().isEmpty())
96 // proxy type not handled
97 return QByteArray();
98
99 key.setUserName(userName: proxy.user());
100 key.setHost(host: proxy.hostName());
101 key.setPort(proxy.port());
102 key.setFragment(fragment: realm);
103 return "auth:" + key.toEncoded();
104}
105#endif
106
107static inline QByteArray authenticationKey(const QUrl &url, const QString &realm)
108{
109 QUrl copy = url;
110 copy.setFragment(fragment: realm);
111 return "auth:" + copy.toEncoded(options: QUrl::RemovePassword | QUrl::RemovePath | QUrl::RemoveQuery);
112}
113
114
115#ifndef QT_NO_NETWORKPROXY
116void QNetworkAccessAuthenticationManager::cacheProxyCredentials(const QNetworkProxy &p,
117 const QAuthenticator *authenticator)
118{
119 Q_ASSERT(authenticator);
120 Q_ASSERT(p.type() != QNetworkProxy::DefaultProxy);
121 Q_ASSERT(p.type() != QNetworkProxy::NoProxy);
122
123 QMutexLocker mutexLocker(&mutex);
124
125 QString realm = authenticator->realm();
126 QNetworkProxy proxy = p;
127 proxy.setUser(authenticator->user());
128
129 // don't cache null passwords, empty password may be valid though
130 if (authenticator->password().isNull())
131 return;
132
133 // Set two credentials: one with the username and one without
134 do {
135 // Set two credentials actually: one with and one without the realm
136 do {
137 QByteArray cacheKey = proxyAuthenticationKey(proxy, realm);
138 if (cacheKey.isEmpty())
139 return; // should not happen
140
141 QNetworkAuthenticationCache *auth = new QNetworkAuthenticationCache;
142 auth->insert(domain: QString(), user: authenticator->user(), password: authenticator->password());
143 authenticationCache.addEntry(key: cacheKey, entry: auth); // replace the existing one, if there's any
144
145 if (realm.isEmpty()) {
146 break;
147 } else {
148 realm.clear();
149 }
150 } while (true);
151
152 if (proxy.user().isEmpty())
153 break;
154 else
155 proxy.setUser(QString());
156 } while (true);
157}
158
159QNetworkAuthenticationCredential
160QNetworkAccessAuthenticationManager::fetchCachedProxyCredentials(const QNetworkProxy &p,
161 const QAuthenticator *authenticator)
162{
163 QNetworkProxy proxy = p;
164 if (proxy.type() == QNetworkProxy::DefaultProxy) {
165 proxy = QNetworkProxy::applicationProxy();
166 }
167 if (!proxy.password().isEmpty())
168 return QNetworkAuthenticationCredential(); // no need to set credentials if it already has them
169
170 QString realm;
171 if (authenticator)
172 realm = authenticator->realm();
173
174 QMutexLocker mutexLocker(&mutex);
175 QByteArray cacheKey = proxyAuthenticationKey(proxy, realm);
176 if (cacheKey.isEmpty())
177 return QNetworkAuthenticationCredential();
178 if (!authenticationCache.hasEntry(key: cacheKey))
179 return QNetworkAuthenticationCredential();
180
181 QNetworkAuthenticationCache *auth =
182 static_cast<QNetworkAuthenticationCache *>(authenticationCache.requestEntryNow(key: cacheKey));
183 QNetworkAuthenticationCredential cred = *auth->findClosestMatch(domain: QString());
184 authenticationCache.releaseEntry(key: cacheKey);
185
186 // proxy cache credentials always have exactly one item
187 Q_ASSERT_X(!cred.isNull(), "QNetworkAccessManager",
188 "Internal inconsistency: found a cache key for a proxy, but it's empty");
189 return cred;
190}
191
192#endif
193
194void QNetworkAccessAuthenticationManager::cacheCredentials(const QUrl &url,
195 const QAuthenticator *authenticator)
196{
197 Q_ASSERT(authenticator);
198 if (authenticator->isNull())
199 return;
200 QString domain = QString::fromLatin1(ba: "/"); // FIXME: make QAuthenticator return the domain
201 QString realm = authenticator->realm();
202
203 QMutexLocker mutexLocker(&mutex);
204
205 // Set two credentials actually: one with and one without the username in the URL
206 QUrl copy = url;
207 copy.setUserName(userName: authenticator->user());
208 do {
209 QByteArray cacheKey = authenticationKey(url: copy, realm);
210 if (authenticationCache.hasEntry(key: cacheKey)) {
211 QNetworkAuthenticationCache *auth =
212 static_cast<QNetworkAuthenticationCache *>(authenticationCache.requestEntryNow(key: cacheKey));
213 auth->insert(domain, user: authenticator->user(), password: authenticator->password());
214 authenticationCache.releaseEntry(key: cacheKey);
215 } else {
216 QNetworkAuthenticationCache *auth = new QNetworkAuthenticationCache;
217 auth->insert(domain, user: authenticator->user(), password: authenticator->password());
218 authenticationCache.addEntry(key: cacheKey, entry: auth);
219 }
220
221 if (copy.userName().isEmpty()) {
222 break;
223 } else {
224 copy.setUserName(userName: QString());
225 }
226 } while (true);
227}
228
229/*!
230 Fetch the credential data from the credential cache.
231
232 If auth is 0 (as it is when called from createRequest()), this will try to
233 look up with an empty realm. That fails in most cases for HTTP (because the
234 realm is seldom empty for HTTP challenges). In any case, QHttpNetworkConnection
235 never sends the credentials on the first attempt: it needs to find out what
236 authentication methods the server supports.
237
238 For FTP, realm is always empty.
239*/
240QNetworkAuthenticationCredential
241QNetworkAccessAuthenticationManager::fetchCachedCredentials(const QUrl &url,
242 const QAuthenticator *authentication)
243{
244 if (!url.password().isEmpty())
245 return QNetworkAuthenticationCredential(); // no need to set credentials if it already has them
246
247 QString realm;
248 if (authentication)
249 realm = authentication->realm();
250
251 QByteArray cacheKey = authenticationKey(url, realm);
252
253 QMutexLocker mutexLocker(&mutex);
254 if (!authenticationCache.hasEntry(key: cacheKey))
255 return QNetworkAuthenticationCredential();
256
257 QNetworkAuthenticationCache *auth =
258 static_cast<QNetworkAuthenticationCache *>(authenticationCache.requestEntryNow(key: cacheKey));
259 auto cred = auth->findClosestMatch(domain: url.path());
260 QNetworkAuthenticationCredential ret;
261 if (cred != auth->end())
262 ret = *cred;
263 authenticationCache.releaseEntry(key: cacheKey);
264 return ret;
265}
266
267void QNetworkAccessAuthenticationManager::clearCache()
268{
269 authenticationCache.clear();
270}
271
272QT_END_NAMESPACE
273
274

source code of qtbase/src/network/access/qnetworkaccessauthenticationmanager.cpp