diff --git a/webenginepart/src/CMakeLists.txt b/webenginepart/src/CMakeLists.txt --- a/webenginepart/src/CMakeLists.txt +++ b/webenginepart/src/CMakeLists.txt @@ -1,3 +1,5 @@ +find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS Wallet) + include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_BINARY_DIR}) set(kwebenginepartlib_LIB_SRCS @@ -8,6 +10,7 @@ websslinfo.cpp webhistoryinterface.cpp webenginepartdownloadmanager.cpp + webenginewallet.cpp settings/webenginesettings.cpp settings/webengine_filter.cpp ui/searchbar.cpp @@ -27,6 +30,7 @@ KF5::Parts KF5::SonnetCore KF5::WindowSystem # for KUserTimestamp + KF5::Wallet ) target_include_directories(kwebenginepartlib PUBLIC diff --git a/webenginepart/src/webenginepage.h b/webenginepart/src/webenginepage.h --- a/webenginepart/src/webenginepage.h +++ b/webenginepart/src/webenginepage.h @@ -40,6 +40,7 @@ class WebEnginePart; class QWebEngineDownloadItem; class KPasswdServerClient; +class WebEngineWallet; class WebEnginePage : public QWebEnginePage { @@ -64,6 +65,8 @@ void download(const QUrl &url, bool newWindow = false); + WebEngineWallet* wallet() const {return m_wallet;} + Q_SIGNALS: /** * This signal is emitted whenever a user cancels/aborts a load resource @@ -107,7 +110,7 @@ private: bool checkLinkSecurity(const QNetworkRequest& req, NavigationType type) const; - bool checkFormData(const QNetworkRequest& req) const; + bool checkFormData(const QUrl& url) const; bool handleMailToUrl (const QUrl& , NavigationType type) const; void setPageJScriptPolicy(const QUrl& url); @@ -121,6 +124,7 @@ QPointer m_part; QScopedPointer m_passwdServerClient; + WebEngineWallet *m_wallet; }; @@ -158,6 +162,7 @@ KParts::WindowArgs m_windowArgs; WebWindowType m_type; bool m_createNewWindow; + WebEngineWallet* m_wallet; }; #endif // WEBENGINEPAGE_H diff --git a/webenginepart/src/webenginepage.cpp b/webenginepart/src/webenginepage.cpp --- a/webenginepart/src/webenginepage.cpp +++ b/webenginepart/src/webenginepage.cpp @@ -27,6 +27,7 @@ #include "webengineview.h" #include "settings/webenginesettings.h" #include "webenginepartdownloadmanager.h" +#include "webenginewallet.h" #include #include @@ -72,7 +73,8 @@ m_kioErrorCode(0), m_ignoreError(false), m_part(part), - m_passwdServerClient(new KPasswdServerClient) + m_passwdServerClient(new KPasswdServerClient), + m_wallet(Q_NULLPTR) { if (view()) WebEngineSettings::self()->computeFontSizes(view()->logicalDpiY()); @@ -94,6 +96,7 @@ this->profile()->setHttpUserAgent(this->profile()->httpUserAgent() + " Konqueror (WebEnginePart)"); } WebEnginePartDownloadManager::instance()->addPage(this); + m_wallet = new WebEngineWallet(this, parent ? parent->window()->winId() : 0); } WebEnginePage::~WebEnginePage() @@ -204,8 +207,9 @@ bool inPageRequest = true; switch (type) { case QWebEnginePage::NavigationTypeFormSubmitted: - //if (!checkFormData(request)) - // return false; + if (!checkFormData(url)) + return false; + m_wallet->saveFormData(this); break; #if 0 case QWebEnginePage::NavigationTypeFormResubmitted: @@ -548,9 +552,9 @@ return true; } -bool WebEnginePage::checkFormData(const QNetworkRequest &req) const +bool WebEnginePage::checkFormData(const QUrl &url) const { - const QString scheme (req.url().scheme()); + const QString scheme (url.scheme()); if (m_sslInfo.isValid() && !scheme.compare(QL1S("https")) && !scheme.compare(QL1S("mailto")) && diff --git a/webenginepart/src/webenginepart.h b/webenginepart/src/webenginepart.h --- a/webenginepart/src/webenginepart.h +++ b/webenginepart/src/webenginepart.h @@ -42,6 +42,7 @@ class FeaturePermissionBar; class KUrlLabel; class WebEngineBrowserExtension; +class WebEngineWallet; /** * A KPart wrapper for the QtWebEngine's browser rendering engine. @@ -98,6 +99,9 @@ void connectWebEnginePageSignals(WebEnginePage* page); void slotShowFeaturePermissionBar(QWebEnginePage::Feature); + + void setWallet(WebEngineWallet* wallet); + protected: /** * Re-implemented for internal reasons. API remains unaffected. @@ -160,6 +164,7 @@ WebEngineBrowserExtension* m_browserExtension; KParts::StatusBarExtension* m_statusBarExtension; WebEngineView* m_webView; + WebEngineWallet* m_wallet; }; #endif // WEBENGINEPART_H diff --git a/webenginepart/src/webenginepart.cpp b/webenginepart/src/webenginepart.cpp --- a/webenginepart/src/webenginepart.cpp +++ b/webenginepart/src/webenginepart.cpp @@ -34,6 +34,7 @@ #include "webenginepage.h" #include "websslinfo.h" #include "webhistoryinterface.h" +#include "webenginewallet.h" #include "ui/searchbar.h" #include "ui/passwordbar.h" @@ -80,7 +81,8 @@ m_statusBarWalletLabel(0), m_searchBar(0), m_passwordBar(0), - m_featurePermissionBar(0) + m_featurePermissionBar(0), + m_wallet(Q_NULLPTR) { KAboutData about = KAboutData(QStringLiteral("webenginepart"), i18nc("Program Name", "WebEnginePart"), @@ -168,6 +170,7 @@ // Load plugins once we are fully ready loadPlugins(); + setWallet(page()->wallet()); } WebEnginePart::~WebEnginePart() @@ -311,17 +314,25 @@ m_browserExtension->setIconUrl(url); } }); +} -#if 0 - KWebWallet *wallet = page->wallet(); - if (wallet) { - connect(wallet, SIGNAL(saveFormDataRequested(QString,QUrl)), - this, SLOT(slotSaveFormDataRequested(QString,QUrl))); - connect(wallet, SIGNAL(fillFormRequestCompleted(bool)), - this, SLOT(slotFillFormRequestCompleted(bool))); - connect(wallet, SIGNAL(walletClosed()), this, SLOT(slotWalletClosed())); +void WebEnginePart::setWallet(WebEngineWallet* wallet) +{ + if(m_wallet){ + disconnect(m_wallet, &WebEngineWallet::saveFormDataRequested, + this, &WebEnginePart::slotSaveFormDataRequested); + disconnect(m_wallet, &WebEngineWallet::fillFormRequestCompleted, + this, &WebEnginePart::slotFillFormRequestCompleted); + disconnect(m_wallet, &WebEngineWallet::walletClosed, this, &WebEnginePart::slotWalletClosed); + } + m_wallet = wallet; + if (m_wallet) { + connect(m_wallet, &WebEngineWallet::saveFormDataRequested, + this, &WebEnginePart::slotSaveFormDataRequested); + connect(m_wallet, &WebEngineWallet::fillFormRequestCompleted, + this, &WebEnginePart::slotFillFormRequestCompleted); + connect(m_wallet, &WebEngineWallet::walletClosed, this, &WebEnginePart::slotWalletClosed); } -#endif } bool WebEnginePart::openUrl(const QUrl &_u) @@ -451,15 +462,15 @@ } if (!Utils::isBlankUrl(url())) { m_hasCachedFormData = false; - if (WebEngineSettings::self()->isNonPasswordStorableSite(url().host())) { addWalletStatusBarIcon(); - } else { + } + else { // Attempt to fill the web form... -// KWebWallet *webWallet = page() ? page()->wallet() : 0; -// if (webWallet) { -// webWallet->fillFormData(frame, false); -// } + WebEngineWallet *wallet = page() ? page()->wallet() : 0; + if (wallet){ + wallet->fillFormData(page()); + } } } @@ -766,10 +777,10 @@ void WebEnginePart::slotRemoveCachedPasswords() { - if (!page()) // || !page()->wallet()) + if (!page() || !page()->wallet()) return; -// page()->wallet()->removeFormData(page()->mainFrame(), true); + page()->wallet()->removeFormData(page()); m_hasCachedFormData = false; } @@ -822,8 +833,8 @@ this, SLOT(slotFeaturePermissionGranted(QWebEnginePage::Feature))); connect(m_featurePermissionBar, SIGNAL(permissionDenied(QWebEnginePage::Feature)), this, SLOT(slotFeaturePermissionDenied(QWebEnginePage::Feature))); -// connect(m_passwordBar, SIGNAL(done()), -// this, SLOT(slotSaveFormDataDone())); + connect(m_passwordBar, SIGNAL(done()), + this, SLOT(slotSaveFormDataDone())); QBoxLayout* lay = qobject_cast(widget()->layout()); if (lay) lay->insertWidget(0, m_featurePermissionBar); @@ -862,19 +873,16 @@ if (!m_passwordBar) { m_passwordBar = new PasswordBar(widget()); -#if 0 - KWebWallet* wallet = page()->wallet(); - if (!wallet) { - kWarning() << "No wallet instance found! This should never happen!"; + if (!m_wallet) { + qDebug() << "No m_wallet instance found! This should never happen!"; return; } connect(m_passwordBar, SIGNAL(saveFormDataAccepted(QString)), - wallet, SLOT(acceptSaveFormDataRequest(QString))); + m_wallet, SLOT(acceptSaveFormDataRequest(QString))); connect(m_passwordBar, SIGNAL(saveFormDataRejected(QString)), - wallet, SLOT(rejectSaveFormDataRequest(QString))); + m_wallet, SLOT(rejectSaveFormDataRequest(QString))); connect(m_passwordBar, SIGNAL(done()), this, SLOT(slotSaveFormDataDone())); -#endif } Q_ASSERT(m_passwordBar); diff --git a/webenginepart/src/webenginepartfactory.cpp b/webenginepart/src/webenginepartfactory.cpp --- a/webenginepart/src/webenginepartfactory.cpp +++ b/webenginepart/src/webenginepartfactory.cpp @@ -24,6 +24,7 @@ #include + WebEngineFactory::~WebEngineFactory() { // kDebug() << this; diff --git a/webenginepart/src/webenginewallet.h b/webenginepart/src/webenginewallet.h new file mode 100644 --- /dev/null +++ b/webenginepart/src/webenginewallet.h @@ -0,0 +1,305 @@ +/* + * This file is part of the KDE project. + * + * Copyright (C) 2009 Dawit Alemayehu + * Copyright (C) 2018 Stefano Crocco + * + * 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. + * + */ +#ifndef WEBENGINEWALLET_H +#define WEBENGINEWALLET_H + +#include +#include +#include +#include +#include +#include +#include + +class WebEnginePage; + +class WebEngineWallet : public QObject +{ + Q_OBJECT + +public: + + /** + * Holds data from a HTML <form> element. + */ + struct WebForm { + /** + * A typedef for storing the name and value attributes of HTML <input> + * elements. + */ + typedef QPair WebField; + + /** The URL the form was found at. */ + QUrl url; + /** The name attribute of the form. */ + QString name; + /** The position of the form on the web page, relative to other forms. */ + QString index; + /** The path of the frame the form belongs to relative to the toplevel window (in the javascript sense). + * + * This is stored as a string containing a javascript array (it is passed as is to javascript code, so no need to store it in C++ format + */ + QString framePath; + /** The name and value attributes of each input element in the form. */ + QVector fields; + }; + + /** + * A list of web forms + */ + typedef QVector WebFormList; + + /** + * Constructs a WebEngineWebWallet + * + * @p parent is usually the WebEnginePage this wallet is being used for. + * + * The @p wid parameter is used to tell the KWallet manager which window + * is requesting access to the wallet. + * + * @param parent the owner of this wallet + * @param wid the window ID of the window the web page will be + * embedded in + */ + explicit WebEngineWallet(QObject *parent = nullptr, WId wid = 0); + + /** + * Destructor + */ + virtual ~WebEngineWallet(); + + /** + * Attempts to save the form data from @p page and its children frames. + * + * You must connect to the @ref saveFormDataRequested signal and call either + * @ref rejectSaveFormDataRequest or @ref acceptSaveFormDataRequest signals + * in order to complete the save request. Otherwise, you request will simply + * be ignored. + * + * Note that this function is asynchronous, as it requires running javascript code + * on the page using @ref QWebEnginePage::runJavaScript. This function only requests + * for the form data to be saved when @ref QWebEnginePage::runJavaScript finishes. + * The actual saving is done by @ref saveFormDataCallback + */ + void saveFormData(WebEnginePage *page, bool ignorePasswordFields = false); + + /** + * Attempts to fill forms contained in @p page with cached data. + * + * Note that this function is asynchronous, as it requires running javascript code + * on the page using @ref QWebEnginePage::runJavaScript. This function only requests + * for the form data to be filled when @ref QWebEnginePage::runJavaScript finishes. + * The actual filling is done by @ref fillFormDataCallback + */ + void fillFormData(WebEnginePage *page); + + /** + * Removes the form data specified by @p forms from the persistent storage. + * + * Note that this function will remove all cached data for forms found in @p page. + * + * Note that this function is asynchronous, as it requires running javascript code + * on the page using @ref QWebEnginePage::runJavaScript. This function only requests + * for the form data to be removed when @ref QWebEnginePage::runJavaScript finishes. + * The actual removing is done by @ref removeFormDataCallback + */ + void removeFormData(WebEnginePage *page); + + /** + * Removes the form data specified by @p forms from the persistent storage. + * + * @see formsWithCachedData + */ + void removeFormData(const WebFormList &forms); + +public Q_SLOTS: + /** + * Accepts the save form data request associated with @p key. + * + * The @p key parameter is the one sent through the @ref saveFormDataRequested + * signal. + * + * You must always call this function or @ref rejectSaveFormDataRequest in + * order to complete the save form data request. Otherwise, the request will + * simply be ignored. + * + * @see saveFormDataRequested. + */ + void acceptSaveFormDataRequest(const QString &key); + + /** + * Rejects the save form data request associated with @p key. + * + * The @p key parameter is the one sent through the @ref saveFormDataRequested + * signal. + * + * @see saveFormDataRequested. + */ + void rejectSaveFormDataRequest(const QString &key); + +Q_SIGNALS: + /** + * This signal is emitted whenever a save form data request is received. + * + * Unless you connect to this signal and and call @ref acceptSaveFormDataRequest + * or @ref rejectSaveFormDataRequest slots, the save form data requested through + * @ref saveFormData will simply be ignored. + * + * @p key is a value that uniquely identifies the save request and @p url + * is the address for which the form data is being saved. + * + * @see acceptSaveFormDataRequest + * @see rejectSaveFormDataRequest + */ + void saveFormDataRequested(const QString &key, const QUrl &url); + + /** + * This signal is emitted whenever a save form data request is completed. + * + * @p ok will be set to true if the save form data request for @p url was + * completed successfully. + * + * @see saveFormDataRequested + */ + void saveFormDataCompleted(const QUrl &url, bool ok); + + /** + * This signal is emitted whenever a fill form data request is completed. + * + * @p ok will be set to true if any forms were successfully filled with + * cached data from the persistent storage. + * + * @see fillFormData + * @since 4.5 + */ + void fillFormRequestCompleted(bool ok); + + /** + * This signal is emitted whenever the current wallet is closed. + */ + void walletClosed(); + +protected: + /** + * Returns a list of forms for @p url that are waiting to be filled. + * + * This function returns an empty list if there is no pending requests + * for filling forms associated with @p url. + */ + WebFormList formsToFill(const QUrl &url) const; + + /** + * Returns a list of for @p key that are waiting to be saved. + * + * This function returns an empty list if there are no pending requests + * for saving forms associated with @p key. + */ + WebFormList formsToSave(const QString &key) const; + + /** + * Returns forms to be removed from persistent storage. + */ + WebFormList formsToDelete() const; + + /** + * Returns true when there is data associated with @p form in the + * persistent storage. + */ + bool hasCachedFormData(const WebForm &form) const; + + /** + * Fills the web forms in frame that point to @p url with data from @p forms. + * + * @see fillFormDataFromCache. + */ + void fillWebForm(const QUrl &url, const WebFormList &forms); + + /** + * Fills form data from persistent storage. + * + * If you reimplement this function, call @ref formsToFill to obtain + * the list of forms pending to be filled. Once you fill the list with + * the cached data from the persistent storage, you must call @p fillWebForm + * to fill out the actual web forms. + * + * @see formsToFill + */ + void fillFormDataFromCache(const QList &list); + + /** + * Stores form data associated with @p key to a persistent storage. + * + * If you reimplement this function, call @ref formsToSave to obtain the + * list of form data pending to be saved to persistent storage. + * + *@see formsToSave + */ + void saveFormDataToCache(const QString &key); + + /** + * Removes all cached form data associated with @p forms from persistent storage. + * + * If you reimplement this function, call @ref formsToDelete to obtain the + * list of form data pending to be removed from persistent storage. + * + *@see formsToDelete + */ + void removeFormDataFromCache(const WebFormList &forms); + +private: + + /** + * Callback used by @ref fillFormData to insert form data + * + * This function is called as a callback from @ref fillFormData after + * @ref QWebEnginePage::runJavaScript has finished + */ + void fillFormDataCallback(WebEnginePage *page, const WebFormList &formsList); + + /** + * Callback used by @ref saveFormData to save data + * + * This function is called as a callback from @ref saveFormData after + * @ref QWebEnginePage::runJavaScript has finished + */ + void saveFormDataCallback(const QString &key, const QUrl &url, const WebFormList &formslist); + + /** + * Callback used by @ref removeFormData to remove data + * + * This function is called as a callback from @ref removeFormData after + * @ref QWebEnginePage::runJavaScript has finished + */ + void removeFormDataCallback(const WebFormList &list); + +private: + class WebEngineWalletPrivate; + friend class WebEngineWalletPrivate; + WebEngineWalletPrivate *const d; + + Q_PRIVATE_SLOT(d, void _k_openWalletDone(bool)) + Q_PRIVATE_SLOT(d, void _k_walletClosed()) +}; + + +#endif // WEBENGINEWALLET_H diff --git a/webenginepart/src/webenginewallet.cpp b/webenginepart/src/webenginewallet.cpp new file mode 100644 --- /dev/null +++ b/webenginepart/src/webenginewallet.cpp @@ -0,0 +1,599 @@ +/* + * This file is part of the KDE project. + * + * Copyright (C) 2009 Dawit Alemayehu + * Copyright (C) 2018 Stefano Crocco + * + * 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 "webenginewallet.h" +#include "webenginepage.h" + +#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. +static const char s_fillableFormElementExtractorJs[] = "(function(){ " +" function findFormsRecursive(wnd, existingList, path){" +" findFormsInFrame(wnd, existingList, path);" +" frameList = wnd.frames;" +" for(var i = 0; i < frameList.length; ++i) {" +" var newPath = path.concat(i);" +" findFormsRecursive(frameList[i], existingList, newPath);" +" }" +" }" +" function findFormsInFrame(frm, existingList, path){" +" var url = frm.location;" +" var formList;" +" try{ formList = frm.document.forms; } " +" catch(e){" +" return;" +" }" +" if (formList.length > 0) { " +" for (var i = 0; i < formList.length; ++i) { " +" var inputList = formList[i].elements; " +" if (inputList.length < 1) { " +" continue; " +" } " +" var formObject = new Object; " +" formObject.url = url;" +" 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) { " +" formObject.framePath = path;" +" console.log(JSON.stringify(formObject));" +" existingList.push(JSON.stringify(formObject)); " +" } " +" } " +" } " +" }" +" var forms = new Array;" +" findFormsRecursive(window, forms, []);" +" return forms;" +"})()"; + +//javascript used to fill a single form element +static const char s_javascriptFillInputFragment[] = "var frm = window;" +" for(var i=0; i < [%1].length; ++i) frm=frm.frames[i];" +" if (frm.document.forms['%2'] && frm.document.forms['%2'].elements['%3']){" +" frm.document.forms['%2'].elements['%3'].value='%4';\n" +" }"; + + +/** + * Creates key used to store and retrieve form data. + * + */ +static QString walletKey(WebEngineWallet::WebForm form) +{ + QString key = form.url.toString(QUrl::RemoveQuery | QUrl::RemoveFragment); + key += QL1C('#'); + key += form.name; + return key; +} + +static QUrl urlForFrame(const QUrl &frameUrl, const QUrl &pageUrl) +{ + return (frameUrl.isEmpty() || frameUrl.isRelative() ? pageUrl.resolved(frameUrl) : frameUrl); +} + +class WebEngineWallet::WebEngineWalletPrivate +{ +public: + struct FormsData { + QPointer page; + WebEngineWallet::WebFormList forms; + }; + + typedef std::function WebWalletCallback; + + WebEngineWalletPrivate(WebEngineWallet *parent); + + void withFormData(WebEnginePage *page, WebWalletCallback callback, bool fillform = true, bool ignorepasswd = false); + WebFormList parseFormData(const QVariant &result, const QUrl &url, bool fillform = true, bool ignorepasswd = false); + void performFormDataParsing(const QVariant &result, const QUrl &url, WebWalletCallback callback, bool fillform, bool ignorepasswd); + void fillDataFromCache(WebEngineWallet::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; + WebEngineWallet *q; + QScopedPointer wallet; + WebEngineWallet::WebFormList pendingRemoveRequests; + QHash pendingFillRequests; + QHash pendingSaveRequests; + QSet confirmSaveRequestOverwrites; +}; + +WebEngineWallet::WebEngineWalletPrivate::WebEngineWalletPrivate(WebEngineWallet *parent) + : wid(0), q(parent) +{ +} + +WebEngineWallet::WebFormList WebEngineWallet::WebEngineWalletPrivate::parseFormData(const QVariant &result, const QUrl &url, bool fillform, bool ignorepasswd) +{ + const QVariantList results(result.toList()); + WebEngineWallet::WebFormList list; + Q_FOREACH (const QVariant &formVariant, results) { + QJsonDocument doc = QJsonDocument::fromJson(formVariant.toString().toUtf8()); + const QVariantMap map = doc.toVariant().toMap(); + WebEngineWallet::WebForm form; + form.url = urlForFrame(QUrl(map[QL1S("url")].toString()), url); + form.name = map[QL1S("name")].toString(); + form.index = map[QL1S("index")].toString(); + form.framePath = map["framePath"].toStringList().join(","); + bool formHasPasswords = false; + const QVariantList elements = map[QL1S("elements")].toList(); + QVector 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 WebEngineWallet::WebEngineWalletPrivate::withFormData(WebEnginePage* page, WebWalletCallback callback, bool fillform, bool ignorepasswd) +{ + Q_ASSERT(page); + QUrl url = page->url(); + auto internalCallback = [this, url, fillform, ignorepasswd, callback](const QVariant &result){ + WebFormList res = parseFormData(result, url, fillform, ignorepasswd); + callback(res); + }; + page->runJavaScript(QL1S(s_fillableFormElementExtractorJs), internalCallback); +} + +void WebEngineWallet::WebEngineWalletPrivate::fillDataFromCache(WebEngineWallet::WebFormList &formList) +{ + if (!wallet) { + qWarning() << "Unable to retrieve form data from wallet"; + return; + } + + QString lastKey; + QMap cachedValues; + QMutableVectorIterator formIt(formList); + + while (formIt.hasNext()) { + WebEngineWallet::WebForm &form = formIt.next(); + const QString key(walletKey(form)); + if (key != lastKey && wallet->readMap(key, cachedValues) != 0) { + qWarning() << "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 WebEngineWallet::WebEngineWalletPrivate::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 WebEngineWallet::WebFormList list = pendingSaveRequests.value(key); + QVectorIterator formIt(list); + + while (formIt.hasNext()) { + QMap values, storedValues; + const WebEngineWallet::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()) { + QVectorIterator fieldIt(form.fields); + while (fieldIt.hasNext()) { + const WebEngineWallet::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; + } + } + QVectorIterator fieldIt(form.fields); + while (fieldIt.hasNext()) { + const WebEngineWallet::WebForm::WebField field = fieldIt.next(); + values.insert(field.first, field.second); + } + + if (wallet->writeMap(accessKey, values) == 0) { + count++; + } else { + qWarning() << "Unable to write form data to wallet"; + } + } + + if (list.isEmpty() || count > 0) { + success = true; + } + + pendingSaveRequests.remove(key); + } else { + qWarning() << "NULL Wallet instance!"; + } + + emit q->saveFormDataCompleted(url, success); +} + +void WebEngineWallet::WebEngineWalletPrivate::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 WebEngineWallet::WebEngineWalletPrivate::removeDataFromCache(const WebFormList &formList) +{ + if (!wallet) { + qWarning() << "NULL Wallet instance!"; + return; + } + + QVectorIterator formIt(formList); + while (formIt.hasNext()) { + wallet->removeEntry(walletKey(formIt.next())); + } +} + +void WebEngineWallet::WebEngineWalletPrivate::_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()) { + QList urlList; + QMutableHashIterator requestIt(pendingFillRequests); + while (requestIt.hasNext()) { + requestIt.next(); + WebEngineWallet::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 WebEngineWallet::WebEngineWalletPrivate::_k_walletClosed() +{ + if (wallet) { + wallet.take()->deleteLater(); + } + + emit q->walletClosed(); +} + +WebEngineWallet::WebEngineWallet(QObject *parent, WId wid) + : QObject(parent), d(new WebEngineWalletPrivate(this)) +{ + d->wid = wid; +} + +WebEngineWallet::~WebEngineWallet() +{ + delete d; +} + +void WebEngineWallet::fillFormDataCallback(WebEnginePage* page, const WebEngineWallet::WebFormList& formsList) +{ + QList urlList; + if (!formsList.isEmpty()) { + const QUrl url(page->url()); + if (d->pendingFillRequests.contains(url)) { + qWarning() << "Duplicate request rejected!"; + } else { + WebEngineWalletPrivate::FormsData data; + data.page = QPointer(page); + data.forms << formsList; + d->pendingFillRequests.insert(url, data); + urlList << url; + } + } + + if (!urlList.isEmpty()) { + fillFormDataFromCache(urlList); + } + +} + +void WebEngineWallet::fillFormData(WebEnginePage *page) +{ + if (!page) return; + auto callback = [this, page](const WebFormList &forms){ + fillFormDataCallback(page, forms); + }; + d->withFormData(page, callback); +} + +static void createSaveKeyFor(WebEnginePage *page, QString *key) +{ + QUrl pageUrl(page->url()); + pageUrl.setPassword(QString()); + + QString keyStr = pageUrl.toString(); + + *key = QString::number(qHash(keyStr), 16); +} + +void WebEngineWallet::saveFormData(WebEnginePage *page, bool ignorePasswordFields) +{ + if (!page) { + return; + } + + QString key; + createSaveKeyFor(page, &key); + if (d->pendingSaveRequests.contains(key)) { + return; + } + + QUrl url = page->url(); + auto callback = [this, key, url](const WebFormList &list){saveFormDataCallback(key, url, list);}; + d->withFormData(page, callback, false, ignorePasswordFields); +} + +void WebEngineWallet::saveFormDataCallback(const QString &key, const QUrl& url, const WebEngineWallet::WebFormList& formsList) +{ + + if (formsList.isEmpty()) { + return; + } + + WebFormList list(formsList); + + d->pendingSaveRequests.insert(key, list); + + QMutableVectorIterator it(list); + while (it.hasNext()) { + const WebForm form(it.next()); + if (hasCachedFormData(form)) { + it.remove(); + } + } + + if (list.isEmpty()) { + d->confirmSaveRequestOverwrites.insert(url); + saveFormDataToCache(key); + return; + } + + emit saveFormDataRequested(key, url); +} + +void WebEngineWallet::removeFormData(WebEnginePage *page) +{ + if (page) { + auto callback = [this](const WebFormList &list){removeFormDataFromCache(list);}; + d->withFormData(page, callback); + } +} + +void WebEngineWallet::removeFormDataCallback(const WebFormList& list) +{ + removeFormDataFromCache(list); +} + + +void WebEngineWallet::removeFormData(const WebFormList &forms) +{ + d->pendingRemoveRequests << forms; + removeFormDataFromCache(forms); +} + +void WebEngineWallet::acceptSaveFormDataRequest(const QString &key) +{ + saveFormDataToCache(key); +} + +void WebEngineWallet::rejectSaveFormDataRequest(const QString &key) +{ + d->pendingSaveRequests.remove(key); +} + +void WebEngineWallet::fillWebForm(const QUrl &url, const WebEngineWallet::WebFormList &forms) +{ + QPointer page = d->pendingFillRequests.value(url).page; + if (!page) { + return; + } + + QString script; + bool wasFilled = false; + + Q_FOREACH (const WebEngineWallet::WebForm &form, forms) { + Q_FOREACH (const WebEngineWallet::WebForm::WebField &field, form.fields) { + QString value = field.second; + value.replace(QL1C('\\'), QL1S("\\\\")); + script+= QString(s_javascriptFillInputFragment) + .arg(form.framePath) + .arg((form.name.isEmpty() ? form.index : form.name)) + .arg(field.first).arg(value); + } + } + if (!script.isEmpty()) { + wasFilled = true; + auto callback = [wasFilled, this](const QVariant &){emit fillFormRequestCompleted(wasFilled);}; + page.data()->runJavaScript(script, callback); + } +} + +WebEngineWallet::WebFormList WebEngineWallet::formsToFill(const QUrl &url) const +{ + return d->pendingFillRequests.value(url).forms; +} + +WebEngineWallet::WebFormList WebEngineWallet::formsToSave(const QString &key) const +{ + return d->pendingSaveRequests.value(key); +} + +bool WebEngineWallet::hasCachedFormData(const WebForm &form) const +{ + return !KWallet::Wallet::keyDoesNotExist(KWallet::Wallet::NetworkWallet(), + KWallet::Wallet::FormDataFolder(), + walletKey(form)); +} + +void WebEngineWallet::fillFormDataFromCache(const QList &urlList) +{ + if (d->wallet) { + QListIterator urlIt(urlList); + while (urlIt.hasNext()) { + const QUrl url = urlIt.next(); + WebFormList list = formsToFill(url); + d->fillDataFromCache(list); + fillWebForm(url, list); + } + d->pendingFillRequests.clear(); + } + d->openWallet(); +} + +void WebEngineWallet::saveFormDataToCache(const QString &key) +{ + if (d->wallet) { + d->saveDataToCache(key); + return; + } + d->openWallet(); +} + +void WebEngineWallet::removeFormDataFromCache(const WebFormList &forms) +{ + if (d->wallet) { + d->removeDataFromCache(forms); + d->pendingRemoveRequests.clear(); + return; + } + d->openWallet(); +} + +#include "moc_webenginewallet.cpp"