Page Menu
Home
GnuPG
Search
Configure Global Search
Log In
Files
F27670638
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Size
14 KB
Subscribers
None
View Options
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
Details
Attached
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
Attached To
rOJ GpgOL.js
Event Timeline
Log In to Comment