diff --git a/server/webserver.cpp b/server/webserver.cpp index 52d7935..2a7d5a7 100644 --- a/server/webserver.cpp +++ b/server/webserver.cpp @@ -1,305 +1,300 @@ // SPDX-FileCopyrightText: 2023 g10 code GmbH // SPDX-Contributor: Carl Schwan // SPDX-License-Identifier: GPL-2.0-or-later #include "webserver.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #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/"_s, &EmailController::draftAction); - m_httpServer->afterRequest([](QHttpServerResponse &&resp) { - resp.setHeader("Access-Control-Allow-Origin", "*"); - return std::move(resp); - }); - 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(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 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(sender()); if (pClient) { pClient->sendBinaryMessage(message); } } void WebServer::socketDisconnected() { QWebSocket *pClient = qobject_cast(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); } }