File indexing completed on 2024-05-19 05:01:16
0001 /* 0002 This file is part of the KDE project 0003 SPDX-FileCopyrightText: 2018 Stefano Crocco <stefano.crocco@alice.it> 0004 0005 SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0006 */ 0007 0008 #include "webenginepartcookiejarkio_test.h" 0009 #include <cookies/webenginepartcookiejar_kio.h> 0010 0011 #include <QTest> 0012 0013 #include <QNetworkCookie> 0014 #include <QWebEngineCookieStore> 0015 #include <QWebEngineProfile> 0016 #include <QDBusInterface> 0017 #include <QDBusReply> 0018 0019 #include <algorithm> 0020 0021 //Cookie expiration dates returned by KCookieServer always have msecs set to 0 0022 static QDateTime currentDateTime(){return QDateTime::fromSecsSinceEpoch(QDateTime::currentMSecsSinceEpoch()/1000);} 0023 0024 namespace QTest { 0025 template <> 0026 char *toString(const QNetworkCookie &cookie){ 0027 QByteArray ba = "QNetworkCookie{"; 0028 ba += "\nname: " + cookie.name(); 0029 ba += "\ndomain: " + (cookie.domain().isEmpty() ? "<EMPTY>" : cookie.domain().toUtf8()); 0030 ba += "\npath: " + (cookie.path().isEmpty() ? "<EMPTY>" : cookie.path().toUtf8()); 0031 ba += "\nvalue: " + cookie.value(); 0032 ba += "\nexpiration: " + (cookie.expirationDate().isValid() ? QByteArray::number(cookie.expirationDate().toMSecsSinceEpoch()) : "<INVALID>"); 0033 ba += "\nsecure: " + QByteArray::number(cookie.isSecure()); 0034 ba += "\nhttp: only" + QByteArray::number(cookie.isHttpOnly()); 0035 return qstrdup(ba.data()); 0036 } 0037 } 0038 0039 QTEST_MAIN(TestWebEnginePartCookieJarKIO); 0040 0041 void TestWebEnginePartCookieJarKIO::initTestCase() 0042 { 0043 m_cookieName = "webenginepartcookiejartest"; 0044 } 0045 0046 void TestWebEnginePartCookieJarKIO::init() 0047 { 0048 m_server = new QDBusInterface("org.kde.kcookiejar5", "/modules/kcookiejar", "org.kde.KCookieServer"); 0049 m_profile = new QWebEngineProfile(this); 0050 m_store = m_profile->cookieStore(); 0051 m_jar = new WebEnginePartCookieJarKIO(m_profile, this); 0052 } 0053 0054 void TestWebEnginePartCookieJarKIO::cleanup() 0055 { 0056 if (m_server->isValid()) { 0057 deleteCookies(findTestCookies()); 0058 } 0059 delete m_server; 0060 m_server = nullptr; 0061 delete m_jar; 0062 m_jar = nullptr; 0063 m_store = nullptr; 0064 delete m_profile; 0065 m_profile = nullptr; 0066 } 0067 0068 QNetworkCookie TestWebEnginePartCookieJarKIO::CookieData::cookie() const 0069 { 0070 QNetworkCookie cookie; 0071 cookie.setName(name.toUtf8()); 0072 cookie.setValue(value.toUtf8()); 0073 cookie.setPath(path); 0074 cookie.setDomain(domain); 0075 cookie.setSecure(secure); 0076 cookie.setExpirationDate(expiration); 0077 return cookie; 0078 } 0079 0080 void TestWebEnginePartCookieJarKIO::testCookieAddedToStoreAreAddedToKCookieServer_data() 0081 { 0082 QTest::addColumn<QNetworkCookie>("cookie"); 0083 QTest::addColumn<QString>("name"); 0084 QTest::addColumn<QString>("value"); 0085 QTest::addColumn<QString>("domain"); 0086 QTest::addColumn<QString>("path"); 0087 QTest::addColumn<QString>("host"); 0088 QTest::addColumn<QDateTime>("expiration"); 0089 QTest::addColumn<bool>("secure"); 0090 0091 const QStringList labels{ 0092 "persistent cookie with domain and path", 0093 "session cookie with domain and path", 0094 "persistent cookie with domain and no path", 0095 "persistent cookie with domain and / as path", 0096 "persistent cookie with path and no domain", 0097 "persistent cookie without secure", 0098 }; 0099 0100 const QList<CookieData> input{ 0101 {m_cookieName + "-persistent", "test-value", ".yyy.xxx.com", "/abc/def/", "zzz.yyy.xxx.com", QDateTime::currentDateTime().addYears(1), true}, 0102 {m_cookieName + "-session", "test-value", ".yyy.xxx.com", "/abc/def/", "zzz.yyy.xxx.com", QDateTime(), true}, 0103 {m_cookieName + "-no-path", "test-value", ".yyy.xxx.com", "", "zzz.yyy.xxx.com", QDateTime::currentDateTime().addYears(1), true}, 0104 {m_cookieName + "-slash-as-path", "test-value", ".yyy.xxx.com", "", "zzz.yyy.xxx.com", QDateTime::currentDateTime().addYears(1), true}, 0105 {m_cookieName + "-no-domain", "test-value", "", "/abc/def/", "zzz.yyy.xxx.com", QDateTime::currentDateTime().addYears(1), true}, 0106 {m_cookieName + "-no-secure", "test-value", ".yyy.xxx.com", "/abc/def/", "zzz.yyy.xxx.com", QDateTime::currentDateTime().addYears(1), false} 0107 }; 0108 0109 QList<CookieData> expected(input); 0110 0111 for (int i = 0; i < input.count(); ++i) { 0112 const CookieData &ex = expected.at(i); 0113 const CookieData &in = input.at(i); 0114 QNetworkCookie c = in.cookie(); 0115 if (in.domain.isEmpty()) { 0116 c.normalize(QUrl("https://" + in.host)); 0117 } 0118 QTest::newRow(labels.at(i).toLatin1().constData()) << c << ex.name << ex.value << ex.domain << ex.path << ex.host << ex.expiration << ex.secure; 0119 } 0120 } 0121 0122 void TestWebEnginePartCookieJarKIO::testCookieAddedToStoreAreAddedToKCookieServer() 0123 { 0124 QFETCH(const QNetworkCookie, cookie); 0125 QFETCH(const QString, name); 0126 QFETCH(const QString, value); 0127 QFETCH(const QString, domain); 0128 QFETCH(const QString, path); 0129 QFETCH(const QString, host); 0130 QFETCH(const QDateTime, expiration); 0131 QFETCH(const bool, secure); 0132 0133 QVERIFY2(m_server->isValid(), qPrintable(m_server->lastError().message())); 0134 0135 // domain=0, path=1, name=2, host=3, value=4, expirationDate=5, protocolVersion=6, secure=7; 0136 const QList<int> fields{0,1,2,3,4,5,6,7}; 0137 0138 emit m_store->cookieAdded(cookie); 0139 const QDBusReply<QStringList> res = m_server->call(QDBus::Block, "findCookies", QVariant::fromValue(fields), domain, host, cookie.path(), QString(cookie.name())); 0140 QVERIFY2(!m_server->lastError().isValid(), m_server->lastError().message().toLatin1().constData()); 0141 QStringList resFields = res.value(); 0142 0143 QVERIFY(!resFields.isEmpty()); 0144 QCOMPARE(fields.count(), resFields.count()); 0145 0146 QCOMPARE(resFields.at(0), domain); 0147 QCOMPARE(resFields.at(1), path); 0148 QCOMPARE(resFields.at(2), name); 0149 if (!domain.isEmpty()){ 0150 QEXPECT_FAIL("", "The value returned by KCookieServer strips the leftmost part of the fqdn. Why?", Continue); 0151 } 0152 QCOMPARE(resFields.at(3), host); 0153 QCOMPARE(resFields.at(4), value); 0154 const int secsSinceEpoch = resFields.at(5).toInt(); 0155 //KCookieServer gives a session cookie an expiration time equal to epoch, while QNetworkCookie uses an invalid QDateTime 0156 if (!expiration.isValid()) { 0157 QCOMPARE(secsSinceEpoch, 0); 0158 } else { 0159 QCOMPARE(secsSinceEpoch, expiration.toSecsSinceEpoch()); 0160 } 0161 const bool sec = resFields.at(7).toInt(); 0162 QCOMPARE(sec, secure); 0163 } 0164 0165 QList<TestWebEnginePartCookieJarKIO::CookieData> TestWebEnginePartCookieJarKIO::findTestCookies() 0166 { 0167 QList<CookieData> cookies; 0168 if (!m_server->isValid()) { 0169 return cookies; 0170 } 0171 QDBusReply<QStringList> rep = m_server->call(QDBus::Block, "findDomains"); 0172 if (!rep.isValid()) { 0173 qDebug() << rep.error().message(); 0174 return cookies; 0175 } 0176 QStringList domains = rep.value(); 0177 //domain, path, name, host 0178 const QList<int> fields{0,1,2,3}; 0179 for (const QString &d: domains){ 0180 rep = m_server->call(QDBus::Block, "findCookies", QVariant::fromValue(fields), d, "", "", ""); 0181 if (!rep.isValid()) { 0182 qDebug() << rep.error().message(); 0183 return cookies; 0184 } 0185 QStringList res = rep.value(); 0186 for (int i = 0; i < res.count(); i+= fields.count()) { 0187 if (res.at(i+2).startsWith(m_cookieName)) { 0188 CookieData d; 0189 d.name = res.at(i+2); 0190 d.domain = res.at(i); 0191 d.path = res.at(i+1); 0192 d.host = res.at(i+3); 0193 cookies.append(d); 0194 } 0195 } 0196 } 0197 return cookies; 0198 } 0199 0200 void TestWebEnginePartCookieJarKIO::deleteCookies(const QList<TestWebEnginePartCookieJarKIO::CookieData> &cookies) 0201 { 0202 if (!m_server->isValid()) { 0203 return; 0204 } 0205 for (const CookieData &c: cookies) { 0206 QDBusMessage deleteRep = m_server->call(QDBus::Block, "deleteCookie", c.domain, c.host, c.path, c.name); 0207 if (m_server->lastError().isValid()) { 0208 qDebug() << m_server->lastError().message(); 0209 } 0210 } 0211 } 0212 0213 void TestWebEnginePartCookieJarKIO::testCookieRemovedFromStoreAreRemovedFromKCookieServer_data() 0214 { 0215 QTest::addColumn<QNetworkCookie>("cookie"); 0216 QTest::addColumn<QString>("name"); 0217 QTest::addColumn<QString>("domain"); 0218 QTest::addColumn<QString>("path"); 0219 QTest::addColumn<QString>("host"); 0220 0221 const QStringList labels{ 0222 "remove persistent cookie with domain and path", 0223 "remove session cookie with domain and path", 0224 "remove persistent cookie with domain and no path", 0225 "remove persistent cookie with domain and / as path", 0226 "remove persistent cookie with path and no domain", 0227 "remove persistent cookie without secure" 0228 }; 0229 0230 const QList<CookieData> input{ 0231 {m_cookieName + "-persistent-remove", "test-remove-value", ".yyy.xxx.com", "/abc/def/", "zzz.yyy.xxx.com", QDateTime::currentDateTime().addYears(1), true}, 0232 {m_cookieName + "-session-remove", "test-remove-value", ".yyy.xxx.com", "/abc/def/", "zzz.yyy.xxx.com", QDateTime(), true}, 0233 {m_cookieName + "-no-path-remove", "test-remove-value", ".yyy.xxx.com", "", "zzz.yyy.xxx.com", QDateTime::currentDateTime().addYears(1), true}, 0234 {m_cookieName + "-slash-as-path-remove", "test-remove-value", ".yyy.xxx.com", "/", "zzz.yyy.xxx.com", QDateTime::currentDateTime().addYears(1), true}, 0235 {m_cookieName + "-no-domain-remove", "test-remove-value", "", "/abc/def/", "zzz.yyy.xxx.com", QDateTime::currentDateTime().addYears(1), true}, 0236 {m_cookieName + "-no-secure-remove", "test-remove-value", ".yyy.xxx.com", "/abc/def/", "zzz.yyy.xxx.com", QDateTime::currentDateTime().addYears(1), false}, 0237 }; 0238 0239 QList<CookieData> expected(input); 0240 0241 for (int i = 0; i < input.count(); ++i) { 0242 const CookieData &ex = expected.at(i); 0243 const CookieData &in = input.at(i); 0244 QNetworkCookie c = in.cookie(); 0245 if (in.domain.isEmpty()) { 0246 c.normalize(QUrl("https://" + in.host)); 0247 } 0248 QTest::newRow(labels.at(i).toLatin1().constData()) << c << ex.name << ex.domain << ex.path << ex.host; 0249 }} 0250 0251 void TestWebEnginePartCookieJarKIO::testCookieRemovedFromStoreAreRemovedFromKCookieServer() 0252 { 0253 QFETCH(const QNetworkCookie, cookie); 0254 QFETCH(const QString, name); 0255 QFETCH(const QString, domain); 0256 QFETCH(const QString, path); 0257 QFETCH(const QString, host); 0258 0259 //Add cookie to KCookieServer 0260 QDBusError e = addCookieToKCookieServer(cookie, host); 0261 QVERIFY2(!e.isValid(), qPrintable(m_server->lastError().message())); 0262 0263 auto findCookies=[this, &domain, &host, &path, &name](){ 0264 QDBusReply<QStringList> reply = m_server->call(QDBus::Block, "findCookies", QVariant::fromValue(QList<int>{2}), domain, host, path, name); 0265 return reply; 0266 }; 0267 0268 //Ensure cookie has been added to KCookieServer 0269 QDBusReply<QStringList> reply = findCookies(); 0270 QVERIFY2(reply.isValid(), qPrintable(reply.error().message())); 0271 QVERIFY2(reply.value().contains(name), "Cookie wasn't added to server"); 0272 0273 //Emit QWebEngineCookieStore::cookieRemoved signal and check that cookie has indeed been removed 0274 emit m_store->cookieRemoved(cookie); 0275 0276 //Check that cookie is no longer in KCookieServer 0277 reply = findCookies(); 0278 QVERIFY2(reply.isValid(), qPrintable(reply.error().message())); 0279 QVERIFY2(!reply.value().contains(name), "Cookie wasn't removed from server"); 0280 } 0281 0282 QDBusError TestWebEnginePartCookieJarKIO::addCookieToKCookieServer(const QNetworkCookie& _cookie, const QString& host) 0283 { 0284 QNetworkCookie cookie(_cookie); 0285 QUrl url; 0286 url.setHost(host); 0287 url.setScheme(cookie.isSecure() ? "https" : "http"); 0288 if (!cookie.domain().startsWith('.')) { 0289 cookie.setDomain(QString()); 0290 } 0291 const QByteArray setCookie = "Set-Cookie: " + cookie.toRawForm(); 0292 m_server->call(QDBus::Block, "addCookies", url.toString(), setCookie, static_cast<qlonglong>(0)); 0293 return m_server->lastError(); 0294 } 0295 0296 void TestWebEnginePartCookieJarKIO::testPersistentCookiesAreAddedToStoreOnCreation() 0297 { 0298 delete m_jar; 0299 QDateTime exp = QDateTime::currentDateTime().addYears(1); 0300 QString baseCookieName = m_cookieName + "-startup"; 0301 QList<CookieData> data { 0302 {baseCookieName + "-persistent", "test-value", ".yyy.xxx.com", "/abc/def/", "zzz.yyy.xxx.com", currentDateTime().addYears(1), true}, 0303 {baseCookieName + "-no-path", "test-value", ".yyy.xxx.com", "", "zzz.yyy.xxx.com", currentDateTime().addYears(1), true}, 0304 {baseCookieName + "-no-domain", "test-value", "", "/abc/def/", "zzz.yyy.xxx.com", currentDateTime().addYears(1), true}, 0305 {baseCookieName + "-no-secure", "test-value", ".yyy.xxx.com", "/abc/def/", "zzz.yyy.xxx.com", currentDateTime().addYears(1), false} 0306 }; 0307 QList<QNetworkCookie> expected; 0308 for(const CookieData &d: data){ 0309 QNetworkCookie c = d.cookie(); 0310 QDBusError e = addCookieToKCookieServer(c, d.host); 0311 QVERIFY2(!e.isValid(), qPrintable(e.message())); 0312 expected << c; 0313 } 0314 m_jar = new WebEnginePartCookieJarKIO(m_profile, this); 0315 QList<QNetworkCookie> cookiesInsertedIntoJar; 0316 for(const QNetworkCookie &c: qAsConst(m_jar->m_testCookies)){ 0317 if(QString(c.name()).startsWith(baseCookieName)) { 0318 cookiesInsertedIntoJar << c; 0319 } 0320 } 0321 0322 //Ensure that cookies in the two lists are in the same order before comparing them 0323 //(the order in cookiesInsertedIntoJar depends on the order KCookieServer::findCookies 0324 //returns them) 0325 auto sortLambda = [](const QNetworkCookie &c1, const QNetworkCookie &c2){ 0326 return c1.name() < c2.name(); 0327 }; 0328 std::sort(cookiesInsertedIntoJar.begin(), cookiesInsertedIntoJar.end(), sortLambda); 0329 std::sort(expected.begin(), expected.end(), sortLambda); 0330 0331 QCOMPARE(cookiesInsertedIntoJar, expected); 0332 } 0333 0334 void TestWebEnginePartCookieJarKIO::testSessionCookiesAreNotAddedToStoreOnCreation() 0335 { 0336 delete m_jar; 0337 CookieData data{m_cookieName + "-startup-session", "test-value", ".yyy.xxx.com", "/abc/def", "zzz.yyy.xxx.com", QDateTime(), true}; 0338 QDBusError e = addCookieToKCookieServer(data.cookie(), data.host); 0339 QVERIFY2(!e.isValid(), qPrintable(e.message())); 0340 m_jar = new WebEnginePartCookieJarKIO(m_profile, this); 0341 QList<QNetworkCookie> cookiesInsertedIntoJar; 0342 for(const QNetworkCookie &c: qAsConst(m_jar->m_testCookies)) { 0343 if (c.name() == data.name) { 0344 cookiesInsertedIntoJar << c; 0345 } 0346 } 0347 QVERIFY2(cookiesInsertedIntoJar.isEmpty(), "Session cookies inserted into cookie store"); 0348 } 0349