Page MenuHome GnuPG

No OneTemporary

diff --git a/client/editor/mailtemplates.cpp b/client/editor/mailtemplates.cpp
index 902a576..278f52a 100644
--- a/client/editor/mailtemplates.cpp
+++ b/client/editor/mailtemplates.cpp
@@ -1,919 +1,920 @@
/*
Copyright (c) 2009 Constantin Berzan <exit3219@gmail.com>
Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
Copyright (c) 2010 Leo Franchi <lfranchi@kde.org>
Copyright (c) 2017 Christian Mollekopf <mollekopf@kolabsys.com>
This library is free software; you can redistribute it and/or modify it
under the terms of the GNU Library General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at your
option) any later version.
This library is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to the
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301, USA.
*/
#include "mailtemplates.h"
#include <KLocalizedString>
#include <QByteArray>
#include <QDebug>
#include <QDomDocument>
#include <QList>
#include <QStringDecoder>
#include <QSysInfo>
#include <QTextDocument>
+#include <QRegularExpression>
#include <functional>
#include <KMime/Types>
+#include <KMime/Headers>
#include <MimeTreeParserCore/ObjectTreeParser>
-#include <qregularexpression.h>
using namespace Qt::Literals::StringLiterals;
QDebug operator<<(QDebug dbg, const KMime::Types::Mailbox &mb)
{
dbg << mb.addrSpec().asString();
return dbg;
}
namespace KMime
{
namespace Types
{
static bool operator==(const KMime::Types::AddrSpec &left, const KMime::Types::AddrSpec &right)
{
return (left.asString() == right.asString());
}
static bool operator==(const KMime::Types::Mailbox &left, const KMime::Types::Mailbox &right)
{
return (left.addrSpec().asString() == right.addrSpec().asString());
}
}
Message *contentToMessage(Content *content)
{
content->assemble();
const auto encoded = content->encodedContent();
auto message = new Message();
message->setContent(encoded);
message->parse();
return message;
}
}
static KMime::Types::Mailbox::List stripMyAddressesFromAddressList(const KMime::Types::Mailbox::List &list, const KMime::Types::AddrSpecList me)
{
KMime::Types::Mailbox::List addresses(list);
for (KMime::Types::Mailbox::List::Iterator it = addresses.begin(); it != addresses.end();) {
if (me.contains(it->addrSpec())) {
it = addresses.erase(it);
} else {
++it;
}
}
return addresses;
}
static QString toPlainText(const QString &s)
{
QTextDocument doc;
doc.setHtml(s);
return doc.toPlainText();
}
QString replacePrefixes(const QString &str, const QStringList &prefixRegExps, const QString &newPrefix)
{
// construct a big regexp that
// 1. is anchored to the beginning of str (sans whitespace)
// 2. matches at least one of the part regexps in prefixRegExps
const QString bigRegExp = QStringLiteral("^(?:\\s+|(?:%1))+\\s*").arg(prefixRegExps.join(QStringLiteral(")|(?:")));
QRegularExpression rx(bigRegExp, QRegularExpression::CaseInsensitiveOption);
if (!rx.isValid()) {
qWarning() << "bigRegExp = \"" << bigRegExp << "\"\n"
<< "prefix regexp is invalid!";
qWarning() << "Error: " << rx.errorString() << rx;
Q_ASSERT(false);
return str;
}
QString tmp = str;
// We expect a match at the beginning of the string
QRegularExpressionMatch match;
if (tmp.indexOf(rx, 0, &match) == 0) {
return tmp.replace(0, match.capturedLength(), newPrefix + QLatin1Char(' '));
}
// No match, we just prefix the newPrefix
return newPrefix + u' ' + str;
}
const QStringList getForwardPrefixes()
{
// See https://en.wikipedia.org/wiki/List_of_email_subject_abbreviations
QStringList list;
// We want to be able to potentially reply to a variety of languages, so only translating is not enough
list << i18nc("FWD abbreviation for forwarded in emails", "fwd");
list << u"fwd"_s;
list << u"fw"_s;
list << u"wg"_s;
list << u"vs"_s;
list << u"tr"_s;
list << u"rv"_s;
list << u"enc"_s;
return list;
}
static QString forwardSubject(const QString &s)
{
// The standandard prefix
const QString localPrefix = QStringLiteral("FW:");
QStringList forwardPrefixes;
for (const auto &prefix : getForwardPrefixes()) {
forwardPrefixes << prefix + QStringLiteral("\\s*:");
}
return replacePrefixes(s, forwardPrefixes, localPrefix);
}
static QStringList getReplyPrefixes()
{
// See https://en.wikipedia.org/wiki/List_of_email_subject_abbreviations
QStringList list;
// We want to be able to potentially reply to a variety of languages, so only translating is not enough
list << i18nc("RE abbreviation for reply in emails", "re");
list << u"re"_s;
list << u"aw"_s;
list << u"sv"_s;
list << u"antw"_s;
list << u"ref"_s;
return list;
}
static QString replySubject(const QString &s)
{
// The standandard prefix (latin for "in re", in matter of)
const QString localPrefix = QStringLiteral("RE:");
QStringList replyPrefixes;
for (const auto &prefix : getReplyPrefixes()) {
replyPrefixes << prefix + u"\\s*:"_s;
replyPrefixes << prefix + u"\\[.+\\]:"_s;
replyPrefixes << prefix + u"\\d+:"_s;
}
return replacePrefixes(s, replyPrefixes, localPrefix);
}
static QByteArray getRefStr(const QByteArray &references, const QByteArray &messageId)
{
QByteArray firstRef, lastRef, refStr{references.trimmed()}, retRefStr;
int i, j;
if (refStr.isEmpty()) {
return messageId;
}
i = refStr.indexOf('<');
j = refStr.indexOf('>');
firstRef = refStr.mid(i, j - i + 1);
if (!firstRef.isEmpty()) {
retRefStr = firstRef + ' ';
}
i = refStr.lastIndexOf('<');
j = refStr.lastIndexOf('>');
lastRef = refStr.mid(i, j - i + 1);
if (!lastRef.isEmpty() && lastRef != firstRef) {
retRefStr += lastRef + ' ';
}
retRefStr += messageId;
return retRefStr;
}
KMime::Content *createPlainPartContent(const QString &plainBody, KMime::Content *parent = nullptr)
{
KMime::Content *textPart = new KMime::Content(parent);
textPart->contentType()->setMimeType("text/plain");
// FIXME This is supposed to select a charset out of the available charsets that contains all necessary characters to render the text
// QTextCodec *charset = selectCharset(m_charsets, plainBody);
// textPart->contentType()->setCharset(charset->name());
textPart->contentType()->setCharset("utf-8");
textPart->contentTransferEncoding()->setEncoding(KMime::Headers::CE8Bit);
textPart->fromUnicodeString(plainBody);
return textPart;
}
KMime::Content *createMultipartAlternativeContent(const QString &plainBody, const QString &htmlBody, KMime::Message *parent = nullptr)
{
KMime::Content *multipartAlternative = new KMime::Content(parent);
multipartAlternative->contentType()->setMimeType("multipart/alternative");
multipartAlternative->contentType()->setBoundary(KMime::multiPartBoundary());
KMime::Content *textPart = createPlainPartContent(plainBody, multipartAlternative);
multipartAlternative->appendContent(textPart);
KMime::Content *htmlPart = new KMime::Content(multipartAlternative);
htmlPart->contentType()->setMimeType("text/html");
// FIXME This is supposed to select a charset out of the available charsets that contains all necessary characters to render the text
// QTextCodec *charset = selectCharset(m_charsets, htmlBody);
// htmlPart->contentType()->setCharset(charset->name());
htmlPart->contentType()->setCharset("utf-8");
htmlPart->contentTransferEncoding()->setEncoding(KMime::Headers::CE8Bit);
htmlPart->fromUnicodeString(htmlBody);
multipartAlternative->appendContent(htmlPart);
return multipartAlternative;
}
KMime::Content *createMultipartMixedContent(QVector<KMime::Content *> contents)
{
KMime::Content *multiPartMixed = new KMime::Content();
multiPartMixed->contentType()->setMimeType("multipart/mixed");
multiPartMixed->contentType()->setBoundary(KMime::multiPartBoundary());
for (const auto &content : contents) {
multiPartMixed->appendContent(content);
}
return multiPartMixed;
}
QString plainToHtml(const QString &body)
{
QString str = body;
str = str.toHtmlEscaped();
str.replace(u'\n', u"<br />\n"_s);
return str;
}
// TODO implement this function using a DOM tree parser
void makeValidHtml(QString &body, const QString &headElement)
{
QRegularExpression regEx(u"<html.*>"_s, QRegularExpression::InvertedGreedinessOption);
if (!body.isEmpty() && !body.contains(regEx)) {
regEx.setPattern(u"<body.*>"_s);
if (!body.contains(regEx)) {
body = u"<body>"_s + body + u"<br/></body>"_s;
}
regEx.setPattern(u"<head.*>"_s);
if (!body.contains(regEx)) {
body = u"<head>"_s + headElement + u"</head>"_s + body;
}
body = u"<html>"_s + body + u"</html>"_s;
}
}
// FIXME strip signature works partially for HTML mails
static QString stripSignature(const QString &msg)
{
// Following RFC 3676, only > before --
// I prefer to not delete a SB instead of delete good mail content.
// We expect no CRLF from the ObjectTreeParser. The regex won't handle it.
if (msg.contains(QStringLiteral("\r\n"))) {
qWarning() << "Message contains CRLF, but shouldn't: " << msg;
Q_ASSERT(false);
}
static const QRegularExpression sbDelimiterSearch(u"(^|\n)[> ]*-- \n"_s);
// The regular expression to look for prefix change
static const QRegularExpression commonReplySearch(u"^[ ]*>"_s);
QString res = msg;
int posDeletingStart = 1; // to start looking at 0
// While there are SB delimiters (start looking just before the deleted SB)
while ((posDeletingStart = res.indexOf(sbDelimiterSearch, posDeletingStart - 1)) >= 0) {
QString prefix; // the current prefix
QString line; // the line to check if is part of the SB
int posNewLine = -1;
// Look for the SB beginning
int posSignatureBlock = res.indexOf(u'-', posDeletingStart);
// The prefix before "-- "$
if (res.at(posDeletingStart) == u'\n') {
++posDeletingStart;
}
prefix = res.mid(posDeletingStart, posSignatureBlock - posDeletingStart);
posNewLine = res.indexOf(QLatin1Char('\n'), posSignatureBlock) + 1;
// now go to the end of the SB
while (posNewLine < res.size() && posNewLine > 0) {
// handle the undefined case for mid ( x , -n ) where n>1
int nextPosNewLine = res.indexOf(QLatin1Char('\n'), posNewLine);
if (nextPosNewLine < 0) {
nextPosNewLine = posNewLine - 1;
}
line = res.mid(posNewLine, nextPosNewLine - posNewLine);
// check when the SB ends:
// * does not starts with prefix or
// * starts with prefix+(any substring of prefix)
if ((prefix.isEmpty() && line.indexOf(commonReplySearch) < 0)
|| (!prefix.isEmpty() && line.startsWith(prefix) && line.mid(prefix.size()).indexOf(commonReplySearch) < 0)) {
posNewLine = res.indexOf(QLatin1Char('\n'), posNewLine) + 1;
} else {
break; // end of the SB
}
}
// remove the SB or truncate when is the last SB
if (posNewLine > 0) {
res.remove(posDeletingStart, posNewLine - posDeletingStart);
} else {
res.truncate(posDeletingStart);
}
}
return res;
}
static void plainMessageText(const QString &plainTextContent, const QString &htmlContent, const std::function<void(const QString &)> &callback)
{
const auto result = plainTextContent.isEmpty() ? toPlainText(htmlContent) : plainTextContent;
callback(result);
}
void htmlMessageText(const QString &plainTextContent, const QString &htmlContent, const std::function<void(const QString &body, QString &head)> &callback)
{
QString htmlElement = htmlContent;
if (htmlElement.isEmpty()) { // plain mails only
QString htmlReplace = plainTextContent.toHtmlEscaped();
htmlReplace = htmlReplace.replace(QStringLiteral("\n"), QStringLiteral("<br />"));
htmlElement = QStringLiteral("<html><head></head><body>%1</body></html>\n").arg(htmlReplace);
}
QDomDocument document;
document.setContent(htmlElement);
QString body;
QTextStream bodyStream(&body);
QString head;
QTextStream headStream(&head);
const auto bodies = document.elementsByTagName(u"body"_s);
const auto heads = document.elementsByTagName(u"head"_s);
if (bodies.isEmpty()) {
body = htmlElement;
} else {
bodies.item(0).save(bodyStream, 2);
}
if (!heads.isEmpty()) {
heads.item(0).save(headStream, 2);
}
callback(body, head);
}
QString formatQuotePrefix(const QString &wildString, const QString &fromDisplayString)
{
QString result;
if (wildString.isEmpty()) {
return wildString;
}
unsigned int strLength(wildString.length());
for (uint i = 0; i < strLength;) {
QChar ch = wildString[i++];
if (ch == QLatin1Char('%') && i < strLength) {
ch = wildString[i++];
switch (ch.toLatin1()) {
case 'f': { // sender's initals
if (fromDisplayString.isEmpty()) {
break;
}
uint j = 0;
const unsigned int strLength(fromDisplayString.length());
for (; j < strLength && fromDisplayString[j] > QLatin1Char(' '); ++j)
;
for (; j < strLength && fromDisplayString[j] <= QLatin1Char(' '); ++j)
;
result += fromDisplayString[0];
if (j < strLength && fromDisplayString[j] > QLatin1Char(' ')) {
result += fromDisplayString[j];
} else if (strLength > 1) {
if (fromDisplayString[1] > QLatin1Char(' ')) {
result += fromDisplayString[1];
}
}
} break;
case '_':
result += QLatin1Char(' ');
break;
case '%':
result += QLatin1Char('%');
break;
default:
result += QLatin1Char('%');
result += ch;
break;
}
} else {
result += ch;
}
}
return result;
}
QString quotedPlainText(const QString &selection, const QString &fromDisplayString)
{
QString content = selection;
// Remove blank lines at the beginning:
const int firstNonWS = content.indexOf(QRegularExpression(u"\\S"_s));
const int lineStart = content.lastIndexOf(QLatin1Char('\n'), firstNonWS);
if (lineStart >= 0) {
content.remove(0, static_cast<unsigned int>(lineStart));
}
const auto quoteString = QStringLiteral("> ");
const QString indentStr = formatQuotePrefix(quoteString, fromDisplayString);
// FIXME
// if (TemplateParserSettings::self()->smartQuote() && mWrap) {
// content = MessageCore::StringUtil::smartQuote(content, mColWrap - indentStr.length());
// }
content.replace(QLatin1Char('\n'), QLatin1Char('\n') + indentStr);
content.prepend(indentStr);
content += QLatin1Char('\n');
return content;
}
QString quotedHtmlText(const QString &selection)
{
QString content = selection;
// TODO 1) look for all the variations of <br> and remove the blank lines
// 2) implement vertical bar for quoted HTML mail.
// 3) After vertical bar is implemented, If a user wants to edit quoted message,
// then the <blockquote> tags below should open and close as when required.
// Add blockquote tag, so that quoted message can be differentiated from normal message
content = QLatin1String("<blockquote>") + content + QLatin1String("</blockquote>");
return content;
}
enum ReplyStrategy { ReplyList, ReplySmart, ReplyAll, ReplyAuthor, ReplyNone };
static QByteArray as7BitString(const KMime::Headers::Base *h)
{
if (h) {
return h->as7BitString(false);
}
return {};
}
static QString asUnicodeString(const KMime::Headers::Base *h)
{
if (h) {
return h->asUnicodeString();
}
return {};
}
static KMime::Types::Mailbox::List getMailingListAddresses(const KMime::Headers::Base *listPostHeader)
{
KMime::Types::Mailbox::List mailingListAddresses;
const QString listPost = asUnicodeString(listPostHeader);
if (listPost.contains(QStringLiteral("mailto:"), Qt::CaseInsensitive)) {
static QRegularExpression rx(QStringLiteral("<mailto:([^@>]+)@([^>]+)>"), QRegularExpression::CaseInsensitiveOption);
QRegularExpressionMatch match;
if (listPost.indexOf(rx, 0, &match) != -1) { // matched
KMime::Types::Mailbox mailbox;
mailbox.fromUnicodeString(QString(match.captured(1) + u'@' + match.captured(2)));
mailingListAddresses << mailbox;
}
}
return mailingListAddresses;
}
struct RecipientMailboxes {
KMime::Types::Mailbox::List to;
KMime::Types::Mailbox::List cc;
};
static RecipientMailboxes getRecipients(const KMime::Types::Mailbox::List &from,
const KMime::Types::Mailbox::List &to,
const KMime::Types::Mailbox::List &cc,
const KMime::Types::Mailbox::List &replyToList,
const KMime::Types::Mailbox::List &mailingListAddresses,
const KMime::Types::AddrSpecList &me)
{
KMime::Types::Mailbox::List toList;
KMime::Types::Mailbox::List ccList;
auto listContainsMe = [&](const KMime::Types::Mailbox::List &list) {
for (const auto &m : me) {
KMime::Types::Mailbox mailbox;
mailbox.setAddress(m);
if (list.contains(mailbox)) {
return true;
}
}
return false;
};
if (listContainsMe(from)) {
// sender seems to be one of our own identities, so we assume that this
// is a reply to a "sent" mail where the users wants to add additional
// information for the recipient.
return {to, cc};
}
KMime::Types::Mailbox::List recipients;
KMime::Types::Mailbox::List ccRecipients;
// add addresses from the Reply-To header to the list of recipients
if (!replyToList.isEmpty()) {
recipients = replyToList;
// strip all possible mailing list addresses from the list of Reply-To addresses
for (const KMime::Types::Mailbox &mailbox : std::as_const(mailingListAddresses)) {
for (const KMime::Types::Mailbox &recipient : std::as_const(recipients)) {
if (mailbox == recipient) {
recipients.removeAll(recipient);
}
}
}
}
if (!mailingListAddresses.isEmpty()) {
// this is a mailing list message
if (recipients.isEmpty() && !from.isEmpty()) {
// The sender didn't set a Reply-to address, so we add the From
// address to the list of CC recipients.
ccRecipients += from;
qDebug() << "Added" << from << "to the list of CC recipients";
}
// if it is a mailing list, add the posting address
recipients.prepend(mailingListAddresses[0]);
} else {
// this is a normal message
if (recipients.isEmpty() && !from.isEmpty()) {
// in case of replying to a normal message only then add the From
// address to the list of recipients if there was no Reply-to address
recipients += from;
qDebug() << "Added" << from << "to the list of recipients";
}
}
// strip all my addresses from the list of recipients
toList = stripMyAddressesFromAddressList(recipients, me);
// merge To header and CC header into a list of CC recipients
auto appendToCcRecipients = [&](const KMime::Types::Mailbox::List &list) {
for (const KMime::Types::Mailbox &mailbox : list) {
if (!recipients.contains(mailbox) && !ccRecipients.contains(mailbox)) {
ccRecipients += mailbox;
qDebug() << "Added" << mailbox.prettyAddress() << "to the list of CC recipients";
}
}
};
appendToCcRecipients(to);
appendToCcRecipients(cc);
if (!ccRecipients.isEmpty()) {
// strip all my addresses from the list of CC recipients
ccRecipients = stripMyAddressesFromAddressList(ccRecipients, me);
// in case of a reply to self, toList might be empty. if that's the case
// then propagate a cc recipient to To: (if there is any).
if (toList.isEmpty() && !ccRecipients.isEmpty()) {
toList << ccRecipients.at(0);
ccRecipients.pop_front();
}
ccList = ccRecipients;
}
if (toList.isEmpty() && !recipients.isEmpty()) {
// reply to self without other recipients
toList << recipients.at(0);
}
return {toList, ccList};
}
void MailTemplates::reply(const KMime::Message::Ptr &origMsg,
const std::function<void(const KMime::Message::Ptr &result)> &callback,
const KMime::Types::AddrSpecList &me)
{
// FIXME
const bool alwaysPlain = true;
// Decrypt what we have to
MimeTreeParser::ObjectTreeParser otp;
otp.parseObjectTree(origMsg.data());
otp.decryptAndVerify();
auto partList = otp.collectContentParts();
if (partList.isEmpty()) {
Q_ASSERT(false);
return;
}
auto part = partList[0];
Q_ASSERT(part);
// Prepare the reply message
KMime::Message::Ptr msg(new KMime::Message);
msg->removeHeader<KMime::Headers::To>();
msg->removeHeader<KMime::Headers::Subject>();
msg->contentType(true)->setMimeType("text/plain");
msg->contentType()->setCharset("utf-8");
auto getMailboxes = [](const KMime::Headers::Base *h) -> KMime::Types::Mailbox::List {
if (h) {
return static_cast<const KMime::Headers::Generics::AddressList *>(h)->mailboxes();
}
return {};
};
auto fromHeader = static_cast<const KMime::Headers::From *>(part->header(KMime::Headers::From::staticType()));
const auto recipients = getRecipients(fromHeader ? fromHeader->mailboxes() : KMime::Types::Mailbox::List{},
getMailboxes(part->header(KMime::Headers::To::staticType())),
getMailboxes(part->header(KMime::Headers::Cc::staticType())),
getMailboxes(part->header(KMime::Headers::ReplyTo::staticType())),
getMailingListAddresses(part->header("List-Post")),
me);
for (const auto &mailbox : recipients.to) {
msg->to()->addAddress(mailbox);
}
for (const auto &mailbox : recipients.cc) {
msg->cc(true)->addAddress(mailbox);
}
const auto messageId = as7BitString(part->header(KMime::Headers::MessageID::staticType()));
const QByteArray refStr = getRefStr(as7BitString(part->header(KMime::Headers::References::staticType())), messageId);
if (!refStr.isEmpty()) {
msg->references()->fromUnicodeString(QString::fromLocal8Bit(refStr), "utf-8");
}
// In-Reply-To = original msg-id
msg->inReplyTo()->from7BitString(messageId);
const auto subjectHeader = part->header(KMime::Headers::Subject::staticType());
msg->subject()->fromUnicodeString(replySubject(asUnicodeString(subjectHeader)), "utf-8");
auto definedLocale = QLocale::system();
// Add quoted body
QString plainBody;
QString htmlBody;
// On $datetime you wrote:
auto dateHeader = static_cast<const KMime::Headers::Date *>(part->header(KMime::Headers::Date::staticType()));
const QDateTime date = dateHeader ? dateHeader->dateTime() : QDateTime{};
const auto dateTimeString =
QStringLiteral("%1 %2").arg(definedLocale.toString(date.date(), QLocale::LongFormat), definedLocale.toString(date.time(), QLocale::LongFormat));
const auto onDateYouWroteLine = i18nc("Reply header", "On %1 you wrote:\n", dateTimeString);
plainBody.append(onDateYouWroteLine);
htmlBody.append(plainToHtml(onDateYouWroteLine));
const auto plainTextContent = otp.plainTextContent();
const auto htmlContent = otp.htmlContent();
plainMessageText(plainTextContent, htmlContent, [=](const QString &body) {
QString result = stripSignature(body);
// Quoted body
result = quotedPlainText(result, fromHeader ? fromHeader->displayString() : QString{});
if (result.endsWith(u'\n')) {
result.chop(1);
}
// The plain body is complete
QString plainBodyResult = plainBody + result;
htmlMessageText(plainTextContent, htmlContent, [=](const QString &body, const QString &headElement) {
QString result = stripSignature(body);
// The html body is complete
const auto htmlBodyResult = [&]() {
if (!alwaysPlain) {
QString htmlBodyResult = htmlBody + quotedHtmlText(result);
makeValidHtml(htmlBodyResult, headElement);
return htmlBodyResult;
}
return QString{};
}();
// Assemble the message
- msg->contentType()->clear(); // to get rid of old boundary
+ msg->removeHeader<KMime::Headers::ContentType>(); // to get rid of old boundary
KMime::Content *const mainTextPart = htmlBodyResult.isEmpty() ? createPlainPartContent(plainBodyResult, msg.data())
: createMultipartAlternativeContent(plainBodyResult, htmlBodyResult, msg.data());
mainTextPart->assemble();
msg->setBody(mainTextPart->encodedBody());
msg->setHeader(mainTextPart->contentType());
msg->setHeader(mainTextPart->contentTransferEncoding());
// FIXME this does more harm than good right now.
msg->assemble();
callback(msg);
});
});
}
void MailTemplates::forward(const KMime::Message::Ptr &origMsg, const std::function<void(const KMime::Message::Ptr &result)> &callback)
{
MimeTreeParser::ObjectTreeParser otp;
otp.parseObjectTree(origMsg.data());
otp.decryptAndVerify();
KMime::Message::Ptr wrapperMsg(new KMime::Message);
- wrapperMsg->to()->clear();
- wrapperMsg->cc()->clear();
+ wrapperMsg->removeHeader<KMime::Headers::To>();
+ wrapperMsg->removeHeader<KMime::Headers::Cc>();
// Decrypt the original message, it will be encrypted again in the composer
// for the right recipient
KMime::Message::Ptr forwardedMessage(new KMime::Message());
if (isEncrypted(origMsg.data())) {
qDebug() << "Original message was encrypted, decrypting it";
auto htmlContent = otp.htmlContent();
KMime::Content *recreatedMsg =
htmlContent.isEmpty() ? createPlainPartContent(otp.plainTextContent()) : createMultipartAlternativeContent(otp.plainTextContent(), htmlContent);
KMime::Message::Ptr tmpForwardedMessage;
auto attachments = otp.collectAttachmentParts();
if (!attachments.isEmpty()) {
QVector<KMime::Content *> contents = {recreatedMsg};
for (const auto &attachment : std::as_const(attachments)) {
// Copy the node, to avoid deleting the parts node.
auto c = new KMime::Content;
c->setContent(attachment->node()->encodedContent());
c->parse();
contents.append(c);
}
auto msg = createMultipartMixedContent(contents);
tmpForwardedMessage.reset(KMime::contentToMessage(msg));
} else {
tmpForwardedMessage.reset(KMime::contentToMessage(recreatedMsg));
}
origMsg->contentType()->fromUnicodeString(tmpForwardedMessage->contentType()->asUnicodeString(), "utf-8");
origMsg->assemble();
forwardedMessage->setHead(origMsg->head());
forwardedMessage->setBody(tmpForwardedMessage->encodedBody());
forwardedMessage->parse();
} else {
qDebug() << "Original message was not encrypted, using it as-is";
forwardedMessage = origMsg;
}
auto partList = otp.collectContentParts();
if (partList.isEmpty()) {
Q_ASSERT(false);
callback({});
return;
}
auto part = partList[0];
Q_ASSERT(part);
const auto subjectHeader = part->header(KMime::Headers::Subject::staticType());
const auto subject = asUnicodeString(subjectHeader);
const QByteArray refStr =
getRefStr(as7BitString(part->header(KMime::Headers::References::staticType())), as7BitString(part->header(KMime::Headers::MessageID::staticType())));
wrapperMsg->subject()->fromUnicodeString(forwardSubject(subject), "utf-8");
if (!refStr.isEmpty()) {
wrapperMsg->references()->fromUnicodeString(QString::fromLocal8Bit(refStr), "utf-8");
}
KMime::Content *fwdAttachment = new KMime::Content;
fwdAttachment->contentDisposition()->setDisposition(KMime::Headers::CDinline);
fwdAttachment->contentType()->setMimeType("message/rfc822");
fwdAttachment->contentDisposition()->setFilename(subject + u".eml"_s);
fwdAttachment->setBody(KMime::CRLFtoLF(forwardedMessage->encodedContent(false)));
wrapperMsg->appendContent(fwdAttachment);
wrapperMsg->assemble();
callback(wrapperMsg);
}
QString MailTemplates::plaintextContent(const KMime::Message::Ptr &msg)
{
MimeTreeParser::ObjectTreeParser otp;
otp.parseObjectTree(msg.data());
otp.decryptAndVerify();
const auto plain = otp.plainTextContent();
if (plain.isEmpty()) {
// Maybe not as good as the webengine version, but works at least for simple html content
return toPlainText(otp.htmlContent());
}
return plain;
}
QString MailTemplates::body(const KMime::Message::Ptr &msg, bool &isHtml)
{
MimeTreeParser::ObjectTreeParser otp;
otp.parseObjectTree(msg.data());
otp.decryptAndVerify();
const auto html = otp.htmlContent();
if (html.isEmpty()) {
isHtml = false;
return otp.plainTextContent();
}
isHtml = true;
return html;
}
static KMime::Types::Mailbox::List stringListToMailboxes(const QStringList &list)
{
KMime::Types::Mailbox::List mailboxes;
for (const auto &s : list) {
KMime::Types::Mailbox mb;
mb.fromUnicodeString(s);
if (mb.hasAddress()) {
mailboxes << mb;
} else {
qWarning() << "Got an invalid address: " << s << list;
Q_ASSERT(false);
}
}
return mailboxes;
}
static void setRecipients(KMime::Message &message, const Recipients &recipients)
{
- message.to(true)->clear();
+ message.removeHeader<KMime::Headers::To>();
const auto tos = stringListToMailboxes(recipients.to);
for (const auto &mb : tos) {
message.to()->addAddress(mb);
}
- message.cc(true)->clear();
+ message.removeHeader<KMime::Headers::Cc>();
const auto ccs = stringListToMailboxes(recipients.cc);
for (const auto &mb : ccs) {
message.cc()->addAddress(mb);
}
- message.bcc(true)->clear();
+ message.removeHeader<KMime::Headers::Bcc>();
const auto bccs = stringListToMailboxes(recipients.bcc);
for (const auto &mb : bccs) {
message.bcc()->addAddress(mb);
}
}
KMime::Message::Ptr
MailTemplates::createIMipMessage(const QString &from, const Recipients &recipients, const QString &subject, const QString &body, const QString &attachment)
{
KMime::Message::Ptr message = KMime::Message::Ptr(new KMime::Message);
- message->contentTransferEncoding()->clear(); // 7Bit, decoded.
+ message->removeHeader<KMime::Headers::ContentTransferEncoding>(); // 7Bit, decoded.
// Set the headers
message->userAgent()->fromUnicodeString(
QStringLiteral("%1/%2(%3)").arg(QString::fromLocal8Bit("GPGOL.js")).arg(u"0.1"_s).arg(QSysInfo::prettyProductName()),
"utf-8");
message->from()->fromUnicodeString(from, "utf-8");
setRecipients(*message, recipients);
message->date()->setDateTime(QDateTime::currentDateTime());
message->subject()->fromUnicodeString(subject, "utf-8");
message->contentType()->setMimeType("multipart/alternative");
message->contentType()->setBoundary(KMime::multiPartBoundary());
// Set the first multipart, the body message.
KMime::Content *bodyMessage = new KMime::Content{message.data()};
bodyMessage->contentType()->setMimeType("text/plain");
bodyMessage->contentType()->setCharset("utf-8");
bodyMessage->contentTransferEncoding()->setEncoding(KMime::Headers::CEbase64);
bodyMessage->setBody(KMime::CRLFtoLF(body.toUtf8()));
message->appendContent(bodyMessage);
// Set the second multipart, the attachment.
KMime::Content *attachMessage = new KMime::Content{message.data()};
attachMessage->contentDisposition()->setDisposition(KMime::Headers::CDattachment);
attachMessage->contentType()->setMimeType("text/calendar");
attachMessage->contentType()->setCharset("utf-8");
attachMessage->contentType()->setName(QLatin1String("event.ics"), "utf-8");
attachMessage->contentType()->setParameter(QLatin1String("method"), QLatin1String("REPLY"));
attachMessage->contentTransferEncoding()->setEncoding(KMime::Headers::CEbase64);
attachMessage->setBody(KMime::CRLFtoLF(attachment.toUtf8()));
message->appendContent(attachMessage);
// Job done, attach the both multiparts and assemble the message.
message->assemble();
return message;
}

File Metadata

Mime Type
text/x-diff
Expires
Mon, Dec 23, 5:06 PM (5 h, 42 m)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
f4/75/2f540ed1e87dadcbce536f36ef3d

Event Timeline