Messagelib

mimetreeparser/src/messagepart.cpp
1 /*
2  SPDX-FileCopyrightText: 2015 Sandro KnauƟ <sknauss@kde.org>
3 
4  SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6 
7 #include "messagepart.h"
8 #include "cryptohelper.h"
9 #include "job/qgpgmejobexecutor.h"
10 #include "memento/compositememento.h"
11 #include "memento/cryptobodypartmemento.h"
12 #include "memento/decryptverifybodypartmemento.h"
13 #include "memento/keycachememento.h"
14 #include "memento/verifydetachedbodypartmemento.h"
15 #include "memento/verifyopaquebodypartmemento.h"
16 #include "mimetreeparser_debug.h"
17 #include "objecttreeparser.h"
18 
19 #include "bodyformatter/utils.h"
20 
21 #include <KMime/Content>
22 #include <KMime/Types>
23 #include <Libkleo/Compliance>
24 #include <Libkleo/KeyCache>
25 
26 #include <QGpgME/DN>
27 #include <QGpgME/ImportJob>
28 #include <QGpgME/Protocol>
29 #include <QGpgME/VerifyDetachedJob>
30 #include <QGpgME/VerifyOpaqueJob>
31 
32 #include <gpgme++/key.h>
33 #include <gpgme++/keylistresult.h>
34 #include <gpgme.h>
35 
36 #include <KLocalizedString>
37 
38 #include <QUrl>
39 
40 using namespace MimeTreeParser;
41 
42 //------MessagePart-----------------------
43 namespace MimeTreeParser
44 {
45 class MessagePartPrivate
46 {
47 public:
48  MessagePart *mParentPart = nullptr;
50  KMime::Content *mNode = nullptr;
51  KMime::Content *mAttachmentNode = nullptr;
52  QString mText;
53  PartMetaData mMetaData;
54  bool mRoot = false;
55  bool mIsImage = false;
56  bool mNeverDisplayInline = false;
57 };
58 }
59 
60 MessagePart::MessagePart(ObjectTreeParser *otp, const QString &text)
61  : mOtp(otp)
62  , d(new MessagePartPrivate)
63 {
64  d->mText = text;
65 }
66 
67 MessagePart::~MessagePart() = default;
68 
69 MessagePart *MessagePart::parentPart() const
70 {
71  return d->mParentPart;
72 }
73 
74 void MessagePart::setParentPart(MessagePart *parentPart)
75 {
76  d->mParentPart = parentPart;
77 }
78 
79 QString MessagePart::htmlContent() const
80 {
81  return text();
82 }
83 
84 QString MessagePart::plaintextContent() const
85 {
86  return text();
87 }
88 
89 PartMetaData *MessagePart::partMetaData() const
90 {
91  return &d->mMetaData;
92 }
93 
94 Interface::BodyPartMemento *MessagePart::memento() const
95 {
96  return nodeHelper()->bodyPartMemento(content(), "__plugin__");
97 }
98 
99 void MessagePart::setMemento(Interface::BodyPartMemento *memento)
100 {
101  nodeHelper()->setBodyPartMemento(content(), "__plugin__", memento);
102 }
103 
104 KMime::Content *MessagePart::content() const
105 {
106  return d->mNode;
107 }
108 
109 void MessagePart::setContent(KMime::Content *node)
110 {
111  d->mNode = node;
112 }
113 
114 KMime::Content *MessagePart::attachmentContent() const
115 {
116  return d->mAttachmentNode;
117 }
118 
119 void MessagePart::setAttachmentContent(KMime::Content *node)
120 {
121  d->mAttachmentNode = node;
122 }
123 
124 bool MessagePart::isAttachment() const
125 {
126  return d->mAttachmentNode;
127 }
128 
129 QString MessagePart::attachmentIndex() const
130 {
131  return attachmentContent()->index().toString();
132 }
133 
134 QString MessagePart::attachmentLink() const
135 {
136  return mOtp->nodeHelper()->asHREF(content(), QStringLiteral("body"));
137 }
138 
139 QString MessagePart::makeLink(const QString &path) const
140 {
141  // FIXME: use a PRNG for the first arg, instead of a serial number
142  static int serial = 0;
143  if (path.isEmpty()) {
144  return {};
145  }
146  return QStringLiteral("x-kmail:/bodypart/%1/%2/%3")
147  .arg(serial++)
148  .arg(mOtp->nodeHelper()->asHREF(content()), QString::fromLatin1(QUrl::toPercentEncoding(path, "/")));
149 }
150 
151 void MessagePart::setIsRoot(bool root)
152 {
153  d->mRoot = root;
154 }
155 
156 bool MessagePart::isRoot() const
157 {
158  return d->mRoot;
159 }
160 
161 QString MessagePart::text() const
162 {
163  return d->mText;
164 }
165 
166 void MessagePart::setText(const QString &text)
167 {
168  d->mText = text;
169 }
170 
171 bool MessagePart::isHtml() const
172 {
173  return false;
174 }
175 
176 Interface::ObjectTreeSource *MessagePart::source() const
177 {
178  Q_ASSERT(mOtp);
179  return mOtp->mSource;
180 }
181 
182 NodeHelper *MessagePart::nodeHelper() const
183 {
184  Q_ASSERT(mOtp);
185  return mOtp->nodeHelper();
186 }
187 
188 void MessagePart::parseInternal(KMime::Content *node, bool onlyOneMimePart)
189 {
190  auto subMessagePart = mOtp->parseObjectTreeInternal(node, onlyOneMimePart);
191  d->mRoot = subMessagePart->isRoot();
192  const QList<MessagePart::Ptr> subParts = subMessagePart->subParts();
193  for (const auto &part : subParts) {
194  appendSubPart(part);
195  }
196 }
197 
198 QString MessagePart::renderInternalText() const
199 {
200  QString text;
201  const auto subPartsLst = subParts();
202  for (const auto &mp : subPartsLst) {
203  text += mp->text();
204  }
205  return text;
206 }
207 
208 void MessagePart::fix() const
209 {
210  const auto subPartsLst = subParts();
211  for (const auto &mp : subPartsLst) {
212  const auto m = mp.dynamicCast<MessagePart>();
213  if (m) {
214  m->fix();
215  }
216  }
217 }
218 
219 void MessagePart::appendSubPart(const MessagePart::Ptr &messagePart)
220 {
221  messagePart->setParentPart(this);
222  d->mBlocks.append(messagePart);
223 }
224 
225 const QList<MessagePart::Ptr> &MessagePart::subParts() const
226 {
227  return d->mBlocks;
228 }
229 
230 bool MessagePart::hasSubParts() const
231 {
232  return !d->mBlocks.isEmpty();
233 }
234 
235 void MessagePart::clearSubParts()
236 {
237  d->mBlocks.clear();
238 }
239 
240 bool MessagePart::neverDisplayInline() const
241 {
242  return d->mNeverDisplayInline;
243 }
244 
245 void MessagePart::setNeverDisplayInline(bool displayInline)
246 {
247  d->mNeverDisplayInline = displayInline;
248 }
249 
250 bool MessagePart::isImage() const
251 {
252  return d->mIsImage;
253 }
254 
255 void MessagePart::setIsImage(bool image)
256 {
257  d->mIsImage = image;
258 }
259 
260 bool MessagePart::hasHeader(const char *headerType) const
261 {
262  Q_UNUSED(headerType)
263  return false;
264 }
265 
266 const KMime::Headers::Base *MimeTreeParser::MessagePart::header(const char *headerType) const
267 {
268  Q_UNUSED(headerType)
269  return nullptr;
270 }
271 
272 QList<KMime::Headers::Base *> MessagePart::headers(const char *headerType) const
273 {
274  Q_UNUSED(headerType)
275  return {};
276 }
277 
278 //-----MessagePartList----------------------
279 MessagePartList::MessagePartList(ObjectTreeParser *otp)
280  : MessagePart(otp, QString())
281 {
282 }
283 
284 MessagePartList::~MessagePartList() = default;
285 
286 QString MessagePartList::text() const
287 {
288  return renderInternalText();
289 }
290 
291 QString MessagePartList::plaintextContent() const
292 {
293  return {};
294 }
295 
296 QString MessagePartList::htmlContent() const
297 {
298  return {};
299 }
300 
301 //-----TextMessageBlock----------------------
302 
303 TextMessagePart::TextMessagePart(ObjectTreeParser *otp, KMime::Content *node, bool decryptMessage)
304  : MessagePartList(otp)
305  , mDecryptMessage(decryptMessage)
306 {
307  if (!node) {
308  qCWarning(MIMETREEPARSER_LOG) << "not a valid node";
309  return;
310  }
311 
312  setContent(node);
313 
314  parseContent();
315 }
316 
317 TextMessagePart::~TextMessagePart() = default;
318 
319 bool TextMessagePart::decryptMessage() const
320 {
321  return mDecryptMessage;
322 }
323 
324 void TextMessagePart::parseContent()
325 {
326  const auto codecName = mOtp->codecNameFor(content());
327  QStringDecoder aCodec(codecName.constData());
328  const QString &fromAddress = mOtp->nodeHelper()->fromAsString(content());
329  mSignatureState = KMMsgNotSigned;
330  mEncryptionState = KMMsgNotEncrypted;
331  const auto blocks = prepareMessageForDecryption(content()->decodedContent());
332 
333  const auto cryptProto = QGpgME::openpgp();
334 
335  if (!blocks.isEmpty()) {
336  /* The (overall) signature/encrypted status is broken
337  * if one unencrypted part is at the beginning or in the middle
338  * because mailmain adds an unencrypted part at the end this should not break the overall status
339  *
340  * That's why we first set the tmp status and if one encrypted/signed block comes afterwards, than
341  * the status is set to unencrypted
342  */
343  bool fullySignedOrEncrypted = true;
344  bool fullySignedOrEncryptedTmp = true;
345 
346  int blockIndex = -1;
347  for (const auto &block : blocks) {
348  blockIndex += 1;
349  if (!fullySignedOrEncryptedTmp) {
350  fullySignedOrEncrypted = false;
351  }
352 
353  if (block.type() == NoPgpBlock && !block.text().trimmed().isEmpty()) {
354  fullySignedOrEncryptedTmp = false;
355  aCodec.resetState();
356  appendSubPart(MessagePart::Ptr(new MessagePart(mOtp, aCodec.decode(block.text()))));
357  } else if (block.type() == PgpMessageBlock) {
358  EncryptedMessagePart::Ptr mp(new EncryptedMessagePart(mOtp, QString(), cryptProto, fromAddress, nullptr));
359  mp->setDecryptMessage(decryptMessage());
360  mp->setIsEncrypted(true);
361  mp->setMementoName(mp->mementoName() + "-" + nodeHelper()->asHREF(content(), QString::number(blockIndex)).toLocal8Bit());
362  appendSubPart(mp);
363  if (!decryptMessage()) {
364  continue;
365  }
366  mp->startDecryption(block.text(), codecName);
367  if (mp->partMetaData()->inProgress) {
368  continue;
369  }
370  } else if (block.type() == ClearsignedBlock) {
371  SignedMessagePart::Ptr mp(new SignedMessagePart(mOtp, QString(), cryptProto, fromAddress, nullptr));
372  mp->setMementoName(mp->mementoName() + "-" + nodeHelper()->asHREF(content(), QString::number(blockIndex)).toLocal8Bit());
373  appendSubPart(mp);
374  mp->startVerification(block.text(), codecName);
375  } else {
376  continue;
377  }
378 
379  const auto mp = subParts().last().staticCast<MessagePart>();
380  const PartMetaData *messagePart(mp->partMetaData());
381 
382  if (!messagePart->isEncrypted && !messagePart->isSigned && !block.text().trimmed().isEmpty()) {
383  aCodec.resetState();
384  mp->setText(aCodec.decode(block.text()));
385  }
386 
387  if (messagePart->isEncrypted) {
388  mEncryptionState = KMMsgPartiallyEncrypted;
389  }
390 
391  if (messagePart->isSigned) {
392  mSignatureState = KMMsgPartiallySigned;
393  }
394  }
395 
396  // Do we have an fully Signed/Encrypted Message?
397  if (fullySignedOrEncrypted) {
398  if (mSignatureState == KMMsgPartiallySigned) {
399  mSignatureState = KMMsgFullySigned;
400  }
401  if (mEncryptionState == KMMsgPartiallyEncrypted) {
402  mEncryptionState = KMMsgFullyEncrypted;
403  }
404  }
405  }
406 }
407 
408 KMMsgEncryptionState TextMessagePart::encryptionState() const
409 {
410  return mEncryptionState;
411 }
412 
413 KMMsgSignatureState TextMessagePart::signatureState() const
414 {
415  return mSignatureState;
416 }
417 
418 bool TextMessagePart::showLink() const
419 {
420  return !temporaryFilePath().isEmpty();
421 }
422 
423 bool TextMessagePart::isFirstTextPart() const
424 {
425  return content()->topLevel()->textContent() == content();
426 }
427 
428 bool TextMessagePart::hasLabel() const
429 {
430  return !NodeHelper::fileName(content()).isEmpty();
431 }
432 
433 QString TextMessagePart::label() const
434 {
435  const QString name = content()->contentType()->name();
437  if (label.isEmpty()) {
438  label = i18nc("display name for an unnamed attachment", "Unnamed");
439  }
440  return label;
441 }
442 
443 QString TextMessagePart::comment() const
444 {
445  const QString comment = content()->contentDescription()->asUnicodeString();
446  if (comment == label()) {
447  return {};
448  }
449  return comment;
450 }
451 
453 {
454  return nodeHelper()->writeNodeToTempFile(content());
455 }
456 
457 //-----AttachmentMessageBlock----------------------
458 
459 AttachmentMessagePart::AttachmentMessagePart(ObjectTreeParser *otp, KMime::Content *node, bool decryptMessage)
460  : TextMessagePart(otp, node, decryptMessage)
461 {
462 }
463 
464 AttachmentMessagePart::~AttachmentMessagePart() = default;
465 
466 //-----HtmlMessageBlock----------------------
467 
468 HtmlMessagePart::HtmlMessagePart(ObjectTreeParser *otp, KMime::Content *node, Interface::ObjectTreeSource *source)
469  : MessagePart(otp, QString())
470 {
471  Q_UNUSED(source)
472  if (!node) {
473  qCWarning(MIMETREEPARSER_LOG) << "not a valid node";
474  return;
475  }
476  setContent(node);
477 
478  const QByteArray partBody(node->decodedContent());
479  mBodyHTML = QStringDecoder(mOtp->codecNameFor(node).constData()).decode(partBody);
480  mCharset = NodeHelper::charset(node);
481 }
482 
483 HtmlMessagePart::~HtmlMessagePart() = default;
484 
485 void HtmlMessagePart::fix() const
486 {
487  mOtp->mHtmlContent += mBodyHTML;
488  mOtp->mHtmlContentCharset = mCharset;
489 }
490 
491 QString HtmlMessagePart::text() const
492 {
493  return mBodyHTML;
494 }
495 
496 QString MimeTreeParser::HtmlMessagePart::plaintextContent() const
497 {
498  return {};
499 }
500 
501 bool HtmlMessagePart::isHtml() const
502 {
503  return true;
504 }
505 
506 QString HtmlMessagePart::bodyHtml() const
507 {
508  return mBodyHTML;
509 }
510 
511 //-----MimeMessageBlock----------------------
512 
513 MimeMessagePart::MimeMessagePart(ObjectTreeParser *otp, KMime::Content *node, bool onlyOneMimePart)
514  : MessagePart(otp, QString())
515  , mOnlyOneMimePart(onlyOneMimePart)
516 {
517  if (!node) {
518  qCWarning(MIMETREEPARSER_LOG) << "not a valid node";
519  return;
520  }
521  setContent(node);
522 
523  parseInternal(node, mOnlyOneMimePart);
524 }
525 
526 MimeMessagePart::~MimeMessagePart() = default;
527 
528 QString MimeMessagePart::text() const
529 {
530  return renderInternalText();
531 }
532 
533 QString MimeMessagePart::plaintextContent() const
534 {
535  return {};
536 }
537 
538 QString MimeMessagePart::htmlContent() const
539 {
540  return {};
541 }
542 
543 //-----AlternativeMessagePart----------------------
544 
545 AlternativeMessagePart::AlternativeMessagePart(ObjectTreeParser *otp, KMime::Content *node, Util::HtmlMode preferredMode)
546  : MessagePart(otp, QString())
547  , mPreferredMode(preferredMode)
548 {
549  setContent(node);
550  KMime::Content *dataIcal = findTypeInDirectChilds(node, "text/calendar");
551  KMime::Content *dataHtml = findTypeInDirectChilds(node, "text/html");
552  KMime::Content *dataText = findTypeInDirectChilds(node, "text/plain");
553 
554  if (!dataHtml) {
555  // If we didn't find the HTML part as the first child of the multipart/alternative, it might
556  // be that this is a HTML message with images, and text/plain and multipart/related are the
557  // immediate children of this multipart/alternative node.
558  // In this case, the HTML node is a child of multipart/related.
559  dataHtml = findTypeInDirectChilds(node, "multipart/related");
560 
561  // Still not found? Stupid apple mail actually puts the attachments inside of the
562  // multipart/alternative, which is wrong. Therefore we also have to look for multipart/mixed
563  // here.
564  // Do this only when preferring HTML mail, though, since otherwise the attachments are hidden
565  // when displaying plain text.
566  if (!dataHtml) {
567  dataHtml = findTypeInDirectChilds(node, "multipart/mixed");
568  }
569  }
570 
571  if (dataIcal) {
572  mChildNodes[Util::MultipartIcal] = dataIcal;
573  }
574 
575  if (dataText) {
576  mChildNodes[Util::MultipartPlain] = dataText;
577  }
578 
579  if (dataHtml) {
580  mChildNodes[Util::MultipartHtml] = dataHtml;
581  }
582 
583  if (mChildNodes.isEmpty()) {
584  qCWarning(MIMETREEPARSER_LOG) << "no valid nodes";
585  return;
586  }
587 
589  while (i.hasNext()) {
590  i.next();
591  mChildParts[i.key()] = MimeMessagePart::Ptr(new MimeMessagePart(mOtp, i.value(), true));
592  }
593 }
594 
595 AlternativeMessagePart::~AlternativeMessagePart() = default;
596 
597 Util::HtmlMode AlternativeMessagePart::preferredMode() const
598 {
599  return mPreferredMode;
600 }
601 
602 void AlternativeMessagePart::setPreferredMode(Util::HtmlMode preferredMode)
603 {
604  mPreferredMode = preferredMode;
605 }
606 
607 QList<Util::HtmlMode> AlternativeMessagePart::availableModes()
608 {
609  return mChildParts.keys();
610 }
611 
612 QString AlternativeMessagePart::text() const
613 {
614  if (mChildParts.contains(Util::MultipartPlain)) {
615  return mChildParts[Util::MultipartPlain]->text();
616  }
617  return {};
618 }
619 
620 void AlternativeMessagePart::fix() const
621 {
622  if (mChildParts.contains(Util::MultipartPlain)) {
623  mChildParts[Util::MultipartPlain]->fix();
624  }
625 
626  const auto mode = preferredMode();
627  if (mode != Util::MultipartPlain && mChildParts.contains(mode)) {
628  mChildParts[mode]->fix();
629  }
630 }
631 
632 const QMap<Util::HtmlMode, MimeMessagePart::Ptr> &AlternativeMessagePart::childParts() const
633 {
634  return mChildParts;
635 }
636 
637 bool AlternativeMessagePart::isHtml() const
638 {
639  return mChildParts.contains(Util::MultipartHtml);
640 }
641 
642 QString AlternativeMessagePart::plaintextContent() const
643 {
644  return text();
645 }
646 
647 QString AlternativeMessagePart::htmlContent() const
648 {
649  if (mChildParts.contains(Util::MultipartHtml)) {
650  return mChildParts[Util::MultipartHtml]->text();
651  } else {
652  return plaintextContent();
653  }
654 }
655 
656 //-----CertMessageBlock----------------------
657 
658 CertMessagePart::CertMessagePart(ObjectTreeParser *otp, KMime::Content *node, const QGpgME::Protocol *cryptoProto, bool autoImport)
659  : MessagePart(otp, QString())
660  , mAutoImport(autoImport)
661  , mCryptoProto(cryptoProto)
662 {
663  if (!node) {
664  qCWarning(MIMETREEPARSER_LOG) << "not a valid node";
665  return;
666  }
667  setContent(node);
668 
669  if (!mAutoImport) {
670  return;
671  }
672 
673  const QByteArray certData = node->decodedContent();
674 
675  QGpgME::ImportJob *import = mCryptoProto->importJob();
676  QGpgMEJobExecutor executor;
677  mImportResult = executor.exec(import, certData);
678 }
679 
680 CertMessagePart::~CertMessagePart() = default;
681 
682 QString CertMessagePart::text() const
683 {
684  return {};
685 }
686 
687 const GpgME::ImportResult &CertMessagePart::importResult() const
688 {
689  return mImportResult;
690 }
691 
692 //-----SignedMessageBlock---------------------
693 SignedMessagePart::SignedMessagePart(ObjectTreeParser *otp,
694  const QString &text,
695  const QGpgME::Protocol *cryptoProto,
696  const QString &fromAddress,
697  KMime::Content *node)
698  : MessagePart(otp, text)
699  , mCryptoProto(cryptoProto)
700  , mFromAddress(fromAddress)
701  , mMementoName("verification")
702 {
703  setContent(node);
704  partMetaData()->technicalProblem = (mCryptoProto == nullptr);
705  partMetaData()->isSigned = true;
706  partMetaData()->isGoodSignature = false;
707  partMetaData()->keyTrust = GpgME::Signature::Unknown;
708  partMetaData()->status = i18n("Wrong Crypto Plug-In.");
709  partMetaData()->status_code = GPGME_SIG_STAT_NONE;
710 }
711 
712 SignedMessagePart::~SignedMessagePart() = default;
713 
714 void SignedMessagePart::setIsSigned(bool isSigned)
715 {
716  partMetaData()->isSigned = isSigned;
717 }
718 
719 bool SignedMessagePart::isSigned() const
720 {
721  return partMetaData()->isSigned;
722 }
723 
724 QByteArray SignedMessagePart::mementoName() const
725 {
726  return mMementoName;
727 }
728 
729 void SignedMessagePart::setMementoName(const QByteArray &name)
730 {
731  mMementoName = name;
732 }
733 
734 static GpgME::Protocol toGpgMeProtocol(const QGpgME::Protocol *protocol)
735 {
736  if (protocol == QGpgME::openpgp()) {
737  return GpgME::OpenPGP;
738  }
739 
740  if (protocol == QGpgME::smime()) {
741  return GpgME::CMS;
742  }
743 
744  return GpgME::UnknownProtocol;
745 }
746 
747 bool SignedMessagePart::okVerify(const QByteArray &data, const QByteArray &signature, KMime::Content *textNode)
748 {
749  NodeHelper *nodeHelper = mOtp->nodeHelper();
750 
751  partMetaData()->isSigned = false;
752  partMetaData()->technicalProblem = (mCryptoProto == nullptr);
753  partMetaData()->keyTrust = GpgME::Signature::Unknown;
754  partMetaData()->status = i18n("Wrong Crypto Plug-In.");
755  partMetaData()->status_code = GPGME_SIG_STAT_NONE;
756 
757  const QByteArray _mementoName = mementoName();
758 
759  auto m = dynamic_cast<CompositeMemento *>(nodeHelper->bodyPartMemento(content(), _mementoName));
760  Q_ASSERT(!m || mCryptoProto); // No CryptoPlugin and having a bodyPartMemento -> there is something completely wrong
761 
762  if (!m && mCryptoProto) {
763  CryptoBodyPartMemento *newM = nullptr;
764  if (!signature.isEmpty()) {
765  QGpgME::VerifyDetachedJob *job = mCryptoProto->verifyDetachedJob();
766  if (job) {
767  newM = new VerifyDetachedBodyPartMemento(job, mCryptoProto->keyListJob(), signature, data);
768  }
769  } else {
770  QGpgME::VerifyOpaqueJob *job = mCryptoProto->verifyOpaqueJob();
771  if (job) {
772  newM = new VerifyOpaqueBodyPartMemento(job, mCryptoProto->keyListJob(), data);
773  }
774  }
775 
776  if (newM) {
777  m = new CompositeMemento();
778  m->addMemento(newM);
779  m->addMemento(new KeyCacheMemento(Kleo::KeyCache::mutableInstance(), toGpgMeProtocol(mCryptoProto)));
780  }
781 
782  if (m) {
783  if (mOtp->allowAsync()) {
784  QObject::connect(m, &CryptoBodyPartMemento::update, nodeHelper, &NodeHelper::update);
785  if (m->start()) {
786  partMetaData()->inProgress = true;
787  mOtp->mHasPendingAsyncJobs = true;
788  }
789  } else {
790  m->exec();
791  }
792  nodeHelper->setBodyPartMemento(content(), _mementoName, m);
793  }
794  } else if (m && m->isRunning()) {
795  partMetaData()->inProgress = true;
796  mOtp->mHasPendingAsyncJobs = true;
797  } else {
798  partMetaData()->inProgress = false;
799  mOtp->mHasPendingAsyncJobs = false;
800  }
801 
802  if (m && !partMetaData()->inProgress) {
803  if (!signature.isEmpty()) {
804  mVerifiedText = data;
805  }
806  setVerificationResult(m, textNode);
807  }
808 
809  if (!m && !partMetaData()->inProgress) {
810  QString errorMsg;
811  QString cryptPlugLibName;
812  QString cryptPlugDisplayName;
813  if (mCryptoProto) {
814  cryptPlugLibName = mCryptoProto->name();
815  cryptPlugDisplayName = mCryptoProto->displayName();
816  }
817 
818  if (!mCryptoProto) {
819  if (cryptPlugDisplayName.isEmpty()) {
820  errorMsg = i18n("No appropriate crypto plug-in was found.");
821  } else {
822  errorMsg = i18nc("%1 is either 'OpenPGP' or 'S/MIME'", "No %1 plug-in was found.", cryptPlugDisplayName);
823  }
824  } else {
825  errorMsg = i18n("Crypto plug-in \"%1\" cannot verify signatures.", cryptPlugLibName);
826  }
827  partMetaData()->errorText = i18n(
828  "The message is signed, but the "
829  "validity of the signature cannot be "
830  "verified.<br />"
831  "Reason: %1",
832  errorMsg);
833  }
834 
835  return partMetaData()->isSigned;
836 }
837 
838 static int signatureToStatus(const GpgME::Signature &sig)
839 {
840  switch (sig.status().code()) {
841  case GPG_ERR_NO_ERROR:
842  return GPGME_SIG_STAT_GOOD;
843  case GPG_ERR_BAD_SIGNATURE:
844  return GPGME_SIG_STAT_BAD;
845  case GPG_ERR_NO_PUBKEY:
846  return GPGME_SIG_STAT_NOKEY;
847  case GPG_ERR_NO_DATA:
848  return GPGME_SIG_STAT_NOSIG;
849  case GPG_ERR_SIG_EXPIRED:
850  return GPGME_SIG_STAT_GOOD_EXP;
851  case GPG_ERR_KEY_EXPIRED:
852  return GPGME_SIG_STAT_GOOD_EXPKEY;
853  default:
854  return GPGME_SIG_STAT_ERROR;
855  }
856 }
857 
858 QString prettifyDN(const char *uid)
859 {
860  return QGpgME::DN(uid).prettyDN();
861 }
862 
863 void SignedMessagePart::sigStatusToMetaData()
864 {
865  GpgME::Key key;
866  if (partMetaData()->isSigned) {
867  GpgME::Signature signature = mSignatures.front();
868  partMetaData()->status_code = signatureToStatus(signature);
869  partMetaData()->isGoodSignature = partMetaData()->status_code == GPGME_SIG_STAT_GOOD;
870  // save extended signature status flags
871  partMetaData()->sigSummary = signature.summary();
872 
873  if (partMetaData()->isGoodSignature && !key.keyID()) {
874  // Search for the key by its fingerprint so that we can check for
875  // trust etc.
876  key = mKeyCache->findByFingerprint(signature.fingerprint());
877  if (key.isNull() && signature.fingerprint()) {
878  // try to find a subkey that was used for signing;
879  // assumes that the key ID is the last 16 characters of the fingerprint
880  const auto fpr = std::string_view{signature.fingerprint()};
881  const auto keyID = std::string{fpr, fpr.size() - 16, 16};
882  const auto subkeys = mKeyCache->findSubkeysByKeyID({keyID});
883  if (subkeys.size() > 0) {
884  key = subkeys[0].parent();
885  }
886  }
887  if (key.isNull()) {
888  qCDebug(MIMETREEPARSER_LOG) << "Found no key or subkey for fingerprint" << signature.fingerprint();
889  }
890  }
891 
892  if (key.keyID()) {
893  partMetaData()->keyId = key.keyID();
894  }
895  if (partMetaData()->keyId.isEmpty()) {
896  partMetaData()->keyId = signature.fingerprint();
897  }
898  partMetaData()->keyTrust = signature.validity();
899  if (key.numUserIDs() > 0 && key.userID(0).id()) {
900  partMetaData()->signer = prettifyDN(key.userID(0).id());
901  }
902  for (const auto &uid : key.userIDs()) {
903  // The following if /should/ always result in TRUE but we
904  // won't trust implicitly the plugin that gave us these data.
905  if (uid.email()) {
907  mbox.from7BitString(uid.email());
908  if (mbox.hasAddress()) {
909  partMetaData()->signerMailAddresses.append(mbox.addrSpec().asString());
910  }
911  }
912  }
913 
914  if (signature.creationTime()) {
915  partMetaData()->creationTime.setSecsSinceEpoch(signature.creationTime());
916  } else {
917  partMetaData()->creationTime = QDateTime();
918  }
919  if (partMetaData()->signer.isEmpty()) {
920  if (key.numUserIDs() > 0 && key.userID(0).name()) {
921  partMetaData()->signer = prettifyDN(key.userID(0).name());
922  }
923  if (!partMetaData()->signerMailAddresses.empty()) {
924  if (partMetaData()->signer.isEmpty()) {
925  partMetaData()->signer = partMetaData()->signerMailAddresses.front();
926  } else {
927  partMetaData()->signer += QLatin1StringView(" <") + partMetaData()->signerMailAddresses.front() + QLatin1Char('>');
928  }
929  }
930  }
931  if (Kleo::DeVSCompliance::isCompliant()) {
932  partMetaData()->isCompliant = signature.isDeVs();
933  partMetaData()->compliance = Kleo::DeVSCompliance::name(signature.isDeVs());
934  } else {
935  partMetaData()->isCompliant = true;
936  }
937  }
938 }
939 
940 void SignedMessagePart::startVerification(const QByteArray &text, QByteArrayView aCodec)
941 {
942  startVerificationDetached(text, nullptr, QByteArray());
943 
944  if (!content() && partMetaData()->isSigned) {
945  QStringDecoder codec(aCodec.constData());
946  setText(codec.decode(mVerifiedText));
947  }
948 }
949 
950 void SignedMessagePart::startVerificationDetached(const QByteArray &text, KMime::Content *textNode, const QByteArray &signature)
951 {
952  partMetaData()->isEncrypted = false;
953  partMetaData()->isDecryptable = false;
954 
955  if (textNode) {
956  parseInternal(textNode, false);
957  }
958 
959  if (!okVerify(text, signature, textNode)) {
960  partMetaData()->creationTime = QDateTime();
961  }
962 }
963 
964 void SignedMessagePart::setVerificationResult(const CompositeMemento *m, KMime::Content *textNode)
965 {
966  {
967  const auto kc = m->memento<KeyCacheMemento>();
968  if (kc) {
969  mKeyCache = kc->keyCache();
970  }
971  }
972  {
973  const auto vm = m->memento<VerifyDetachedBodyPartMemento>();
974  if (vm) {
975  mSignatures = vm->verifyResult().signatures();
976  }
977  }
978  {
979  const auto vm = m->memento<VerifyOpaqueBodyPartMemento>();
980  if (vm) {
981  mVerifiedText = vm->plainText();
982  mSignatures = vm->verifyResult().signatures();
983  }
984  }
985  {
986  const auto vm = m->memento<DecryptVerifyBodyPartMemento>();
987  if (vm) {
988  mVerifiedText = vm->plainText();
989  mSignatures = vm->verifyResult().signatures();
990  }
991  }
992  partMetaData()->auditLogError = m->auditLogError();
993  partMetaData()->auditLog = m->auditLogAsHtml();
994  partMetaData()->isSigned = !mSignatures.empty();
995 
996  if (partMetaData()->isSigned) {
997  sigStatusToMetaData();
998  if (content()) {
999  mOtp->nodeHelper()->setSignatureState(content(), KMMsgFullySigned);
1000  if (!textNode) {
1001  mOtp->nodeHelper()->setPartMetaData(content(), *partMetaData());
1002 
1003  if (!mVerifiedText.isEmpty()) {
1004  auto tempNode = new KMime::Content();
1005  tempNode->setContent(KMime::CRLFtoLF(mVerifiedText.constData()));
1006  tempNode->parse();
1007 
1008  if (!tempNode->head().isEmpty()) {
1009  tempNode->contentDescription()->from7BitString("signed data");
1010  }
1011  mOtp->nodeHelper()->attachExtraContent(content(), tempNode);
1012 
1013  parseInternal(tempNode, false);
1014  }
1015  }
1016  }
1017  }
1018 }
1019 
1020 QString SignedMessagePart::plaintextContent() const
1021 {
1022  if (!content()) {
1023  return MessagePart::text();
1024  } else {
1025  return {};
1026  }
1027 }
1028 
1029 QString SignedMessagePart::htmlContent() const
1030 {
1031  if (!content()) {
1032  return MessagePart::text();
1033  } else {
1034  return {};
1035  }
1036 }
1037 
1038 const QGpgME::Protocol *SignedMessagePart::cryptoProto() const
1039 {
1040  return mCryptoProto;
1041 }
1042 
1043 QString SignedMessagePart::fromAddress() const
1044 {
1045  return mFromAddress;
1046 }
1047 
1048 bool SignedMessagePart::hasHeader(const char *headerType) const
1049 {
1050  if (content()) {
1051  return content()->hasHeader(headerType);
1052  }
1053  return false;
1054 }
1055 
1056 const KMime::Headers::Base *MimeTreeParser::SignedMessagePart::header(const char *headerType) const
1057 {
1058  if (content()) {
1059  return content()->headerByType(headerType);
1060  }
1061  return nullptr;
1062 }
1063 
1064 QList<KMime::Headers::Base *> SignedMessagePart::headers(const char *headerType) const
1065 {
1066  if (content()) {
1067  return content()->headersByType(headerType);
1068  }
1069  return {};
1070 }
1071 
1072 //-----CryptMessageBlock---------------------
1073 EncryptedMessagePart::EncryptedMessagePart(ObjectTreeParser *otp,
1074  const QString &text,
1075  const QGpgME::Protocol *cryptoProto,
1076  const QString &fromAddress,
1077  KMime::Content *node)
1078  : MessagePart(otp, text)
1079  , mPassphraseError(false)
1080  , mNoSecKey(false)
1081  , mDecryptMessage(false)
1082  , mCryptoProto(cryptoProto)
1083  , mFromAddress(fromAddress)
1084  , mMementoName("decryptverify")
1085 {
1086  setContent(node);
1087  partMetaData()->technicalProblem = (mCryptoProto == nullptr);
1088  partMetaData()->isSigned = false;
1089  partMetaData()->isGoodSignature = false;
1090  partMetaData()->isEncrypted = false;
1091  partMetaData()->isDecryptable = false;
1092  partMetaData()->keyTrust = GpgME::Signature::Unknown;
1093  partMetaData()->status = i18n("Wrong Crypto Plug-In.");
1094  partMetaData()->status_code = GPGME_SIG_STAT_NONE;
1095 }
1096 
1097 EncryptedMessagePart::~EncryptedMessagePart() = default;
1098 
1099 void EncryptedMessagePart::setDecryptMessage(bool decrypt)
1100 {
1101  mDecryptMessage = decrypt;
1102 }
1103 
1104 bool EncryptedMessagePart::decryptMessage() const
1105 {
1106  return mDecryptMessage;
1107 }
1108 
1109 void EncryptedMessagePart::setIsEncrypted(bool encrypted)
1110 {
1111  partMetaData()->isEncrypted = encrypted;
1112 }
1113 
1114 bool EncryptedMessagePart::isEncrypted() const
1115 {
1116  return partMetaData()->isEncrypted;
1117 }
1118 
1119 bool EncryptedMessagePart::isDecryptable() const
1120 {
1121  return partMetaData()->isDecryptable;
1122 }
1123 
1124 bool EncryptedMessagePart::isNoSecKey() const
1125 {
1126  return mNoSecKey;
1127 }
1128 
1129 bool EncryptedMessagePart::passphraseError() const
1130 {
1131  return mPassphraseError;
1132 }
1133 
1134 QByteArray EncryptedMessagePart::mementoName() const
1135 {
1136  return mMementoName;
1137 }
1138 
1139 void EncryptedMessagePart::setMementoName(const QByteArray &name)
1140 {
1141  mMementoName = name;
1142 }
1143 
1144 void EncryptedMessagePart::startDecryption(const QByteArray &text, QByteArrayView aCodec)
1145 {
1146  auto content = new KMime::Content;
1147  content->setBody(text);
1148  content->parse();
1149 
1150  startDecryption(content);
1151 
1152  if (!partMetaData()->inProgress && partMetaData()->isDecryptable) {
1153  QStringDecoder codec(aCodec.constData());
1154  if (hasSubParts()) {
1155  auto _mp = (subParts()[0]).dynamicCast<SignedMessagePart>();
1156  if (_mp) {
1157  _mp->setText(codec.decode(mDecryptedData));
1158  } else {
1159  setText(codec.decode(mDecryptedData));
1160  }
1161  } else {
1162  setText(codec.decode(mDecryptedData));
1163  }
1164  }
1165  delete content;
1166 }
1167 
1168 bool EncryptedMessagePart::okDecryptMIME(KMime::Content &data)
1169 {
1170  mPassphraseError = false;
1171  partMetaData()->inProgress = false;
1172  partMetaData()->errorText.clear();
1173  partMetaData()->auditLogError = GpgME::Error();
1174  partMetaData()->auditLog.clear();
1175  bool bDecryptionOk = false;
1176  bool cannotDecrypt = false;
1177  NodeHelper *nodeHelper = mOtp->nodeHelper();
1178 
1179  Q_ASSERT(decryptMessage());
1180 
1181  const QByteArray _mementoName = mementoName();
1182  // Check whether the memento contains a result from last time:
1183  const auto *m = dynamic_cast<CompositeMemento *>(nodeHelper->bodyPartMemento(&data, _mementoName));
1184 
1185  Q_ASSERT(!m || mCryptoProto); // No CryptoPlugin and having a bodyPartMemento -> there is something completely wrong
1186 
1187  if (!m && mCryptoProto) {
1188  QGpgME::DecryptVerifyJob *job = mCryptoProto->decryptVerifyJob();
1189  if (!job) {
1190  cannotDecrypt = true;
1191  } else {
1192  const QByteArray ciphertext = data.decodedContent();
1193  auto newM = new CompositeMemento();
1194  newM->addMemento(new KeyCacheMemento(Kleo::KeyCache::mutableInstance(), toGpgMeProtocol(mCryptoProto)));
1195  newM->addMemento(new DecryptVerifyBodyPartMemento(job, ciphertext));
1196  if (mOtp->allowAsync()) {
1197  QObject::connect(newM, &CryptoBodyPartMemento::update, nodeHelper, &NodeHelper::update);
1198  if (newM->start()) {
1199  partMetaData()->inProgress = true;
1200  mOtp->mHasPendingAsyncJobs = true;
1201  } else {
1202  m = newM;
1203  }
1204  } else {
1205  newM->exec();
1206  m = newM;
1207  }
1208 
1209  nodeHelper->setBodyPartMemento(&data, _mementoName, newM);
1210  }
1211  } else if (m && m->isRunning()) {
1212  partMetaData()->inProgress = true;
1213  mOtp->mHasPendingAsyncJobs = true;
1214  m = nullptr;
1215  }
1216 
1217  if (m) {
1218  {
1219  const auto *kcm = m->memento<KeyCacheMemento>();
1220  if (kcm) {
1221  mKeyCache = kcm->keyCache();
1222  }
1223  }
1224  auto *decryptMemento = m->memento<DecryptVerifyBodyPartMemento>();
1225  const QByteArray &plainText = decryptMemento->plainText();
1226  const GpgME::DecryptionResult &decryptResult = decryptMemento->decryptResult();
1227  const GpgME::VerificationResult &verifyResult = decryptMemento->verifyResult();
1228  partMetaData()->isSigned = verifyResult.signatures().size() > 0;
1229 
1230  if (partMetaData()->isSigned) {
1231  auto subPart = SignedMessagePart::Ptr(new SignedMessagePart(mOtp, MessagePart::text(), mCryptoProto, mFromAddress, content()));
1232  subPart->setVerificationResult(m, nullptr);
1233  appendSubPart(subPart);
1234  }
1235 
1236  mDecryptRecipients.clear();
1237  bDecryptionOk = !decryptResult.error();
1238 
1239  // std::stringstream ss;
1240  // ss << decryptResult << '\n' << verifyResult;
1241  // qCDebug(MIMETREEPARSER_LOG) << ss.str().c_str();
1242 
1243  for (const auto &recipient : decryptResult.recipients()) {
1244  if (!recipient.status()) {
1245  bDecryptionOk = true;
1246  }
1247  GpgME::Key key;
1248  key = mKeyCache->findByKeyIDOrFingerprint(recipient.keyID());
1249  if (key.isNull()) {
1250  auto ret = mKeyCache->findSubkeysByKeyID({recipient.keyID()});
1251  if (ret.size() == 1) {
1252  key = ret.front().parent();
1253  }
1254  if (key.isNull()) {
1255  qCDebug(MIMETREEPARSER_LOG) << "Found no Key for KeyID " << recipient.keyID();
1256  }
1257  }
1258  mDecryptRecipients.emplace_back(recipient, key);
1259  }
1260 
1261  if (!bDecryptionOk && partMetaData()->isSigned) {
1262  // Only a signed part
1263  partMetaData()->isEncrypted = false;
1264  bDecryptionOk = true;
1265  mDecryptedData = plainText;
1266  } else {
1267  mPassphraseError = decryptResult.error().isCanceled() || decryptResult.error().code() == GPG_ERR_NO_SECKEY;
1268  partMetaData()->isEncrypted = bDecryptionOk || decryptResult.error().code() != GPG_ERR_NO_DATA;
1269 
1270  if (decryptResult.error().isCanceled()) {
1271  setDecryptMessage(false);
1272  }
1273 
1274  partMetaData()->errorText = QString::fromLocal8Bit(decryptResult.error().asString());
1275  if (Kleo::DeVSCompliance::isCompliant()) {
1276  partMetaData()->isCompliant = decryptResult.isDeVs();
1277  partMetaData()->compliance = Kleo::DeVSCompliance::name(decryptResult.isDeVs());
1278  } else {
1279  partMetaData()->isCompliant = true;
1280  }
1281  if (partMetaData()->isEncrypted && decryptResult.numRecipients() > 0) {
1282  partMetaData()->keyId = decryptResult.recipient(0).keyID();
1283  }
1284 
1285  if (bDecryptionOk) {
1286  mDecryptedData = plainText;
1287  } else {
1288  mNoSecKey = true;
1289  const auto decryRecipients = decryptResult.recipients();
1290  for (const GpgME::DecryptionResult::Recipient &recipient : decryRecipients) {
1291  mNoSecKey &= (recipient.status().code() == GPG_ERR_NO_SECKEY);
1292  }
1293  if (!mPassphraseError && !mNoSecKey) { // GpgME do not detect passphrase error correctly
1294  mPassphraseError = true;
1295  }
1296  }
1297  }
1298  }
1299 
1300  if (!bDecryptionOk) {
1301  QString cryptPlugLibName;
1302  if (mCryptoProto) {
1303  cryptPlugLibName = mCryptoProto->name();
1304  }
1305 
1306  if (!mCryptoProto) {
1307  partMetaData()->errorText = i18n("No appropriate crypto plug-in was found.");
1308  } else if (cannotDecrypt) {
1309  partMetaData()->errorText = i18n("Crypto plug-in \"%1\" cannot decrypt messages.", cryptPlugLibName);
1310  } else if (!passphraseError()) {
1311  partMetaData()->errorText = i18n("Crypto plug-in \"%1\" could not decrypt the data.", cryptPlugLibName) + QLatin1StringView("<br />")
1312  + i18n("Error: %1", partMetaData()->errorText);
1313  }
1314  }
1315  return bDecryptionOk;
1316 }
1317 
1318 void EncryptedMessagePart::startDecryption(KMime::Content *data)
1319 {
1320  if (!content() && !data) {
1321  return;
1322  }
1323 
1324  if (!data) {
1325  data = content();
1326  }
1327 
1328  partMetaData()->isEncrypted = true;
1329 
1330  bool bOkDecrypt = okDecryptMIME(*data);
1331 
1332  if (partMetaData()->inProgress) {
1333  return;
1334  }
1335  partMetaData()->isDecryptable = bOkDecrypt;
1336 
1337  if (!partMetaData()->isDecryptable) {
1338  setText(QString::fromUtf8(mDecryptedData.constData()));
1339  }
1340 
1341  if (partMetaData()->isEncrypted && !decryptMessage()) {
1342  partMetaData()->isDecryptable = true;
1343  }
1344 
1345  if (content() && !partMetaData()->isSigned) {
1346  mOtp->nodeHelper()->setPartMetaData(content(), *partMetaData());
1347 
1348  if (decryptMessage()) {
1349  auto tempNode = new KMime::Content();
1350  tempNode->setContent(KMime::CRLFtoLF(mDecryptedData.constData()));
1351  tempNode->parse();
1352 
1353  if (!tempNode->head().isEmpty()) {
1354  tempNode->contentDescription()->from7BitString("encrypted data");
1355  }
1356  mOtp->nodeHelper()->attachExtraContent(content(), tempNode);
1357 
1358  parseInternal(tempNode, false);
1359  }
1360  }
1361 }
1362 
1363 QString EncryptedMessagePart::plaintextContent() const
1364 {
1365  if (!content()) {
1366  return MessagePart::text();
1367  } else {
1368  return {};
1369  }
1370 }
1371 
1372 QString EncryptedMessagePart::htmlContent() const
1373 {
1374  if (!content()) {
1375  return MessagePart::text();
1376  } else {
1377  return {};
1378  }
1379 }
1380 
1381 QString EncryptedMessagePart::text() const
1382 {
1383  if (hasSubParts()) {
1384  auto _mp = (subParts()[0]).dynamicCast<SignedMessagePart>();
1385  if (_mp) {
1386  return _mp->text();
1387  } else {
1388  return MessagePart::text();
1389  }
1390  } else {
1391  return MessagePart::text();
1392  }
1393 }
1394 
1395 const QGpgME::Protocol *EncryptedMessagePart::cryptoProto() const
1396 {
1397  return mCryptoProto;
1398 }
1399 
1400 QString EncryptedMessagePart::fromAddress() const
1401 {
1402  return mFromAddress;
1403 }
1404 
1405 const std::vector<std::pair<GpgME::DecryptionResult::Recipient, GpgME::Key>> &EncryptedMessagePart::decryptRecipients() const
1406 {
1407  return mDecryptRecipients;
1408 }
1409 
1410 bool EncryptedMessagePart::hasHeader(const char *headerType) const
1411 {
1412  const auto extraContent = mOtp->nodeHelper()->decryptedNodeForContent(content());
1413  if (extraContent) {
1414  return nodeHelper()->hasMailHeader(headerType, extraContent);
1415  }
1416  return false;
1417 }
1418 
1419 const KMime::Headers::Base *EncryptedMessagePart::header(const char *headerType) const
1420 {
1421  const auto extraContent = mOtp->nodeHelper()->decryptedNodeForContent(content());
1422  if (extraContent) {
1423  return nodeHelper()->mailHeaderAsBase(headerType, extraContent);
1424  }
1425  return nullptr;
1426 }
1427 
1428 QList<KMime::Headers::Base *> EncryptedMessagePart::headers(const char *headerType) const
1429 {
1430  const auto extraContent = mOtp->nodeHelper()->decryptedNodeForContent(content());
1431  if (extraContent) {
1432  return nodeHelper()->headers(headerType, extraContent);
1433  }
1434  return {};
1435 }
1436 
1437 EncapsulatedRfc822MessagePart::EncapsulatedRfc822MessagePart(ObjectTreeParser *otp, KMime::Content *node, const KMime::Message::Ptr &message)
1438  : MessagePart(otp, QString())
1439  , mMessage(message)
1440 {
1441  setContent(node);
1442  partMetaData()->isEncrypted = false;
1443  partMetaData()->isSigned = false;
1444  partMetaData()->isEncapsulatedRfc822Message = true;
1445 
1446  mOtp->nodeHelper()->setNodeDisplayedEmbedded(node, true);
1447  mOtp->nodeHelper()->setPartMetaData(node, *partMetaData());
1448 
1449  if (!mMessage) {
1450  qCWarning(MIMETREEPARSER_LOG) << "Node is of type message/rfc822 but doesn't have a message!";
1451  return;
1452  }
1453 
1454  // The link to "Encapsulated message" is clickable, therefore the temp file needs to exists,
1455  // since the user can click the link and expect to have normal attachment operations there.
1456  mOtp->nodeHelper()->writeNodeToTempFile(message.data());
1457 
1458  parseInternal(message.data(), false);
1459 }
1460 
1461 EncapsulatedRfc822MessagePart::~EncapsulatedRfc822MessagePart() = default;
1462 
1463 QString EncapsulatedRfc822MessagePart::text() const
1464 {
1465  return renderInternalText();
1466 }
1467 
1468 void EncapsulatedRfc822MessagePart::fix() const
1469 {
1470 }
1471 
1472 const KMime::Message::Ptr EncapsulatedRfc822MessagePart::message() const
1473 {
1474  return mMessage;
1475 }
1476 
1477 #include "moc_messagepart.cpp"
bool hasAddress() const
T * data() const const
QList< Headers::Base * > headersByType(const char *type) const
static QString fileName(const KMime::Content *node)
Returns a usable filename for a node, that can be the filename from the content disposition header,...
QString number(int n, int base)
QString fromUtf8(const char *str, int size)
Helper class for synchronous execution of Kleo crypto jobs.
QString asUnicodeString() const override
QString temporaryFilePath() const
Temporary file containing the part content.
Parses messages and generates HTML display code out of them.
@ MultipartHtml
A multipart/alternative message, the HTML part is currently displayed.
Interface for object tree sources.
void from7BitString(const QByteArray &s)
Content * topLevel() const
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
static QByteArray charset(KMime::Content *node)
Returns the charset for the given node.
QString i18n(const char *text, const TYPE &arg...)
void setBody(const QByteArray &body)
QString fromLocal8Bit(const char *str, int size)
KMime::Content * content() const
The KMime::Content* node that's represented by this part.
bool isEmpty() const const
bool isEmpty() const const
QString name(StandardAction id)
QByteArray toPercentEncoding(const QString &input, const QByteArray &exclude, const QByteArray &include)
Content * textContent()
interface of classes that implement status for BodyPartFormatters.
Definition: bodypart.h:33
T & last()
void attachExtraContent(KMime::Content *topLevelNode, KMime::Content *content)
Attach an extra node to an existing node.
HtmlMode
Describes the type of the displayed message.
QByteArray decodedContent()
QString label(StandardShortcut id)
bool isEmpty() const const
@ MultipartIcal
A multipart/alternative message, the ICal part is currently displayed.
const char * constData() const const
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
QString fromLatin1(const char *str, int size)
QString writeNodeToTempFile(KMime::Content *node)
Writes the given message part to a temporary file and returns the name of this file or QString() if w...
QString i18nc(const char *context, const char *text, const TYPE &arg...)
bool hasHeader(const char *type) const
Headers::ContentType * contentType(bool create=true)
@ MultipartPlain
A multipart/alternative message, the plain text part is currently displayed.
Headers::ContentDescription * contentDescription(bool create=true)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Thu Feb 15 2024 03:55:20 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.