File indexing completed on 2024-12-22 04:43:19

0001 /*
0002     This file is part of the KDE project.
0003 
0004     SPDX-FileCopyrightText: 2018 Stefano Crocco <stefano.crocco@alice.it>
0005 
0006     SPDX-License-Identifier: LGPL-2.1-or-later
0007 */
0008 
0009 #include "webenginepartcookiejar6.h"
0010 #include "settings/webenginesettings.h"
0011 #include <webenginepart_debug.h>
0012 #include "cookiealertdlg.h"
0013 #include "interfaces/browser.h"
0014 
0015 #include <QWebEngineProfile>
0016 #include <QStringList>
0017 #include <QDBusReply>
0018 #include <QDebug>
0019 #include <QWidget>
0020 #include <QDateTime>
0021 #include <QTimeZone>
0022 #include <QApplication>
0023 #include <QJsonDocument>
0024 #include <QJsonObject>
0025 #include <QFile>
0026 #include <QDataStream>
0027 #include <QDir>
0028 #include <QNetworkCookie>
0029 
0030 #include <kio_version.h>
0031 #include <KSharedConfig>
0032 #include <KConfigGroup>
0033 
0034 #include <QTimer>
0035 
0036 WebEnginePartCookieJar6::CookieIdentifier::CookieIdentifier(const QNetworkCookie& cookie):
0037     name(cookie.name()), domain(cookie.domain()), path(cookie.path())
0038 {
0039 }
0040 
0041 WebEnginePartCookieJar6::CookieIdentifier::CookieIdentifier(const QString& n, const QString& d, const QString& p):
0042     name(n), domain(d), path(p)
0043 {
0044 }
0045 
0046 WebEnginePartCookieJar6::WebEnginePartCookieJar6(QWebEngineProfile *prof, QObject *parent):
0047     CookieJar(parent), m_cookieStore(prof->cookieStore())
0048 {
0049     auto filter = [this](const QWebEngineCookieStore::FilterRequest &req){return filterCookie(req);};
0050     m_cookieStore->setCookieFilter(filter);
0051 
0052     connect(m_cookieStore, &QWebEngineCookieStore::cookieAdded, this, &WebEnginePartCookieJar6::handleCookieAdditionToStore);
0053     connect(m_cookieStore, &QWebEngineCookieStore::cookieRemoved, this, &WebEnginePartCookieJar6::removeCookieFromSet);
0054     connect(qApp, &QApplication::lastWindowClosed, this, &WebEnginePartCookieJar6::saveCookieAdvice);
0055     KonqInterfaces::Browser *br = KonqInterfaces::Browser::browser(qApp);
0056     if (br) {
0057         connect(br, &KonqInterfaces::Browser::configurationChanged, this, &WebEnginePartCookieJar6::applyConfiguration);
0058     }
0059 
0060     //WARNING: call this *before* applyConfiguration(), otherwise, if the default policy is Ask, the user will be asked again for all cookies
0061     //already in the jar
0062     readCookieAdvice();
0063     loadCookies();
0064     m_cookieStore->loadAllCookies();
0065     applyConfiguration();
0066 }
0067 
0068 WebEnginePartCookieJar6::~WebEnginePartCookieJar6()
0069 {
0070     QFile f(cookieDataPath());
0071     if (!f.open(QFile::WriteOnly)) {
0072         return;
0073     }
0074 #if QT_VERSION_MAJOR > 5
0075     m_cookies.removeIf([](const QNetworkCookie &c){return !c.expirationDate().isValid();});
0076 #else
0077     const QSet<QNetworkCookie> orig (m_cookies);
0078     for (const QNetworkCookie &c : orig) {
0079         if (!c.expirationDate().isValid()) {
0080             m_cookies.remove(c);
0081         }
0082     }
0083 #endif
0084     QDataStream ds(&f);
0085     ds << m_cookies;
0086     f.close();
0087 }
0088 
0089 QSet<QNetworkCookie> WebEnginePartCookieJar6::cookies() const
0090 {
0091     return m_cookies;
0092 }
0093 
0094 void WebEnginePartCookieJar6::writeConfig()
0095 {
0096     KSharedConfig::Ptr cfg = KSharedConfig::openConfig();
0097     KConfigGroup grp = cfg->group("Cookie Policy");
0098     writeAdviceConfigEntry(grp, "CookieGlobalAdvice", m_policy.defaultPolicy);
0099     QJsonObject obj;
0100     for (auto it = m_policy.domainExceptions.constBegin(); it != m_policy.domainExceptions.constEnd(); ++it) {
0101         obj.insert(it.key(), adviceToInt(it.value()));
0102     }
0103     grp.writeEntry("CookieDomainAdvice", QJsonDocument(obj).toJson());
0104     cfg->sync();
0105 }
0106 
0107 void WebEnginePartCookieJar6::applyConfiguration()
0108 {
0109     KConfigGroup grp = KSharedConfig::openConfig()->group("Cookie Policy");
0110     m_policy.cookiesEnabled = grp.readEntry("Cookies", true);
0111     m_policy.rejectThirdPartyCookies = grp.readEntry("RejectCrossDomainCookies", true);
0112     m_policy.acceptSessionCookies = grp.readEntry("AcceptSessionCookies", true);
0113     m_policy.defaultPolicy = readAdviceConfigEntry(grp, "CookieGlobalAdvice", CookieAdvice::Accept);
0114     QJsonObject obj = QJsonDocument::fromJson(grp.readEntry("CookieDomainAdvice", QByteArray())).object();
0115     for (auto it = obj.constBegin(); it != obj.constEnd(); ++it) {
0116         m_policy.domainExceptions.insert(it.key(), intToAdvice(it.value().toInt(0), CookieAdvice::Unknown));
0117     }
0118     if (!m_policy.cookiesEnabled) {
0119         m_cookieStore->setCookieFilter([](const QWebEngineCookieStore::FilterRequest &){return false;});
0120         m_cookieStore->deleteAllCookies();
0121     }
0122 }
0123 
0124 void WebEnginePartCookieJar6::removeAllCookies()
0125 {
0126     m_cookieStore->deleteAllCookies();
0127     m_cookies.clear();
0128     QFile::remove(cookieAdvicePath());
0129 }
0130 
0131 void WebEnginePartCookieJar6::removeCookiesWithDomain(const QString& domain)
0132 {
0133     QStringList possibleDomains{domain};
0134     if (domain.startsWith('.')) {
0135         possibleDomains.append(domain.mid(1));
0136     } else {
0137         possibleDomains.append(QString('.') + domain);
0138     }
0139 
0140     bool exceptionsRemoved = false;
0141     QSet<QNetworkCookie> cookiesOrig(m_cookies);
0142     for (auto c : cookiesOrig) {
0143         if (possibleDomains.contains(c.domain())) {
0144             m_cookieStore->deleteCookie(c);
0145             if (m_policy.cookieExceptions.remove(CookieIdentifier{c}) ) {
0146                 exceptionsRemoved = true;
0147             }
0148         }
0149     }
0150     if (exceptionsRemoved) {
0151         saveCookieAdvice();
0152     }
0153 }
0154 
0155 void WebEnginePartCookieJar6::removeCookies(const QVector<QNetworkCookie>& cookies)
0156 {
0157     bool exceptionsRemoved = false;
0158     for (const QNetworkCookie &c : cookies) {
0159         m_cookieStore->deleteCookie(c);
0160         if (m_policy.cookieExceptions.remove(CookieIdentifier{c}) > 0) {
0161             exceptionsRemoved = true;
0162         }
0163     }
0164     if (exceptionsRemoved) {
0165         saveCookieAdvice();
0166     }
0167 }
0168 
0169 void WebEnginePartCookieJar6::removeCookie(const QNetworkCookie& cookie, const QUrl &origin)
0170 {
0171     m_cookieStore->deleteCookie(cookie, origin);
0172     if (m_policy.cookieExceptions.remove(CookieIdentifier{cookie}) > 0) {
0173         saveCookieAdvice();
0174     }
0175 }
0176 
0177 bool WebEnginePartCookieJar6::filterCookie(const QWebEngineCookieStore::FilterRequest& req)
0178 {
0179     if (!m_policy.cookiesEnabled) {
0180         return false;
0181     }
0182     if (req.thirdParty && m_policy.rejectThirdPartyCookies) {
0183         return false;
0184     }
0185     return true;
0186 }
0187 
0188 void WebEnginePartCookieJar6::removeSessionCookies()
0189 {
0190     for (const QNetworkCookie &c : m_cookies) {
0191         if (!c.expirationDate().isValid()) {
0192             m_cookieStore->deleteCookie(c);
0193         }
0194     }
0195 }
0196 
0197 WebEnginePartCookieJar6::CookieAdvice WebEnginePartCookieJar6::decideCookieAction(const QNetworkCookie cookie)
0198 {
0199     CookieAdvice option = CookieAdvice::Unknown;
0200     auto cookiesExIt = m_policy.cookieExceptions.constFind(CookieIdentifier{cookie.name(), cookie.domain(), cookie.path()});
0201     if (cookiesExIt != m_policy.cookieExceptions.constEnd()) {
0202         option = cookiesExIt.value();
0203     }
0204 
0205     if (option == CookieAdvice::Unknown && m_policy.acceptSessionCookies && !cookie.expirationDate().isValid()) {
0206         return CookieAdvice::Accept;
0207     }
0208 
0209     auto domainExIt = m_policy.domainExceptions.constFind(cookie.domain());
0210     if (domainExIt != m_policy.domainExceptions.constEnd()) {
0211         option = domainExIt.value();
0212     }
0213 
0214     if (option == CookieAdvice::Unknown) {
0215         option = m_policy.defaultPolicy != CookieAdvice::Unknown ? m_policy.defaultPolicy : CookieAdvice::Accept;
0216     }
0217 
0218     if (option == CookieAdvice::Ask) {
0219         option = askCookieQuestion(cookie);
0220     }
0221 
0222     return option;
0223 }
0224 
0225 WebEnginePartCookieJar6::CookieAdvice WebEnginePartCookieJar6::askCookieQuestion(const QNetworkCookie cookie)
0226 {
0227     CookieAlertDlg dlg(cookie, qApp->activeWindow());
0228     dlg.exec();
0229     CookieAdvice option = dlg.choice();
0230     switch (dlg.applyTo()) {
0231         case CookieAlertDlg::This:
0232             m_policy.cookieExceptions.insert(CookieIdentifier(cookie), option);
0233             break;
0234         case CookieAlertDlg::Domain:
0235             m_policy.domainExceptions.insert(cookie.domain(), option);
0236             break;
0237         case CookieAlertDlg::Cookies:
0238             m_policy.defaultPolicy = option;
0239             break;
0240     }
0241     writeConfig();
0242     return option;
0243 }
0244 
0245 void WebEnginePartCookieJar6::handleCookieAdditionToStore(const QNetworkCookie& cookie)
0246 {   
0247     CookieAdvice action = decideCookieAction(cookie);
0248     if (action == CookieAdvice::Reject)  {
0249         m_cookieStore->deleteCookie(cookie);
0250         return;
0251     } else if (action == CookieAdvice::AcceptForSession && cookie.expirationDate().isValid()) {
0252         QNetworkCookie sessionCookie(cookie);
0253         sessionCookie.setExpirationDate(QDateTime());
0254         m_cookieStore->deleteCookie(cookie);
0255         m_cookieStore->setCookie(sessionCookie);
0256         return;
0257     }
0258     m_cookies.insert(cookie);
0259 }
0260 
0261 QString WebEnginePartCookieJar6::cookieAdvicePath()
0262 {
0263     QString s_cookieAdvicePath;
0264     if (s_cookieAdvicePath.isEmpty()) {
0265         QDir dir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation));
0266         dir.mkpath(QStringLiteral("."));
0267         s_cookieAdvicePath = dir.filePath(QStringLiteral("cookieadvice"));
0268     }
0269     return s_cookieAdvicePath;
0270 }
0271 
0272 QString WebEnginePartCookieJar6::cookieDataPath()
0273 {
0274     QString s_cookieDataPath;
0275     if (s_cookieDataPath.isEmpty()) {
0276         QDir dir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation));
0277         dir.mkpath(QStringLiteral("."));
0278         s_cookieDataPath = dir.filePath(QStringLiteral("cookies"));
0279     }
0280     return s_cookieDataPath;
0281 }
0282 void WebEnginePartCookieJar6::saveCookieAdvice()
0283 {
0284     QFile f(cookieAdvicePath());
0285     if (!f.open(QFile::WriteOnly)) {
0286         return;
0287     }
0288     QDataStream ds(&f);
0289     ds << m_policy.cookieExceptions;
0290 }
0291 
0292 void WebEnginePartCookieJar6::readCookieAdvice()
0293 {
0294     QFile f(cookieAdvicePath());
0295     if (!f.open(QFile::ReadOnly)) {
0296         return;
0297     }
0298     QDataStream ds(&f);
0299     ds >> m_policy.cookieExceptions;
0300 }
0301 
0302 void WebEnginePartCookieJar6::removeCookieFromSet(const QNetworkCookie& cookie)
0303 {
0304     m_cookies.remove(cookie);
0305 }
0306 
0307 void WebEnginePartCookieJar6::loadCookies()
0308 {
0309     QFile f(cookieDataPath());
0310     if (!f.open(QFile::ReadOnly)) {
0311         return;
0312     }
0313     QDataStream ds(&f);
0314     ds >> m_cookies;
0315     f.close();
0316 }
0317 
0318 QDebug operator<<(QDebug deb, const WebEnginePartCookieJar6::CookieIdentifier& id)
0319 {
0320     QDebugStateSaver saver(deb);
0321     deb << "(" << id.name << "," << id.domain << "," << id.path << ")";
0322     return deb;
0323 }
0324 
0325 qHashReturnType qHash(const QNetworkCookie& cookie, uint seed)
0326 {
0327     return qHash(QStringList{cookie.name(), cookie.domain(), cookie.path()}, seed);
0328 }
0329 
0330 QDataStream& operator>>(QDataStream& ds, WebEnginePartCookieJar6::CookieIdentifier& id)
0331 {
0332     ds >> id.name >> id.domain >> id.path;
0333     return ds;
0334 }
0335 
0336 QDataStream& operator<<(QDataStream& ds, const WebEnginePartCookieJar6::CookieIdentifier& id)
0337 {
0338     ds << id.name << id.domain << id.path;
0339     return ds;
0340 }
0341 
0342 QDataStream& operator>>(QDataStream& ds, QNetworkCookie& cookie)
0343 {
0344     QByteArray name, value;
0345     QString domain, path;
0346     QDateTime expirationDate;
0347     bool secure;
0348     ds >> name >> value >> domain >> path >> expirationDate >> secure;
0349     cookie.setName(name);
0350     cookie.setValue(value);
0351     cookie.setDomain(domain);
0352     cookie.setPath(path);
0353     cookie.setExpirationDate(expirationDate);
0354     cookie.setSecure(secure);
0355     return ds;
0356 }
0357 
0358 QDataStream& operator<<(QDataStream& ds, const QNetworkCookie& cookie)
0359 {
0360     ds << cookie.name() << cookie.value() << cookie.domain() << cookie.path() << cookie.expirationDate() << cookie.isSecure();
0361     return ds;
0362 }
0363