Page MenuHome GnuPG

No OneTemporary

diff --git a/server/webserver.cpp b/server/webserver.cpp
index 0c860c3..33ae38d 100644
--- a/server/webserver.cpp
+++ b/server/webserver.cpp
@@ -1,383 +1,383 @@
// SPDX-FileCopyrightText: 2023 g10 code GmbH
// SPDX-Contributor: Carl Schwan <carl.schwan@gnupg.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#include "webserver.h"
#include <QDebug>
#include <QFile>
#include <QHttpServer>
#include <QHttpServerResponse>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QSslCertificate>
#include <QSslKey>
#include <QSslServer>
#include <QStandardPaths>
#include <QWebSocket>
#include <QWebSocketCorsAuthenticator>
#include <QWebSocketServer>
#include <protocol.h>
#include "controllers/staticcontroller.h"
#include "http_debug.h"
#include "model/serverstate.h"
#include "websocket_debug.h"
#include <KLocalizedString>
using namespace Qt::Literals::StringLiterals;
using namespace Protocol;
WebServer::WebServer(QObject *parent)
: QObject(parent)
, m_httpServer(new QHttpServer(this))
, m_webSocketServer(new QWebSocketServer(u"GPGOL"_s, QWebSocketServer::SslMode::SecureMode, this))
{
}
WebServer::~WebServer() = default;
bool WebServer::run()
{
auto keyPath = QStandardPaths::locate(QStandardPaths::AppLocalDataLocation, QStringLiteral("certificate-key.pem"));
auto certPath = QStandardPaths::locate(QStandardPaths::AppLocalDataLocation, QStringLiteral("certificate.pem"));
Q_ASSERT(!keyPath.isEmpty());
Q_ASSERT(!certPath.isEmpty());
QFile privateKeyFile(keyPath);
if (!privateKeyFile.open(QIODevice::ReadOnly)) {
qCFatal(HTTP_LOG) << "Couldn't open file" << keyPath << "for reading:" << privateKeyFile.errorString();
return false;
}
const QSslKey sslKey(&privateKeyFile, QSsl::Rsa);
privateKeyFile.close();
const auto sslCertificateChain = QSslCertificate::fromPath(certPath);
if (sslCertificateChain.isEmpty()) {
qCFatal(HTTP_LOG) << u"Couldn't retrieve SSL certificate from file:"_s << certPath;
return false;
}
// Static assets controller
m_httpServer->route(u"/home"_s, &StaticController::homeAction);
m_httpServer->route(u"/assets/"_s, &StaticController::assetsAction);
QSslConfiguration sslConfiguration;
sslConfiguration.setPeerVerifyMode(QSslSocket::VerifyNone);
sslConfiguration.setLocalCertificate(sslCertificateChain.front());
sslConfiguration.setPrivateKey(sslKey);
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
m_tcpserver = std::make_unique<QSslServer>();
m_tcpserver->setSslConfiguration(sslConfiguration);
if (!m_tcpserver->listen(QHostAddress::LocalHost, WebServer::Port) || !m_httpServer->bind(m_tcpserver.get())) {
qCFatal(HTTP_LOG) << "Server failed to listen on a port.";
return false;
}
quint16 port = m_tcpserver->serverPort();
#else
m_httpServer->sslSetup(sslCertificateChain.front(), sslKey);
quint16 port = m_httpServer->listen(QHostAddress::Any, WebServer::Port);
#endif
if (!port) {
qCFatal(HTTP_LOG) << "Server failed to listen on a port.";
return false;
}
qInfo(HTTP_LOG) << u"Running http server on https://127.0.0.1:%1/ (Press CTRL+C to quit)"_s.arg(port);
m_webSocketServer->setSslConfiguration(sslConfiguration);
connect(m_webSocketServer, &QWebSocketServer::newConnection, this, &WebServer::onNewConnection);
connect(m_webSocketServer, &QWebSocketServer::originAuthenticationRequired, this, [](QWebSocketCorsAuthenticator *authenticator) {
const auto origin = authenticator->origin();
// Only allow the gpgol-client and localhost:5656 to connect to this host
// Otherwise any tab from the browser is able to access the websockets
authenticator->setAllowed(origin == u"Client"_s || origin == u"https://localhost:5656"_s);
});
if (m_webSocketServer->listen(QHostAddress::LocalHost, WebServer::WebSocketPort)) {
qCInfo(WEBSOCKET_LOG) << u"Running websocket server on wss://127.0.0.1:%1/ (Press CTRL+C to quit)"_s.arg(WebServer::Port + 1);
}
return true;
}
void WebServer::onNewConnection()
{
auto pSocket = m_webSocketServer->nextPendingConnection();
if (!pSocket) {
return;
}
qCInfo(WEBSOCKET_LOG) << "Client connected:" << pSocket->peerName() << pSocket->origin() << pSocket->localAddress() << pSocket->localPort();
connect(pSocket, &QWebSocket::textMessageReceived, this, &WebServer::processTextMessage);
connect(pSocket, &QWebSocket::binaryMessageReceived, this, &WebServer::processBinaryMessage);
connect(pSocket, &QWebSocket::disconnected, this, &WebServer::socketDisconnected);
m_clients << pSocket;
}
void WebServer::processTextMessage(QString message)
{
auto webClient = qobject_cast<QWebSocket *>(sender());
if (webClient) {
QJsonParseError error;
const auto doc = QJsonDocument::fromJson(message.toUtf8(), &error);
if (error.error != QJsonParseError::NoError) {
qCWarning(WEBSOCKET_LOG) << "Error parsing json" << error.errorString();
return;
}
if (!doc.isObject()) {
qCWarning(WEBSOCKET_LOG) << "Invalid json received";
return;
}
const auto object = doc.object();
processCommand(object, webClient);
}
}
void WebServer::processCommand(const QJsonObject &object, QWebSocket *socket)
{
if (!object.contains("command"_L1) || !object["command"_L1].isString() || !object.contains("arguments"_L1) || !object["arguments"_L1].isObject()) {
qCWarning(WEBSOCKET_LOG) << "Invalid json received: no type or arguments set" << object;
return;
}
const auto arguments = object["arguments"_L1].toObject();
const auto command = commandFromString(object["command"_L1].toString());
switch (command) {
case Command::Register: {
const auto type = arguments["type"_L1].toString();
qCWarning(WEBSOCKET_LOG) << "Register" << arguments;
if (type.isEmpty()) {
qCWarning(WEBSOCKET_LOG) << "empty client type given when registering";
return;
}
const auto emails = arguments["emails"_L1].toArray();
if (type == "webclient"_L1) {
if (emails.isEmpty()) {
qCWarning(WEBSOCKET_LOG) << "empty email given";
}
for (const auto &email : emails) {
m_webClientsMappingToEmail[email.toString()] = socket;
qCWarning(WEBSOCKET_LOG) << "email" << email.toString() << "mapped to a web client";
const auto nativeClient = m_nativeClientsMappingToEmail[email.toString()];
if (nativeClient) {
QJsonDocument doc(QJsonObject{{"command"_L1, Protocol::commandToString(Protocol::Connection)},
{"payload"_L1, QJsonObject{{"client_type"_L1, "web_client"_L1}}}});
nativeClient->sendTextMessage(QString::fromUtf8(doc.toJson()));
}
}
} else {
if (emails.isEmpty()) {
qCWarning(WEBSOCKET_LOG) << "empty email given";
}
for (const auto &email : emails) {
m_nativeClientsMappingToEmail[email.toString()] = socket;
qCWarning(WEBSOCKET_LOG) << "email" << email.toString() << "mapped to a native client";
const auto webClient = m_webClientsMappingToEmail[email.toString()];
bool hasWebClient = false;
if (webClient) {
hasWebClient = true;
const QJsonDocument doc(QJsonObject{{"command"_L1, Protocol::commandToString(Protocol::Connection)},
{
"payload"_L1,
QJsonObject{{"client_type"_L1, "native_client"_L1}},
}});
webClient->sendTextMessage(QString::fromUtf8(doc.toJson()));
}
if (hasWebClient) {
const QJsonDocument doc(QJsonObject{
{"command"_L1, Protocol::commandToString(Protocol::Connection)},
{"payload"_L1, QJsonObject{{"client_type"_L1, "web_client"_L1}}},
});
socket->sendTextMessage(QString::fromUtf8(doc.toJson()));
}
}
}
return;
}
case Command::EwsResponse: {
const auto email = arguments["email"_L1].toString();
QJsonDocument doc(QJsonObject{
{"command"_L1, Protocol::commandToString(Protocol::EwsResponse)},
{"arguments"_L1, arguments},
});
sendMessageToNativeClient(email, doc.toJson());
return;
}
case Command::View:
case Command::Reply:
case Command::Forward:
case Command::Composer:
case Command::OpenDraft:
case Command::RestoreAutosave:
case Command::Info:
case Command::Reencrypt:
case Command::DeleteDraft: {
const auto email = arguments["email"_L1].toString();
const auto token = QUuid::createUuid().toString(QUuid::WithoutBraces);
object["token"_L1] = token;
auto &serverState = ServerState::instance();
serverState.composerRequest[token.toUtf8()] = email;
const QJsonDocument doc(object); // simple forwarding to the native client
const auto ok = sendMessageToNativeClient(email, doc.toJson());
if (!ok) {
const QJsonDocument doc(QJsonObject{
{"command"_L1, Protocol::commandToString(Protocol::Error)},
{"arguments"_L1,
{
- {"error"_L1, i18n("Unable to found corresponding native client.")},
+ {"error"_L1, i18n("Unable to find corresponding native client.")},
}},
});
socket->sendTextMessage(QString::fromUtf8(doc.toJson()));
}
return;
}
case Command::InfoFetched: {
const auto email = arguments["email"_L1].toString();
QJsonDocument doc(QJsonObject{
{"command"_L1, Protocol::commandToString(Protocol::InfoFetched)},
{"arguments"_L1, arguments},
});
const auto ok = sendMessageToWebClient(email, doc.toJson());
if (!ok) {
qCWarning(WEBSOCKET_LOG) << "Unable to send info fetched to web client";
}
return;
}
case Command::Ews: {
const auto email = arguments["email"_L1].toString();
QJsonDocument doc(QJsonObject{
{"command"_L1, Protocol::commandToString(Protocol::Ews)},
{"arguments"_L1, arguments},
});
const auto ok = sendMessageToWebClient(email, doc.toJson());
if (!ok) {
qCWarning(WEBSOCKET_LOG) << "Unable to send ews request to web client";
}
return;
}
case Command::ViewerClosed:
case Command::ViewerOpened: {
const auto email = arguments["email"_L1].toString();
QJsonDocument doc(QJsonObject{
{"command"_L1, Protocol::commandToString(command)},
{"arguments"_L1, arguments},
});
const auto ok = sendMessageToWebClient(email, doc.toJson());
if (!ok) {
qCWarning(WEBSOCKET_LOG) << "Unable to send viewer close/open request to web client";
}
return;
}
case Command::Log:
qWarning(WEBSOCKET_LOG) << arguments["message"_L1].toString() << arguments["args"_L1].toString();
return;
default:
qCWarning(WEBSOCKET_LOG) << "Invalid json received: invalid command";
return;
}
}
bool WebServer::sendMessageToWebClient(const QString &email, const QByteArray &payload)
{
auto socket = m_webClientsMappingToEmail[email];
if (!socket) {
return false;
}
socket->sendTextMessage(QString::fromUtf8(payload));
return true;
}
bool WebServer::sendMessageToNativeClient(const QString &email, const QByteArray &payload)
{
auto socket = m_nativeClientsMappingToEmail[email];
if (!socket) {
return false;
}
socket->sendTextMessage(QString::fromUtf8(payload));
return true;
}
void WebServer::processBinaryMessage(QByteArray message)
{
qCWarning(WEBSOCKET_LOG) << "got binary message" << message;
QWebSocket *pClient = qobject_cast<QWebSocket *>(sender());
if (pClient) {
pClient->sendBinaryMessage(message);
}
}
void WebServer::socketDisconnected()
{
auto pClient = qobject_cast<QWebSocket *>(sender());
if (!pClient) {
return;
}
qCWarning(WEBSOCKET_LOG) << "Client disconnected" << pClient;
// Web client was disconnected
{
const auto it = std::find_if(m_webClientsMappingToEmail.cbegin(), m_webClientsMappingToEmail.cend(), [pClient](QWebSocket *webSocket) {
return pClient == webSocket;
});
if (it != m_webClientsMappingToEmail.cend()) {
const auto email = it.key();
const auto nativeClient = m_nativeClientsMappingToEmail[email];
qCInfo(WEBSOCKET_LOG) << "webclient with email disconnected" << email << nativeClient;
if (nativeClient) {
QJsonDocument doc(QJsonObject{
{"command"_L1, Protocol::commandToString(Protocol::Disconnection)},
});
nativeClient->sendTextMessage(QString::fromUtf8(doc.toJson()));
}
m_webClientsMappingToEmail.removeIf([pClient](auto socket) {
return pClient == socket.value();
});
}
}
// Native client was disconnected
const auto emails = m_nativeClientsMappingToEmail.keys();
for (const auto &email : emails) {
const auto webSocket = m_nativeClientsMappingToEmail[email];
if (webSocket != pClient) {
qCWarning(WEBSOCKET_LOG) << "webSocket not equal" << email << webSocket << pClient;
continue;
}
qCInfo(WEBSOCKET_LOG) << "native client for" << email << "was disconnected.";
QJsonDocument doc(QJsonObject{
{"command"_L1, Protocol::commandToString(Protocol::Disconnection)},
});
sendMessageToWebClient(email, doc.toJson());
}
m_nativeClientsMappingToEmail.removeIf([pClient](auto socket) {
return pClient == socket.value();
});
m_clients.removeAll(pClient);
}

File Metadata

Mime Type
text/x-diff
Expires
Mon, Sep 15, 10:27 PM (1 d, 11 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
bd/70/37bd8362e48aecae9bcd11edd80c

Event Timeline