Page MenuHome GnuPG

No OneTemporary

diff --git a/broker/assets/script.js b/broker/assets/script.js
index 8ebe5e7..b33a00d 100644
--- a/broker/assets/script.js
+++ b/broker/assets/script.js
@@ -1,229 +1,261 @@
// SPDX-FileCopyrightText: 2023 g10 code GmbH
// SPDX-Contributor: Carl Schwan <carl.schwan@gnupg.com>
// SPDX-License-Identifier: GPL-2.0-or-later
function downloadViaRest(callback) {
const context = {
isRest: true
};
Office.context.mailbox.getCallbackTokenAsync(context, (tokenResults) => {
if (tokenResults.status === Office.AsyncResultStatus.Failed) {
console.error('Failed to get rest api auth token');
return;
}
const request =
'<?xml version="1.0" encoding="utf-8"?>' +
'<soap:Envelope xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"' +
' xmlns:xsd="https://www.w3.org/2001/XMLSchema"' +
' xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"' +
' xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types">' +
' <soap:Header>' +
' <RequestServerVersion Version="Exchange2013" xmlns="http://schemas.microsoft.com/exchange/services/2006/types" soap:mustUnderstand="0" />' +
' </soap:Header>' +
' <soap:Body>' +
' <GetItem xmlns="http://schemas.microsoft.com/exchange/services/2006/messages">' +
' <ItemShape>' +
' <t:BaseShape>IdOnly</t:BaseShape>' +
' <t:IncludeMimeContent>true</t:IncludeMimeContent>' +
' </ItemShape>' +
' <ItemIds><t:ItemId Id="' + Office.context.mailbox.item.itemId + '"/></ItemIds>' +
' </GetItem>' +
' </soap:Body>' +
'</soap:Envelope>';
Office.context.mailbox.makeEwsRequestAsync(request, (asyncResult) => {
const parser = new DOMParser();
xmlDoc = parser.parseFromString(asyncResult.value, "text/xml");
const mimeContent = xmlDoc.getElementsByTagName('t:MimeContent')[0].innerHTML;
callback(atob(mimeContent));
});
});
}
async function view(content) {
const response = await fetch('https://localhost:5656/view', {
method: 'POST',
body: content,
headers: {
'X-EMAIL': Office.context.mailbox.userProfile.emailAddress,
'X-NAME': Office.context.mailbox.userProfile.displayName,
},
});
const json = await response.json();
return json;
}
async function info(content) {
const response = await fetch('https://localhost:5656/info', {
method: 'POST',
body: content,
headers: {
'X-EMAIL': Office.context.mailbox.userProfile.emailAddress,
'X-NAME': Office.context.mailbox.userProfile.displayName,
},
});
const json = await response.json();
return json;
}
async function newEmail(content) {
const response = await fetch('https://localhost:5656/new', {
method: 'POST',
headers: {
'X-EMAIL': Office.context.mailbox.userProfile.emailAddress,
'X-NAME': Office.context.mailbox.userProfile.displayName,
},
});
const json = await response.json();
return json;
}
async function reply(content) {
const response = await fetch('https://localhost:5656/reply', {
method: 'POST',
body: content,
headers: {
'X-EMAIL': Office.context.mailbox.userProfile.emailAddress,
'X-NAME': Office.context.mailbox.userProfile.displayName,
},
});
const json = await response.json();
return json;
}
async function forward(content) {
const response = await fetch('https://localhost:5656/forward', {
method: 'POST',
body: content,
headers: {
'X-EMAIL': Office.context.mailbox.userProfile.emailAddress,
'X-NAME': Office.context.mailbox.userProfile.displayName,
},
});
const json = await response.json();
return json;
}
async function openDraft(id) {
const response = await fetch(`https://localhost:5656/draft/${id}`, {
method: 'POST',
headers: {
'X-EMAIL': Office.context.mailbox.userProfile.emailAddress,
'X-NAME': Office.context.mailbox.userProfile.displayName,
},
});
const json = await response.json();
return json;
}
async function deleteDraft(id) {
const response = await fetch(`https://localhost:5656/draft/${id}`, {
method: 'DELETE',
headers: {
'X-EMAIL': Office.context.mailbox.userProfile.emailAddress,
'X-NAME': Office.context.mailbox.userProfile.displayName,
},
});
const json = await response.json();
return json;
}
+function showError(errorMessage) {
+ const errorElement = document.getElementById('error');
+ errorElement.innerHTML = errorMessage;
+ errorElement.classList.remove('d-none');
+}
+
+function hideError() {
+ const errorElement = document.getElementById('error');
+ errorElement.classList.add('d-none');
+}
+
Office.onReady().
then(()=> {
downloadViaRest(async (content) => {
const status = await info(content)
const statusText = document.getElementById('status-text');
if (status.encrypted || status.signed) {
const decryptButton = document.getElementById('decrypt-button');
decryptButton.classList.remove('d-none');
if (status.encrypted) {
decryptButton.innerText = "Decrypt";
statusText.innerText = status.signed ? "This mail is encrypted and signed." : "This mail is encrypted.";
} else if (status.signed) {
decryptButton.innerText = "Show signature";
statusText.innerText = "This mail is signed";
}
decryptButton.addEventListener('click', (event) => {
view(content);
});
}
document.getElementById('reply-button').addEventListener('click', (event) => {
reply(content);
});
document.getElementById('forward-button').addEventListener('click', (event) => {
forward(content);
});
document.getElementById('new-button').addEventListener('click', (event) => {
newEmail();
});
if (status.drafts.length === 0) {
document.getElementById('no-draft').classList.remove('d-none');
} else {
const draftsContainer = document.getElementById('drafts');
status.drafts.forEach(draft => {
const draftElementContainer = document.createElement('li');
const draftElement = document.createElement('button');
draftElement.classList.add('btn', 'w-100', 'd-flex', 'flex-row', 'align-items-center');
draftElement.addEventListener('click', (event) => {
openDraft(draft.id);
});
const date = new Date(draft.last_modification * 1000);
let todaysDate = new Date();
let lastModification = '';
if ((new Date(date)).setHours(0, 0, 0, 0) == todaysDate.setHours(0, 0, 0, 0)) {
lastModification = date.toLocaleTimeString([], {
hour: 'numeric',
minute: 'numeric',
});
} else {
lastModification = date.toLocaleDateString();
}
const content = document.createTextNode('Last Modified: ' + lastModification);
draftElement.appendChild(content);
const deleteDraftButton = document.createElement('button');
deleteDraftButton.classList.add('btn', 'btn-danger', 'ms-auto', 'py-1');
deleteDraftButton.addEventListener('click', (event) => {
deleteDraft(draft.id);
draftElement.remove();
});
const deleteDraftButtonContent = document.createTextNode('X');
deleteDraftButton.appendChild(deleteDraftButtonContent);
draftElement.appendChild(deleteDraftButton);
draftElementContainer.appendChild(draftElement);
draftsContainer.appendChild(draftElementContainer);
});
}
- // Create WebSocket connection.
- const socket = new WebSocket("wss://localhost:5657");
-
- // Connection opened
- socket.addEventListener("open", (event) => {
- socket.send(JSON.stringify({
- command: "register",
- arguments: {
- email: Office.context.mailbox.userProfile.emailAddress,
- },
- }));
- });
+ function webSocketConnect() {
+ // Create WebSocket connection.
+ const socket = new WebSocket("wss://localhost:5657");
- // Listen for messages
- socket.addEventListener("message", (event) => {
- console.log("Message from server ", event.data);
- Office.context.mailbox.makeEwsRequestAsync(event.data, (asyncResult) => {
- console.log("email sent", asyncResult);
+ // Connection opened
+ socket.addEventListener("open", (event) => {
+ hideError();
+ socket.send(JSON.stringify({
+ command: "register",
+ arguments: {
+ email: Office.context.mailbox.userProfile.emailAddress,
+ type: 'webclient',
+ },
+ }));
});
- });
+ socket.addEventListener("close", (event) => {
+ showError('Native client was disconnected');
+ setTimeout(function() {
+ webSocketConnect();
+ }, 1000);
+ });
+
+ socket.addEventListener("error", (event) => {
+ showError('Native client received an error');
+ setTimeout(function() {
+ webSocketConnect();
+ }, 1000);
+ });
+
+ // Listen for messages
+ socket.addEventListener("message", ({ data }) => {
+ console.log("Message from server ", data.type);
+ if (data.type === 'ews') {
+ Office.context.mailbox.makeEwsRequestAsync(data.payload, (asyncResult) => {
+ console.log("email sent", asyncResult);
+ });
+ }
+ });
+ }
+
+ webSocketConnect();
});
});
diff --git a/broker/controllers/emailcontroller.cpp b/broker/controllers/emailcontroller.cpp
index 21b3d04..b277bbb 100644
--- a/broker/controllers/emailcontroller.cpp
+++ b/broker/controllers/emailcontroller.cpp
@@ -1,166 +1,164 @@
// SPDX-FileCopyrightText: 2023 g10 code GmbH
// SPDX-Contributor: Carl Schwan <carl.schwan@gnupg.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#include "emailcontroller.h"
#include <QJsonObject>
#include <QHttpServerRequest>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QtConcurrent/QtConcurrent>
#include <QPromise>
#include <QEventLoop>
#include <QUuid>
#include "websocketrequest.h"
using namespace Qt::Literals::StringLiterals;
QHttpServerResponse EmailController::abstractEmailAction(const QHttpServerRequest &request, const QString &action, QHttpServerRequest::Method method)
{
const auto server = checkAuthentification(request);
if (!server) {
return forbidden();
}
QNetworkRequest viewEmailRequest(QUrl(u"http://127.0.0.1:"_s + QString::number(server->port) + u'/' + action));
viewEmailRequest.setHeader(QNetworkRequest::ContentTypeHeader, u"application/json"_s);
auto email = AbstractController::findHeader(request.headers(), "X-EMAIL");
auto displayName = AbstractController::findHeader(request.headers(), "X-NAME");
auto token = QUuid::createUuid().toString(QUuid::WithoutBraces).toUtf8();
viewEmailRequest.setRawHeader("X-EMAIL", email);
viewEmailRequest.setRawHeader("X-TOKEN", token);
viewEmailRequest.setRawHeader("X-NAME", displayName);
auto &serverState = ServerState::instance();
serverState.composerRequest[token] = QString::fromUtf8(email);
auto &state = ServerState::instance();
QNetworkReply *reply;
if (method == QHttpServerRequest::Method::Post) {
const auto body = request.body();
reply = state.qnam.post(viewEmailRequest, body);
} else {
reply = state.qnam.deleteResource(viewEmailRequest);
}
QObject::connect(reply, &QNetworkReply::finished, reply, [reply]() {
if (reply->error() != QNetworkReply::NoError) {
qWarning() << reply->error() << reply->errorString();
} else {
qWarning() << "sent request to view message to server";
}
});
return QHttpServerResponse(QJsonObject {
{"status"_L1, "ok"_L1},
});
}
QHttpServerResponse EmailController::viewEmailAction(const QHttpServerRequest &request)
{
if (request.method() != QHttpServerRequest::Method::Post) {
return badRequest(u"Endpoint only supports POST request"_s);
}
return abstractEmailAction(request, u"view"_s);
}
QHttpServerResponse EmailController::newEmailAction(const QHttpServerRequest &request)
{
if (request.method() != QHttpServerRequest::Method::Post) {
return badRequest(u"Endpoint only supports POST request"_s);
}
return abstractEmailAction(request, u"new"_s);
}
QHttpServerResponse EmailController::draftAction(QString, const QHttpServerRequest &request)
{
if (request.method() != QHttpServerRequest::Method::Post && request.method() != QHttpServerRequest::Method::Delete) {
return badRequest(u"Endpoint only supports POST request"_s);
}
return abstractEmailAction(request, request.url().path(), request.method());
}
QHttpServerResponse EmailController::replyEmailAction(const QHttpServerRequest &request)
{
if (request.method() != QHttpServerRequest::Method::Post) {
return badRequest(u"Endpoint only supports POST request"_s);
}
return abstractEmailAction(request, u"reply"_s);
}
QHttpServerResponse EmailController::forwardEmailAction(const QHttpServerRequest &request)
{
if (request.method() != QHttpServerRequest::Method::Post) {
return badRequest(u"Endpoint only supports POST request"_s);
}
return abstractEmailAction(request, u"forward"_s);
}
QHttpServerResponse checkStatus(int port, const QByteArray &body)
{
QNetworkRequest infoEmailRequest(QUrl(u"http://127.0.0.1:"_s + QString::number(port) + u"/info"_s));
infoEmailRequest.setHeader(QNetworkRequest::ContentTypeHeader, u"application/json"_s);
auto &state = ServerState::instance();
QEventLoop eventLoop;
auto reply = state.qnam.post(infoEmailRequest, body);
QObject::connect(reply, &QNetworkReply::finished, &eventLoop, &QEventLoop::quit);
eventLoop.exec();
QJsonParseError error;
const auto resultBody = QJsonDocument::fromJson(reply->readAll(), &error);
if (resultBody.isNull()) {
return QHttpServerResponse(QHttpServerResponse::StatusCode::BadRequest);
}
if (!resultBody.isObject()) {
return QHttpServerResponse(QHttpServerResponse::StatusCode::BadRequest);
}
return QHttpServerResponse{resultBody.object()};
}
QHttpServerResponse EmailController::infoEmailAction(const QHttpServerRequest &request)
{
if (request.method() != QHttpServerRequest::Method::Post) {
return badRequest(u"Endpoint only supports POST request"_s);
}
const auto server = checkAuthentification(request);
if (!server) {
return forbidden();
}
return checkStatus(server->port, request.body());
}
-QHttpServerResponse EmailController::sendEmailAction(const QHttpServerRequest &request)
+QHttpServerResponse EmailController::socketWebAction(const QHttpServerRequest &request)
{
- const auto soapMail = request.body();
-
const auto email = QString::fromUtf8(findHeader(request.headers(), "X-EMAIL"));
const auto token = findHeader(request.headers(), "X-TOKEN");
const auto &serverState = ServerState::instance();
qDebug() << serverState.composerRequest << email << token;
if (serverState.composerRequest[token] != email) {
return forbidden();
}
WebsocketRequest websocketRequest(email);
- websocketRequest.setPayload(soapMail);
+ websocketRequest.setPayload(request.body());
websocketRequest.send();
return QHttpServerResponse(QJsonObject{
{ "status"_L1, "OK"_L1 },
});
}
diff --git a/broker/controllers/emailcontroller.h b/broker/controllers/emailcontroller.h
index b5e07e1..ead4a20 100644
--- a/broker/controllers/emailcontroller.h
+++ b/broker/controllers/emailcontroller.h
@@ -1,27 +1,27 @@
// SPDX-FileCopyrightText: 2023 g10 code GmbH
// SPDX-Contributor: Carl Schwan <carl.schwan@gnupg.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "abstractcontroller.h"
#include <QHttpServerRequest>
class EmailController : public AbstractController
{
public:
// Request from the web client
static QHttpServerResponse viewEmailAction(const QHttpServerRequest &request);
static QHttpServerResponse infoEmailAction(const QHttpServerRequest &request);
static QHttpServerResponse newEmailAction(const QHttpServerRequest &request);
static QHttpServerResponse replyEmailAction(const QHttpServerRequest &request);
static QHttpServerResponse forwardEmailAction(const QHttpServerRequest &request);
static QHttpServerResponse draftAction(QString draft, const QHttpServerRequest &request);
- // Request from the native client
- static QHttpServerResponse sendEmailAction(const QHttpServerRequest &request);
+ /// Forward request from the native client to the web client
+ static QHttpServerResponse socketWebAction(const QHttpServerRequest &request);
private:
static QHttpServerResponse abstractEmailAction(const QHttpServerRequest &request, const QString &action, QHttpServerRequest::Method method = QHttpServerRequest::Method::Post);
};
diff --git a/broker/main.cpp b/broker/main.cpp
index 992ee1f..13b220a 100644
--- a/broker/main.cpp
+++ b/broker/main.cpp
@@ -1,90 +1,90 @@
// SPDX-FileCopyrightText: 2023 g10 code GmbH
// SPDX-Contributor: Carl Schwan <carl.schwan@gnupg.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#include <QCoreApplication>
#include <QHttpServer>
#include <QHttpServerResponse>
#include <QFile>
#include <QJsonDocument>
#include <QJsonObject>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QSslCertificate>
#include <QSslKey>
#include "controllers/registrationcontroller.h"
#include "controllers/staticcontroller.h"
#include "controllers/emailcontroller.h"
#include "websocketserver.h"
#include "websocketrequest.h"
using namespace Qt::Literals::StringLiterals;
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
QHttpServer server;
QNetworkAccessManager qnam;
// Static assets controller
server.route(u"/"_s, &StaticController::homeAction);
server.route(u"/assets/"_s, &StaticController::assetsAction);
// Registration controller
server.route(u"/register"_s, &RegistrationController::registerAction);
// Email controller
server.route(u"/view"_s, &EmailController::viewEmailAction);
server.route(u"/info"_s, &EmailController::infoEmailAction);
server.route(u"/reply"_s, &EmailController::replyEmailAction);
server.route(u"/forward"_s, &EmailController::forwardEmailAction);
server.route(u"/new"_s, &EmailController::newEmailAction);
- server.route(u"/send-mail"_s, &EmailController::sendEmailAction);
+ server.route(u"/socket-web"_s, &EmailController::socketWebAction);
server.route(u"/draft/<arg>"_s, &EmailController::draftAction);
server.afterRequest([](QHttpServerResponse &&resp) {
resp.setHeader("Access-Control-Allow-Origin", "*");
return std::move(resp);
});
const auto sslCertificateChain =
QSslCertificate::fromPath(QStringLiteral(":/assets/certificate.crt"));
if (sslCertificateChain.empty()) {
qWarning() << QCoreApplication::translate("QHttpServerExample",
"Couldn't retrieve SSL certificate from file.");
return -1;
}
QFile privateKeyFile(QStringLiteral(":/assets/private.key"));
if (!privateKeyFile.open(QIODevice::ReadOnly)) {
qWarning() << QCoreApplication::translate("QHttpServerExample",
"Couldn't open file for reading: %1")
.arg(privateKeyFile.errorString());
return -1;
}
const QSslKey sslKey(&privateKeyFile, QSsl::Rsa);
server.sslSetup(sslCertificateChain.front(), sslKey);
privateKeyFile.close();
const auto sslPort = server.listen(QHostAddress::Any);
if (!sslPort) {
qWarning() << QCoreApplication::translate("QHttpServerExample",
"Server failed to listen on a port.");
return -1;
}
const auto port = server.listen(QHostAddress::Any, 5656);
if (!port) {
qWarning() << "Server failed to listen on a port.";
return 1;
}
qWarning() << u"Running on https://127.0.0.1:%1/ (Press CTRL+C to quit)"_s.arg(port);
WebsocketServer websocketServer(sslCertificateChain.front(), sslKey, 5657);
WebsocketRequestBackend::instance().setServer(&websocketServer);
return app.exec();
}
diff --git a/broker/web/index.html b/broker/web/index.html
index 997c199..4e0d704 100644
--- a/broker/web/index.html
+++ b/broker/web/index.html
@@ -1,107 +1,111 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>Demo gpgol.js</title>
<style>
.d-flex {
display: flex;
flex-direction: column;
}
.flex-row {
flex-direction: row !important;
}
.d-none {
display: none;
}
.w-100 {
width: 100%;
}
.btn {
background: none;
border: none;
padding: 0.5rem;
text-align: left;
background-color: rgb(235, 243, 252);
border-radius: 3px;
}
.btn:hover {
background-color: rgb(207, 228, 250);
}
.btn-danger {
background-color: #f4a4a4;
}
.btn-danger:hover {
background-color: #fb8484;
}
.align-items-center {
align-items: center;
}
.gap {
gap: 0.5rem;
}
.mt-0, .my-0, .m-0 {
margin-top: 0 !important;
}
.mb-0, .my-0, .m-0 {
margin-bottom: 0 !important;
}
.pt-0, .py-0, .p-0 {
padding-top: 0 !important;
}
.pb-0, .py-0, .p-0 {
padding-bottom: 0 !important;
}
.ps-0, .px-0, .p-0 {
padding-left: 0 !important;
}
.pe-0, .px-0, .p-0 {
padding-right: 0 !important;
}
.pt-1, .py-1, .p-1 {
padding-top: 0.25rem !important;
}
.pb-1, .py-1, .p-1 {
padding-bottom: 0.25rem !important;
}
.ps-1, .px-1, .p-1 {
padding-left: 0.25rem !important;
}
.pe-1, .px-1, .p-1 {
padding-right: 0.25rem !important;
}
.ms-auto, .mx-auto {
margin-left: auto !important;
}
.me-auto, .mx-auto {
margin-right: auto !important;
}
.list-unstyled {
padding-left: 0;
list-style: none;
}
body {
font-family: "Noto Sans";
}
h2 {
font-size: 1.5rem;
+}
+.alert-error {
+ background-color: #f4a4a4;
}
</style>
<script type="text/javascript" src="https://appsforoffice.microsoft.com/lib/beta/hosted/office.js"></script>
<script type="text/javascript" src="/assets/script.js"></script>
</head>
<body>
<div class="d-flex gap">
+ <div id="error" class="d-none alert alert-error"></div>
<p id="status-text">Loading...</p>
<button id="decrypt-button" class="d-none w-100 btn fa">Decrypt</button>
<hr class="w-100 my-0" />
<button id="new-button" class="w-100 btn">New secure email</button>
<button id="reply-button" class="w-100 btn">Reply securely</button>
<button id="forward-button" class="w-100 btn">Forward securely</button>
<h2 class="mb-0">Drafts</h2>
<ul id="drafts" class="my-0 list-unstyled">
</ul>
<p id="no-draft" class="d-none">No draft found</p>
</div>
</body>
</html>
\ No newline at end of file
diff --git a/broker/websocketserver.cpp b/broker/websocketserver.cpp
index 662c6ff..bff82b3 100644
--- a/broker/websocketserver.cpp
+++ b/broker/websocketserver.cpp
@@ -1,153 +1,158 @@
// SPDX-FileCopyrightText: 2023 g10 code GmbH
// SPDX-Contributor: Carl Schwan <carl.schwan@gnupg.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#include "websocketserver.h"
#include "QWebSocketServer"
#include "QWebSocket"
#include <QDebug>
#include <QFile>
#include <QSslCertificate>
#include <QJsonDocument>
#include <QJsonObject>
#include <QSslKey>
using namespace Qt::Literals::StringLiterals;
WebsocketServer::WebsocketServer(const QSslCertificate &certificate,
const QSslKey &sslKey,
quint16 port,
QObject *parent)
: QObject(parent)
, m_pWebSocketServer(nullptr)
{
m_pWebSocketServer = new QWebSocketServer(QStringLiteral("SSL Echo Server"),
QWebSocketServer::SecureMode,
this);
QSslConfiguration sslConfiguration;
sslConfiguration.setPeerVerifyMode(QSslSocket::VerifyNone);
sslConfiguration.setLocalCertificate(certificate);
sslConfiguration.setPrivateKey(sslKey);
m_pWebSocketServer->setSslConfiguration(sslConfiguration);
if (m_pWebSocketServer->listen(QHostAddress::Any, port))
{
qDebug() << "SSL Echo Server listening on port" << port;
connect(m_pWebSocketServer, &QWebSocketServer::newConnection,
this, &WebsocketServer::onNewConnection);
connect(m_pWebSocketServer, &QWebSocketServer::sslErrors,
this, &WebsocketServer::onSslErrors);
}
}
WebsocketServer::~WebsocketServer()
{
m_pWebSocketServer->close();
qDeleteAll(m_clients.begin(), m_clients.end());
}
void WebsocketServer::onNewConnection()
{
QWebSocket *pSocket = m_pWebSocketServer->nextPendingConnection();
qDebug() << "Client connected:" << pSocket->peerName() << pSocket->origin();
connect(pSocket, &QWebSocket::textMessageReceived, this, &WebsocketServer::processTextMessage);
connect(pSocket, &QWebSocket::binaryMessageReceived,
this, &WebsocketServer::processBinaryMessage);
connect(pSocket, &QWebSocket::disconnected, this, &WebsocketServer::socketDisconnected);
m_clients << pSocket;
}
void WebsocketServer::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, WebsocketServer::Command> commandMapping {
{ "register"_L1, WebsocketServer::Command::Register },
};
const auto command = commandMapping[doc["command"_L1].toString()];
processCommand(command, object["arguments"_L1].toObject(), webClient);
}
}
void WebsocketServer::processCommand(Command command, const QJsonObject &arguments, QWebSocket *socket)
{
switch (command) {
case Command::Register: {
const auto email = arguments["email"_L1].toString();
- if (email.isEmpty()) {
- qWarning() << "empty email given";
+ const auto type = arguments["type"_L1].toString();
+ if (email.isEmpty() || type.isEmpty()) {
+ qWarning() << "empty email or type given";
return;
}
- m_clientsMappingToEmail[email] = socket;
+ if (type == "webclient"_L1) {
+ m_webClientsMappingToEmail[email] = socket;
+ } else {
+ m_nativeClientsMappingToEmail[email] = socket;
+ }
qWarning() << "email" << email << "mapped to a client";
return;
}
case Command::Undefined:
qWarning() << "Invalid json received: invalid command" ;
return;
}
}
bool WebsocketServer::sendMessageToWebClient(const QString &email, const QString &payload)
{
- if (!m_clientsMappingToEmail.contains(email)) {
+ if (!m_webClientsMappingToEmail.contains(email)) {
qWarning() << "trying to send to an unregistered client";
return false;
}
- auto socket = m_clientsMappingToEmail[email];
+ auto socket = m_webClientsMappingToEmail[email];
Q_ASSERT(socket);
socket->sendTextMessage(payload);
return true;
}
void WebsocketServer::processBinaryMessage(QByteArray message)
{
qWarning() << "got binary message" << message;
QWebSocket *pClient = qobject_cast<QWebSocket *>(sender());
if (pClient) {
pClient->sendBinaryMessage(message);
}
}
void WebsocketServer::socketDisconnected()
{
qDebug() << "Client disconnected";
QWebSocket *pClient = qobject_cast<QWebSocket *>(sender());
if (pClient)
{
m_clients.removeAll(pClient);
pClient->deleteLater();
}
}
void WebsocketServer::onSslErrors(const QList<QSslError> &)
{
qDebug() << "Ssl errors occurred";
}
diff --git a/broker/websocketserver.h b/broker/websocketserver.h
index a7d454d..a2e6510 100644
--- a/broker/websocketserver.h
+++ b/broker/websocketserver.h
@@ -1,50 +1,51 @@
// SPDX-FileCopyrightText: 2023 g10 code GmbH
// SPDX-Contributor: Carl Schwan <carl.schwan@gnupg.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <QObject>
#include <QList>
#include <QByteArray>
#include <QSslError>
QT_FORWARD_DECLARE_CLASS(QWebSocketServer)
QT_FORWARD_DECLARE_CLASS(QWebSocket)
class WebsocketRequestBackend;
class WebsocketServer : public QObject
{
Q_OBJECT
public:
explicit WebsocketServer(const QSslCertificate &certificate,
const QSslKey &key,
quint16 port,
QObject *parent = nullptr);
~WebsocketServer() override;
private Q_SLOTS:
void onNewConnection();
void processTextMessage(QString message);
void processBinaryMessage(QByteArray message);
void socketDisconnected();
void onSslErrors(const QList<QSslError> &errors);
private:
bool sendMessageToWebClient(const QString &email, const QString &payload);
enum class Command {
Undefined,
Register,
};
void processCommand(Command command, const QJsonObject &arguments, QWebSocket *socket);
QWebSocketServer *m_pWebSocketServer;
QList<QWebSocket *> m_clients;
- QHash<QString, QWebSocket *> m_clientsMappingToEmail;
+ QHash<QString, QWebSocket *> m_webClientsMappingToEmail;
+ QHash<QString, QWebSocket *> m_nativeClientsMappingToEmail;
friend WebsocketRequestBackend;
};
diff --git a/server/editor/composerviewbase.cpp b/server/editor/composerviewbase.cpp
index 6aa0a3f..5bc104f 100644
--- a/server/editor/composerviewbase.cpp
+++ b/server/editor/composerviewbase.cpp
@@ -1,1370 +1,1377 @@
/*
SPDX-FileCopyrightText: 2010 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
SPDX-FileCopyrightText: 2010 Leo Franchi <lfranchi@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "composerviewbase.h"
#include "attachment/attachmentcontrollerbase.h"
#include "attachment/attachmentmodel.h"
#include "richtextcomposerng.h"
#include "richtextcomposersignatures.h"
#include "../identity/identitycombo.h"
#include "../identity/identitymanager.h"
#include "composer.h"
#include "nodehelper.h"
#include "signaturecontroller.h"
#include "part/globalpart.h"
#include "part/infopart.h"
#include "kleo_util.h"
#include "util.h"
#include "util_p.h"
#include "ews/ewsmailfactory.h"
#include "mailtemplates.h"
#include "../qnam.h"
#include "messagecomposersettings.h"
#include "recipientseditor.h"
#include <KCursorSaver>
#include <KIdentityManagementCore/Identity>
#include <MimeTreeParserCore/ObjectTreeParser>
#include <Sonnet/DictionaryComboBox>
#include <KPIMTextEdit/RichTextComposerControler>
#include <KPIMTextEdit/RichTextComposerImages>
#include <KEmailAddress>
#include <KIdentityManagementCore/IdentityManager>
#include <KIdentityManagementWidgets/IdentityCombo>
#include "editor_debug.h"
#include <Libkleo/ExpiryChecker>
#include <Libkleo/KeyResolverCore>
#include <Libkleo/ExpiryCheckerSettings>
#include <libkleo/enum.h>
#include <libkleo/keyresolver.h>
#include <QGpgME/ExportJob>
#include <QGpgME/ImportJob>
#include <QGpgME/Protocol>
#include <global.h>
#include <gpgme++/context.h>
#include <gpgme++/importresult.h>
#include <KLocalizedString>
#include <KMessageBox>
#include <QDir>
#include <QStandardPaths>
#include <QTemporaryDir>
#include <QTimer>
#include <QUuid>
#include <QSaveFile>
#include <QNetworkReply>
+#include <qjsondocument.h>
#include "draft/draftmanager.h"
using namespace MessageComposer;
using namespace Qt::Literals::StringLiterals;
ComposerViewBase::ComposerViewBase(QObject *parent, QWidget *parentGui)
: QObject(parent)
, m_msg(KMime::Message::Ptr(new KMime::Message))
, m_parentWidget(parentGui)
, m_cryptoMessageFormat(Kleo::AutoFormat)
, m_autoSaveInterval(60000) // default of 1 min
{
m_charsets << "utf-8"; // default, so we have a backup in case client code forgot to set.
initAutoSave();
connect(this, &ComposerViewBase::composerCreated, this, &ComposerViewBase::slotComposerCreated);
}
ComposerViewBase::~ComposerViewBase() = default;
bool ComposerViewBase::isComposing() const
{
return !m_composers.isEmpty();
}
void ComposerViewBase::setMessage(const KMime::Message::Ptr &msg, bool allowDecryption)
{
if (m_attachmentModel) {
const auto attachments{m_attachmentModel->attachments()};
for (const MessageCore::AttachmentPart::Ptr &attachment : attachments) {
if (!m_attachmentModel->removeAttachment(attachment)) {
qCWarning(EDITOR_LOG) << "Attachment not found.";
}
}
}
m_msg = msg;
if (m_recipientsEditor) {
m_recipientsEditor->clear();
bool resultTooManyRecipients = m_recipientsEditor->setRecipientString(m_msg->to()->mailboxes(), Recipient::To);
if (!resultTooManyRecipients) {
resultTooManyRecipients = m_recipientsEditor->setRecipientString(m_msg->cc()->mailboxes(), Recipient::Cc);
}
if (!resultTooManyRecipients) {
resultTooManyRecipients = m_recipientsEditor->setRecipientString(m_msg->bcc()->mailboxes(), Recipient::Bcc);
}
if (!resultTooManyRecipients) {
resultTooManyRecipients = m_recipientsEditor->setRecipientString(m_msg->replyTo()->mailboxes(), Recipient::ReplyTo);
}
m_recipientsEditor->setFocusBottom();
Q_EMIT tooManyRecipient(resultTooManyRecipients);
}
// First, we copy the message and then parse it to the object tree parser.
// The otp gets the message text out of it, in textualContent(), and also decrypts
// the message if necessary.
auto msgContent = new KMime::Content;
msgContent->setContent(m_msg->encodedContent());
msgContent->parse();
//MimeTreeParser::ObjectTreeParser otp(); // All default are ok
//emptySource.setDecryptMessage(allowDecryption);
//otp.parseObjectTree(msgContent);
// Load the attachments
//const auto attachmentsOfExtraContents{otp.nodeHelper()->attachmentsOfExtraContents()};
//for (const auto &att : attachmentsOfExtraContents) {
// addAttachmentPart(att);
//}
const auto attachments{msgContent->attachments()};
for (const auto &att : attachments) {
addAttachmentPart(att);
}
// Set the HTML text and collect HTML images
bool isHtml = false;
const auto body = MailTemplates::body(msg, isHtml);
if (isHtml) {
enableHtml();
} else {
disableHtml(LetUserConfirm);
}
editor()->setText(body);
if (auto hdr = m_msg->headerByType("X-KMail-CursorPos")) {
m_editor->setCursorPositionFromStart(hdr->asUnicodeString().toUInt());
}
delete msgContent;
}
void ComposerViewBase::saveMailSettings()
{
const auto identity = currentIdentity();
auto header = new KMime::Headers::Generic("X-KMail-Identity");
header->fromUnicodeString(QString::number(identity.uoid()), "utf-8");
m_msg->setHeader(header);
header = new KMime::Headers::Generic("X-KMail-Identity-Name");
header->fromUnicodeString(identity.identityName(), "utf-8");
m_msg->setHeader(header);
header = new KMime::Headers::Generic("X-KMail-Dictionary");
header->fromUnicodeString(m_dictionary->currentDictionary(), "utf-8");
m_msg->setHeader(header);
// Save the quote prefix which is used for this message. Each message can have
// a different quote prefix, for example depending on the original sender.
if (m_editor->quotePrefixName().isEmpty()) {
m_msg->removeHeader("X-KMail-QuotePrefix");
} else {
header = new KMime::Headers::Generic("X-KMail-QuotePrefix");
header->fromUnicodeString(m_editor->quotePrefixName(), "utf-8");
m_msg->setHeader(header);
}
if (m_editor->composerControler()->isFormattingUsed()) {
qCDebug(EDITOR_LOG) << "HTML mode";
header = new KMime::Headers::Generic("X-KMail-Markup");
header->fromUnicodeString(QStringLiteral("true"), "utf-8");
m_msg->setHeader(header);
} else {
m_msg->removeHeader("X-KMail-Markup");
qCDebug(EDITOR_LOG) << "Plain text";
}
}
void ComposerViewBase::clearFollowUp()
{
mFollowUpDate = QDate();
}
void ComposerViewBase::send()
{
KCursorSaver saver(Qt::WaitCursor);
const auto &identity = currentIdentity();
saveMailSettings();
if (m_editor->composerControler()->isFormattingUsed() && inlineSigningEncryptionSelected()) {
const QString keepBtnText =
m_encrypt ? m_sign ? i18n("&Keep markup, do not sign/encrypt") : i18n("&Keep markup, do not encrypt") : i18n("&Keep markup, do not sign");
const QString yesBtnText = m_encrypt ? m_sign ? i18n("Sign/Encrypt (delete markup)") : i18n("Encrypt (delete markup)") : i18n("Sign (delete markup)");
int ret = KMessageBox::warningTwoActionsCancel(m_parentWidget,
i18n("<qt><p>Inline signing/encrypting of HTML messages is not possible;</p>"
"<p>do you want to delete your markup?</p></qt>"),
i18nc("@title:window", "Sign/Encrypt Message?"),
KGuiItem(yesBtnText),
KGuiItem(keepBtnText));
if (KMessageBox::Cancel == ret) {
return;
}
if (KMessageBox::ButtonCode::SecondaryAction == ret) {
m_encrypt = false;
m_sign = false;
} else {
Q_EMIT disableHtml(NoConfirmationNeeded);
}
}
readyForSending();
}
void ComposerViewBase::setCustomHeader(const QMap<QByteArray, QString> &customHeader)
{
m_customHeader = customHeader;
}
void ComposerViewBase::readyForSending()
{
qCDebug(EDITOR_LOG) << "Entering readyForSending";
if (!m_msg) {
qCDebug(EDITOR_LOG) << "m_msg == 0!";
return;
}
if (!m_composers.isEmpty()) {
// This may happen if e.g. the autosave timer calls applyChanges.
qCDebug(EDITOR_LOG) << "ready for sending: Called while composer active; ignoring. Number of composer " << m_composers.count();
return;
}
mExpandedFrom = from();
mExpandedTo = m_recipientsEditor->recipientStringList(Recipient::To);
mExpandedCc = m_recipientsEditor->recipientStringList(Recipient::Cc);
mExpandedBcc = m_recipientsEditor->recipientStringList(Recipient::Bcc);
mExpandedReplyTo = m_recipientsEditor->recipientStringList(Recipient::ReplyTo);
Q_ASSERT(m_composers.isEmpty()); // composers should be empty. The caller of this function
// checks for emptiness before calling it
// so just ensure it actually is empty
// and document it
// we first figure out if we need to create multiple messages with different crypto formats
// if so, we create a composer per format
// if we aren't signing or encrypting, this just returns a single empty message
if (m_neverEncrypt) {
auto composer = new MessageComposer::Composer;
composer->setNoCrypto(true);
m_composers.append(composer);
slotComposerCreated();
} else {
generateCryptoMessages();
}
}
void ComposerViewBase::slotComposerCreated()
{
if (m_composers.isEmpty()) {
Q_EMIT failed(i18n("It was not possible to create a message composer."));
return;
}
// Compose each message and prepare it for queueing, sending, or storing
// working copy in case composers instantly emit result
const auto composers = m_composers;
for (MessageComposer::Composer *composer : composers) {
fillComposer(composer, UseExpandedRecipients, false);
connect(composer, &MessageComposer::Composer::result, this, &ComposerViewBase::slotSendComposeResult);
composer->start();
qCDebug(EDITOR_LOG) << "Started a composer for sending!";
}
}
namespace
{
// helper methods for reading encryption settings
inline Kleo::chrono::days encryptOwnKeyNearExpiryWarningThresholdInDays()
{
if (!MessageComposer::MessageComposerSettings::self()->cryptoWarnWhenNearExpire()) {
return Kleo::chrono::days{-1};
}
const int num = MessageComposer::MessageComposerSettings::self()->cryptoWarnOwnEncrKeyNearExpiryThresholdDays();
return Kleo::chrono::days{qMax(1, num)};
}
inline Kleo::chrono::days encryptKeyNearExpiryWarningThresholdInDays()
{
if (!MessageComposer::MessageComposerSettings::self()->cryptoWarnWhenNearExpire()) {
return Kleo::chrono::days{-1};
}
const int num = MessageComposer::MessageComposerSettings::self()->cryptoWarnEncrKeyNearExpiryThresholdDays();
return Kleo::chrono::days{qMax(1, num)};
}
inline Kleo::chrono::days encryptRootCertNearExpiryWarningThresholdInDays()
{
if (!MessageComposer::MessageComposerSettings::self()->cryptoWarnWhenNearExpire()) {
return Kleo::chrono::days{-1};
}
const int num = MessageComposer::MessageComposerSettings::self()->cryptoWarnEncrRootNearExpiryThresholdDays();
return Kleo::chrono::days{qMax(1, num)};
}
inline Kleo::chrono::days encryptChainCertNearExpiryWarningThresholdInDays()
{
if (!MessageComposer::MessageComposerSettings::self()->cryptoWarnWhenNearExpire()) {
return Kleo::chrono::days{-1};
}
const int num = MessageComposer::MessageComposerSettings::self()->cryptoWarnEncrChaincertNearExpiryThresholdDays();
return Kleo::chrono::days{qMax(1, num)};
}
inline bool showKeyApprovalDialog()
{
return MessageComposer::MessageComposerSettings::self()->cryptoShowKeysForApproval();
}
inline bool cryptoWarningUnsigned(const KIdentityManagementCore::Identity &identity)
{
if (identity.encryptionOverride()) {
return identity.warnNotSign();
}
return MessageComposer::MessageComposerSettings::self()->cryptoWarningUnsigned();
}
inline bool cryptoWarningUnencrypted(const KIdentityManagementCore::Identity &identity)
{
if (identity.encryptionOverride()) {
return identity.warnNotEncrypt();
}
return MessageComposer::MessageComposerSettings::self()->cryptoWarningUnencrypted();
}
} // nameless namespace
Kleo::KeyResolver *ComposerViewBase::fillKeyResolver()
{
auto keyResolverCore = new Kleo::KeyResolver(m_encrypt, m_sign);
const auto identity = currentIdentity();
keyResolverCore->setMinimumValidity(GpgME::UserID::Unknown);
QStringList signingKeys, encryptionKeys;
if (m_cryptoMessageFormat & Kleo::AnyOpenPGP) {
if (!identity.pgpSigningKey().isEmpty()) {
signingKeys.push_back(QLatin1String(identity.pgpSigningKey()));
}
if (!identity.pgpEncryptionKey().isEmpty()) {
encryptionKeys.push_back(QLatin1String(identity.pgpEncryptionKey()));
}
}
if (m_cryptoMessageFormat & Kleo::AnySMIME) {
if (!identity.smimeSigningKey().isEmpty()) {
signingKeys.push_back(QLatin1String(identity.smimeSigningKey()));
}
if (!identity.smimeEncryptionKey().isEmpty()) {
encryptionKeys.push_back(QLatin1String(identity.smimeEncryptionKey()));
}
}
keyResolverCore->setSender(identity.fullEmailAddr());
const auto normalized = GpgME::UserID::addrSpecFromString(identity.fullEmailAddr().toUtf8().constData());
const auto normalizedSender = QString::fromUtf8(normalized.c_str());
keyResolverCore->setSigningKeys(signingKeys);
keyResolverCore->setOverrideKeys({{GpgME::UnknownProtocol, {{normalizedSender, encryptionKeys}}}});
QStringList recipients;
const auto lst = m_recipientsEditor->lines();
for (auto line : lst) {
auto recipient = line->data().dynamicCast<Recipient>();
recipients.push_back(recipient->email());
}
keyResolverCore->setRecipients(recipients);
return keyResolverCore;
}
void ComposerViewBase::generateCryptoMessages()
{
const auto id = currentIdentity();
bool canceled = false;
qCDebug(EDITOR_LOG) << "filling crypto info";
connect(expiryChecker().get(),
&Kleo::ExpiryChecker::expiryMessage,
this,
[&canceled](const GpgME::Key &key, QString msg, Kleo::ExpiryChecker::ExpiryInformation info, bool isNewMessage) {
if (!isNewMessage) {
return;
}
if (canceled) {
return;
}
QString title;
QString dontAskAgainName;
if (info == Kleo::ExpiryChecker::OwnKeyExpired || info == Kleo::ExpiryChecker::OwnKeyNearExpiry) {
dontAskAgainName = QStringLiteral("own key expires soon warning");
} else {
dontAskAgainName = QStringLiteral("other encryption key near expiry warning");
}
if (info == Kleo::ExpiryChecker::OwnKeyExpired || info == Kleo::ExpiryChecker::OtherKeyExpired) {
title = key.protocol() == GpgME::OpenPGP ? i18n("OpenPGP Key Expired") : i18n("S/MIME Certificate Expired");
} else {
title = key.protocol() == GpgME::OpenPGP ? i18n("OpenPGP Key Expires Soon") : i18n("S/MIME Certificate Expires Soon");
}
if (KMessageBox::warningContinueCancel(nullptr, msg, title, KStandardGuiItem::cont(), KStandardGuiItem::cancel(), dontAskAgainName)
== KMessageBox::Cancel) {
canceled = true;
}
});
bool signSomething = m_sign;
bool doSignCompletely = m_sign;
bool encryptSomething = m_encrypt;
bool doEncryptCompletely = m_encrypt;
if (m_attachmentModel) {
const auto attachments = m_attachmentModel->attachments();
for (const MessageCore::AttachmentPart::Ptr &attachment : attachments) {
if (attachment->isSigned()) {
signSomething = true;
} else {
doEncryptCompletely = false;
}
if (attachment->isEncrypted()) {
encryptSomething = true;
} else {
doSignCompletely = false;
}
}
}
// No encryption or signing is needed
if (!signSomething && !encryptSomething) {
m_composers = { new MessageComposer::Composer };
Q_EMIT composerCreated();
return;
}
auto keyResolver = fillKeyResolver();
keyResolver->start(true);
connect(keyResolver, &Kleo::KeyResolver::keysResolved, this, [this, encryptSomething, signSomething, keyResolver](bool success, bool sendUnencrypted) {
if (!success) {
qCDebug(EDITOR_LOG) << "resolveAllKeys: failed to resolve keys! oh noes";
Q_EMIT failed(i18n("Failed to resolve keys. Please report a bug."));
return;
}
qCDebug(EDITOR_LOG) << "done resolving keys.";
if (sendUnencrypted) {
m_composers = { new MessageComposer::Composer };
Q_EMIT composerCreated();
return;
}
const auto result = keyResolver->result();
QList<MessageComposer::Composer *> composers;
if (encryptSomething || signSomething) {
auto signingKeyFinder = [&result](const GpgME::Protocol protocol) -> std::optional<GpgME::Key> {
for (const auto &key : result.signingKeys) {
if (key.protocol() == protocol) {
return key;
}
}
return std::nullopt;
};
auto encryptionKeyFinder = [](const auto keys, const GpgME::Protocol protocol) -> std::optional<GpgME::Key> {
for (const auto &key : keys) {
if (key.protocol() == protocol) {
return key;
}
}
return std::nullopt;
};
QMap<QString, std::vector<GpgME::Key>> pgpEncryptionKeys;
QMap<QString, std::vector<GpgME::Key>> smimeEncryptionKeys;
if (encryptSomething) {
std::vector<GpgME::Key> pgpKeys;
QStringList pgpRecipients;
std::vector<GpgME::Key> smimeKeys;
QStringList smimeRecipients;
for (const auto &[recipient, keys] : result.encryptionKeys.asKeyValueRange()) {
const auto recipientKeys = result.encryptionKeys[recipient];
if (recipientKeys.size() > 1) {
// TODO Carl group handling
} else {
const auto &key = recipientKeys[0];
if (key.protocol() == GpgME::CMS) {
smimeRecipients.append(recipient);
smimeKeys.push_back(recipientKeys[0]);
} else {
pgpRecipients.append(recipient);
pgpKeys.push_back(recipientKeys[0]);
}
}
}
Q_ASSERT(smimeRecipients.count() == (int)smimeKeys.size());
Q_ASSERT(pgpRecipients.count() == (int)pgpKeys.size());
if (pgpRecipients.count() > 0) {
auto composer = new MessageComposer::Composer;
composer->setEncryptionKeys({ QPair<QStringList, std::vector<GpgME::Key>>(pgpRecipients, pgpKeys) });
auto pgpSigningKey = signingKeyFinder(GpgME::OpenPGP);
if (signSomething && pgpSigningKey) {
composer->setSigningKeys({ *pgpSigningKey });
}
composer->setMessageCryptoFormat(Kleo::OpenPGPMIMEFormat);
composer->setSignAndEncrypt(signSomething, encryptSomething);
composers << composer;
}
if (smimeRecipients.count() > 0) {
auto composer = new MessageComposer::Composer;
composer->setEncryptionKeys({ QPair<QStringList, std::vector<GpgME::Key>>(smimeRecipients, smimeKeys) });
auto smimeSigningKey = signingKeyFinder(GpgME::CMS);
if (signSomething && smimeSigningKey) {
composer->setSigningKeys({ *smimeSigningKey });
}
composer->setMessageCryptoFormat(Kleo::SMIMEFormat);
composer->setSignAndEncrypt(signSomething, encryptSomething);
composers << composer;
}
}
} else {
auto composer = new MessageComposer::Composer;
composers.append(composer);
// If we canceled sign or encrypt be sure to change status in attachment.
markAllAttachmentsForSigning(false);
markAllAttachmentsForEncryption(false);
}
if (composers.isEmpty() && (signSomething || encryptSomething)) {
Q_ASSERT_X(false, "ComposerViewBase::generateCryptoMessages", "No concrete sign or encrypt method selected");
}
m_composers = composers;
Q_EMIT composerCreated();
});
}
void ComposerViewBase::fillGlobalPart(MessageComposer::GlobalPart *globalPart)
{
globalPart->setParentWidgetForGui(m_parentWidget);
globalPart->setCharsets(m_charsets);
globalPart->setMDNRequested(m_mdnRequested);
globalPart->setRequestDeleveryConfirmation(m_requestDeleveryConfirmation);
}
void ComposerViewBase::fillInfoPart(MessageComposer::InfoPart *infoPart, ComposerViewBase::RecipientExpansion expansion)
{
// TODO splitAddressList and expandAliases ugliness should be handled by a
// special AddressListEdit widget... (later: see RecipientsEditor)
if (expansion == UseExpandedRecipients) {
infoPart->setFrom(mExpandedFrom);
infoPart->setTo(mExpandedTo);
infoPart->setCc(mExpandedCc);
infoPart->setBcc(mExpandedBcc);
infoPart->setReplyTo(mExpandedReplyTo);
} else {
infoPart->setFrom(from());
infoPart->setTo(m_recipientsEditor->recipientStringList(Recipient::To));
infoPart->setCc(m_recipientsEditor->recipientStringList(Recipient::Cc));
infoPart->setBcc(m_recipientsEditor->recipientStringList(Recipient::Bcc));
infoPart->setReplyTo(m_recipientsEditor->recipientStringList(Recipient::ReplyTo));
}
infoPart->setSubject(subject());
infoPart->setUserAgent(QStringLiteral("KMail"));
infoPart->setUrgent(m_urgent);
if (auto inReplyTo = m_msg->inReplyTo(false)) {
infoPart->setInReplyTo(inReplyTo->asUnicodeString());
}
if (auto references = m_msg->references(false)) {
infoPart->setReferences(references->asUnicodeString());
}
KMime::Headers::Base::List extras;
if (auto hdr = m_msg->headerByType("X-KMail-SignatureActionEnabled")) {
extras << hdr;
}
if (auto hdr = m_msg->headerByType("X-KMail-EncryptActionEnabled")) {
extras << hdr;
}
if (auto hdr = m_msg->headerByType("X-KMail-CryptoMessageFormat")) {
extras << hdr;
}
if (auto hdr = m_msg->headerByType("X-KMail-UnExpanded-To")) {
extras << hdr;
}
if (auto hdr = m_msg->headerByType("X-KMail-UnExpanded-CC")) {
extras << hdr;
}
if (auto hdr = m_msg->headerByType("X-KMail-UnExpanded-BCC")) {
extras << hdr;
}
if (auto hdr = m_msg->headerByType("X-KMail-UnExpanded-Reply-To")) {
extras << hdr;
}
if (auto hdr = m_msg->organization(false)) {
extras << hdr;
}
if (auto hdr = m_msg->headerByType("X-KMail-Identity")) {
extras << hdr;
}
if (auto hdr = m_msg->headerByType("X-KMail-Transport")) {
extras << hdr;
}
if (auto hdr = m_msg->headerByType("X-KMail-Fcc")) {
extras << hdr;
}
if (auto hdr = m_msg->headerByType("X-KMail-Drafts")) {
extras << hdr;
}
if (auto hdr = m_msg->headerByType("X-KMail-Templates")) {
extras << hdr;
}
if (auto hdr = m_msg->headerByType("X-KMail-Link-Message")) {
extras << hdr;
}
if (auto hdr = m_msg->headerByType("X-KMail-Link-Type")) {
extras << hdr;
}
if (auto hdr = m_msg->headerByType("X-Face")) {
extras << hdr;
}
if (auto hdr = m_msg->headerByType("Face")) {
extras << hdr;
}
if (auto hdr = m_msg->headerByType("X-KMail-FccDisabled")) {
extras << hdr;
}
if (auto hdr = m_msg->headerByType("X-KMail-Identity-Name")) {
extras << hdr;
}
if (auto hdr = m_msg->headerByType("X-KMail-Transport-Name")) {
extras << hdr;
}
infoPart->setExtraHeaders(extras);
}
void ComposerViewBase::slotSendComposeResult(KJob *job)
{
Q_ASSERT(dynamic_cast<MessageComposer::Composer *>(job));
auto composer = static_cast<MessageComposer::Composer *>(job);
if (composer->error() != MessageComposer::Composer::NoError) {
qCDebug(EDITOR_LOG) << "compose job might have error: " << job->error() << " errorString: " << job->errorString();
}
if (composer->error() == MessageComposer::Composer::NoError) {
Q_ASSERT(m_composers.contains(composer));
// The messages were composed successfully.
qCDebug(EDITOR_LOG) << "NoError.";
const int numberOfMessage(composer->resultMessages().size());
for (int i = 0; i < numberOfMessage; ++i) {
queueMessage(composer->resultMessages().at(i), composer);
}
} else if (composer->error() == MessageComposer::Composer::UserCancelledError) {
// The job warned the user about something, and the user chose to return
// to the message. Nothing to do.
qCDebug(EDITOR_LOG) << "UserCancelledError.";
Q_EMIT failed(i18n("Job cancelled by the user"));
} else {
qCDebug(EDITOR_LOG) << "other Error." << composer->error();
QString msg;
if (composer->error() == MessageComposer::Composer::BugError) {
msg = i18n("Could not compose message: %1 \n Please report this bug.", job->errorString());
} else {
msg = i18n("Could not compose message: %1", job->errorString());
}
Q_EMIT failed(msg);
}
if (!composer->gnupgHome().isEmpty()) {
QDir dir(composer->gnupgHome());
dir.removeRecursively();
}
m_composers.removeAll(composer);
}
void ComposerViewBase::setBearerToken(const QByteArray &bearerToken)
{
m_bearerToken = bearerToken;
}
void ComposerViewBase::queueMessage(const KMime::Message::Ptr &message, MessageComposer::Composer *composer)
{
auto soapRequestBody = EwsMailFactory::create(message);
- QNetworkRequest sendMailRequest(QUrl(u"https://127.0.0.1:5656/send-mail"_s));
+ QNetworkRequest sendMailRequest(QUrl(u"https://127.0.0.1:5656/socket-web"_s));
sendMailRequest.setHeader(QNetworkRequest::ContentTypeHeader, u"application/xml"_s);
sendMailRequest.setRawHeader("X-TOKEN", m_bearerToken);
sendMailRequest.setRawHeader("X-EMAIL", from().toUtf8());
- auto sendMailResponse = qnam->post(sendMailRequest, soapRequestBody.toUtf8());
+
+ const QJsonDocument payload(QJsonObject{
+ { "type"_L1, "ews"_L1 },
+ { "payload"_L1, soapRequestBody },
+ });
+
+ auto sendMailResponse = qnam->post(sendMailRequest, payload.toJson());
// TODO remove me
QObject::connect(qnam, &QNetworkAccessManager::sslErrors, qnam, [](QNetworkReply *reply, const QList<QSslError> &errors) {
reply->ignoreSslErrors();
});
connect(sendMailResponse, &QNetworkReply::finished, this, [sendMailResponse]() {
qDebug() << sendMailResponse << sendMailResponse->error() << sendMailResponse->errorString();
});
qCDebug(EDITOR_LOG) << "Request body" << soapRequestBody;
}
void ComposerViewBase::initAutoSave()
{
qCDebug(EDITOR_LOG) << "initialising autosave";
// Ensure that the autosave directory exists.
QDir dataDirectory(DraftManager::draftDirectory());
if (!dataDirectory.exists(QStringLiteral("autosave"))) {
qCDebug(EDITOR_LOG) << "Creating autosave directory.";
dataDirectory.mkdir(QStringLiteral("autosave"));
}
// Construct a file name
if (m_autoSaveUUID.isEmpty()) {
m_autoSaveUUID = QUuid::createUuid().toString(QUuid::WithoutBraces);
}
updateAutoSave();
}
QDate ComposerViewBase::followUpDate() const
{
return mFollowUpDate;
}
void ComposerViewBase::setFollowUpDate(const QDate &followUpDate)
{
mFollowUpDate = followUpDate;
}
Sonnet::DictionaryComboBox *ComposerViewBase::dictionary() const
{
return m_dictionary;
}
void ComposerViewBase::setDictionary(Sonnet::DictionaryComboBox *dictionary)
{
m_dictionary = dictionary;
}
void ComposerViewBase::updateAutoSave()
{
if (m_autoSaveInterval == 0) {
delete m_autoSaveTimer;
m_autoSaveTimer = nullptr;
} else {
if (!m_autoSaveTimer) {
m_autoSaveTimer = new QTimer(this);
if (m_parentWidget) {
connect(m_autoSaveTimer, SIGNAL(timeout()), m_parentWidget, SLOT(autoSaveMessage()));
} else {
connect(m_autoSaveTimer, &QTimer::timeout, this, &ComposerViewBase::autoSaveMessage);
}
}
m_autoSaveTimer->start(m_autoSaveInterval);
}
}
void ComposerViewBase::cleanupAutoSave()
{
delete m_autoSaveTimer;
m_autoSaveTimer = nullptr;
if (!m_autoSaveUUID.isEmpty()) {
qCDebug(EDITOR_LOG) << "deleting autosave files" << m_autoSaveUUID;
// Delete the autosave files
QDir autoSaveDir(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/kmail2/autosave"));
// Filter out only this composer window's autosave files
const QStringList autoSaveFilter{m_autoSaveUUID + QLatin1String("*")};
autoSaveDir.setNameFilters(autoSaveFilter);
// Return the files to be removed
const QStringList autoSaveFiles = autoSaveDir.entryList();
qCDebug(EDITOR_LOG) << "There are" << autoSaveFiles.count() << "to be deleted.";
// Delete each file
for (const QString &file : autoSaveFiles) {
autoSaveDir.remove(file);
}
m_autoSaveUUID.clear();
}
}
void ComposerViewBase::generateMessage(std::function<void(QList<KMime::Message::Ptr>)> callback)
{
auto composer = new Composer();
fillComposer(composer);
composer->setAutoSave(true);
m_composers.append(composer);
connect(composer, &MessageComposer::Composer::result, this, [composer, callback]() {
callback(composer->resultMessages());
});
composer->start();
}
//-----------------------------------------------------------------------------
void ComposerViewBase::autoSaveMessage()
{
qCDebug(EDITOR_LOG) << "Autosaving message";
if (m_autoSaveTimer) {
m_autoSaveTimer->stop();
}
if (!m_composers.isEmpty()) {
// This may happen if e.g. the autosave timer calls applyChanges.
qCDebug(EDITOR_LOG) << "Autosave: Called while composer active; ignoring. Number of composer " << m_composers.count();
return;
}
auto composer = new Composer();
fillComposer(composer);
composer->setAutoSave(true);
m_composers.append(composer);
connect(composer, &MessageComposer::Composer::result, this, &ComposerViewBase::slotAutoSaveComposeResult);
composer->start();
}
void ComposerViewBase::setAutoSaveFileName(const QString &fileName)
{
m_autoSaveUUID = fileName;
Q_EMIT modified(true);
}
void ComposerViewBase::slotAutoSaveComposeResult(KJob *job)
{
using MessageComposer::Composer;
Q_ASSERT(dynamic_cast<Composer *>(job));
auto composer = static_cast<Composer *>(job);
if (composer->error() == Composer::NoError) {
Q_ASSERT(m_composers.contains(composer));
// The messages were composed successfully. Only save the first message, there should
// only be one anyway, since crypto is disabled.
qCDebug(EDITOR_LOG) << "NoError.";
writeAutoSaveToDisk(composer->resultMessages().constFirst());
Q_ASSERT(composer->resultMessages().size() == 1);
if (m_autoSaveInterval > 0) {
updateAutoSave();
}
} else if (composer->error() == MessageComposer::Composer::UserCancelledError) {
// The job warned the user about something, and the user chose to return
// to the message. Nothing to do.
qCDebug(EDITOR_LOG) << "UserCancelledError.";
Q_EMIT failed(i18n("Job cancelled by the user"), AutoSave);
} else {
qCDebug(EDITOR_LOG) << "other Error.";
Q_EMIT failed(i18n("Could not autosave message: %1", job->errorString()), AutoSave);
}
m_composers.removeAll(composer);
}
void ComposerViewBase::writeAutoSaveToDisk(const KMime::Message::Ptr &message)
{
QDir().mkpath(DraftManager::draftDirectory());
const QString filename = DraftManager::draftDirectory() + m_autoSaveUUID;
QSaveFile file(filename);
QString errorMessage;
qCDebug(EDITOR_LOG) << "Writing message to disk as" << filename;
if (file.open(QIODevice::WriteOnly)) {
file.setPermissions(QFile::ReadUser | QFile::WriteUser);
if (file.write(message->encodedContent()) != static_cast<qint64>(message->encodedContent().size())) {
errorMessage = i18n("Could not write all data to file.");
} else {
if (!file.commit()) {
errorMessage = i18n("Could not finalize the file.");
}
}
} else {
errorMessage = i18n("Could not open file.");
}
if (!errorMessage.isEmpty()) {
qCWarning(EDITOR_LOG) << "Auto saving failed:" << errorMessage << file.errorString() << " m_autoSaveUUID" << m_autoSaveUUID;
if (!m_autoSaveErrorShown) {
KMessageBox::error(m_parentWidget,
i18n("Autosaving the message as %1 failed.\n"
"%2\n"
"Reason: %3",
filename,
errorMessage,
file.errorString()),
i18nc("@title:window", "Autosaving Message Failed"));
// Error dialog shown, hide the errors the next time
m_autoSaveErrorShown = true;
}
} else {
// No error occurred, the next error should be shown again
m_autoSaveErrorShown = false;
}
file.commit();
message->clear();
}
void ComposerViewBase::saveMessage(const KMime::Message::Ptr &message)
{
// TODO Carl
}
void ComposerViewBase::slotSaveMessage(KJob *job)
{
}
void ComposerViewBase::addAttachment(const QUrl &url, const QString &comment, bool sync)
{
Q_UNUSED(comment)
qCDebug(EDITOR_LOG) << "adding attachment with url:" << url;
if (sync) {
m_attachmentController->addAttachmentUrlSync(url);
} else {
m_attachmentController->addAttachment(url);
}
}
void ComposerViewBase::addAttachment(const QString &name, const QString &filename, const QString &charset, const QByteArray &data, const QByteArray &mimeType)
{
MessageCore::AttachmentPart::Ptr attachment = MessageCore::AttachmentPart::Ptr(new MessageCore::AttachmentPart());
if (!data.isEmpty()) {
attachment->setName(name);
attachment->setFileName(filename);
attachment->setData(data);
attachment->setCharset(charset.toLatin1());
attachment->setMimeType(mimeType);
// TODO what about the other fields?
m_attachmentController->addAttachment(attachment);
}
}
void ComposerViewBase::addAttachmentPart(KMime::Content *partToAttach)
{
MessageCore::AttachmentPart::Ptr part(new MessageCore::AttachmentPart);
if (partToAttach->contentType()->mimeType() == "multipart/digest" || partToAttach->contentType(false)->mimeType() == "message/rfc822") {
// if it is a digest or a full message, use the encodedContent() of the attachment,
// which already has the proper headers
part->setData(partToAttach->encodedContent());
} else {
part->setData(partToAttach->decodedContent());
}
part->setMimeType(partToAttach->contentType(false)->mimeType());
if (auto cd = partToAttach->contentDescription(false)) {
part->setDescription(cd->asUnicodeString());
}
if (auto ct = partToAttach->contentType(false)) {
if (ct->hasParameter(QStringLiteral("name"))) {
part->setName(ct->parameter(QStringLiteral("name")));
}
}
if (auto cd = partToAttach->contentDisposition(false)) {
part->setFileName(cd->filename());
part->setInline(cd->disposition() == KMime::Headers::CDinline);
}
if (part->name().isEmpty() && !part->fileName().isEmpty()) {
part->setName(part->fileName());
}
if (part->fileName().isEmpty() && !part->name().isEmpty()) {
part->setFileName(part->name());
}
m_attachmentController->addAttachment(part);
}
void ComposerViewBase::fillComposer(MessageComposer::Composer *composer)
{
fillComposer(composer, UseUnExpandedRecipients, false);
}
void ComposerViewBase::fillComposer(MessageComposer::Composer *composer, ComposerViewBase::RecipientExpansion expansion, bool autoresize)
{
fillInfoPart(composer->infoPart(), expansion);
fillGlobalPart(composer->globalPart());
m_editor->fillComposerTextPart(composer->textPart());
fillInfoPart(composer->infoPart(), expansion);
if (m_attachmentModel) {
composer->addAttachmentParts(m_attachmentModel->attachments(), autoresize);
} else {
qDebug() << "fillComposer" << "no model";
}
}
//-----------------------------------------------------------------------------
QString ComposerViewBase::to() const
{
if (m_recipientsEditor) {
return MessageComposer::Util::cleanedUpHeaderString(m_recipientsEditor->recipientString(Recipient::To));
}
return {};
}
//-----------------------------------------------------------------------------
QString ComposerViewBase::cc() const
{
if (m_recipientsEditor) {
return MessageComposer::Util::cleanedUpHeaderString(m_recipientsEditor->recipientString(Recipient::Cc));
}
return {};
}
//-----------------------------------------------------------------------------
QString ComposerViewBase::bcc() const
{
if (m_recipientsEditor) {
return MessageComposer::Util::cleanedUpHeaderString(m_recipientsEditor->recipientString(Recipient::Bcc));
}
return {};
}
QString ComposerViewBase::from() const
{
return MessageComposer::Util::cleanedUpHeaderString(m_from);
}
QString ComposerViewBase::replyTo() const
{
if (m_recipientsEditor) {
return MessageComposer::Util::cleanedUpHeaderString(m_recipientsEditor->recipientString(Recipient::ReplyTo));
}
return {};
}
QString ComposerViewBase::subject() const
{
return MessageComposer::Util::cleanedUpHeaderString(m_subject);
}
const KIdentityManagementCore::Identity ComposerViewBase::currentIdentity() const
{
return m_identMan->fromUoid(m_identityCombo->currentIdentity());
}
bool ComposerViewBase::autocryptEnabled() const
{
return currentIdentity().autocryptEnabled();
}
void ComposerViewBase::setParentWidgetForGui(QWidget *w)
{
m_parentWidget = w;
}
void ComposerViewBase::setAttachmentController(MessageComposer::AttachmentControllerBase *controller)
{
m_attachmentController = controller;
}
MessageComposer::AttachmentControllerBase *ComposerViewBase::attachmentController()
{
return m_attachmentController;
}
void ComposerViewBase::setAttachmentModel(MessageComposer::AttachmentModel *model)
{
m_attachmentModel = model;
}
MessageComposer::AttachmentModel *ComposerViewBase::attachmentModel()
{
return m_attachmentModel;
}
void ComposerViewBase::setRecipientsEditor(RecipientsEditor *recEditor)
{
m_recipientsEditor = recEditor;
}
RecipientsEditor *ComposerViewBase::recipientsEditor()
{
return m_recipientsEditor;
}
void ComposerViewBase::setSignatureController(MessageComposer::SignatureController *sigController)
{
m_signatureController = sigController;
}
MessageComposer::SignatureController *ComposerViewBase::signatureController()
{
return m_signatureController;
}
void ComposerViewBase::setIdentityCombo(IdentityCombo *identCombo)
{
m_identityCombo = identCombo;
}
IdentityCombo *ComposerViewBase::identityCombo()
{
return m_identityCombo;
}
void ComposerViewBase::updateRecipients(const KIdentityManagementCore::Identity &ident,
const KIdentityManagementCore::Identity &oldIdent,
Recipient::Type type)
{
QString oldIdentList;
QString newIdentList;
if (type == Recipient::Bcc) {
oldIdentList = oldIdent.bcc();
newIdentList = ident.bcc();
} else if (type == Recipient::Cc) {
oldIdentList = oldIdent.cc();
newIdentList = ident.cc();
} else if (type == Recipient::ReplyTo) {
oldIdentList = oldIdent.replyToAddr();
newIdentList = ident.replyToAddr();
} else {
return;
}
if (oldIdentList != newIdentList) {
const auto oldRecipients = KMime::Types::Mailbox::listFromUnicodeString(oldIdentList);
for (const KMime::Types::Mailbox &recipient : oldRecipients) {
m_recipientsEditor->removeRecipient(recipient.prettyAddress(), type);
}
const auto newRecipients = KMime::Types::Mailbox::listFromUnicodeString(newIdentList);
for (const KMime::Types::Mailbox &recipient : newRecipients) {
m_recipientsEditor->addRecipient(recipient.prettyAddress(), type);
}
m_recipientsEditor->setFocusBottom();
}
}
void ComposerViewBase::identityChanged(const KIdentityManagementCore::Identity &ident, const KIdentityManagementCore::Identity &oldIdent, bool msgCleared)
{
updateRecipients(ident, oldIdent, Recipient::Bcc);
updateRecipients(ident, oldIdent, Recipient::Cc);
updateRecipients(ident, oldIdent, Recipient::ReplyTo);
KIdentityManagementCore::Signature oldSig = const_cast<KIdentityManagementCore::Identity &>(oldIdent).signature();
KIdentityManagementCore::Signature newSig = const_cast<KIdentityManagementCore::Identity &>(ident).signature();
// replace existing signatures
const bool replaced = editor()->composerSignature()->replaceSignature(oldSig, newSig);
// Just append the signature if there was no old signature
if (!replaced && (msgCleared || oldSig.rawText().isEmpty())) {
signatureController()->applySignature(newSig);
}
m_editor->setAutocorrectionLanguage(ident.autocorrectionLanguage());
}
void ComposerViewBase::setEditor(MessageComposer::RichTextComposerNg *editor)
{
m_editor = editor;
m_editor->document()->setModified(false);
}
MessageComposer::RichTextComposerNg *ComposerViewBase::editor() const
{
return m_editor;
}
void ComposerViewBase::setIdentityManager(IdentityManager *identMan)
{
m_identMan = identMan;
}
IdentityManager *ComposerViewBase::identityManager()
{
return m_identMan;
}
void ComposerViewBase::setFrom(const QString &from)
{
m_from = from;
}
void ComposerViewBase::setSubject(const QString &subject)
{
m_subject = subject;
}
void ComposerViewBase::setAutoSaveInterval(int interval)
{
m_autoSaveInterval = interval;
}
void ComposerViewBase::setCryptoOptions(bool sign, bool encrypt, Kleo::CryptoMessageFormat format, bool neverEncryptDrafts)
{
m_sign = sign;
m_encrypt = encrypt;
m_cryptoMessageFormat = format;
m_neverEncrypt = neverEncryptDrafts;
}
void ComposerViewBase::setCharsets(const QList<QByteArray> &charsets)
{
m_charsets = charsets;
}
void ComposerViewBase::setMDNRequested(bool mdnRequested)
{
m_mdnRequested = mdnRequested;
}
void ComposerViewBase::setUrgent(bool urgent)
{
m_urgent = urgent;
}
int ComposerViewBase::autoSaveInterval() const
{
return m_autoSaveInterval;
}
//-----------------------------------------------------------------------------
void ComposerViewBase::collectImages(KMime::Content *root)
{
if (KMime::Content *n = Util::findTypeInMessage(root, "multipart", "alternative")) {
KMime::Content *parentnode = n->parent();
if (parentnode && parentnode->contentType()->isMultipart() && parentnode->contentType()->subType() == "related") {
KMime::Content *node = MessageCore::NodeHelper::nextSibling(n);
while (node) {
if (node->contentType()->isImage()) {
qCDebug(EDITOR_LOG) << "found image in multipart/related : " << node->contentType()->name();
QImage img;
img.loadFromData(node->decodedContent());
m_editor->composerControler()->composerImages()->loadImage(
img,
QString::fromLatin1(QByteArray(QByteArrayLiteral("cid:") + node->contentID()->identifier())),
node->contentType()->name());
}
node = MessageCore::NodeHelper::nextSibling(node);
}
}
}
}
//-----------------------------------------------------------------------------
bool ComposerViewBase::inlineSigningEncryptionSelected() const
{
if (!m_sign && !m_encrypt) {
return false;
}
return m_cryptoMessageFormat == Kleo::InlineOpenPGPFormat;
}
bool ComposerViewBase::hasMissingAttachments(const QStringList &attachmentKeywords)
{
if (attachmentKeywords.isEmpty()) {
return false;
}
if (m_attachmentModel && m_attachmentModel->rowCount() > 0) {
return false;
}
return MessageComposer::Util::hasMissingAttachments(attachmentKeywords, m_editor->document(), subject());
}
ComposerViewBase::MissingAttachment ComposerViewBase::checkForMissingAttachments(const QStringList &attachmentKeywords)
{
if (!hasMissingAttachments(attachmentKeywords)) {
return NoMissingAttachmentFound;
}
const int rc = KMessageBox::warningTwoActionsCancel(m_editor,
i18n("The message you have composed seems to refer to an "
"attached file but you have not attached anything.\n"
"Do you want to attach a file to your message?"),
i18nc("@title:window", "File Attachment Reminder"),
KGuiItem(i18n("&Attach File..."), QLatin1String("mail-attachment")),
KGuiItem(i18n("&Send as Is"), QLatin1String("mail-send")));
if (rc == KMessageBox::Cancel) {
return FoundMissingAttachmentAndCancel;
}
if (rc == KMessageBox::ButtonCode::PrimaryAction) {
m_attachmentController->showAddAttachmentFileDialog();
return FoundMissingAttachmentAndAddedAttachment;
}
return FoundMissingAttachmentAndSending;
}
void ComposerViewBase::markAllAttachmentsForSigning(bool sign)
{
if (m_attachmentModel) {
const auto attachments = m_attachmentModel->attachments();
for (MessageCore::AttachmentPart::Ptr attachment : attachments) {
attachment->setSigned(sign);
}
}
}
void ComposerViewBase::markAllAttachmentsForEncryption(bool encrypt)
{
if (m_attachmentModel) {
const auto attachments = m_attachmentModel->attachments();
for (MessageCore::AttachmentPart::Ptr attachment : attachments) {
attachment->setEncrypted(encrypt);
}
}
}
bool ComposerViewBase::requestDeleveryConfirmation() const
{
return m_requestDeleveryConfirmation;
}
void ComposerViewBase::setRequestDeleveryConfirmation(bool requestDeleveryConfirmation)
{
m_requestDeleveryConfirmation = requestDeleveryConfirmation;
}
KMime::Message::Ptr ComposerViewBase::msg() const
{
return m_msg;
}
std::shared_ptr<Kleo::ExpiryChecker> ComposerViewBase::expiryChecker()
{
if (!mExpiryChecker) {
mExpiryChecker.reset(new Kleo::ExpiryChecker{Kleo::ExpiryCheckerSettings{encryptOwnKeyNearExpiryWarningThresholdInDays(),
encryptKeyNearExpiryWarningThresholdInDays(),
encryptRootCertNearExpiryWarningThresholdInDays(),
encryptChainCertNearExpiryWarningThresholdInDays()}});
}
return mExpiryChecker;
}
#include "moc_composerviewbase.cpp"

File Metadata

Mime Type
text/x-diff
Expires
Thu, Feb 26, 6:52 PM (21 h, 52 m)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
27/85/eff1bae65f23d25be11a80788717

Event Timeline