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 
446 QString TextMessagePart::temporaryFilePath() const
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 }
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
const QLatin1String name
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:33
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 Fri Nov 26 2021 23:16:42 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.