From 757004ecf484289f576870b4f251c3e7551294c5 Mon Sep 17 00:00:00 2001 From: Allan Sandfeld Jensen Date: Fri, 1 Sep 2017 13:10:34 +0200 Subject: Reinstate cookie filter API Expose API to block cookies for specific domains, or third party cookies in general. Task-number: QTBUG-62897 Change-Id: I7f0e3f346368a2ef2fbd77f3197ee2dea50d57ce Reviewed-by: Peter Varga --- src/core/api/core_api.pro | 4 +- src/core/api/qwebenginecallback_p.h | 10 ++- src/core/api/qwebenginecookiestore.cpp | 90 +++++++++++++++++++++- src/core/api/qwebenginecookiestore.h | 12 +++ src/core/api/qwebenginecookiestore_p.h | 3 + src/core/content_browser_client_qt.cpp | 26 ++++++- src/core/content_browser_client_qt.h | 15 ++++ src/core/cookie_monster_delegate_qt.cpp | 16 +++- src/core/cookie_monster_delegate_qt.h | 1 + src/core/network_delegate_qt.cpp | 22 ++++-- src/core/network_delegate_qt.h | 3 + .../tst_qwebenginecookiestore.cpp | 33 ++++++++ tests/quicktestbrowser/BrowserWindow.qml | 8 ++ tests/quicktestbrowser/main.cpp | 22 +++++- 14 files changed, 250 insertions(+), 15 deletions(-) diff --git a/src/core/api/core_api.pro b/src/core/api/core_api.pro index 93eebbc45..0fea312f4 100644 --- a/src/core/api/core_api.pro +++ b/src/core/api/core_api.pro @@ -24,7 +24,9 @@ DEFINES += \ CHROMIUM_SRC_DIR = $$QTWEBENGINE_ROOT/$$getChromiumSrcDir() INCLUDEPATH += $$QTWEBENGINE_ROOT/src/core \ - $$CHROMIUM_SRC_DIR + $$CHROMIUM_SRC_DIR \ + $$CHROMIUM_SRC_DIR/third_party/skia/include/core \ + $$CHROMIUM_SRC_DIR/third_party/skia/include/config linux-g++*: QMAKE_CXXFLAGS += -Wno-unused-parameter diff --git a/src/core/api/qwebenginecallback_p.h b/src/core/api/qwebenginecallback_p.h index fdaf84d21..24b4495df 100644 --- a/src/core/api/qwebenginecallback_p.h +++ b/src/core/api/qwebenginecallback_p.h @@ -114,9 +114,15 @@ public: #undef DEFINE_INVOKE_FOR_TYPE template - void invokeDirectly(const QWebEngineCallback &callback, const A &argument) + void invokeDirectly(const QWebEngineCallback::type &> &callback, A &argument) { - return callback.d.data()->operator()(std::forward(argument)); + return callback.d.data()->operator()(argument); + } + + template + void invokeDirectly(const QWebEngineCallback::type> &callback, const A &argument) + { + return callback.d.data()->operator()(std::forward(argument)); } private: diff --git a/src/core/api/qwebenginecookiestore.cpp b/src/core/api/qwebenginecookiestore.cpp index 031eb8d3d..e034e08ea 100644 --- a/src/core/api/qwebenginecookiestore.cpp +++ b/src/core/api/qwebenginecookiestore.cpp @@ -40,7 +40,10 @@ #include "qwebenginecookiestore.h" #include "qwebenginecookiestore_p.h" -#include +#include "net/base/registry_controlled_domains/registry_controlled_domain.h" + +#include "cookie_monster_delegate_qt.h" +#include "type_conversion.h" #include #include @@ -179,6 +182,21 @@ void QWebEngineCookieStorePrivate::onCookieChanged(const QNetworkCookie &cookie, Q_EMIT q->cookieAdded(cookie); } +bool QWebEngineCookieStorePrivate::canAccessCookies(const QUrl &firstPartyUrl, const QUrl &url) +{ + if (!filterCallback) + return true; + + bool thirdParty = + !net::registry_controlled_domains::SameDomainOrHost(toGurl(url), + toGurl(firstPartyUrl), + net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES); + + QWebEngineCookieStore::FilterRequest request = { true, thirdParty, firstPartyUrl, url }; + callbackDirectory.invokeDirectly(filterCallback, request); + return request.accepted; +} + /*! \class QWebEngineCookieStore \inmodule QtWebEngineCore @@ -314,4 +332,74 @@ void QWebEngineCookieStore::deleteAllCookies() d->deleteAllCookies(); } +/*! + \fn void QWebEngineCookieStore::setCookieFilter(FunctorOrLambda filterCallback) + \since 5.11 + + Installs a cookie filter that can prevent sites and resources from using cookies. + The \a filterCallback must be a lambda or functor taking a FilterRequest structure. If the + cookie is to be rejected, the filter can set FilterRequest::accepted to \c false. + + The callback should not be used to execute heavy tasks since it is running on the + IO thread and therefore blocks the Chromium networking. + + \sa deleteAllCookies(), loadAllCookies() +*/ +void QWebEngineCookieStore::setCookieFilter(const QWebEngineCallback &filter) +{ + Q_D(QWebEngineCookieStore); + d->filterCallback = filter; +} + +/*! + \class QWebEngineCookieStore::FilterRequest + \inmodule QtWebEngineCore + \since 5.11 + + \brief This struct is used in conjunction with QWebEngineCookieStore::setCookieFilter() and is + the type \a filterCallback operates on. + + \sa QWebEngineCookieStore::setCookieFilter() +*/ + +/*! + \variable QWebEngineCookieStore::FilterRequest::accepted + \brief Whether the cookie access should be accepted or not. Defaults to \c true. + + Can be set to \c false by the filter to block the cookie access. +*/ + +/*! + \variable QWebEngineCookieStore::FilterRequest::firstPartyUrl + \brief The URL that was navigated to. + + The site that would be showing in the location bar if the application has one. + + Can be used to white-list or black-list cookie access or third-party cookie access + for specific sites visited. + + \sa origin, thirdParty +*/ + +/*! + \variable QWebEngineCookieStore::FilterRequest::origin + \brief The URL of the script or content accessing a cookie + + Can be used to white-list or black-list third-party cookie access + for specific services. + + \sa firstPartyUrl, thirdParty +*/ + +/*! + \variable QWebEngineCookieStore::FilterRequest::thirdParty + \brief Whether this is considered a third-party access + + This is calculated by comparing FilterRequest::origin and FilterRequest::firstPartyUrl and + checking if they share a common origin that is not a top-domain (like .com or .co.uk), + or a known hosting site with independently owned subdomains. + + \sa firstPartyUrl, origin +*/ + QT_END_NAMESPACE diff --git a/src/core/api/qwebenginecookiestore.h b/src/core/api/qwebenginecookiestore.h index c05a72cf1..d3feacefe 100644 --- a/src/core/api/qwebenginecookiestore.h +++ b/src/core/api/qwebenginecookiestore.h @@ -60,8 +60,20 @@ class QWEBENGINE_EXPORT QWebEngineCookieStore : public QObject { Q_OBJECT public: + struct FilterRequest { + bool accepted; + const bool thirdParty; + + const QUrl firstPartyUrl; + const QUrl origin; + }; virtual ~QWebEngineCookieStore(); +#ifdef Q_QDOC + void setCookieFilter(FunctorOrLambda filterCallback); +#else + void setCookieFilter(const QWebEngineCallback &filterCallback); +#endif void setCookie(const QNetworkCookie &cookie, const QUrl &origin = QUrl()); void deleteCookie(const QNetworkCookie &cookie, const QUrl &origin = QUrl()); void deleteSessionCookies(); diff --git a/src/core/api/qwebenginecookiestore_p.h b/src/core/api/qwebenginecookiestore_p.h index 582c0712a..809aa39b0 100644 --- a/src/core/api/qwebenginecookiestore_p.h +++ b/src/core/api/qwebenginecookiestore_p.h @@ -78,6 +78,7 @@ class QWEBENGINE_PRIVATE_EXPORT QWebEngineCookieStorePrivate QWebEngineCookieStore *q_ptr; public: QtWebEngineCore::CallbackDirectory callbackDirectory; + QWebEngineCallback filterCallback; QVector m_pendingUserCookies; quint64 m_nextCallbackId; bool m_deleteSessionCookiesPending; @@ -96,6 +97,8 @@ public: void deleteAllCookies(); void getAllCookies(); + bool canAccessCookies(const QUrl &firstPartyUrl, const QUrl &url); + void onGetAllCallbackResult(qint64 callbackId, const QByteArray &cookieList); void onSetCallbackResult(qint64 callbackId, bool success); void onDeleteCallbackResult(qint64 callbackId, int numCookies); diff --git a/src/core/content_browser_client_qt.cpp b/src/core/content_browser_client_qt.cpp index d0019e4f3..5689a4fb8 100644 --- a/src/core/content_browser_client_qt.cpp +++ b/src/core/content_browser_client_qt.cpp @@ -68,6 +68,7 @@ #include "device/geolocation/geolocation_provider.h" #include "mojo/public/cpp/bindings/binding.h" #include "mojo/public/cpp/bindings/binding_set.h" +#include "printing/features/features.h" #include "net/ssl/client_cert_identity.h" #include "services/service_manager/public/cpp/bind_source_info.h" #include "services/service_manager/public/cpp/binder_registry.h" @@ -93,7 +94,7 @@ #include "device/geolocation/location_provider.h" #endif #include "media_capture_devices_dispatcher.h" -#include "printing/features/features.h" +#include "network_delegate_qt.h" #if BUILDFLAG(ENABLE_BASIC_PRINTING) #include "printing_message_filter_qt.h" #endif // BUILDFLAG(ENABLE_BASIC_PRINTING) @@ -660,6 +661,29 @@ bool ContentBrowserClientQt::CanCreateWindow( return (settings && settings->getJavaScriptCanOpenWindowsAutomatically()) || user_gesture; } +bool ContentBrowserClientQt::AllowGetCookie(const GURL &url, + const GURL &first_party, + const net::CookieList & /*cookie_list*/, + content::ResourceContext *context, + int /*render_process_id*/, + int /*render_frame_id*/) +{ + NetworkDelegateQt *networkDelegate = static_cast(context->GetRequestContext()->network_delegate()); + return networkDelegate->canGetCookies(first_party, url); +} + +bool ContentBrowserClientQt::AllowSetCookie(const GURL &url, + const GURL &first_party, + const std::string &cookie_line, + content::ResourceContext *context, + int /*render_process_id*/, + int /*render_frame_id*/, + const net::CookieOptions& /*options*/) +{ + NetworkDelegateQt *networkDelegate = static_cast(context->GetRequestContext()->network_delegate()); + return networkDelegate->canSetCookies(first_party, url, cookie_line); +} + } // namespace QtWebEngineCore DEFINE_WEB_CONTENTS_USER_DATA_KEY(QtWebEngineCore::ServiceDriver); diff --git a/src/core/content_browser_client_qt.h b/src/core/content_browser_client_qt.h index d810fc08a..db5cbd191 100644 --- a/src/core/content_browser_client_qt.h +++ b/src/core/content_browser_client_qt.h @@ -137,6 +137,21 @@ public: bool opener_suppressed, bool* no_javascript_access) override; + bool AllowGetCookie(const GURL& url, + const GURL& first_party, + const net::CookieList& cookie_list, + content::ResourceContext* context, + int render_process_id, + int render_frame_id) override; + + bool AllowSetCookie(const GURL& url, + const GURL& first_party, + const std::string& cookie_line, + content::ResourceContext* context, + int render_process_id, + int render_frame_id, + const net::CookieOptions& options) override; + #if defined(Q_OS_LINUX) void GetAdditionalMappedFilesForChildProcess(const base::CommandLine& command_line, int child_process_id, content::FileDescriptorInfo* mappings) override; #endif diff --git a/src/core/cookie_monster_delegate_qt.cpp b/src/core/cookie_monster_delegate_qt.cpp index 0fa8dead0..58cd481dc 100644 --- a/src/core/cookie_monster_delegate_qt.cpp +++ b/src/core/cookie_monster_delegate_qt.cpp @@ -211,10 +211,20 @@ void CookieMonsterDelegateQt::setClient(QWebEngineCookieStore *client) m_client->d_func()->processPendingUserCookies(); } -bool CookieMonsterDelegateQt::canSetCookie(const QUrl &firstPartyUrl, const QByteArray &cookieLine, const QUrl &url) +bool CookieMonsterDelegateQt::canSetCookie(const QUrl &firstPartyUrl, const QByteArray &/*cookieLine*/, const QUrl &url) { - // TODO: should be used for FilterRequest implementation - return true; + if (!m_client) + return true; + + return m_client->d_func()->canAccessCookies(firstPartyUrl, url); +} + +bool CookieMonsterDelegateQt::canGetCookies(const QUrl &firstPartyUrl, const QUrl &url) +{ + if (!m_client) + return true; + + return m_client->d_func()->canAccessCookies(firstPartyUrl, url); } void CookieMonsterDelegateQt::OnCookieChanged(const net::CanonicalCookie& cookie, bool removed, net::CookieStore::ChangeCause cause) diff --git a/src/core/cookie_monster_delegate_qt.h b/src/core/cookie_monster_delegate_qt.h index 4625eb264..941992a7b 100644 --- a/src/core/cookie_monster_delegate_qt.h +++ b/src/core/cookie_monster_delegate_qt.h @@ -83,6 +83,7 @@ public: void setClient(QWebEngineCookieStore *client); bool canSetCookie(const QUrl &firstPartyUrl, const QByteArray &cookieLine, const QUrl &url); + bool canGetCookies(const QUrl &firstPartyUrl, const QUrl &url); void OnCookieChanged(const net::CanonicalCookie& cookie, bool removed, net::CookieStore::ChangeCause cause) override; private: diff --git a/src/core/network_delegate_qt.cpp b/src/core/network_delegate_qt.cpp index 90d4e6ce2..f82d47fa1 100644 --- a/src/core/network_delegate_qt.cpp +++ b/src/core/network_delegate_qt.cpp @@ -237,11 +237,26 @@ void NetworkDelegateQt::NotifyNavigationRequestedOnUIThread(net::URLRequest *req bool NetworkDelegateQt::OnCanSetCookie(const net::URLRequest& request, const std::string& cookie_line, net::CookieOptions*) +{ + return canSetCookies(request.first_party_for_cookies(), request.url(), cookie_line); +} + +bool NetworkDelegateQt::OnCanGetCookies(const net::URLRequest& request, const net::CookieList&) +{ + return canGetCookies(request.first_party_for_cookies(), request.url()); +} + +bool NetworkDelegateQt::canSetCookies(const GURL &first_party, const GURL &url, const std::string &cookie_line) const { Q_ASSERT(m_requestContextGetter); - return m_requestContextGetter->m_cookieDelegate->canSetCookie(toQt(request.first_party_for_cookies()), QByteArray::fromStdString(cookie_line), toQt(request.url())); + return m_requestContextGetter->m_cookieDelegate->canSetCookie(toQt(first_party), QByteArray::fromStdString(cookie_line), toQt(url)); } +bool NetworkDelegateQt::canGetCookies(const GURL &first_party, const GURL &url) const +{ + Q_ASSERT(m_requestContextGetter); + return m_requestContextGetter->m_cookieDelegate->canGetCookies(toQt(first_party), toQt(url)); +} int NetworkDelegateQt::OnBeforeStartTransaction(net::URLRequest *request, const net::CompletionCallback &callback, net::HttpRequestHeaders *headers) { @@ -291,11 +306,6 @@ net::NetworkDelegate::AuthRequiredResponse NetworkDelegateQt::OnAuthRequired(net return AUTH_REQUIRED_RESPONSE_NO_ACTION; } -bool NetworkDelegateQt::OnCanGetCookies(const net::URLRequest&, const net::CookieList&) -{ - return true; -} - bool NetworkDelegateQt::OnCanAccessFile(const net::URLRequest&, const base::FilePath&, const base::FilePath&) const { return true; diff --git a/src/core/network_delegate_qt.h b/src/core/network_delegate_qt.h index b0a8e1ef9..452d1dd77 100644 --- a/src/core/network_delegate_qt.h +++ b/src/core/network_delegate_qt.h @@ -100,6 +100,9 @@ public: const GURL& endpoint) const override; virtual bool OnCanUseReportingClient(const url::Origin& origin, const GURL& endpoint) const override; + + bool canSetCookies(const GURL &first_party, const GURL &url, const std::string &cookie_line) const; + bool canGetCookies(const GURL &first_party, const GURL &url) const; }; } // namespace QtWebEngineCore diff --git a/tests/auto/core/qwebenginecookiestore/tst_qwebenginecookiestore.cpp b/tests/auto/core/qwebenginecookiestore/tst_qwebenginecookiestore.cpp index 930c208ee..913614df2 100644 --- a/tests/auto/core/qwebenginecookiestore/tst_qwebenginecookiestore.cpp +++ b/tests/auto/core/qwebenginecookiestore/tst_qwebenginecookiestore.cpp @@ -51,6 +51,7 @@ private Q_SLOTS: void cookieSignals(); void setAndDeleteCookie(); void batchCookieTasks(); + void basicFilter(); private: QWebEngineProfile m_profile; @@ -186,5 +187,37 @@ void tst_QWebEngineCookieStore::batchCookieTasks() QTRY_COMPARE(cookieRemovedSpy.count(), 4); } +void tst_QWebEngineCookieStore::basicFilter() +{ + QWebEnginePage page(&m_profile); + QWebEngineCookieStore *client = m_profile.cookieStore(); + + QAtomicInt accessTested = 0; + client->setCookieFilter([&](QWebEngineCookieStore::FilterRequest &){ ++accessTested; }); + + QSignalSpy loadSpy(&page, SIGNAL(loadFinished(bool))); + QSignalSpy cookieAddedSpy(client, SIGNAL(cookieAdded(const QNetworkCookie &))); + QSignalSpy cookieRemovedSpy(client, SIGNAL(cookieRemoved(const QNetworkCookie &))); + + page.load(QUrl("qrc:///resources/index.html")); + + QTRY_COMPARE(loadSpy.count(), 1); + QVERIFY(loadSpy.takeFirst().takeFirst().toBool()); + QTRY_COMPARE(cookieAddedSpy.count(), 2); + QTRY_COMPARE(accessTested.loadAcquire(), 2); + + client->deleteAllCookies(); + QTRY_COMPARE(cookieRemovedSpy.count(), 2); + + client->setCookieFilter([&](QWebEngineCookieStore::FilterRequest &request){ ++accessTested; request.accepted = false; }); + page.triggerAction(QWebEnginePage::ReloadAndBypassCache); + QTRY_COMPARE(loadSpy.count(), 1); + QVERIFY(loadSpy.takeFirst().takeFirst().toBool()); + QTRY_COMPARE(accessTested.loadAcquire(), 4); + // Test cookies are NOT added: + QTest::qWait(100); + QCOMPARE(cookieAddedSpy.count(), 2); +} + QTEST_MAIN(tst_QWebEngineCookieStore) #include "tst_qwebenginecookiestore.moc" diff --git a/tests/quicktestbrowser/BrowserWindow.qml b/tests/quicktestbrowser/BrowserWindow.qml index 2d8807e8c..22f98e1c5 100644 --- a/tests/quicktestbrowser/BrowserWindow.qml +++ b/tests/quicktestbrowser/BrowserWindow.qml @@ -66,6 +66,7 @@ ApplicationWindow { property alias javaScriptEnabled: javaScriptEnabled.checked; property alias errorPageEnabled: errorPageEnabled.checked; property alias pluginsEnabled: pluginsEnabled.checked; + property alias thirdPartyCookiesEnabled: thirdPartyCookiesEnabled.checked; } // Make sure the Qt.WindowFullscreenButtonHint is set on OS X. @@ -240,6 +241,13 @@ ApplicationWindow { checkable: true checked: true } + MenuItem { + id: thirdPartyCookiesEnabled + text: "Third party cookies enabled" + checkable: true + checked: true + onToggled: applicationRoot.thirdPartyCookiesEnabled = checked + } MenuItem { id: offTheRecordEnabled text: "Off The Record" diff --git a/tests/quicktestbrowser/main.cpp b/tests/quicktestbrowser/main.cpp index 3f513f6a6..d56841974 100644 --- a/tests/quicktestbrowser/main.cpp +++ b/tests/quicktestbrowser/main.cpp @@ -70,7 +70,27 @@ int main(int argc, char **argv) Utils utils; appEngine.rootContext()->setContextProperty("utils", &utils); appEngine.load(QUrl("qrc:/ApplicationRoot.qml")); - QMetaObject::invokeMethod(appEngine.rootObjects().first(), "load", Q_ARG(QVariant, startupUrl())); + + QObject *rootObject = appEngine.rootObjects().first(); + + QQuickWebEngineProfile *profile = new QQuickWebEngineProfile(rootObject); + + const QMetaObject *rootMeta = rootObject->metaObject(); + int index = rootMeta->indexOfProperty("thirdPartyCookiesEnabled"); + Q_ASSERT(index != -1); + QMetaProperty thirdPartyCookiesProperty = rootMeta->property(index); + profile->cookieStore()->setCookieFilter( + [rootObject,&thirdPartyCookiesProperty](QWebEngineCookieStore::FilterRequest &request) + { + request.accepted = !request.thirdParty || thirdPartyCookiesProperty.read(rootObject).toBool(); + }); + + index = rootMeta->indexOfProperty("testProfile"); + Q_ASSERT(index != -1); + QMetaProperty profileProperty = rootMeta->property(index); + profileProperty.write(rootObject, qVariantFromValue(profile)); + + QMetaObject::invokeMethod(rootObject, "load", Q_ARG(QVariant, startupUrl())); return app.exec(); } -- cgit v1.2.3