Complete refactor of Browser Integration classes

* Removed option to attach KeePassXC to the browser extension. Users must use the proxy application to communicate with KeePassXC.
* Significantly streamlined proxy code. Used same implementation of stdin/stdout interface across all platforms.
* Moved browser service entry point to BrowserService class instead of NativeMessagingHost. BrowserService now coordinates the communication to/from clients.
* Moved settings page definition out of MainWindow
* Decoupled BrowserService from DatabaseTabWidget
* Reduced complexity of various functions and cleaned the ABI (public vs private).
* Eliminated BrowserClients class, moved functionality into the BrowserService
* Renamed HostInstaller to NativeMessageInstaller and renamed NativeMessageHost to BrowserHost.
* Recognize XDG_CONFIG_HOME when installing native message file on Linux. Fix #4121 and fix #4123.
This commit is contained in:
Jonathan White 2020-05-10 21:20:00 -04:00
parent 3b4057a78c
commit a145bf9119
43 changed files with 1221 additions and 1919 deletions

View file

@ -1,5 +1,4 @@
# Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
# Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
# Copyright (C) 2020 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
@ -15,19 +14,17 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
if(WITH_XC_BROWSER)
include_directories(${BROWSER_SOURCE_DIR})
set(proxy_SOURCES
../core/Alloc.cpp
../browser/BrowserShared.cpp
keepassxc-proxy.cpp
${BROWSER_SOURCE_DIR}/NativeMessagingBase.cpp
NativeMessagingHost.cpp)
NativeMessagingProxy.cpp)
add_library(proxy STATIC ${proxy_SOURCES})
target_link_libraries(proxy Qt5::Core Qt5::Network ${sodium_LIBRARY_RELEASE})
add_executable(keepassxc-proxy keepassxc-proxy.cpp)
target_link_libraries(keepassxc-proxy proxy)
# Alloc must be defined in a static library to prevent clashing with clang ASAN definitions
add_library(proxy_alloc STATIC ../core/Alloc.cpp)
target_link_libraries(proxy_alloc PRIVATE Qt5::Core ${sodium_LIBRARY_RELEASE})
add_executable(keepassxc-proxy ${proxy_SOURCES})
target_link_libraries(keepassxc-proxy proxy_alloc Qt5::Core Qt5::Network)
install(TARGETS keepassxc-proxy
BUNDLE DESTINATION . COMPONENT Runtime
RUNTIME DESTINATION ${PROXY_INSTALL_DIR} COMPONENT Runtime)
@ -56,7 +53,4 @@ if(WITH_XC_BROWSER)
COMMAND ${CMAKE_COMMAND} -E copy keepassxc-proxy ${PROXY_APP_DIR}/keepassxc-proxy
COMMENT "Copying keepassxc-proxy inside the application")
endif()
if(MINGW)
target_link_libraries(keepassxc-proxy Wtsapi32.lib Ws2_32.lib)
endif()
endif()

View file

