Page Menu
Home
GnuPG
Search
Configure Global Search
Log In
Files
F41057444
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Size
11 KB
Subscribers
None
View Options
diff --git a/server/webserver.cpp b/server/webserver.cpp
index 8488d09..0c9bd83 100644
--- a/server/webserver.cpp
+++ b/server/webserver.cpp
@@ -1,299 +1,300 @@
// 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 <QWebSocketServer>
#include "controllers/emailcontroller.h"
#include "controllers/registrationcontroller.h"
#include "controllers/staticcontroller.h"
#include "http_debug.h"
#include "websocket_debug.h"
using namespace Qt::Literals::StringLiterals;
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);
// Registration controller
m_httpServer->route(u"/register"_s, &RegistrationController::registerAction);
// Email controller
m_httpServer->route(u"/view"_s, &EmailController::viewEmailAction);
m_httpServer->route(u"/info"_s, &EmailController::infoEmailAction);
m_httpServer->route(u"/reply"_s, &EmailController::replyEmailAction);
m_httpServer->route(u"/forward"_s, &EmailController::forwardEmailAction);
m_httpServer->route(u"/new"_s, &EmailController::newEmailAction);
m_httpServer->route(u"/socket-web"_s, [this](const QHttpServerRequest &request) {
return EmailController::socketWebAction(request, this);
});
m_httpServer->route(u"/draft/<arg>"_s, &EmailController::draftAction);
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::Any, 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);
if (m_webSocketServer->listen(QHostAddress::Any, 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);
connect(m_webSocketServer, &QWebSocketServer::newConnection, this, &WebServer::onNewConnection);
}
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();
if (!object.contains("command"_L1) || !object["command"_L1].isString() || !object.contains("arguments"_L1) || !object["arguments"_L1].isObject()) {
qCWarning(WEBSOCKET_LOG) << "Invalid json received: no command or arguments set";
return;
}
static QHash<QString, WebServer::Command> commandMapping{
{"register"_L1, WebServer::Command::Register},
{"email-sent"_L1, WebServer::Command::EmailSent},
};
const auto command = commandMapping[doc["command"_L1].toString()];
processCommand(command, object["arguments"_L1].toObject(), webClient);
}
}
void WebServer::processCommand(Command command, const QJsonObject &arguments, QWebSocket *socket)
{
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{{"type"_L1, "connection"_L1}, {"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()];
if (webClient) {
QJsonDocument doc(QJsonObject{{"type"_L1, "connection"_L1}, {"payload"_L1, QJsonObject{{"client_type"_L1, "native_client"_L1}}}});
webClient->sendTextMessage(QString::fromUtf8(doc.toJson()));
}
}
}
return;
}
case Command::EmailSent: {
const auto email = arguments["email"_L1].toString();
QJsonDocument doc(QJsonObject{
{"type"_L1, "email-sent"_L1},
{"arguments"_L1, arguments},
});
sendMessageToNativeClient(email, doc.toJson());
return;
}
case Command::Undefined:
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()
{
QWebSocket *pClient = qobject_cast<QWebSocket *>(sender());
if (pClient) {
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{
{"type"_L1, "disconnection"_L1},
});
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{
{"type"_L1, "disconnection"_L1},
});
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
Tue, May 5, 6:13 AM (1 d, 23 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
20/b7/7867d05700b7baa11f4016c6640e
Attached To
rOJ GpgOL.js
Event Timeline
Log In to Comment