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