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(QStringView path, QStringView 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(n: reference.size()) == u'/')
129 return true;
130 }
131 return false;
132}
133
134static inline bool isParentDomain(QStringView domain, QStringView reference)
135{
136 if (!reference.startsWith(c: u'.'))
137 return domain == reference;
138
139 return domain.endsWith(s: reference) || domain == 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 const bool isEncrypted = url.scheme() == "https"_L1;
204
205 // scan our cookies for something that matches
206 for (const auto &cookie : std::as_const(t: d->allCookies)) {
207 if (!isEncrypted && cookie.isSecure())
208 continue;
209 if (!cookie.isSessionCookie() && cookie.expirationDate() < now)
210 continue;
211 const QString urlHost = url.host();
212 const QString cookieDomain = cookie.domain();
213 if (!isParentDomain(domain: urlHost, reference: cookieDomain))
214 continue;
215 if (!isParentPath(path: url.path(), reference: cookie.path()))
216 continue;
217
218 QStringView domain = cookieDomain;
219 if (domain.startsWith(c: u'.')) /// Qt6?: remove when compliant with RFC6265
220 domain = domain.sliced(pos: 1);
221#if QT_CONFIG(topleveldomain)
222 if (urlHost != domain && qIsEffectiveTLD(domain))
223 continue;
224#else
225 if (!domain.contains(u'.') && urlHost != domain)
226 continue;
227#endif // topleveldomain
228
229 result += cookie;
230 }
231
232 auto longerPath = [](const auto &c1, const auto &c2)
233 { return c1.path().size() > c2.path().size(); };
234 std::sort(first: result.begin(), last: result.end(), comp: longerPath);
235 return result;
236}
237
238/*!
239 \since 5.0
240 Adds \a cookie to this cookie jar.
241
242 Returns \c true if \a cookie was added, false otherwise.
243
244 If a cookie with the same identifier already exists in the
245 cookie jar, it will be overridden.
246*/
247bool QNetworkCookieJar::insertCookie(const QNetworkCookie &cookie)
248{
249 Q_D(QNetworkCookieJar);
250 const QDateTime now = QDateTime::currentDateTimeUtc();
251 bool isDeletion = !cookie.isSessionCookie() &&
252 cookie.expirationDate() < now;
253
254 deleteCookie(cookie);
255
256 if (!isDeletion) {
257 d->allCookies += cookie;
258 return true;
259 }
260 return false;
261}
262
263/*!
264 \since 5.0
265 If a cookie with the same identifier as \a cookie exists in this cookie jar
266 it will be updated. This function uses insertCookie().
267
268 Returns \c true if \a cookie was updated, false if no cookie in the jar matches
269 the identifier of \a cookie.
270
271 \sa QNetworkCookie::hasSameIdentifier()
272*/
273bool QNetworkCookieJar::updateCookie(const QNetworkCookie &cookie)
274{
275 if (deleteCookie(cookie))
276 return insertCookie(cookie);
277 return false;
278}
279
280/*!
281 \since 5.0
282 Deletes from cookie jar the cookie found to have the same identifier as \a cookie.
283
284 Returns \c true if a cookie was deleted, false otherwise.
285
286 \sa QNetworkCookie::hasSameIdentifier()
287*/
288bool QNetworkCookieJar::deleteCookie(const QNetworkCookie &cookie)
289{
290 Q_D(QNetworkCookieJar);
291 const auto it = std::find_if(first: d->allCookies.cbegin(), last: d->allCookies.cend(),
292 pred: [&cookie](const auto &c) { return c.hasSameIdentifier(cookie); });
293 if (it != d->allCookies.cend()) {
294 d->allCookies.erase(pos: it);
295 return true;
296 }
297 return false;
298}
299
300/*!
301 \since 5.0
302 Returns \c true if the domain and path of \a cookie are valid, false otherwise.
303 The \a url parameter is used to determine if the domain specified in the cookie
304 is allowed.
305*/
306bool QNetworkCookieJar::validateCookie(const QNetworkCookie &cookie, const QUrl &url) const
307{
308 const QString cookieDomain = cookie.domain();
309 QStringView domain = cookieDomain;
310 const QString host = url.host();
311 if (!isParentDomain(domain, reference: host) && !isParentDomain(domain: host, reference: domain))
312 return false; // not accepted
313
314 if (domain.startsWith(c: u'.'))
315 domain = domain.sliced(pos: 1);
316
317 // We shouldn't reject if:
318 // "[...] the domain-attribute is identical to the canonicalized request-host"
319 // https://tools.ietf.org/html/rfc6265#section-5.3 step 5
320 if (host == domain)
321 return true;
322 // the check for effective TLDs makes the "embedded dot" rule from RFC 2109 section 4.3.2
323 // redundant; the "leading dot" rule has been relaxed anyway, see QNetworkCookie::normalize()
324 // we remove the leading dot for this check if it's present
325 // Normally defined in qtldurl_p.h, but uses fall-back in this file when topleveldomain isn't
326 // configured:
327 return !qIsEffectiveTLD(domain);
328}
329
330QT_END_NAMESPACE
331
332#include "moc_qnetworkcookiejar.cpp"
333

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

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