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

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