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

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