Messagelib

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

KDE's Doxygen guidelines are available online.