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