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)
273 }
274 
275 //-----MessagePartList----------------------
276 MessagePartList::MessagePartList(ObjectTreeParser *otp)
277  : MessagePart(otp, QString())
278 {
279 }
280 
281 MessagePartList::~MessagePartList()
282 {
283 }
284 
285 QString MessagePartList::text() const
286 {
287  return renderInternalText();
288 }
289 
290 QString MessagePartList::plaintextContent() const
291 {
292  return QString();
293 }
294 
295 QString MessagePartList::htmlContent() const
296 {
297  return QString();
298 }
299 
300 //-----TextMessageBlock----------------------
301 
302 TextMessagePart::TextMessagePart(ObjectTreeParser *otp, KMime::Content *node, bool decryptMessage)
303  : MessagePartList(otp)
304  , mDecryptMessage(decryptMessage)
305 {
306  if (!node) {
307  qCWarning(MIMETREEPARSER_LOG) << "not a valid node";
308  return;
309  }
310 
311  setContent(node);
312 
313  parseContent();
314 }
315 
316 TextMessagePart::~TextMessagePart()
317 {
318 }
319 
320 bool TextMessagePart::decryptMessage() const
321 {
322  return mDecryptMessage;
323 }
324 
325 void TextMessagePart::parseContent()
326 {
327  const auto aCodec = mOtp->codecFor(content());
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 crypted/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  appendSubPart(MessagePart::Ptr(new MessagePart(mOtp, aCodec->toUnicode(block.text()))));
356  } else if (block.type() == PgpMessageBlock) {
357  EncryptedMessagePart::Ptr mp(new EncryptedMessagePart(mOtp, QString(), cryptProto, fromAddress, nullptr));
358  mp->setDecryptMessage(decryptMessage());
359  mp->setIsEncrypted(true);
360  mp->setMementoName(mp->mementoName() + "-" + nodeHelper()->asHREF(content(), QString::number(blockIndex)).toLocal8Bit());
361  appendSubPart(mp);
362  if (!decryptMessage()) {
363  continue;
364  }
365  mp->startDecryption(block.text(), aCodec);
366  if (mp->partMetaData()->inProgress) {
367  continue;
368  }
369  } else if (block.type() == ClearsignedBlock) {
370  SignedMessagePart::Ptr mp(new SignedMessagePart(mOtp, QString(), cryptProto, fromAddress, nullptr));
371  mp->setMementoName(mp->mementoName() + "-" + nodeHelper()->asHREF(content(), QString::number(blockIndex)).toLocal8Bit());
372  appendSubPart(mp);
373  mp->startVerification(block.text(), aCodec);
374  } else {
375  continue;
376  }
377 
378  const auto mp = subParts().last().staticCast<MessagePart>();
379  const PartMetaData *messagePart(mp->partMetaData());
380 
381  if (!messagePart->isEncrypted && !messagePart->isSigned && !block.text().trimmed().isEmpty()) {
382  mp->setText(aCodec->toUnicode(block.text()));
383  }
384 
385  if (messagePart->isEncrypted) {
386  mEncryptionState = KMMsgPartiallyEncrypted;
387  }
388 
389  if (messagePart->isSigned) {
390  mSignatureState = KMMsgPartiallySigned;
391  }
392  }
393 
394  // Do we have an fully Signed/Encrypted Message?
395  if (fullySignedOrEncrypted) {
396  if (mSignatureState == KMMsgPartiallySigned) {
397  mSignatureState = KMMsgFullySigned;
398  }
399  if (mEncryptionState == KMMsgPartiallyEncrypted) {
400  mEncryptionState = KMMsgFullyEncrypted;
401  }
402  }
403  }
404 }
405 
406 KMMsgEncryptionState TextMessagePart::encryptionState() const
407 {
408  return mEncryptionState;
409 }
410 
411 KMMsgSignatureState TextMessagePart::signatureState() const
412 {
413  return mSignatureState;
414 }
415 
416 bool TextMessagePart::showLink() const
417 {
418  return !temporaryFilePath().isEmpty();
419 }
420 
421 bool TextMessagePart::isFirstTextPart() const
422 {
423  return content()->topLevel()->textContent() == content();
424 }
425 
426 bool TextMessagePart::hasLabel() const
427 {
428  return !NodeHelper::fileName(content()).isEmpty();
429 }
430 
431 QString TextMessagePart::label() const
432 {
433  const QString name = content()->contentType()->name();
434  QString label = name.isEmpty() ? NodeHelper::fileName(content()) : name;
435  if (label.isEmpty()) {
436  label = i18nc("display name for an unnamed attachment", "Unnamed");
437  }
438  return label;
439 }
440 
441 QString TextMessagePart::comment() const
442 {
443  const QString comment = content()->contentDescription()->asUnicodeString();
444  if (comment == label()) {
445  return {};
446  }
447  return comment;
448 }
449 
450 QString TextMessagePart::temporaryFilePath() const
451 {
452  return nodeHelper()->writeNodeToTempFile(content());
453 }
454 
455 //-----AttachmentMessageBlock----------------------
456 
457 AttachmentMessagePart::AttachmentMessagePart(ObjectTreeParser *otp, KMime::Content *node, bool decryptMessage)
458  : TextMessagePart(otp, node, decryptMessage)
459 {
460 }
461 
462 AttachmentMessagePart::~AttachmentMessagePart()
463 {
464 }
465 
466 //-----HtmlMessageBlock----------------------
467 
468 HtmlMessagePart::HtmlMessagePart(ObjectTreeParser *otp, KMime::Content *node, Interface::ObjectTreeSource *source)
469  : MessagePart(otp, QString())
470  , mSource(source)
471 {
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 = mOtp->codecFor(node)->toUnicode(partBody);
480  mCharset = NodeHelper::charset(node);
481 }
482 
483 HtmlMessagePart::~HtmlMessagePart()
484 {
485 }
486 
487 void HtmlMessagePart::fix() const
488 {
489  mOtp->mHtmlContent += mBodyHTML;
490  mOtp->mHtmlContentCharset = mCharset;
491 }
492 
493 QString HtmlMessagePart::text() const
494 {
495  return mBodyHTML;
496 }
497 
498 QString MimeTreeParser::HtmlMessagePart::plaintextContent() const
499 {
500  return QString();
501 }
502 
503 bool HtmlMessagePart::isHtml() const
504 {
505  return true;
506 }
507 
508 QString HtmlMessagePart::bodyHtml() const
509 {
510  return mBodyHTML;
511 }
512 
513 //-----MimeMessageBlock----------------------
514 
515 MimeMessagePart::MimeMessagePart(ObjectTreeParser *otp, KMime::Content *node, bool onlyOneMimePart)
516  : MessagePart(otp, QString())
517  , mOnlyOneMimePart(onlyOneMimePart)
518 {
519  if (!node) {
520  qCWarning(MIMETREEPARSER_LOG) << "not a valid node";
521  return;
522  }
523  setContent(node);
524 
525  parseInternal(node, mOnlyOneMimePart);
526 }
527 
528 MimeMessagePart::~MimeMessagePart()
529 {
530 }
531 
532 QString MimeMessagePart::text() const
533 {
534  return renderInternalText();
535 }
536 
537 QString MimeMessagePart::plaintextContent() const
538 {
539  return QString();
540 }
541 
542 QString MimeMessagePart::htmlContent() const
543 {
544  return QString();
545 }
546 
547 //-----AlternativeMessagePart----------------------
548 
549 AlternativeMessagePart::AlternativeMessagePart(ObjectTreeParser *otp, KMime::Content *node, Util::HtmlMode preferredMode)
550  : MessagePart(otp, QString())
551  , mPreferredMode(preferredMode)
552 {
553  setContent(node);
554  KMime::Content *dataIcal = findTypeInDirectChilds(node, "text/calendar");
555  KMime::Content *dataHtml = findTypeInDirectChilds(node, "text/html");
556  KMime::Content *dataText = findTypeInDirectChilds(node, "text/plain");
557 
558  if (!dataHtml) {
559  // If we didn't find the HTML part as the first child of the multipart/alternative, it might
560  // be that this is a HTML message with images, and text/plain and multipart/related are the
561  // immediate children of this multipart/alternative node.
562  // In this case, the HTML node is a child of multipart/related.
563  dataHtml = findTypeInDirectChilds(node, "multipart/related");
564 
565  // Still not found? Stupid apple mail actually puts the attachments inside of the
566  // multipart/alternative, which is wrong. Therefore we also have to look for multipart/mixed
567  // here.
568  // Do this only when preferring HTML mail, though, since otherwise the attachments are hidden
569  // when displaying plain text.
570  if (!dataHtml) {
571  dataHtml = findTypeInDirectChilds(node, "multipart/mixed");
572  }
573  }
574 
575  if (dataIcal) {
576  mChildNodes[Util::MultipartIcal] = dataIcal;
577  }
578 
579  if (dataText) {
580  mChildNodes[Util::MultipartPlain] = dataText;
581  }
582 
583  if (dataHtml) {
584  mChildNodes[Util::MultipartHtml] = dataHtml;
585  }
586 
587  if (mChildNodes.isEmpty()) {
588  qCWarning(MIMETREEPARSER_LOG) << "no valid nodes";
589  return;
590  }
591 
593  while (i.hasNext()) {
594  i.next();
595  mChildParts[i.key()] = MimeMessagePart::Ptr(new MimeMessagePart(mOtp, i.value(), true));
596  }
597 }
598 
599 AlternativeMessagePart::~AlternativeMessagePart()
600 {
601 }
602 
603 Util::HtmlMode AlternativeMessagePart::preferredMode() const
604 {
605  return mPreferredMode;
606 }
607 
608 void AlternativeMessagePart::setPreferredMode(Util::HtmlMode preferredMode)
609 {
610  mPreferredMode = preferredMode;
611 }
612 
613 QList<Util::HtmlMode> AlternativeMessagePart::availableModes()
614 {
615  return mChildParts.keys();
616 }
617 
618 QString AlternativeMessagePart::text() const
619 {
620  if (mChildParts.contains(Util::MultipartPlain)) {
621  return mChildParts[Util::MultipartPlain]->text();
622  }
623  return QString();
624 }
625 
626 void AlternativeMessagePart::fix() const
627 {
628  if (mChildParts.contains(Util::MultipartPlain)) {
629  mChildParts[Util::MultipartPlain]->fix();
630  }
631 
632  const auto mode = preferredMode();
633  if (mode != Util::MultipartPlain && mChildParts.contains(mode)) {
634  mChildParts[mode]->fix();
635  }
636 }
637 
638 const QMap<Util::HtmlMode, MimeMessagePart::Ptr> &AlternativeMessagePart::childParts() const
639 {
640  return mChildParts;
641 }
642 
643 bool AlternativeMessagePart::isHtml() const
644 {
645  return mChildParts.contains(Util::MultipartHtml);
646 }
647 
648 QString AlternativeMessagePart::plaintextContent() const
649 {
650  return text();
651 }
652 
653 QString AlternativeMessagePart::htmlContent() const
654 {
655  if (mChildParts.contains(Util::MultipartHtml)) {
656  return mChildParts[Util::MultipartHtml]->text();
657  } else {
658  return plaintextContent();
659  }
660 }
661 
662 //-----CertMessageBlock----------------------
663 
664 CertMessagePart::CertMessagePart(ObjectTreeParser *otp, KMime::Content *node, const QGpgME::Protocol *cryptoProto, bool autoImport)
665  : MessagePart(otp, QString())
666  , mAutoImport(autoImport)
667  , mCryptoProto(cryptoProto)
668 {
669  if (!node) {
670  qCWarning(MIMETREEPARSER_LOG) << "not a valid node";
671  return;
672  }
673  setContent(node);
674 
675  if (!mAutoImport) {
676  return;
677  }
678 
679  const QByteArray certData = node->decodedContent();
680 
681  QGpgME::ImportJob *import = mCryptoProto->importJob();
682  QGpgMEJobExecutor executor;
683  mImportResult = executor.exec(import, certData);
684 }
685 
686 CertMessagePart::~CertMessagePart()
687 {
688 }
689 
690 QString CertMessagePart::text() const
691 {
692  return QString();
693 }
694 
695 const GpgME::ImportResult &CertMessagePart::importResult() const
696 {
697  return mImportResult;
698 }
699 
700 //-----SignedMessageBlock---------------------
701 SignedMessagePart::SignedMessagePart(ObjectTreeParser *otp,
702  const QString &text,
703  const QGpgME::Protocol *cryptoProto,
704  const QString &fromAddress,
705  KMime::Content *node)
706  : MessagePart(otp, text)
707  , mCryptoProto(cryptoProto)
708  , mFromAddress(fromAddress)
709  , mMementoName("verification")
710 {
711  setContent(node);
712  partMetaData()->technicalProblem = (mCryptoProto == nullptr);
713  partMetaData()->isSigned = true;
714  partMetaData()->isGoodSignature = false;
715  partMetaData()->keyTrust = GpgME::Signature::Unknown;
716  partMetaData()->status = i18n("Wrong Crypto Plug-In.");
717  partMetaData()->status_code = GPGME_SIG_STAT_NONE;
718 }
719 
720 SignedMessagePart::~SignedMessagePart()
721 {
722 }
723 
724 void SignedMessagePart::setIsSigned(bool isSigned)
725 {
726  partMetaData()->isSigned = isSigned;
727 }
728 
729 bool SignedMessagePart::isSigned() const
730 {
731  return partMetaData()->isSigned;
732 }
733 
734 QByteArray SignedMessagePart::mementoName() const
735 {
736  return mMementoName;
737 }
738 
739 void SignedMessagePart::setMementoName(const QByteArray &name)
740 {
741  mMementoName = name;
742 }
743 
744 bool SignedMessagePart::okVerify(const QByteArray &data, const QByteArray &signature, KMime::Content *textNode)
745 {
746  NodeHelper *nodeHelper = mOtp->nodeHelper();
747 
748  partMetaData()->isSigned = false;
749  partMetaData()->technicalProblem = (mCryptoProto == nullptr);
750  partMetaData()->keyTrust = GpgME::Signature::Unknown;
751  partMetaData()->status = i18n("Wrong Crypto Plug-In.");
752  partMetaData()->status_code = GPGME_SIG_STAT_NONE;
753 
754  const QByteArray _mementoName = mementoName();
755 
756  auto *m = dynamic_cast<CryptoBodyPartMemento *>(nodeHelper->bodyPartMemento(content(), _mementoName));
757  Q_ASSERT(!m || mCryptoProto); // No CryptoPlugin and having a bodyPartMemento -> there is something completely wrong
758 
759  if (!m && mCryptoProto) {
760  if (!signature.isEmpty()) {
761  QGpgME::VerifyDetachedJob *job = mCryptoProto->verifyDetachedJob();
762  if (job) {
763  m = new VerifyDetachedBodyPartMemento(job, mCryptoProto->keyListJob(), signature, data);
764  }
765  } else {
766  QGpgME::VerifyOpaqueJob *job = mCryptoProto->verifyOpaqueJob();
767  if (job) {
768  m = new VerifyOpaqueBodyPartMemento(job, mCryptoProto->keyListJob(), data);
769  }
770  }
771  if (m) {
772  if (mOtp->allowAsync()) {
773  QObject::connect(m, &CryptoBodyPartMemento::update, nodeHelper, &NodeHelper::update);
774  if (m->start()) {
775  partMetaData()->inProgress = true;
776  mOtp->mHasPendingAsyncJobs = true;
777  }
778  } else {
779  m->exec();
780  }
781  nodeHelper->setBodyPartMemento(content(), _mementoName, m);
782  }
783  } else if (m && m->isRunning()) {
784  partMetaData()->inProgress = true;
785  mOtp->mHasPendingAsyncJobs = true;
786  } else {
787  partMetaData()->inProgress = false;
788  mOtp->mHasPendingAsyncJobs = false;
789  }
790 
791  if (m && !partMetaData()->inProgress) {
792  if (!signature.isEmpty()) {
793  mVerifiedText = data;
794  }
795  setVerificationResult(m, textNode);
796  }
797 
798  if (!m && !partMetaData()->inProgress) {
799  QString errorMsg;
800  QString cryptPlugLibName;
801  QString cryptPlugDisplayName;
802  if (mCryptoProto) {
803  cryptPlugLibName = mCryptoProto->name();
804  cryptPlugDisplayName = mCryptoProto->displayName();
805  }
806 
807  if (!mCryptoProto) {
808  if (cryptPlugDisplayName.isEmpty()) {
809  errorMsg = i18n("No appropriate crypto plug-in was found.");
810  } else {
811  errorMsg = i18nc("%1 is either 'OpenPGP' or 'S/MIME'", "No %1 plug-in was found.", cryptPlugDisplayName);
812  }
813  } else {
814  errorMsg = i18n("Crypto plug-in \"%1\" cannot verify signatures.", cryptPlugLibName);
815  }
816  partMetaData()->errorText = i18n(
817  "The message is signed, but the "
818  "validity of the signature cannot be "
819  "verified.<br />"
820  "Reason: %1",
821  errorMsg);
822  }
823 
824  return partMetaData()->isSigned;
825 }
826 
827 static int signatureToStatus(const GpgME::Signature &sig)
828 {
829  switch (sig.status().code()) {
830  case GPG_ERR_NO_ERROR:
831  return GPGME_SIG_STAT_GOOD;
832  case GPG_ERR_BAD_SIGNATURE:
833  return GPGME_SIG_STAT_BAD;
834  case GPG_ERR_NO_PUBKEY:
835  return GPGME_SIG_STAT_NOKEY;
836  case GPG_ERR_NO_DATA:
837  return GPGME_SIG_STAT_NOSIG;
838  case GPG_ERR_SIG_EXPIRED:
839  return GPGME_SIG_STAT_GOOD_EXP;
840  case GPG_ERR_KEY_EXPIRED:
841  return GPGME_SIG_STAT_GOOD_EXPKEY;
842  default:
843  return GPGME_SIG_STAT_ERROR;
844  }
845 }
846 
847 QString prettifyDN(const char *uid)
848 {
849  return QGpgME::DN(uid).prettyDN();
850 }
851 
852 void SignedMessagePart::sigStatusToMetaData()
853 {
854  GpgME::Key key;
855  if (partMetaData()->isSigned) {
856  GpgME::Signature signature = mSignatures.front();
857  partMetaData()->status_code = signatureToStatus(signature);
858  partMetaData()->isGoodSignature = partMetaData()->status_code & GPGME_SIG_STAT_GOOD;
859  // save extended signature status flags
860  partMetaData()->sigSummary = signature.summary();
861 
862  if (partMetaData()->isGoodSignature && !key.keyID()) {
863  // Search for the key by its fingerprint so that we can check for
864  // trust etc.
865  QGpgME::KeyListJob *job = mCryptoProto->keyListJob(false, false, false); // local, no sigs
866  if (!job) {
867  qCDebug(MIMETREEPARSER_LOG) << "The Crypto backend does not support listing keys. ";
868  } else {
869  std::vector<GpgME::Key> found_keys;
870  // As we are local it is ok to make this synchronous
871  GpgME::KeyListResult res = job->exec(QStringList(QLatin1String(signature.fingerprint())), false, found_keys);
872  if (res.error()) {
873  qCDebug(MIMETREEPARSER_LOG) << "Error while searching key for Fingerprint: " << signature.fingerprint();
874  }
875  if (found_keys.size() > 1) {
876  // Should not Happen
877  qCDebug(MIMETREEPARSER_LOG) << "Oops: Found more then one Key for Fingerprint: " << signature.fingerprint();
878  }
879  if (found_keys.size() != 1) {
880  // Should not Happen at this point
881  qCDebug(MIMETREEPARSER_LOG) << "Oops: Found no Key for Fingerprint: " << signature.fingerprint();
882  } else {
883  key = found_keys[0];
884  }
885  delete job;
886  }
887  }
888 
889  if (key.keyID()) {
890  partMetaData()->keyId = key.keyID();
891  }
892  if (partMetaData()->keyId.isEmpty()) {
893  partMetaData()->keyId = signature.fingerprint();
894  }
895  partMetaData()->keyTrust = signature.validity();
896  if (key.numUserIDs() > 0 && key.userID(0).id()) {
897  partMetaData()->signer = prettifyDN(key.userID(0).id());
898  }
899  for (uint iMail = 0; iMail < key.numUserIDs(); ++iMail) {
900  // The following if /should/ always result in TRUE but we
901  // won't trust implicitly the plugin that gave us these data.
902  if (key.userID(iMail).email()) {
903  QString email = QString::fromUtf8(key.userID(iMail).email());
904  // ### work around gpgme 0.3.QString text() const override;x / cryptplug bug where the
905  // ### email addresses are specified as angle-addr, not addr-spec:
906  if (email.startsWith(QLatin1Char('<')) && email.endsWith(QLatin1Char('>'))) {
907  email = email.mid(1, email.length() - 2);
908  }
909  if (!email.isEmpty()) {
910  partMetaData()->signerMailAddresses.append(email);
911  }
912  }
913  }
914 
915  if (signature.creationTime()) {
916  partMetaData()->creationTime.setSecsSinceEpoch(signature.creationTime());
917  } else {
918  partMetaData()->creationTime = QDateTime();
919  }
920  if (partMetaData()->signer.isEmpty()) {
921  if (key.numUserIDs() > 0 && key.userID(0).name()) {
922  partMetaData()->signer = prettifyDN(key.userID(0).name());
923  }
924  if (!partMetaData()->signerMailAddresses.empty()) {
925  if (partMetaData()->signer.isEmpty()) {
926  partMetaData()->signer = partMetaData()->signerMailAddresses.front();
927  } else {
928  partMetaData()->signer += QLatin1String(" <") + partMetaData()->signerMailAddresses.front() + QLatin1Char('>');
929  }
930  }
931  }
932  }
933 }
934 
935 void SignedMessagePart::startVerification(const QByteArray &text, const QTextCodec *aCodec)
936 {
937  startVerificationDetached(text, nullptr, QByteArray());
938 
939  if (!content() && partMetaData()->isSigned) {
940  setText(aCodec->toUnicode(mVerifiedText));
941  }
942 }
943 
944 void SignedMessagePart::startVerificationDetached(const QByteArray &text, KMime::Content *textNode, const QByteArray &signature)
945 {
946  partMetaData()->isEncrypted = false;
947  partMetaData()->isDecryptable = false;
948 
949  if (textNode) {
950  parseInternal(textNode, false);
951  }
952 
953  if (!okVerify(text, signature, textNode)) {
954  partMetaData()->creationTime = QDateTime();
955  }
956 }
957 
958 void SignedMessagePart::setVerificationResult(const CryptoBodyPartMemento *m, KMime::Content *textNode)
959 {
960  {
961  const auto vm = dynamic_cast<const VerifyDetachedBodyPartMemento *>(m);
962  if (vm) {
963  mSignatures = vm->verifyResult().signatures();
964  }
965  }
966  {
967  const auto vm = dynamic_cast<const VerifyOpaqueBodyPartMemento *>(m);
968  if (vm) {
969  mVerifiedText = vm->plainText();
970  mSignatures = vm->verifyResult().signatures();
971  }
972  }
973  {
974  const auto vm = dynamic_cast<const DecryptVerifyBodyPartMemento *>(m);
975  if (vm) {
976  mVerifiedText = vm->plainText();
977  mSignatures = vm->verifyResult().signatures();
978  }
979  }
980  partMetaData()->auditLogError = m->auditLogError();
981  partMetaData()->auditLog = m->auditLogAsHtml();
982  partMetaData()->isSigned = !mSignatures.empty();
983 
984  if (partMetaData()->isSigned) {
985  sigStatusToMetaData();
986  if (content()) {
987  mOtp->nodeHelper()->setSignatureState(content(), KMMsgFullySigned);
988  if (!textNode) {
989  mOtp->nodeHelper()->setPartMetaData(content(), *partMetaData());
990 
991  if (!mVerifiedText.isEmpty()) {
992  auto tempNode = new KMime::Content();
993  tempNode->setContent(KMime::CRLFtoLF(mVerifiedText.constData()));
994  tempNode->parse();
995 
996  if (!tempNode->head().isEmpty()) {
997  tempNode->contentDescription()->from7BitString("signed data");
998  }
999  mOtp->nodeHelper()->attachExtraContent(content(), tempNode);
1000 
1001  parseInternal(tempNode, false);
1002  }
1003  }
1004  }
1005  }
1006 }
1007 
1008 QString SignedMessagePart::plaintextContent() const
1009 {
1010  if (!content()) {
1011  return MessagePart::text();
1012  } else {
1013  return QString();
1014  }
1015 }
1016 
1017 QString SignedMessagePart::htmlContent() const
1018 {
1019  if (!content()) {
1020  return MessagePart::text();
1021  } else {
1022  return QString();
1023  }
1024 }
1025 
1026 const QGpgME::Protocol *SignedMessagePart::cryptoProto() const
1027 {
1028  return mCryptoProto;
1029 }
1030 
1031 QString SignedMessagePart::fromAddress() const
1032 {
1033  return mFromAddress;
1034 }
1035 
1036 bool SignedMessagePart::hasHeader(const char *headerType) const
1037 {
1038  if (content()) {
1039  return content()->hasHeader(headerType);
1040  }
1041  return false;
1042 }
1043 
1044 const KMime::Headers::Base *MimeTreeParser::SignedMessagePart::header(const char *headerType) const
1045 {
1046  if (content()) {
1047  return content()->headerByType(headerType);
1048  }
1049  return nullptr;
1050 }
1051 
1052 QVector<KMime::Headers::Base *> SignedMessagePart::headers(const char *headerType) const
1053 {
1054  if (content()) {
1055  return content()->headersByType(headerType);
1056  }
1058 }
1059 
1060 //-----CryptMessageBlock---------------------
1061 EncryptedMessagePart::EncryptedMessagePart(ObjectTreeParser *otp,
1062  const QString &text,
1063  const QGpgME::Protocol *cryptoProto,
1064  const QString &fromAddress,
1065  KMime::Content *node)
1066  : MessagePart(otp, text)
1067  , mPassphraseError(false)
1068  , mNoSecKey(false)
1069  , mDecryptMessage(false)
1070  , mCryptoProto(cryptoProto)
1071  , mFromAddress(fromAddress)
1072  , mMementoName("decryptverify")
1073 {
1074  setContent(node);
1075  partMetaData()->technicalProblem = (mCryptoProto == nullptr);
1076  partMetaData()->isSigned = false;
1077  partMetaData()->isGoodSignature = false;
1078  partMetaData()->isEncrypted = false;
1079  partMetaData()->isDecryptable = false;
1080  partMetaData()->keyTrust = GpgME::Signature::Unknown;
1081  partMetaData()->status = i18n("Wrong Crypto Plug-In.");
1082  partMetaData()->status_code = GPGME_SIG_STAT_NONE;
1083 }
1084 
1085 EncryptedMessagePart::~EncryptedMessagePart()
1086 {
1087 }
1088 
1089 void EncryptedMessagePart::setDecryptMessage(bool decrypt)
1090 {
1091  mDecryptMessage = decrypt;
1092 }
1093 
1094 bool EncryptedMessagePart::decryptMessage() const
1095 {
1096  return mDecryptMessage;
1097 }
1098 
1099 void EncryptedMessagePart::setIsEncrypted(bool encrypted)
1100 {
1101  partMetaData()->isEncrypted = encrypted;
1102 }
1103 
1104 bool EncryptedMessagePart::isEncrypted() const
1105 {
1106  return partMetaData()->isEncrypted;
1107 }
1108 
1109 bool EncryptedMessagePart::isDecryptable() const
1110 {
1111  return partMetaData()->isDecryptable;
1112 }
1113 
1114 bool EncryptedMessagePart::isNoSecKey() const
1115 {
1116  return mNoSecKey;
1117 }
1118 
1119 bool EncryptedMessagePart::passphraseError() const
1120 {
1121  return mPassphraseError;
1122 }
1123 
1124 QByteArray EncryptedMessagePart::mementoName() const
1125 {
1126  return mMementoName;
1127 }
1128 
1129 void EncryptedMessagePart::setMementoName(const QByteArray &name)
1130 {
1131  mMementoName = name;
1132 }
1133 
1134 void EncryptedMessagePart::startDecryption(const QByteArray &text, const QTextCodec *aCodec)
1135 {
1136  auto content = new KMime::Content;
1137  content->setBody(text);
1138  content->parse();
1139 
1140  startDecryption(content);
1141 
1142  if (!partMetaData()->inProgress && partMetaData()->isDecryptable) {
1143  if (hasSubParts()) {
1144  auto _mp = (subParts()[0]).dynamicCast<SignedMessagePart>();
1145  if (_mp) {
1146  _mp->setText(aCodec->toUnicode(mDecryptedData));
1147  } else {
1148  setText(aCodec->toUnicode(mDecryptedData));
1149  }
1150  } else {
1151  setText(aCodec->toUnicode(mDecryptedData));
1152  }
1153  }
1154  delete content;
1155 }
1156 
1157 bool EncryptedMessagePart::okDecryptMIME(KMime::Content &data)
1158 {
1159  mPassphraseError = false;
1160  partMetaData()->inProgress = false;
1161  partMetaData()->errorText.clear();
1162  partMetaData()->auditLogError = GpgME::Error();
1163  partMetaData()->auditLog.clear();
1164  bool bDecryptionOk = false;
1165  bool cannotDecrypt = false;
1166  NodeHelper *nodeHelper = mOtp->nodeHelper();
1167 
1168  Q_ASSERT(decryptMessage());
1169 
1170  const QByteArray _mementoName = mementoName();
1171  // Check whether the memento contains a result from last time:
1172  const DecryptVerifyBodyPartMemento *m = dynamic_cast<DecryptVerifyBodyPartMemento *>(nodeHelper->bodyPartMemento(&data, _mementoName));
1173 
1174  Q_ASSERT(!m || mCryptoProto); // No CryptoPlugin and having a bodyPartMemento -> there is something completely wrong
1175 
1176  if (!m && mCryptoProto) {
1177  QGpgME::DecryptVerifyJob *job = mCryptoProto->decryptVerifyJob();
1178  if (!job) {
1179  cannotDecrypt = true;
1180  } else {
1181  const QByteArray ciphertext = data.decodedContent();
1182  auto newM = new DecryptVerifyBodyPartMemento(job, ciphertext);
1183  if (mOtp->allowAsync()) {
1184  QObject::connect(newM, &CryptoBodyPartMemento::update, nodeHelper, &NodeHelper::update);
1185  if (newM->start()) {
1186  partMetaData()->inProgress = true;
1187  mOtp->mHasPendingAsyncJobs = true;
1188  } else {
1189  m = newM;
1190  }
1191  } else {
1192  newM->exec();
1193  m = newM;
1194  }
1195  nodeHelper->setBodyPartMemento(&data, _mementoName, newM);
1196  }
1197  } else if (m && m->isRunning()) {
1198  partMetaData()->inProgress = true;
1199  mOtp->mHasPendingAsyncJobs = true;
1200  m = nullptr;
1201  }
1202 
1203  if (m) {
1204  const QByteArray &plainText = m->plainText();
1205  const GpgME::DecryptionResult &decryptResult = m->decryptResult();
1206  const GpgME::VerificationResult &verifyResult = m->verifyResult();
1207  partMetaData()->isSigned = verifyResult.signatures().size() > 0;
1208 
1209  if (partMetaData()->isSigned) {
1210  auto subPart = SignedMessagePart::Ptr(new SignedMessagePart(mOtp, MessagePart::text(), mCryptoProto, mFromAddress, content()));
1211  subPart->setVerificationResult(m, nullptr);
1212  appendSubPart(subPart);
1213  }
1214 
1215  mDecryptRecipients.clear();
1216  bDecryptionOk = !decryptResult.error();
1217 
1218  // std::stringstream ss;
1219  // ss << decryptResult << '\n' << verifyResult;
1220  // qCDebug(MIMETREEPARSER_LOG) << ss.str().c_str();
1221 
1222  for (const auto &recipient : decryptResult.recipients()) {
1223  if (!recipient.status()) {
1224  bDecryptionOk = true;
1225  }
1226  GpgME::Key key;
1227  QGpgME::KeyListJob *job = mCryptoProto->keyListJob(false, false, false); // local, no sigs
1228  if (!job) {
1229  qCDebug(MIMETREEPARSER_LOG) << "The Crypto backend does not support listing keys. ";
1230  } else {
1231  std::vector<GpgME::Key> found_keys;
1232  // As we are local it is ok to make this synchronous
1233  GpgME::KeyListResult res = job->exec(QStringList(QLatin1String(recipient.keyID())), false, found_keys);
1234  if (res.error()) {
1235  qCDebug(MIMETREEPARSER_LOG) << "Error while searching key for Fingerprint: " << recipient.keyID();
1236  }
1237  if (found_keys.size() > 1) {
1238  // Should not Happen
1239  qCDebug(MIMETREEPARSER_LOG) << "Oops: Found more then one Key for Fingerprint: " << recipient.keyID();
1240  }
1241  if (found_keys.size() != 1) {
1242  // Should not Happen at this point
1243  qCDebug(MIMETREEPARSER_LOG) << "Oops: Found no Key for Fingerprint: " << recipient.keyID();
1244  } else {
1245  key = found_keys[0];
1246  }
1247  }
1248  mDecryptRecipients.push_back(std::make_pair(recipient, key));
1249  }
1250 
1251  if (!bDecryptionOk && partMetaData()->isSigned) {
1252  // Only a signed part
1253  partMetaData()->isEncrypted = false;
1254  bDecryptionOk = true;
1255  mDecryptedData = plainText;
1256  } else {
1257  mPassphraseError = decryptResult.error().isCanceled() || decryptResult.error().code() == GPG_ERR_NO_SECKEY;
1258  partMetaData()->isEncrypted = bDecryptionOk || decryptResult.error().code() != GPG_ERR_NO_DATA;
1259 
1260  if (decryptResult.error().isCanceled()) {
1261  setDecryptMessage(false);
1262  }
1263 
1264  partMetaData()->errorText = QString::fromLocal8Bit(decryptResult.error().asString());
1265  if (partMetaData()->isEncrypted && decryptResult.numRecipients() > 0) {
1266  partMetaData()->keyId = decryptResult.recipient(0).keyID();
1267  }
1268 
1269  if (bDecryptionOk) {
1270  mDecryptedData = plainText;
1271  } else {
1272  mNoSecKey = true;
1273  const auto decryRecipients = decryptResult.recipients();
1274  for (const GpgME::DecryptionResult::Recipient &recipient : decryRecipients) {
1275  mNoSecKey &= (recipient.status().code() == GPG_ERR_NO_SECKEY);
1276  }
1277  if (!mPassphraseError && !mNoSecKey) { // GpgME do not detect passphrase error correctly
1278  mPassphraseError = true;
1279  }
1280  }
1281  }
1282  }
1283 
1284  if (!bDecryptionOk) {
1285  QString cryptPlugLibName;
1286  if (mCryptoProto) {
1287  cryptPlugLibName = mCryptoProto->name();
1288  }
1289 
1290  if (!mCryptoProto) {
1291  partMetaData()->errorText = i18n("No appropriate crypto plug-in was found.");
1292  } else if (cannotDecrypt) {
1293  partMetaData()->errorText = i18n("Crypto plug-in \"%1\" cannot decrypt messages.", cryptPlugLibName);
1294  } else if (!passphraseError()) {
1295  partMetaData()->errorText = i18n("Crypto plug-in \"%1\" could not decrypt the data.", cryptPlugLibName) + QLatin1String("<br />")
1296  + i18n("Error: %1", partMetaData()->errorText);
1297  }
1298  }
1299  return bDecryptionOk;
1300 }
1301 
1302 void EncryptedMessagePart::startDecryption(KMime::Content *data)
1303 {
1304  if (!content() && !data) {
1305  return;
1306  }
1307 
1308  if (!data) {
1309  data = content();
1310  }
1311 
1312  partMetaData()->isEncrypted = true;
1313 
1314  bool bOkDecrypt = okDecryptMIME(*data);
1315 
1316  if (partMetaData()->inProgress) {
1317  return;
1318  }
1319  partMetaData()->isDecryptable = bOkDecrypt;
1320 
1321  if (!partMetaData()->isDecryptable) {
1322  setText(QString::fromUtf8(mDecryptedData.constData()));
1323  }
1324 
1325  if (partMetaData()->isEncrypted && !decryptMessage()) {
1326  partMetaData()->isDecryptable = true;
1327  }
1328 
1329  if (content() && !partMetaData()->isSigned) {
1330  mOtp->nodeHelper()->setPartMetaData(content(), *partMetaData());
1331 
1332  if (decryptMessage()) {
1333  auto tempNode = new KMime::Content();
1334  tempNode->setContent(KMime::CRLFtoLF(mDecryptedData.constData()));
1335  tempNode->parse();
1336 
1337  if (!tempNode->head().isEmpty()) {
1338  tempNode->contentDescription()->from7BitString("encrypted data");
1339  }
1340  mOtp->nodeHelper()->attachExtraContent(content(), tempNode);
1341 
1342  parseInternal(tempNode, false);
1343  }
1344  }
1345 }
1346 
1347 QString EncryptedMessagePart::plaintextContent() const
1348 {
1349  if (!content()) {
1350  return MessagePart::text();
1351  } else {
1352  return QString();
1353  }
1354 }
1355 
1356 QString EncryptedMessagePart::htmlContent() const
1357 {
1358  if (!content()) {
1359  return MessagePart::text();
1360  } else {
1361  return QString();
1362  }
1363 }
1364 
1365 QString EncryptedMessagePart::text() const
1366 {
1367  if (hasSubParts()) {
1368  auto _mp = (subParts()[0]).dynamicCast<SignedMessagePart>();
1369  if (_mp) {
1370  return _mp->text();
1371  } else {
1372  return MessagePart::text();
1373  }
1374  } else {
1375  return MessagePart::text();
1376  }
1377 }
1378 
1379 const QGpgME::Protocol *EncryptedMessagePart::cryptoProto() const
1380 {
1381  return mCryptoProto;
1382 }
1383 
1384 QString EncryptedMessagePart::fromAddress() const
1385 {
1386  return mFromAddress;
1387 }
1388 
1389 const std::vector<std::pair<GpgME::DecryptionResult::Recipient, GpgME::Key>> &EncryptedMessagePart::decryptRecipients() const
1390 {
1391  return mDecryptRecipients;
1392 }
1393 
1394 bool EncryptedMessagePart::hasHeader(const char *headerType) const
1395 {
1396  const auto extraContent = mOtp->nodeHelper()->decryptedNodeForContent(content());
1397  if (extraContent) {
1398  return nodeHelper()->hasMailHeader(headerType, extraContent);
1399  }
1400  return false;
1401 }
1402 
1403 const KMime::Headers::Base *EncryptedMessagePart::header(const char *headerType) const
1404 {
1405  const auto extraContent = mOtp->nodeHelper()->decryptedNodeForContent(content());
1406  if (extraContent) {
1407  return nodeHelper()->mailHeaderAsBase(headerType, extraContent);
1408  }
1409  return nullptr;
1410 }
1411 
1412 QVector<KMime::Headers::Base *> EncryptedMessagePart::headers(const char *headerType) const
1413 {
1414  const auto extraContent = mOtp->nodeHelper()->decryptedNodeForContent(content());
1415  if (extraContent) {
1416  return nodeHelper()->headers(headerType, extraContent);
1417  }
1419 }
1420 
1421 EncapsulatedRfc822MessagePart::EncapsulatedRfc822MessagePart(ObjectTreeParser *otp, KMime::Content *node, const KMime::Message::Ptr &message)
1422  : MessagePart(otp, QString())
1423  , mMessage(message)
1424 {
1425  setContent(node);
1426  partMetaData()->isEncrypted = false;
1427  partMetaData()->isSigned = false;
1428  partMetaData()->isEncapsulatedRfc822Message = true;
1429 
1430  mOtp->nodeHelper()->setNodeDisplayedEmbedded(node, true);
1431  mOtp->nodeHelper()->setPartMetaData(node, *partMetaData());
1432 
1433  if (!mMessage) {
1434  qCWarning(MIMETREEPARSER_LOG) << "Node is of type message/rfc822 but doesn't have a message!";
1435  return;
1436  }
1437 
1438  // The link to "Encapsulated message" is clickable, therefore the temp file needs to exists,
1439  // since the user can click the link and expect to have normal attachment operations there.
1440  mOtp->nodeHelper()->writeNodeToTempFile(message.data());
1441 
1442  parseInternal(message.data(), false);
1443 }
1444 
1445 EncapsulatedRfc822MessagePart::~EncapsulatedRfc822MessagePart()
1446 {
1447 }
1448 
1449 QString EncapsulatedRfc822MessagePart::text() const
1450 {
1451  return renderInternalText();
1452 }
1453 
1454 void EncapsulatedRfc822MessagePart::fix() const
1455 {
1456 }
1457 
1458 const KMime::Message::Ptr EncapsulatedRfc822MessagePart::message() const
1459 {
1460  return mMessage;
1461 }
QString & append(QChar ch)
bool contains(const Key &key) const const
HtmlMode
Describes the type of the displayed message.
QChar front() const const
bool isEmpty() const const
T * data() const const
QString number(int n, int base)
QString fromLocal8Bit(const char *str, int size)
QByteArray decodedContent()
QString fromUtf8(const char *str, int size)
QMapIterator::Item next()
QString i18nc(const char *context, const char *text, const TYPE &arg...)
bool isEmpty() const const
void setBody(const QByteArray &body)
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const const
bool endsWith(const QString &s, Qt::CaseSensitivity cs) const const
const Key & key() const const
const T & value() const const
QString i18n(const char *text, const TYPE &arg...)
QString mid(int position, int n) const const
Interface for object tree sources.
interface of classes that implement status for BodyPartFormatters.
Definition: bodypart.h:34
bool isEmpty() const const
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
QByteArray toPercentEncoding(const QString &input, const QByteArray &exclude, const QByteArray &include)
QLatin1Char front() const const
int length() const const
Parses messages and generates HTML display code out of them.
QString fromLatin1(const char *str, int size)
char front() const const
int size() const const
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QSharedPointer< X > staticCast() const const
Helper class for synchronous execution of Kleo crypto jobs.
QString toUnicode(const QByteArray &a) const const
bool hasNext() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Wed Mar 3 2021 00:13:25 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.