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"