File indexing completed on 2024-06-23 05:18:29

0001 /*
0002   SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
0003   SPDX-FileCopyrightText: 2010 Leo Franchi <lfranchi@kde.org>
0004   SPDX-FileCopyrightText: 2017-2024 Laurent Montel <montel@kde.org>
0005 
0006   SPDX-License-Identifier: GPL-2.0-or-later
0007 */
0008 
0009 #include "messagefactoryng.h"
0010 
0011 #include "draftstatus/draftstatus.h"
0012 #include "messagefactoryforwardjob.h"
0013 #include "messagefactoryreplyjob.h"
0014 #include "settings/messagecomposersettings.h"
0015 #include <MessageComposer/Util>
0016 
0017 #include <KCursorSaver>
0018 
0019 #include <KIdentityManagementCore/Identity>
0020 #include <KIdentityManagementCore/IdentityManager>
0021 
0022 #include "helper/messagehelper.h"
0023 #include "messagecomposer_debug.h"
0024 #include <KEmailAddress>
0025 #include <KLocalizedString>
0026 #include <KMime/DateFormatter>
0027 #include <MessageCore/MailingList>
0028 #include <MessageCore/StringUtil>
0029 #include <MessageCore/Util>
0030 #include <QRegularExpression>
0031 #include <QStringDecoder>
0032 #include <QStringEncoder>
0033 
0034 using namespace MessageComposer;
0035 
0036 namespace KMime
0037 {
0038 namespace Types
0039 {
0040 static bool operator==(const KMime::Types::Mailbox &left, const KMime::Types::Mailbox &right)
0041 {
0042     return left.addrSpec().asString() == right.addrSpec().asString();
0043 }
0044 }
0045 }
0046 
0047 /**
0048  * Strips all the user's addresses from an address list. This is used
0049  * when replying.
0050  */
0051 static KMime::Types::Mailbox::List stripMyAddressesFromAddressList(const KMime::Types::Mailbox::List &list,
0052                                                                    const KIdentityManagementCore::IdentityManager *manager)
0053 {
0054     KMime::Types::Mailbox::List addresses(list);
0055     for (KMime::Types::Mailbox::List::Iterator it = addresses.begin(); it != addresses.end();) {
0056         if (manager->thatIsMe(it->prettyAddress())) {
0057             it = addresses.erase(it);
0058         } else {
0059             ++it;
0060         }
0061     }
0062 
0063     return addresses;
0064 }
0065 
0066 MessageFactoryNG::MessageFactoryNG(const KMime::Message::Ptr &origMsg, Akonadi::Item::Id id, const Akonadi::Collection &col, QObject *parent)
0067     : QObject(parent)
0068     , mOrigMsg(origMsg)
0069     , mFolderId(0)
0070     , mParentFolderId(0)
0071     , mCollection(col)
0072     , mReplyStrategy(MessageComposer::ReplySmart)
0073     , mId(id)
0074 {
0075 }
0076 
0077 MessageFactoryNG::~MessageFactoryNG() = default;
0078 
0079 // Return the addresses to use when replying to the author of msg.
0080 // See <https://cr.yp.to/proto/replyto.html>.
0081 static KMime::Types::Mailbox::List authorMailboxes(const KMime::Message::Ptr &msg, const KMime::Types::Mailbox::List &mailingLists)
0082 {
0083     if (auto mrt = msg->headerByType("Mail-Reply-To")) {
0084         return KMime::Types::Mailbox::listFrom7BitString(mrt->as7BitString(false));
0085     }
0086     if (auto rt = msg->replyTo(false)) {
0087         // Did a mailing list munge Reply-To?
0088         auto mboxes = rt->mailboxes();
0089         for (const auto &list : mailingLists) {
0090             mboxes.removeAll(list);
0091         }
0092         if (!mboxes.isEmpty()) {
0093             return mboxes;
0094         }
0095     }
0096     return msg->from(true)->mailboxes();
0097 }
0098 
0099 void MessageFactoryNG::slotCreateReplyDone(const KMime::Message::Ptr &msg, bool replyAll)
0100 {
0101     MessageComposer::Util::addLinkInformation(msg, mId, Akonadi::MessageStatus::statusReplied());
0102     if (mParentFolderId > 0) {
0103         auto header = new KMime::Headers::Generic("X-KMail-Fcc");
0104         header->fromUnicodeString(QString::number(mParentFolderId), "utf-8");
0105         msg->setHeader(header);
0106     }
0107 
0108     if (DraftEncryptionState(mOrigMsg).encryptionState()) {
0109         DraftEncryptionState(msg).setState(true);
0110     }
0111     msg->assemble();
0112 
0113     MessageReply reply;
0114     reply.msg = msg;
0115     reply.replyAll = replyAll;
0116     Q_EMIT createReplyDone(reply);
0117 }
0118 
0119 void MessageFactoryNG::createReplyAsync()
0120 {
0121     KMime::Message::Ptr msg(new KMime::Message);
0122     QByteArray refStr;
0123     bool replyAll = true;
0124     KMime::Types::Mailbox::List toList;
0125     KMime::Types::Mailbox::List replyToList;
0126 
0127     const uint originalIdentity = identityUoid(mOrigMsg);
0128     MessageHelper::initFromMessage(msg, mOrigMsg, mIdentityManager, originalIdentity);
0129     replyToList = mOrigMsg->replyTo()->mailboxes();
0130 
0131     msg->contentType()->setCharset("utf-8");
0132 
0133     if (auto hdr = mOrigMsg->headerByType("List-Post")) {
0134         static const QRegularExpression rx{QStringLiteral("<\\s*mailto\\s*:([^@>]+@[^>]+)>"), QRegularExpression::CaseInsensitiveOption};
0135         const auto match = rx.match(hdr->asUnicodeString());
0136         if (match.hasMatch()) {
0137             KMime::Types::Mailbox mailbox;
0138             mailbox.fromUnicodeString(match.captured(1));
0139             mMailingListAddresses << mailbox;
0140         }
0141     }
0142 
0143     switch (mReplyStrategy) {
0144     case MessageComposer::ReplySmart: {
0145         if (auto hdr = mOrigMsg->headerByType("Mail-Followup-To")) {
0146             toList << KMime::Types::Mailbox::listFrom7BitString(hdr->as7BitString(false));
0147         } else if (!mMailingListAddresses.isEmpty()) {
0148             if (replyToList.isEmpty()) {
0149                 toList = (KMime::Types::Mailbox::List() << mMailingListAddresses.at(0));
0150             } else {
0151                 toList = replyToList;
0152             }
0153         } else {
0154             // Doesn't seem to be a mailing list.
0155             auto originalFromList = mOrigMsg->from()->mailboxes();
0156             auto originalToList = mOrigMsg->to()->mailboxes();
0157 
0158             if (mIdentityManager->thatIsMe(KMime::Types::Mailbox::listToUnicodeString(originalFromList))
0159                 && !mIdentityManager->thatIsMe(KMime::Types::Mailbox::listToUnicodeString(originalToList))) {
0160                 // Sender seems to be one of our own identities and recipient is not,
0161                 // so we assume that this is a reply to a "sent" mail where the user
0162                 // wants to add additional information for the recipient.
0163                 toList = originalToList;
0164             } else {
0165                 // "Normal" case:  reply to sender.
0166                 toList = authorMailboxes(mOrigMsg, mMailingListAddresses);
0167             }
0168 
0169             replyAll = false;
0170         }
0171         // strip all my addresses from the list of recipients
0172         const KMime::Types::Mailbox::List recipients = toList;
0173 
0174         toList = stripMyAddressesFromAddressList(recipients, mIdentityManager);
0175 
0176         // ... unless the list contains only my addresses (reply to self)
0177         if (toList.isEmpty() && !recipients.isEmpty()) {
0178             toList << recipients.first();
0179         }
0180         break;
0181     }
0182     case MessageComposer::ReplyList: {
0183         if (auto hdr = mOrigMsg->headerByType("Mail-Followup-To")) {
0184             KMime::Types::Mailbox mailbox;
0185             mailbox.from7BitString(hdr->as7BitString(false));
0186             toList << mailbox;
0187         } else if (!mMailingListAddresses.isEmpty()) {
0188             toList << mMailingListAddresses[0];
0189         } else if (!replyToList.isEmpty()) {
0190             // assume a Reply-To header mangling mailing list
0191             toList = replyToList;
0192         }
0193 
0194         // strip all my addresses from the list of recipients
0195         const KMime::Types::Mailbox::List recipients = toList;
0196 
0197         toList = stripMyAddressesFromAddressList(recipients, mIdentityManager);
0198         break;
0199     }
0200     case MessageComposer::ReplyAll:
0201         if (auto hdr = mOrigMsg->headerByType("Mail-Followup-To")) {
0202             toList = KMime::Types::Mailbox::listFrom7BitString(hdr->as7BitString(false));
0203         } else {
0204             auto ccList = stripMyAddressesFromAddressList(mOrigMsg->cc()->mailboxes(), mIdentityManager);
0205 
0206             if (!mMailingListAddresses.isEmpty()) {
0207                 toList = stripMyAddressesFromAddressList(mOrigMsg->to()->mailboxes(), mIdentityManager);
0208                 bool addMailingList = true;
0209                 for (const KMime::Types::Mailbox &m : std::as_const(mMailingListAddresses)) {
0210                     if (toList.contains(m)) {
0211                         addMailingList = false;
0212                         break;
0213                     }
0214                 }
0215                 if (addMailingList) {
0216                     toList += mMailingListAddresses.front();
0217                 }
0218 
0219                 ccList += authorMailboxes(mOrigMsg, mMailingListAddresses);
0220             } else {
0221                 // Doesn't seem to be a mailing list.
0222                 auto originalFromList = mOrigMsg->from()->mailboxes();
0223                 auto originalToList = mOrigMsg->to()->mailboxes();
0224                 if (mIdentityManager->thatIsMe(KMime::Types::Mailbox::listToUnicodeString(originalFromList))
0225                     && !mIdentityManager->thatIsMe(KMime::Types::Mailbox::listToUnicodeString(originalToList))) {
0226                     // Sender seems to be one of our own identities and recipient is not,
0227                     // so we assume that this is a reply to a "sent" mail where the user
0228                     // wants to add additional information for the recipient.
0229                     toList = originalToList;
0230                 } else {
0231                     // "Normal" case:  reply to sender.
0232                     toList = stripMyAddressesFromAddressList(mOrigMsg->to()->mailboxes(), mIdentityManager);
0233                     const auto authors = authorMailboxes(mOrigMsg, mMailingListAddresses);
0234                     if (toList.isEmpty() || !mIdentityManager->thatIsMe(KMime::Types::Mailbox::listToUnicodeString(authors))) {
0235                         toList += authors;
0236                     }
0237                 }
0238             }
0239 
0240             for (const KMime::Types::Mailbox &mailbox : std::as_const(ccList)) {
0241                 msg->cc()->addAddress(mailbox);
0242             }
0243         }
0244         break;
0245     case MessageComposer::ReplyAuthor:
0246         toList = authorMailboxes(mOrigMsg, mMailingListAddresses);
0247         replyAll = false;
0248         break;
0249     case MessageComposer::ReplyNone:
0250         // the addressees will be set by the caller
0251         break;
0252     default:
0253         Q_UNREACHABLE();
0254     }
0255 
0256     for (const KMime::Types::Mailbox &mailbox : std::as_const(toList)) {
0257         msg->to()->addAddress(mailbox);
0258     }
0259 
0260     refStr = getRefStr(mOrigMsg);
0261     if (!refStr.isEmpty()) {
0262         msg->references()->fromUnicodeString(QString::fromLocal8Bit(refStr), "utf-8");
0263     }
0264     // In-Reply-To = original msg-id
0265     msg->inReplyTo()->from7BitString(mOrigMsg->messageID()->as7BitString(false));
0266 
0267     msg->subject()->fromUnicodeString(MessageCore::StringUtil::replySubject(mOrigMsg.data()), "utf-8");
0268 
0269     // If the reply shouldn't be blank, apply the template to the message
0270     if (mQuote) {
0271         auto job = new MessageFactoryReplyJob;
0272         connect(job, &MessageFactoryReplyJob::replyDone, this, &MessageFactoryNG::slotCreateReplyDone);
0273         job->setMsg(msg);
0274         job->setReplyAll(replyAll);
0275         job->setIdentityManager(mIdentityManager);
0276         job->setSelection(mSelection);
0277         job->setTemplate(mTemplate);
0278         job->setOrigMsg(mOrigMsg);
0279         job->setCollection(mCollection);
0280         job->setReplyAsHtml(mReplyAsHtml);
0281         job->start();
0282     } else {
0283         slotCreateReplyDone(msg, replyAll);
0284     }
0285 }
0286 
0287 void MessageFactoryNG::slotCreateForwardDone(const KMime::Message::Ptr &msg)
0288 {
0289     MessageComposer::Util::addLinkInformation(msg, mId, Akonadi::MessageStatus::statusForwarded());
0290     msg->assemble();
0291     Q_EMIT createForwardDone(msg);
0292 }
0293 
0294 void MessageFactoryNG::createForwardAsync()
0295 {
0296     KMime::Message::Ptr msg(new KMime::Message);
0297 
0298     // This is a non-multipart, non-text mail (e.g. text/calendar). Construct
0299     // a multipart/mixed mail and add the original body as an attachment.
0300     if (!mOrigMsg->contentType()->isMultipart()
0301         && (!mOrigMsg->contentType(false)->isText()
0302             || (mOrigMsg->contentType(false)->isText() && mOrigMsg->contentType(false)->subType() != "html"
0303                 && mOrigMsg->contentType(false)->subType() != "plain"))) {
0304         const uint originalIdentity = identityUoid(mOrigMsg);
0305         MessageHelper::initFromMessage(msg, mOrigMsg, mIdentityManager, originalIdentity);
0306         msg->removeHeader<KMime::Headers::ContentType>();
0307         msg->removeHeader<KMime::Headers::ContentTransferEncoding>();
0308 
0309         msg->contentType(true)->setMimeType("multipart/mixed");
0310 
0311         // TODO: Andras: somebody should check if this is correct. :)
0312         // empty text part
0313         auto msgPart = new KMime::Content;
0314         msgPart->contentType()->setMimeType("text/plain");
0315         msg->appendContent(msgPart);
0316 
0317         // the old contents of the mail
0318         auto secondPart = new KMime::Content;
0319         secondPart->contentType()->setMimeType(mOrigMsg->contentType()->mimeType());
0320         secondPart->setBody(mOrigMsg->body());
0321         // use the headers of the original mail
0322         secondPart->setHead(mOrigMsg->head());
0323         msg->appendContent(secondPart);
0324         msg->assemble();
0325     }
0326     // Normal message (multipart or text/plain|html)
0327     // Just copy the message, the template parser will do the hard work of
0328     // replacing the body text in TemplateParser::addProcessedBodyToMessage()
0329     else {
0330         // TODO Check if this is ok
0331         msg->setHead(mOrigMsg->head());
0332         msg->setBody(mOrigMsg->body());
0333         const QString oldContentType = msg->contentType(true)->asUnicodeString();
0334         const uint originalIdentity = identityUoid(mOrigMsg);
0335         MessageHelper::initFromMessage(msg, mOrigMsg, mIdentityManager, originalIdentity);
0336 
0337         // restore the content type, MessageHelper::initFromMessage() sets the contents type to
0338         // text/plain, via initHeader(), for unclear reasons
0339         msg->contentType()->fromUnicodeString(oldContentType, "utf-8");
0340         msg->assemble();
0341     }
0342 
0343     msg->subject()->fromUnicodeString(MessageCore::StringUtil::forwardSubject(mOrigMsg.data()), "utf-8");
0344     auto job = new MessageFactoryForwardJob;
0345     connect(job, &MessageFactoryForwardJob::forwardDone, this, &MessageFactoryNG::slotCreateForwardDone);
0346     job->setIdentityManager(mIdentityManager);
0347     job->setMsg(msg);
0348     job->setSelection(mSelection);
0349     job->setTemplate(mTemplate);
0350     job->setOrigMsg(mOrigMsg);
0351     job->setCollection(mCollection);
0352     job->start();
0353 }
0354 
0355 QPair<KMime::Message::Ptr, QList<KMime::Content *>> MessageFactoryNG::createAttachedForward(const Akonadi::Item::List &items)
0356 {
0357     // create forwarded message with original message as attachment
0358     // remove headers that shouldn't be forwarded
0359     KMime::Message::Ptr msg(new KMime::Message);
0360     QList<KMime::Content *> attachments;
0361 
0362     const int numberOfItems(items.count());
0363     if (numberOfItems >= 2) {
0364         // don't respect X-KMail-Identity headers because they might differ for
0365         // the selected mails
0366         MessageHelper::initHeader(msg, mIdentityManager, 0);
0367     } else if (numberOfItems == 1) {
0368         KMime::Message::Ptr firstMsg = MessageComposer::Util::message(items.first());
0369         const uint originalIdentity = identityUoid(firstMsg);
0370         MessageHelper::initFromMessage(msg, firstMsg, mIdentityManager, originalIdentity);
0371         msg->subject()->fromUnicodeString(MessageCore::StringUtil::forwardSubject(firstMsg.data()), "utf-8");
0372     }
0373 
0374     MessageHelper::setAutomaticFields(msg, true);
0375     KCursorSaver saver(Qt::WaitCursor);
0376     if (numberOfItems == 0) {
0377         attachments << createForwardAttachmentMessage(mOrigMsg);
0378         MessageComposer::Util::addLinkInformation(msg, mId, Akonadi::MessageStatus::statusForwarded());
0379     } else {
0380         // iterate through all the messages to be forwarded
0381         attachments.reserve(items.count());
0382         for (const Akonadi::Item &item : std::as_const(items)) {
0383             attachments << createForwardAttachmentMessage(MessageComposer::Util::message(item));
0384             MessageComposer::Util::addLinkInformation(msg, item.id(), Akonadi::MessageStatus::statusForwarded());
0385         }
0386     }
0387 
0388     // msg->assemble();
0389     return QPair<KMime::Message::Ptr, QList<KMime::Content *>>(msg, QList<KMime::Content *>() << attachments);
0390 }
0391 
0392 KMime::Content *MessageFactoryNG::createForwardAttachmentMessage(const KMime::Message::Ptr &fwdMsg)
0393 {
0394     // remove headers that shouldn't be forwarded
0395     MessageCore::StringUtil::removePrivateHeaderFields(fwdMsg);
0396     fwdMsg->removeHeader<KMime::Headers::Bcc>();
0397     fwdMsg->assemble();
0398     // set the part
0399     auto msgPart = new KMime::Content(fwdMsg.data());
0400     auto ct = msgPart->contentType();
0401     ct->setMimeType("message/rfc822");
0402 
0403     auto cd = msgPart->contentDisposition(); // create
0404     cd->setParameter(QStringLiteral("filename"), i18n("forwarded message"));
0405     cd->setDisposition(KMime::Headers::CDinline);
0406     const QString subject = fwdMsg->subject()->asUnicodeString();
0407     ct->setParameter(QStringLiteral("name"), subject);
0408     cd->fromUnicodeString(fwdMsg->from()->asUnicodeString() + QLatin1StringView(": ") + subject, "utf-8");
0409     msgPart->setBody(fwdMsg->encodedContent());
0410     msgPart->assemble();
0411 
0412     MessageComposer::Util::addLinkInformation(fwdMsg, 0, Akonadi::MessageStatus::statusForwarded());
0413     return msgPart;
0414 }
0415 
0416 bool MessageFactoryNG::replyAsHtml() const
0417 {
0418     return mReplyAsHtml;
0419 }
0420 
0421 void MessageFactoryNG::setReplyAsHtml(bool replyAsHtml)
0422 {
0423     mReplyAsHtml = replyAsHtml;
0424 }
0425 
0426 KMime::Message::Ptr MessageFactoryNG::createResend()
0427 {
0428     KMime::Message::Ptr msg(new KMime::Message);
0429     msg->setContent(mOrigMsg->encodedContent());
0430     msg->parse();
0431     msg->removeHeader<KMime::Headers::MessageID>();
0432     uint originalIdentity = identityUoid(mOrigMsg);
0433 
0434     // Set the identity from above
0435     auto header = new KMime::Headers::Generic("X-KMail-Identity");
0436     header->fromUnicodeString(QString::number(originalIdentity), "utf-8");
0437     msg->setHeader(header);
0438 
0439     // Restore the original bcc field as this is overwritten in applyIdentity
0440     msg->bcc(mOrigMsg->bcc());
0441     return msg;
0442 }
0443 
0444 KMime::Message::Ptr
0445 MessageFactoryNG::createRedirect(const QString &toStr, const QString &ccStr, const QString &bccStr, int transportId, const QString &fcc, int identity)
0446 {
0447     if (!mOrigMsg) {
0448         return {};
0449     }
0450 
0451     // copy the message 1:1
0452     KMime::Message::Ptr msg(new KMime::Message);
0453     msg->setContent(mOrigMsg->encodedContent());
0454     msg->parse();
0455 
0456     uint id = identity;
0457     if (identity == -1) {
0458         if (auto hrd = msg->headerByType("X-KMail-Identity")) {
0459             const QString strId = hrd->asUnicodeString().trimmed();
0460             if (!strId.isEmpty()) {
0461                 id = strId.toUInt();
0462             }
0463         }
0464     }
0465     const KIdentityManagementCore::Identity &ident = mIdentityManager->identityForUoidOrDefault(id);
0466 
0467     // X-KMail-Redirect-From: content
0468     const QString strByWayOf =
0469         QString::fromLocal8Bit("%1 (by way of %2 <%3>)").arg(mOrigMsg->from()->asUnicodeString(), ident.fullName(), ident.primaryEmailAddress());
0470 
0471     // Resent-From: content
0472     const QString strFrom = QString::fromLocal8Bit("%1 <%2>").arg(ident.fullName(), ident.primaryEmailAddress());
0473 
0474     // format the current date to be used in Resent-Date:
0475     // FIXME: generate datetime the same way as KMime, otherwise we get inconsistency
0476     // in unit-tests. Unfortunately RFC2822Date is not enough for us, we need the
0477     // composition hack below
0478     const QDateTime dt = QDateTime::currentDateTime();
0479     const QString newDate = QLocale::c().toString(dt, QStringLiteral("ddd, ")) + dt.toString(Qt::RFC2822Date);
0480 
0481     // Clean up any resent headers
0482     msg->removeHeader("Resent-Cc");
0483     msg->removeHeader("Resent-Bcc");
0484     msg->removeHeader("Resent-Sender");
0485     // date, from to and id will be set anyway
0486 
0487     // prepend Resent-*: headers (c.f. RFC2822 3.6.6)
0488     QString msgIdSuffix;
0489     if (MessageComposer::MessageComposerSettings::useCustomMessageIdSuffix()) {
0490         msgIdSuffix = MessageComposer::MessageComposerSettings::customMsgIDSuffix();
0491     }
0492     auto header = new KMime::Headers::Generic("Resent-Message-ID");
0493     header->fromUnicodeString(MessageCore::StringUtil::generateMessageId(msg->sender()->asUnicodeString(), msgIdSuffix), "utf-8");
0494     msg->setHeader(header);
0495 
0496     header = new KMime::Headers::Generic("Resent-Date");
0497     header->fromUnicodeString(newDate, "utf-8");
0498     msg->setHeader(header);
0499 
0500     header = new KMime::Headers::Generic("Resent-From");
0501     header->fromUnicodeString(strFrom, "utf-8");
0502     msg->setHeader(header);
0503 
0504     if (msg->to(false)) {
0505         auto headerT = new KMime::Headers::To;
0506         headerT->fromUnicodeString(mOrigMsg->to()->asUnicodeString(), "utf-8");
0507         msg->setHeader(headerT);
0508     }
0509 
0510     header = new KMime::Headers::Generic("Resent-To");
0511     header->fromUnicodeString(toStr, "utf-8");
0512     msg->setHeader(header);
0513 
0514     if (!ccStr.isEmpty()) {
0515         header = new KMime::Headers::Generic("Resent-Cc");
0516         header->fromUnicodeString(ccStr, "utf-8");
0517         msg->setHeader(header);
0518     }
0519 
0520     if (!bccStr.isEmpty()) {
0521         header = new KMime::Headers::Generic("Resent-Bcc");
0522         header->fromUnicodeString(bccStr, "utf-8");
0523         msg->setHeader(header);
0524     }
0525 
0526     header = new KMime::Headers::Generic("X-KMail-Redirect-From");
0527     header->fromUnicodeString(strByWayOf, "utf-8");
0528     msg->setHeader(header);
0529 
0530     if (transportId != -1) {
0531         header = new KMime::Headers::Generic("X-KMail-Transport");
0532         header->fromUnicodeString(QString::number(transportId), "utf-8");
0533         msg->setHeader(header);
0534     }
0535 
0536     if (!fcc.isEmpty()) {
0537         header = new KMime::Headers::Generic("X-KMail-Fcc");
0538         header->fromUnicodeString(fcc, "utf-8");
0539         msg->setHeader(header);
0540     }
0541 
0542     const bool fccIsDisabled = ident.disabledFcc();
0543     if (fccIsDisabled) {
0544         header = new KMime::Headers::Generic("X-KMail-FccDisabled");
0545         header->fromUnicodeString(QStringLiteral("true"), "utf-8");
0546         msg->setHeader(header);
0547     } else {
0548         msg->removeHeader("X-KMail-FccDisabled");
0549     }
0550 
0551     msg->assemble();
0552 
0553     MessageComposer::Util::addLinkInformation(msg, mId, Akonadi::MessageStatus::statusForwarded());
0554     return msg;
0555 }
0556 
0557 KMime::Message::Ptr MessageFactoryNG::createDeliveryReceipt()
0558 {
0559     QString receiptTo;
0560     if (auto hrd = mOrigMsg->headerByType("Disposition-Notification-To")) {
0561         receiptTo = hrd->asUnicodeString();
0562     }
0563     if (receiptTo.trimmed().isEmpty()) {
0564         return {};
0565     }
0566     receiptTo.remove(QChar::fromLatin1('\n'));
0567 
0568     KMime::Message::Ptr receipt(new KMime::Message);
0569     const uint originalIdentity = identityUoid(mOrigMsg);
0570     MessageHelper::initFromMessage(receipt, mOrigMsg, mIdentityManager, originalIdentity);
0571     receipt->to()->fromUnicodeString(receiptTo, QStringLiteral("utf-8").toLatin1());
0572     receipt->subject()->fromUnicodeString(i18n("Receipt: ") + mOrigMsg->subject()->asUnicodeString(), "utf-8");
0573 
0574     QString str = QStringLiteral("Your message was successfully delivered.");
0575     str += QLatin1StringView("\n\n---------- Message header follows ----------\n");
0576     str += QString::fromLatin1(mOrigMsg->head());
0577     str += QLatin1StringView("--------------------------------------------\n");
0578     // Conversion to toLatin1 is correct here as Mail headers should contain
0579     // ascii only
0580     receipt->setBody(str.toLatin1());
0581     MessageHelper::setAutomaticFields(receipt);
0582     receipt->assemble();
0583 
0584     return receipt;
0585 }
0586 
0587 KMime::Message::Ptr MessageFactoryNG::createMDN(KMime::MDN::ActionMode a,
0588                                                 KMime::MDN::DispositionType d,
0589                                                 KMime::MDN::SendingMode s,
0590                                                 int mdnQuoteOriginal,
0591                                                 const QList<KMime::MDN::DispositionModifier> &m)
0592 {
0593     // extract where to send to:
0594     QString receiptTo;
0595     if (auto hrd = mOrigMsg->headerByType("Disposition-Notification-To")) {
0596         receiptTo = hrd->asUnicodeString();
0597     }
0598     if (receiptTo.trimmed().isEmpty()) {
0599         return KMime::Message::Ptr(new KMime::Message);
0600     }
0601     receiptTo.remove(QChar::fromLatin1('\n'));
0602 
0603     QString special; // fill in case of error, warning or failure
0604 
0605     // extract where to send from:
0606     QString finalRecipient = mIdentityManager->identityForUoidOrDefault(identityUoid(mOrigMsg)).fullEmailAddr();
0607 
0608     //
0609     // Generate message:
0610     //
0611 
0612     KMime::Message::Ptr receipt(new KMime::Message());
0613     const uint originalIdentity = identityUoid(mOrigMsg);
0614     MessageHelper::initFromMessage(receipt, mOrigMsg, mIdentityManager, originalIdentity);
0615     auto contentType = receipt->contentType(true); // create it
0616     contentType->from7BitString("multipart/report");
0617     contentType->setBoundary(KMime::multiPartBoundary());
0618     contentType->setCharset("us-ascii");
0619     receipt->removeHeader<KMime::Headers::ContentTransferEncoding>();
0620     // Modify the ContentType directly (replaces setAutomaticFields(true))
0621     contentType->setParameter(QStringLiteral("report-type"), QStringLiteral("disposition-notification"));
0622 
0623     const QString description = replaceHeadersInString(mOrigMsg, KMime::MDN::descriptionFor(d, m));
0624 
0625     // text/plain part:
0626     auto firstMsgPart = new KMime::Content(mOrigMsg.data());
0627     auto firstMsgPartContentType = firstMsgPart->contentType(); // create it
0628     firstMsgPartContentType->setMimeType("text/plain");
0629     firstMsgPartContentType->setCharset("utf-8");
0630     firstMsgPart->contentTransferEncoding(true)->setEncoding(KMime::Headers::CE7Bit);
0631     firstMsgPart->setBody(description.toUtf8());
0632     receipt->appendContent(firstMsgPart);
0633 
0634     // message/disposition-notification part:
0635     auto secondMsgPart = new KMime::Content(mOrigMsg.data());
0636     secondMsgPart->contentType()->setMimeType("message/disposition-notification");
0637 
0638     secondMsgPart->contentTransferEncoding()->setEncoding(KMime::Headers::CE7Bit);
0639     QByteArray originalRecipient = "";
0640     if (auto hrd = mOrigMsg->headerByType("Original-Recipient")) {
0641         originalRecipient = hrd->as7BitString(false);
0642     }
0643     secondMsgPart->setBody(KMime::MDN::dispositionNotificationBodyContent(finalRecipient,
0644                                                                           originalRecipient,
0645                                                                           mOrigMsg->messageID()->as7BitString(false), /* Message-ID */
0646                                                                           d,
0647                                                                           a,
0648                                                                           s,
0649                                                                           m,
0650                                                                           special));
0651     receipt->appendContent(secondMsgPart);
0652 
0653     if ((mdnQuoteOriginal < 0) || (mdnQuoteOriginal > 2)) {
0654         mdnQuoteOriginal = 0;
0655     }
0656     /* 0=> Nothing, 1=>Full Message, 2=>HeadersOnly*/
0657 
0658     auto thirdMsgPart = new KMime::Content(mOrigMsg.data());
0659     switch (mdnQuoteOriginal) {
0660     case 1:
0661         thirdMsgPart->contentType()->setMimeType("message/rfc822");
0662         thirdMsgPart->setBody(MessageCore::StringUtil::asSendableString(mOrigMsg));
0663         receipt->appendContent(thirdMsgPart);
0664         break;
0665     case 2:
0666         thirdMsgPart->contentType()->setMimeType("text/rfc822-headers");
0667         thirdMsgPart->setBody(MessageCore::StringUtil::headerAsSendableString(mOrigMsg));
0668         receipt->appendContent(thirdMsgPart);
0669         break;
0670     case 0:
0671     default:
0672         delete thirdMsgPart;
0673         break;
0674     }
0675 
0676     receipt->to()->fromUnicodeString(receiptTo, "utf-8");
0677     // Laurent: We don't translate subject ?
0678     receipt->subject()->from7BitString("Message Disposition Notification");
0679     auto header = new KMime::Headers::InReplyTo;
0680     header->fromUnicodeString(mOrigMsg->messageID()->asUnicodeString(), "utf-8");
0681     receipt->setHeader(header);
0682 
0683     receipt->references()->from7BitString(getRefStr(mOrigMsg));
0684 
0685     receipt->assemble();
0686 
0687     qCDebug(MESSAGECOMPOSER_LOG) << "final message:" + receipt->encodedContent();
0688 
0689     receipt->assemble();
0690     return receipt;
0691 }
0692 
0693 QPair<KMime::Message::Ptr, KMime::Content *> MessageFactoryNG::createForwardDigestMIME(const Akonadi::Item::List &items)
0694 {
0695     KMime::Message::Ptr msg(new KMime::Message);
0696     auto digest = new KMime::Content(msg.data());
0697 
0698     const QString mainPartText = i18n(
0699         "\nThis is a MIME digest forward. The content of the"
0700         " message is contained in the attachment(s).\n\n\n");
0701 
0702     auto ct = digest->contentType();
0703     ct->setMimeType("multipart/digest");
0704     ct->setBoundary(KMime::multiPartBoundary());
0705     digest->contentDescription()->fromUnicodeString(QStringLiteral("Digest of %1 messages.").arg(items.count()), "utf8");
0706     digest->contentDisposition()->setFilename(QStringLiteral("digest"));
0707     digest->fromUnicodeString(mainPartText);
0708 
0709     int id = 0;
0710     for (const Akonadi::Item &item : std::as_const(items)) {
0711         KMime::Message::Ptr fMsg = MessageComposer::Util::message(item);
0712         if (id == 0) {
0713             if (auto hrd = fMsg->headerByType("X-KMail-Identity")) {
0714                 id = hrd->asUnicodeString().toInt();
0715             }
0716         }
0717 
0718         MessageCore::StringUtil::removePrivateHeaderFields(fMsg);
0719         fMsg->removeHeader<KMime::Headers::Bcc>();
0720         fMsg->assemble();
0721         auto part = new KMime::Content(digest);
0722 
0723         part->contentType()->setMimeType("message/rfc822");
0724         part->contentType(false)->setCharset(fMsg->contentType()->charset());
0725         part->contentID()->setIdentifier(fMsg->contentID()->identifier());
0726         part->contentDescription()->fromUnicodeString(fMsg->contentDescription()->asUnicodeString(), "utf8");
0727         part->contentDisposition()->setParameter(QStringLiteral("name"), i18n("forwarded message"));
0728         part->fromUnicodeString(QString::fromLatin1(fMsg->encodedContent()));
0729         part->assemble();
0730         MessageComposer::Util::addLinkInformation(msg, item.id(), Akonadi::MessageStatus::statusForwarded());
0731         digest->appendContent(part);
0732     }
0733     digest->assemble();
0734 
0735     id = mFolderId;
0736     MessageHelper::initHeader(msg, mIdentityManager, id);
0737 
0738     //   qCDebug(MESSAGECOMPOSER_LOG) << "digest:" << digest->contents().size() << digest->encodedContent();
0739 
0740     return QPair<KMime::Message::Ptr, KMime::Content *>(msg, digest);
0741 }
0742 
0743 void MessageFactoryNG::setIdentityManager(KIdentityManagementCore::IdentityManager *ident)
0744 {
0745     mIdentityManager = ident;
0746 }
0747 
0748 void MessageFactoryNG::setReplyStrategy(MessageComposer::ReplyStrategy replyStrategy)
0749 {
0750     mReplyStrategy = replyStrategy;
0751 }
0752 
0753 void MessageFactoryNG::setSelection(const QString &selection)
0754 {
0755     mSelection = selection;
0756 }
0757 
0758 void MessageFactoryNG::setQuote(bool quote)
0759 {
0760     mQuote = quote;
0761 }
0762 
0763 void MessageFactoryNG::setTemplate(const QString &templ)
0764 {
0765     mTemplate = templ;
0766 }
0767 
0768 void MessageFactoryNG::setMailingListAddresses(const KMime::Types::Mailbox::List &listAddresses)
0769 {
0770     mMailingListAddresses << listAddresses;
0771 }
0772 
0773 void MessageFactoryNG::setFolderIdentity(uint folderIdentityId)
0774 {
0775     mFolderId = folderIdentityId;
0776 }
0777 
0778 void MessageFactoryNG::putRepliesInSameFolder(Akonadi::Collection::Id parentColId)
0779 {
0780     mParentFolderId = parentColId;
0781 }
0782 
0783 bool MessageFactoryNG::MDNRequested(const KMime::Message::Ptr &msg)
0784 {
0785     // extract where to send to:
0786     QString receiptTo;
0787     if (auto hrd = msg->headerByType("Disposition-Notification-To")) {
0788         receiptTo = hrd->asUnicodeString();
0789     }
0790     if (receiptTo.trimmed().isEmpty()) {
0791         return false;
0792     }
0793     receiptTo.remove(QChar::fromLatin1('\n'));
0794     return !receiptTo.isEmpty();
0795 }
0796 
0797 bool MessageFactoryNG::MDNConfirmMultipleRecipients(const KMime::Message::Ptr &msg)
0798 {
0799     // extract where to send to:
0800     QString receiptTo;
0801     if (auto hrd = msg->headerByType("Disposition-Notification-To")) {
0802         receiptTo = hrd->asUnicodeString();
0803     }
0804     if (receiptTo.trimmed().isEmpty()) {
0805         return false;
0806     }
0807     receiptTo.remove(QChar::fromLatin1('\n'));
0808 
0809     // RFC 2298: [ Confirmation from the user SHOULD be obtained (or no
0810     // MDN sent) ] if there is more than one distinct address in the
0811     // Disposition-Notification-To header.
0812     qCDebug(MESSAGECOMPOSER_LOG) << "KEmailAddress::splitAddressList(receiptTo):" << KEmailAddress::splitAddressList(receiptTo).join(QLatin1Char('\n'));
0813 
0814     return KEmailAddress::splitAddressList(receiptTo).count() > 1;
0815 }
0816 
0817 bool MessageFactoryNG::MDNReturnPathEmpty(const KMime::Message::Ptr &msg)
0818 {
0819     // extract where to send to:
0820     QString receiptTo;
0821     if (auto hrd = msg->headerByType("Disposition-Notification-To")) {
0822         receiptTo = hrd->asUnicodeString();
0823     }
0824     if (receiptTo.trimmed().isEmpty()) {
0825         return false;
0826     }
0827     receiptTo.remove(QChar::fromLatin1('\n'));
0828 
0829     // RFC 2298: MDNs SHOULD NOT be sent automatically if the address in
0830     // the Disposition-Notification-To header differs from the address
0831     // in the Return-Path header. [...] Confirmation from the user
0832     // SHOULD be obtained (or no MDN sent) if there is no Return-Path
0833     // header in the message [...]
0834     KMime::Types::AddrSpecList returnPathList = MessageHelper::extractAddrSpecs(msg, "Return-Path");
0835     const QString returnPath = returnPathList.isEmpty() ? QString() : returnPathList.front().localPart + QChar::fromLatin1('@') + returnPathList.front().domain;
0836     qCDebug(MESSAGECOMPOSER_LOG) << "clean return path:" << returnPath;
0837     return returnPath.isEmpty();
0838 }
0839 
0840 bool MessageFactoryNG::MDNReturnPathNotInRecieptTo(const KMime::Message::Ptr &msg)
0841 {
0842     // extract where to send to:
0843     QString receiptTo;
0844     if (auto hrd = msg->headerByType("Disposition-Notification-To")) {
0845         receiptTo = hrd->asUnicodeString();
0846     }
0847     if (receiptTo.trimmed().isEmpty()) {
0848         return false;
0849     }
0850     receiptTo.remove(QChar::fromLatin1('\n'));
0851 
0852     // RFC 2298: MDNs SHOULD NOT be sent automatically if the address in
0853     // the Disposition-Notification-To header differs from the address
0854     // in the Return-Path header. [...] Confirmation from the user
0855     // SHOULD be obtained (or no MDN sent) if there is no Return-Path
0856     // header in the message [...]
0857     KMime::Types::AddrSpecList returnPathList = MessageHelper::extractAddrSpecs(msg, QStringLiteral("Return-Path").toLatin1());
0858     const QString returnPath = returnPathList.isEmpty() ? QString() : returnPathList.front().localPart + QChar::fromLatin1('@') + returnPathList.front().domain;
0859     qCDebug(MESSAGECOMPOSER_LOG) << "clean return path:" << returnPath;
0860     return !receiptTo.contains(returnPath, Qt::CaseSensitive);
0861 }
0862 
0863 bool MessageFactoryNG::MDNMDNUnknownOption(const KMime::Message::Ptr &msg)
0864 {
0865     // RFC 2298: An importance of "required" indicates that
0866     // interpretation of the parameter is necessary for proper
0867     // generation of an MDN in response to this request.  If a UA does
0868     // not understand the meaning of the parameter, it MUST NOT generate
0869     // an MDN with any disposition type other than "failed" in response
0870     // to the request.
0871     QString notificationOptions;
0872     if (auto hrd = msg->headerByType("Disposition-Notification-Options")) {
0873         notificationOptions = hrd->asUnicodeString();
0874     }
0875     if (notificationOptions.contains(QLatin1StringView("required"), Qt::CaseSensitive)) {
0876         // ### hacky; should parse...
0877         // There is a required option that we don't understand. We need to
0878         // ask the user what we should do:
0879         return true;
0880     }
0881     return false;
0882 }
0883 
0884 uint MessageFactoryNG::identityUoid(const KMime::Message::Ptr &msg)
0885 {
0886     QString idString;
0887     if (auto hdr = msg->headerByType("X-KMail-Identity")) {
0888         idString = hdr->asUnicodeString().trimmed();
0889     }
0890     bool ok = false;
0891     uint id = idString.toUInt(&ok);
0892 
0893     if (!ok || id == 0) {
0894         id = MessageCore::Util::identityForMessage(msg.data(), mIdentityManager, mFolderId).uoid();
0895     }
0896     return id;
0897 }
0898 
0899 QString MessageFactoryNG::replaceHeadersInString(const KMime::Message::Ptr &msg, const QString &s)
0900 {
0901     QString result = s;
0902     static QRegularExpression rx{QStringLiteral("\\$\\{([a-z0-9-]+)\\}"), QRegularExpression::CaseInsensitiveOption};
0903 
0904     const QString sDate = KMime::DateFormatter::formatDate(KMime::DateFormatter::Localized, msg->date()->dateTime());
0905     qCDebug(MESSAGECOMPOSER_LOG) << "creating mdn date:" << msg->date()->dateTime().toSecsSinceEpoch() << sDate;
0906 
0907     result.replace(QStringLiteral("${date}"), sDate);
0908 
0909     int idx = 0;
0910     for (auto match = rx.match(result); match.hasMatch(); match = rx.match(result, idx)) {
0911         idx = match.capturedStart(0);
0912         const QByteArray ba = match.captured(1).toLatin1();
0913         if (auto hdr = msg->headerByType(ba.constData())) {
0914             const auto replacement = hdr->asUnicodeString();
0915             result.replace(idx, match.capturedLength(0), replacement);
0916             idx += replacement.length();
0917         } else {
0918             result.remove(idx, match.capturedLength(0));
0919         }
0920     }
0921     return result;
0922 }
0923 
0924 QByteArray MessageFactoryNG::getRefStr(const KMime::Message::Ptr &msg)
0925 {
0926     QByteArray firstRef;
0927     QByteArray lastRef;
0928     QByteArray refStr;
0929     QByteArray retRefStr;
0930     int i;
0931     int j;
0932 
0933     if (auto hdr = msg->references(false)) {
0934         refStr = hdr->as7BitString(false).trimmed();
0935     }
0936 
0937     if (refStr.isEmpty()) {
0938         return msg->messageID()->as7BitString(false);
0939     }
0940 
0941     i = refStr.indexOf('<');
0942     j = refStr.indexOf('>');
0943     firstRef = refStr.mid(i, j - i + 1);
0944     if (!firstRef.isEmpty()) {
0945         retRefStr = firstRef + ' ';
0946     }
0947 
0948     i = refStr.lastIndexOf('<');
0949     j = refStr.lastIndexOf('>');
0950 
0951     lastRef = refStr.mid(i, j - i + 1);
0952     if (!lastRef.isEmpty() && lastRef != firstRef) {
0953         retRefStr += lastRef + ' ';
0954     }
0955 
0956     retRefStr += msg->messageID()->as7BitString(false);
0957     return retRefStr;
0958 }
0959 
0960 #include "moc_messagefactoryng.cpp"