Page MenuHome GnuPG

No OneTemporary

diff --git a/src/core/partmodel.cpp b/src/core/partmodel.cpp
index 7b9dad8..8261e0a 100644
--- a/src/core/partmodel.cpp
+++ b/src/core/partmodel.cpp
@@ -1,596 +1,596 @@
// SPDX-FileCopyrightText: 2016 Sandro Knauß <knauss@kolabsys.com>
// SPDX-License-Identifier: LGPL-2.0-or-later
#include "partmodel.h"
#include "htmlutils.h"
#include "objecttreeparser.h"
#include "utils.h"
#include <KLocalizedString>
#include <QDebug>
#include <QGpgME/Protocol>
#include <QRegularExpression>
#include <QStringLiteral>
#include <QTextDocument>
// We return a pair containing the trimmed string, as well as a boolean indicating whether the string was trimmed or not
std::pair<QString, bool> PartModel::trim(const QString &text)
{
// The delimiters have <p>.? prefixed including the .? because sometimes we get a byte order mark <feff> (seen with user-agent:
// Microsoft-MacOutlook/10.1d.0.190908) We match both regulard withspace with \s and non-breaking spaces with \u00A0
const QList<QRegularExpression> delimiters{
// English
QRegularExpression{QStringLiteral("<p>.?-+Original(\\s|\u00A0)Message-+"), QRegularExpression::CaseInsensitiveOption},
// The remainder is not quoted
QRegularExpression{QStringLiteral("<p>.?On.*wrote:"), QRegularExpression::CaseInsensitiveOption},
// The remainder is quoted
QRegularExpression{QStringLiteral("&gt; On.*wrote:"), QRegularExpression::CaseInsensitiveOption},
// German
// Forwarded
QRegularExpression{QStringLiteral("<p>.?Von:.*</p>"), QRegularExpression::CaseInsensitiveOption},
// Reply
QRegularExpression{QStringLiteral("<p>.?Am.*schrieb.*:</p>"), QRegularExpression::CaseInsensitiveOption},
// Signature
QRegularExpression{QStringLiteral("<p>.?--(\\s|\u00A0)<br>"), QRegularExpression::CaseInsensitiveOption},
};
for (const auto &expression : delimiters) {
auto i = expression.globalMatchView(text);
while (i.hasNext()) {
const auto match = i.next();
const int startOffset = match.capturedStart(0);
// This is a very simplistic detection for an inline reply where we would have the patterns before the actual message content.
// We simply ignore anything we find within the first few lines.
if (startOffset >= 5) {
return {text.mid(0, startOffset), true};
} else {
// Search for the next delimiter
continue;
}
}
}
return {text, false};
}
static QString addCss(const QString &s)
{
// Get the default font from QApplication
static const QString fontFamily = QFont{}.family();
// overflow:hidden ensures no scrollbars are ever shown.
static const QString css = QStringLiteral("<style>\n")
+ QStringLiteral(
"body {\n"
" overflow:hidden;\n"
" font-family: \"%1\" ! important;\n"
" color: #31363b ! important;\n"
" background-color: #fcfcfc ! important\n"
"}\n")
.arg(fontFamily)
+ QStringLiteral("blockquote { \n"
" border-left: 2px solid #bdc3c7 ! important;\n"
"}\n")
+ QStringLiteral("</style>");
const QString header = QLatin1StringView(
"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n"
"<html><head><title></title>")
+ css + QLatin1StringView("</head>\n<body>\n");
return header + s + QStringLiteral("</body></html>");
}
class PartModelPrivate
{
public:
PartModelPrivate(PartModel *q_ptr, const std::shared_ptr<MimeTreeParser::ObjectTreeParser> &parser)
: q(q_ptr)
, mParser(parser)
{
collectContents();
}
~PartModelPrivate() = default;
void checkPart(const MimeTreeParser::MessagePart::Ptr part)
{
mMimeTypeCache[part.data()] = part->mimeType();
// Extract the content of the part and
mContents.insert(part.data(), extractContent(part.data()));
}
// Recursively find encapsulated messages
void findEncapsulated(const MimeTreeParser::EncapsulatedRfc822MessagePart::Ptr &e)
{
mEncapsulatedParts[e.data()] = mParser->collectContentParts(e);
for (const auto &subPart : std::as_const(mEncapsulatedParts[e.data()])) {
checkPart(subPart);
mParents[subPart.data()] = e.data();
if (auto encapsulatedSub = subPart.dynamicCast<MimeTreeParser::EncapsulatedRfc822MessagePart>()) {
findEncapsulated(encapsulatedSub);
}
}
}
QVariant extractContent(MimeTreeParser::MessagePart *messagePart)
{
if (auto alternativePart = dynamic_cast<MimeTreeParser::AlternativeMessagePart *>(messagePart)) {
if (alternativePart->availableModes().contains(MimeTreeParser::AlternativeMessagePart::MultipartIcal)) {
return alternativePart->icalContent();
}
}
auto preprocessPlaintext = [&](const QString &text) {
// Reduce consecutive new lines to never exceed 2
auto cleaned = text;
cleaned.replace(QRegularExpression(QStringLiteral("[\n\r]{2,}")), QStringLiteral("\n\n"));
// We always do rich text (so we get highlighted links and stuff).
const auto html = Qt::convertFromPlainText(cleaned, Qt::WhiteSpaceNormal);
if (trimMail) {
const auto result = PartModel::trim(html);
isTrimmed = result.second;
Q_EMIT q->trimMailChanged();
return MimeTreeParser::linkify(result.first);
}
return MimeTreeParser::linkify(html);
};
if (messagePart->isHtml()) {
if (dynamic_cast<MimeTreeParser::AlternativeMessagePart *>(messagePart)) {
containsHtmlAndPlain = true;
Q_EMIT q->containsHtmlChanged();
if (!showHtml) {
return preprocessPlaintext(messagePart->plaintextContent());
}
}
return addCss(mParser->resolveCidLinks(messagePart->htmlContent()));
}
if (auto attachmentPart = dynamic_cast<MimeTreeParser::AttachmentMessagePart *>(messagePart)) {
auto node = attachmentPart->node();
if (node && mMimeTypeCache[attachmentPart] == QByteArrayLiteral("text/calendar")) {
return messagePart->text();
}
}
return preprocessPlaintext(messagePart->text());
}
QVariant contentForPart(MimeTreeParser::MessagePart *messagePart) const
{
return mContents.value(messagePart);
}
void collectContents()
{
mEncapsulatedParts.clear();
mParents.clear();
mContents.clear();
containsHtmlAndPlain = false;
isTrimmed = false;
const auto parts = mParser->collectContentParts();
MimeTreeParser::MessagePart::List filteredParts;
for (const auto &part : parts) {
if (part->node()) {
const auto contentType = part->node()->contentType();
if (contentType && contentType->hasParameter(QStringLiteral("protected-headers"))) {
const auto contentDisposition = part->node()->contentDisposition();
if (contentDisposition && contentDisposition->disposition() == KMime::Headers::CDinline) {
continue;
}
}
}
filteredParts << part;
}
for (const auto &part : std::as_const(filteredParts)) {
checkPart(part);
if (auto encapsulatedPart = part.dynamicCast<MimeTreeParser::EncapsulatedRfc822MessagePart>()) {
findEncapsulated(encapsulatedPart);
}
}
for (const auto &part : std::as_const(filteredParts)) {
if (mMimeTypeCache[part.data()] == QByteArrayLiteral("text/calendar")) {
mParts.prepend(part);
} else {
mParts.append(part);
}
}
}
PartModel *q;
MimeTreeParser::MessagePart::List mParts;
QHash<MimeTreeParser::MessagePart *, QByteArray> mMimeTypeCache;
QHash<MimeTreeParser::MessagePart *, MimeTreeParser::MessagePart::List> mEncapsulatedParts;
QHash<MimeTreeParser::MessagePart *, MimeTreeParser::MessagePart *> mParents;
QMap<MimeTreeParser::MessagePart *, QVariant> mContents;
std::shared_ptr<MimeTreeParser::ObjectTreeParser> mParser;
bool showHtml{false};
bool containsHtmlAndPlain{false};
bool trimMail{false};
bool isTrimmed{false};
};
PartModel::PartModel(std::shared_ptr<MimeTreeParser::ObjectTreeParser> parser)
: d(std::unique_ptr<PartModelPrivate>(new PartModelPrivate(this, parser)))
{
}
PartModel::~PartModel()
{
}
void PartModel::setShowHtml(bool html)
{
if (d->showHtml != html) {
beginResetModel();
d->showHtml = html;
d->collectContents();
endResetModel();
Q_EMIT showHtmlChanged();
}
}
bool PartModel::showHtml() const
{
return d->showHtml;
}
void PartModel::setTrimMail(bool trim)
{
if (d->trimMail != trim) {
beginResetModel();
d->trimMail = trim;
d->collectContents();
endResetModel();
Q_EMIT trimMailChanged();
}
}
bool PartModel::trimMail() const
{
return d->trimMail;
}
bool PartModel::isTrimmed() const
{
return d->isTrimmed;
}
bool PartModel::containsHtml() const
{
return d->containsHtmlAndPlain;
}
QHash<int, QByteArray> PartModel::roleNames() const
{
QHash<int, QByteArray> roles;
roles[TypeRole] = QByteArrayLiteral("type");
roles[ContentRole] = QByteArrayLiteral("content");
roles[IsEmbeddedRole] = QByteArrayLiteral("embedded");
roles[IsEncryptedRole] = QByteArrayLiteral("encrypted");
roles[IsSignedRole] = QByteArrayLiteral("signed");
roles[SecurityLevelRole] = QByteArrayLiteral("securityLevel");
roles[EncryptionSecurityLevelRole] = QByteArrayLiteral("encryptionSecurityLevel");
roles[SignatureSecurityLevelRole] = QByteArrayLiteral("signatureSecurityLevel");
roles[ErrorType] = QByteArrayLiteral("errorType");
roles[ErrorString] = QByteArrayLiteral("errorString");
roles[IsErrorRole] = QByteArrayLiteral("error");
roles[SenderRole] = QByteArrayLiteral("sender");
roles[SignatureDetails] = QByteArrayLiteral("signatureDetails");
roles[EncryptionDetails] = QByteArrayLiteral("encryptionDetails");
roles[DateRole] = QByteArrayLiteral("date");
return roles;
}
QModelIndex PartModel::index(int row, int column, const QModelIndex &parent) const
{
if (row < 0 || column != 0) {
return QModelIndex();
}
if (parent.isValid()) {
const auto part = static_cast<MimeTreeParser::MessagePart *>(parent.internalPointer());
auto encapsulatedPart = dynamic_cast<MimeTreeParser::EncapsulatedRfc822MessagePart *>(part);
if (encapsulatedPart) {
const auto parts = d->mEncapsulatedParts[encapsulatedPart];
if (row < parts.size()) {
return createIndex(row, column, parts.at(row).data());
}
}
return QModelIndex();
}
if (row < d->mParts.size()) {
return createIndex(row, column, d->mParts.at(row).data());
}
return QModelIndex();
}
SignatureInfo encryptionInfo(MimeTreeParser::MessagePart *messagePart)
{
SignatureInfo signatureInfo;
const auto encryptions = messagePart->encryptions();
if (encryptions.size() > 1) {
qWarning() << "Can't deal with more than one encryption";
}
for (const auto &encryptionPart : encryptions) {
signatureInfo.keyId = encryptionPart->partMetaData()->keyId;
signatureInfo.cryptoProto = encryptionPart->cryptoProto();
signatureInfo.decryptRecipients = encryptionPart->decryptRecipients();
}
return signatureInfo;
};
SignatureInfo signatureInfo(MimeTreeParser::MessagePart *messagePart)
{
SignatureInfo signatureInfo;
const auto signatures = messagePart->signatures();
if (signatures.size() > 1) {
qWarning() << "Can't deal with more than one signature";
}
for (const auto &signaturePart : signatures) {
signatureInfo.keyId = signaturePart->partMetaData()->keyId;
signatureInfo.cryptoProto = signaturePart->cryptoProto();
signatureInfo.keyMissing = signaturePart->partMetaData()->sigSummary & GpgME::Signature::KeyMissing;
signatureInfo.keyExpired = signaturePart->partMetaData()->sigSummary & GpgME::Signature::KeyExpired;
signatureInfo.keyRevoked = signaturePart->partMetaData()->sigSummary & GpgME::Signature::KeyRevoked;
signatureInfo.sigExpired = signaturePart->partMetaData()->sigSummary & GpgME::Signature::SigExpired;
signatureInfo.crlMissing = signaturePart->partMetaData()->sigSummary & GpgME::Signature::CrlMissing;
signatureInfo.crlTooOld = signaturePart->partMetaData()->sigSummary & GpgME::Signature::CrlTooOld;
signatureInfo.signer = signaturePart->partMetaData()->signer;
signatureInfo.isCompliant = signaturePart->partMetaData()->isCompliant;
signatureInfo.signerMailAddresses = signaturePart->partMetaData()->signerMailAddresses;
signatureInfo.signatureIsGood = signaturePart->partMetaData()->isGoodSignature;
signatureInfo.keyTrust = signaturePart->partMetaData()->keyTrust;
}
return signatureInfo;
}
QVariant PartModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid()) {
return QVariant();
}
if (index.internalPointer()) {
const auto messagePart = static_cast<MimeTreeParser::MessagePart *>(index.internalPointer());
// qWarning() << "Found message part " << messagePart->metaObject()->className() << messagePart->partMetaData()->status << messagePart->error();
Q_ASSERT(messagePart);
switch (role) {
case Qt::DisplayRole:
return QStringLiteral("Content%1");
case SenderRole: {
if (auto e = dynamic_cast<MimeTreeParser::EncapsulatedRfc822MessagePart *>(messagePart)) {
return e->from();
}
return {};
}
case DateRole: {
if (auto e = dynamic_cast<MimeTreeParser::EncapsulatedRfc822MessagePart *>(messagePart)) {
return e->date();
}
return {};
}
case TypeRole: {
if (messagePart->error()) {
return QVariant::fromValue(Types::Error);
}
if (dynamic_cast<MimeTreeParser::EncapsulatedRfc822MessagePart *>(messagePart)) {
return QVariant::fromValue(Types::Encapsulated);
}
if (auto alternativePart = dynamic_cast<MimeTreeParser::AlternativeMessagePart *>(messagePart)) {
if (alternativePart->availableModes().contains(MimeTreeParser::AlternativeMessagePart::MultipartIcal)) {
return QVariant::fromValue(Types::Ical);
}
}
if (auto attachmentPart = dynamic_cast<MimeTreeParser::AttachmentMessagePart *>(messagePart)) {
auto node = attachmentPart->node();
if (!node) {
qWarning() << "no content for attachment";
return {};
}
if (d->mMimeTypeCache[attachmentPart] == QByteArrayLiteral("text/calendar")) {
return QVariant::fromValue(Types::Ical);
}
}
if (!d->showHtml && d->containsHtmlAndPlain) {
return QVariant::fromValue(Types::Plain);
}
// For simple html we don't need a browser
auto complexHtml = [&] {
if (messagePart->isHtml()) {
const auto text = messagePart->htmlContent();
if (text.contains(QStringLiteral("<!DOCTYPE html PUBLIC"))) {
// We can probably deal with this if it adheres to the strict dtd
//(that's what our composer produces as well)
if (!text.contains(QStringLiteral("http://www.w3.org/TR/REC-html40/strict.dtd"))) {
return true;
}
}
// Blockquotes don't support any styling which would be necessary so they become readable.
if (text.contains(QStringLiteral("blockquote"))) {
return true;
}
// Media queries are too advanced
if (text.contains(QStringLiteral("@media"))) {
return true;
}
// auto css properties are not supported e.g margin-left: auto;
if (text.contains(QStringLiteral(": auto;"))) {
return true;
}
return false;
} else {
return false;
}
}();
if (complexHtml) {
return QVariant::fromValue(Types::Html);
}
return QVariant::fromValue(Types::Plain);
}
case IsEmbeddedRole:
return false;
case IsErrorRole:
return messagePart->error();
case ContentRole:
return d->contentForPart(messagePart);
case IsEncryptedRole:
return messagePart->encryptionState() != MimeTreeParser::KMMsgNotEncrypted;
case IsSignedRole:
return messagePart->signatureState() != MimeTreeParser::KMMsgNotSigned;
case SecurityLevelRole: {
auto signature = messagePart->signatureState();
auto encryption = messagePart->encryptionState();
bool messageIsSigned = signature == MimeTreeParser::KMMsgPartiallySigned || signature == MimeTreeParser::KMMsgFullySigned;
bool messageIsEncrypted = encryption == MimeTreeParser::KMMsgPartiallyEncrypted || encryption == MimeTreeParser::KMMsgFullyEncrypted;
if (messageIsSigned) {
const auto sigInfo = signatureInfo(messagePart);
if (!sigInfo.signatureIsGood) {
if (sigInfo.keyMissing || sigInfo.keyExpired) {
return SecurityLevel::NotSoGood;
}
return SecurityLevel::Bad;
}
}
// All good
if ((messageIsSigned || messageIsEncrypted) && !messagePart->error()) {
return SecurityLevel::Good;
}
// No info
return SecurityLevel::Unknow;
}
case EncryptionSecurityLevelRole: {
auto encryption = messagePart->encryptionState();
bool messageIsEncrypted = encryption == MimeTreeParser::KMMsgPartiallyEncrypted || encryption == MimeTreeParser::KMMsgFullyEncrypted;
if (messagePart->error()) {
return SecurityLevel::Bad;
}
// All good
if (messageIsEncrypted) {
return SecurityLevel::Good;
}
// No info
return SecurityLevel::Unknow;
}
case SignatureSecurityLevelRole: {
auto signature = messagePart->signatureState();
bool messageIsSigned = signature == MimeTreeParser::KMMsgPartiallySigned || signature == MimeTreeParser::KMMsgFullySigned;
if (messageIsSigned) {
const auto sigInfo = signatureInfo(messagePart);
if (!sigInfo.signatureIsGood) {
if (sigInfo.keyMissing || sigInfo.keyExpired) {
return SecurityLevel::NotSoGood;
}
return SecurityLevel::Bad;
}
return SecurityLevel::Good;
}
// No info
return SecurityLevel::Unknow;
}
case SignatureDetails:
return QVariant::fromValue(signatureInfo(messagePart));
case EncryptionDetails:
return QVariant::fromValue(encryptionInfo(messagePart));
case ErrorType:
return messagePart->error();
case ErrorString: {
switch (messagePart->error()) {
case MimeTreeParser::MessagePart::NoKeyError: {
if (auto encryptedMessagePart = dynamic_cast<MimeTreeParser::EncryptedMessagePart *>(messagePart)) {
if (encryptedMessagePart->isNoSecKey()) {
QString errorMessage;
if (encryptedMessagePart->cryptoProto() == QGpgME::smime()) {
errorMessage +=
i18ndc("mimetreeparser", "@info:status", "This message cannot be decrypted with any S/MIME certificate in your keyring.");
} else {
errorMessage +=
- i18ndc("mimetreeparser", "@info:status", "This message cannot be decrypted with any OpenPGP certificate in your keyring.");
+ i18ndc("mimetreeparser", "@info:status", "This message cannot be decrypted with any OpenPGP key in your keyring.");
}
if (!encryptedMessagePart->decryptRecipients().empty()) {
errorMessage += QLatin1Char(' ')
+ i18ndcp("mimetreeparser",
"@info:status",
- "The message is encrypted for the following certificate:",
- "The message is encrypted for the following certificates:",
+ "The message is encrypted for the following recipient:",
+ "The message is encrypted for the following recipients:",
encryptedMessagePart->decryptRecipients().size());
errorMessage +=
MimeTreeParser::decryptRecipientsToHtml(encryptedMessagePart->decryptRecipients(), encryptedMessagePart->cryptoProto());
}
return errorMessage;
}
}
}
return messagePart->errorString();
case MimeTreeParser::MessagePart::PassphraseError:
- return i18ndc("mimetreeparser", "@info:status", "Wrong passphrase.");
+ return i18ndc("mimetreeparser", "@info:status", "Wrong password.");
case MimeTreeParser::MessagePart::UnknownError:
break;
default:
break;
}
return messagePart->errorString();
}
}
}
return QVariant();
}
QModelIndex PartModel::parent(const QModelIndex &index) const
{
if (index.isValid()) {
if (auto indexPart = static_cast<MimeTreeParser::MessagePart *>(index.internalPointer())) {
for (const auto &part : std::as_const(d->mParts)) {
if (part.data() == indexPart) {
return QModelIndex();
}
}
const auto parentPart = d->mParents[indexPart];
Q_ASSERT(parentPart);
int row = 0;
const auto parts = d->mEncapsulatedParts[parentPart];
for (const auto &part : parts) {
if (part.data() == indexPart) {
break;
}
row++;
}
return createIndex(row, 0, parentPart);
}
return {};
}
return {};
}
int PartModel::rowCount(const QModelIndex &parent) const
{
if (parent.isValid()) {
const auto part = static_cast<MimeTreeParser::MessagePart *>(parent.internalPointer());
auto encapsulatedPart = dynamic_cast<MimeTreeParser::EncapsulatedRfc822MessagePart *>(part);
if (encapsulatedPart) {
const auto parts = d->mEncapsulatedParts[encapsulatedPart];
return parts.size();
}
return 0;
}
return d->mParts.count();
}
int PartModel::columnCount(const QModelIndex &) const
{
return 1;
}
#include "moc_partmodel.cpp"
diff --git a/src/quick/qml/private/MailPartModel.qml b/src/quick/qml/private/MailPartModel.qml
index 6f89d73..ecca222 100644
--- a/src/quick/qml/private/MailPartModel.qml
+++ b/src/quick/qml/private/MailPartModel.qml
@@ -1,170 +1,170 @@
// SPDX-FileCopyrightText: 2016 Michael Bohlender <michael.bohlender@kdemail.net>
// SPDX-FileCopyrightText: 2022 Carl Schwan <carl@carlschwan.eu>
// SPDX-License-Identifier: GPL-2.0-or-later
import QtQuick 2.15
import QtQuick.Controls 2.15 as QQC2
import QtQuick.Layouts 1.15
import QtQml.Models 2.2
import org.kde.pim.mimetreeparser 1.0
import org.kde.kirigami 2.19 as Kirigami
DelegateModel {
id: root
property string searchString: ""
property bool autoLoadImages: false
property int padding: Kirigami.Units.largeSpacing
property url icalCustomComponent
delegate: RowLayout {
id: partColumn
width: ListView.view.width - Kirigami.Units.largeSpacing
x: Kirigami.Units.smallSpacing
function getType(securityLevel) {
if (securityLevel === PartModel.Good) {
return Kirigami.MessageType.Positive
}
if (securityLevel === PartModel.Bad) {
return Kirigami.MessageType.Error
}
if (securityLevel === PartModel.NotSoGood) {
return Kirigami.MessageType.Warning
}
return Kirigami.MessageType.Information
}
function getColor(securityLevel) {
if (securityLevel === PartModel.Good) {
return Kirigami.Theme.positiveTextColor
}
if (securityLevel === PartModel.Bad) {
return Kirigami.Theme.negativeTextColor
}
if (securityLevel === PartModel.NotSoGood) {
return Kirigami.Theme.neutralTextColor
}
return "transparent"
}
function getDetails(signatureDetails) {
let details = "";
if (signatureDetails.keyMissing) {
- details += i18ndc("mimetreeparser", "@label", "This message has been signed using the certificate %1.", signatureDetails.keyId) + "\n";
- details += i18ndc("mimetreeparser", "@label", "The certificate details are not available.")
+ details += i18ndc("mimetreeparser", "@info", "This message was signed by %1.", signatureDetails.keyId) + "\n";
+ details += i18ndc("mimetreeparser", "@info", "The certificate details are not available.")
} else {
- details += i18ndc("mimetreeparser", "@label", "This message has been signed using the certificate %1 by %2.", signatureDetails.keyId, signatureDetails.signer) + "\n";
+ details += i18ndc("mimetreeparser", "@info", "This message was signed by %1 - %2.", signatureDetails.keyId, signatureDetails.signer) + "\n";
if (signatureDetails.keyRevoked) {
- details += "\n" + i18ndc("mimetreeparser", "@label", "The certificate was revoked.")
+ details += "\n" + i18ndc("mimetreeparser", "@info", "The certificate was revoked.")
}
if (signatureDetails.keyExpired) {
- details += "\n" + i18ndc("mimetreeparser", "@label", "The certificate has expired.")
+ details += "\n" + i18ndc("mimetreeparser", "@info", "The certificate has expired.")
}
if (signatureDetails.keyIsTrusted) {
- details += "\n" + i18ndc("mimetreeparser", "@label", "You are trusting this certificate.")
+ details += "\n" + i18ndc("mimetreeparser", "@info", "You are trusting this certificate.")
}
if (!signatureDetails.signatureIsGood && !signatureDetails.keyRevoked && !signatureDetails.keyExpired && !signatureDetails.keyIsTrusted) {
- details += "\n" + i18ndc("mimetreeparser", "@label", "The signature is invalid.")
+ details += "\n" + i18ndc("mimetreeparser", "@info", "The signature is invalid.")
}
}
return details
}
QQC2.Control {
Layout.preferredWidth: Kirigami.Units.smallSpacing
Layout.fillHeight: true
visible: model.encrypted
background: Rectangle {
id: border
color: getColor(model.securityLevel)
opacity: 0.5
}
QQC2.ToolTip.text: getDetails(model.encryptionSecurityLevel)
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
}
ColumnLayout {
Banner {
iconName: "mail-encrypted"
type: getType(model.encryptionSecurityLevel)
visible: model.encrypted
text: !model.encryptionDetails.keyId ? i18n("This message is encrypted but we don't have the certificate for it.") : i18n("This message is encrypted to the certificate: %1", model.encryptionDetails.keyId);
Layout.fillWidth: true
}
Banner {
iconName: 'mail-signed'
visible: model.signed
type: getType(model.signatureSecurityLevel)
text: getDetails(model.signatureDetails)
Layout.fillWidth: true
}
Loader {
id: partLoader
Layout.preferredHeight: item ? item.contentHeight : 0
Layout.fillWidth: true
Layout.leftMargin: root.padding
Layout.rightMargin: root.padding
Component.onCompleted: {
switch (model.type + 0) {
case PartModel.Plain:
partLoader.setSource("TextPart.qml", {
content: model.content,
embedded: model.embedded,
})
break
case PartModel.Html:
partLoader.setSource("HtmlPart.qml", {
content: model.content,
})
break;
case PartModel.Error:
partLoader.setSource("ErrorPart.qml", {
errorType: model.errorType,
errorString: model.errorString,
})
break;
case PartModel.Encapsulated:
partLoader.setSource("MailPart.qml", {
rootIndex: root.modelIndex(index),
model: root.model,
sender: model.sender,
date: model.date,
})
break;
case PartModel.Ical:
partLoader.setSource(root.icalCustomComponent ? root.icalCustomComponent : "ICalPart.qml", {
content: model.content,
})
break;
}
}
Binding {
target: partLoader.item
property: "searchString"
value: root.searchString
when: partLoader.status === Loader.Ready
}
Binding {
target: partLoader.item
property: "autoLoadImages"
value: root.autoLoadImages
when: partLoader.status === Loader.Ready
}
}
}
}
}
diff --git a/src/widgets/messagecontainerwidget.cpp b/src/widgets/messagecontainerwidget.cpp
index e938303..044fecb 100644
--- a/src/widgets/messagecontainerwidget.cpp
+++ b/src/widgets/messagecontainerwidget.cpp
@@ -1,268 +1,268 @@
// SPDX-FileCopyrightText: 2023 g10 Code GmbH
// SPDX-FileContributor: Carl Schwan <carl.schwan@gnupg.com>
// SPDX-License-Identifier: LGPL-2.0-or-later
#include "../core/utils.h"
#include "messagecontainerwidget_p.h"
#include "urlhandler_p.h"
#include <KLocalizedString>
#include <KMessageWidget>
#include <Libkleo/Compliance>
#include <Libkleo/Formatting>
#include <QGpgME/Protocol>
#include <QLabel>
#include <QPaintEvent>
#include <QPainter>
#include <QVBoxLayout>
namespace
{
const int borderWidth = 5;
QColor getColor(PartModel::SecurityLevel securityLevel)
{
const static QHash<PartModel::SecurityLevel, QColor> colors{
{PartModel::Good, QColor(39, 174, 96)}, // Window: ForegroundPositive
{PartModel::Bad, QColor(218, 68, 83)}, // Window: ForegroundNegative
{PartModel::NotSoGood, QColor(246, 116, 0)}, // Window: ForegroundNeutral
};
return colors.value(securityLevel, QColor());
}
KMessageWidget::MessageType getType(PartModel::SecurityLevel securityLevel)
{
const static QHash<PartModel::SecurityLevel, KMessageWidget::MessageType> messageTypes{
{PartModel::Good, KMessageWidget::MessageType::Positive},
{PartModel::Bad, KMessageWidget::MessageType::Error},
{PartModel::NotSoGood, KMessageWidget::MessageType::Warning},
};
return messageTypes.value(securityLevel, KMessageWidget::MessageType::Information);
}
QString getDetails(const SignatureInfo &signatureDetails)
{
QString href;
if (signatureDetails.cryptoProto) {
href = QStringLiteral("messageviewer:showCertificate#%1 ### %2 ### %3")
.arg(signatureDetails.cryptoProto->displayName(), signatureDetails.cryptoProto->name(), QString::fromLatin1(signatureDetails.keyId));
}
QString details;
if (signatureDetails.keyMissing) {
if (Kleo::DeVSCompliance::isCompliant() && signatureDetails.isCompliant) {
details += i18ndc("mimetreeparser",
- "@label",
+ "@info",
"This message has been signed VS-NfD compliant using the certificate <a href=\"%1\">%2</a>.",
href,
Kleo::Formatting::prettyID(signatureDetails.keyId.toStdString().data()))
+ QLatin1Char('\n');
} else {
details += i18ndc("mimetreeparser",
- "@label",
+ "@info",
"This message has been signed using the certificate <a href=\"%1\">%2</a>.",
href,
Kleo::Formatting::prettyID(signatureDetails.keyId.toStdString().data()))
+ QLatin1Char('\n');
}
- details += i18ndc("mimetreeparser", "@label", "The certificate details are not available.");
+ details += i18ndc("mimetreeparser", "@info", "The certificate details are not available.");
} else {
QString signerDisplayName = signatureDetails.signer.toHtmlEscaped();
if (signatureDetails.cryptoProto == QGpgME::smime()) {
Kleo::DN dn(signatureDetails.signer);
signerDisplayName = MimeTreeParser::dnToDisplayName(dn).toHtmlEscaped();
}
if (Kleo::DeVSCompliance::isCompliant() && signatureDetails.isCompliant) {
- details += i18ndc("mimetreeparser", "@label", "This message has been signed VS-NfD compliant by %1.", signerDisplayName);
+ details += i18ndc("mimetreeparser", "@info", "This message has been signed VS-NfD compliant by %1.", signerDisplayName);
} else {
- details += i18ndc("mimetreeparser", "@label", "This message has been signed by %1.", signerDisplayName);
+ details += i18ndc("mimetreeparser", "@info", "This message has been signed by %1.", signerDisplayName);
}
if (signatureDetails.keyRevoked) {
- details += QLatin1Char('\n') + i18ndc("mimetreeparser", "@label", "The <a href=\"%1\">certificate</a> was revoked.", href);
+ details += QLatin1Char('\n') + i18ndc("mimetreeparser", "@info", "The <a href=\"%1\">certificate</a> was revoked.", href);
}
if (signatureDetails.keyExpired) {
- details += QLatin1Char('\n') + i18ndc("mimetreeparser", "@label", "The <a href=\"%1\">certificate</a> was expired.", href);
+ details += QLatin1Char('\n') + i18ndc("mimetreeparser", "@info", "The <a href=\"%1\">certificate</a> is expired.", href);
}
if (signatureDetails.keyTrust == GpgME::Signature::Unknown) {
details += QLatin1Char(' ')
- + i18ndc("mimetreeparser", "@label", "The signature is valid, but the <a href=\"%1\">certificate</a>'s validity is unknown.", href);
+ + i18ndc("mimetreeparser", "@info", "The signature is valid, but the <a href=\"%1\">certificate</a>'s validity is unknown.", href);
} else if (signatureDetails.keyTrust == GpgME::Signature::Marginal) {
details += QLatin1Char(' ')
- + i18ndc("mimetreeparser", "@label", "The signature is valid and the <a href=\"%1\">certificate</a> is marginally trusted.", href);
+ + i18ndc("mimetreeparser", "@info", "The signature is valid and the <a href=\"%1\">certificate</a> is marginally trusted.", href);
} else if (signatureDetails.keyTrust == GpgME::Signature::Full) {
details +=
- QLatin1Char(' ') + i18ndc("mimetreeparser", "@label", "The signature is valid and the <a href=\"%1\">certificate</a> is fully trusted.", href);
+ QLatin1Char(' ') + i18ndc("mimetreeparser", "@info", "The signature is valid and the <a href=\"%1\">certificate</a> is fully trusted.", href);
} else if (signatureDetails.keyTrust == GpgME::Signature::Ultimate) {
details += QLatin1Char(' ')
- + i18ndc("mimetreeparser", "@label", "The signature is valid and the <a href=\"%1\">certificate</a> is ultimately trusted.", href);
+ + i18ndc("mimetreeparser", "@info", "The signature is valid and the <a href=\"%1\">certificate</a> is ultimately trusted.", href);
} else {
details +=
- QLatin1Char(' ') + i18ndc("mimetreeparser", "@label", "The signature is valid, but the <a href=\"%1\">certificate</a> is untrusted.", href);
+ QLatin1Char(' ') + i18ndc("mimetreeparser", "@info", "The signature is valid, but the <a href=\"%1\">certificate</a> is untrusted.", href);
}
if (!signatureDetails.signatureIsGood && !signatureDetails.keyRevoked && !signatureDetails.keyExpired
&& signatureDetails.keyTrust != GpgME::Signature::Unknown) {
- details += QLatin1Char(' ') + i18ndc("mimetreeparser", "@label", "The signature is invalid.");
+ details += QLatin1Char(' ') + i18ndc("mimetreeparser", "@info", "The signature is invalid.");
}
}
return details;
}
}
MessageWidgetContainer::MessageWidgetContainer(bool isSigned,
const SignatureInfo &signatureInfo,
PartModel::SecurityLevel signatureSecurityLevel,
bool displaySignatureInfo,
bool isEncrypted,
const SignatureInfo &encryptionInfo,
PartModel::SecurityLevel encryptionSecurityLevel,
bool displayEncryptionInfo,
UrlHandler *urlHandler,
QWidget *parent)
: QFrame(parent)
, m_isSigned(isSigned)
, m_signatureInfo(signatureInfo)
, m_signatureSecurityLevel(signatureSecurityLevel)
, m_displaySignatureInfo(displaySignatureInfo)
, m_isEncrypted(isEncrypted)
, m_encryptionInfo(encryptionInfo)
, m_encryptionSecurityLevel(encryptionSecurityLevel)
, m_displayEncryptionInfo(displayEncryptionInfo)
, m_urlHandler(urlHandler)
{
createLayout();
}
MessageWidgetContainer::~MessageWidgetContainer() = default;
void MessageWidgetContainer::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event)
if (!m_isSigned && !m_isEncrypted) {
return;
}
QPainter painter(this);
if (layoutDirection() == Qt::RightToLeft) {
auto r = rect();
r.setX(width() - borderWidth);
r.setWidth(borderWidth);
const QColor color = getColor(PartModel::SecurityLevel::Good);
painter.setRenderHint(QPainter::Antialiasing);
painter.setBrush(QColor(color));
painter.setPen(QPen(Qt::NoPen));
painter.drawRect(r);
} else {
auto r = rect();
r.setWidth(borderWidth);
const QColor color = getColor(PartModel::SecurityLevel::Good);
painter.setRenderHint(QPainter::Antialiasing);
painter.setBrush(QColor(color));
painter.setPen(QPen(Qt::NoPen));
painter.drawRect(r);
}
}
bool MessageWidgetContainer::event(QEvent *event)
{
if (event->type() == QEvent::Polish && !layout()) {
createLayout();
}
return QFrame::event(event);
}
void MessageWidgetContainer::createLayout()
{
delete layout();
auto vLayout = new QVBoxLayout(this);
if (m_isSigned || m_isEncrypted) {
if (layoutDirection() == Qt::RightToLeft) {
layout()->setContentsMargins(0, 0, borderWidth * 2, 0);
} else {
layout()->setContentsMargins(borderWidth * 2, 0, 0, 0);
}
}
if (m_isEncrypted && m_displayEncryptionInfo) {
auto encryptionMessage = new KMessageWidget(this);
encryptionMessage->setObjectName(QLatin1StringView("EncryptionMessage"));
encryptionMessage->setCloseButtonVisible(false);
QString text;
if (m_encryptionInfo.keyId.isEmpty()) {
encryptionMessage->setIcon(QIcon::fromTheme(QStringLiteral("data-error")));
encryptionMessage->setMessageType(KMessageWidget::Error);
if (Kleo::DeVSCompliance::isCompliant() && m_encryptionInfo.isCompliant) {
- text = i18n("This message is VS-NfD compliant encrypted but we don't have the certificate for it.", QString::fromUtf8(m_encryptionInfo.keyId));
+ text = i18n("This message is VS-NfD compliant encrypted but you don't have the matching secret key for it.", QString::fromUtf8(m_encryptionInfo.keyId));
} else {
- text = i18n("This message is encrypted but we don't have the certificate for it.");
+ text = i18n("This message is encrypted but you don't have the matching secret key for it.");
}
} else {
encryptionMessage->setIcon(QIcon::fromTheme(QStringLiteral("mail-encrypted")));
encryptionMessage->setMessageType(KMessageWidget::Positive);
if (Kleo::DeVSCompliance::isCompliant() && m_encryptionInfo.isCompliant) {
text = i18n("This message is VS-NfD compliant encrypted.");
} else {
text = i18n("This message is encrypted.");
}
}
encryptionMessage->setText(text + QLatin1Char(' ') + QStringLiteral("<a href=\"messageviewer:showDetails\">Details</a>"));
connect(encryptionMessage, &KMessageWidget::linkActivated, this, [this, encryptionMessage, text](const QString &link) {
QUrl url(link);
if (url.path() == QStringLiteral("showDetails")) {
- QString newText = text + QLatin1Char(' ') + i18n("The message is encrypted for the following certificates:");
+ QString newText = text + QLatin1Char(' ') + i18n("The message is encrypted for the following recipients:");
newText += MimeTreeParser::decryptRecipientsToHtml(m_encryptionInfo.decryptRecipients, m_encryptionInfo.cryptoProto);
encryptionMessage->setText(newText);
return;
}
if (url.path() == QStringLiteral("showCertificate")) {
m_urlHandler->handleClick(QUrl(link), window()->windowHandle());
}
});
vLayout->addWidget(encryptionMessage);
}
if (m_isSigned && m_displaySignatureInfo) {
auto signatureMessage = new KMessageWidget(this);
signatureMessage->setObjectName(QStringLiteral("SignatureMessage"));
signatureMessage->setCloseButtonVisible(false);
signatureMessage->setText(getDetails(m_signatureInfo));
connect(signatureMessage, &KMessageWidget::linkActivated, this, [this](const QString &link) {
m_urlHandler->handleClick(QUrl(link), window()->windowHandle());
});
signatureMessage->setMessageType(getType(m_signatureSecurityLevel));
switch (m_signatureSecurityLevel) {
case PartModel::Good:
signatureMessage->setIcon(QIcon::fromTheme(QStringLiteral("mail-signed")));
break;
case PartModel::Bad:
signatureMessage->setIcon(QIcon::fromTheme(QStringLiteral("data-error")));
break;
case PartModel::NotSoGood:
signatureMessage->setIcon(QIcon::fromTheme(QStringLiteral("data-warning")));
break;
default:
break;
}
signatureMessage->setWordWrap(true);
vLayout->addWidget(signatureMessage);
}
}
#include "moc_messagecontainerwidget_p.cpp"

File Metadata

Mime Type
text/x-diff
Expires
Sat, May 10, 8:31 AM (1 d, 11 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
c0/7c/e6a3ef7cb9696659344c3c0ce13a

Event Timeline