@ -1,133 +0,0 @@
/*
* 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 "NativeMessagingHost.h"
#include <QCoreApplication>
#ifdef Q_OS_WIN
#include <winsock2.h>
#endif
NativeMessagingHost::NativeMessagingHost()
: NativeMessagingBase(true)
{
m_localSocket = new QLocalSocket();
m_localSocket->connectToServer(getLocalServerPath());
m_localSocket->setReadBufferSize(NATIVE_MSG_MAX_LENGTH);
int socketDesc = m_localSocket->socketDescriptor();
if (socketDesc) {
int max = NATIVE_MSG_MAX_LENGTH;
setsockopt(socketDesc, SOL_SOCKET, SO_SNDBUF, reinterpret_cast<char*>(&max), sizeof(max));
}
#ifdef Q_OS_WIN
m_running.store(1);
m_future = QtConcurrent::run(this, &NativeMessagingHost::readNativeMessages);
#endif
connect(m_localSocket, SIGNAL(readyRead()), this, SLOT(newLocalMessage()));
connect(m_localSocket, SIGNAL(disconnected()), this, SLOT(deleteSocket()));
connect(m_localSocket,
SIGNAL(stateChanged(QLocalSocket::LocalSocketState)),
SLOT(socketStateChanged(QLocalSocket::LocalSocketState)));
}
NativeMessagingHost::~NativeMessagingHost()
{
#ifdef Q_OS_WIN
m_future.waitForFinished();
#endif
}
void NativeMessagingHost::readNativeMessages()
{
#ifdef Q_OS_WIN
quint32 length = 0;
while (m_running.load() == 1 && !std::cin.eof()) {
length = 0;
std::cin.read(reinterpret_cast<char*>(&length), 4);
if (!readStdIn(length)) {
QCoreApplication::quit();
}
QThread::msleep(1);
}
#endif
}
void NativeMessagingHost::readLength()
{
quint32 length = 0;
std::cin.read(reinterpret_cast<char*>(&length), 4);
if (!std::cin.eof() && length > 0) {
readStdIn(length);
} else {
QCoreApplication::quit();
}
}
bool NativeMessagingHost::readStdIn(const quint32 length)
{
if (length <= 0) {
return false;
}
QByteArray arr;
arr.reserve(length);
for (quint32 i = 0; i < length; ++i) {
int c = std::getchar();
if (c == EOF) {
// message ended prematurely, ignore it and return
return false;
}
arr.append(static_cast<char>(c));
}
if (arr.length() > 0 && m_localSocket && m_localSocket->state() == QLocalSocket::ConnectedState) {
m_localSocket->write(arr.constData(), arr.length());
m_localSocket->flush();
}
return true;
}
void NativeMessagingHost::newLocalMessage()
{
if (!m_localSocket || m_localSocket->bytesAvailable() <= 0) {
return;
}
QByteArray arr = m_localSocket->readAll();
if (!arr.isEmpty()) {
sendReply(arr);
}
}
void NativeMessagingHost::deleteSocket()
{
if (m_notifier) {
m_notifier->setEnabled(false);
}
m_localSocket->deleteLater();
QCoreApplication::quit();
}
void NativeMessagingHost::socketStateChanged(QLocalSocket::LocalSocketState socketState)
{
if (socketState == QLocalSocket::UnconnectedState || socketState == QLocalSocket::ClosingState) {
m_running.testAndSetOrdered(1, 0);
}
}

View file

@ -0,0 +1,110 @@
/*
* Copyright (C) 2020 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 "NativeMessagingProxy.h"
#include "browser/BrowserShared.h"
#include <QCoreApplication>
#include <QtConcurrent/QtConcurrent>
#include <iostream>
#ifdef Q_OS_WIN
#include <fcntl.h>
#include <windows.h>
#endif
NativeMessagingProxy::NativeMessagingProxy()
: QObject()
{
connect(this,
&NativeMessagingProxy::stdinMessage,
this,
&NativeMessagingProxy::transferStdinMessage,
Qt::QueuedConnection);
setupStandardInput();
setupLocalSocket();
}
void NativeMessagingProxy::setupStandardInput()
{
#ifdef Q_OS_WIN
setmode(fileno(stdin), _O_BINARY);
setmode(fileno(stdout), _O_BINARY);
#endif
QtConcurrent::run([this] {
while (std::cin.good()) {
if (std::cin.peek() != EOF) {
uint length = 0;
for (uint i = 0; i < sizeof(uint); ++i) {
length |= getchar() << (i * 8);
}
QString msg;
msg.reserve(length);
for (uint i = 0; i < length; ++i) {
msg.append(getchar());
}
if (msg.length() > 0) {
emit stdinMessage(msg);
}
}
QThread::msleep(100);
}
QCoreApplication::quit();
});
}
void NativeMessagingProxy::transferStdinMessage(const QString& msg)
{
if (m_localSocket && m_localSocket->state() == QLocalSocket::ConnectedState) {
m_localSocket->write(msg.toUtf8(), msg.length());
m_localSocket->flush();
}
}
void NativeMessagingProxy::setupLocalSocket()
{
m_localSocket.reset(new QLocalSocket());
m_localSocket->connectToServer(BrowserShared::localServerPath());
m_localSocket->setReadBufferSize(BrowserShared::NATIVEMSG_MAX_LENGTH);
connect(m_localSocket.data(), SIGNAL(readyRead()), this, SLOT(transferSocketMessage()));
connect(m_localSocket.data(), SIGNAL(disconnected()), this, SLOT(socketDisconnected()));
}
void NativeMessagingProxy::transferSocketMessage()
{
auto msg = m_localSocket->readAll();
if (!msg.isEmpty()) {
// Explicitly write the message length as 1 byte chunks
uint len = msg.size();
std::cout.write(reinterpret_cast<char*>(&len), sizeof(len));
// Write the message and flush the stream
std::cout << msg.toStdString() << std::flush;
}
}
void NativeMessagingProxy::socketDisconnected()
{
// Shutdown the proxy when disconnected from the application
QCoreApplication::quit();
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
* Copyright (C) 2020 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
@ -15,32 +15,39 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef NATIVEMESSAGINGHOST_H
#define NATIVEMESSAGINGHOST_H
#ifndef NATIVEMESSAGINGPROXY_H
#define NATIVEMESSAGINGPROXY_H
#include "NativeMessagingBase.h"
#include <QLocalSocket>
#include <QObject>
#include <QScopedPointer>
class NativeMessagingHost : public NativeMessagingBase
class QWinEventNotifier;
class QSocketNotifier;
class NativeMessagingProxy : public QObject
{
Q_OBJECT
public:
NativeMessagingHost();
~NativeMessagingHost() override;
NativeMessagingProxy();
~NativeMessagingProxy() override = default;
signals:
void stdinMessage(QString msg);
public slots:
void newLocalMessage();
void deleteSocket();
void socketStateChanged(QLocalSocket::LocalSocketState socketState);
void transferSocketMessage();
void transferStdinMessage(const QString& msg);
void socketDisconnected();
private:
void readNativeMessages() override;
void readLength() override;
bool readStdIn(const quint32 length) override;
void setupStandardInput();
void setupLocalSocket();
private:
QLocalSocket* m_localSocket;
QScopedPointer<QLocalSocket> m_localSocket;
Q_DISABLE_COPY(NativeMessagingHost)
Q_DISABLE_COPY(NativeMessagingProxy)
};
#endif // NATIVEMESSAGINGHOST_H
#endif // NATIVEMESSAGINGPROXY_H

View file

@ -16,8 +16,9 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "NativeMessagingHost.h"
#include "NativeMessagingProxy.h"
#include <QCoreApplication>
#include <iostream>
#ifndef Q_OS_WIN
@ -79,6 +80,6 @@ int main(int argc, char* argv[])
#else
SetConsoleCtrlHandler(static_cast<PHANDLER_ROUTINE>(ConsoleHandler), TRUE);
#endif
NativeMessagingHost host;
NativeMessagingProxy proxy;
return a.exec();
}