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

KDE's Doxygen guidelines are available online.