1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtWebEngine module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qwebenginecookiestore.h"
41#include "qwebenginecookiestore_p.h"
42
43#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
44
45#include "net/cookie_monster_delegate_qt.h"
46
47#include <QByteArray>
48#include <QUrl>
49
50namespace {
51
52inline GURL toGurl(const QUrl &url)
53{
54 return GURL(url.toString().toStdString());
55}
56
57} // namespace
58
59QT_BEGIN_NAMESPACE
60
61using namespace QtWebEngineCore;
62
63QWebEngineCookieStorePrivate::QWebEngineCookieStorePrivate(QWebEngineCookieStore *q)
64 : q_ptr(q)
65 , m_nextCallbackId(CallbackDirectory::ReservedCallbackIdsEnd)
66 , m_deleteSessionCookiesPending(false)
67 , m_deleteAllCookiesPending(false)
68 , m_getAllCookiesPending(false)
69 , delegate(0)
70{}
71
72void QWebEngineCookieStorePrivate::processPendingUserCookies()
73{
74 Q_ASSERT(delegate);
75 Q_ASSERT(delegate->hasCookieMonster());
76
77 if (m_getAllCookiesPending) {
78 m_getAllCookiesPending = false;
79 delegate->getAllCookies(CallbackDirectory::GetAllCookiesCallbackId);
80 }
81
82 if (m_deleteAllCookiesPending) {
83 m_deleteAllCookiesPending = false;
84 delegate->deleteAllCookies(CallbackDirectory::DeleteAllCookiesCallbackId);
85 }
86
87 if (m_deleteSessionCookiesPending) {
88 m_deleteSessionCookiesPending = false;
89 delegate->deleteSessionCookies(CallbackDirectory::DeleteSessionCookiesCallbackId);
90 }
91
92 if (m_pendingUserCookies.isEmpty())
93 return;
94
95 for (const CookieData &cookieData : qAsConst(m_pendingUserCookies)) {
96 if (cookieData.callbackId == CallbackDirectory::DeleteCookieCallbackId)
97 delegate->deleteCookie(cookieData.cookie, cookieData.origin);
98 else
99 delegate->setCookie(cookieData.callbackId, cookieData.cookie, cookieData.origin);
100 }
101
102 m_pendingUserCookies.clear();
103}
104
105void QWebEngineCookieStorePrivate::rejectPendingUserCookies()
106{
107 m_getAllCookiesPending = false;
108 m_deleteAllCookiesPending = false;
109 m_deleteSessionCookiesPending = false;
110 m_pendingUserCookies.clear();
111}
112
113void QWebEngineCookieStorePrivate::setCookie(const QWebEngineCallback<bool> &callback, const QNetworkCookie &cookie,
114 const QUrl &origin)
115{
116 const quint64 currentCallbackId = callback ? m_nextCallbackId++ : static_cast<quint64>(CallbackDirectory::NoCallbackId);
117
118 if (currentCallbackId != CallbackDirectory::NoCallbackId)
119 callbackDirectory.registerCallback(currentCallbackId, callback);
120
121 if (!delegate || !delegate->hasCookieMonster()) {
122 m_pendingUserCookies.append(CookieData{ currentCallbackId, cookie, origin });
123 return;
124 }
125
126 delegate->setCookie(currentCallbackId, cookie, origin);
127}
128
129void QWebEngineCookieStorePrivate::deleteCookie(const QNetworkCookie &cookie, const QUrl &url)
130{
131 if (!delegate || !delegate->hasCookieMonster()) {
132 m_pendingUserCookies.append(CookieData{ CallbackDirectory::DeleteCookieCallbackId, cookie, url });
133 return;
134 }
135
136 delegate->deleteCookie(cookie, url);
137}
138
139void QWebEngineCookieStorePrivate::deleteSessionCookies()
140{
141 if (!delegate || !delegate->hasCookieMonster()) {
142 m_deleteSessionCookiesPending = true;
143 return;
144 }
145
146 delegate->deleteSessionCookies(CallbackDirectory::DeleteSessionCookiesCallbackId);
147}
148
149void QWebEngineCookieStorePrivate::deleteAllCookies()
150{
151 if (!delegate || !delegate->hasCookieMonster()) {
152 m_deleteAllCookiesPending = true;
153 m_deleteSessionCookiesPending = false;
154 return;
155 }
156
157 delegate->deleteAllCookies(CallbackDirectory::DeleteAllCookiesCallbackId);
158}
159
160void QWebEngineCookieStorePrivate::getAllCookies()
161{
162 if (!delegate || !delegate->hasCookieMonster()) {
163 m_getAllCookiesPending = true;
164 return;
165 }
166
167 delegate->getAllCookies(CallbackDirectory::GetAllCookiesCallbackId);
168}
169
170void QWebEngineCookieStorePrivate::onGetAllCallbackResult(qint64 callbackId, const QByteArray &cookieList)
171{
172 callbackDirectory.invoke(callbackId, cookieList);
173}
174void QWebEngineCookieStorePrivate::onSetCallbackResult(qint64 callbackId, bool success)
175{
176 callbackDirectory.invoke(callbackId, success);
177}
178
179void QWebEngineCookieStorePrivate::onDeleteCallbackResult(qint64 callbackId, int numCookies)
180{
181 callbackDirectory.invoke(callbackId, numCookies);
182}
183
184void QWebEngineCookieStorePrivate::onCookieChanged(const QNetworkCookie &cookie, bool removed)
185{
186 if (removed)
187 Q_EMIT q_ptr->cookieRemoved(cookie);
188 else
189 Q_EMIT q_ptr->cookieAdded(cookie);
190}
191
192bool QWebEngineCookieStorePrivate::canAccessCookies(const QUrl &firstPartyUrl, const QUrl &url) const
193{
194 if (!filterCallback)
195 return true;
196
197 // Empty first-party URL indicates a first-party request (see net/base/static_cookie_policy.cc)
198 bool thirdParty = !firstPartyUrl.isEmpty() &&
199 !net::registry_controlled_domains::SameDomainOrHost(toGurl(url),
200 toGurl(firstPartyUrl),
201 net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES);
202
203 QWebEngineCookieStore::FilterRequest request = { firstPartyUrl, url, thirdParty, false, 0 };
204 return filterCallback(request);
205}
206
207/*!
208 \class QWebEngineCookieStore
209 \inmodule QtWebEngineCore
210 \since 5.6
211 \brief The QWebEngineCookieStore class provides access to Chromium's cookies.
212
213 The class allows to access HTTP cookies of Chromium for a specific profile.
214 It can be used to synchronize cookies of Chromium and the QNetworkAccessManager, as well as
215 to set, delete, and intercept cookies during navigation.
216 Because cookie operations are asynchronous, the user can choose to provide a callback function
217 to get notified about the success of the operation.
218 The signal handlers for removal and addition should not be used to execute heavy tasks,
219 because they might block the IO thread in case of a blocking connection.
220
221 Use QWebEngineProfile::cookieStore() and QQuickWebEngineProfile::cookieStore()
222 to access the cookie store object for a specific profile.
223*/
224
225/*!
226 \fn void QWebEngineCookieStore::cookieAdded(const QNetworkCookie &cookie)
227
228 This signal is emitted whenever a new \a cookie is added to the cookie store.
229*/
230
231/*!
232 \fn void QWebEngineCookieStore::cookieRemoved(const QNetworkCookie &cookie)
233
234 This signal is emitted whenever a \a cookie is deleted from the cookie store.
235*/
236
237/*!
238 Creates a new QWebEngineCookieStore object with \a parent.
239*/
240
241QWebEngineCookieStore::QWebEngineCookieStore(QObject *parent)
242 : QObject(parent)
243 , d_ptr(new QWebEngineCookieStorePrivate(this))
244{
245}
246
247/*!
248 Destroys this QWebEngineCookieStore object.
249*/
250
251QWebEngineCookieStore::~QWebEngineCookieStore() {}
252
253/*!
254 Adds \a cookie to the cookie store.
255 \note If \a cookie specifies a QNetworkCookie::domain() that does not start with a dot,
256 a dot is automatically prepended. To limit the cookie to the exact server,
257 omit QNetworkCookie::domain() and set \a origin instead.
258
259 The provided URL should also include the scheme.
260
261 \note This operation is asynchronous.
262*/
263
264void QWebEngineCookieStore::setCookie(const QNetworkCookie &cookie, const QUrl &origin)
265{
266 //TODO: use callbacks or delete dummy ones
267 d_ptr->setCookie(QWebEngineCallback<bool>(), cookie, origin);
268}
269
270/*!
271 Deletes \a cookie from the cookie store.
272 It is possible to provide an optional \a origin URL argument to limit the scope of the
273 cookie to be deleted.
274
275 \note This operation is asynchronous.
276*/
277
278void QWebEngineCookieStore::deleteCookie(const QNetworkCookie &cookie, const QUrl &origin)
279{
280 d_ptr->deleteCookie(cookie, origin);
281}
282
283/*!
284 Loads all the cookies into the cookie store. The cookieAdded() signal is emitted on every
285 loaded cookie. Cookies are loaded automatically when the store gets initialized, which
286 in most cases happens on loading the first URL. However, calling this function is useful
287 if cookies should be listed before entering the web content.
288
289 \note This operation is asynchronous.
290*/
291
292void QWebEngineCookieStore::loadAllCookies()
293{
294 //TODO: use callbacks or delete dummy ones
295 if (d_ptr->m_getAllCookiesPending)
296 return;
297 d_ptr->callbackDirectory.registerCallback(CallbackDirectory::GetAllCookiesCallbackId,
298 QWebEngineCallback<const QByteArray &>());
299 //this will trigger cookieAdded signal
300 d_ptr->getAllCookies();
301}
302
303/*!
304 Deletes all the session cookies in the cookie store. Session cookies do not have an
305 expiration date assigned to them.
306
307 \note This operation is asynchronous.
308 \sa loadAllCookies()
309*/
310
311void QWebEngineCookieStore::deleteSessionCookies()
312{
313 //TODO: use callbacks or delete dummy ones
314 if (d_ptr->m_deleteAllCookiesPending || d_ptr->m_deleteSessionCookiesPending)
315 return;
316 d_ptr->callbackDirectory.registerCallback(CallbackDirectory::DeleteSessionCookiesCallbackId, QWebEngineCallback<int>());
317 d_ptr->deleteSessionCookies();
318}
319
320/*!
321 Deletes all the cookies in the cookie store.
322 \note This operation is asynchronous.
323 \sa loadAllCookies()
324*/
325
326void QWebEngineCookieStore::deleteAllCookies()
327{
328 //TODO: use callbacks or delete dummy ones
329 if (d_ptr->m_deleteAllCookiesPending)
330 return;
331 d_ptr->callbackDirectory.registerCallback(CallbackDirectory::DeleteAllCookiesCallbackId, QWebEngineCallback<int>());
332 d_ptr->deleteAllCookies();
333}
334
335/*!
336 \since 5.11
337
338 Installs a cookie filter that can prevent sites and resources from using cookies.
339 The \a filterCallback must be a lambda or functor taking a FilterRequest structure. If the
340 cookie access is to be accepted, the filter function should return \c true; otherwise
341 it should return \c false.
342
343 The following code snippet illustrates how to set a cookie filter:
344
345 \code
346 profile->cookieStore()->setCookieFilter(
347 [&allowThirdPartyCookies](const QWebEngineCookieStore::FilterRequest &request)
348 { return !request.thirdParty || allowThirdPartyCookies; }
349 );
350 \endcode
351
352 You can unset the filter with a \c nullptr argument.
353
354 The callback should not be used to execute heavy tasks since it is running on the
355 IO thread and therefore blocks the Chromium networking.
356
357 \note The cookie filter also controls other features with tracking capabilities similar to
358 those of cookies; including IndexedDB, DOM storage, filesystem API, service workers,
359 and AppCache.
360
361 \sa deleteAllCookies(), loadAllCookies()
362*/
363void QWebEngineCookieStore::setCookieFilter(const std::function<bool(const FilterRequest &)> &filterCallback)
364{
365 d_ptr->filterCallback = filterCallback;
366}
367
368/*!
369 \since 5.11
370 \overload
371*/
372void QWebEngineCookieStore::setCookieFilter(std::function<bool(const FilterRequest &)> &&filterCallback)
373{
374 d_ptr->filterCallback = std::move(filterCallback);
375}
376
377/*!
378 \class QWebEngineCookieStore::FilterRequest
379 \inmodule QtWebEngineCore
380 \since 5.11
381
382 \brief The QWebEngineCookieStore::FilterRequest struct is used in conjunction with QWebEngineCookieStore::setCookieFilter() and is
383 the type \a filterCallback operates on.
384
385 \sa QWebEngineCookieStore::setCookieFilter()
386*/
387
388/*!
389 \variable QWebEngineCookieStore::FilterRequest::firstPartyUrl
390 \brief The URL that was navigated to.
391
392 The site that would be showing in the location bar if the application has one.
393
394 Can be used to white-list or black-list cookie access or third-party cookie access
395 for specific sites visited.
396
397 \sa origin, thirdParty
398*/
399
400/*!
401 \variable QWebEngineCookieStore::FilterRequest::_reservedFlag
402 \internal
403*/
404
405/*!
406 \variable QWebEngineCookieStore::FilterRequest::_reservedType
407 \internal
408*/
409
410/*!
411 \variable QWebEngineCookieStore::FilterRequest::origin
412 \brief The URL of the script or content accessing a cookie.
413
414 Can be used to white-list or black-list third-party cookie access
415 for specific services.
416
417 \sa firstPartyUrl, thirdParty
418*/
419
420/*!
421 \variable QWebEngineCookieStore::FilterRequest::thirdParty
422 \brief Whether this is considered a third-party access.
423
424 This is calculated by comparing FilterRequest::origin and FilterRequest::firstPartyUrl and
425 checking if they share a common origin that is not a top-domain (like .com or .co.uk),
426 or a known hosting site with independently owned subdomains.
427
428 \sa firstPartyUrl, origin
429*/
430
431QT_END_NAMESPACE
432