Coverage Report

Created: 2026-10-28 07:09

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/qtbase/src/network/access/qnetworkcookiejar.cpp
Line
Count
Source
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
5
#include "qnetworkcookiejar.h"
6
#include "qnetworkcookiejar_p.h"
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
14
QT_BEGIN_NAMESPACE
15
static bool qIsEffectiveTLD(QStringView domain)
16
{
17
    // provide minimal checking by not accepting cookies on real TLDs
18
    return !domain.contains(u'.');
19
}
20
QT_END_NAMESPACE
21
#endif
22
23
QT_BEGIN_NAMESPACE
24
25
using 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
*/
72
QNetworkCookieJar::QNetworkCookieJar(QObject *parent)
73
0
    : QObject(*new QNetworkCookieJarPrivate, parent)
74
0
{
75
0
}
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
*/
85
QNetworkCookieJar::~QNetworkCookieJar()
86
0
{
87
0
}
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
*/
96
QList<QNetworkCookie> QNetworkCookieJar::allCookies() const
97
0
{
98
0
    return d_func()->allCookies;
99
0
}
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
*/
110
void QNetworkCookieJar::setAllCookies(const QList<QNetworkCookie> &cookieList)
111
0
{
112
0
    Q_D(QNetworkCookieJar);
113
0
    d->allCookies = cookieList;
114
0
}
115
116
static inline bool isParentPath(QStringView path, QStringView reference)
117
0
{
118
0
    if ((path.isEmpty() && reference == "/"_L1) || path.startsWith(reference)) {
119
        //The cookie-path and the request-path are identical.
120
0
        if (path.size() == reference.size())
121
0
            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
0
        if (reference.endsWith(u'/'))
125
0
            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
0
        if (path.at(reference.size()) == u'/')
130
0
            return true;
131
0
    }
132
0
    return false;
133
0
}
134
135
static inline bool isParentDomain(QStringView domain, QStringView reference)
136
0
{
137
0
    if (!reference.startsWith(u'.'))
138
0
        return domain == reference;
139
140
0
    return domain.endsWith(reference) || domain == reference.mid(1);
141
0
}
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
*/
164
bool QNetworkCookieJar::setCookiesFromUrl(const QList<QNetworkCookie> &cookieList,
165
                                          const QUrl &url)
166
0
{
167
0
    bool added = false;
168
0
    for (QNetworkCookie cookie : cookieList) {
169
0
        cookie.normalize(url);
170
0
        if (validateCookie(cookie, url) && insertCookie(cookie))
171
0
            added = true;
172
0
    }
173
0
    return added;
174
0
}
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
*/
194
QList<QNetworkCookie> QNetworkCookieJar::cookiesForUrl(const QUrl &url) const
195
0
{
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
0
    Q_D(const QNetworkCookieJar);
202
0
    const QDateTime now = QDateTime::currentDateTimeUtc();
203
0
    QList<QNetworkCookie> result;
204
0
    const bool isEncrypted = url.scheme() == "https"_L1;
205
206
    // scan our cookies for something that matches
207
0
    for (const auto &cookie : std::as_const(d->allCookies)) {
208
0
        if (!isEncrypted && cookie.isSecure())
209
0
            continue;
210
0
        if (!cookie.isSessionCookie() && cookie.expirationDate() < now)
211
0
            continue;
212
0
        const QString urlHost = url.host();
213
0
        const QString cookieDomain = cookie.domain();
214
0
        if (!isParentDomain(urlHost, cookieDomain))
215
0
            continue;
216
0
        if (!isParentPath(url.path(), cookie.path()))
217
0
            continue;
218
219
0
        QStringView domain = cookieDomain;
220
0
        if (domain.startsWith(u'.')) /// Qt6?: remove when compliant with RFC6265
221
0
            domain = domain.sliced(1);
222
0
#if QT_CONFIG(topleveldomain)
223
0
        if (urlHost != domain && qIsEffectiveTLD(domain))
224
0
            continue;
225
#else
226
        if (!domain.contains(u'.') && urlHost != domain)
227
            continue;
228
#endif // topleveldomain
229
230
0
        result += cookie;
231
0
    }
232
233
0
    auto longerPath = [](const auto &c1, const auto &c2)
234
0
                      { return c1.path().size() > c2.path().size(); };
235
0
    std::sort(result.begin(), result.end(), longerPath);
236
0
    return result;
237
0
}
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
*/
248
bool QNetworkCookieJar::insertCookie(const QNetworkCookie &cookie)
249
0
{
250
0
    Q_D(QNetworkCookieJar);
251
0
    const QDateTime now = QDateTime::currentDateTimeUtc();
252
0
    bool isDeletion = !cookie.isSessionCookie() &&
253
0
                      cookie.expirationDate() < now;
254
255
0
    deleteCookie(cookie);
256
257
0
    if (!isDeletion) {
258
0
        d->allCookies += cookie;
259
0
        return true;
260
0
    }
261
0
    return false;
262
0
}
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
*/
274
bool QNetworkCookieJar::updateCookie(const QNetworkCookie &cookie)
275
0
{
276
0
    if (deleteCookie(cookie))
277
0
        return insertCookie(cookie);
278
0
    return false;
279
0
}
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
*/
289
bool QNetworkCookieJar::deleteCookie(const QNetworkCookie &cookie)
290
0
{
291
0
    Q_D(QNetworkCookieJar);
292
0
    const auto it = std::find_if(d->allCookies.cbegin(), d->allCookies.cend(),
293
0
                                 [&cookie](const auto &c) { return c.hasSameIdentifier(cookie); });
294
0
    if (it != d->allCookies.cend()) {
295
0
        d->allCookies.erase(it);
296
0
        return true;
297
0
    }
298
0
    return false;
299
0
}
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
*/
307
bool QNetworkCookieJar::validateCookie(const QNetworkCookie &cookie, const QUrl &url) const
308
0
{
309
0
    const QString cookieDomain = cookie.domain();
310
0
    QStringView domain = cookieDomain;
311
0
    const QString host = url.host();
312
0
    if (!isParentDomain(domain, host) && !isParentDomain(host, domain))
313
0
        return false; // not accepted
314
315
0
    if (domain.startsWith(u'.'))
316
0
        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
0
    if (host == domain)
322
0
        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
0
    return !qIsEffectiveTLD(domain);
329
0
}
330
331
QT_END_NAMESPACE
332
333
#include "moc_qnetworkcookiejar.cpp"