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 "qnetworkcookiejar.h"
41#include "qnetworkcookiejar_p.h"
42
43#include "QtNetwork/qnetworkcookie.h"
44#include "QtCore/qurl.h"
45#include "QtCore/qdatetime.h"
46#if QT_CONFIG(topleveldomain)
47#include "private/qtldurl_p.h"
48#else
49QT_BEGIN_NAMESPACE
50static bool qIsEffectiveTLD(QStringView domain)
51{
52 // provide minimal checking by not accepting cookies on real TLDs
53 return !domain.contains(QLatin1Char('.'));
54}
55QT_END_NAMESPACE
56#endif
57
58QT_BEGIN_NAMESPACE
59
60/*!
61 \class QNetworkCookieJar
62 \since 4.4
63 \inmodule QtNetwork
64
65 \brief The QNetworkCookieJar class implements a simple jar of QNetworkCookie objects.
66
67 Cookies are small bits of information that stateless protocols
68 like HTTP use to maintain some persistent information across
69 requests.
70
71 A cookie is set by a remote server when it replies to a request
72 and it expects the same cookie to be sent back when further
73 requests are sent.
74
75 The cookie jar is the object that holds all cookies set in
76 previous requests. Web browsers save their cookie jars to disk in
77 order to conserve permanent cookies across invocations of the
78 application.
79
80 QNetworkCookieJar does not implement permanent storage: it only
81 keeps the cookies in memory. Once the QNetworkCookieJar object is
82 deleted, all cookies it held will be discarded as well. If you
83 want to save the cookies, you should derive from this class and
84 implement the saving to disk to your own storage format.
85
86 This class implements only the basic security recommended by the
87 cookie specifications and does not implement any cookie acceptance
88 policy (it accepts all cookies set by any requests). In order to
89 override those rules, you should reimplement the
90 cookiesForUrl() and setCookiesFromUrl() virtual
91 functions. They are called by QNetworkReply and
92 QNetworkAccessManager when they detect new cookies and when they
93 require cookies.
94
95 \sa QNetworkCookie, QNetworkAccessManager, QNetworkReply,
96 QNetworkRequest, QNetworkAccessManager::setCookieJar()
97*/
98
99/*!
100 Creates a QNetworkCookieJar object and sets the parent object to
101 be \a parent.
102
103 The cookie jar is initialized to empty.
104*/
105QNetworkCookieJar::QNetworkCookieJar(QObject *parent)
106 : QObject(*new QNetworkCookieJarPrivate, parent)
107{
108}
109
110/*!
111 Destroys this cookie jar object and discards all cookies stored in
112 it. Cookies are not saved to disk in the QNetworkCookieJar default
113 implementation.
114
115 If you need to save the cookies to disk, you have to derive from
116 QNetworkCookieJar and save the cookies to disk yourself.
117*/
118QNetworkCookieJar::~QNetworkCookieJar()
119{
120}
121
122/*!
123 Returns all cookies stored in this cookie jar. This function is
124 suitable for derived classes to save cookies to disk, as well as
125 to implement cookie expiration and other policies.
126
127 \sa setAllCookies(), cookiesForUrl()
128*/
129QList<QNetworkCookie> QNetworkCookieJar::allCookies() const
130{
131 return d_func()->allCookies;
132}
133
134/*!
135 Sets the internal list of cookies held by this cookie jar to be \a
136 cookieList. This function is suitable for derived classes to
137 implement loading cookies from permanent storage, or their own
138 cookie acceptance policies by reimplementing
139 setCookiesFromUrl().
140
141 \sa allCookies(), setCookiesFromUrl()
142*/
143void QNetworkCookieJar::setAllCookies(const QList<QNetworkCookie> &cookieList)
144{
145 Q_D(QNetworkCookieJar);
146 d->allCookies = cookieList;
147}
148
149static inline bool isParentPath(const QString &path, const QString &reference)
150{
151 if ((path.isEmpty() && reference == QLatin1String("/")) || path.startsWith(reference)) {
152 //The cookie-path and the request-path are identical.
153 if (path.length() == reference.length())
154 return true;
155 //The cookie-path is a prefix of the request-path, and the last
156 //character of the cookie-path is %x2F ("/").
157 if (reference.endsWith(u'/'))
158 return true;
159 //The cookie-path is a prefix of the request-path, and the first
160 //character of the request-path that is not included in the cookie-
161 //path is a %x2F ("/") character.
162 if (path.at(reference.length()) == u'/')
163 return true;
164 }
165 return false;
166}
167
168static inline bool isParentDomain(const QString &domain, const QString &reference)
169{
170 if (!reference.startsWith(QLatin1Char('.')))
171 return domain == reference;
172
173 return domain.endsWith(reference) || domain == QStringView{reference}.mid(1);
174}
175
176/*!
177 Adds the cookies in the list \a cookieList to this cookie
178 jar. Before being inserted cookies are normalized.
179
180 Returns \c true if one or more cookies are set for \a url,
181 otherwise false.
182
183 If a cookie already exists in the cookie jar, it will be
184 overridden by those in \a cookieList.
185
186 The default QNetworkCookieJar class implements only a very basic
187 security policy (it makes sure that the cookies' domain and path
188 match the reply's). To enhance the security policy with your own
189 algorithms, override setCookiesFromUrl().
190
191 Also, QNetworkCookieJar does not have a maximum cookie jar
192 size. Reimplement this function to discard older cookies to create
193 room for new ones.
194
195 \sa cookiesForUrl(), QNetworkAccessManager::setCookieJar(), QNetworkCookie::normalize()
196*/
197bool QNetworkCookieJar::setCookiesFromUrl(const QList<QNetworkCookie> &cookieList,
198 const QUrl &url)
199{
200 bool added = false;
201 for (QNetworkCookie cookie : cookieList) {
202 cookie.normalize(url);
203 if (validateCookie(cookie, url) && insertCookie(cookie))
204 added = true;
205 }
206 return added;
207}
208
209/*!
210 Returns the cookies to be added to when a request is sent to
211 \a url. This function is called by the default
212 QNetworkAccessManager::createRequest(), which adds the
213 cookies returned by this function to the request being sent.
214
215 If more than one cookie with the same name is found, but with
216 differing paths, the one with longer path is returned before the
217 one with shorter path. In other words, this function returns
218 cookies sorted decreasingly by path length.
219
220 The default QNetworkCookieJar class implements only a very basic
221 security policy (it makes sure that the cookies' domain and path
222 match the reply's). To enhance the security policy with your own
223 algorithms, override cookiesForUrl().
224
225 \sa setCookiesFromUrl(), QNetworkAccessManager::setCookieJar()
226*/
227QList<QNetworkCookie> QNetworkCookieJar::cookiesForUrl(const QUrl &url) const
228{
229// \b Warning! This is only a dumb implementation!
230// It does NOT follow all of the recommendations from
231// http://wp.netscape.com/newsref/std/cookie_spec.html
232// It does not implement a very good cross-domain verification yet.
233
234 Q_D(const QNetworkCookieJar);
235 const QDateTime now = QDateTime::currentDateTimeUtc();
236 QList<QNetworkCookie> result;
237 bool isEncrypted = url.scheme() == QLatin1String("https");
238
239 // scan our cookies for something that matches
240 QList<QNetworkCookie>::ConstIterator it = d->allCookies.constBegin(),
241 end = d->allCookies.constEnd();
242 for ( ; it != end; ++it) {
243 if (!isParentDomain(url.host(), it->domain()))
244 continue;
245 if (!isParentPath(url.path(), it->path()))
246 continue;
247 if (!(*it).isSessionCookie() && (*it).expirationDate() < now)
248 continue;
249 if ((*it).isSecure() && !isEncrypted)
250 continue;
251
252 QString domain = it->domain();
253 if (domain.startsWith(QLatin1Char('.'))) /// Qt6?: remove when compliant with RFC6265
254 domain = domain.mid(1);
255#if QT_CONFIG(topleveldomain)
256 if (qIsEffectiveTLD(domain) && url.host() != domain)
257 continue;
258#else
259 if (!domain.contains(QLatin1Char('.')) && url.host() != domain)
260 continue;
261#endif // topleveldomain
262
263 // insert this cookie into result, sorted by path
264 QList<QNetworkCookie>::Iterator insertIt = result.begin();
265 while (insertIt != result.end()) {
266 if (insertIt->path().length() < it->path().length()) {
267 // insert here
268 insertIt = result.insert(insertIt, *it);
269 break;
270 } else {
271 ++insertIt;
272 }
273 }
274
275 // this is the shortest path yet, just append
276 if (insertIt == result.end())
277 result += *it;
278 }
279
280 return result;
281}
282
283/*!
284 \since 5.0
285 Adds \a cookie to this cookie jar.
286
287 Returns \c true if \a cookie was added, false otherwise.
288
289 If a cookie with the same identifier already exists in the
290 cookie jar, it will be overridden.
291*/
292bool QNetworkCookieJar::insertCookie(const QNetworkCookie &cookie)
293{
294 Q_D(QNetworkCookieJar);
295 const QDateTime now = QDateTime::currentDateTimeUtc();
296 bool isDeletion = !cookie.isSessionCookie() &&
297 cookie.expirationDate() < now;
298
299 deleteCookie(cookie);
300
301 if (!isDeletion) {
302 d->allCookies += cookie;
303 return true;
304 }
305 return false;
306}
307
308/*!
309 \since 5.0
310 If a cookie with the same identifier as \a cookie exists in this cookie jar
311 it will be updated. This function uses insertCookie().
312
313 Returns \c true if \a cookie was updated, false if no cookie in the jar matches
314 the identifier of \a cookie.
315
316 \sa QNetworkCookie::hasSameIdentifier()
317*/
318bool QNetworkCookieJar::updateCookie(const QNetworkCookie &cookie)
319{
320 if (deleteCookie(cookie))
321 return insertCookie(cookie);
322 return false;
323}
324
325/*!
326 \since 5.0
327 Deletes from cookie jar the cookie found to have the same identifier as \a cookie.
328
329 Returns \c true if a cookie was deleted, false otherwise.
330
331 \sa QNetworkCookie::hasSameIdentifier()
332*/
333bool QNetworkCookieJar::deleteCookie(const QNetworkCookie &cookie)
334{
335 Q_D(QNetworkCookieJar);
336 QList<QNetworkCookie>::Iterator it;
337 for (it = d->allCookies.begin(); it != d->allCookies.end(); ++it) {
338 if (it->hasSameIdentifier(cookie)) {
339 d->allCookies.erase(it);
340 return true;
341 }
342 }
343 return false;
344}
345
346/*!
347 \since 5.0
348 Returns \c true if the domain and path of \a cookie are valid, false otherwise.
349 The \a url parameter is used to determine if the domain specified in the cookie
350 is allowed.
351*/
352bool QNetworkCookieJar::validateCookie(const QNetworkCookie &cookie, const QUrl &url) const
353{
354 QString domain = cookie.domain();
355 const QString host = url.host();
356 if (!isParentDomain(domain, host) && !isParentDomain(host, domain))
357 return false; // not accepted
358
359 if (domain.startsWith(QLatin1Char('.')))
360 domain = domain.mid(1);
361
362 // We shouldn't reject if:
363 // "[...] the domain-attribute is identical to the canonicalized request-host"
364 // https://tools.ietf.org/html/rfc6265#section-5.3 step 5
365 if (host == domain)
366 return true;
367 // the check for effective TLDs makes the "embedded dot" rule from RFC 2109 section 4.3.2
368 // redundant; the "leading dot" rule has been relaxed anyway, see QNetworkCookie::normalize()
369 // we remove the leading dot for this check if it's present
370 // Normally defined in qtldurl_p.h, but uses fall-back in this file when topleveldomain isn't
371 // configured:
372 return !qIsEffectiveTLD(domain);
373}
374
375QT_END_NAMESPACE
376