File add-kwallet-support-to-webengine.patch of Package konqueror

From 56ea2a32d2f1d9f4e6c97b27080cf8144a932ee3 Mon Sep 17 00:00:00 2001
From: Stefano Crocco <stefano.crocco@alice.it>
Date: Sun, 14 Jan 2018 11:25:17 +0100
Subject: Add back KWallet support to Konqueror

Summary:
Added back the ability to store user names and passwords to KWallet and automatically fill them when loading a page.

I copied the files `kwebwallet.cpp` and `kwebwallet.h` from the `KDEWebKit` framework and modified it to adapt to `QWebEngine`. In particular, I needed to modify the way form data is passed from javascript to C++ serializing it using JSON and read it back using `QJsonDocument`. Also, since `QWebEnginePage` doesn't allow to access frames from the C++ side, I needed to remove all the code dealing with recurisve searches and implement it in javascript.

As the old code, this intentionally doesn't work with input elements with the `autocomplete` attribute set to `false`.

I've not modified the copyright information in the files I copied from `KDEWebKit` as I don't know which is the correct way to do so: I must make clear my files are based on the work on other people but at the same time that I've changed them: how do I do this?

Test Plan: I tried to access several sites requiring a login in, asking Konqueror to save the password, then logging out and visiting the page again. Except when the `autocomplete` attribute was `false` (checked by looking at the html code), Konqueror always saved and restored login information correctly.

Reviewers: dfaure

Reviewed By: dfaure

Differential Revision: https://phabricator.kde.org/D10178
---
 webenginepart/src/CMakeLists.txt           |   4 +
 webenginepart/src/webenginepage.cpp        |  14 +-
 webenginepart/src/webenginepage.h          |   7 +-
 webenginepart/src/webenginepart.cpp        |  62 +--
 webenginepart/src/webenginepart.h          |   5 +
 webenginepart/src/webenginepartfactory.cpp |   1 +
 webenginepart/src/webenginewallet.cpp      | 599 +++++++++++++++++++++++++++++
 webenginepart/src/webenginewallet.h        | 305 +++++++++++++++
 8 files changed, 964 insertions(+), 33 deletions(-)
 create mode 100644 webenginepart/src/webenginewallet.cpp
 create mode 100644 webenginepart/src/webenginewallet.h

