mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2025-11-07 01:12:57 -05:00
Browser Integration: Add support for WebSocket listener
This commit is contained in:
parent
f53c7e5af5
commit
83e73398cb
15 changed files with 297 additions and 34 deletions
|
|
@ -491,7 +491,7 @@ endif()
|
|||
|
||||
include(CLangFormat)
|
||||
|
||||
set(QT_COMPONENTS Core Network Concurrent Gui Svg Widgets Test LinguistTools)
|
||||
set(QT_COMPONENTS Core Network Concurrent Gui Svg Widgets Test LinguistTools WebSockets)
|
||||
if(UNIX AND NOT APPLE)
|
||||
if(WITH_XC_X11)
|
||||
list(APPEND QT_COMPONENTS X11Extras)
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ static const QString BROWSER_REQUEST_REQUEST_AUTOTYPE = QStringLiteral("request-
|
|||
static const QString BROWSER_REQUEST_SET_LOGIN = QStringLiteral("set-login");
|
||||
static const QString BROWSER_REQUEST_TEST_ASSOCIATE = QStringLiteral("test-associate");
|
||||
|
||||
QJsonObject BrowserAction::processClientMessage(QLocalSocket* socket, const QJsonObject& json)
|
||||
template <typename T> QJsonObject BrowserAction::processClientMessage(T* socket, const QJsonObject& json)
|
||||
{
|
||||
if (json.isEmpty()) {
|
||||
return getErrorReply("", ERROR_KEEPASS_EMPTY_MESSAGE_RECEIVED);
|
||||
|
|
@ -75,10 +75,14 @@ QJsonObject BrowserAction::processClientMessage(QLocalSocket* socket, const QJso
|
|||
return handleAction(socket, json);
|
||||
}
|
||||
|
||||
// Explicit template instantiation
|
||||
template QJsonObject BrowserAction::processClientMessage<QLocalSocket>(QLocalSocket*, const QJsonObject&);
|
||||
template QJsonObject BrowserAction::processClientMessage<QWebSocket>(QWebSocket*, const QJsonObject&);
|
||||
|
||||
// Private functions
|
||||
///////////////////////
|
||||
|
||||
QJsonObject BrowserAction::handleAction(QLocalSocket* socket, const QJsonObject& json)
|
||||
template <typename T> QJsonObject BrowserAction::handleAction(T* socket, const QJsonObject& json)
|
||||
{
|
||||
QString action = json.value("action").toString();
|
||||
|
||||
|
|
@ -262,7 +266,8 @@ QJsonObject BrowserAction::handleGetLogins(const QJsonObject& json, const QStrin
|
|||
return buildResponse(action, browserRequest.incrementedNonce, params);
|
||||
}
|
||||
|
||||
QJsonObject BrowserAction::handleGeneratePassword(QLocalSocket* socket, const QJsonObject& json, const QString& action)
|
||||
template <typename T>
|
||||
QJsonObject BrowserAction::handleGeneratePassword(T* socket, const QJsonObject& json, const QString& action)
|
||||
{
|
||||
const auto browserRequest = decodeRequest(json);
|
||||
if (browserRequest.isEmpty()) {
|
||||
|
|
@ -281,11 +286,12 @@ QJsonObject BrowserAction::handleGeneratePassword(QLocalSocket* socket, const QJ
|
|||
}
|
||||
|
||||
// Show the existing password generator
|
||||
browserService()->showPasswordGenerator({});
|
||||
// browserService()->showPasswordGenerator({});
|
||||
browserService()->showPasswordGenerator(KeyPairMessage<T>{});
|
||||
return errorReply;
|
||||
}
|
||||
|
||||
KeyPairMessage keyPairMessage{socket, browserRequest.incrementedNonce, m_clientPublicKey, m_secretKey};
|
||||
KeyPairMessage<T> keyPairMessage{socket, browserRequest.incrementedNonce, m_clientPublicKey, m_secretKey};
|
||||
|
||||
browserService()->showPasswordGenerator(keyPairMessage);
|
||||
return {};
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@
|
|||
#include <QString>
|
||||
|
||||
class QLocalSocket;
|
||||
class QWebSocket;
|
||||
|
||||
struct BrowserRequest
|
||||
{
|
||||
|
|
@ -66,16 +67,16 @@ public:
|
|||
explicit BrowserAction() = default;
|
||||
~BrowserAction() = default;
|
||||
|
||||
QJsonObject processClientMessage(QLocalSocket* socket, const QJsonObject& json);
|
||||
template <typename T> QJsonObject processClientMessage(T* socket, const QJsonObject& json);
|
||||
|
||||
private:
|
||||
QJsonObject handleAction(QLocalSocket* socket, const QJsonObject& json);
|
||||
template <typename T> QJsonObject handleAction(T* socket, 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(QLocalSocket* socket, const QJsonObject& json, const QString& action);
|
||||
template <typename T> QJsonObject handleGeneratePassword(T* socket, const QJsonObject& json, const QString& action);
|
||||
QJsonObject handleSetLogin(const QJsonObject& json, const QString& action);
|
||||
QJsonObject handleLockDatabase(const QJsonObject& json, const QString& action);
|
||||
QJsonObject handleGetDatabaseGroups(const QJsonObject& json, const QString& action);
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@
|
|||
#include "BrowserHost.h"
|
||||
#include "BrowserMessageBuilder.h"
|
||||
#include "BrowserSettings.h"
|
||||
#include "BrowserWebSocketHost.h"
|
||||
#include "core/EntryAttributes.h"
|
||||
#include "core/Tools.h"
|
||||
#include "gui/MainWindow.h"
|
||||
|
|
@ -53,6 +54,7 @@
|
|||
#include <QProgressDialog>
|
||||
#include <QStringView>
|
||||
#include <QUrl>
|
||||
#include <QtWebSockets/qwebsocket.h>
|
||||
|
||||
const QString BrowserService::KEEPASSXCBROWSER_NAME = QStringLiteral("KeePassXC-Browser Settings");
|
||||
const QString BrowserService::KEEPASSXCBROWSER_OLD_NAME = QStringLiteral("keepassxc-browser Settings");
|
||||
|
|
@ -78,12 +80,17 @@ Q_GLOBAL_STATIC(BrowserService, s_browserService);
|
|||
BrowserService::BrowserService()
|
||||
: QObject()
|
||||
, m_browserHost(new BrowserHost)
|
||||
, m_browserWebSocketHost(new BrowserWebSocketHost)
|
||||
, m_dialogActive(false)
|
||||
, m_bringToFrontRequested(false)
|
||||
, m_prevWindowState(WindowState::Normal)
|
||||
, m_keepassBrowserUUID(Tools::hexToUuid("de887cc3036343b8974b5911b8816224"))
|
||||
{
|
||||
connect(m_browserHost, &BrowserHost::clientMessageReceived, this, &BrowserService::processClientMessage);
|
||||
connect(m_browserHost, &BrowserHost::clientMessageReceived, this, &BrowserService::processLocalSocketClientMessage);
|
||||
connect(m_browserWebSocketHost,
|
||||
&BrowserWebSocketHost::clientMessageReceived,
|
||||
this,
|
||||
&BrowserService::processWebSocketClientMessage);
|
||||
connect(getMainWindow(), &MainWindow::databaseUnlocked, this, &BrowserService::databaseUnlocked);
|
||||
connect(getMainWindow(), &MainWindow::databaseLocked, this, &BrowserService::databaseLocked);
|
||||
connect(getMainWindow(), &MainWindow::activeDatabaseChanged, this, &BrowserService::activeDatabaseChanged);
|
||||
|
|
@ -109,8 +116,12 @@ void BrowserService::setEnabled(bool enabled)
|
|||
}
|
||||
|
||||
m_browserHost->start();
|
||||
if (browserSettings()->webSocketSupport()) {
|
||||
m_browserWebSocketHost->start();
|
||||
}
|
||||
} else {
|
||||
m_browserHost->stop();
|
||||
m_browserWebSocketHost->stop();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -527,7 +538,7 @@ QList<Entry*> BrowserService::confirmEntries(QList<Entry*>& entriesToConfirm,
|
|||
return allowedEntries;
|
||||
}
|
||||
|
||||
void BrowserService::showPasswordGenerator(const KeyPairMessage& keyPairMessage)
|
||||
template <typename T> void BrowserService::showPasswordGenerator(const KeyPairMessage<T>& keyPairMessage)
|
||||
{
|
||||
if (!m_passwordGenerator) {
|
||||
m_passwordGenerator = PasswordGeneratorWidget::popupGenerator();
|
||||
|
|
@ -539,7 +550,11 @@ void BrowserService::showPasswordGenerator(const KeyPairMessage& keyPairMessage)
|
|||
if (!m_passwordGenerator->isPasswordGenerated()) {
|
||||
auto errorMessage = browserMessageBuilder()->getErrorReply(
|
||||
"generate-password", ERROR_KEEPASS_ACTION_CANCELLED_OR_DENIED);
|
||||
m_browserHost->sendClientMessage(keyPairMessage.socket, errorMessage);
|
||||
if constexpr (std::is_same<T, QWebSocket>::value) {
|
||||
m_browserWebSocketHost->sendClientMessage(keyPairMessage.socket, errorMessage);
|
||||
} else {
|
||||
m_browserHost->sendClientMessage(keyPairMessage.socket, errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
QTimer::singleShot(50, this, [&] { hideWindow(); });
|
||||
|
|
@ -550,12 +565,24 @@ void BrowserService::showPasswordGenerator(const KeyPairMessage& keyPairMessage)
|
|||
m_passwordGenerator.data(),
|
||||
[this, keyPairMessage](const QString& password) {
|
||||
const Parameters params{{"password", password}};
|
||||
m_browserHost->sendClientMessage(keyPairMessage.socket,
|
||||
browserMessageBuilder()->buildResponse("generate-password",
|
||||
keyPairMessage.nonce,
|
||||
params,
|
||||
keyPairMessage.publicKey,
|
||||
keyPairMessage.secretKey));
|
||||
if constexpr (std::is_same<T, QWebSocket>::value) {
|
||||
m_browserWebSocketHost->sendClientMessage(
|
||||
keyPairMessage.socket,
|
||||
browserMessageBuilder()->buildResponse("generate-password",
|
||||
keyPairMessage.nonce,
|
||||
params,
|
||||
keyPairMessage.publicKey,
|
||||
keyPairMessage.secretKey));
|
||||
|
||||
} else {
|
||||
m_browserHost->sendClientMessage(
|
||||
keyPairMessage.socket,
|
||||
browserMessageBuilder()->buildResponse("generate-password",
|
||||
keyPairMessage.nonce,
|
||||
params,
|
||||
keyPairMessage.publicKey,
|
||||
keyPairMessage.secretKey));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -565,6 +592,9 @@ void BrowserService::showPasswordGenerator(const KeyPairMessage& keyPairMessage)
|
|||
m_passwordGenerator->activateWindow();
|
||||
}
|
||||
|
||||
template void BrowserService::showPasswordGenerator<QLocalSocket>(const KeyPairMessage<QLocalSocket>&);
|
||||
template void BrowserService::showPasswordGenerator<QWebSocket>(const KeyPairMessage<QWebSocket>&);
|
||||
|
||||
bool BrowserService::isPasswordGeneratorRequested() const
|
||||
{
|
||||
return m_passwordGenerator && m_passwordGenerator->isVisible();
|
||||
|
|
@ -1763,11 +1793,23 @@ void BrowserService::handleDatabaseUnlockDialogFinished(bool accepted, DatabaseW
|
|||
}
|
||||
}
|
||||
|
||||
void BrowserService::processClientMessage(QLocalSocket* socket, const QJsonObject& message)
|
||||
void BrowserService::processLocalSocketClientMessage(QLocalSocket* socket, const QJsonObject& message)
|
||||
{
|
||||
auto response = processClientMessage<QLocalSocket>(socket, message);
|
||||
m_browserHost->sendClientMessage(socket, response);
|
||||
}
|
||||
|
||||
void BrowserService::processWebSocketClientMessage(QWebSocket* socket, const QJsonObject& message)
|
||||
{
|
||||
auto response = processClientMessage<QWebSocket>(socket, message);
|
||||
m_browserWebSocketHost->sendClientMessage(socket, response);
|
||||
}
|
||||
|
||||
template <typename T> QJsonObject BrowserService::processClientMessage(T* socket, const QJsonObject& message)
|
||||
{
|
||||
auto clientID = message["clientID"].toString();
|
||||
if (clientID.isEmpty()) {
|
||||
return;
|
||||
return {};
|
||||
}
|
||||
|
||||
// Create a new client action if we haven't seen this id yet
|
||||
|
|
@ -1776,6 +1818,7 @@ void BrowserService::processClientMessage(QLocalSocket* socket, const QJsonObjec
|
|||
}
|
||||
|
||||
auto& action = m_browserClients.value(clientID);
|
||||
auto response = action->processClientMessage(socket, message);
|
||||
m_browserHost->sendClientMessage(socket, response);
|
||||
return action->processClientMessage<T>(socket, message);
|
||||
// auto response = action->processClientMessage(socket, message);
|
||||
// m_browserHost->sendClientMessage(socket, response);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@
|
|||
#include "gui/PasswordGeneratorWidget.h"
|
||||
|
||||
class QLocalSocket;
|
||||
class QWebSocket;
|
||||
|
||||
typedef QPair<QString, QString> StringPair;
|
||||
typedef QList<StringPair> StringPairList;
|
||||
|
|
@ -35,9 +36,9 @@ enum
|
|||
max_length = 16 * 1024
|
||||
};
|
||||
|
||||
struct KeyPairMessage
|
||||
template <typename T> struct KeyPairMessage
|
||||
{
|
||||
QLocalSocket* socket;
|
||||
T* socket;
|
||||
QString nonce;
|
||||
QString publicKey;
|
||||
QString secretKey;
|
||||
|
|
@ -58,6 +59,7 @@ struct EntryParameters
|
|||
|
||||
class DatabaseWidget;
|
||||
class BrowserHost;
|
||||
class BrowserWebSocketHost;
|
||||
class BrowserAction;
|
||||
|
||||
class BrowserService : public QObject
|
||||
|
|
@ -82,7 +84,7 @@ public:
|
|||
QJsonArray getDatabaseEntries();
|
||||
QJsonObject createNewGroup(const QString& groupName, bool isPasskeysGroup = false);
|
||||
QString getCurrentTotp(const QString& uuid);
|
||||
void showPasswordGenerator(const KeyPairMessage& keyPairMessage);
|
||||
template <typename T> void showPasswordGenerator(const KeyPairMessage<T>& keyPairMessage);
|
||||
bool isPasswordGeneratorRequested() const;
|
||||
QSharedPointer<Database> getDatabase(const QUuid& rootGroupUuid = {});
|
||||
QSharedPointer<Database> selectedDatabase();
|
||||
|
|
@ -137,7 +139,7 @@ public:
|
|||
|
||||
signals:
|
||||
void requestUnlock();
|
||||
void passwordGenerated(QLocalSocket* socket, const QString& password, const QString& nonce);
|
||||
void passwordGenerated(QWebSocket* socket, const QString& password, const QString& nonce);
|
||||
|
||||
public slots:
|
||||
void databaseLocked(DatabaseWidget* dbWidget);
|
||||
|
|
@ -145,7 +147,8 @@ public slots:
|
|||
void activeDatabaseChanged(DatabaseWidget* dbWidget);
|
||||
|
||||
private slots:
|
||||
void processClientMessage(QLocalSocket* socket, const QJsonObject& message);
|
||||
void processLocalSocketClientMessage(QLocalSocket* socket, const QJsonObject& message);
|
||||
void processWebSocketClientMessage(QWebSocket* socket, const QJsonObject& message);
|
||||
void handleDatabaseUnlockDialogFinished(bool accepted, DatabaseWidget* dbWidget);
|
||||
|
||||
private:
|
||||
|
|
@ -208,8 +211,10 @@ private:
|
|||
void hideWindow() const;
|
||||
void raiseWindow(const bool force = false);
|
||||
void updateWindowState();
|
||||
template <typename T> QJsonObject processClientMessage(T* socket, const QJsonObject& message);
|
||||
|
||||
QPointer<BrowserHost> m_browserHost;
|
||||
QPointer<BrowserWebSocketHost> m_browserWebSocketHost;
|
||||
QHash<QString, QSharedPointer<BrowserAction>> m_browserClients;
|
||||
|
||||
bool m_dialogActive;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (C) 2024 KeePassXC Team <team@keepassxc.org>
|
||||
* Copyright (C) 2025 KeePassXC Team <team@keepassxc.org>
|
||||
* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
|
||||
* Copyright (C) 2013 Francois Ferrand
|
||||
*
|
||||
|
|
@ -295,3 +295,12 @@ QString BrowserSettings::replaceTildeHomePath(QString location)
|
|||
|
||||
return location;
|
||||
}
|
||||
|
||||
void BrowserSettings:: setWebSocketSupport(bool enabled)
|
||||
{
|
||||
config()->set(Config::Browser_WebSocketSupport, enabled);
|
||||
}
|
||||
bool BrowserSettings::webSocketSupport()
|
||||
{
|
||||
return config()->get(Config::Browser_WebSocketSupport).toBool();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (C) 2024 KeePassXC Team <team@keepassxc.org>
|
||||
* Copyright (C) 2025 KeePassXC Team <team@keepassxc.org>
|
||||
* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
|
||||
* Copyright (C) 2013 Francois Ferrand
|
||||
*
|
||||
|
|
@ -82,6 +82,8 @@ public:
|
|||
void updateBinaryPaths();
|
||||
QString replaceHomePath(QString location);
|
||||
QString replaceTildeHomePath(QString location);
|
||||
void setWebSocketSupport(bool enabled);
|
||||
bool webSocketSupport();
|
||||
|
||||
private:
|
||||
static BrowserSettings* m_instance;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (C) 2020 KeePassXC Team <team@keepassxc.org>
|
||||
* Copyright (C) 2025 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
|
||||
|
|
@ -166,6 +166,7 @@ void BrowserSettingsWidget::loadSettings()
|
|||
m_ui->browserTypeComboBox->setCurrentIndex(typeIndex);
|
||||
}
|
||||
m_ui->customBrowserLocation->setText(settings->replaceHomePath(settings->customBrowserLocation()));
|
||||
m_ui->webSocketSupport->setChecked(settings->webSocketSupport());
|
||||
|
||||
#ifdef QT_DEBUG
|
||||
m_ui->customExtensionId->setText(settings->customExtensionId());
|
||||
|
|
@ -241,6 +242,7 @@ void BrowserSettingsWidget::saveSettings()
|
|||
settings->setSupportKphFields(m_ui->supportKphFields->isChecked());
|
||||
settings->setAllowLocalhostWithPasskeys(m_ui->allowLocalhostWithPasskeys->isChecked());
|
||||
settings->setNoMigrationPrompt(m_ui->noMigrationPrompt->isChecked());
|
||||
settings->setWebSocketSupport(m_ui->webSocketSupport->isChecked());
|
||||
|
||||
#ifdef QT_DEBUG
|
||||
settings->setCustomExtensionId(m_ui->customExtensionId->text());
|
||||
|
|
|
|||
|
|
@ -340,6 +340,16 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="webSocketSupport">
|
||||
<property name="toolTip">
|
||||
<string>Listens to connections using WebSocket in addition to native messaging.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Enable WebSocket listener</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="allowGetDatabaseEntriesRequest">
|
||||
<property name="toolTip">
|
||||
|
|
|
|||
59
src/browser/BrowserWebSocketHost.h
Normal file
59
src/browser/BrowserWebSocketHost.h
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Copyright (C) 2025 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 KEEPASSXC_BROWSERWEBSOCKETHOST_H
|
||||
#define KEEPASSXC_BROWSERWEBSOCKETHOST_H
|
||||
|
||||
#include <QJsonObject>
|
||||
#include <QObject>
|
||||
#include <QPointer>
|
||||
|
||||
class QWebSocketServer;
|
||||
class QWebSocket;
|
||||
class QString;
|
||||
|
||||
class BrowserWebSocketHost : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit BrowserWebSocketHost(QObject* parent = nullptr);
|
||||
~BrowserWebSocketHost() override;
|
||||
|
||||
void start();
|
||||
void stop();
|
||||
|
||||
void broadcastClientMessage(const QJsonObject& json);
|
||||
void sendClientMessage(QWebSocket* socket, const QJsonObject& json);
|
||||
|
||||
signals:
|
||||
void clientMessageReceived(QWebSocket* socket, const QJsonObject& json);
|
||||
|
||||
private slots:
|
||||
void clientConnected();
|
||||
void readClientMessage(QString message);
|
||||
void clientDisconnected();
|
||||
|
||||
private:
|
||||
void sendClientData(QWebSocket* socket, const QString& data);
|
||||
|
||||
private:
|
||||
QPointer<QWebSocketServer> m_webSocketServer;
|
||||
QList<QWebSocket*> m_socketList;
|
||||
};
|
||||
|
||||
#endif // KEEPASSXC_BROWSERWEBSOCKETHOST_H
|
||||
123
src/browser/BrowserWebsocketHost.cpp
Normal file
123
src/browser/BrowserWebsocketHost.cpp
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
* Copyright (C) 2025 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 "BrowserShared.h"
|
||||
#include "BrowserWebSocketHost.h"
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QtWebSockets/qwebsocket.h>
|
||||
#include <QtWebSockets/qwebsocketserver.h>
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <fcntl.h>
|
||||
#undef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <sys/socket.h>
|
||||
#endif
|
||||
|
||||
BrowserWebSocketHost::BrowserWebSocketHost(QObject* parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
m_webSocketServer =
|
||||
new QWebSocketServer(QStringLiteral("KeePassXC HTTP server"), QWebSocketServer::NonSecureMode, this);
|
||||
}
|
||||
|
||||
BrowserWebSocketHost::~BrowserWebSocketHost()
|
||||
{
|
||||
stop();
|
||||
}
|
||||
|
||||
void BrowserWebSocketHost::start()
|
||||
{
|
||||
int socketDesc = m_webSocketServer->nativeDescriptor();
|
||||
if (socketDesc) {
|
||||
int max = BrowserShared::NATIVEMSG_MAX_LENGTH;
|
||||
setsockopt(socketDesc, SOL_SOCKET, SO_SNDBUF, reinterpret_cast<char*>(&max), sizeof(max));
|
||||
}
|
||||
|
||||
if (!m_webSocketServer->isListening()) {
|
||||
m_webSocketServer->listen(QHostAddress::LocalHost, 7580);
|
||||
connect(m_webSocketServer, &QWebSocketServer::newConnection, this, &BrowserWebSocketHost::clientConnected);
|
||||
connect(m_webSocketServer, &QWebSocketServer::closed, this, &BrowserWebSocketHost::stop);
|
||||
}
|
||||
}
|
||||
|
||||
void BrowserWebSocketHost::stop()
|
||||
{
|
||||
m_socketList.clear();
|
||||
m_webSocketServer->close();
|
||||
}
|
||||
|
||||
void BrowserWebSocketHost::clientConnected()
|
||||
{
|
||||
auto socket = m_webSocketServer->nextPendingConnection();
|
||||
if (socket) {
|
||||
m_socketList.append(socket);
|
||||
connect(socket, &QWebSocket::textMessageReceived, this, &BrowserWebSocketHost::readClientMessage);
|
||||
connect(socket, &QWebSocket::disconnected, this, &BrowserWebSocketHost::clientDisconnected);
|
||||
}
|
||||
}
|
||||
|
||||
void BrowserWebSocketHost::readClientMessage(QString message)
|
||||
{
|
||||
auto* socket = qobject_cast<QWebSocket*>(QObject::sender());
|
||||
if (!socket || !socket->isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
socket->setReadBufferSize(BrowserShared::NATIVEMSG_MAX_LENGTH);
|
||||
socket->setOutgoingFrameSize(BrowserShared::NATIVEMSG_MAX_LENGTH);
|
||||
|
||||
QJsonParseError error;
|
||||
auto json = QJsonDocument::fromJson(message.toUtf8(), &error);
|
||||
if (json.isNull()) {
|
||||
qWarning() << "Failed to read proxy message: " << error.errorString();
|
||||
return;
|
||||
}
|
||||
|
||||
emit clientMessageReceived(socket, json.object());
|
||||
}
|
||||
|
||||
void BrowserWebSocketHost::broadcastClientMessage(const QJsonObject& json)
|
||||
{
|
||||
QString reply(QJsonDocument(json).toJson(QJsonDocument::Compact));
|
||||
for (const auto socket : m_socketList) {
|
||||
sendClientData(socket, reply);
|
||||
}
|
||||
}
|
||||
|
||||
void BrowserWebSocketHost::sendClientMessage(QWebSocket* socket, const QJsonObject& json)
|
||||
{
|
||||
QString reply(QJsonDocument(json).toJson(QJsonDocument::Compact));
|
||||
sendClientData(socket, reply);
|
||||
}
|
||||
|
||||
void BrowserWebSocketHost::sendClientData(QWebSocket* socket, const QString& data)
|
||||
{
|
||||
if (socket && socket->isValid() && socket->state() == QAbstractSocket::ConnectedState) {
|
||||
socket->sendTextMessage(data);
|
||||
socket->flush();
|
||||
}
|
||||
}
|
||||
|
||||
void BrowserWebSocketHost::clientDisconnected()
|
||||
{
|
||||
auto socket = qobject_cast<QWebSocket*>(QObject::sender());
|
||||
m_socketList.removeOne(socket);
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (C) 2024 KeePassXC Team <team@keepassxc.org>
|
||||
# Copyright (C) 2025 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
|
||||
|
|
@ -28,6 +28,7 @@ if(WITH_XC_BROWSER)
|
|||
BrowserService.cpp
|
||||
BrowserSettings.cpp
|
||||
BrowserShared.cpp
|
||||
BrowserWebSocketHost.cpp
|
||||
CustomTableWidget.cpp
|
||||
NativeMessageInstaller.cpp)
|
||||
|
||||
|
|
@ -41,5 +42,5 @@ if(WITH_XC_BROWSER)
|
|||
endif()
|
||||
|
||||
add_library(browser STATIC ${browser_SOURCES})
|
||||
target_link_libraries(browser Qt5::Core Qt5::Concurrent Qt5::Widgets Qt5::Network ${BOTAN_LIBRARIES})
|
||||
target_link_libraries(browser Qt5::Core Qt5::Concurrent Qt5::Widgets Qt5::Network Qt5::WebSockets ${BOTAN_LIBRARIES})
|
||||
endif()
|
||||
|
|
|
|||
|
|
@ -175,6 +175,7 @@ static const QHash<Config::ConfigKey, ConfigDirective> configStrings = {
|
|||
{Config::Browser_CustomBrowserType, {QS("Browser/CustomBrowserType"), Local, -1}},
|
||||
{Config::Browser_CustomBrowserLocation, {QS("Browser/CustomBrowserLocation"), Local, {}}},
|
||||
{Config::Browser_AllowLocalhostWithPasskeys, {QS("Browser/Browser_AllowLocalhostWithPasskeys"), Roaming, false}},
|
||||
{Config::Browser_WebSocketSupport, {QS("Browser/WebSocketSupport"), Roaming, false}},
|
||||
#ifdef QT_DEBUG
|
||||
{Config::Browser_CustomExtensionId, {QS("Browser/CustomExtensionId"), Local, {}}},
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (C) 2024 KeePassXC Team <team@keepassxc.org>
|
||||
* Copyright (C) 2025 KeePassXC Team <team@keepassxc.org>
|
||||
* Copyright (C) 2011 Felix Geyer <debfx@fobos.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
|
|
@ -155,6 +155,7 @@ public:
|
|||
Browser_CustomBrowserType,
|
||||
Browser_CustomBrowserLocation,
|
||||
Browser_AllowLocalhostWithPasskeys,
|
||||
Browser_WebSocketSupport,
|
||||
#ifdef QT_DEBUG
|
||||
Browser_CustomExtensionId,
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ void TestBrowser::testChangePublicKeys()
|
|||
json["publicKey"] = PUBLICKEY;
|
||||
json["nonce"] = NONCE;
|
||||
|
||||
auto response = m_browserAction->processClientMessage(nullptr, json);
|
||||
auto response = m_browserAction->processClientMessage<QLocalSocket>(nullptr, json);
|
||||
QCOMPARE(response["action"].toString(), QString("change-public-keys"));
|
||||
QCOMPARE(response["publicKey"].toString() == PUBLICKEY, false);
|
||||
QCOMPARE(response["success"].toString(), TRUE_STR);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue