File indexing completed on 2024-05-19 05:00:54

0001 /**
0002     kcookiespolicies.cpp - Cookies configuration
0003 
0004     Original Authors
0005     SPDX-FileCopyrightText: Waldo Bastian <bastian@kde.org>
0006     SPDX-FileCopyrightText: 1999 David Faure <faure@kde.org>
0007     SPDX-FileCopyrightText: 2008 Urs Wolfer <uwolfer @ kde.org>
0008 
0009     Re-written by:
0010     SPDX-FileCopyrightText: 2000 Dawit Alemayehu <adawit@kde.org>
0011 
0012     SPDX-License-Identifier: GPL-2.0-or-later
0013 */
0014 
0015 // Own
0016 #include "kcookiespolicies.h"
0017 
0018 // Qt
0019 #include <QCheckBox>
0020 #include <QDBusInterface>
0021 #include <QDBusReply>
0022 #include <QPushButton>
0023 #include <QJsonDocument>
0024 #include <QJsonObject>
0025 
0026 // KDE
0027 #include <KConfig>
0028 #include <KConfigGroup>
0029 #include <KLocalizedString>
0030 #include <KMessageBox>
0031 #include <QUrl>
0032 #include <KSharedConfig>
0033 
0034 using namespace KonqInterfaces;
0035 
0036 
0037 // QUrl::fromAce/toAce don't accept a domain that starts with a '.', like we do here.
0038 // So we use these wrappers.
0039 QString tolerantFromAce(const QByteArray &_domain)
0040 {
0041     QByteArray domain(_domain);
0042     const bool hasDot = domain.startsWith('.');
0043     if (hasDot) {
0044         domain.remove(0, 1);
0045     }
0046     QString ret = QUrl::fromAce(domain);
0047     if (hasDot) {
0048         ret.prepend(QLatin1Char('.'));
0049     }
0050     return ret;
0051 }
0052 
0053 static QByteArray tolerantToAce(const QString &_domain)
0054 {
0055     QString domain(_domain);
0056     const bool hasDot = domain.startsWith(QLatin1Char('.'));
0057     if (hasDot) {
0058         domain.remove(0, 1);
0059     }
0060     QByteArray ret = QUrl::toAce(domain);
0061     if (hasDot) {
0062         ret.prepend('.');
0063     }
0064     return ret;
0065 }
0066 
0067 KCookiesPolicies::KCookiesPolicies(QObject *parent, const KPluginMetaData &md, const QVariantList &)
0068     : KCModule(parent, md)
0069     , mSelectedItemsCount(0)
0070 {
0071     mUi.setupUi(widget());
0072     mUi.kListViewSearchLine->setTreeWidget(mUi.policyTreeWidget);
0073     QList<int> columns;
0074     columns.append(0);
0075     mUi.kListViewSearchLine->setSearchColumns(columns);
0076 
0077     mUi.pbNew->setIcon(QIcon::fromTheme(QStringLiteral("list-add")));
0078     mUi.pbChange->setIcon(QIcon::fromTheme(QStringLiteral("edit-rename")));
0079     mUi.pbDelete->setIcon(QIcon::fromTheme(QStringLiteral("list-remove")));
0080     mUi.pbDeleteAll->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete")));
0081 
0082     // Connect the main switch :) Enable/disable cookie support
0083     connect(mUi.cbEnableCookies, &QAbstractButton::toggled, this, &KCookiesPolicies::cookiesEnabled);
0084     connect(mUi.cbEnableCookies, &QAbstractButton::toggled, this, &KCookiesPolicies::configChanged);
0085 
0086     // Connect the preference check boxes...
0087     connect(mUi.cbRejectCrossDomainCookies, &QAbstractButton::toggled, this, &KCookiesPolicies::configChanged);
0088     connect(mUi.cbAutoAcceptSessionCookies, &QAbstractButton::toggled, this, &KCookiesPolicies::configChanged);
0089 
0090     connect(mUi.rbPolicyAsk, &QAbstractButton::toggled, this, &KCookiesPolicies::configChanged);
0091     connect(mUi.rbPolicyAccept, &QAbstractButton::toggled, this, &KCookiesPolicies::configChanged);
0092     connect(mUi.rbPolicyAcceptForSession, &QAbstractButton::toggled, this, &KCookiesPolicies::configChanged);
0093     connect(mUi.rbPolicyReject, &QAbstractButton::toggled, this, &KCookiesPolicies::configChanged);
0094     // Connect signals from the domain specific policy listview.
0095     connect(mUi.policyTreeWidget, &QTreeWidget::itemSelectionChanged, this, &KCookiesPolicies::selectionChanged);
0096     connect(mUi.policyTreeWidget, &QTreeWidget::itemDoubleClicked, this, QOverload<>::of(&KCookiesPolicies::changePressed));
0097 
0098     // Connect the buttons...
0099     connect(mUi.pbNew, &QAbstractButton::clicked, this, QOverload<>::of(&KCookiesPolicies::addPressed));
0100     connect(mUi.pbChange, &QAbstractButton::clicked, this, QOverload<>::of(&KCookiesPolicies::changePressed));
0101     connect(mUi.pbDelete, &QAbstractButton::clicked, this, &KCookiesPolicies::deletePressed);
0102     connect(mUi.pbDeleteAll, &QAbstractButton::clicked, this, &KCookiesPolicies::deleteAllPressed);
0103 }
0104 
0105 KCookiesPolicies::~KCookiesPolicies()
0106 {
0107 }
0108 
0109 void KCookiesPolicies::configChanged()
0110 {
0111     // kDebug() << "KCookiesPolicies::configChanged...";
0112     setNeedsSave(true);
0113 }
0114 
0115 void KCookiesPolicies::cookiesEnabled(bool enable)
0116 {
0117     mUi.bgDefault->setEnabled(enable);
0118     mUi.bgPreferences->setEnabled(enable);
0119     mUi.gbDomainSpecific->setEnabled(enable);
0120 }
0121 
0122 void KCookiesPolicies::setPolicy(const QString &domain)
0123 {
0124     QTreeWidgetItemIterator it(mUi.policyTreeWidget);
0125     bool hasExistingPolicy = false;
0126     while (*it) {
0127         if ((*it)->text(0) == domain) {
0128             hasExistingPolicy = true;
0129             break;
0130         }
0131         ++it;
0132     }
0133 
0134     if (hasExistingPolicy) {
0135         changePressed((*it), false);
0136     } else {
0137         addPressed(domain);
0138     }
0139 }
0140 
0141 void KCookiesPolicies::changePressed()
0142 {
0143     changePressed(mUi.policyTreeWidget->currentItem());
0144 }
0145 
0146 void KCookiesPolicies::addPressed()
0147 {
0148     addPressed(QString());
0149 }
0150 
0151 void KCookiesPolicies::changePressed(QTreeWidgetItem *item, bool state)
0152 {
0153     Q_ASSERT(item);
0154     const QString oldDomain(item->text(0));
0155 
0156     KCookiesPolicySelectionDlg pdlg(widget());
0157     pdlg.setWindowTitle(i18nc("@title:window", "Change Cookie Policy"));
0158     pdlg.setPolicy(mDomainPolicyMap.value(oldDomain));
0159     pdlg.setEnableHostEdit(state, oldDomain);
0160 
0161     if (pdlg.exec() && !pdlg.domain().isEmpty()) {
0162         const QString newDomain = tolerantFromAce(pdlg.domain().toLatin1());
0163         CookieJar::CookieAdvice advice = pdlg.advice();
0164         if (newDomain == oldDomain || !handleDuplicate(newDomain, advice)) {
0165             mDomainPolicyMap[newDomain] = advice;
0166             item->setText(0, newDomain);
0167             item->setText(1, i18n(KCookieAdvice::adviceToStr(mDomainPolicyMap.value(newDomain))));
0168             configChanged();
0169         }
0170     }
0171 }
0172 
0173 void KCookiesPolicies::addPressed(const QString &domain, bool state)
0174 {
0175     KCookiesPolicySelectionDlg pdlg(widget());
0176     pdlg.setWindowTitle(i18nc("@title:window", "New Cookie Policy"));
0177     pdlg.setEnableHostEdit(state, domain);
0178 
0179     if (mUi.rbPolicyAccept->isChecked()) {
0180         pdlg.setPolicy(CookieJar::CookieAdvice::Reject);
0181     } else {
0182         pdlg.setPolicy(CookieJar::CookieAdvice::Accept);
0183     }
0184 
0185     if (pdlg.exec() && !pdlg.domain().isEmpty()) {
0186         const QString domain = tolerantFromAce(pdlg.domain().toLatin1());
0187         CookieJar::CookieAdvice advice = pdlg.advice();
0188 
0189         if (!handleDuplicate(domain, advice)) {
0190             const char *strAdvice = KCookieAdvice::adviceToStr(advice);
0191             const QStringList items{
0192                 domain,
0193                 i18n(strAdvice),
0194             };
0195             QTreeWidgetItem *item = new QTreeWidgetItem(mUi.policyTreeWidget, items);
0196             mDomainPolicyMap.insert(item->text(0), advice);
0197             configChanged();
0198             updateButtons();
0199         }
0200     }
0201 }
0202 
0203 bool KCookiesPolicies::handleDuplicate(const QString &domain, CookieJar::CookieAdvice advice)
0204 {
0205     QTreeWidgetItem *item = mUi.policyTreeWidget->topLevelItem(0);
0206     while (item != nullptr) {
0207         if (item->text(0) == domain) {
0208             const int res = KMessageBox::warningContinueCancel(widget(),
0209                                                                i18n("<qt>A policy already exists for"
0210                                                                     "<center><b>%1</b></center>"
0211                                                                     "Do you want to replace it?</qt>",
0212                                                                     domain),
0213                                                                i18nc("@title:window", "Duplicate Policy"),
0214                                                                KGuiItem(i18n("Replace")));
0215             if (res == KMessageBox::Continue) {
0216                 mDomainPolicyMap[domain] = advice;
0217                 item->setText(0, domain);
0218                 item->setText(1, i18n(KCookieAdvice::adviceToStr(mDomainPolicyMap.value(domain))));
0219                 configChanged();
0220                 return true;
0221             } else {
0222                 return true; // User Cancelled!!
0223             }
0224         }
0225         item = mUi.policyTreeWidget->itemBelow(item);
0226     }
0227     return false;
0228 }
0229 
0230 void KCookiesPolicies::deletePressed()
0231 {
0232     QTreeWidgetItem *nextItem = nullptr;
0233 
0234     const QList<QTreeWidgetItem *> selectedItems = mUi.policyTreeWidget->selectedItems();
0235     for (const QTreeWidgetItem *item : selectedItems) {
0236         nextItem = mUi.policyTreeWidget->itemBelow(item);
0237         if (!nextItem) {
0238             nextItem = mUi.policyTreeWidget->itemAbove(item);
0239         }
0240 
0241         mDomainPolicyMap.remove(item->text(0));
0242         delete item;
0243     }
0244 
0245     if (nextItem) {
0246         nextItem->setSelected(true);
0247     }
0248 
0249     updateButtons();
0250     configChanged();
0251 }
0252 
0253 void KCookiesPolicies::deleteAllPressed()
0254 {
0255     mDomainPolicyMap.clear();
0256     mUi.policyTreeWidget->clear();
0257     updateButtons();
0258     configChanged();
0259 }
0260 
0261 void KCookiesPolicies::updateButtons()
0262 {
0263     bool hasItems = mUi.policyTreeWidget->topLevelItemCount() > 0;
0264 
0265     mUi.pbChange->setEnabled((hasItems && mSelectedItemsCount == 1));
0266     mUi.pbDelete->setEnabled((hasItems && mSelectedItemsCount > 0));
0267     mUi.pbDeleteAll->setEnabled(hasItems);
0268 }
0269 
0270 void KCookiesPolicies::updateDomainList(const QStringList &domainConfig)
0271 {
0272     mUi.policyTreeWidget->clear();
0273 
0274     QStringList::ConstIterator it = domainConfig.begin();
0275     for (; it != domainConfig.end(); ++it) {
0276         QString domain;
0277         CookieJar::CookieAdvice advice = CookieJar::CookieAdvice::Unknown;
0278         splitDomainAdvice(*it, domain, advice);
0279         if (!domain.isEmpty()) {
0280             const QStringList items{
0281                 tolerantFromAce(domain.toLatin1()),
0282                 i18n(KCookieAdvice::adviceToStr(advice)),
0283             };
0284             QTreeWidgetItem *item = new QTreeWidgetItem(mUi.policyTreeWidget, items);
0285             mDomainPolicyMap[item->text(0)] = advice;
0286         }
0287     }
0288 
0289     mUi.policyTreeWidget->sortItems(0, Qt::AscendingOrder);
0290 }
0291 
0292 void KCookiesPolicies::selectionChanged()
0293 {
0294     mSelectedItemsCount = mUi.policyTreeWidget->selectedItems().count();
0295     updateButtons();
0296 }
0297 
0298 void KCookiesPolicies::load()
0299 {
0300     mSelectedItemsCount = 0;
0301 
0302     KSharedConfig::Ptr cfg = KSharedConfig::openConfig();
0303     KConfigGroup group = cfg->group("Cookie Policy");
0304 
0305     bool enableCookies = group.readEntry("Cookies", true);
0306     mUi.cbEnableCookies->setChecked(enableCookies);
0307     cookiesEnabled(enableCookies);
0308 
0309     CookieJar::CookieAdvice advice = CookieJar::readAdviceConfigEntry(group, "CookieGlobalAdvice");
0310     switch (advice) {
0311     case CookieJar::CookieAdvice::Accept:
0312         mUi.rbPolicyAccept->setChecked(true);
0313         break;
0314     case CookieJar::CookieAdvice::AcceptForSession:
0315         mUi.rbPolicyAcceptForSession->setChecked(true);
0316         break;
0317     case CookieJar::CookieAdvice::Reject:
0318         mUi.rbPolicyReject->setChecked(true);
0319         break;
0320     case CookieJar::CookieAdvice::Ask:
0321     case CookieJar::CookieAdvice::Unknown:
0322     default:
0323         mUi.rbPolicyAsk->setChecked(true);
0324     }
0325 
0326     bool enable = group.readEntry("RejectCrossDomainCookies", true);
0327     mUi.cbRejectCrossDomainCookies->setChecked(enable);
0328 
0329     bool sessionCookies = group.readEntry("AcceptSessionCookies", true);
0330     mUi.cbAutoAcceptSessionCookies->setChecked(sessionCookies);
0331     updateDomainList(group.readEntry("CookieDomainAdvice", QStringList()));
0332 
0333     if (enableCookies) {
0334         updateButtons();
0335     }
0336 }
0337 
0338 void KCookiesPolicies::save()
0339 {
0340     KSharedConfig::Ptr cfg = KSharedConfig::openConfig();
0341     KConfigGroup group = cfg->group("Cookie Policy");
0342 
0343     bool state = mUi.cbEnableCookies->isChecked();
0344     group.writeEntry("Cookies", state);
0345     state = mUi.cbRejectCrossDomainCookies->isChecked();
0346     group.writeEntry("RejectCrossDomainCookies", state);
0347     state = mUi.cbAutoAcceptSessionCookies->isChecked();
0348     group.writeEntry("AcceptSessionCookies", state);
0349 
0350     CookieJar::CookieAdvice advice;
0351     if (mUi.rbPolicyAccept->isChecked()) {
0352         advice = CookieJar::CookieAdvice::Accept;
0353     } else if (mUi.rbPolicyAcceptForSession->isChecked()) {
0354         advice = CookieJar::CookieAdvice::AcceptForSession;
0355     } else if (mUi.rbPolicyReject->isChecked()) {
0356         advice = CookieJar::CookieAdvice::Reject;
0357     } else {
0358         advice = CookieJar::CookieAdvice::Ask;
0359     }
0360 
0361     CookieJar::writeAdviceConfigEntry(group, "CookieGlobalAdvice", advice);
0362 
0363     QStringList domainConfig;
0364     QJsonObject obj;
0365     for (auto it = mDomainPolicyMap.constBegin(); it != mDomainPolicyMap.constEnd(); ++it) {
0366         obj.insert(it.key(), CookieJar::adviceToInt(it.value()));
0367     }
0368     group.writeEntry("CookieDomainAdvice", QJsonDocument(obj).toJson());
0369 
0370     group.sync();
0371 
0372     QDBusMessage message =
0373         QDBusMessage::createSignal(QStringLiteral("/KonqMain"), QStringLiteral("org.kde.Konqueror.Main"), QStringLiteral("reparseConfiguration"));
0374     QDBusConnection::sessionBus().send(message);
0375     setNeedsSave(false);
0376 }
0377 
0378 void KCookiesPolicies::defaults()
0379 {
0380     mUi.cbEnableCookies->setChecked(true);
0381     mUi.rbPolicyAsk->setChecked(true);
0382     mUi.rbPolicyAccept->setChecked(false);
0383     mUi.rbPolicyAcceptForSession->setChecked(false);
0384     mUi.rbPolicyReject->setChecked(false);
0385     mUi.cbRejectCrossDomainCookies->setChecked(true);
0386     mUi.cbAutoAcceptSessionCookies->setChecked(false);
0387     mUi.policyTreeWidget->clear();
0388     mDomainPolicyMap.clear();
0389 
0390     cookiesEnabled(mUi.cbEnableCookies->isChecked());
0391     updateButtons();
0392 }
0393 
0394 void KCookiesPolicies::splitDomainAdvice(const QString &cfg, QString &domain, CookieJar::CookieAdvice &advice)
0395 {
0396     int sepPos = cfg.lastIndexOf(QLatin1Char(':'));
0397 
0398     // Ignore any policy that does not contain a domain...
0399     if (sepPos <= 0) {
0400         return;
0401     }
0402 
0403     domain = cfg.left(sepPos);
0404     advice = KCookieAdvice::strToAdvice(cfg.mid(sepPos + 1));
0405 }