Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/keepassxreboot/keepassxc.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'src/browser')
-rwxr-xr-xsrc/browser/BrowserAccessControlDialog.cpp59
-rwxr-xr-xsrc/browser/BrowserAccessControlDialog.h48
-rwxr-xr-xsrc/browser/BrowserAccessControlDialog.ui69
-rwxr-xr-xsrc/browser/BrowserAction.cpp554
-rwxr-xr-xsrc/browser/BrowserAction.h98
-rwxr-xr-xsrc/browser/BrowserClients.cpp77
-rwxr-xr-xsrc/browser/BrowserClients.h56
-rw-r--r--src/browser/BrowserEntryConfig.cpp109
-rw-r--r--src/browser/BrowserEntryConfig.h60
-rwxr-xr-xsrc/browser/BrowserOptionDialog.cpp141
-rwxr-xr-xsrc/browser/BrowserOptionDialog.h53
-rwxr-xr-xsrc/browser/BrowserOptionDialog.ui377
-rw-r--r--src/browser/BrowserService.cpp744
-rw-r--r--src/browser/BrowserService.h82
-rwxr-xr-xsrc/browser/BrowserSettings.cpp380
-rwxr-xr-xsrc/browser/BrowserSettings.h105
-rwxr-xr-xsrc/browser/CMakeLists.txt37
-rw-r--r--src/browser/HostInstaller.cpp240
-rw-r--r--src/browser/HostInstaller.h63
-rw-r--r--src/browser/NativeMessagingBase.cpp141
-rw-r--r--src/browser/NativeMessagingBase.h61
-rwxr-xr-xsrc/browser/NativeMessagingHost.cpp208
-rwxr-xr-xsrc/browser/NativeMessagingHost.h67
-rw-r--r--src/browser/Variant.cpp37
-rw-r--r--src/browser/Variant.h25
25 files changed, 3891 insertions, 0 deletions
diff --git a/src/browser/BrowserAccessControlDialog.cpp b/src/browser/BrowserAccessControlDialog.cpp
new file mode 100755
index 000000000..7090a4d16
--- /dev/null
+++ b/src/browser/BrowserAccessControlDialog.cpp
@@ -0,0 +1,59 @@
+/*
+* Copyright (C) 2013 Francois Ferrand
+* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
+*
+* This program is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 3 of the License, or
+* (at your option) any later version.
+*
+* This program 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 General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "BrowserAccessControlDialog.h"
+#include "ui_BrowserAccessControlDialog.h"
+#include "core/Entry.h"
+
+BrowserAccessControlDialog::BrowserAccessControlDialog(QWidget* parent) :
+ QDialog(parent),
+ ui(new Ui::BrowserAccessControlDialog())
+{
+ this->setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint);
+
+ ui->setupUi(this);
+ connect(ui->allowButton, SIGNAL(clicked()), this, SLOT(accept()));
+ connect(ui->denyButton, SIGNAL(clicked()), this, SLOT(reject()));
+}
+
+BrowserAccessControlDialog::~BrowserAccessControlDialog()
+{
+}
+
+void BrowserAccessControlDialog::setUrl(const QString& url)
+{
+ ui->label->setText(QString(tr("%1 has requested access to passwords for the following item(s).\n"
+ "Please select whether you want to allow access.")).arg(QUrl(url).host()));
+}
+
+void BrowserAccessControlDialog::setItems(const QList<Entry*>& items)
+{
+ for (Entry* entry : items) {
+ ui->itemsList->addItem(entry->title() + " - " + entry->username());
+ }
+}
+
+bool BrowserAccessControlDialog::remember() const
+{
+ return ui->rememberDecisionCheckBox->isChecked();
+}
+
+void BrowserAccessControlDialog::setRemember(bool r)
+{
+ ui->rememberDecisionCheckBox->setChecked(r);
+}
diff --git a/src/browser/BrowserAccessControlDialog.h b/src/browser/BrowserAccessControlDialog.h
new file mode 100755
index 000000000..d2a66ce0d
--- /dev/null
+++ b/src/browser/BrowserAccessControlDialog.h
@@ -0,0 +1,48 @@
+/*
+* Copyright (C) 2013 Francois Ferrand
+* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
+*
+* This program is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 3 of the License, or
+* (at your option) any later version.
+*
+* This program 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 General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef BROWSERACCESSCONTROLDIALOG_H
+#define BROWSERACCESSCONTROLDIALOG_H
+
+#include <QDialog>
+#include <QScopedPointer>
+
+class Entry;
+
+namespace Ui {
+class BrowserAccessControlDialog;
+}
+
+class BrowserAccessControlDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+ explicit BrowserAccessControlDialog(QWidget* parent = nullptr);
+ ~BrowserAccessControlDialog();
+
+ void setUrl(const QString& url);
+ void setItems(const QList<Entry*>& items);
+ bool remember() const;
+ void setRemember(bool r);
+
+private:
+ QScopedPointer<Ui::BrowserAccessControlDialog> ui;
+};
+
+#endif // BROWSERACCESSCONTROLDIALOG_H
diff --git a/src/browser/BrowserAccessControlDialog.ui b/src/browser/BrowserAccessControlDialog.ui
new file mode 100755
index 000000000..29715314d
--- /dev/null
+++ b/src/browser/BrowserAccessControlDialog.ui
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>BrowserAccessControlDialog</class>
+ <widget class="QDialog" name="BrowserAccessControlDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>221</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>KeePassXC-Browser Confirm Access</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QListWidget" name="itemsList"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="rememberDecisionCheckBox">
+ <property name="text">
+ <string>Remember this decision</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="allowButton">
+ <property name="text">
+ <string>Allow</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="denyButton">
+ <property name="text">
+ <string>Deny</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/browser/BrowserAction.cpp b/src/browser/BrowserAction.cpp
new file mode 100755
index 000000000..b15d8ed59
--- /dev/null
+++ b/src/browser/BrowserAction.cpp
@@ -0,0 +1,554 @@
+/*
+* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
+* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
+*
+* This program is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 3 of the License, or
+* (at your option) any later version.
+*
+* This program 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 General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <QJsonDocument>
+#include <QJsonParseError>
+#include "BrowserAction.h"
+#include "BrowserSettings.h"
+#include "sodium.h"
+#include "sodium/crypto_box.h"
+#include "sodium/randombytes.h"
+#include "config-keepassx.h"
+
+BrowserAction::BrowserAction(BrowserService& browserService) :
+ m_mutex(QMutex::Recursive),
+ m_browserService(browserService),
+ m_associated(false)
+{
+
+}
+
+QJsonObject BrowserAction::readResponse(const QJsonObject& json)
+{
+ if (json.isEmpty()) {
+ return QJsonObject();
+ }
+
+ bool triggerUnlock = false;
+ const QString trigger = json.value("triggerUnlock").toString();
+ if (!trigger.isEmpty() && trigger.compare("true", Qt::CaseSensitive) == 0) {
+ triggerUnlock = true;
+ }
+
+ const QString action = json.value("action").toString();
+ if (action.isEmpty()) {
+ return QJsonObject();
+ }
+
+ QMutexLocker locker(&m_mutex);
+ if (action.compare("change-public-keys", Qt::CaseSensitive) != 0 && !m_browserService.isDatabaseOpened()) {
+ if (m_clientPublicKey.isEmpty()) {
+ return getErrorReply(action, ERROR_KEEPASS_CLIENT_PUBLIC_KEY_NOT_RECEIVED);
+ } else if (!m_browserService.openDatabase(triggerUnlock)) {
+ return getErrorReply(action, ERROR_KEEPASS_DATABASE_NOT_OPENED);
+ }
+ }
+
+ return handleAction(json);
+}
+
+
+// Private functions
+///////////////////////
+
+QJsonObject BrowserAction::handleAction(const QJsonObject& json)
+{
+ QString action = json.value("action").toString();
+ if (action.isEmpty()) {
+ return getErrorReply(action, ERROR_KEEPASS_INCORRECT_ACTION);
+ }
+
+ if (action.compare("change-public-keys", Qt::CaseSensitive) == 0) {
+ return handleChangePublicKeys(json, action);
+ } else if (action.compare("get-databasehash", Qt::CaseSensitive) == 0) {
+ return handleGetDatabaseHash(json, action);
+ } else if (action.compare("associate", Qt::CaseSensitive) == 0) {
+ return handleAssociate(json, action);
+ } else if (action.compare("test-associate", Qt::CaseSensitive) == 0) {
+ return handleTestAssociate(json, action);
+ } else if (action.compare("get-logins", Qt::CaseSensitive) == 0) {
+ return handleGetLogins(json, action);
+ } else if (action.compare("generate-password", Qt::CaseSensitive) == 0) {
+ return handleGeneratePassword(json, action);
+ } else if (action.compare("set-login", Qt::CaseSensitive) == 0) {
+ return handleSetLogin(json, action);
+ } else if (action.compare("lock-database", Qt::CaseSensitive) == 0) {
+ return handleLockDatabase(json, action);
+ }
+
+ // Action was not recognized
+ return getErrorReply(action, ERROR_KEEPASS_INCORRECT_ACTION);
+}
+
+QJsonObject BrowserAction::handleChangePublicKeys(const QJsonObject& json, const QString& action)
+{
+ QMutexLocker locker(&m_mutex);
+ const QString nonce = json.value("nonce").toString();
+ const QString clientPublicKey = json.value("publicKey").toString();
+
+ if (clientPublicKey.isEmpty() || nonce.isEmpty()) {
+ return getErrorReply(action, ERROR_KEEPASS_CLIENT_PUBLIC_KEY_NOT_RECEIVED);
+ }
+
+ m_associated = false;
+ unsigned char pk[crypto_box_PUBLICKEYBYTES];
+ unsigned char sk[crypto_box_SECRETKEYBYTES];
+ crypto_box_keypair(pk, sk);
+
+ const QString publicKey = getBase64FromKey(pk, crypto_box_PUBLICKEYBYTES);
+ const QString secretKey = getBase64FromKey(sk, crypto_box_SECRETKEYBYTES);
+ m_clientPublicKey = clientPublicKey;
+ m_publicKey = publicKey;
+ m_secretKey = secretKey;
+
+ QJsonObject response = buildMessage(incrementNonce(nonce));
+ response["action"] = action;
+ response["publicKey"] = publicKey;
+
+ return response;
+}
+
+QJsonObject BrowserAction::handleGetDatabaseHash(const QJsonObject& json, const QString& action)
+{
+ const QString hash = getDatabaseHash();
+ const QString nonce = json.value("nonce").toString();
+ const QString encrypted = json.value("message").toString();
+ const QJsonObject decrypted = decryptMessage(encrypted, nonce, action);
+
+ if (decrypted.isEmpty()) {
+ return getErrorReply(action, ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE);
+ }
+
+ if (hash.isEmpty()) {
+ return getErrorReply(action, ERROR_KEEPASS_DATABASE_HASH_NOT_RECEIVED);
+ }
+
+ QString command = decrypted.value("action").toString();
+ if (!command.isEmpty() && command.compare("get-databasehash", Qt::CaseSensitive) == 0) {
+ const QString newNonce = incrementNonce(nonce);
+
+ QJsonObject message = buildMessage(newNonce);
+ message["hash"] = hash;
+ return buildResponse(action, message, newNonce);
+ }
+
+ return getErrorReply(action, ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE);
+}
+
+QJsonObject BrowserAction::handleAssociate(const QJsonObject& json, const QString& action)
+{
+ const QString hash = getDatabaseHash();
+ const QString nonce = json.value("nonce").toString();
+ const QString encrypted = json.value("message").toString();
+ const QJsonObject decrypted = decryptMessage(encrypted, nonce, action);
+
+ if (decrypted.isEmpty()) {
+ return getErrorReply(action, ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE);
+ }
+
+ const QString key = decrypted.value("key").toString();
+ if (key.isEmpty()) {
+ return getErrorReply(action, ERROR_KEEPASS_ASSOCIATION_FAILED);
+ }
+
+ QMutexLocker locker(&m_mutex);
+ if (key.compare(m_clientPublicKey, Qt::CaseSensitive) == 0) {
+ const QString id = m_browserService.storeKey(key);
+ if (id.isEmpty()) {
+ return getErrorReply(action, ERROR_KEEPASS_ACTION_CANCELLED_OR_DENIED);
+ }
+
+ m_associated = true;
+ const QString newNonce = incrementNonce(nonce);
+
+ QJsonObject message = buildMessage(newNonce);
+ message["hash"] = hash;
+ message["id"] = id;
+ return buildResponse(action, message, newNonce);
+ }
+
+ return getErrorReply(action, ERROR_KEEPASS_ASSOCIATION_FAILED);
+}
+
+QJsonObject BrowserAction::handleTestAssociate(const QJsonObject& json, const QString& action)
+{
+ const QString hash = getDatabaseHash();
+ const QString nonce = json.value("nonce").toString();
+ const QString encrypted = json.value("message").toString();
+ const QJsonObject decrypted = decryptMessage(encrypted, nonce, action);
+
+ if (decrypted.isEmpty()) {
+ return getErrorReply(action, ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE);
+ }
+
+ const QString responseKey = decrypted.value("key").toString();
+ const QString id = decrypted.value("id").toString();
+ if (responseKey.isEmpty() || id.isEmpty()) {
+ return getErrorReply(action, ERROR_KEEPASS_DATABASE_NOT_OPENED);
+ }
+
+ QMutexLocker locker(&m_mutex);
+ const QString key = m_browserService.getKey(id);
+ if (key.isEmpty() || key.compare(responseKey, Qt::CaseSensitive) != 0) {
+ return getErrorReply(action, ERROR_KEEPASS_ASSOCIATION_FAILED);
+ }
+
+ m_associated = true;
+ const QString newNonce = incrementNonce(nonce);
+
+ QJsonObject message = buildMessage(newNonce);
+ message["hash"] = hash;
+ message["id"] = id;
+
+ return buildResponse(action, message, newNonce);
+}
+
+QJsonObject BrowserAction::handleGetLogins(const QJsonObject& json, const QString& action)
+{
+ const QString hash = getDatabaseHash();
+ const QString nonce = json.value("nonce").toString();
+ const QString encrypted = json.value("message").toString();
+
+ QMutexLocker locker(&m_mutex);
+ if (!m_associated) {
+ return getErrorReply(action, ERROR_KEEPASS_ASSOCIATION_FAILED);
+ }
+
+ const QJsonObject decrypted = decryptMessage(encrypted, nonce, action);
+ if (decrypted.isEmpty()) {
+ return getErrorReply(action, ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE);
+ }
+
+ const QString url = decrypted.value("url").toString();
+ if (url.isEmpty()) {
+ return getErrorReply(action, ERROR_KEEPASS_NO_URL_PROVIDED);
+ }
+
+ const QString id = decrypted.value("id").toString();
+ const QString submit = decrypted.value("submitUrl").toString();
+ const QJsonArray users = m_browserService.findMatchingEntries(id, url, submit, "");
+
+ if (users.isEmpty()) {
+ return getErrorReply(action, ERROR_KEEPASS_NO_LOGINS_FOUND);
+ }
+
+ const QString newNonce = incrementNonce(nonce);
+
+ QJsonObject message = buildMessage(newNonce);
+ message["count"] = users.count();
+ message["entries"] = users;
+ message["hash"] = hash;
+ message["id"] = id;
+
+ return buildResponse(action, message, newNonce);
+}
+
+QJsonObject BrowserAction::handleGeneratePassword(const QJsonObject& json, const QString& action)
+{
+ const QString nonce = json.value("nonce").toString();
+ const QString password = BrowserSettings::generatePassword();
+ const QString bits = QString::number(BrowserSettings::getbits()); // For some reason this always returns 1140 bits?
+
+ if (nonce.isEmpty() || password.isEmpty()) {
+ return QJsonObject();
+ }
+
+ QJsonArray arr;
+ QJsonObject passwd;
+ passwd["login"] = QString::number(password.length() * 8); //bits;
+ passwd["password"] = password;
+ arr.append(passwd);
+
+ const QString newNonce = incrementNonce(nonce);
+
+ QJsonObject message = buildMessage(newNonce);
+ message["entries"] = arr;
+
+ return buildResponse(action, message, newNonce);
+}
+
+QJsonObject BrowserAction::handleSetLogin(const QJsonObject& json, const QString& action)
+{
+ const QString hash = getDatabaseHash();
+ const QString nonce = json.value("nonce").toString();
+ const QString encrypted = json.value("message").toString();
+
+ QMutexLocker locker(&m_mutex);
+ if (!m_associated) {
+ return getErrorReply(action, ERROR_KEEPASS_ASSOCIATION_FAILED);
+ }
+
+ const QJsonObject decrypted = decryptMessage(encrypted, nonce, action);
+ if (decrypted.isEmpty()) {
+ return getErrorReply(action, ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE);
+ }
+
+ const QString url = decrypted.value("url").toString();
+ if (url.isEmpty()) {
+ return getErrorReply(action, ERROR_KEEPASS_NO_URL_PROVIDED);
+ }
+
+ const QString id = decrypted.value("id").toString();
+ const QString login = decrypted.value("login").toString();
+ const QString password = decrypted.value("password").toString();
+ const QString submitUrl = decrypted.value("submitUrl").toString();
+ const QString uuid = decrypted.value("uuid").toString();
+ const QString realm;
+
+ if (uuid.isEmpty()) {
+ m_browserService.addEntry(id, login, password, url, submitUrl, realm);
+ } else {
+ m_browserService.updateEntry(id, uuid, login, password, url);
+ }
+
+ const QString newNonce = incrementNonce(nonce);
+
+ QJsonObject message = buildMessage(newNonce);
+ message["count"] = QJsonValue::Null;
+ message["entries"] = QJsonValue::Null;
+ message["error"] = "";
+ message["hash"] = hash;
+
+ return buildResponse(action, message, newNonce);
+}
+
+QJsonObject BrowserAction::handleLockDatabase(const QJsonObject& json, const QString& action)
+{
+ const QString hash = getDatabaseHash();
+ const QString nonce = json.value("nonce").toString();
+ const QString encrypted = json.value("message").toString();
+ const QJsonObject decrypted = decryptMessage(encrypted, nonce, action);
+
+ if (decrypted.isEmpty()) {
+ return getErrorReply(action, ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE);
+ }
+
+ if (hash.isEmpty()) {
+ return getErrorReply(action, ERROR_KEEPASS_DATABASE_HASH_NOT_RECEIVED);
+ }
+
+ QString command = decrypted.value("action").toString();
+ if (!command.isEmpty() && command.compare("lock-database", Qt::CaseSensitive) == 0) {
+ QMutexLocker locker(&m_mutex);
+ m_browserService.lockDatabase();
+
+ const QString newNonce = incrementNonce(nonce);
+ QJsonObject message = buildMessage(newNonce);
+
+ return buildResponse(action, message, newNonce);
+ }
+
+ return getErrorReply(action, ERROR_KEEPASS_DATABASE_HASH_NOT_RECEIVED);
+}
+
+QJsonObject BrowserAction::getErrorReply(const QString& action, const int errorCode) const
+{
+ QJsonObject response;
+ response["action"] = action;
+ response["errorCode"] = QString::number(errorCode);
+ response["error"] = getErrorMessage(errorCode);
+ return response;
+}
+
+QJsonObject BrowserAction::buildMessage(const QString& nonce) const
+{
+ QJsonObject message;
+ message["version"] = KEEPASSX_VERSION;
+ message["success"] = "true";
+ message["nonce"] = nonce;
+ return message;
+}
+
+QJsonObject BrowserAction::buildResponse(const QString& action, const QJsonObject& message, const QString& nonce)
+{
+ QJsonObject response;
+ response["action"] = action;
+ response["message"] = encryptMessage(message, nonce);
+ response["nonce"] = nonce;
+ return response;
+}
+
+QString BrowserAction::getErrorMessage(const int errorCode) const
+{
+ switch (errorCode) {
+ case ERROR_KEEPASS_DATABASE_NOT_OPENED: return QObject::tr("Database not opened");
+ case ERROR_KEEPASS_DATABASE_HASH_NOT_RECEIVED: return QObject::tr("Database hash not available");
+ case ERROR_KEEPASS_CLIENT_PUBLIC_KEY_NOT_RECEIVED: return QObject::tr("Client public key not received");
+ case ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE: return QObject::tr("Cannot decrypt message");
+ case ERROR_KEEPASS_TIMEOUT_OR_NOT_CONNECTED: return QObject::tr("Timeout or cannot connect to KeePassXC");
+ case ERROR_KEEPASS_ACTION_CANCELLED_OR_DENIED: return QObject::tr("Action cancelled or denied");
+ case ERROR_KEEPASS_CANNOT_ENCRYPT_MESSAGE: return QObject::tr("Cannot encrypt message or public key not found. Is Native Messaging enabled in KeePassXC?");
+ case ERROR_KEEPASS_ASSOCIATION_FAILED: return QObject::tr("KeePassXC association failed, try again");
+ case ERROR_KEEPASS_KEY_CHANGE_FAILED: return QObject::tr("Key change was not successful");
+ case ERROR_KEEPASS_ENCRYPTION_KEY_UNRECOGNIZED: return QObject::tr("Encryption key is not recognized");
+ case ERROR_KEEPASS_NO_SAVED_DATABASES_FOUND: return QObject::tr("No saved databases found");
+ case ERROR_KEEPASS_INCORRECT_ACTION: return QObject::tr("Incorrect action");
+ case ERROR_KEEPASS_EMPTY_MESSAGE_RECEIVED: return QObject::tr("Empty message received");
+ case ERROR_KEEPASS_NO_URL_PROVIDED: return QObject::tr("No URL provided");
+ case ERROR_KEEPASS_NO_LOGINS_FOUND: return QObject::tr("No logins found");
+ default: return QObject::tr("Unknown error");
+ }
+}
+
+QString BrowserAction::getDatabaseHash()
+{
+ QMutexLocker locker(&m_mutex);
+ QByteArray hash = QCryptographicHash::hash(
+ (m_browserService.getDatabaseRootUuid() + m_browserService.getDatabaseRecycleBinUuid()).toUtf8(),
+ QCryptographicHash::Sha256).toHex();
+ return QString(hash);
+}
+
+QString BrowserAction::encryptMessage(const QJsonObject& message, const QString& nonce)
+{
+ if (message.isEmpty() || nonce.isEmpty()) {
+ return QString();
+ }
+
+ const QString reply(QJsonDocument(message).toJson());
+ if (!reply.isEmpty()) {
+ return encrypt(reply, nonce);
+ }
+
+ return QString();
+}
+
+QJsonObject BrowserAction::decryptMessage(const QString& message, const QString& nonce, const QString& action)
+{
+ if (message.isEmpty() || nonce.isEmpty()) {
+ return QJsonObject();
+ }
+
+ QByteArray ba = decrypt(message, nonce);
+ if (!ba.isEmpty()) {
+ return getJsonObject(ba);
+ }
+
+ return getErrorReply(action, ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE);
+}
+
+QString BrowserAction::encrypt(const QString plaintext, const QString nonce)
+{
+ QMutexLocker locker(&m_mutex);
+ const QByteArray ma = plaintext.toUtf8();
+ const QByteArray na = base64Decode(nonce);
+ const QByteArray ca = base64Decode(m_clientPublicKey);
+ const QByteArray sa = base64Decode(m_secretKey);
+
+ std::vector<unsigned char> m(ma.cbegin(), ma.cend());
+ std::vector<unsigned char> n(na.cbegin(), na.cend());
+ std::vector<unsigned char> ck(ca.cbegin(), ca.cend());
+ std::vector<unsigned char> sk(sa.cbegin(), sa.cend());
+
+ std::vector<unsigned char> e;
+ e.resize(max_length);
+
+ if (m.empty() || n.empty() || ck.empty() || sk.empty()) {
+ return QString();
+ }
+
+ if (crypto_box_easy(e.data(), m.data(), m.size(), n.data(), ck.data(), sk.data()) == 0) {
+ QByteArray res = getQByteArray(e.data(), (crypto_box_MACBYTES + ma.length()));
+ return res.toBase64();
+ }
+
+ return QString();
+}
+
+QByteArray BrowserAction::decrypt(const QString encrypted, const QString nonce)
+{
+ QMutexLocker locker(&m_mutex);
+ const QByteArray ma = base64Decode(encrypted);
+ const QByteArray na = base64Decode(nonce);
+ const QByteArray ca = base64Decode(m_clientPublicKey);
+ const QByteArray sa = base64Decode(m_secretKey);
+
+ std::vector<unsigned char> m(ma.cbegin(), ma.cend());
+ std::vector<unsigned char> n(na.cbegin(), na.cend());
+ std::vector<unsigned char> ck(ca.cbegin(), ca.cend());
+ std::vector<unsigned char> sk(sa.cbegin(), sa.cend());
+
+ std::vector<unsigned char> d;
+ d.resize(max_length);
+
+ if (m.empty() || n.empty() || ck.empty() || sk.empty()) {
+ return QByteArray();
+ }
+
+ if (crypto_box_open_easy(d.data(), m.data(), ma.length(), n.data(), ck.data(), sk.data()) == 0) {
+ return getQByteArray(d.data(), std::char_traits<char>::length(reinterpret_cast<const char *>(d.data())));
+ }
+
+ return QByteArray();
+}
+
+QString BrowserAction::getBase64FromKey(const uchar* array, const uint len)
+{
+ return getQByteArray(array, len).toBase64();
+}
+
+QByteArray BrowserAction::getQByteArray(const uchar* array, const uint len) const
+{
+ QByteArray qba;
+ qba.reserve(len);
+ for (uint i = 0; i < len; ++i) {
+ qba.append(static_cast<char>(array[i]));
+ }
+ return qba;
+}
+
+QJsonObject BrowserAction::getJsonObject(const uchar* pArray, const uint len) const
+{
+ QByteArray arr = getQByteArray(pArray, len);
+ QJsonParseError err;
+ QJsonDocument doc(QJsonDocument::fromJson(arr, &err));
+ return doc.object();
+}
+
+QJsonObject BrowserAction::getJsonObject(const QByteArray ba) const
+{
+ QJsonParseError err;
+ QJsonDocument doc(QJsonDocument::fromJson(ba, &err));
+ return doc.object();
+}
+
+QByteArray BrowserAction::base64Decode(const QString str)
+{
+ return QByteArray::fromBase64(str.toUtf8());
+}
+
+QString BrowserAction::incrementNonce(const QString& nonce)
+{
+ const QByteArray nonceArray = base64Decode(nonce);
+ std::vector<unsigned char> n(nonceArray.cbegin(), nonceArray.cend());
+
+ sodium_increment(n.data(), n.size());
+ return getQByteArray(n.data(), n.size()).toBase64();
+}
+
+void BrowserAction::removeSharedEncryptionKeys()
+{
+ QMutexLocker locker(&m_mutex);
+ m_browserService.removeSharedEncryptionKeys();
+}
+
+void BrowserAction::removeStoredPermissions()
+{
+ QMutexLocker locker(&m_mutex);
+ m_browserService.removeStoredPermissions();
+}
diff --git a/src/browser/BrowserAction.h b/src/browser/BrowserAction.h
new file mode 100755
index 000000000..c4d59d3c9
--- /dev/null
+++ b/src/browser/BrowserAction.h
@@ -0,0 +1,98 @@
+/*
+* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
+* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
+*
+* This program is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 3 of the License, or
+* (at your option) any later version.
+*
+* This program 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 General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef BROWSERACTION_H
+#define BROWSERACTION_H
+
+#include <QtCore>
+#include <QObject>
+#include <QJsonObject>
+#include <QMutex>
+#include "BrowserService.h"
+
+class BrowserAction : public QObject
+{
+ Q_OBJECT
+
+ enum {
+ ERROR_KEEPASS_DATABASE_NOT_OPENED = 1,
+ ERROR_KEEPASS_DATABASE_HASH_NOT_RECEIVED = 2,
+ ERROR_KEEPASS_CLIENT_PUBLIC_KEY_NOT_RECEIVED = 3,
+ ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE = 4,
+ ERROR_KEEPASS_TIMEOUT_OR_NOT_CONNECTED = 5,
+ ERROR_KEEPASS_ACTION_CANCELLED_OR_DENIED = 6,
+ ERROR_KEEPASS_CANNOT_ENCRYPT_MESSAGE = 7,
+ ERROR_KEEPASS_ASSOCIATION_FAILED = 8,
+ ERROR_KEEPASS_KEY_CHANGE_FAILED = 9,
+ ERROR_KEEPASS_ENCRYPTION_KEY_UNRECOGNIZED = 10,
+ ERROR_KEEPASS_NO_SAVED_DATABASES_FOUND = 11,
+ ERROR_KEEPASS_INCORRECT_ACTION = 12,
+ ERROR_KEEPASS_EMPTY_MESSAGE_RECEIVED = 13,
+ ERROR_KEEPASS_NO_URL_PROVIDED = 14,
+ ERROR_KEEPASS_NO_LOGINS_FOUND = 15
+ };
+
+public:
+ BrowserAction(BrowserService& browserService);
+ ~BrowserAction() = default;
+
+ QJsonObject readResponse(const QJsonObject& json);
+
+public slots:
+ void removeSharedEncryptionKeys();
+ void removeStoredPermissions();
+
+private:
+ QJsonObject handleAction(const QJsonObject& json);
+ QJsonObject handleChangePublicKeys(const QJsonObject& json, const QString& action);
+ QJsonObject handleGetDatabaseHash(const QJsonObject& json, const QString& action);
+ QJsonObject handleAssociate(const QJsonObject& json, const QString& action);
+ QJsonObject handleTestAssociate(const QJsonObject& json, const QString& action);
+ QJsonObject handleGetLogins(const QJsonObject& json, const QString& action);
+ QJsonObject handleGeneratePassword(const QJsonObject& json, const QString& action);
+ QJsonObject handleSetLogin(const QJsonObject& json, const QString& action);
+ QJsonObject handleLockDatabase(const QJsonObject& json, const QString& action);
+
+ QJsonObject buildMessage(const QString& nonce) const;
+ QJsonObject buildResponse(const QString& action, const QJsonObject& message, const QString& nonce);
+ QJsonObject getErrorReply(const QString& action, const int errorCode) const;
+ QString getErrorMessage(const int errorCode) const;
+ QString getDatabaseHash();
+
+ QString encryptMessage(const QJsonObject& message, const QString& nonce);
+ QJsonObject decryptMessage(const QString& message, const QString& nonce, const QString& action = QString());
+ QString encrypt(const QString plaintext, const QString nonce);
+ QByteArray decrypt(const QString encrypted, const QString nonce);
+
+ QString getBase64FromKey(const uchar* array, const uint len);
+ QByteArray getQByteArray(const uchar* array, const uint len) const;
+ QJsonObject getJsonObject(const uchar* pArray, const uint len) const;
+ QJsonObject getJsonObject(const QByteArray ba) const;
+ QByteArray base64Decode(const QString str);
+ QString incrementNonce(const QString& nonce);
+
+private:
+ QMutex m_mutex;
+ BrowserService& m_browserService;
+ QString m_clientPublicKey;
+ QString m_publicKey;
+ QString m_secretKey;
+ bool m_associated;
+};
+
+#endif // BROWSERACTION_H
diff --git a/src/browser/BrowserClients.cpp b/src/browser/BrowserClients.cpp
new file mode 100755
index 000000000..9c47fdd46
--- /dev/null
+++ b/src/browser/BrowserClients.cpp
@@ -0,0 +1,77 @@
+/*
+* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
+* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
+*
+* This program is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 3 of the License, or
+* (at your option) any later version.
+*
+* This program 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 General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <QJsonValue>
+#include <QJsonParseError>
+#include "BrowserClients.h"
+
+BrowserClients::BrowserClients(BrowserService& browserService) :
+ m_mutex(QMutex::Recursive),
+ m_browserService(browserService)
+{
+ m_clients.reserve(1000);
+}
+
+QJsonObject BrowserClients::readResponse(const QByteArray& arr)
+{
+ QJsonObject json;
+ const QJsonObject message = byteArrayToJson(arr);
+ const QString clientID = getClientID(message);
+
+ if (!clientID.isEmpty()) {
+ const ClientPtr client = getClient(clientID);
+ if (client->browserAction) {
+ json = client->browserAction->readResponse(message);
+ }
+ }
+
+ return json;
+}
+
+QJsonObject BrowserClients::byteArrayToJson(const QByteArray& arr) const
+{
+ QJsonObject json;
+ QJsonParseError err;
+ QJsonDocument doc(QJsonDocument::fromJson(arr, &err));
+ if (doc.isObject()) {
+ json = doc.object();
+ }
+
+ return json;
+}
+
+QString BrowserClients::getClientID(const QJsonObject& json) const
+{
+ return json["clientID"].toString();
+}
+
+BrowserClients::ClientPtr BrowserClients::getClient(const QString& clientID)
+{
+ QMutexLocker locker(&m_mutex);
+ for (const auto &i : m_clients) {
+ if (i->clientID.compare(clientID, Qt::CaseSensitive) == 0) {
+ return i;
+ }
+ }
+
+ // clientID not found, create a new client
+ QSharedPointer<BrowserAction> ba = QSharedPointer<BrowserAction>::create(m_browserService);
+ ClientPtr client = ClientPtr::create(clientID, ba);
+ m_clients.push_back(client);
+ return m_clients.back();
+}
diff --git a/src/browser/BrowserClients.h b/src/browser/BrowserClients.h
new file mode 100755
index 000000000..f9052e7de
--- /dev/null
+++ b/src/browser/BrowserClients.h
@@ -0,0 +1,56 @@
+/*
+* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
+* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
+*
+* This program is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 3 of the License, or
+* (at your option) any later version.
+*
+* This program 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 General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef BROWSERCLIENTS_H
+#define BROWSERCLIENTS_H
+
+#include <QJsonObject>
+#include <QMutex>
+#include <QVector>
+#include <QSharedPointer>
+#include <QLocalSocket>
+#include "BrowserAction.h"
+
+class BrowserClients
+{
+ struct Client {
+ Client(const QString& id, QSharedPointer<BrowserAction> ba) : clientID(id), browserAction(ba) {}
+ QString clientID;
+ QSharedPointer<BrowserAction> browserAction;
+ };
+
+ typedef QSharedPointer<Client> ClientPtr;
+
+public:
+ BrowserClients(BrowserService& browserService);
+ ~BrowserClients() = default;
+
+ QJsonObject readResponse(const QByteArray& arr);
+
+private:
+ QJsonObject byteArrayToJson(const QByteArray& arr) const;
+ QString getClientID(const QJsonObject& json) const;
+ ClientPtr getClient(const QString& clientID);
+
+private:
+ QMutex m_mutex;
+ QVector<ClientPtr> m_clients;
+ BrowserService& m_browserService;
+};
+
+#endif // BROWSERCLIENTS_H
diff --git a/src/browser/BrowserEntryConfig.cpp b/src/browser/BrowserEntryConfig.cpp
new file mode 100644
index 000000000..36d0c7339
--- /dev/null
+++ b/src/browser/BrowserEntryConfig.cpp
@@ -0,0 +1,109 @@
+/*
+* Copyright (C) 2013 Francois Ferrand
+* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
+*
+* This program is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 3 of the License, or
+* (at your option) any later version.
+*
+* This program 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 General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "BrowserEntryConfig.h"
+#include <QtCore>
+#include "core/Entry.h"
+#include "core/EntryAttributes.h"
+
+static const char KEEPASSBROWSER_NAME[] = "KeePassXC-Browser Settings";
+
+
+BrowserEntryConfig::BrowserEntryConfig(QObject* parent) :
+ QObject(parent)
+{
+}
+
+QStringList BrowserEntryConfig::allowedHosts() const
+{
+ return m_allowedHosts.toList();
+}
+
+void BrowserEntryConfig::setAllowedHosts(const QStringList& allowedHosts)
+{
+ m_allowedHosts = allowedHosts.toSet();
+}
+
+QStringList BrowserEntryConfig::deniedHosts() const
+{
+ return m_deniedHosts.toList();
+}
+
+void BrowserEntryConfig::setDeniedHosts(const QStringList& deniedHosts)
+{
+ m_deniedHosts = deniedHosts.toSet();
+}
+
+bool BrowserEntryConfig::isAllowed(const QString& host) const
+{
+ return m_allowedHosts.contains(host);
+}
+
+void BrowserEntryConfig::allow(const QString& host)
+{
+ m_allowedHosts.insert(host);
+ m_deniedHosts.remove(host);
+}
+
+bool BrowserEntryConfig::isDenied(const QString& host) const
+{
+ return m_deniedHosts.contains(host);
+}
+
+void BrowserEntryConfig::deny(const QString& host)
+{
+ m_deniedHosts.insert(host);
+ m_allowedHosts.remove(host);
+}
+
+QString BrowserEntryConfig::realm() const
+{
+ return m_realm;
+}
+
+void BrowserEntryConfig::setRealm(const QString& realm)
+{
+ m_realm = realm;
+}
+
+bool BrowserEntryConfig::load(const Entry* entry)
+{
+ QString s = entry->attributes()->value(KEEPASSBROWSER_NAME);
+ if (s.isEmpty()) {
+ return false;
+ }
+
+ QJsonDocument doc = QJsonDocument::fromJson(s.toUtf8());
+ if (doc.isNull()) {
+ return false;
+ }
+
+ QVariantMap map = doc.object().toVariantMap();
+ for (QVariantMap::const_iterator iter = map.cbegin(); iter != map.cend(); ++iter) {
+ setProperty(iter.key().toLatin1(), iter.value());
+ }
+ return true;
+}
+
+void BrowserEntryConfig::save(Entry* entry)
+{
+ QVariantMap v = qo2qv(this);
+ QJsonObject o = QJsonObject::fromVariantMap(v);
+ QByteArray json = QJsonDocument(o).toJson(QJsonDocument::Compact);
+ entry->attributes()->set(KEEPASSBROWSER_NAME, json);
+}
diff --git a/src/browser/BrowserEntryConfig.h b/src/browser/BrowserEntryConfig.h
new file mode 100644
index 000000000..0ffaf985b
--- /dev/null
+++ b/src/browser/BrowserEntryConfig.h
@@ -0,0 +1,60 @@
+/*
+* Copyright (C) 2013 Francois Ferrand
+* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
+*
+* This program is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 3 of the License, or
+* (at your option) any later version.
+*
+* This program 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 General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef BROWSERENTRYCONFIG_H
+#define BROWSERENTRYCONFIG_H
+
+#include <QtCore/QObject>
+#include <QtCore/QString>
+#include <QtCore/QStringList>
+#include <QtCore/QSet>
+#include "Variant.h"
+
+class Entry;
+
+class BrowserEntryConfig : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(QStringList Allow READ allowedHosts WRITE setAllowedHosts)
+ Q_PROPERTY(QStringList Deny READ deniedHosts WRITE setDeniedHosts )
+ Q_PROPERTY(QString Realm READ realm WRITE setRealm )
+
+public:
+ BrowserEntryConfig(QObject* object = 0);
+
+ bool load(const Entry* entry);
+ void save(Entry* entry);
+ bool isAllowed(const QString& host) const;
+ void allow(const QString& host);
+ bool isDenied(const QString& host) const;
+ void deny(const QString& host);
+ QString realm() const;
+ void setRealm(const QString& realm);
+
+private:
+ QStringList allowedHosts() const;
+ void setAllowedHosts(const QStringList& allowedHosts);
+ QStringList deniedHosts() const;
+ void setDeniedHosts(const QStringList& deniedHosts);
+
+ QSet<QString> m_allowedHosts;
+ QSet<QString> m_deniedHosts;
+ QString m_realm;
+};
+
+#endif // BROWSERENTRYCONFIG_H
diff --git a/src/browser/BrowserOptionDialog.cpp b/src/browser/BrowserOptionDialog.cpp
new file mode 100755
index 000000000..693e62d26
--- /dev/null
+++ b/src/browser/BrowserOptionDialog.cpp
@@ -0,0 +1,141 @@
+/*
+* Copyright (C) 2013 Francois Ferrand
+* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
+* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
+*
+* This program is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 3 of the License, or
+* (at your option) any later version.
+*
+* This program 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 General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "BrowserOptionDialog.h"
+#include "ui_BrowserOptionDialog.h"
+#include "config-keepassx.h"
+#include "BrowserSettings.h"
+#include "core/FilePath.h"
+
+#include <QMessageBox>
+#include <QFileDialog>
+
+BrowserOptionDialog::BrowserOptionDialog(QWidget* parent) :
+ QWidget(parent),
+ m_ui(new Ui::BrowserOptionDialog())
+{
+ m_ui->setupUi(this);
+ connect(m_ui->removeSharedEncryptionKeys, SIGNAL(clicked()), this, SIGNAL(removeSharedEncryptionKeys()));
+ connect(m_ui->removeStoredPermissions, SIGNAL(clicked()), this, SIGNAL(removeStoredPermissions()));
+
+ m_ui->warningWidget->showMessage(tr("<b>Warning:</b> The following options can be dangerous!"), MessageWidget::Warning);
+ m_ui->warningWidget->setCloseButtonVisible(false);
+ m_ui->warningWidget->setAutoHideTimeout(-1);
+
+ m_ui->tabWidget->setEnabled(m_ui->enableBrowserSupport->isChecked());
+ connect(m_ui->enableBrowserSupport, SIGNAL(toggled(bool)), m_ui->tabWidget, SLOT(setEnabled(bool)));
+
+ m_ui->customProxyLocation->setEnabled(m_ui->useCustomProxy->isChecked());
+ m_ui->customProxyLocationBrowseButton->setEnabled(m_ui->useCustomProxy->isChecked());
+ connect(m_ui->useCustomProxy, SIGNAL(toggled(bool)), m_ui->customProxyLocation, SLOT(setEnabled(bool)));
+ connect(m_ui->useCustomProxy, SIGNAL(toggled(bool)), m_ui->customProxyLocationBrowseButton, SLOT(setEnabled(bool)));
+ connect(m_ui->customProxyLocationBrowseButton, SIGNAL(clicked()), this, SLOT(showProxyLocationFileDialog()));
+
+ m_ui->browserGlobalWarningWidget->setVisible(false);
+}
+
+BrowserOptionDialog::~BrowserOptionDialog()
+{
+}
+
+void BrowserOptionDialog::loadSettings()
+{
+ BrowserSettings settings;
+ m_ui->enableBrowserSupport->setChecked(settings.isEnabled());
+
+ m_ui->showNotification->setChecked(settings.showNotification());
+ m_ui->bestMatchOnly->setChecked(settings.bestMatchOnly());
+ m_ui->unlockDatabase->setChecked(settings.unlockDatabase());
+ m_ui->matchUrlScheme->setChecked(settings.matchUrlScheme());
+
+ // hide unimplemented options
+ // TODO: fix this
+ m_ui->showNotification->hide();
+ m_ui->bestMatchOnly->hide();
+
+ if (settings.sortByUsername()) {
+ m_ui->sortByUsername->setChecked(true);
+ } else {
+ m_ui->sortByTitle->setChecked(true);
+ }
+
+ m_ui->alwaysAllowAccess->setChecked(settings.alwaysAllowAccess());
+ m_ui->alwaysAllowUpdate->setChecked(settings.alwaysAllowUpdate());
+ m_ui->searchInAllDatabases->setChecked(settings.searchInAllDatabases());
+ m_ui->supportKphFields->setChecked(settings.supportKphFields());
+ m_ui->supportBrowserProxy->setChecked(settings.supportBrowserProxy());
+ m_ui->useCustomProxy->setChecked(settings.useCustomProxy());
+ m_ui->customProxyLocation->setText(settings.customProxyLocation());
+ m_ui->updateBinaryPath->setChecked(settings.updateBinaryPath());
+ m_ui->chromeSupport->setChecked(settings.chromeSupport());
+ m_ui->chromiumSupport->setChecked(settings.chromiumSupport());
+ m_ui->firefoxSupport->setChecked(settings.firefoxSupport());
+ m_ui->vivaldiSupport->setChecked(settings.vivaldiSupport());
+
+#if defined(KEEPASSXC_DIST_APPIMAGE)
+ m_ui->supportBrowserProxy->setChecked(true);
+ m_ui->supportBrowserProxy->setEnabled(false);
+#elif defined(KEEPASSXC_DIST_SNAP)
+ m_ui->enableBrowserSupport->setChecked(false);
+ m_ui->enableBrowserSupport->setEnabled(false);
+ m_ui->browserGlobalWarningWidget->showMessage(
+ tr("We're sorry, but KeePassXC-Browser is not supported for Snap releases at the moment."), MessageWidget::Warning);
+ m_ui->browserGlobalWarningWidget->setCloseButtonVisible(false);
+ m_ui->browserGlobalWarningWidget->setAutoHideTimeout(-1);
+#endif
+}
+
+void BrowserOptionDialog::saveSettings()
+{
+ BrowserSettings settings;
+ settings.setEnabled(m_ui->enableBrowserSupport->isChecked());
+ settings.setShowNotification(m_ui->showNotification->isChecked());
+ settings.setBestMatchOnly(m_ui->bestMatchOnly->isChecked());
+ settings.setUnlockDatabase(m_ui->unlockDatabase->isChecked());
+ settings.setMatchUrlScheme(m_ui->matchUrlScheme->isChecked());
+ settings.setSortByUsername(m_ui->sortByUsername->isChecked());
+
+ settings.setSupportBrowserProxy(m_ui->supportBrowserProxy->isChecked());
+ settings.setUseCustomProxy(m_ui->useCustomProxy->isChecked());
+ settings.setCustomProxyLocation(m_ui->customProxyLocation->text());
+
+ settings.setUpdateBinaryPath(m_ui->updateBinaryPath->isChecked());
+ settings.setAlwaysAllowAccess(m_ui->alwaysAllowAccess->isChecked());
+ settings.setAlwaysAllowUpdate(m_ui->alwaysAllowUpdate->isChecked());
+ settings.setSearchInAllDatabases(m_ui->searchInAllDatabases->isChecked());
+ settings.setSupportKphFields(m_ui->supportKphFields->isChecked());
+
+ settings.setChromeSupport(m_ui->chromeSupport->isChecked());
+ settings.setChromiumSupport(m_ui->chromiumSupport->isChecked());
+ settings.setFirefoxSupport(m_ui->firefoxSupport->isChecked());
+ settings.setVivaldiSupport(m_ui->vivaldiSupport->isChecked());
+}
+
+void BrowserOptionDialog::showProxyLocationFileDialog()
+{
+#ifdef Q_OS_WIN
+ QString fileTypeFilter(tr("Executable Files (*.exe);;All Files (*.*)"));
+#else
+ QString fileTypeFilter(tr("Executable Files (*)"));
+#endif
+ auto proxyLocation = QFileDialog::getOpenFileName(this, tr("Select custom proxy location"),
+ QFileInfo(QCoreApplication::applicationDirPath()).filePath(),
+ fileTypeFilter);
+ m_ui->customProxyLocation->setText(proxyLocation);
+}
diff --git a/src/browser/BrowserOptionDialog.h b/src/browser/BrowserOptionDialog.h
new file mode 100755
index 000000000..b562f6c18
--- /dev/null
+++ b/src/browser/BrowserOptionDialog.h
@@ -0,0 +1,53 @@
+/*
+* Copyright (C) 2013 Francois Ferrand
+* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
+* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
+*
+* This program is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 3 of the License, or
+* (at your option) any later version.
+*
+* This program 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 General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef BROWSEROPTIONDIALOG_H
+#define BROWSEROPTIONDIALOG_H
+
+#include <QWidget>
+#include <QScopedPointer>
+
+namespace Ui {
+class BrowserOptionDialog;
+}
+
+class BrowserOptionDialog : public QWidget
+{
+ Q_OBJECT
+
+public:
+ explicit BrowserOptionDialog(QWidget* parent = nullptr);
+ ~BrowserOptionDialog();
+
+public slots:
+ void loadSettings();
+ void saveSettings();
+
+signals:
+ void removeSharedEncryptionKeys();
+ void removeStoredPermissions();
+
+private slots:
+ void showProxyLocationFileDialog();
+
+private:
+ QScopedPointer<Ui::BrowserOptionDialog> m_ui;
+};
+
+#endif // BROWSEROPTIONDIALOG_H
diff --git a/src/browser/BrowserOptionDialog.ui b/src/browser/BrowserOptionDialog.ui
new file mode 100755
index 000000000..e82379452
--- /dev/null
+++ b/src/browser/BrowserOptionDialog.ui
@@ -0,0 +1,377 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>BrowserOptionDialog</class>
+ <widget class="QWidget" name="BrowserOptionDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>523</width>
+ <height>456</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Dialog</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="MessageWidget" name="browserGlobalWarningWidget" native="true"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="enableBrowserSupport">
+ <property name="toolTip">
+ <string>This is required for accessing your databases with KeePassXC-Browser</string>
+ </property>
+ <property name="text">
+ <string>Enable KeepassXC browser integration</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QTabWidget" name="tabWidget">
+ <property name="currentIndex">
+ <number>0</number>
+ </property>
+ <widget class="QWidget" name="tab">
+ <attribute name="title">
+ <string>General</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QGroupBox" name="browsersGroupBox">
+ <property name="title">
+ <string>Enable integration for these browsers:</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <property name="horizontalSpacing">
+ <number>40</number>
+ </property>
+ <item row="0" column="0">
+ <widget class="QCheckBox" name="chromeSupport">
+ <property name="text">
+ <string>&amp;Google Chrome</string>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QCheckBox" name="firefoxSupport">
+ <property name="text">
+ <string>&amp;Firefox</string>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="2">
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>179</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="1" column="0">
+ <widget class="QCheckBox" name="chromiumSupport">
+ <property name="text">
+ <string>&amp;Chromium</string>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QCheckBox" name="vivaldiSupport">
+ <property name="text">
+ <string>&amp;Vivaldi</string>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer_3">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Fixed</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>4</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="showNotification">
+ <property name="text">
+ <string extracomment="Credentials mean login data requested via browser extension">Show a &amp;notification when credentials are requested</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="unlockDatabase">
+ <property name="text">
+ <string>Re&amp;quest to unlock the database if it is locked</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="matchUrlScheme">
+ <property name="toolTip">
+ <string>Only entries with the same scheme (http://, https://, ...) are returned.</string>
+ </property>
+ <property name="text">
+ <string>&amp;Match URL scheme (e.g., https://...)</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="bestMatchOnly">
+ <property name="toolTip">
+ <string>Only returns the best matches for a specific URL instead of all entries for the whole domain.</string>
+ </property>
+ <property name="text">
+ <string>&amp;Return only best-matching credentials</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QRadioButton" name="sortByTitle">
+ <property name="text">
+ <string extracomment="Credentials mean login data requested via browser extension">Sort &amp;matching credentials by title</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QRadioButton" name="sortByUsername">
+ <property name="text">
+ <string extracomment="Credentials mean login data requested via browser extension">Sort matching credentials by &amp;username</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Fixed</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>10</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <item>
+ <widget class="QPushButton" name="removeSharedEncryptionKeys">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>&amp;Disconnect all browsers</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="removeStoredPermissions">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Forget all remembered &amp;permissions</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <spacer name="verticalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="tab_2">
+ <attribute name="title">
+ <string>Advanced</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_6">
+ <item>
+ <widget class="MessageWidget" name="warningWidget" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="alwaysAllowAccess">
+ <property name="text">
+ <string extracomment="Credentials mean login data requested via browser extension">Never &amp;ask before accessing credentials</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="alwaysAllowUpdate">
+ <property name="text">
+ <string extracomment="Credentials mean login data requested via browser extension">Never ask before &amp;updating credentials</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="searchInAllDatabases">
+ <property name="toolTip">
+ <string>Only the selected database has to be connected with a client.</string>
+ </property>
+ <property name="text">
+ <string extracomment="Credentials mean login data requested via browser extension">Searc&amp;h in all opened databases for matching credentials</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="supportKphFields">
+ <property name="toolTip">
+ <string>Automatically creating or updating string fields is not supported.</string>
+ </property>
+ <property name="text">
+ <string>&amp;Return advanced string fields which start with &quot;KPH: &quot;</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="updateBinaryPath">
+ <property name="toolTip">
+ <string>Updates KeePassXC or keepassxc-proxy binary path automatically to native messaging scripts on startup.</string>
+ </property>
+ <property name="text">
+ <string>Update &amp;native messaging manifest files at startup</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="supportBrowserProxy">
+ <property name="toolTip">
+ <string>Support a proxy application between KeePassXC and browser extension.</string>
+ </property>
+ <property name="text">
+ <string>Use a &amp;proxy application between KeePassXC and browser extension</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="useCustomProxy">
+ <property name="toolTip">
+ <string>Use a custom proxy location if you installed a proxy manually.</string>
+ </property>
+ <property name="text">
+ <string comment="Meant is the proxy for KeePassXC-Browser">Use a &amp;custom proxy location</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QLineEdit" name="customProxyLocation">
+ <property name="maxLength">
+ <number>999</number>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="customProxyLocationBrowseButton">
+ <property name="text">
+ <string extracomment="Button for opening file dialog">Browse...</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <spacer name="verticalSpacer_4">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>MessageWidget</class>
+ <extends>QWidget</extends>
+ <header>gui/MessageWidget.h</header>
+ <container>1</container>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/browser/BrowserService.cpp b/src/browser/BrowserService.cpp
new file mode 100644
index 000000000..fb89e8bcb
--- /dev/null
+++ b/src/browser/BrowserService.cpp
@@ -0,0 +1,744 @@
+/*
+* Copyright (C) 2013 Francois Ferrand
+* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
+* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
+*
+* This program is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 3 of the License, or
+* (at your option) any later version.
+*
+* This program 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 General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <QJsonArray>
+#include <QInputDialog>
+#include <QProgressDialog>
+#include <QMessageBox>
+#include "BrowserService.h"
+#include "BrowserSettings.h"
+#include "BrowserEntryConfig.h"
+#include "BrowserAccessControlDialog.h"
+#include "core/Database.h"
+#include "core/Group.h"
+#include "core/EntrySearcher.h"
+#include "core/Metadata.h"
+#include "core/Uuid.h"
+#include "core/PasswordGenerator.h"
+#include "gui/MainWindow.h"
+
+
+// de887cc3-0363-43b8-974b-5911b8816224
+static const unsigned char KEEPASSXCBROWSER_UUID_DATA[] = {
+ 0xde, 0x88, 0x7c, 0xc3, 0x03, 0x63, 0x43, 0xb8,
+ 0x97, 0x4b, 0x59, 0x11, 0xb8, 0x81, 0x62, 0x24
+};
+static const Uuid KEEPASSXCBROWSER_UUID = Uuid(QByteArray::fromRawData(reinterpret_cast<const char *>(KEEPASSXCBROWSER_UUID_DATA), sizeof(KEEPASSXCBROWSER_UUID_DATA)));
+static const char KEEPASSXCBROWSER_NAME[] = "KeePassXC-Browser Settings";
+static const char ASSOCIATE_KEY_PREFIX[] = "Public Key: ";
+static const char KEEPASSXCBROWSER_GROUP_NAME[] = "KeePassXC-Browser Passwords";
+static int KEEPASSXCBROWSER_DEFAULT_ICON = 1;
+
+BrowserService::BrowserService(DatabaseTabWidget* parent) :
+ m_dbTabWidget(parent),
+ m_dialogActive(false)
+{
+ connect(m_dbTabWidget, SIGNAL(databaseLocked(DatabaseWidget*)), this, SLOT(databaseLocked(DatabaseWidget*)));
+ connect(m_dbTabWidget, SIGNAL(databaseUnlocked(DatabaseWidget*)), this, SLOT(databaseUnlocked(DatabaseWidget*)));
+ connect(m_dbTabWidget, SIGNAL(activateDatabaseChanged(DatabaseWidget*)), this, SLOT(activateDatabaseChanged(DatabaseWidget*)));
+}
+
+bool BrowserService::isDatabaseOpened() const
+{
+ DatabaseWidget* dbWidget = m_dbTabWidget->currentDatabaseWidget();
+ if (!dbWidget) {
+ return false;
+ }
+
+ return dbWidget->currentMode() == DatabaseWidget::ViewMode || dbWidget->currentMode() == DatabaseWidget::EditMode;
+
+}
+
+bool BrowserService::openDatabase(bool triggerUnlock)
+{
+ if (!BrowserSettings::unlockDatabase()) {
+ return false;
+ }
+
+ DatabaseWidget* dbWidget = m_dbTabWidget->currentDatabaseWidget();
+ if (!dbWidget) {
+ return false;
+ }
+
+ if (dbWidget->currentMode() == DatabaseWidget::ViewMode || dbWidget->currentMode() == DatabaseWidget::EditMode) {
+ return true;
+ }
+
+ if (triggerUnlock) {
+ KEEPASSXC_MAIN_WINDOW->bringToFront();
+ }
+
+ return false;
+}
+
+void BrowserService::lockDatabase()
+{
+ if (thread() != QThread::currentThread()) {
+ QMetaObject::invokeMethod(this, "lockDatabase", Qt::BlockingQueuedConnection);
+ }
+
+ DatabaseWidget* dbWidget = m_dbTabWidget->currentDatabaseWidget();
+ if (!dbWidget) {
+ return;
+ }
+
+ if (dbWidget->currentMode() == DatabaseWidget::ViewMode || dbWidget->currentMode() == DatabaseWidget::EditMode) {
+ dbWidget->lock();
+ }
+}
+
+QString BrowserService::getDatabaseRootUuid()
+{
+ Database* db = getDatabase();
+ if (!db) {
+ return QString();
+ }
+
+ Group* rootGroup = db->rootGroup();
+ if (!rootGroup) {
+ return QString();
+ }
+
+ return rootGroup->uuid().toHex();
+}
+
+QString BrowserService::getDatabaseRecycleBinUuid()
+{
+ Database* db = getDatabase();
+ if (!db) {
+ return QString();
+ }
+
+ Group* recycleBin = db->metadata()->recycleBin();
+ if (!recycleBin) {
+ return QString();
+ }
+ return recycleBin->uuid().toHex();
+}
+
+Entry* BrowserService::getConfigEntry(bool create)
+{
+ Entry* entry = nullptr;
+ Database* db = getDatabase();
+ if (!db) {
+ return nullptr;
+ }
+
+ entry = db->resolveEntry(KEEPASSXCBROWSER_UUID);
+ if (!entry && create) {
+ entry = new Entry();
+ entry->setTitle(QLatin1String(KEEPASSXCBROWSER_NAME));
+ entry->setUuid(KEEPASSXCBROWSER_UUID);
+ entry->setAutoTypeEnabled(false);
+ entry->setGroup(db->rootGroup());
+ return entry;
+ }
+
+ if (entry && entry->group() == db->metadata()->recycleBin()) {
+ if (!create) {
+ return nullptr;
+ } else {
+ entry->setGroup(db->rootGroup());
+ return entry;
+ }
+ }
+
+ return entry;
+}
+
+QString BrowserService::storeKey(const QString& key)
+{
+ QString id;
+
+ if (thread() != QThread::currentThread()) {
+ QMetaObject::invokeMethod(this, "storeKey", Qt::BlockingQueuedConnection,
+ Q_RETURN_ARG(QString, id),
+ Q_ARG(const QString&, key));
+ return id;
+ }
+
+ Entry* config = getConfigEntry(true);
+ if (!config) {
+ return {};
+ }
+
+ bool contains;
+ QMessageBox::StandardButton dialogResult = QMessageBox::No;
+
+ do {
+ QInputDialog keyDialog;
+ keyDialog.setWindowTitle(tr("KeePassXC: New key association request"));
+ keyDialog.setLabelText(tr("You have received an association request for the above key.\n\n"
+ "If you would like to allow it access to your KeePassXC database,\n"
+ "give it a unique name to identify and accept it."));
+ keyDialog.setOkButtonText(tr("Save and allow access"));
+ keyDialog.setWindowFlags(keyDialog.windowFlags() | Qt::WindowStaysOnTopHint);
+ keyDialog.show();
+ keyDialog.activateWindow();
+ keyDialog.raise();
+ auto ok = keyDialog.exec();
+
+ id = keyDialog.textValue();
+
+ if (ok != QDialog::Accepted || id.isEmpty()) {
+ return {};
+ }
+
+ contains = config->attributes()->contains(QLatin1String(ASSOCIATE_KEY_PREFIX) + id);
+ if (contains) {
+ dialogResult = QMessageBox::warning(nullptr, tr("KeePassXC: Overwrite existing key?"),
+ tr("A shared encryption key with the name \"%1\" "
+ "already exists.\nDo you want to overwrite it?")
+ .arg(id),
+ QMessageBox::Yes | QMessageBox::No);
+ }
+ } while (contains && dialogResult == QMessageBox::No);
+
+ config->attributes()->set(QLatin1String(ASSOCIATE_KEY_PREFIX) + id, key, true);
+ return id;
+}
+
+QString BrowserService::getKey(const QString& id)
+{
+ Entry* config = getConfigEntry();
+ if (!config) {
+ return QString();
+ }
+
+ return config->attributes()->value(QLatin1String(ASSOCIATE_KEY_PREFIX) + id);
+}
+
+QJsonArray BrowserService::findMatchingEntries(const QString& id, const QString& url, const QString& submitUrl, const QString& realm)
+{
+ QJsonArray result;
+ if (thread() != QThread::currentThread()) {
+ QMetaObject::invokeMethod(this, "findMatchingEntries", Qt::BlockingQueuedConnection,
+ Q_RETURN_ARG(QJsonArray, result),
+ Q_ARG(const QString&, id),
+ Q_ARG(const QString&, url),
+ Q_ARG(const QString&, submitUrl),
+ Q_ARG(const QString&, realm));
+ return result;
+ }
+
+ const bool alwaysAllowAccess = BrowserSettings::alwaysAllowAccess();
+ const QString host = QUrl(url).host();
+ const QString submitHost = QUrl(submitUrl).host();
+
+ // Check entries for authorization
+ QList<Entry*> pwEntriesToConfirm;
+ QList<Entry*> pwEntries;
+ for (Entry* entry : searchEntries(url)) {
+ switch (checkAccess(entry, host, submitHost, realm)) {
+ case Denied:
+ continue;
+
+ case Unknown:
+ if (alwaysAllowAccess) {
+ pwEntries.append(entry);
+ } else {
+ pwEntriesToConfirm.append(entry);
+ }
+ break;
+
+ case Allowed:
+ pwEntries.append(entry);
+ break;
+ }
+ }
+
+ // Confirm entries
+ if (confirmEntries(pwEntriesToConfirm, url, host, submitHost, realm)) {
+ pwEntries.append(pwEntriesToConfirm);
+ }
+
+ if (pwEntries.isEmpty()) {
+ return QJsonArray();
+ }
+
+ // Sort results
+ pwEntries = sortEntries(pwEntries, host, submitUrl);
+
+ // Fill the list
+ for (Entry* entry : pwEntries) {
+ result << prepareEntry(entry);
+ }
+
+ return result;
+}
+
+void BrowserService::addEntry(const QString&, const QString& login, const QString& password, const QString& url, const QString& submitUrl, const QString& realm)
+{
+ Group* group = findCreateAddEntryGroup();
+ if (!group) {
+ return;
+ }
+
+ Entry* entry = new Entry();
+ entry->setUuid(Uuid::random());
+ entry->setTitle(QUrl(url).host());
+ entry->setUrl(url);
+ entry->setIcon(KEEPASSXCBROWSER_DEFAULT_ICON);
+ entry->setUsername(login);
+ entry->setPassword(password);
+ entry->setGroup(group);
+
+ const QString host = QUrl(url).host();
+ const QString submitHost = QUrl(submitUrl).host();
+ BrowserEntryConfig config;
+ config.allow(host);
+
+ if (!submitHost.isEmpty()) {
+ config.allow(submitHost);
+ }
+ if (!realm.isEmpty()) {
+ config.setRealm(realm);
+ }
+ config.save(entry);
+}
+
+void BrowserService::updateEntry(const QString& id, const QString& uuid, const QString& login, const QString& password, const QString& url)
+{
+ if (thread() != QThread::currentThread()) {
+ QMetaObject::invokeMethod(this, "updateEntry", Qt::BlockingQueuedConnection,
+ Q_ARG(const QString&, id),
+ Q_ARG(const QString&, uuid),
+ Q_ARG(const QString&, login),
+ Q_ARG(const QString&, password),
+ Q_ARG(const QString&, url));
+ }
+
+ Database* db = getDatabase();
+ if (!db) {
+ return;
+ }
+
+ Entry* entry = db->resolveEntry(Uuid::fromHex(uuid));
+ if (!entry) {
+ return;
+ }
+
+ QString username = entry->username();
+ if (username.isEmpty()) {
+ return;
+ }
+
+ if (username.compare(login, Qt::CaseSensitive) != 0 || entry->password().compare(password, Qt::CaseSensitive) != 0) {
+ QMessageBox::StandardButton dialogResult = QMessageBox::No;
+ if (!BrowserSettings::alwaysAllowUpdate()) {
+ dialogResult = QMessageBox::warning(0, tr("KeePassXC: Update Entry"),
+ tr("Do you want to update the information in %1 - %2?")
+ .arg(QUrl(url).host()).arg(username),
+ QMessageBox::Yes|QMessageBox::No);
+ }
+
+ if (BrowserSettings::alwaysAllowUpdate() || dialogResult == QMessageBox::Yes) {
+ entry->beginUpdate();
+ entry->setUsername(login);
+ entry->setPassword(password);
+ entry->endUpdate();
+ }
+ }
+}
+
+QList<Entry*> BrowserService::searchEntries(Database* db, const QString& hostname)
+{
+ QList<Entry*> entries;
+ Group* rootGroup = db->rootGroup();
+ if (!rootGroup) {
+ return entries;
+ }
+
+ for (Entry* entry : EntrySearcher().search(hostname, rootGroup, Qt::CaseInsensitive)) {
+ QString title = entry->title();
+ QString url = entry->url();
+
+ // Filter to match hostname in Title and Url fields
+ if ((!title.isEmpty() && hostname.contains(title))
+ || (!url.isEmpty() && hostname.contains(url))
+ || (matchUrlScheme(title) && hostname.endsWith(QUrl(title).host()))
+ || (matchUrlScheme(url) && hostname.endsWith(QUrl(url).host())) ) {
+ entries.append(entry);
+ }
+ }
+
+ return entries;
+}
+
+QList<Entry*> BrowserService::searchEntries(const QString& text)
+{
+ // Get the list of databases to search
+ QList<Database*> databases;
+ if (BrowserSettings::searchInAllDatabases()) {
+ const int count = m_dbTabWidget->count();
+ for (int i = 0; i < count; ++i) {
+ if (DatabaseWidget* dbWidget = qobject_cast<DatabaseWidget*>(m_dbTabWidget->widget(i))) {
+ if (Database* db = dbWidget->database()) {
+ databases << db;
+ }
+ }
+ }
+ } else if (Database* db = getDatabase()) {
+ databases << db;
+ }
+
+ // Search entries matching the hostname
+ QString hostname = QUrl(text).host();
+ QList<Entry*> entries;
+ do {
+ for (Database* db : databases) {
+ entries << searchEntries(db, hostname);
+ }
+ } while (entries.isEmpty() && removeFirstDomain(hostname));
+
+ return entries;
+}
+
+void BrowserService::removeSharedEncryptionKeys()
+{
+ if (!isDatabaseOpened()) {
+ QMessageBox::critical(0, tr("KeePassXC: Database locked!"),
+ tr("The active database is locked!\n"
+ "Please unlock the selected database or choose another one which is unlocked."),
+ QMessageBox::Ok);
+ return;
+ }
+
+ Entry* entry = getConfigEntry();
+ if (!entry) {
+ QMessageBox::information(0, tr("KeePassXC: Settings not available!"),
+ tr("The active database does not contain a settings entry."),
+ QMessageBox::Ok);
+ return;
+ }
+
+ QStringList keysToRemove;
+ for (const QString& key : entry->attributes()->keys()) {
+ if (key.startsWith(ASSOCIATE_KEY_PREFIX)) {
+ keysToRemove << key;
+ }
+ }
+
+ if (keysToRemove.isEmpty()) {
+ QMessageBox::information(0, tr("KeePassXC: No keys found"),
+ tr("No shared encryption keys found in KeePassXC Settings."),
+ QMessageBox::Ok);
+ return;
+ }
+
+ entry->beginUpdate();
+ for (const QString& key : keysToRemove) {
+ entry->attributes()->remove(key);
+ }
+ entry->endUpdate();
+
+ const int count = keysToRemove.count();
+ QMessageBox::information(0, tr("KeePassXC: Removed keys from database"),
+ tr("Successfully removed %n encryption key(s) from KeePassXC settings.", "", count),
+ QMessageBox::Ok);
+
+}
+
+void BrowserService::removeStoredPermissions()
+{
+ if (!isDatabaseOpened()) {
+ QMessageBox::critical(0, tr("KeePassXC: Database locked!"),
+ tr("The active database is locked!\n"
+ "Please unlock the selected database or choose another one which is unlocked."),
+ QMessageBox::Ok);
+ return;
+ }
+
+ Database* db = m_dbTabWidget->currentDatabaseWidget()->database();
+ if (!db) {
+ return;
+ }
+
+ QList<Entry*> entries = db->rootGroup()->entriesRecursive();
+
+ QProgressDialog progress(tr("Removing stored permissions…"), tr("Abort"), 0, entries.count());
+ progress.setWindowModality(Qt::WindowModal);
+
+ uint counter = 0;
+ for (Entry* entry : entries) {
+ if (progress.wasCanceled()) {
+ return;
+ }
+
+ if (entry->attributes()->contains(KEEPASSXCBROWSER_NAME)) {
+ entry->beginUpdate();
+ entry->attributes()->remove(KEEPASSXCBROWSER_NAME);
+ entry->endUpdate();
+ ++counter;
+ }
+ progress.setValue(progress.value() + 1);
+ }
+ progress.reset();
+
+ if (counter > 0) {
+ QMessageBox::information(0, tr("KeePassXC: Removed permissions"),
+ tr("Successfully removed permissions from %n entry(s).", "", counter),
+ QMessageBox::Ok);
+ } else {
+ QMessageBox::information(0, tr("KeePassXC: No entry with permissions found!"),
+ tr("The active database does not contain an entry with permissions."),
+ QMessageBox::Ok);
+ }
+}
+
+QList<Entry*> BrowserService::sortEntries(QList<Entry*>& pwEntries, const QString& host, const QString& entryUrl)
+{
+ QUrl url(entryUrl);
+ if (url.scheme().isEmpty()) {
+ url.setScheme("http");
+ }
+
+ const QString submitUrl = url.toString(QUrl::StripTrailingSlash);
+ const QString baseSubmitUrl = url.toString(QUrl::StripTrailingSlash | QUrl::RemovePath | QUrl::RemoveQuery | QUrl::RemoveFragment);
+
+ QMultiMap<int, const Entry*> priorities;
+ for (const Entry* entry : pwEntries) {
+ priorities.insert(sortPriority(entry, host, submitUrl, baseSubmitUrl), entry);
+ }
+
+ QString field = BrowserSettings::sortByTitle() ? "Title" : "UserName";
+ std::sort(pwEntries.begin(), pwEntries.end(), [&priorities, &field](const Entry* left, const Entry* right) {
+ int res = priorities.key(left) - priorities.key(right);
+ if (res == 0) {
+ return QString::localeAwareCompare(left->attributes()->value(field), right->attributes()->value(field)) < 0;
+ }
+ return res < 0;
+ });
+
+ return pwEntries;
+}
+
+bool BrowserService::confirmEntries(QList<Entry*>& pwEntriesToConfirm, const QString& url, const QString& host, const QString& submitHost, const QString& realm)
+{
+ if (pwEntriesToConfirm.isEmpty() || m_dialogActive) {
+ return false;
+ }
+
+ m_dialogActive = true;
+ BrowserAccessControlDialog accessControlDialog;
+ accessControlDialog.setUrl(url);
+ accessControlDialog.setItems(pwEntriesToConfirm);
+
+ int res = accessControlDialog.exec();
+ if (accessControlDialog.remember()) {
+ for (Entry* entry : pwEntriesToConfirm) {
+ BrowserEntryConfig config;
+ config.load(entry);
+ if (res == QDialog::Accepted) {
+ config.allow(host);
+ if (!submitHost.isEmpty() && host != submitHost)
+ config.allow(submitHost);
+ } else if (res == QDialog::Rejected) {
+ config.deny(host);
+ if (!submitHost.isEmpty() && host != submitHost) {
+ config.deny(submitHost);
+ }
+ }
+ if (!realm.isEmpty()) {
+ config.setRealm(realm);
+ }
+ config.save(entry);
+ }
+ }
+
+ m_dialogActive = false;
+ if (res == QDialog::Accepted) {
+ return true;
+ }
+
+ return false;
+}
+
+QJsonObject BrowserService::prepareEntry(const Entry* entry)
+{
+ QJsonObject res;
+ res["login"] = entry->resolveMultiplePlaceholders(entry->username());
+ res["password"] = entry->resolveMultiplePlaceholders(entry->password());
+ res["name"] = entry->resolveMultiplePlaceholders(entry->title());
+ res["uuid"] = entry->resolveMultiplePlaceholders(entry->uuid().toHex());
+
+ if (BrowserSettings::supportKphFields()) {
+ const EntryAttributes* attr = entry->attributes();
+ QJsonArray stringFields;
+ for (const QString& key : attr->keys()) {
+ if (key.startsWith(QLatin1String("KPH: "))) {
+ QJsonObject sField;
+ sField[key] = entry->resolveMultiplePlaceholders(attr->value(key));
+ stringFields << sField;
+ }
+ }
+ res["stringFields"] = stringFields;
+ }
+ return res;
+}
+
+BrowserService::Access BrowserService::checkAccess(const Entry* entry, const QString& host, const QString& submitHost, const QString& realm)
+{
+ BrowserEntryConfig config;
+ if (!config.load(entry)) {
+ return Unknown;
+ }
+ if ((config.isAllowed(host)) && (submitHost.isEmpty() || config.isAllowed(submitHost))) {
+ return Allowed;
+ }
+ if ((config.isDenied(host)) || (!submitHost.isEmpty() && config.isDenied(submitHost))) {
+ return Denied;
+ }
+ if (!realm.isEmpty() && config.realm() != realm) {
+ return Denied;
+ }
+ return Unknown;
+}
+
+Group* BrowserService::findCreateAddEntryGroup()
+{
+ Database* db = getDatabase();
+ if (!db) {
+ return nullptr;
+ }
+
+ Group* rootGroup = db->rootGroup();
+ if (!rootGroup) {
+ return nullptr;
+ }
+
+ const QString groupName = QLatin1String(KEEPASSXCBROWSER_GROUP_NAME); //TODO: setting to decide where new keys are created
+
+ for (const Group* g : rootGroup->groupsRecursive(true)) {
+ if (g->name() == groupName) {
+ return db->resolveGroup(g->uuid());
+ }
+ }
+
+ Group* group = new Group();
+ group->setUuid(Uuid::random());
+ group->setName(groupName);
+ group->setIcon(KEEPASSXCBROWSER_DEFAULT_ICON);
+ group->setParent(rootGroup);
+ return group;
+}
+
+int BrowserService::sortPriority(const Entry* entry, const QString& host, const QString& submitUrl, const QString& baseSubmitUrl) const
+{
+ QUrl url(entry->url());
+ if (url.scheme().isEmpty()) {
+ url.setScheme("http");
+ }
+ const QString entryURL = url.toString(QUrl::StripTrailingSlash);
+ const QString baseEntryURL = url.toString(QUrl::StripTrailingSlash | QUrl::RemovePath | QUrl::RemoveQuery | QUrl::RemoveFragment);
+
+ if (submitUrl == entryURL) {
+ return 100;
+ }
+ if (submitUrl.startsWith(entryURL) && entryURL != host && baseSubmitUrl != entryURL) {
+ return 90;
+ }
+ if (submitUrl.startsWith(baseEntryURL) && entryURL != host && baseSubmitUrl != baseEntryURL) {
+ return 80;
+ }
+ if (entryURL == host) {
+ return 70;
+ }
+ if (entryURL == baseSubmitUrl) {
+ return 60;
+ }
+ if (entryURL.startsWith(submitUrl)) {
+ return 50;
+ }
+ if (entryURL.startsWith(baseSubmitUrl) && baseSubmitUrl != host) {
+ return 40;
+ }
+ if (submitUrl.startsWith(entryURL)) {
+ return 30;
+ }
+ if (submitUrl.startsWith(baseEntryURL)) {
+ return 20;
+ }
+ if (entryURL.startsWith(host)) {
+ return 10;
+ }
+ if (host.startsWith(entryURL)) {
+ return 5;
+ }
+ return 0;
+}
+
+bool BrowserService::matchUrlScheme(const QString& url)
+{
+ QUrl address(url);
+ return !address.scheme().isEmpty();
+}
+
+bool BrowserService::removeFirstDomain(QString& hostname)
+{
+ int pos = hostname.indexOf(".");
+ if (pos < 0) {
+ return false;
+ }
+
+ // Don't remove the second-level domain if it's the only one
+ if (hostname.count(".") > 1) {
+ hostname = hostname.mid(pos + 1);
+ return !hostname.isEmpty();
+ }
+
+ // Nothing removed
+ return false;
+}
+
+Database* BrowserService::getDatabase()
+{
+ if (DatabaseWidget* dbWidget = m_dbTabWidget->currentDatabaseWidget()) {
+ if (Database* db = dbWidget->database()) {
+ return db;
+ }
+ }
+ return nullptr;
+}
+
+void BrowserService::databaseLocked(DatabaseWidget* dbWidget)
+{
+ if (dbWidget) {
+ emit databaseLocked();
+ }
+}
+
+void BrowserService::databaseUnlocked(DatabaseWidget* dbWidget)
+{
+ if (dbWidget) {
+ emit databaseUnlocked();
+ }
+}
+
+void BrowserService::activateDatabaseChanged(DatabaseWidget* dbWidget)
+{
+ if (dbWidget) {
+ auto currentMode = dbWidget->currentMode();
+ if (currentMode == DatabaseWidget::ViewMode || currentMode == DatabaseWidget::EditMode) {
+ emit databaseUnlocked();
+ } else {
+ emit databaseLocked();
+ }
+ }
+}
diff --git a/src/browser/BrowserService.h b/src/browser/BrowserService.h
new file mode 100644
index 000000000..5a96e1ecd
--- /dev/null
+++ b/src/browser/BrowserService.h
@@ -0,0 +1,82 @@
+/*
+* Copyright (C) 2013 Francois Ferrand
+* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
+* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
+*
+* This program is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 2 or (at your option)
+* version 3 of the License.
+*
+* This program 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 General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef BROWSERSERVICE_H
+#define BROWSERSERVICE_H
+
+#include <QtCore>
+#include <QObject>
+#include "gui/DatabaseTabWidget.h"
+#include "core/Entry.h"
+
+enum { max_length = 16*1024 };
+
+class BrowserService : public QObject
+{
+ Q_OBJECT
+
+public:
+ explicit BrowserService(DatabaseTabWidget* parent);
+
+ bool isDatabaseOpened() const;
+ bool openDatabase(bool triggerUnlock);
+ QString getDatabaseRootUuid();
+ QString getDatabaseRecycleBinUuid();
+ Entry* getConfigEntry(bool create = false);
+ QString getKey(const QString& id);
+ void addEntry(const QString& id, const QString& login, const QString& password, const QString& url, const QString& submitUrl, const QString& realm);
+ QList<Entry*> searchEntries(Database* db, const QString& hostname);
+ QList<Entry*> searchEntries(const QString& text);
+ void removeSharedEncryptionKeys();
+ void removeStoredPermissions();
+
+public slots:
+ QJsonArray findMatchingEntries(const QString& id, const QString& url, const QString& submitUrl, const QString& realm);
+ QString storeKey(const QString& key);
+ void updateEntry(const QString& id, const QString& uuid, const QString& login, const QString& password, const QString& url);
+ void databaseLocked(DatabaseWidget* dbWidget);
+ void databaseUnlocked(DatabaseWidget* dbWidget);
+ void activateDatabaseChanged(DatabaseWidget* dbWidget);
+ void lockDatabase();
+
+signals:
+ void databaseLocked();
+ void databaseUnlocked();
+ void databaseChanged();
+
+private:
+ enum Access { Denied, Unknown, Allowed};
+
+private:
+ QList<Entry*> sortEntries(QList<Entry*>& pwEntries, const QString& host, const QString& submitUrl);
+ bool confirmEntries(QList<Entry*>& pwEntriesToConfirm, const QString& url, const QString& host, const QString& submitHost, const QString& realm);
+ QJsonObject prepareEntry(const Entry* entry);
+ Access checkAccess(const Entry* entry, const QString& host, const QString& submitHost, const QString& realm);
+ Group* findCreateAddEntryGroup();
+ int sortPriority(const Entry* entry, const QString &host, const QString& submitUrl, const QString& baseSubmitUrl) const;
+ bool matchUrlScheme(const QString& url);
+ bool removeFirstDomain(QString& hostname);
+ Database* getDatabase();
+
+private:
+ DatabaseTabWidget* const m_dbTabWidget;
+ bool m_dialogActive;
+};
+
+#endif // BROWSERSERVICE_H
diff --git a/src/browser/BrowserSettings.cpp b/src/browser/BrowserSettings.cpp
new file mode 100755
index 000000000..ecaf8e23e
--- /dev/null
+++ b/src/browser/BrowserSettings.cpp
@@ -0,0 +1,380 @@
+/*
+* Copyright (C) 2013 Francois Ferrand
+* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
+* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
+*
+* This program is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 3 of the License, or
+* (at your option) any later version.
+*
+* This program 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 General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "BrowserSettings.h"
+#include "core/Config.h"
+
+PasswordGenerator BrowserSettings::m_passwordGenerator;
+PassphraseGenerator BrowserSettings::m_passPhraseGenerator;
+HostInstaller BrowserSettings::m_hostInstaller;
+
+bool BrowserSettings::isEnabled()
+{
+ return config()->get("Browser/Enabled", false).toBool();
+}
+
+void BrowserSettings::setEnabled(bool enabled)
+{
+ config()->set("Browser/Enabled", enabled);
+}
+
+bool BrowserSettings::showNotification()
+{
+ return config()->get("Browser/ShowNotification", true).toBool();
+}
+
+void BrowserSettings::setShowNotification(bool showNotification)
+{
+ config()->set("Browser/ShowNotification", showNotification);
+}
+
+bool BrowserSettings::bestMatchOnly()
+{
+ return config()->get("Browser/BestMatchOnly", false).toBool();
+}
+
+void BrowserSettings::setBestMatchOnly(bool bestMatchOnly)
+{
+ config()->set("Browser/BestMatchOnly", bestMatchOnly);
+}
+
+bool BrowserSettings::unlockDatabase()
+{
+ return config()->get("Browser/UnlockDatabase", true).toBool();
+}
+
+void BrowserSettings::setUnlockDatabase(bool unlockDatabase)
+{
+ config()->set("Browser/UnlockDatabase", unlockDatabase);
+}
+
+bool BrowserSettings::matchUrlScheme()
+{
+ return config()->get("Browser/MatchUrlScheme", true).toBool();
+}
+
+void BrowserSettings::setMatchUrlScheme(bool matchUrlScheme)
+{
+ config()->set("Browser/MatchUrlScheme", matchUrlScheme);
+}
+
+bool BrowserSettings::sortByUsername()
+{
+ return config()->get("Browser/SortByUsername", false).toBool();
+}
+
+void BrowserSettings::setSortByUsername(bool sortByUsername)
+{
+ config()->set("Browser/SortByUsername", sortByUsername);
+}
+
+bool BrowserSettings::sortByTitle()
+{
+ return !sortByUsername();
+}
+
+void BrowserSettings::setSortByTitle(bool sortByUsertitle)
+{
+ setSortByUsername(!sortByUsertitle);
+}
+
+bool BrowserSettings::alwaysAllowAccess()
+{
+ return config()->get("Browser/AlwaysAllowAccess", false).toBool();
+}
+
+void BrowserSettings::setAlwaysAllowAccess(bool alwaysAllowAccess)
+{
+ config()->set("Browser/AlwaysAllowAccess", alwaysAllowAccess);
+}
+
+bool BrowserSettings::alwaysAllowUpdate()
+{
+ return config()->get("Browser/AlwaysAllowUpdate", false).toBool();
+}
+
+void BrowserSettings::setAlwaysAllowUpdate(bool alwaysAllowUpdate)
+{
+ config()->set("Browser/AlwaysAllowUpdate", alwaysAllowUpdate);
+}
+
+bool BrowserSettings::searchInAllDatabases()
+{
+ return config()->get("Browser/SearchInAllDatabases", false).toBool();
+}
+
+void BrowserSettings::setSearchInAllDatabases(bool searchInAllDatabases)
+{
+ config()->set("Browser/SearchInAllDatabases", searchInAllDatabases);
+}
+
+bool BrowserSettings::supportKphFields()
+{
+ return config()->get("Browser/SupportKphFields", true).toBool();
+}
+
+void BrowserSettings::setSupportKphFields(bool supportKphFields)
+{
+ config()->set("Browser/SupportKphFields", supportKphFields);
+}
+
+bool BrowserSettings::supportBrowserProxy()
+{
+ return config()->get("Browser/SupportBrowserProxy", true).toBool();
+}
+
+void BrowserSettings::setSupportBrowserProxy(bool enabled)
+{
+ config()->set("Browser/SupportBrowserProxy", enabled);
+}
+
+bool BrowserSettings::useCustomProxy()
+{
+ return config()->get("Browser/UseCustomProxy", false).toBool();
+}
+
+void BrowserSettings::setUseCustomProxy(bool enabled)
+{
+ config()->set("Browser/UseCustomProxy", enabled);
+}
+
+QString BrowserSettings::customProxyLocation()
+{
+ if (!useCustomProxy()) {
+ return QString();
+ }
+ return config()->get("Browser/CustomProxyLocation", "").toString();
+}
+
+void BrowserSettings::setCustomProxyLocation(QString location)
+{
+ config()->set("Browser/CustomProxyLocation", location);
+}
+
+bool BrowserSettings::updateBinaryPath()
+{
+ return config()->get("Browser/UpdateBinaryPath", false).toBool();
+}
+
+void BrowserSettings::setUpdateBinaryPath(bool enabled)
+{
+ config()->set("Browser/UpdateBinaryPath", enabled);
+}
+
+bool BrowserSettings::chromeSupport() {
+ return m_hostInstaller.checkIfInstalled(HostInstaller::SupportedBrowsers::CHROME);
+}
+
+void BrowserSettings::setChromeSupport(bool enabled) {
+ m_hostInstaller.installBrowser(HostInstaller::SupportedBrowsers::CHROME, enabled, supportBrowserProxy(), customProxyLocation());
+}
+
+bool BrowserSettings::chromiumSupport() {
+ return m_hostInstaller.checkIfInstalled(HostInstaller::SupportedBrowsers::CHROMIUM);
+}
+
+void BrowserSettings::setChromiumSupport(bool enabled) {
+ m_hostInstaller.installBrowser(HostInstaller::SupportedBrowsers::CHROMIUM, enabled, supportBrowserProxy(), customProxyLocation());
+}
+
+bool BrowserSettings::firefoxSupport() {
+ return m_hostInstaller.checkIfInstalled(HostInstaller::SupportedBrowsers::FIREFOX);
+}
+
+void BrowserSettings::setFirefoxSupport(bool enabled) {
+ m_hostInstaller.installBrowser(HostInstaller::SupportedBrowsers::FIREFOX, enabled, supportBrowserProxy(), customProxyLocation());
+}
+
+bool BrowserSettings::vivaldiSupport() {
+ return m_hostInstaller.checkIfInstalled(HostInstaller::SupportedBrowsers::VIVALDI);
+}
+
+void BrowserSettings::setVivaldiSupport(bool enabled) {
+ m_hostInstaller.installBrowser(HostInstaller::SupportedBrowsers::VIVALDI, enabled, supportBrowserProxy(), customProxyLocation());
+}
+
+bool BrowserSettings::passwordUseNumbers()
+{
+ return config()->get("generator/Numbers", PasswordGenerator::DefaultNumbers).toBool();
+}
+
+void BrowserSettings::setPasswordUseNumbers(bool useNumbers)
+{
+ config()->set("generator/Numbers", useNumbers);
+}
+
+bool BrowserSettings::passwordUseLowercase()
+{
+ return config()->get("generator/LowerCase", PasswordGenerator::DefaultLower).toBool();
+}
+
+void BrowserSettings::setPasswordUseLowercase(bool useLowercase)
+{
+ config()->set("generator/LowerCase", useLowercase);
+}
+
+bool BrowserSettings::passwordUseUppercase()
+{
+ return config()->get("generator/UpperCase", PasswordGenerator::DefaultUpper).toBool();
+}
+
+void BrowserSettings::setPasswordUseUppercase(bool useUppercase)
+{
+ config()->set("generator/UpperCase", useUppercase);
+}
+
+bool BrowserSettings::passwordUseSpecial()
+{
+ return config()->get("generator/SpecialChars", PasswordGenerator::DefaultSpecial).toBool();
+}
+
+void BrowserSettings::setPasswordUseSpecial(bool useSpecial)
+{
+ config()->set("generator/SpecialChars", useSpecial);
+}
+
+bool BrowserSettings::passwordUseEASCII()
+{
+ return config()->get("generator/EASCII", PasswordGenerator::DefaultEASCII).toBool();
+}
+
+void BrowserSettings::setPasswordUseEASCII(bool useEASCII)
+{
+ config()->set("generator/EASCII", useEASCII);
+}
+
+int BrowserSettings::passPhraseWordCount()
+{
+ return config()->get("generator/WordCount", PassphraseGenerator::DefaultWordCount).toInt();
+}
+
+void BrowserSettings::setPassPhraseWordCount(int wordCount)
+{
+ config()->set("generator/WordCount", wordCount);
+}
+
+QString BrowserSettings::passPhraseWordSeparator()
+{
+ return config()->get("generator/WordSeparator", PassphraseGenerator::DefaultSeparator).toString();
+}
+
+void BrowserSettings::setPassPhraseWordSeparator(QString separator)
+{
+ config()->set("generator/WordSeparator", separator);
+}
+
+int BrowserSettings::generatorType()
+{
+ return config()->get("generator/Type", 0).toInt();
+}
+
+void BrowserSettings::setGeneratorType(int type)
+{
+ config()->set("generator/Type", type);
+}
+
+bool BrowserSettings::passwordEveryGroup()
+{
+ return config()->get("generator/EnsureEvery", PasswordGenerator::DefaultFromEveryGroup).toBool();
+}
+
+void BrowserSettings::setPasswordEveryGroup(bool everyGroup)
+{
+ config()->get("generator/EnsureEvery", everyGroup);
+}
+
+bool BrowserSettings::passwordExcludeAlike()
+{
+ return config()->get("generator/ExcludeAlike", PasswordGenerator::DefaultLookAlike).toBool();
+}
+
+void BrowserSettings::setPasswordExcludeAlike(bool excludeAlike)
+{
+ config()->set("generator/ExcludeAlike", excludeAlike);
+}
+
+int BrowserSettings::passwordLength()
+{
+ return config()->get("generator/Length", PasswordGenerator::DefaultLength).toInt();
+}
+
+void BrowserSettings::setPasswordLength(int length)
+{
+ config()->set("generator/Length", length);
+ m_passwordGenerator.setLength(length);
+}
+
+PasswordGenerator::CharClasses BrowserSettings::passwordCharClasses()
+{
+ PasswordGenerator::CharClasses classes;
+ if (passwordUseLowercase()) {
+ classes |= PasswordGenerator::LowerLetters;
+ }
+ if (passwordUseUppercase()) {
+ classes |= PasswordGenerator::UpperLetters;
+ }
+ if (passwordUseNumbers()) {
+ classes |= PasswordGenerator::Numbers;
+ }
+ if (passwordUseSpecial()) {
+ classes |= PasswordGenerator::SpecialCharacters;
+ }
+ if (passwordUseEASCII()) {
+ classes |= PasswordGenerator::EASCII;
+ }
+ return classes;
+}
+
+PasswordGenerator::GeneratorFlags BrowserSettings::passwordGeneratorFlags()
+{
+ PasswordGenerator::GeneratorFlags flags;
+ if (passwordExcludeAlike()) {
+ flags |= PasswordGenerator::ExcludeLookAlike;
+ }
+ if (passwordEveryGroup()) {
+ flags |= PasswordGenerator::CharFromEveryGroup;
+ }
+ return flags;
+}
+
+QString BrowserSettings::generatePassword()
+{
+ if (generatorType() == 0) {
+ m_passwordGenerator.setLength(passwordLength());
+ m_passwordGenerator.setCharClasses(passwordCharClasses());
+ m_passwordGenerator.setFlags(passwordGeneratorFlags());
+ return m_passwordGenerator.generatePassword();
+ } else {
+ m_passPhraseGenerator.setDefaultWordList();
+ m_passPhraseGenerator.setWordCount(passPhraseWordCount());
+ m_passPhraseGenerator.setWordSeparator(passPhraseWordSeparator());
+ return m_passPhraseGenerator.generatePassphrase();
+ }
+}
+
+int BrowserSettings::getbits()
+{
+ return m_passwordGenerator.getbits();
+}
+
+void BrowserSettings::updateBinaryPaths(QString customProxyLocation)
+{
+ bool isProxy = supportBrowserProxy();
+ m_hostInstaller.updateBinaryPaths(isProxy, customProxyLocation);
+}
diff --git a/src/browser/BrowserSettings.h b/src/browser/BrowserSettings.h
new file mode 100755
index 000000000..eb59fa5ac
--- /dev/null
+++ b/src/browser/BrowserSettings.h
@@ -0,0 +1,105 @@
+/*
+* Copyright (C) 2013 Francois Ferrand
+* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
+* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
+*
+* This program is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 3 of the License, or
+* (at your option) any later version.
+*
+* This program 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 General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef BROWSERSETTINGS_H
+#define BROWSERSETTINGS_H
+
+#include "core/PasswordGenerator.h"
+#include "core/PassphraseGenerator.h"
+#include "HostInstaller.h"
+
+class BrowserSettings
+{
+public:
+ static bool isEnabled();
+ static void setEnabled(bool enabled);
+
+ static bool showNotification(); //TODO!!
+ static void setShowNotification(bool showNotification);
+ static bool bestMatchOnly(); //TODO!!
+ static void setBestMatchOnly(bool bestMatchOnly);
+ static bool unlockDatabase();
+ static void setUnlockDatabase(bool unlockDatabase);
+ static bool matchUrlScheme();
+ static void setMatchUrlScheme(bool matchUrlScheme);
+ static bool sortByUsername();
+ static void setSortByUsername(bool sortByUsername = true);
+ static bool sortByTitle();
+ static void setSortByTitle(bool sortByUsertitle = true);
+ static bool alwaysAllowAccess();
+ static void setAlwaysAllowAccess(bool alwaysAllowAccess);
+ static bool alwaysAllowUpdate();
+ static void setAlwaysAllowUpdate(bool alwaysAllowUpdate);
+ static bool searchInAllDatabases();//TODO!!
+ static void setSearchInAllDatabases(bool searchInAllDatabases);
+ static bool supportKphFields();
+ static void setSupportKphFields(bool supportKphFields);
+
+ static bool supportBrowserProxy();
+ static void setSupportBrowserProxy(bool enabled);
+ static bool useCustomProxy();
+ static void setUseCustomProxy(bool enabled);
+ static QString customProxyLocation();
+ static void setCustomProxyLocation(QString location);
+ static bool updateBinaryPath();
+ static void setUpdateBinaryPath(bool enabled);
+ static bool chromeSupport();
+ static void setChromeSupport(bool enabled);
+ static bool chromiumSupport();
+ static void setChromiumSupport(bool enabled);
+ static bool firefoxSupport();
+ static void setFirefoxSupport(bool enabled);
+ static bool vivaldiSupport();
+ static void setVivaldiSupport(bool enabled);
+
+ static bool passwordUseNumbers();
+ static void setPasswordUseNumbers(bool useNumbers);
+ static bool passwordUseLowercase();
+ static void setPasswordUseLowercase(bool useLowercase);
+ static bool passwordUseUppercase();
+ static void setPasswordUseUppercase(bool useUppercase);
+ static bool passwordUseSpecial();
+ static void setPasswordUseSpecial(bool useSpecial);
+ static bool passwordUseEASCII();
+ static void setPasswordUseEASCII(bool useEASCII);
+ static int passPhraseWordCount();
+ static void setPassPhraseWordCount(int wordCount);
+ static QString passPhraseWordSeparator();
+ static void setPassPhraseWordSeparator(QString separator);
+ static int generatorType();
+ static void setGeneratorType(int type);
+ static bool passwordEveryGroup();
+ static void setPasswordEveryGroup(bool everyGroup);
+ static bool passwordExcludeAlike();
+ static void setPasswordExcludeAlike(bool excludeAlike);
+ static int passwordLength();
+ static void setPasswordLength(int length);
+ static PasswordGenerator::CharClasses passwordCharClasses();
+ static PasswordGenerator::GeneratorFlags passwordGeneratorFlags();
+ static QString generatePassword();
+ static int getbits();
+ static void updateBinaryPaths(QString customProxyLocation = QString());
+
+private:
+ static PasswordGenerator m_passwordGenerator;
+ static PassphraseGenerator m_passPhraseGenerator;
+ static HostInstaller m_hostInstaller;
+};
+
+#endif // BROWSERSETTINGS_H
diff --git a/src/browser/CMakeLists.txt b/src/browser/CMakeLists.txt
new file mode 100755
index 000000000..61215c181
--- /dev/null
+++ b/src/browser/CMakeLists.txt
@@ -0,0 +1,37 @@
+# Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
+# Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+if(WITH_XC_BROWSER)
+ include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR})
+ find_package(sodium 1.0.12 REQUIRED)
+
+ set(keepassxcbrowser_SOURCES
+ BrowserAccessControlDialog.cpp
+ BrowserAction.cpp
+ BrowserClients.cpp
+ BrowserEntryConfig.cpp
+ BrowserOptionDialog.cpp
+ BrowserService.cpp
+ BrowserSettings.cpp
+ HostInstaller.cpp
+ NativeMessagingBase.cpp
+ NativeMessagingHost.cpp
+ Variant.cpp
+ )
+
+ add_library(keepassxcbrowser STATIC ${keepassxcbrowser_SOURCES})
+ target_link_libraries(keepassxcbrowser Qt5::Core Qt5::Concurrent Qt5::Widgets Qt5::Network sodium)
+endif()
diff --git a/src/browser/HostInstaller.cpp b/src/browser/HostInstaller.cpp
new file mode 100644
index 000000000..9b27ab1cf
--- /dev/null
+++ b/src/browser/HostInstaller.cpp
@@ -0,0 +1,240 @@
+/*
+* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
+* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
+*
+* This program is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 3 of the License, or
+* (at your option) any later version.
+*
+* This program 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 General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "HostInstaller.h"
+#include "config-keepassx.h"
+#include <QDir>
+#include <QFile>
+#include <QStandardPaths>
+#include <QJsonArray>
+#include <QJsonDocument>
+#include <QCoreApplication>
+#include <QProcessEnvironment>
+#include <QMessageBox>
+
+const QString HostInstaller::HOST_NAME = "org.keepassxc.keepassxc_browser";
+const QStringList HostInstaller::ALLOWED_ORIGINS = QStringList()
+ << "chrome-extension://iopaggbpplllidnfmcghoonnokmjoicf/"
+ << "chrome-extension://oboonakemofpalcgghocfoadofidjkkk/";
+
+const QStringList HostInstaller::ALLOWED_EXTENSIONS = QStringList()
+ << "keepassxc-browser@keepassxc.org";
+
+#if defined(Q_OS_OSX)
+ const QString HostInstaller::TARGET_DIR_CHROME = "/Library/Application Support/Google/Chrome/NativeMessagingHosts";
+ const QString HostInstaller::TARGET_DIR_CHROMIUM = "/Library/Application Support/Chromium/NativeMessagingHosts";
+ const QString HostInstaller::TARGET_DIR_FIREFOX = "/Library/Application Support/Mozilla/NativeMessagingHosts";
+ const QString HostInstaller::TARGET_DIR_VIVALDI = "/Library/Application Support/Vivaldi/NativeMessagingHosts";
+#elif defined(Q_OS_LINUX)
+ const QString HostInstaller::TARGET_DIR_CHROME = "/.config/google-chrome/NativeMessagingHosts";
+ const QString HostInstaller::TARGET_DIR_CHROMIUM = "/.config/chromium/NativeMessagingHosts";
+ const QString HostInstaller::TARGET_DIR_FIREFOX = "/.mozilla/native-messaging-hosts";
+ const QString HostInstaller::TARGET_DIR_VIVALDI = "/.config/vivaldi/NativeMessagingHosts";
+#elif defined(Q_OS_WIN)
+ const QString HostInstaller::TARGET_DIR_CHROME = "HKEY_CURRENT_USER\\Software\\Google\\Chrome\\NativeMessagingHosts\\" + HostInstaller::HOST_NAME;
+ const QString HostInstaller::TARGET_DIR_CHROMIUM = "HKEY_CURRENT_USER\\Software\\Chromium\\NativeMessagingHosts\\" + HostInstaller::HOST_NAME;
+ const QString HostInstaller::TARGET_DIR_FIREFOX = "HKEY_CURRENT_USER\\Software\\Mozilla\\NativeMessagingHosts\\" + HostInstaller::HOST_NAME;
+ const QString HostInstaller::TARGET_DIR_VIVALDI = "HKEY_CURRENT_USER\\Software\\Vivaldi\\NativeMessagingHosts\\" + HostInstaller::HOST_NAME;
+#endif
+
+HostInstaller::HostInstaller()
+{
+
+}
+
+bool HostInstaller::checkIfInstalled(SupportedBrowsers browser)
+{
+ QString fileName = getPath(browser);
+#ifdef Q_OS_WIN
+ QSettings settings(getTargetPath(browser), QSettings::NativeFormat);
+ return registryEntryFound(settings);
+#else
+ return QFile::exists(fileName);
+#endif
+}
+
+void HostInstaller::installBrowser(SupportedBrowsers browser, const bool& enabled, const bool& proxy, const QString& location)
+{
+ if (enabled) {
+ #ifdef Q_OS_WIN
+ // Create a registry key
+ QSettings settings(getTargetPath(browser), QSettings::NativeFormat);
+ if (!registryEntryFound(settings)) {
+ settings.setValue("Default", getPath(browser));
+ }
+ #endif
+ // Always create the script file
+ QJsonObject script = constructFile(browser, proxy, location);
+ if (!saveFile(browser, script)) {
+ QMessageBox::critical(0, tr("KeePassXC: Cannot save file!"),
+ tr("Cannot save the native messaging script file."), QMessageBox::Ok);
+ }
+ } else {
+ // Remove the script file
+ QString fileName = getPath(browser);
+ QFile::remove(fileName);
+ #ifdef Q_OS_WIN
+ // Remove the registry entry
+ QSettings settings(getTargetPath(browser), QSettings::NativeFormat);
+ if (registryEntryFound(settings)) {
+ settings.remove("Default");
+ }
+ #endif
+ }
+}
+
+void HostInstaller::updateBinaryPaths(const bool& proxy, const QString& location)
+{
+ for (int i = 0; i < 4; ++i) {
+ if (checkIfInstalled(static_cast<SupportedBrowsers>(i))) {
+ installBrowser(static_cast<SupportedBrowsers>(i), true, proxy, location);
+ }
+ }
+}
+
+QString HostInstaller::getTargetPath(SupportedBrowsers browser) const
+{
+ switch (browser) {
+ case SupportedBrowsers::CHROME: return HostInstaller::TARGET_DIR_CHROME;
+ case SupportedBrowsers::CHROMIUM: return HostInstaller::TARGET_DIR_CHROMIUM;
+ case SupportedBrowsers::FIREFOX: return HostInstaller::TARGET_DIR_FIREFOX;
+ case SupportedBrowsers::VIVALDI: return HostInstaller::TARGET_DIR_VIVALDI;
+ default: return QString();
+ }
+}
+
+QString HostInstaller::getBrowserName(SupportedBrowsers browser) const
+{
+ switch (browser) {
+ case SupportedBrowsers::CHROME: return "chrome";
+ case SupportedBrowsers::CHROMIUM: return "chromium";
+ case SupportedBrowsers::FIREFOX: return "firefox";
+ case SupportedBrowsers::VIVALDI: return "vivaldi";
+ default: return QString();
+ }
+}
+
+QString HostInstaller::getPath(SupportedBrowsers browser) const
+{
+#ifdef Q_OS_WIN
+ // If portable settings file exists save the JSON scripts to application folder
+ QString userPath;
+ QString portablePath = QCoreApplication::applicationDirPath() + "/keepassxc.ini";
+ if (QFile::exists(portablePath)) {
+ userPath = QCoreApplication::applicationDirPath();
+ } else {
+ userPath = QDir::fromNativeSeparators(QStandardPaths::writableLocation(QStandardPaths::DataLocation));
+ }
+
+ QString winPath = QString("%1/%2_%3.json").arg(userPath, HostInstaller::HOST_NAME, getBrowserName(browser));
+ winPath.replace("/","\\");
+ return winPath;
+#else
+ QString path = getTargetPath(browser);
+ return QString("%1%2/%3.json").arg(QDir::homePath(), path, HostInstaller::HOST_NAME);
+#endif
+}
+
+QString HostInstaller::getInstallDir(SupportedBrowsers browser) const
+{
+ QString path = getTargetPath(browser);
+#ifdef Q_OS_WIN
+ return QCoreApplication::applicationDirPath();
+#else
+ return QString("%1%2").arg(QDir::homePath(), path);
+#endif
+}
+
+QJsonObject HostInstaller::constructFile(SupportedBrowsers browser, const bool& proxy, const QString& location)
+{
+ QString path;
+#ifdef KEEPASSXC_DIST_APPIMAGE
+ if (proxy && !location.isEmpty()) {
+ path = location;
+ } else {
+ path = QProcessEnvironment::systemEnvironment().value("APPIMAGE");
+ }
+#else
+ if (proxy) {
+ if (!location.isEmpty()) {
+ path = location;
+ } else {
+ path = QFileInfo(QCoreApplication::applicationFilePath()).absolutePath();
+ path.append("/keepassxc-proxy");
+#ifdef Q_OS_WIN
+ path.append(".exe");
+#endif
+ }
+ } else {
+ path = QFileInfo(QCoreApplication::applicationFilePath()).absoluteFilePath();
+ }
+#ifdef Q_OS_WIN
+ path.replace("/","\\");
+#endif
+
+#endif // #ifdef KEEPASSXC_DIST_APPIMAGE
+
+ QJsonObject script;
+ script["name"] = HostInstaller::HOST_NAME;
+ script["description"] = "KeePassXC integration with native messaging support";
+ script["path"] = path;
+ script["type"] = "stdio";
+
+ QJsonArray arr;
+ if (browser == SupportedBrowsers::FIREFOX) {
+ for (const QString extension : HostInstaller::ALLOWED_EXTENSIONS) {
+ arr.append(extension);
+ }
+ script["allowed_extensions"] = arr;
+ } else {
+ for (const QString origin : HostInstaller::ALLOWED_ORIGINS) {
+ arr.append(origin);
+ }
+ script["allowed_origins"] = arr;
+ }
+
+ return script;
+}
+
+bool HostInstaller::registryEntryFound(const QSettings& settings)
+{
+ return !settings.value("Default").isNull();
+}
+
+bool HostInstaller::saveFile(SupportedBrowsers browser, const QJsonObject& script)
+{
+ QString path = getPath(browser);
+ QString installDir = getInstallDir(browser);
+ QDir dir(installDir);
+ if (!dir.exists()) {
+ QDir().mkpath(installDir);
+ }
+
+ QFile scriptFile(path);
+ if (!scriptFile.open(QIODevice::WriteOnly)) {
+ return false;
+ }
+
+ QJsonDocument doc(script);
+ qint64 bytesWritten = scriptFile.write(doc.toJson());
+ if (bytesWritten < 0) {
+ return false;
+ }
+
+ return true;
+}
diff --git a/src/browser/HostInstaller.h b/src/browser/HostInstaller.h
new file mode 100644
index 000000000..c3fc85620
--- /dev/null
+++ b/src/browser/HostInstaller.h
@@ -0,0 +1,63 @@
+/*
+* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
+* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
+*
+* This program is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 3 of the License, or
+* (at your option) any later version.
+*
+* This program 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 General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef HOSTINSTALLER_H
+#define HOSTINSTALLER_H
+
+#include <QObject>
+#include <QJsonObject>
+#include <QSettings>
+
+class HostInstaller : public QObject
+{
+ Q_OBJECT
+
+public:
+ enum SupportedBrowsers : int {
+ CHROME = 0,
+ CHROMIUM = 1,
+ FIREFOX = 2,
+ VIVALDI = 3
+ };
+
+public:
+ HostInstaller();
+ bool checkIfInstalled(SupportedBrowsers browser);
+ void installBrowser(SupportedBrowsers browser, const bool& enabled, const bool& proxy = false, const QString& location = "");
+ void updateBinaryPaths(const bool& proxy, const QString& location = "");
+
+private:
+ QString getTargetPath(SupportedBrowsers browser) const;
+ QString getBrowserName(SupportedBrowsers browser) const;
+ QString getPath(SupportedBrowsers browser) const;
+ QString getInstallDir(SupportedBrowsers browser) const;
+ QJsonObject constructFile(SupportedBrowsers browser, const bool& proxy, const QString& location);
+ bool registryEntryFound(const QSettings& settings);
+ bool saveFile(SupportedBrowsers browser, const QJsonObject& script);
+
+private:
+ static const QString HOST_NAME;
+ static const QStringList ALLOWED_EXTENSIONS;
+ static const QStringList ALLOWED_ORIGINS;
+ static const QString TARGET_DIR_CHROME;
+ static const QString TARGET_DIR_CHROMIUM;
+ static const QString TARGET_DIR_FIREFOX;
+ static const QString TARGET_DIR_VIVALDI;
+};
+
+#endif // HOSTINSTALLER_H
diff --git a/src/browser/NativeMessagingBase.cpp b/src/browser/NativeMessagingBase.cpp
new file mode 100644
index 000000000..743953e95
--- /dev/null
+++ b/src/browser/NativeMessagingBase.cpp
@@ -0,0 +1,141 @@
+/*
+* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
+* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
+*
+* This program is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 3 of the License, or
+* (at your option) any later version.
+*
+* This program 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 General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "NativeMessagingBase.h"
+#include <QStandardPaths>
+
+#if defined(Q_OS_UNIX) && !defined(Q_OS_LINUX)
+#include <sys/types.h>
+#include <sys/event.h>
+#include <sys/time.h>
+#include <unistd.h>
+#endif
+
+#ifdef Q_OS_LINUX
+#include <sys/epoll.h>
+#include <unistd.h>
+#endif
+
+#ifdef Q_OS_WIN
+#include <fcntl.h>
+#include <io.h>
+#endif
+
+NativeMessagingBase::NativeMessagingBase()
+{
+#ifdef Q_OS_WIN
+ _setmode(_fileno(stdin), _O_BINARY);
+ _setmode(_fileno(stdout), _O_BINARY);
+#else
+ m_notifier.reset(new QSocketNotifier(fileno(stdin), QSocketNotifier::Read, this));
+ connect(m_notifier.data(), SIGNAL(activated(int)), this, SLOT(newNativeMessage()));
+#endif
+}
+
+void NativeMessagingBase::newNativeMessage()
+{
+#if defined(Q_OS_UNIX) && !defined(Q_OS_LINUX)
+ struct kevent ev[1];
+ struct timespec ts = { 5, 0 };
+
+ int fd = kqueue();
+ if (fd == -1) {
+ m_notifier->setEnabled(false);
+ return;
+ }
+
+ EV_SET(ev, fileno(stdin), EVFILT_READ, EV_ADD, 0, 0, nullptr);
+ if (kevent(fd, ev, 1, nullptr, 0, &ts) == -1) {
+ m_notifier->setEnabled(false);
+ return;
+ }
+
+ int ret = kevent(fd, NULL, 0, ev, 1, &ts);
+ if (ret < 1) {
+ m_notifier->setEnabled(false);
+ ::close(fd);
+ return;
+ }
+#elif defined(Q_OS_LINUX)
+ int fd = epoll_create(5);
+ struct epoll_event event;
+ event.events = EPOLLIN;
+ event.data.fd = 0;
+ if (epoll_ctl(fd, EPOLL_CTL_ADD, 0, &event) != 0) {
+ m_notifier->setEnabled(false);
+ return;
+ }
+
+ if (epoll_wait(fd, &event, 1, 5000) < 1) {
+ m_notifier->setEnabled(false);
+ ::close(fd);
+ return;
+ }
+#endif
+ readLength();
+#ifndef Q_OS_WIN
+ ::close(fd);
+#endif
+}
+
+void NativeMessagingBase::readNativeMessages()
+{
+#ifdef Q_OS_WIN
+ quint32 length = 0;
+ while (m_running.load() && !std::cin.eof()) {
+ length = 0;
+ std::cin.read(reinterpret_cast<char*>(&length), 4);
+ readStdIn(length);
+ QThread::msleep(1);
+ }
+#endif
+}
+
+QString NativeMessagingBase::jsonToString(const QJsonObject& json) const
+{
+ return QString(QJsonDocument(json).toJson(QJsonDocument::Compact));
+}
+
+void NativeMessagingBase::sendReply(const QJsonObject& json)
+{
+ if (!json.isEmpty()) {
+ sendReply(jsonToString(json));
+ }
+}
+
+void NativeMessagingBase::sendReply(const QString& reply)
+{
+ if (!reply.isEmpty()) {
+ uint len = reply.length();
+ std::cout << char(((len>>0) & 0xFF)) << char(((len>>8) & 0xFF)) << char(((len>>16) & 0xFF)) << char(((len>>24) & 0xFF));
+ std::cout << reply.toStdString() << std::flush;
+ }
+}
+
+QString NativeMessagingBase::getLocalServerPath() const
+{
+#if defined(Q_OS_WIN)
+ return QStandardPaths::writableLocation(QStandardPaths::TempLocation) + "/kpxc_server";
+#elif defined(Q_OS_UNIX) && !defined(Q_OS_MAC)
+ // Use XDG_RUNTIME_DIR instead of /tmp/ if it's available
+ QString path = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation) + "/kpxc_server";
+ return path.isEmpty() ? "/tmp/kpxc_server" : path;
+#else // Q_OS_MAC and others
+ return "/tmp/kpxc_server";
+#endif
+}
diff --git a/src/browser/NativeMessagingBase.h b/src/browser/NativeMessagingBase.h
new file mode 100644
index 000000000..4253b7585
--- /dev/null
+++ b/src/browser/NativeMessagingBase.h
@@ -0,0 +1,61 @@
+/*
+* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
+* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
+*
+* This program is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 3 of the License, or
+* (at your option) any later version.
+*
+* This program 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 General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef NATIVEMESSAGINGBASE_H
+#define NATIVEMESSAGINGBASE_H
+
+#include <QObject>
+#include <QJsonObject>
+#include <QJsonDocument>
+#include <QFuture>
+#include <QtConcurrent/QtConcurrent>
+#include <QMutex>
+#include <QSocketNotifier>
+#include <QLocalServer>
+#include <QLocalSocket>
+#include <QAtomicInteger>
+#include <iostream>
+#include <unistd.h>
+
+class NativeMessagingBase : public QObject
+{
+ Q_OBJECT
+
+public:
+ explicit NativeMessagingBase();
+ ~NativeMessagingBase() = default;
+
+protected slots:
+ void newNativeMessage();
+
+protected:
+ virtual void readLength() = 0;
+ virtual void readStdIn(const quint32 length) = 0;
+ void readNativeMessages();
+ QString jsonToString(const QJsonObject& json) const;
+ void sendReply(const QJsonObject& json);
+ void sendReply(const QString& reply);
+ QString getLocalServerPath() const;
+
+protected:
+ QAtomicInteger<quint8> m_running;
+ QSharedPointer<QSocketNotifier> m_notifier;
+ QFuture<void> m_future;
+};
+
+#endif // NATIVEMESSAGINGBASE_H
diff --git a/src/browser/NativeMessagingHost.cpp b/src/browser/NativeMessagingHost.cpp
new file mode 100755
index 000000000..4dfa87d51
--- /dev/null
+++ b/src/browser/NativeMessagingHost.cpp
@@ -0,0 +1,208 @@
+/*
+* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
+* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
+*
+* This program is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 3 of the License, or
+* (at your option) any later version.
+*
+* This program 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 General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <QMutexLocker>
+#include <QtNetwork>
+#include <iostream>
+#include "sodium.h"
+#include "NativeMessagingHost.h"
+#include "BrowserSettings.h"
+
+NativeMessagingHost::NativeMessagingHost(DatabaseTabWidget* parent) :
+ NativeMessagingBase(),
+ m_mutex(QMutex::Recursive),
+ m_browserClients(m_browserService),
+ m_browserService(parent)
+{
+ m_localServer.reset(new QLocalServer(this));
+ m_localServer->setSocketOptions(QLocalServer::UserAccessOption);
+ m_running.store(false);
+
+ if (BrowserSettings::isEnabled() && !m_running) {
+ run();
+ }
+
+ connect(&m_browserService, SIGNAL(databaseLocked()), this, SLOT(databaseLocked()));
+ connect(&m_browserService, SIGNAL(databaseUnlocked()), this, SLOT(databaseUnlocked()));
+}
+
+NativeMessagingHost::~NativeMessagingHost()
+{
+ stop();
+}
+
+int NativeMessagingHost::init()
+{
+ QMutexLocker locker(&m_mutex);
+ return sodium_init();
+}
+
+void NativeMessagingHost::run()
+{
+ QMutexLocker locker(&m_mutex);
+ if (!m_running.load() && init() == -1) {
+ return;
+ }
+
+ // Update KeePassXC/keepassxc-proxy binary paths to Native Messaging scripts
+ if (BrowserSettings::updateBinaryPath()) {
+ BrowserSettings::updateBinaryPaths(BrowserSettings::useCustomProxy() ? BrowserSettings::customProxyLocation() : "");
+ }
+
+ m_running.store(true);
+#ifdef Q_OS_WIN
+ m_future = QtConcurrent::run(this, static_cast<void(NativeMessagingHost::*)()>(&NativeMessagingHost::readNativeMessages));
+#endif
+
+ if (BrowserSettings::supportBrowserProxy()) {
+ QString serverPath = getLocalServerPath();
+ QFile::remove(serverPath);
+
+ if (m_localServer->isListening()) {
+ m_localServer->close();
+ }
+
+ m_localServer->listen(serverPath);
+ connect(m_localServer.data(), SIGNAL(newConnection()), this, SLOT(newLocalConnection()));
+ } else {
+ m_localServer->close();
+ }
+}
+
+void NativeMessagingHost::stop()
+{
+ databaseLocked();
+ QMutexLocker locker(&m_mutex);
+ m_socketList.clear();
+ m_running.testAndSetOrdered(true, false);
+ m_future.waitForFinished();
+ m_localServer->close();
+}
+
+void NativeMessagingHost::readLength()
+{
+ quint32 length = 0;
+ std::cin.read(reinterpret_cast<char*>(&length), 4);
+ if (!std::cin.eof() && length > 0) {
+ readStdIn(length);
+ } else {
+ m_notifier->setEnabled(false);
+ }
+}
+
+void NativeMessagingHost::readStdIn(const quint32 length)
+{
+ if (length > 0) {
+ QByteArray arr;
+ arr.reserve(length);
+
+ for (quint32 i = 0; i < length; ++i) {
+ arr.append(getchar());
+ }
+
+ if (arr.length() > 0) {
+ QMutexLocker locker(&m_mutex);
+ sendReply(m_browserClients.readResponse(arr));
+ }
+ }
+}
+
+void NativeMessagingHost::newLocalConnection()
+{
+ QLocalSocket* socket = m_localServer->nextPendingConnection();
+ if (socket) {
+ connect(socket, SIGNAL(readyRead()), this, SLOT(newLocalMessage()));
+ connect(socket, SIGNAL(disconnected()), this, SLOT(disconnectSocket()));
+ }
+}
+
+void NativeMessagingHost::newLocalMessage()
+{
+ QLocalSocket* socket = qobject_cast<QLocalSocket*>(QObject::sender());
+
+ if (!socket || socket->bytesAvailable() <= 0) {
+ return;
+ }
+
+ QByteArray arr = socket->readAll();
+ if (arr.isEmpty()) {
+ return;
+ }
+
+ QMutexLocker locker(&m_mutex);
+ if (!m_socketList.contains(socket)) {
+ m_socketList.push_back(socket);
+ }
+
+ QString reply = jsonToString(m_browserClients.readResponse(arr));
+ if (socket && socket->isValid() && socket->state() == QLocalSocket::ConnectedState) {
+ QByteArray arr = reply.toUtf8();
+ socket->write(arr.constData(), arr.length());
+ socket->flush();
+ }
+}
+
+void NativeMessagingHost::sendReplyToAllClients(const QJsonObject& json)
+{
+ QString reply = jsonToString(json);
+ QMutexLocker locker(&m_mutex);
+ for (const auto socket : m_socketList) {
+ if (socket && socket->isValid() && socket->state() == QLocalSocket::ConnectedState) {
+ QByteArray arr = reply.toUtf8();
+ socket->write(arr.constData(), arr.length());
+ socket->flush();
+ }
+ }
+}
+
+void NativeMessagingHost::disconnectSocket()
+{
+ QLocalSocket* socket(qobject_cast<QLocalSocket*>(QObject::sender()));
+ QMutexLocker locker(&m_mutex);
+ for (auto s : m_socketList) {
+ if (s == socket) {
+ m_socketList.removeOne(s);
+ }
+ }
+}
+
+void NativeMessagingHost::removeSharedEncryptionKeys()
+{
+ QMutexLocker locker(&m_mutex);
+ m_browserService.removeSharedEncryptionKeys();
+}
+
+void NativeMessagingHost::removeStoredPermissions()
+{
+ QMutexLocker locker(&m_mutex);
+ m_browserService.removeStoredPermissions();
+}
+
+void NativeMessagingHost::databaseLocked()
+{
+ QJsonObject response;
+ response["action"] = "database-locked";
+ sendReplyToAllClients(response);
+}
+
+void NativeMessagingHost::databaseUnlocked()
+{
+ QJsonObject response;
+ response["action"] = "database-unlocked";
+ sendReplyToAllClients(response);
+}
diff --git a/src/browser/NativeMessagingHost.h b/src/browser/NativeMessagingHost.h
new file mode 100755
index 000000000..80825237b
--- /dev/null
+++ b/src/browser/NativeMessagingHost.h
@@ -0,0 +1,67 @@
+/*
+* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
+* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
+*
+* This program is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 3 of the License, or
+* (at your option) any later version.
+*
+* This program 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 General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef NATIVEMESSAGINGHOST_H
+#define NATIVEMESSAGINGHOST_H
+
+#include "NativeMessagingBase.h"
+#include "BrowserClients.h"
+#include "BrowserService.h"
+#include "gui/DatabaseTabWidget.h"
+
+class NativeMessagingHost : public NativeMessagingBase
+{
+ Q_OBJECT
+
+ typedef QList<QLocalSocket*> SocketList;
+
+public:
+ explicit NativeMessagingHost(DatabaseTabWidget* parent = 0);
+ ~NativeMessagingHost();
+ int init();
+ void run();
+ void stop();
+
+public slots:
+ void removeSharedEncryptionKeys();
+ void removeStoredPermissions();
+
+signals:
+ void quit();
+
+private:
+ void readLength();
+ void readStdIn(const quint32 length);
+ void sendReplyToAllClients(const QJsonObject& json);
+
+private slots:
+ void databaseLocked();
+ void databaseUnlocked();
+ void newLocalConnection();
+ void newLocalMessage();
+ void disconnectSocket();
+
+private:
+ QMutex m_mutex;
+ BrowserClients m_browserClients;
+ BrowserService m_browserService;
+ QSharedPointer<QLocalServer> m_localServer;
+ SocketList m_socketList;
+};
+
+#endif // NATIVEMESSAGINGHOST_H
diff --git a/src/browser/Variant.cpp b/src/browser/Variant.cpp
new file mode 100644
index 000000000..2d42ac4ec
--- /dev/null
+++ b/src/browser/Variant.cpp
@@ -0,0 +1,37 @@
+/*
+* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
+*
+* This program is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 3 of the License, or
+* (at your option) any later version.
+*
+* This program 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 General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "Variant.h"
+
+QVariantMap qo2qv(const QObject* object, const QStringList& ignoredProperties)
+{
+ QVariantMap result;
+ const QMetaObject* metaobject = object->metaObject();
+ int count = metaobject->propertyCount();
+ for (int i = 0; i < count; ++i) {
+ QMetaProperty metaproperty = metaobject->property(i);
+ const char* name = metaproperty.name();
+
+ if (ignoredProperties.contains(QLatin1String(name)) || (!metaproperty.isReadable())) {
+ continue;
+ }
+
+ QVariant value = object->property(name);
+ result[QLatin1String(name)] = value;
+ }
+ return result;
+}
diff --git a/src/browser/Variant.h b/src/browser/Variant.h
new file mode 100644
index 000000000..e467b4211
--- /dev/null
+++ b/src/browser/Variant.h
@@ -0,0 +1,25 @@
+/*
+* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
+*
+* This program is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 3 of the License, or
+* (at your option) any later version.
+*
+* This program 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 General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef VARIANT_H
+#define VARIANT_H
+
+#include <QtCore>
+
+QVariantMap qo2qv(const QObject* object, const QStringList& ignoredProperties = QStringList(QString(QLatin1String("objectName"))));
+
+#endif // VARIANT_H