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"