Page MenuHome GnuPG

webserver.cpp
No OneTemporary

webserver.cpp

// 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 <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QJsonDocument>
#include <QHttpServer>
#include <QHttpServerResponse>
#include <QSslCertificate>
#include <QSslKey>
#include <QWebSocketServer>
#include <QWebSocket>
#include <QStandardPaths>
#include "controllers/registrationcontroller.h"
#include "controllers/staticcontroller.h"
#include "controllers/emailcontroller.h"
using namespace Qt::Literals::StringLiterals;
WebServer WebServer::s_instance = WebServer();
WebServer &WebServer::self()
{
return s_instance;
}
WebServer::WebServer()
: QObject(nullptr)
, 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)) {
qWarning() << u"Couldn't open file for reading: %1"_s.arg(privateKeyFile.errorString());
return false;
}
const QSslKey sslKey(&privateKeyFile, QSsl::Rsa);
privateKeyFile.close();
const auto sslCertificateChain = QSslCertificate::fromPath(certPath);
if (sslCertificateChain.isEmpty()) {
qWarning() << u"Couldn't retrieve SSL certificate from file."_s;
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, &EmailController::socketWebAction);
m_httpServer->route(u"/draft/<arg>"_s, &EmailController::draftAction);
m_httpServer->sslSetup(sslCertificateChain.front(), sslKey);
const auto port = m_httpServer->listen(QHostAddress::Any, WebServer::Port);
if (!port) {
qWarning() << "Server failed to listen on a port.";
return false;
}
qWarning() << u"Running http server on https://127.0.0.1:%1/ (Press CTRL+C to quit)"_s.arg(port);
QSslConfiguration sslConfiguration;
sslConfiguration.setPeerVerifyMode(QSslSocket::VerifyNone);
sslConfiguration.setLocalCertificate(sslCertificateChain.front());
sslConfiguration.setPrivateKey(sslKey);
m_webSocketServer->setSslConfiguration(sslConfiguration);
if (m_webSocketServer->listen(QHostAddress::Any, WebServer::WebSocketPort)) {
qWarning() << 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;
}
qDebug() << "Client connected:" << pSocket->peerName() << pSocket->origin();
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) {
qWarning() << "Error parsing json" << error.errorString();
return;
}
if (!doc.isObject()) {
qWarning() << "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()) {
qWarning() << "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();
qDebug() << "Register" << arguments;
if (type.isEmpty()) {
qWarning() << "empty client type given when registering";
return;
}
const auto emails = arguments["emails"_L1].toArray();
if (type == "webclient"_L1) {
if (emails.isEmpty()) {
qWarning() << "empty email given";
}
for (const auto &email : emails) {
m_webClientsMappingToEmail[email.toString()] = socket;
qWarning() << "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()) {
qWarning() << "empty email given";
}
for (const auto &email : emails) {
m_nativeClientsMappingToEmail[email.toString()] = socket;
qWarning() << "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();
const auto socket = m_nativeClientsMappingToEmail[email];
if (!socket) {
return;
}
QJsonDocument doc(QJsonObject{
{ "type"_L1, "email-sent"_L1 },
{ "arguments"_L1, arguments },
});
socket->sendTextMessage(QString::fromUtf8(doc.toJson()));
return;
}
case Command::Undefined:
qWarning() << "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;
}
void WebServer::processBinaryMessage(QByteArray message)
{
qWarning() << "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) {
qDebug() << "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];
qDebug() << "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) {
qDebug() << "webSocket not equal" << email << webSocket << pClient;
continue;
}
qDebug() << "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

Mime Type
text/x-c
Expires
Mon, Aug 25, 11:02 PM (1 d, 6 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
83/d6/600d2b8533bb9268498b4b84ae3b

Event Timeline