Messagelib

messagefactoryng.cpp
1 /*
2  SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, [email protected]
3  SPDX-FileCopyrightText: 2010 Leo Franchi <[email protected]>
4  SPDX-FileCopyrightText: 2017-2022 Laurent Montel <[email protected]>
5 
6  SPDX-License-Identifier: GPL-2.0-or-later
7 */
8 
9 #include "messagefactoryng.h"
10 
11 #include "messagefactoryforwardjob.h"
12 #include "messagefactoryreplyjob.h"
13 #include "settings/messagecomposersettings.h"
14 #include "draftstatus/draftstatus.h"
15 #include <MessageComposer/Util>
16 
17 #include <KCursorSaver>
18 
19 #include <KIdentityManagement/Identity>
20 #include <KIdentityManagement/IdentityManager>
21 
22 #include "helper/messagehelper.h"
23 #include "messagecomposer_debug.h"
24 #include <KCharsets>
25 #include <KEmailAddress>
26 #include <KLocalizedString>
27 #include <KMime/DateFormatter>
28 #include <MessageCore/MailingList>
29 #include <MessageCore/StringUtil>
30 #include <MessageCore/Util>
31 #include <QRegularExpression>
32 #include <QTextCodec>
33 
34 using namespace MessageComposer;
35 
36 namespace KMime
37 {
38 namespace Types
39 {
40 static bool operator==(const KMime::Types::Mailbox &left, const KMime::Types::Mailbox &right)
41 {
42  return left.addrSpec().asString() == right.addrSpec().asString();
43 }
44 }
45 }
46 
47 /**
48  * Strips all the user's addresses from an address list. This is used
49  * when replying.
50  */
51 static KMime::Types::Mailbox::List stripMyAddressesFromAddressList(const KMime::Types::Mailbox::List &list, const KIdentityManagement::IdentityManager *manager)
52 {
53  KMime::Types::Mailbox::List addresses(list);
54  for (KMime::Types::Mailbox::List::Iterator it = addresses.begin(); it != addresses.end();) {
55  if (manager->thatIsMe(it->prettyAddress())) {
56  it = addresses.erase(it);
57  } else {
58  ++it;
59  }
60  }
61 
62  return addresses;
63 }
64 
65 MessageFactoryNG::MessageFactoryNG(const KMime::Message::Ptr &origMsg, Akonadi::Item::Id id, const Akonadi::Collection &col, QObject *parent)
66  : QObject(parent)
67  , mOrigMsg(origMsg)
68  , mFolderId(0)
69  , mParentFolderId(0)
70  , mCollection(col)
71  , mReplyStrategy(MessageComposer::ReplySmart)
72  , mId(id)
73 {
74 }
75 
76 MessageFactoryNG::~MessageFactoryNG() = default;
77 
78 // Return the addresses to use when replying to the author of msg.
79 // See <https://cr.yp.to/proto/replyto.html>.
80 static KMime::Types::Mailbox::List authorMailboxes(const KMime::Message::Ptr &msg, const KMime::Types::Mailbox::List &mailingLists)
81 {
82  if (auto mrt = msg->headerByType("Mail-Reply-To")) {
83  return KMime::Types::Mailbox::listFrom7BitString(mrt->as7BitString(false));
84  }
85  if (auto rt = msg->replyTo(false)) {
86  // Did a mailing list munge Reply-To?
87  auto mboxes = rt->mailboxes();
88  for (const auto &list : mailingLists) {
89  mboxes.removeAll(list);
90  }
91  if (!mboxes.isEmpty()) {
92  return mboxes;
93  }
94  }
95  return msg->from(true)->mailboxes();
96 }
97 
98 void MessageFactoryNG::slotCreateReplyDone(const KMime::Message::Ptr &msg, bool replyAll)
99 {
100  applyCharset(msg);
101 
102  MessageComposer::Util::addLinkInformation(msg, mId, Akonadi::MessageStatus::statusReplied());
103  if (mParentFolderId > 0) {
104  auto header = new KMime::Headers::Generic("X-KMail-Fcc");
105  header->fromUnicodeString(QString::number(mParentFolderId), "utf-8");
106  msg->setHeader(header);
107  }
108 
109  if (DraftEncryptionState(mOrigMsg).encryptionState()) {
110  DraftEncryptionState(msg).setState(true);
111  }
112  msg->assemble();
113 
114  MessageReply reply;
115  reply.msg = msg;
116  reply.replyAll = replyAll;
117  Q_EMIT createReplyDone(reply);
118 }
119 
120 void MessageFactoryNG::createReplyAsync()
121 {
123  QByteArray refStr;
124  bool replyAll = true;
125  KMime::Types::Mailbox::List toList;
126  KMime::Types::Mailbox::List replyToList;
127 
128  const uint originalIdentity = identityUoid(mOrigMsg);
129  MessageHelper::initFromMessage(msg, mOrigMsg, mIdentityManager, originalIdentity);
130  replyToList = mOrigMsg->replyTo()->mailboxes();
131 
132  msg->contentType()->setCharset("utf-8");
133 
134  if (auto hdr = mOrigMsg->headerByType("List-Post")) {
135  static const QRegularExpression rx{QStringLiteral("<\\s*mailto\\s*:([^@>][email protected][^>]+)>"), QRegularExpression::CaseInsensitiveOption};
136  const auto match = rx.match(hdr->asUnicodeString());
137  if (match.hasMatch()) {
138  KMime::Types::Mailbox mailbox;
139  mailbox.fromUnicodeString(match.captured(1));
140  mMailingListAddresses << mailbox;
141  }
142  }
143 
144  switch (mReplyStrategy) {
145  case MessageComposer::ReplySmart: {
146  if (auto hdr = mOrigMsg->headerByType("Mail-Followup-To")) {
147  toList << KMime::Types::Mailbox::listFrom7BitString(hdr->as7BitString(false));
148  } else if (!mMailingListAddresses.isEmpty()) {
149  if (replyToList.isEmpty()) {
150  toList = (KMime::Types::Mailbox::List() << mMailingListAddresses.at(0));
151  } else {
152  toList = replyToList;
153  }
154  } else {
155  // Doesn't seem to be a mailing list.
156  auto originalFromList = mOrigMsg->from()->mailboxes();
157  auto originalToList = mOrigMsg->to()->mailboxes();
158 
159  if (mIdentityManager->thatIsMe(KMime::Types::Mailbox::listToUnicodeString(originalFromList))
160  && !mIdentityManager->thatIsMe(KMime::Types::Mailbox::listToUnicodeString(originalToList))) {
161  // Sender seems to be one of our own identities and recipient is not,
162  // so we assume that this is a reply to a "sent" mail where the user
163  // wants to add additional information for the recipient.
164  toList = originalToList;
165  } else {
166  // "Normal" case: reply to sender.
167  toList = authorMailboxes(mOrigMsg, mMailingListAddresses);
168  }
169 
170  replyAll = false;
171  }
172  // strip all my addresses from the list of recipients
173  const KMime::Types::Mailbox::List recipients = toList;
174 
175  toList = stripMyAddressesFromAddressList(recipients, mIdentityManager);
176 
177  // ... unless the list contains only my addresses (reply to self)
178  if (toList.isEmpty() && !recipients.isEmpty()) {
179  toList << recipients.first();
180  }
181  break;
182  }
183  case MessageComposer::ReplyList: {
184  if (auto hdr = mOrigMsg->headerByType("Mail-Followup-To")) {
185  KMime::Types::Mailbox mailbox;
186  mailbox.from7BitString(hdr->as7BitString(false));
187  toList << mailbox;
188  } else if (!mMailingListAddresses.isEmpty()) {
189  toList << mMailingListAddresses[0];
190  } else if (!replyToList.isEmpty()) {
191  // assume a Reply-To header mangling mailing list
192  toList = replyToList;
193  }
194 
195  // strip all my addresses from the list of recipients
196  const KMime::Types::Mailbox::List recipients = toList;
197 
198  toList = stripMyAddressesFromAddressList(recipients, mIdentityManager);
199  break;
200  }
201  case MessageComposer::ReplyAll:
202  if (auto hdr = mOrigMsg->headerByType("Mail-Followup-To")) {
203  toList = KMime::Types::Mailbox::listFrom7BitString(hdr->as7BitString(false));
204  } else {
205  auto ccList = stripMyAddressesFromAddressList(mOrigMsg->cc()->mailboxes(), mIdentityManager);
206 
207  if (!mMailingListAddresses.isEmpty()) {
208  toList = stripMyAddressesFromAddressList(mOrigMsg->to()->mailboxes(), mIdentityManager);
209  bool addMailingList = true;
210  for (const KMime::Types::Mailbox &m : std::as_const(mMailingListAddresses)) {
211  if (toList.contains(m)) {
212  addMailingList = false;
213  break;
214  }
215  }
216  if (addMailingList) {
217  toList += mMailingListAddresses.front();
218  }
219 
220  ccList += authorMailboxes(mOrigMsg, mMailingListAddresses);
221  } else {
222  // Doesn't seem to be a mailing list.
223  auto originalFromList = mOrigMsg->from()->mailboxes();
224  auto originalToList = mOrigMsg->to()->mailboxes();
225 
226  if (mIdentityManager->thatIsMe(KMime::Types::Mailbox::listToUnicodeString(originalFromList))
227  && !mIdentityManager->thatIsMe(KMime::Types::Mailbox::listToUnicodeString(originalToList))) {
228  // Sender seems to be one of our own identities and recipient is not,
229  // so we assume that this is a reply to a "sent" mail where the user
230  // wants to add additional information for the recipient.
231  toList = originalToList;
232  } else {
233  // "Normal" case: reply to sender.
234  toList = stripMyAddressesFromAddressList(mOrigMsg->to()->mailboxes(), mIdentityManager);
235  toList += authorMailboxes(mOrigMsg, mMailingListAddresses);
236  }
237  }
238 
239  for (const KMime::Types::Mailbox &mailbox : std::as_const(ccList)) {
240  msg->cc()->addAddress(mailbox);
241  }
242  }
243  break;
244  case MessageComposer::ReplyAuthor:
245  toList = authorMailboxes(mOrigMsg, mMailingListAddresses);
246  replyAll = false;
247  break;
248  case MessageComposer::ReplyNone:
249  // the addressees will be set by the caller
250  break;
251  default:
252  Q_UNREACHABLE();
253  }
254 
255  for (const KMime::Types::Mailbox &mailbox : std::as_const(toList)) {
256  msg->to()->addAddress(mailbox);
257  }
258 
259  refStr = getRefStr(mOrigMsg);
260  if (!refStr.isEmpty()) {
261  msg->references()->fromUnicodeString(QString::fromLocal8Bit(refStr), "utf-8");
262  }
263  // In-Reply-To = original msg-id
264  msg->inReplyTo()->from7BitString(mOrigMsg->messageID()->as7BitString(false));
265 
266  msg->subject()->fromUnicodeString(MessageCore::StringUtil::replySubject(mOrigMsg.data()), "utf-8");
267 
268  // If the reply shouldn't be blank, apply the template to the message
269  if (mQuote) {
270  auto job = new MessageFactoryReplyJob;
271  connect(job, &MessageFactoryReplyJob::replyDone, this, &MessageFactoryNG::slotCreateReplyDone);
272  job->setMsg(msg);
273  job->setReplyAll(replyAll);
274  job->setIdentityManager(mIdentityManager);
275  job->setSelection(mSelection);
276  job->setTemplate(mTemplate);
277  job->setOrigMsg(mOrigMsg);
278  job->setCollection(mCollection);
279  job->setReplyAsHtml(mReplyAsHtml);
280  job->start();
281  } else {
282  slotCreateReplyDone(msg, replyAll);
283  }
284 }
285 
286 void MessageFactoryNG::slotCreateForwardDone(const KMime::Message::Ptr &msg)
287 {
288  applyCharset(msg);
289 
290  MessageComposer::Util::addLinkInformation(msg, mId, Akonadi::MessageStatus::statusForwarded());
291  msg->assemble();
292  Q_EMIT createForwardDone(msg);
293 }
294 
295 void MessageFactoryNG::createForwardAsync()
296 {
298 
299  // This is a non-multipart, non-text mail (e.g. text/calendar). Construct
300  // a multipart/mixed mail and add the original body as an attachment.
301  if (!mOrigMsg->contentType()->isMultipart()
302  && (!mOrigMsg->contentType(false)->isText()
303  || (mOrigMsg->contentType(false)->isText() && mOrigMsg->contentType(false)->subType() != "html"
304  && mOrigMsg->contentType(false)->subType() != "plain"))) {
305  const uint originalIdentity = identityUoid(mOrigMsg);
306  MessageHelper::initFromMessage(msg, mOrigMsg, mIdentityManager, originalIdentity);
307  msg->removeHeader<KMime::Headers::ContentType>();
308  msg->removeHeader<KMime::Headers::ContentTransferEncoding>();
309 
310  msg->contentType(true)->setMimeType("multipart/mixed");
311 
312  // TODO: Andras: somebody should check if this is correct. :)
313  // empty text part
314  auto msgPart = new KMime::Content;
315  msgPart->contentType()->setMimeType("text/plain");
316  msg->addContent(msgPart);
317 
318  // the old contents of the mail
319  auto secondPart = new KMime::Content;
320  secondPart->contentType()->setMimeType(mOrigMsg->contentType()->mimeType());
321  secondPart->setBody(mOrigMsg->body());
322  // use the headers of the original mail
323  secondPart->setHead(mOrigMsg->head());
324  msg->addContent(secondPart);
325  msg->assemble();
326  }
327  // Normal message (multipart or text/plain|html)
328  // Just copy the message, the template parser will do the hard work of
329  // replacing the body text in TemplateParser::addProcessedBodyToMessage()
330  else {
331  // TODO Check if this is ok
332  msg->setHead(mOrigMsg->head());
333  msg->setBody(mOrigMsg->body());
334  const QString oldContentType = msg->contentType(true)->asUnicodeString();
335  const uint originalIdentity = identityUoid(mOrigMsg);
336  MessageHelper::initFromMessage(msg, mOrigMsg, mIdentityManager, originalIdentity);
337 
338  // restore the content type, MessageHelper::initFromMessage() sets the contents type to
339  // text/plain, via initHeader(), for unclear reasons
340  msg->contentType()->fromUnicodeString(oldContentType, "utf-8");
341  msg->assemble();
342  }
343 
344  msg->subject()->fromUnicodeString(MessageCore::StringUtil::forwardSubject(mOrigMsg.data()), "utf-8");
345  auto job = new MessageFactoryForwardJob;
346  connect(job, &MessageFactoryForwardJob::forwardDone, this, &MessageFactoryNG::slotCreateForwardDone);
347  job->setIdentityManager(mIdentityManager);
348  job->setMsg(msg);
349  job->setSelection(mSelection);
350  job->setTemplate(mTemplate);
351  job->setOrigMsg(mOrigMsg);
352  job->setCollection(mCollection);
353  job->start();
354 }
355 
356 QPair<KMime::Message::Ptr, QVector<KMime::Content *>> MessageFactoryNG::createAttachedForward(const Akonadi::Item::List &items)
357 {
358  // create forwarded message with original message as attachment
359  // remove headers that shouldn't be forwarded
361  QVector<KMime::Content *> attachments;
362 
363  const int numberOfItems(items.count());
364  if (numberOfItems >= 2) {
365  // don't respect X-KMail-Identity headers because they might differ for
366  // the selected mails
367  MessageHelper::initHeader(msg, mIdentityManager, 0);
368  } else if (numberOfItems == 1) {
369  KMime::Message::Ptr firstMsg = MessageComposer::Util::message(items.first());
370  const uint originalIdentity = identityUoid(firstMsg);
371  MessageHelper::initFromMessage(msg, firstMsg, mIdentityManager, originalIdentity);
372  msg->subject()->fromUnicodeString(MessageCore::StringUtil::forwardSubject(firstMsg.data()), "utf-8");
373  }
374 
377  if (numberOfItems == 0) {
378  attachments << createForwardAttachmentMessage(mOrigMsg);
379  MessageComposer::Util::addLinkInformation(msg, mId, Akonadi::MessageStatus::statusForwarded());
380  } else {
381  // iterate through all the messages to be forwarded
382  attachments.reserve(items.count());
383  for (const Akonadi::Item &item : std::as_const(items)) {
384  attachments << createForwardAttachmentMessage(MessageComposer::Util::message(item));
385  MessageComposer::Util::addLinkInformation(msg, item.id(), Akonadi::MessageStatus::statusForwarded());
386  }
387  }
388 
389  applyCharset(msg);
390 
391  // msg->assemble();
393 }
394 
395 KMime::Content *MessageFactoryNG::createForwardAttachmentMessage(const KMime::Message::Ptr &fwdMsg)
396 {
397  // remove headers that shouldn't be forwarded
399  fwdMsg->removeHeader<KMime::Headers::Bcc>();
400  fwdMsg->assemble();
401  // set the part
402  auto msgPart = new KMime::Content(fwdMsg.data());
403  auto ct = msgPart->contentType();
404  ct->setMimeType("message/rfc822");
405 
406  auto cd = msgPart->contentDisposition(); // create
407  cd->setParameter(QStringLiteral("filename"), i18n("forwarded message"));
408  cd->setDisposition(KMime::Headers::CDinline);
409  const QString subject = fwdMsg->subject()->asUnicodeString();
410  ct->setParameter(QStringLiteral("name"), subject);
411  cd->fromUnicodeString(fwdMsg->from()->asUnicodeString() + QLatin1String(": ") + subject, "utf-8");
412  msgPart->setBody(fwdMsg->encodedContent());
413  msgPart->assemble();
414 
415  MessageComposer::Util::addLinkInformation(fwdMsg, 0, Akonadi::MessageStatus::statusForwarded());
416  return msgPart;
417 }
418 
419 bool MessageFactoryNG::replyAsHtml() const
420 {
421  return mReplyAsHtml;
422 }
423 
424 void MessageFactoryNG::setReplyAsHtml(bool replyAsHtml)
425 {
426  mReplyAsHtml = replyAsHtml;
427 }
428 
429 KMime::Message::Ptr MessageFactoryNG::createResend()
430 {
432  msg->setContent(mOrigMsg->encodedContent());
433  msg->parse();
434  msg->removeHeader<KMime::Headers::MessageID>();
435  uint originalIdentity = identityUoid(mOrigMsg);
436 
437  // Set the identity from above
438  auto header = new KMime::Headers::Generic("X-KMail-Identity");
439  header->fromUnicodeString(QString::number(originalIdentity), "utf-8");
440  msg->setHeader(header);
441 
442  // Restore the original bcc field as this is overwritten in applyIdentity
443  msg->bcc(mOrigMsg->bcc());
444  return msg;
445 }
446 
448 MessageFactoryNG::createRedirect(const QString &toStr, const QString &ccStr, const QString &bccStr, int transportId, const QString &fcc, int identity)
449 {
450  if (!mOrigMsg) {
451  return {};
452  }
453 
454  // copy the message 1:1
456  msg->setContent(mOrigMsg->encodedContent());
457  msg->parse();
458 
459  uint id = identity;
460  if (identity == -1) {
461  if (auto hrd = msg->headerByType("X-KMail-Identity")) {
462  const QString strId = hrd->asUnicodeString().trimmed();
463  if (!strId.isEmpty()) {
464  id = strId.toUInt();
465  }
466  }
467  }
468  const KIdentityManagement::Identity &ident = mIdentityManager->identityForUoidOrDefault(id);
469 
470  // X-KMail-Redirect-From: content
471  const QString strByWayOf =
472  QString::fromLocal8Bit("%1 (by way of %2 <%3>)").arg(mOrigMsg->from()->asUnicodeString(), ident.fullName(), ident.primaryEmailAddress());
473 
474  // Resent-From: content
475  const QString strFrom = QString::fromLocal8Bit("%1 <%2>").arg(ident.fullName(), ident.primaryEmailAddress());
476 
477  // format the current date to be used in Resent-Date:
478  // FIXME: generate datetime the same way as KMime, otherwise we get inconsistency
479  // in unit-tests. Unfortunately RFC2822Date is not enough for us, we need the
480  // composition hack below
482  const QString newDate = QLocale::c().toString(dt, QStringLiteral("ddd, ")) + dt.toString(Qt::RFC2822Date);
483 
484  // Clean up any resent headers
485  msg->removeHeader("Resent-Cc");
486  msg->removeHeader("Resent-Bcc");
487  msg->removeHeader("Resent-Sender");
488  // date, from to and id will be set anyway
489 
490  // prepend Resent-*: headers (c.f. RFC2822 3.6.6)
491  QString msgIdSuffix;
492  if (MessageComposer::MessageComposerSettings::useCustomMessageIdSuffix()) {
493  msgIdSuffix = MessageComposer::MessageComposerSettings::customMsgIDSuffix();
494  }
495  auto header = new KMime::Headers::Generic("Resent-Message-ID");
496  header->fromUnicodeString(MessageCore::StringUtil::generateMessageId(msg->sender()->asUnicodeString(), msgIdSuffix), "utf-8");
497  msg->setHeader(header);
498 
499  header = new KMime::Headers::Generic("Resent-Date");
500  header->fromUnicodeString(newDate, "utf-8");
501  msg->setHeader(header);
502 
503  header = new KMime::Headers::Generic("Resent-From");
504  header->fromUnicodeString(strFrom, "utf-8");
505  msg->setHeader(header);
506 
507  if (msg->to(false)) {
508  auto headerT = new KMime::Headers::To;
509  headerT->fromUnicodeString(mOrigMsg->to()->asUnicodeString(), "utf-8");
510  msg->setHeader(headerT);
511  }
512 
513  header = new KMime::Headers::Generic("Resent-To");
514  header->fromUnicodeString(toStr, "utf-8");
515  msg->setHeader(header);
516 
517  if (!ccStr.isEmpty()) {
518  header = new KMime::Headers::Generic("Resent-Cc");
519  header->fromUnicodeString(ccStr, "utf-8");
520  msg->setHeader(header);
521  }
522 
523  if (!bccStr.isEmpty()) {
524  header = new KMime::Headers::Generic("Resent-Bcc");
525  header->fromUnicodeString(bccStr, "utf-8");
526  msg->setHeader(header);
527  }
528 
529  header = new KMime::Headers::Generic("X-KMail-Redirect-From");
530  header->fromUnicodeString(strByWayOf, "utf-8");
531  msg->setHeader(header);
532 
533  if (transportId != -1) {
534  header = new KMime::Headers::Generic("X-KMail-Transport");
535  header->fromUnicodeString(QString::number(transportId), "utf-8");
536  msg->setHeader(header);
537  }
538 
539  if (!fcc.isEmpty()) {
540  header = new KMime::Headers::Generic("X-KMail-Fcc");
541  header->fromUnicodeString(fcc, "utf-8");
542  msg->setHeader(header);
543  }
544 
545  const bool fccIsDisabled = ident.disabledFcc();
546  if (fccIsDisabled) {
547  header = new KMime::Headers::Generic("X-KMail-FccDisabled");
548  header->fromUnicodeString(QStringLiteral("true"), "utf-8");
549  msg->setHeader(header);
550  } else {
551  msg->removeHeader("X-KMail-FccDisabled");
552  }
553 
554  msg->assemble();
555 
556  MessageComposer::Util::addLinkInformation(msg, mId, Akonadi::MessageStatus::statusForwarded());
557  return msg;
558 }
559 
560 KMime::Message::Ptr MessageFactoryNG::createDeliveryReceipt()
561 {
562  QString receiptTo;
563  if (auto hrd = mOrigMsg->headerByType("Disposition-Notification-To")) {
564  receiptTo = hrd->asUnicodeString();
565  }
566  if (receiptTo.trimmed().isEmpty()) {
567  return {};
568  }
569  receiptTo.remove(QChar::fromLatin1('\n'));
570 
571  KMime::Message::Ptr receipt(new KMime::Message);
572  const uint originalIdentity = identityUoid(mOrigMsg);
573  MessageHelper::initFromMessage(receipt, mOrigMsg, mIdentityManager, originalIdentity);
574  receipt->to()->fromUnicodeString(receiptTo, QStringLiteral("utf-8").toLatin1());
575  receipt->subject()->fromUnicodeString(i18n("Receipt: ") + mOrigMsg->subject()->asUnicodeString(), "utf-8");
576 
577  QString str = QStringLiteral("Your message was successfully delivered.");
578  str += QLatin1String("\n\n---------- Message header follows ----------\n");
579  str += QString::fromLatin1(mOrigMsg->head());
580  str += QLatin1String("--------------------------------------------\n");
581  // Conversion to toLatin1 is correct here as Mail headers should contain
582  // ascii only
583  receipt->setBody(str.toLatin1());
585  receipt->assemble();
586 
587  return receipt;
588 }
589 
590 KMime::Message::Ptr MessageFactoryNG::createMDN(KMime::MDN::ActionMode a,
591  KMime::MDN::DispositionType d,
592  KMime::MDN::SendingMode s,
593  int mdnQuoteOriginal,
595 {
596  // extract where to send to:
597  QString receiptTo;
598  if (auto hrd = mOrigMsg->headerByType("Disposition-Notification-To")) {
599  receiptTo = hrd->asUnicodeString();
600  }
601  if (receiptTo.trimmed().isEmpty()) {
603  }
604  receiptTo.remove(QChar::fromLatin1('\n'));
605 
606  QString special; // fill in case of error, warning or failure
607 
608  // extract where to send from:
609  QString finalRecipient = mIdentityManager->identityForUoidOrDefault(identityUoid(mOrigMsg)).fullEmailAddr();
610 
611  //
612  // Generate message:
613  //
614 
615  KMime::Message::Ptr receipt(new KMime::Message());
616  const uint originalIdentity = identityUoid(mOrigMsg);
617  MessageHelper::initFromMessage(receipt, mOrigMsg, mIdentityManager, originalIdentity);
618  auto contentType = receipt->contentType(true); // create it
619  contentType->from7BitString("multipart/report");
620  contentType->setBoundary(KMime::multiPartBoundary());
621  contentType->setCharset("us-ascii");
622  receipt->removeHeader<KMime::Headers::ContentTransferEncoding>();
623  // Modify the ContentType directly (replaces setAutomaticFields(true))
624  contentType->setParameter(QStringLiteral("report-type"), QStringLiteral("disposition-notification"));
625 
626  const QString description = replaceHeadersInString(mOrigMsg, KMime::MDN::descriptionFor(d, m));
627 
628  // text/plain part:
629  auto firstMsgPart = new KMime::Content(mOrigMsg.data());
630  auto firstMsgPartContentType = firstMsgPart->contentType(); // create it
631  firstMsgPartContentType->setMimeType("text/plain");
632  firstMsgPartContentType->setCharset("utf-8");
633  firstMsgPart->contentTransferEncoding(true)->setEncoding(KMime::Headers::CE7Bit);
634  firstMsgPart->setBody(description.toUtf8());
635  receipt->addContent(firstMsgPart);
636 
637  // message/disposition-notification part:
638  auto secondMsgPart = new KMime::Content(mOrigMsg.data());
639  secondMsgPart->contentType()->setMimeType("message/disposition-notification");
640 
641  secondMsgPart->contentTransferEncoding()->setEncoding(KMime::Headers::CE7Bit);
642  QByteArray originalRecipient = "";
643  if (auto hrd = mOrigMsg->headerByType("Original-Recipient")) {
644  originalRecipient = hrd->as7BitString(false);
645  }
646  secondMsgPart->setBody(KMime::MDN::dispositionNotificationBodyContent(finalRecipient,
647  originalRecipient,
648  mOrigMsg->messageID()->as7BitString(false), /* Message-ID */
649  d,
650  a,
651  s,
652  m,
653  special));
654  receipt->addContent(secondMsgPart);
655 
656  if ((mdnQuoteOriginal < 0) || (mdnQuoteOriginal > 2)) {
657  mdnQuoteOriginal = 0;
658  }
659  /* 0=> Nothing, 1=>Full Message, 2=>HeadersOnly*/
660 
661  auto thirdMsgPart = new KMime::Content(mOrigMsg.data());
662  switch (mdnQuoteOriginal) {
663  case 1:
664  thirdMsgPart->contentType()->setMimeType("message/rfc822");
665  thirdMsgPart->setBody(MessageCore::StringUtil::asSendableString(mOrigMsg));
666  receipt->addContent(thirdMsgPart);
667  break;
668  case 2:
669  thirdMsgPart->contentType()->setMimeType("text/rfc822-headers");
670  thirdMsgPart->setBody(MessageCore::StringUtil::headerAsSendableString(mOrigMsg));
671  receipt->addContent(thirdMsgPart);
672  break;
673  case 0:
674  default:
675  delete thirdMsgPart;
676  break;
677  }
678 
679  receipt->to()->fromUnicodeString(receiptTo, "utf-8");
680  // Laurent: We don't translate subject ?
681  receipt->subject()->from7BitString("Message Disposition Notification");
682  auto header = new KMime::Headers::InReplyTo;
683  header->fromUnicodeString(mOrigMsg->messageID()->asUnicodeString(), "utf-8");
684  receipt->setHeader(header);
685 
686  receipt->references()->from7BitString(getRefStr(mOrigMsg));
687 
688  receipt->assemble();
689 
690  qCDebug(MESSAGECOMPOSER_LOG) << "final message:" + receipt->encodedContent();
691 
692  receipt->assemble();
693  return receipt;
694 }
695 
696 QPair<KMime::Message::Ptr, KMime::Content *> MessageFactoryNG::createForwardDigestMIME(const Akonadi::Item::List &items)
697 {
699  auto digest = new KMime::Content(msg.data());
700 
701  const QString mainPartText = i18n(
702  "\nThis is a MIME digest forward. The content of the"
703  " message is contained in the attachment(s).\n\n\n");
704 
705  auto ct = digest->contentType();
706  ct->setMimeType("multipart/digest");
707  ct->setBoundary(KMime::multiPartBoundary());
708  digest->contentDescription()->fromUnicodeString(QStringLiteral("Digest of %1 messages.").arg(items.count()), "utf8");
709  digest->contentDisposition()->setFilename(QStringLiteral("digest"));
710  digest->fromUnicodeString(mainPartText);
711 
712  int id = 0;
713  for (const Akonadi::Item &item : std::as_const(items)) {
714  KMime::Message::Ptr fMsg = MessageComposer::Util::message(item);
715  if (id == 0) {
716  if (auto hrd = fMsg->headerByType("X-KMail-Identity")) {
717  id = hrd->asUnicodeString().toInt();
718  }
719  }
720 
722  fMsg->removeHeader<KMime::Headers::Bcc>();
723  fMsg->assemble();
724  auto part = new KMime::Content(digest);
725 
726  part->contentType()->setMimeType("message/rfc822");
727  part->contentType(false)->setCharset(fMsg->contentType()->charset());
728  part->contentID()->setIdentifier(fMsg->contentID()->identifier());
729  part->contentDescription()->fromUnicodeString(fMsg->contentDescription()->asUnicodeString(), "utf8");
730  part->contentDisposition()->setParameter(QStringLiteral("name"), i18n("forwarded message"));
731  part->fromUnicodeString(QString::fromLatin1(fMsg->encodedContent()));
732  part->assemble();
733  MessageComposer::Util::addLinkInformation(msg, item.id(), Akonadi::MessageStatus::statusForwarded());
734  digest->addContent(part);
735  }
736  digest->assemble();
737 
738  id = mFolderId;
739  MessageHelper::initHeader(msg, mIdentityManager, id);
740 
741  // qCDebug(MESSAGECOMPOSER_LOG) << "digest:" << digest->contents().size() << digest->encodedContent();
742 
744 }
745 
746 void MessageFactoryNG::setIdentityManager(KIdentityManagement::IdentityManager *ident)
747 {
748  mIdentityManager = ident;
749 }
750 
751 void MessageFactoryNG::setReplyStrategy(MessageComposer::ReplyStrategy replyStrategy)
752 {
753  mReplyStrategy = replyStrategy;
754 }
755 
756 void MessageFactoryNG::setSelection(const QString &selection)
757 {
758  mSelection = selection;
759 }
760 
761 void MessageFactoryNG::setQuote(bool quote)
762 {
763  mQuote = quote;
764 }
765 
766 void MessageFactoryNG::setTemplate(const QString &templ)
767 {
768  mTemplate = templ;
769 }
770 
771 void MessageFactoryNG::setMailingListAddresses(const KMime::Types::Mailbox::List &listAddresses)
772 {
773  mMailingListAddresses << listAddresses;
774 }
775 
776 void MessageFactoryNG::setFolderIdentity(uint folderIdentityId)
777 {
778  mFolderId = folderIdentityId;
779 }
780 
781 void MessageFactoryNG::putRepliesInSameFolder(Akonadi::Collection::Id parentColId)
782 {
783  mParentFolderId = parentColId;
784 }
785 
786 bool MessageFactoryNG::MDNRequested(const KMime::Message::Ptr &msg)
787 {
788  // extract where to send to:
789  QString receiptTo;
790  if (auto hrd = msg->headerByType("Disposition-Notification-To")) {
791  receiptTo = hrd->asUnicodeString();
792  }
793  if (receiptTo.trimmed().isEmpty()) {
794  return false;
795  }
796  receiptTo.remove(QChar::fromLatin1('\n'));
797  return !receiptTo.isEmpty();
798 }
799 
800 bool MessageFactoryNG::MDNConfirmMultipleRecipients(const KMime::Message::Ptr &msg)
801 {
802  // extract where to send to:
803  QString receiptTo;
804  if (auto hrd = msg->headerByType("Disposition-Notification-To")) {
805  receiptTo = hrd->asUnicodeString();
806  }
807  if (receiptTo.trimmed().isEmpty()) {
808  return false;
809  }
810  receiptTo.remove(QChar::fromLatin1('\n'));
811 
812  // RFC 2298: [ Confirmation from the user SHOULD be obtained (or no
813  // MDN sent) ] if there is more than one distinct address in the
814  // Disposition-Notification-To header.
815  qCDebug(MESSAGECOMPOSER_LOG) << "KEmailAddress::splitAddressList(receiptTo):" << KEmailAddress::splitAddressList(receiptTo).join(QLatin1Char('\n'));
816 
817  return KEmailAddress::splitAddressList(receiptTo).count() > 1;
818 }
819 
820 bool MessageFactoryNG::MDNReturnPathEmpty(const KMime::Message::Ptr &msg)
821 {
822  // extract where to send to:
823  QString receiptTo;
824  if (auto hrd = msg->headerByType("Disposition-Notification-To")) {
825  receiptTo = hrd->asUnicodeString();
826  }
827  if (receiptTo.trimmed().isEmpty()) {
828  return false;
829  }
830  receiptTo.remove(QChar::fromLatin1('\n'));
831 
832  // RFC 2298: MDNs SHOULD NOT be sent automatically if the address in
833  // the Disposition-Notification-To header differs from the address
834  // in the Return-Path header. [...] Confirmation from the user
835  // SHOULD be obtained (or no MDN sent) if there is no Return-Path
836  // header in the message [...]
837  KMime::Types::AddrSpecList returnPathList = MessageHelper::extractAddrSpecs(msg, "Return-Path");
838  const QString returnPath = returnPathList.isEmpty() ? QString() : returnPathList.front().localPart + QChar::fromLatin1('@') + returnPathList.front().domain;
839  qCDebug(MESSAGECOMPOSER_LOG) << "clean return path:" << returnPath;
840  return returnPath.isEmpty();
841 }
842 
843 bool MessageFactoryNG::MDNReturnPathNotInRecieptTo(const KMime::Message::Ptr &msg)
844 {
845  // extract where to send to:
846  QString receiptTo;
847  if (auto hrd = msg->headerByType("Disposition-Notification-To")) {
848  receiptTo = hrd->asUnicodeString();
849  }
850  if (receiptTo.trimmed().isEmpty()) {
851  return false;
852  }
853  receiptTo.remove(QChar::fromLatin1('\n'));
854 
855  // RFC 2298: MDNs SHOULD NOT be sent automatically if the address in
856  // the Disposition-Notification-To header differs from the address
857  // in the Return-Path header. [...] Confirmation from the user
858  // SHOULD be obtained (or no MDN sent) if there is no Return-Path
859  // header in the message [...]
860  KMime::Types::AddrSpecList returnPathList = MessageHelper::extractAddrSpecs(msg, QStringLiteral("Return-Path").toLatin1());
861  const QString returnPath = returnPathList.isEmpty() ? QString() : returnPathList.front().localPart + QChar::fromLatin1('@') + returnPathList.front().domain;
862  qCDebug(MESSAGECOMPOSER_LOG) << "clean return path:" << returnPath;
863  return !receiptTo.contains(returnPath, Qt::CaseSensitive);
864 }
865 
866 bool MessageFactoryNG::MDNMDNUnknownOption(const KMime::Message::Ptr &msg)
867 {
868  // RFC 2298: An importance of "required" indicates that
869  // interpretation of the parameter is necessary for proper
870  // generation of an MDN in response to this request. If a UA does
871  // not understand the meaning of the parameter, it MUST NOT generate
872  // an MDN with any disposition type other than "failed" in response
873  // to the request.
874  QString notificationOptions;
875  if (auto hrd = msg->headerByType("Disposition-Notification-Options")) {
876  notificationOptions = hrd->asUnicodeString();
877  }
878  if (notificationOptions.contains(QLatin1String("required"), Qt::CaseSensitive)) {
879  // ### hacky; should parse...
880  // There is a required option that we don't understand. We need to
881  // ask the user what we should do:
882  return true;
883  }
884  return false;
885 }
886 
887 uint MessageFactoryNG::identityUoid(const KMime::Message::Ptr &msg)
888 {
889  QString idString;
890  if (auto hdr = msg->headerByType("X-KMail-Identity")) {
891  idString = hdr->asUnicodeString().trimmed();
892  }
893  bool ok = false;
894  uint id = idString.toUInt(&ok);
895 
896  if (!ok || id == 0) {
897  id = MessageCore::Util::identityForMessage(msg.data(), mIdentityManager, mFolderId).uoid();
898  }
899  return id;
900 }
901 
902 QString MessageFactoryNG::replaceHeadersInString(const KMime::Message::Ptr &msg, const QString &s)
903 {
904  QString result = s;
905  static QRegularExpression rx{QStringLiteral("\\$\\{([a-z0-9-]+)\\}"), QRegularExpression::CaseInsensitiveOption};
906 
907  const QString sDate = KMime::DateFormatter::formatDate(KMime::DateFormatter::Localized, msg->date()->dateTime().toSecsSinceEpoch());
908  qCDebug(MESSAGECOMPOSER_LOG) << "creating mdn date:" << msg->date()->dateTime().toSecsSinceEpoch() << sDate;
909 
910  result.replace(QStringLiteral("${date}"), sDate);
911 
912  int idx = 0;
913  for (auto match = rx.match(result); match.hasMatch(); match = rx.match(result, idx)) {
914  idx = match.capturedStart(0);
915  const QByteArray ba = match.captured(1).toLatin1();
916  if (auto hdr = msg->headerByType(ba.constData())) {
917  const auto replacement = hdr->asUnicodeString();
918  result.replace(idx, match.capturedLength(0), replacement);
919  idx += replacement.length();
920  } else {
921  result.remove(idx, match.capturedLength(0));
922  }
923  }
924  return result;
925 }
926 
927 void MessageFactoryNG::applyCharset(const KMime::Message::Ptr msg)
928 {
929  if (MessageComposer::MessageComposerSettings::forceReplyCharset()) {
930  // first convert the body from its current encoding to unicode representation
931  QTextCodec *bodyCodec = KCharsets::charsets()->codecForName(QString::fromLatin1(msg->contentType()->charset()));
932  if (!bodyCodec) {
933  bodyCodec = KCharsets::charsets()->codecForName(QStringLiteral("UTF-8"));
934  }
935 
936  const QString body = bodyCodec->toUnicode(msg->body());
937 
938  // then apply the encoding of the original message
939  msg->contentType()->setCharset(mOrigMsg->contentType()->charset());
940 
941  QTextCodec *codec = KCharsets::charsets()->codecForName(QString::fromLatin1(msg->contentType()->charset()));
942  if (!codec) {
943  qCCritical(MESSAGECOMPOSER_LOG) << "Could not get text codec for charset" << msg->contentType()->charset();
944  } else if (!codec->canEncode(body)) { // charset can't encode body, fall back to preferred
945  const QStringList charsets = MessageComposer::MessageComposerSettings::preferredCharsets();
946 
947  QVector<QByteArray> chars;
948  chars.reserve(charsets.count());
949  for (const QString &charset : charsets) {
950  chars << charset.toLatin1();
951  }
952 
953  QByteArray fallbackCharset = MessageComposer::Util::selectCharset(chars, body);
954  if (fallbackCharset.isEmpty()) { // UTF-8 as fall-through
955  fallbackCharset = "UTF-8";
956  }
957 
958  codec = KCharsets::charsets()->codecForName(QString::fromLatin1(fallbackCharset));
959  msg->setBody(codec->fromUnicode(body));
960  } else {
961  msg->setBody(codec->fromUnicode(body));
962  }
963  }
964 }
965 
966 QByteArray MessageFactoryNG::getRefStr(const KMime::Message::Ptr &msg)
967 {
968  QByteArray firstRef;
969  QByteArray lastRef;
970  QByteArray refStr;
971  QByteArray retRefStr;
972  int i;
973  int j;
974 
975  if (auto hdr = msg->references(false)) {
976  refStr = hdr->as7BitString(false).trimmed();
977  }
978 
979  if (refStr.isEmpty()) {
980  return msg->messageID()->as7BitString(false);
981  }
982 
983  i = refStr.indexOf('<');
984  j = refStr.indexOf('>');
985  firstRef = refStr.mid(i, j - i + 1);
986  if (!firstRef.isEmpty()) {
987  retRefStr = firstRef + ' ';
988  }
989 
990  i = refStr.lastIndexOf('<');
991  j = refStr.lastIndexOf('>');
992 
993  lastRef = refStr.mid(i, j - i + 1);
994  if (!lastRef.isEmpty() && lastRef != firstRef) {
995  retRefStr += lastRef + ' ';
996  }
997 
998  retRefStr += msg->messageID()->as7BitString(false);
999  return retRefStr;
1000 }
QTextCodec * codecForName(const QString &name) const
QString toString(Qt::DateFormat format) const const
QByteArray fromUnicode(const QString &str) const const
bool thatIsMe(const QString &addressList) const
QString replySubject(KMime::Message *msg)
Return this mails subject, formatted for "reply" mails.
Definition: stringutil.cpp:744
QString toString(qlonglong i) const const
QByteArray trimmed() const const
QByteArray asSendableString(const KMime::Message::Ptr &originalMessage)
Returns the message contents with the headers that should not be sent stripped off.
Definition: stringutil.cpp:383
void setMimeType(const QByteArray &mimeType)
int lastIndexOf(char ch, int from) const const
RFC2822Date
bool isEmpty() const const
void fromUnicodeString(const QString &s, const QByteArray &b) override
T & first()
Headers::ContentDisposition * contentDisposition(bool create=true)
QString join(const QString &separator) const const
QString & remove(int position, int n)
void from7BitString(const QByteArray &s)
T * data() const const
bool operator==(const Qt3DRender::QGraphicsApiFilter &reference, const Qt3DRender::QGraphicsApiFilter &sample)
QLocale c()
int indexOf(char ch, int from) const const
QString number(int n, int base)
int count(const T &value) const const
QString fromLocal8Bit(const char *str, int size)
QString forwardSubject(KMime::Message *msg)
Return this mails subject, formatted for "forward" mails.
Definition: stringutil.cpp:736
void initHeader(const KMime::Message::Ptr &message, const KIdentityManagement::IdentityManager *identMan, uint id)
Initialize header fields.
void fromUnicodeString(const QString &s)
ReplyStrategy
Enumeration that defines the available reply "modes".
void fromUnicodeString(const QString &s, const QByteArray &b) override
QChar fromLatin1(char c)
static QString listToUnicodeString(const QVector< Mailbox > &mailboxes)
CaseSensitive
void fromUnicodeString(const QString &s)
bool isEmpty() const const
QString trimmed() const const
const char * constData() const const
static QVector< Mailbox > listFrom7BitString(const QByteArray &s)
static const MessageStatus statusForwarded()
WaitCursor
void setAutomaticFields(const KMime::Message::Ptr &msg, bool aIsMulti)
Set fields that are either automatically set (Message-id) or that do not change from one message to a...
QByteArray mid(int pos, int len) const const
Headers::ContentTransferEncoding * contentTransferEncoding(bool create=true)
Headers::ContentType * contentType(bool create=true)
void reserve(int size)
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
static KCharsets * charsets()
KCODECS_EXPORT QStringList splitAddressList(const QString &aStr)
QString i18n(const char *text, const TYPE &arg...)
QString & replace(int position, int n, QChar after)
void setParameter(const QString &key, const QString &value)
void removePrivateHeaderFields(const KMime::Message::Ptr &message, bool cleanUpHeader)
Removes all private header fields (e.g.
Definition: stringutil.cpp:354
QDateTime currentDateTime()
void initFromMessage(const KMime::Message::Ptr &msg, const KMime::Message::Ptr &origMsg, KIdentityManagement::IdentityManager *identMan, uint id, bool idHeaders)
Initialize headers fields according to the identity and the transport header of the given original me...
Simple interface that both EncryptJob and SignEncryptJob implement so the composer can extract some e...
QByteArray headerAsSendableString(const KMime::Message::Ptr &originalMessage)
Return the message header with the headers that should not be sent stripped off.
Definition: stringutil.cpp:394
QSharedPointer< Message > Ptr
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
static QString formatDate(DateFormatter::FormatType ftype, time_t t, const QString &data=QString(), bool shortFormat=true)
int count(const T &value) const const
int length() const const
bool canEncode(QChar ch) const const
void setEncoding(contentEncoding e)
QString fromLatin1(const char *str, int size)
static const MessageStatus statusReplied()
QString generateMessageId(const QString &address, const QString &suffix)
Generates the Message-Id.
Definition: stringutil.cpp:300
QString toUnicode(const QByteArray &a) const const
QString primaryEmailAddress() const
uint toUInt(bool *ok, int base) const const
QByteArray toUtf8() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Fri Jan 28 2022 23:05:45 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.