File indexing completed on 2024-05-19 05:01:23

0001 /*
0002     This file is part of the KDE project.
0003 
0004     SPDX-FileCopyrightText: 2009 Dawit Alemayehu <adawit@kde.org>
0005     SPDX-FileCopyrightText: 2018 Stefano Crocco <stefano.crocco@alice.it>
0006 
0007     SPDX-License-Identifier: LGPL-2.0-or-later
0008 */
0009 
0010 #include "webenginewallet.h"
0011 #include "webenginepage.h"
0012 #include "utils.h"
0013 #include "webenginecustomizecacheablefieldsdlg.h"
0014 #include "webenginepart.h"
0015 
0016 #include <webenginepart_debug.h>
0017 
0018 #include <KWallet>
0019 #include <KLocalizedString>
0020 
0021 #include <QFile>
0022 #include <QPointer>
0023 #include <qwindowdefs.h>
0024 #include <QWebEngineScript>
0025 
0026 #include <algorithm>
0027 
0028 #include "webenginewalletprivate.cpp"
0029 
0030 #define QL1S(x)   QLatin1String(x)
0031 #define QL1C(x)   QLatin1Char(x)
0032 
0033 WebEngineSettings::WebFormInfo WebEngineWallet::WebForm::toSettingsInfo() const
0034 {
0035     QStringList fieldNames;
0036     fieldNames.reserve(fields.size());
0037     std::transform(fields.constBegin(), fields.constEnd(), std::back_inserter(fieldNames), [](const WebForm::WebField &f){return f.name;});
0038     return WebEngineSettings::WebFormInfo{name, framePath, fieldNames};
0039 }
0040 
0041 QString WebEngineWallet::customFormsKey(const QUrl& url)
0042 {
0043     return url.toString(QUrl::RemoveQuery | QUrl::RemoveFragment);
0044 }
0045 
0046 bool WebEngineWallet::hasCustomizedCacheableForms(const QUrl& url){
0047     return WebEngineSettings::self()->hasPageCustomizedCacheableFields(customFormsKey(url));
0048 }
0049 
0050 void WebEngineWallet::WebForm::deleteNotAutoFillableFields()
0051 {
0052     auto p = std::remove_if(fields.begin(), fields.end(), [](const WebField &f){return !f.isAutoFillable();});
0053     fields.erase(p, fields.end());
0054 }
0055 
0056 WebEngineWallet::WebForm WebEngineWallet::WebForm::withAutoFillableFieldsOnly() const
0057 {
0058     WebForm form{url, name, index, framePath, {}};
0059     std::copy_if(fields.constBegin(), fields.constEnd(), std::back_inserter(form.fields), [](const WebField &f){return f.isAutoFillable();});
0060     return form;
0061 }
0062 
0063 bool WebEngineWallet::WebForm::hasPasswords() const
0064 {
0065     return std::any_of(fields.constBegin(), fields.constEnd(), [](const WebField &f){return f.type == WebFieldType::Password;});
0066 }
0067 
0068 bool WebEngineWallet::WebForm::hasAutoFillableFields() const
0069 {
0070     return std::any_of(fields.constBegin(), fields.constEnd(), [](const WebField &f){return !f.disabled && !f.readOnly && f.autocompleteAllowed;});
0071 }
0072 
0073 bool WebEngineWallet::WebForm::hasFieldsWithWrittenValues() const
0074 {
0075     return std::any_of(fields.constBegin(), fields.constEnd(), [](const WebField &f){return !f.readOnly && !f.value.isEmpty();});
0076 }
0077 
0078 WebEngineWallet::WebForm::WebFieldType WebEngineWallet::WebForm::fieldTypeFromTypeName(const QString& name)
0079 {
0080         static QMap<QString, WebFieldType> s_typeNameMap{
0081             std::make_pair("text", WebForm::WebFieldType::Text),
0082             std::make_pair("password", WebForm::WebFieldType::Password),
0083             std::make_pair("email", WebForm::WebFieldType::Email)
0084         };
0085         return s_typeNameMap.value(name, WebFieldType::Other);
0086 }
0087 
0088 QString WebEngineWallet::WebForm::fieldNameFromType(WebEngineWallet::WebForm::WebFieldType type, bool localized)
0089 {
0090     switch(type) {
0091         case WebFieldType::Text: return localized ? i18nc("Web field with type 'text'", "text") : "text";
0092         case WebFieldType::Password: return localized ? i18nc("Web field with type 'password'", "password") : "password";
0093         case WebFieldType::Email: return localized ? i18nc("Web field with type 'e-mail'", "e-mail") :"e-mail";
0094         case WebFieldType::Other: return localized ? i18nc("Web field with type different from 'text', 'password' or 'e-mail'", "other") :"other";
0095     }
0096     //Needed to make the compiler happy. You can't actually get here
0097     return QString();
0098 }
0099 
0100 WebEngineWallet::WebEngineWallet(WebEnginePart *parent, WId wid)
0101     : QObject(parent), d(new WebEngineWalletPrivate(this))
0102 {
0103     d->wid = wid;
0104 }
0105 
0106 WebEngineWallet::~WebEngineWallet()
0107 {
0108     delete d;
0109 }
0110 
0111 bool WebEngineWallet::isOpen() const
0112 {
0113     return d->wallet && d->wallet->isOpen();
0114 }
0115 
0116 void WebEngineWallet::detectAndFillPageForms(WebEnginePage *page)
0117 {
0118     QUrl url = page->url();
0119 
0120     //There are no forms in konq: URLs, so don't waste time looking for them
0121     if (Utils::isKonqUrl(url)) {
0122         return;
0123     }
0124 
0125     auto callback = [this, url, page](const WebFormList &forms) {
0126         emit formDetectionDone(url, !forms.isEmpty(), d->hasAutoFillableFields(forms));
0127         if (!WebEngineSettings::self()->isNonPasswordStorableSite(url.host())) {
0128             fillFormData(page, cacheableForms(url, forms, CacheOperation::Fill));
0129         }
0130     };
0131     WebEngineWalletPrivate::detectFormsInPage(page, callback);
0132 }
0133 
0134 void WebEngineWallet::fillFormData(WebEnginePage *page, const WebFormList &allForms)
0135 {
0136     if (!page) return;
0137     QList<QUrl> urlList;
0138     if (!allForms.isEmpty()) {
0139         const QUrl url(page->url());
0140         if (d->pendingFillRequests.contains(url)) {
0141             qCWarning(WEBENGINEPART_LOG) << "Duplicate request rejected!";
0142         } else {
0143             WebEngineWalletPrivate::FormsData data;
0144             data.page = QPointer<WebEnginePage>(page);
0145             data.forms << allForms;
0146             d->pendingFillRequests.insert(url, data);
0147             urlList << url;
0148         }
0149     } else {
0150         emit fillFormRequestCompleted(false);
0151     }
0152     if (!urlList.isEmpty()) {
0153         fillFormDataFromCache(urlList);
0154     }
0155 }
0156 
0157 WebEngineWallet::WebFormList WebEngineWallet::cacheableForms(const QUrl& url, const WebEngineWallet::WebFormList& allForms, WebEngineWallet::CacheOperation op) const
0158 {
0159     WebEngineSettings::WebFormInfoList customForms = WebEngineSettings::self()->customizedCacheableFieldsForPage(customFormsKey(url));
0160     if (customForms.isEmpty()) {
0161         return op == CacheOperation::Fill ? d->formsToFill(allForms) : d->formsToSave(allForms);
0162     }
0163     WebFormList forms;
0164     for (const WebForm &form : allForms) {
0165         auto sameForm = [form](const WebEngineSettings::WebFormInfo &info){return info.name == form.name && info.framePath == form.framePath;};
0166         auto it = std::find_if(customForms.constBegin(), customForms.constEnd(), sameForm);
0167         if (it == customForms.constEnd()) {
0168             continue;
0169         }
0170         QVector<WebForm::WebField> fields;
0171         auto filter = [it](const WebForm::WebField &f){return (*it).fields.contains(f.name);};
0172         std::copy_if(form.fields.constBegin(), form.fields.constEnd(), std::back_inserter(fields), filter);
0173         if (fields.isEmpty()) {
0174             continue;
0175         }
0176         WebForm f(form);
0177         f.fields = std::move(fields);
0178         forms.append(f);
0179     }
0180     return forms;
0181 }
0182 
0183 static void createSaveKeyFor(WebEnginePage *page, QString *key)
0184 {
0185     QUrl pageUrl(page->url());
0186     pageUrl.setPassword(QString());
0187 
0188     QString keyStr = pageUrl.toString();
0189 
0190     *key = QString::number(qHash(keyStr), 16);
0191 }
0192 
0193 void WebEngineWallet::saveFormsInPage(WebEnginePage* page)
0194 {
0195     if (!page) {
0196         return;
0197     }
0198     WebEngineWalletPrivate::detectFormsInPage(page, [this, page](const WebFormList &forms){saveFormData(page, forms);}, true);
0199 }
0200 
0201 void WebEngineWallet::saveFormData(WebEnginePage *page, const WebFormList &allForms, bool force)
0202 {
0203     if (!page) {
0204         return;
0205     }
0206 
0207     QString key;
0208     createSaveKeyFor(page, &key);
0209     if (d->pendingSaveRequests.contains(key)) {
0210         return;
0211     }
0212 
0213     QUrl url = page->url();
0214     WebFormList formsToSave = cacheableForms(url, allForms, CacheOperation::Save);
0215 
0216     if (!formsToSave.isEmpty()) {
0217         d->pendingSaveRequests.insert(key, formsToSave);
0218     } else {
0219         return;
0220     }
0221 
0222     if (force) {
0223         saveFormDataToCache(key);
0224     } else if (std::all_of(formsToSave.constBegin(), formsToSave.constEnd(), [this](const WebForm &f){return hasCachedFormData(f);})) {
0225         d->confirmSaveRequestOverwrites.insert(url);
0226         saveFormDataToCache(key);
0227     } else {
0228         if (std::any_of(formsToSave.constBegin(), formsToSave.constEnd(), [](const WebForm &f){return f.hasFieldsWithWrittenValues();})) {
0229             emit saveFormDataRequested(key, url);
0230         }
0231     }
0232 }
0233 
0234 void WebEngineWallet::savePageDataNow(WebEnginePage* page)
0235 {
0236     if (!page) {
0237         return;
0238     }
0239     QUrl url = page->url();
0240     WebEngineWalletPrivate::detectFormsInPage(page, [this, page](const WebFormList &forms){saveFormData(page, forms, true);});
0241 }
0242 
0243 void WebEngineWallet::removeFormData(WebEnginePage *page)
0244 {
0245     if (page) {
0246     QUrl url = page->url();
0247         auto callback = [this, url](const WebFormList &forms){
0248             removeFormDataFromCache(forms);
0249             WebEngineSettings::self()->removeCacheableFieldsCustomizationForPage(customFormsKey(url));
0250         };
0251         WebEngineWalletPrivate::detectFormsInPage(page, callback);
0252     }
0253 }
0254 
0255 void WebEngineWallet::removeFormData(const WebFormList &forms)
0256 {
0257     d->pendingRemoveRequests << forms;
0258     removeFormDataFromCache(forms);
0259 }
0260 
0261 void WebEngineWallet::acceptSaveFormDataRequest(const QString &key)
0262 {
0263     saveFormDataToCache(key);
0264 }
0265 
0266 void WebEngineWallet::rejectSaveFormDataRequest(const QString &key)
0267 {
0268     d->pendingSaveRequests.remove(key);
0269 }
0270 
0271 void WebEngineWallet::fillWebForm(const QUrl &url, const WebEngineWallet::WebFormList &forms)
0272 {
0273     QPointer<WebEnginePage> page = d->pendingFillRequests.value(url).page;
0274     if (!page) {
0275         return;
0276     }
0277 
0278     QString script;
0279     bool wasFilled = false;
0280 
0281     for (const WebEngineWallet::WebForm &form: forms) {
0282         for (const WebEngineWallet::WebForm::WebField &field: form.fields) {
0283             QString value = field.value;
0284             value.replace(QL1C('\\'), QL1S("\\\\"));
0285             if (!field.value.isEmpty()) {
0286                 script+= QString("fillFormElement(%1, '%2', '%3', '%4');")
0287                         .arg(form.framePath.isEmpty() ? "''" : form.framePath)
0288                         .arg((form.name.isEmpty() ? form.index : form.name))
0289                         .arg(field.name).arg(value);
0290             }
0291         }
0292     }
0293     if (!script.isEmpty()) {
0294         wasFilled = true;
0295         auto callback = [wasFilled, this](const QVariant &res){
0296             if (!res.isValid()) {
0297                 return;
0298             }
0299             emit fillFormRequestCompleted(wasFilled);
0300         };
0301         page.data()->runJavaScript(script, QWebEngineScript::ApplicationWorld, callback);
0302     }
0303 }
0304 
0305 WebEngineWallet::WebFormList WebEngineWallet::formsToFill(const QUrl &url) const
0306 {
0307     return d->pendingFillRequests.value(url).forms;
0308 }
0309 
0310 bool WebEngineWallet::hasCachedFormData(const WebForm &form) const
0311 {
0312     return !KWallet::Wallet::keyDoesNotExist(KWallet::Wallet::NetworkWallet(),
0313             KWallet::Wallet::FormDataFolder(),
0314             walletKey(form));
0315 }
0316 
0317 void WebEngineWallet::fillFormDataFromCache(const QList<QUrl> &urlList)
0318 {
0319     if (d->wallet) {
0320         QListIterator<QUrl> urlIt(urlList);
0321         while (urlIt.hasNext()) {
0322             const QUrl url = urlIt.next();
0323             WebFormList list = formsToFill(url);
0324             d->fillDataFromCache(list, hasCustomizedCacheableForms(url));
0325             fillWebForm(url, list);
0326         }
0327         d->pendingFillRequests.clear();
0328     }
0329     d->openWallet();
0330 }
0331 
0332 void WebEngineWallet::saveFormDataToCache(const QString &key)
0333 {
0334     if (d->wallet) {
0335         bool removeEntry = d->saveDataToCache(key);
0336         if (removeEntry){
0337             d->pendingSaveRequests.remove(key);
0338         }
0339         return;
0340     }
0341     d->openWallet();
0342 }
0343 
0344 void WebEngineWallet::removeFormDataFromCache(const WebFormList &forms)
0345 {
0346     if (d->wallet) {
0347         d->removeDataFromCache(forms);
0348         d->pendingRemoveRequests.clear();
0349         return;
0350     }
0351     d->openWallet();
0352 }
0353 
0354 void WebEngineWallet::customizeFieldsToCache(WebEnginePage* page, QWidget* widget)
0355 {
0356     if (!page) {
0357         return;
0358     }
0359     QUrl url = page->url();
0360     auto callback = [this, url, page, widget](const WebFormList &forms){
0361         WebEngineSettings::WebFormInfoList oldSettings = WebEngineSettings::self()->customizedCacheableFieldsForPage(customFormsKey(url));
0362         QMap<QString, QStringList> oldSettingsMap;
0363         for (const WebEngineSettings::WebFormInfo &info : oldSettings) {
0364             oldSettingsMap.insert(info.name, info.fields);
0365         }
0366         WebEngineCustomizeCacheableFieldsDlg dlg(forms, oldSettingsMap, widget);
0367         if (dlg.exec() == QDialog::Rejected) {
0368             return;
0369         }
0370         WebFormList selected = dlg.selectedFields();
0371         if (selected.isEmpty()) {
0372             return;
0373         }
0374         WebEngineSettings::WebFormInfoList vec;
0375         vec.reserve(selected.size());
0376         std::transform(selected.constBegin(), selected.constEnd(), std::back_inserter(vec), [](const WebForm &form){return form.toSettingsInfo();});
0377         WebEngineSettings::self()->setCustomizedCacheableFieldsForPage(customFormsKey(url), vec);
0378         if (dlg.immediatelyCacheData()) {
0379             //Pass only the selected fields to saveFormData instead of all the forms in the page, since
0380             //we already know they're the ones to be cached.
0381             saveFormData(page, selected, true);
0382             emit fillFormRequestCompleted(true);
0383         }
0384     };
0385     WebEngineWalletPrivate::detectFormsInPage(page, callback, true);
0386 }
0387 
0388 void WebEngineWallet::removeCustomizationForPage(const QUrl& url)
0389 {
0390     WebEngineSettings::self()->removeCacheableFieldsCustomizationForPage(customFormsKey(url));
0391 }
0392 
0393 WebEngineWallet::WebFormList WebEngineWallet::pendingSaveData(const QString& key)
0394 {
0395     return d->pendingSaveRequests.value(key);
0396 }
0397 
0398 QDebug operator<< (QDebug dbg, const WebEngineWallet::WebForm::WebFieldType type)
0399 {
0400     dbg.maybeSpace() << WebEngineWallet::WebForm::fieldNameFromType(type);
0401     return dbg;
0402 }
0403 
0404 QDebug operator<< (QDebug dbg, const WebEngineWallet::WebForm::WebField field)
0405 {
0406     QDebugStateSaver state(dbg);
0407     dbg.maybeSpace() << "WebField<";
0408     dbg.nospace() << "id: " << field.id;
0409     dbg.space() << "name: " << field.name;
0410     dbg << "type:" << field.type;
0411     dbg << "disabled:" << field.disabled;
0412     dbg << "readonly:" << field.readOnly;
0413     dbg << "autocompleteAllowed:" << field.autocompleteAllowed;
0414     dbg << "value:" << field.value;
0415     dbg.nospace() << ">";
0416     return dbg;
0417 }
0418 
0419 QDebug operator<< (QDebug dbg, const WebEngineWallet::WebForm form)
0420 {
0421     QDebugStateSaver state(dbg);
0422     dbg.nospace() << "WebForm<name: " << form.name;
0423     dbg.space() << "URL:" << form.url;
0424     dbg << "index:" << form.index;
0425     dbg << "framePath:" << form.framePath;
0426     QStringList fieldNames;
0427     fieldNames.reserve(form.fields.size());
0428     std::transform(form.fields.constBegin(), form.fields.constEnd(), std::back_inserter(fieldNames), [](const WebEngineWallet::WebForm::WebField &f){return f.name;});
0429     dbg << "field names:" << fieldNames.join(", ");
0430     dbg << ">";
0431     return dbg;
0432 }
0433 
0434 #include "moc_webenginewallet.cpp"