/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" |