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