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

KDE's Doxygen guidelines are available online.