Messagelib

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

KDE's Doxygen guidelines are available online.