/* * This file is part of the KDE project. * * Copyright (C) 2009 Dawit Alemayehu * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * */ #include "kwebwallet.h" #include #include #include #include #include #include #include #include #include #include #include #define QL1S(x) QLatin1String(x) #define QL1C(x) QLatin1Char(x) // Javascript used to extract/set data from
elements. #define FILLABLE_FORM_ELEMENT_EXTRACTOR_JS "(function (){ \ var forms; \ var formList = document.forms; \ if (formList.length > 0) { \ forms = new Array; \ for (var i = 0; i < formList.length; ++i) { \ var inputList = formList[i].elements; \ if (inputList.length < 1) { \ continue; \ } \ var formObject = new Object; \ formObject.name = formList[i].name; \ if (typeof(formObject.name) != 'string') { \ formObject.name = String(formList[i].id); \ } \ formObject.index = i; \ formObject.elements = new Array; \ for (var j = 0; j < inputList.length; ++j) { \ if (inputList[j].type != 'text' && inputList[j].type != 'email' && inputList[j].type != 'password') { \ continue; \ } \ if (inputList[j].disabled || inputList[j].autocomplete == 'off') { \ continue; \ } \ var element = new Object; \ element.name = inputList[j].name; \ if (typeof(element.name) != 'string' ) { \ element.name = String(inputList[j].id); \ } \ element.value = String(inputList[j].value); \ element.type = String(inputList[j].type); \ element.readonly = Boolean(inputList[j].readOnly); \ formObject.elements.push(element); \ } \ if (formObject.elements.length > 0) { \ forms.push(formObject); \ } \ } \ } \ return forms; \ }())" /** * Creates key used to store and retrieve form data. * */ static QString walletKey(KWebWallet::WebForm form) { QString key = form.url.toString(QUrl::RemoveQuery|QUrl::RemoveFragment); key += QL1C('#'); key += form.name; return key; } static void collectAllChildFrames(QWebFrame* frame, QList& list) { list << frame->childFrames(); QListIterator it(frame->childFrames()); while (it.hasNext()) { collectAllChildFrames(it.next(), list); } } static QUrl urlForFrame(QWebFrame* frame) { return (frame->url().isEmpty() ? frame->baseUrl().resolved(frame->url()) : frame->url()); } /* Returns the top most window associated with widget. Unlike QWidget::window(), this function does its best to find and return the main application window associated with a given widget. It will not stop when it encounters a dialog which likely "has (or could have) a window-system frame". */ static QWidget* topLevelWindow(QObject* obj) { QWebPage *page = qobject_cast(obj); QWidget* widget = (page ? page->view() : qobject_cast(page)); while (widget && widget->parentWidget()) { widget = widget->parentWidget(); } return (widget ? widget->window() : 0); } class KWebWallet::KWebWalletPrivate { public: struct FormsData { QWeakPointer frame; KWebWallet::WebFormList forms; }; KWebWalletPrivate(KWebWallet* parent); KWebWallet::WebFormList parseFormData(QWebFrame* frame, bool fillform = true, bool ignorepasswd = false); void fillDataFromCache(KWebWallet::WebFormList &formList); void saveDataToCache(const QString &key); void removeDataFromCache(const WebFormList &formList); void openWallet(); // Private slots... void _k_openWalletDone(bool); void _k_walletClosed(); WId wid; KWebWallet *q; QScopedPointer wallet; KWebWallet::WebFormList pendingRemoveRequests; QHash pendingFillRequests; QHash pendingSaveRequests; QSet confirmSaveRequestOverwrites; }; KWebWallet::KWebWalletPrivate::KWebWalletPrivate(KWebWallet *parent) :wid (0), q(parent) { } KWebWallet::WebFormList KWebWallet::KWebWalletPrivate::parseFormData(QWebFrame *frame, bool fillform, bool ignorepasswd) { Q_ASSERT(frame); KWebWallet::WebFormList list; const QVariant result (frame->evaluateJavaScript(QL1S(FILLABLE_FORM_ELEMENT_EXTRACTOR_JS))); const QVariantList results (result.toList()); Q_FOREACH (const QVariant &formVariant, results) { QVariantMap map = formVariant.toMap(); KWebWallet::WebForm form; form.url = urlForFrame(frame); form.name = map[QL1S("name")].toString(); form.index = map[QL1S("index")].toString(); bool formHasPasswords = false; const QVariantList elements = map[QL1S("elements")].toList(); QList inputFields; Q_FOREACH (const QVariant &element, elements) { QVariantMap elementMap (element.toMap()); const QString name (elementMap[QL1S("name")].toString()); const QString value (ignorepasswd ? QString() : elementMap[QL1S("value")].toString()); if (name.isEmpty()) { continue; } if (fillform && elementMap[QL1S("readonly")].toBool()) { continue; } if (elementMap[QL1S("type")].toString().compare(QL1S("password"), Qt::CaseInsensitive) == 0) { if (!fillform && value.isEmpty()) continue; formHasPasswords = true; } inputFields.append(qMakePair(name, value)); } // Only add the input fields on form save requests... if (formHasPasswords || fillform) { form.fields = inputFields; } // Add the form to the list if we are saving it or it has cached data. if ((fillform && q->hasCachedFormData(form)) || (!fillform && !form.fields.isEmpty())) list << form; } return list; } void KWebWallet::KWebWalletPrivate::fillDataFromCache(KWebWallet::WebFormList &formList) { if (!wallet) { kWarning(800) << "Unable to retrieve form data from wallet"; return; } QString lastKey; QMap cachedValues; QMutableListIterator formIt (formList); while (formIt.hasNext()) { KWebWallet::WebForm &form = formIt.next(); const QString key (walletKey(form)); if (key != lastKey && wallet->readMap(key, cachedValues) != 0) { kWarning(800) << "Unable to read form data for key:" << key; continue; } for (int i = 0, count = form.fields.count(); i < count; ++i) { form.fields[i].second = cachedValues.value(form.fields[i].first); } lastKey = key; } } void KWebWallet::KWebWalletPrivate::saveDataToCache(const QString &key) { // Make sure the specified keys exists before acting on it. See BR# 270209. if (!pendingSaveRequests.contains(key)) { return; } bool success = false; const QUrl url = pendingSaveRequests.value(key).first().url; if (wallet) { int count = 0; const KWebWallet::WebFormList list = pendingSaveRequests.value(key); QListIterator formIt (list); while (formIt.hasNext()) { QMap values, storedValues; const KWebWallet::WebForm form = formIt.next(); const QString accessKey = walletKey(form); if (confirmSaveRequestOverwrites.contains(url)) { confirmSaveRequestOverwrites.remove(url); const int status = wallet->readMap(accessKey, storedValues); if (status == 0 && storedValues.count()) { QListIterator fieldIt (form.fields); while (fieldIt.hasNext()) { const KWebWallet::WebForm::WebField field = fieldIt.next(); if (storedValues.contains(field.first) && storedValues.value(field.first) != field.second) { emit q->saveFormDataRequested(key, url); return; } } // If we got here it means the new credential is exactly // the same as the one already cached ; so skip the // re-saving part... success = true; continue; } } QListIterator fieldIt (form.fields); while (fieldIt.hasNext()) { const KWebWallet::WebForm::WebField field = fieldIt.next(); values.insert(field.first, field.second); } if (wallet->writeMap(accessKey, values) == 0) count++; else kWarning(800) << "Unable to write form data to wallet"; } if (list.isEmpty() || count > 0) success = true; pendingSaveRequests.remove(key); } else { kWarning(800) << "NULL KWallet instance!"; } emit q->saveFormDataCompleted(url, success); } void KWebWallet::KWebWalletPrivate::openWallet() { if (!wallet.isNull()) { return; } wallet.reset(KWallet::Wallet::openWallet(KWallet::Wallet::NetworkWallet(), wid, KWallet::Wallet::Asynchronous)); if (wallet.isNull()) { return; } connect(wallet.data(), SIGNAL(walletOpened(bool)), q, SLOT(_k_openWalletDone(bool))); connect(wallet.data(), SIGNAL(walletClosed()), q, SLOT(_k_walletClosed())); } void KWebWallet::KWebWalletPrivate::removeDataFromCache(const WebFormList &formList) { if (!wallet) { kWarning(800) << "NULL KWallet instance!"; return; } QListIterator formIt (formList); while (formIt.hasNext()) wallet->removeEntry(walletKey(formIt.next())); } void KWebWallet::KWebWalletPrivate::_k_openWalletDone(bool ok) { Q_ASSERT (wallet); if (ok && (wallet->hasFolder(KWallet::Wallet::FormDataFolder()) || wallet->createFolder(KWallet::Wallet::FormDataFolder())) && wallet->setFolder(KWallet::Wallet::FormDataFolder())) { // Do pending fill requests... if (!pendingFillRequests.isEmpty()) { KUrl::List urlList; QMutableHashIterator requestIt (pendingFillRequests); while (requestIt.hasNext()) { requestIt.next(); KWebWallet::WebFormList list = requestIt.value().forms; fillDataFromCache(list); q->fillWebForm(requestIt.key(), list); } pendingFillRequests.clear(); } // Do pending save requests... if (!pendingSaveRequests.isEmpty()) { QListIterator keysIt (pendingSaveRequests.keys()); while (keysIt.hasNext()) saveDataToCache(keysIt.next()); } // Do pending remove requests... if (!pendingRemoveRequests.isEmpty()) { removeDataFromCache(pendingRemoveRequests); pendingRemoveRequests.clear(); } } else { // Delete the wallet if opening the wallet failed or we were unable // to change to the folder we wanted to change to. delete wallet.take(); } } void KWebWallet::KWebWalletPrivate::_k_walletClosed() { if (wallet) wallet.take()->deleteLater(); emit q->walletClosed(); } KWebWallet::KWebWallet(QObject *parent, WId wid) :QObject(parent), d(new KWebWalletPrivate(this)) { if (!wid) { // If wid is 0, make a best effort attempt to discern it from our // parent object. QWidget* widget = topLevelWindow(parent); if (widget) { wid = widget->winId(); } } d->wid = wid; } KWebWallet::~KWebWallet() { delete d; } KWebWallet::WebFormList KWebWallet::formsWithCachedData(QWebFrame* frame, bool recursive) const { WebFormList list; if (frame) { list << d->parseFormData(frame); if (recursive) { QList childFrameList; collectAllChildFrames(frame, childFrameList); QListIterator framesIt (childFrameList); while (framesIt.hasNext()) { list << d->parseFormData(framesIt.next()); } } } return list; } void KWebWallet::fillFormData(QWebFrame *frame, bool recursive) { if (!frame) return; KUrl::List urlList; WebFormList formsList = d->parseFormData(frame); if (!formsList.isEmpty()) { const QUrl url (urlForFrame(frame)); if (d->pendingFillRequests.contains(url)) { kWarning(800) << "Duplicate request rejected!"; } else { KWebWalletPrivate::FormsData data; data.frame = QWeakPointer(frame); data.forms << formsList; d->pendingFillRequests.insert(url, data); urlList << url; } } if (recursive) { QList childFrameList; collectAllChildFrames(frame, childFrameList); QListIterator frameIt (childFrameList); while (frameIt.hasNext()) { QWebFrame *childFrame = frameIt.next(); formsList = d->parseFormData(childFrame); if (formsList.isEmpty()) continue; const QUrl url (childFrame->url()); if (d->pendingFillRequests.contains(url)) { kWarning(800) << "Duplicate request rejected!!!"; } else { KWebWalletPrivate::FormsData data; data.frame = QWeakPointer(childFrame); data.forms << formsList; d->pendingFillRequests.insert(url, data); urlList << url; } } } if (!urlList.isEmpty()) fillFormDataFromCache(urlList); } static void createSaveKeyFor(QWebFrame* frame, QString* key) { QUrl frameUrl(urlForFrame(frame)); frameUrl.setPassword(QString()); frameUrl.setPassword(QString()); QString keyStr = frameUrl.toString(); if (!frame->frameName().isEmpty()) keyStr += frame->frameName(); *key = QString::number(qHash(keyStr), 16); } void KWebWallet::saveFormData(QWebFrame *frame, bool recursive, bool ignorePasswordFields) { if (!frame) return; QString key; createSaveKeyFor(frame, &key); if (d->pendingSaveRequests.contains(key)) return; WebFormList list = d->parseFormData(frame, false, ignorePasswordFields); if (recursive) { QList childFrameList; collectAllChildFrames(frame, childFrameList); QListIterator frameIt (childFrameList); while (frameIt.hasNext()) list << d->parseFormData(frameIt.next(), false, ignorePasswordFields); } if (list.isEmpty()) return; d->pendingSaveRequests.insert(key, list); QMutableListIterator it (list); while (it.hasNext()) { const WebForm form (it.next()); if (hasCachedFormData(form)) it.remove(); } if (list.isEmpty()) { d->confirmSaveRequestOverwrites.insert(urlForFrame(frame)); saveFormDataToCache(key); return; } emit saveFormDataRequested(key, urlForFrame(frame)); } void KWebWallet::removeFormData(QWebFrame *frame, bool recursive) { if (frame) removeFormDataFromCache(formsWithCachedData(frame, recursive)); } void KWebWallet::removeFormData(const WebFormList &forms) { d->pendingRemoveRequests << forms; removeFormDataFromCache(forms); } void KWebWallet::acceptSaveFormDataRequest(const QString &key) { saveFormDataToCache(key); } void KWebWallet::rejectSaveFormDataRequest(const QString & key) { d->pendingSaveRequests.remove(key); } void KWebWallet::fillWebForm(const KUrl &url, const KWebWallet::WebFormList &forms) { QWeakPointer frame = d->pendingFillRequests.value(url).frame; if (!frame) return; QString script; bool wasFilled = false; Q_FOREACH (const KWebWallet::WebForm& form, forms) { Q_FOREACH(const KWebWallet::WebForm::WebField& field, form.fields) { QString value = field.second; value.replace(QL1C('\\'), QL1S("\\\\")); script += QString::fromLatin1("if (document.forms[\"%1\"].elements[\"%2\"]) document.forms[\"%1\"].elements[\"%2\"].value=\"%3\";\n") .arg((form.name.isEmpty() ? form.index : form.name)) .arg(field.first).arg(value); } } if (!script.isEmpty()) { wasFilled = true; frame.data()->evaluateJavaScript(script); } emit fillFormRequestCompleted(wasFilled); } KWebWallet::WebFormList KWebWallet::formsToFill(const KUrl &url) const { return d->pendingFillRequests.value(url).forms; } KWebWallet::WebFormList KWebWallet::formsToSave(const QString &key) const { return d->pendingSaveRequests.value(key); } bool KWebWallet::hasCachedFormData(const WebForm &form) const { return !KWallet::Wallet::keyDoesNotExist(KWallet::Wallet::NetworkWallet(), KWallet::Wallet::FormDataFolder(), walletKey(form)); } void KWebWallet::fillFormDataFromCache(const KUrl::List &urlList) { if (d->wallet) { QListIterator urlIt (urlList); while (urlIt.hasNext()) { const KUrl url = urlIt.next(); WebFormList list = formsToFill(url); d->fillDataFromCache(list); fillWebForm(url, list); } d->pendingFillRequests.clear(); } d->openWallet(); } void KWebWallet::saveFormDataToCache(const QString &key) { if (d->wallet) { d->saveDataToCache(key); return; } d->openWallet(); } void KWebWallet::removeFormDataFromCache(const WebFormList &forms) { if (d->wallet) { d->removeDataFromCache(forms); d->pendingRemoveRequests.clear(); return; } d->openWallet(); } #include "moc_kwebwallet.cpp"