diff --git a/webenginepart/src/CMakeLists.txt b/webenginepart/src/CMakeLists.txt
index bfac067..fffce88 100644
--- 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 @@ set(kwebenginepartlib_LIB_SRCS
     websslinfo.cpp
     webhistoryinterface.cpp
     webenginepartdownloadmanager.cpp
+    webenginewallet.cpp
     settings/webenginesettings.cpp
     settings/webengine_filter.cpp
     ui/searchbar.cpp
@@ -27,6 +30,7 @@ target_link_libraries(kwebenginepartlib Qt5::Core Qt5::DBus Qt5::Gui Qt5::Widget
     KF5::Parts
     KF5::SonnetCore
     KF5::WindowSystem  # for KUserTimestamp
+    KF5::Wallet
 )
 
 target_include_directories(kwebenginepartlib PUBLIC
diff --git a/webenginepart/src/webenginepage.cpp b/webenginepart/src/webenginepage.cpp
index 212e189..615e8f7 100644
--- 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 <QWebEngineSettings>
 #include <QWebEngineProfile>
 
@@ -72,7 +73,8 @@ WebEnginePage::WebEnginePage(WebEnginePart *part, QWidget *parent)
          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 @@ WebEnginePage::WebEnginePage(WebEnginePart *part, QWidget *parent)
         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 WebEnginePage::acceptNavigationRequest(const QUrl& url, NavigationType type
     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 @@ bool WebEnginePage::checkLinkSecurity(const QNetworkRequest &req, NavigationType
     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/webenginepage.h b/webenginepart/src/webenginepage.h
index 8fa0386..cb3d5ed 100644
--- a/webenginepart/src/webenginepage.h
+++ b/webenginepart/src/webenginepage.h
@@ -40,6 +40,7 @@ class WebSslInfo;
 class WebEnginePart;
 class QWebEngineDownloadItem;
 class KPasswdServerClient;
+class WebEngineWallet;
 
 class WebEnginePage : public QWebEnginePage
 {
@@ -64,6 +65,8 @@ public:
 
     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 @@ protected Q_SLOTS:
 
 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 @@ private:
     QPointer<WebEnginePart> m_part;
 
     QScopedPointer<KPasswdServerClient> m_passwdServerClient;
+    WebEngineWallet *m_wallet;
 };
 
 
@@ -158,6 +162,7 @@ private:
     KParts::WindowArgs m_windowArgs;
     WebWindowType m_type;
     bool m_createNewWindow;
+    WebEngineWallet* m_wallet; 
 };
 
 #endif // WEBENGINEPAGE_H
diff --git a/webenginepart/src/webenginepart.cpp b/webenginepart/src/webenginepart.cpp
index 10bbffc..66edce7 100644
--- 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 @@ WebEnginePart::WebEnginePart(QWidget *parentWidget, QObject *parent,
              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 @@ WebEnginePart::WebEnginePart(QWidget *parentWidget, QObject *parent,
 
     // Load plugins once we are fully ready
     loadPlugins();
+    setWallet(page()->wallet());
 }
 
 WebEnginePart::~WebEnginePart()
@@ -311,17 +314,25 @@ void WebEnginePart::connectWebEnginePageSignals(WebEnginePage* page)
                 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 @@ void WebEnginePart::slotLoadFinished (bool ok)
     }
     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::slotDeleteNonPasswordStorableSite()
 
 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 @@ void WebEnginePart::slotShowFeaturePermissionBar(QWebEnginePage::Feature feature
                 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<QBoxLayout*>(widget()->layout());
         if (lay)
             lay->insertWidget(0, m_featurePermissionBar);
@@ -862,19 +873,16 @@ void WebEnginePart::slotSaveFormDataRequested (const QString& key, const QUrl& u
 
     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/webenginepart.h b/webenginepart/src/webenginepart.h
index 6889e6d..91afa42 100644
--- a/webenginepart/src/webenginepart.h
+++ b/webenginepart/src/webenginepart.h
@@ -42,6 +42,7 @@ class PasswordBar;
 class FeaturePermissionBar;
 class KUrlLabel;
 class WebEngineBrowserExtension;
+class WebEngineWallet;
 
 /**
  * A KPart wrapper for the QtWebEngine's browser rendering engine.
@@ -98,6 +99,9 @@ public:
     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 @@ private:
     WebEngineBrowserExtension* m_browserExtension;
     KParts::StatusBarExtension* m_statusBarExtension;
     WebEngineView* m_webView;
+    WebEngineWallet* m_wallet;
 };
 
 #endif // WEBENGINEPART_H
diff --git a/webenginepart/src/webenginepartfactory.cpp b/webenginepart/src/webenginepartfactory.cpp
index 04853bd..7a877b4 100644
--- a/webenginepart/src/webenginepartfactory.cpp
+++ b/webenginepart/src/webenginepartfactory.cpp
@@ -24,6 +24,7 @@
 
 #include <QWidget>
 
+
 WebEngineFactory::~WebEngineFactory()
 {
     // kDebug() << this;
diff --git a/webenginepart/src/webenginewallet.cpp b/webenginepart/src/webenginewallet.cpp
new file mode 100644
index 0000000..f501b27
--- /dev/null
+++ b/webenginepart/src/webenginewallet.cpp
@@ -0,0 +1,599 @@
+/*
+ * This file is part of the KDE project.
+ *
+ * Copyright (C) 2009 Dawit Alemayehu <adawit@kde.org>
+ * Copyright (C) 2018 Stefano Crocco <stefano.crocco@alice.it>
+ * 
+ * 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 <KWallet>
+#include <QDebug>
+
+#include <QtCore/QSet>
+#include <QtCore/QHash>
+#include <QtCore/QFile>
+#include <QtCore/QPointer>
+#include <QtCore/QScopedPointer>
+#include <QJsonDocument>
+#include <qwindowdefs.h>
+
+#define QL1S(x)   QLatin1String(x)
+#define QL1C(x)   QLatin1Char(x)
+
+// Javascript used to extract/set data from <form> 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<WebEnginePage> page;
+        WebEngineWallet::WebFormList forms;
+    };
+
+    typedef std::function<void(const WebEngineWallet::WebFormList &)> 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<KWallet::Wallet> wallet;
+    WebEngineWallet::WebFormList pendingRemoveRequests;
+    QHash<QUrl, FormsData> pendingFillRequests;
+    QHash<QString, WebEngineWallet::WebFormList> pendingSaveRequests;
+    QSet<QUrl> 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<WebEngineWallet::WebForm::WebField> 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<QString, QString> cachedValues;
+    QMutableVectorIterator <WebForm> 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<WebEngineWallet::WebForm> formIt(list);
+
+        while (formIt.hasNext()) {
+            QMap<QString, QString> 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<WebEngineWallet::WebForm::WebField> 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<WebEngineWallet::WebForm::WebField> 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<WebForm> 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<QUrl> urlList;
+            QMutableHashIterator<QUrl, FormsData> 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<QString> 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<QUrl> 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<WebEnginePage>(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<WebForm> 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<WebEnginePage> 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<QUrl> &urlList)
+{
+    if (d->wallet) {
+        QListIterator<QUrl> 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"
diff --git a/webenginepart/src/webenginewallet.h b/webenginepart/src/webenginewallet.h
new file mode 100644
index 0000000..2c3d65b
--- /dev/null
+++ b/webenginepart/src/webenginewallet.h
@@ -0,0 +1,305 @@
+/*
+ * This file is part of the KDE project.
+ *
+ * Copyright (C) 2009 Dawit Alemayehu <adawit@kde.org>
+ * Copyright (C) 2018 Stefano Crocco <stefano.crocco@alice.it>
+ *
+ * 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 <QtCore/QObject>
+#include <QtCore/QString>
+#include <QtCore/QList>
+#include <QtCore/QPair>
+#include <QUrl>
+#include <QWidget>
+#include <QtCore/QtGlobal>
+
+class WebEnginePage;
+
+class WebEngineWallet : public QObject
+{
+    Q_OBJECT
+
+public:
+
+    /**
+     * Holds data from a HTML &lt;form&gt; element.
+     */
+    struct WebForm {
+        /**
+         * A typedef for storing the name and value attributes of HTML &lt;input&gt;
+         * elements.
+         */
+        typedef QPair<QString, QString> 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<WebField> fields;
+    };
+
+    /**
+     * A list of web forms
+     */
+    typedef QVector<WebForm> 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<QUrl> &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
-- 
cgit v0.11.2