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;
349 if (!fullySignedOrEncryptedTmp) {
350 fullySignedOrEncrypted = false;
351 }
352
353 if (block.type() == NoPgpBlock && !block.text().trimmed().isEmpty()) {
354 fullySignedOrEncryptedTmp = false;
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?
397 if (fullySignedOrEncrypted) {
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}
489
490QString HtmlMessagePart::text() const
491{
492 return mBodyHTML;
493}
494
495QString MimeTreeParser::HtmlMessagePart::plaintextContent() const
496{
497 return {};
498}
499
500bool HtmlMessagePart::isHtml() const
501{
502 return true;
503}
504
505QString HtmlMessagePart::bodyHtml() const
506{
507 return mBodyHTML;
508}
509
510//-----MimeMessageBlock----------------------
511
512MimeMessagePart::MimeMessagePart(ObjectTreeParser *otp, KMime::Content *node, bool onlyOneMimePart)
513 : MessagePart(otp, QString())
514 , mOnlyOneMimePart(onlyOneMimePart)
515{
516 if (!node) {
517 qCWarning(MIMETREEPARSER_LOG) << "not a valid node";
518 return;
519 }
520 setContent(node);
521
522 parseInternal(node, mOnlyOneMimePart);
523}
524
525MimeMessagePart::~MimeMessagePart() = default;
526
527QString MimeMessagePart::text() const
528{
529 return renderInternalText();
530}
531
532QString MimeMessagePart::plaintextContent() const
533{
534 return {};
535}
536
537QString MimeMessagePart::htmlContent() const
538{
539 return {};
540}
541
542//-----AlternativeMessagePart----------------------
543
544AlternativeMessagePart::AlternativeMessagePart(ObjectTreeParser *otp, KMime::Content *node, Util::HtmlMode preferredMode)
545 : MessagePart(otp, QString())
546 , mPreferredMode(preferredMode)
547{
548 setContent(node);
549 KMime::Content *dataIcal = findTypeInDirectChilds(node, "text/calendar");
550 KMime::Content *dataHtml = findTypeInDirectChilds(node, "text/html");
551 KMime::Content *dataText = findTypeInDirectChilds(node, "text/plain");
552
553 if (!dataHtml) {
554 // If we didn't find the HTML part as the first child of the multipart/alternative, it might
555 // be that this is a HTML message with images, and text/plain and multipart/related are the
556 // immediate children of this multipart/alternative node.
557 // In this case, the HTML node is a child of multipart/related.
558 dataHtml = findTypeInDirectChilds(node, "multipart/related");
559
560 // Still not found? Stupid apple mail actually puts the attachments inside of the
561 // multipart/alternative, which is wrong. Therefore we also have to look for multipart/mixed
562 // here.
563 // Do this only when preferring HTML mail, though, since otherwise the attachments are hidden
564 // when displaying plain text.
565 if (!dataHtml) {
566 dataHtml = findTypeInDirectChilds(node, "multipart/mixed");
567 }
568 }
569
570 if (dataIcal) {
571 mChildNodes[Util::MultipartIcal] = dataIcal;
572 }
573
574 if (dataText) {
575 mChildNodes[Util::MultipartPlain] = dataText;
576 }
577
578 if (dataHtml) {
579 mChildNodes[Util::MultipartHtml] = dataHtml;
580 }
581
582 if (mChildNodes.isEmpty()) {
583 qCWarning(MIMETREEPARSER_LOG) << "no valid nodes";
584 return;
585 }
586
588 while (i.hasNext()) {
589 i.next();
590 mChildParts[i.key()] = MimeMessagePart::Ptr(new MimeMessagePart(mOtp, i.value(), true));
591 }
592}
593
594AlternativeMessagePart::~AlternativeMessagePart() = default;
595
596Util::HtmlMode AlternativeMessagePart::preferredMode() const
597{
598 return mPreferredMode;
599}
600
601void AlternativeMessagePart::setPreferredMode(Util::HtmlMode preferredMode)
602{
603 mPreferredMode = preferredMode;
604}
605
606QList<Util::HtmlMode> AlternativeMessagePart::availableModes()
607{
608 return mChildParts.keys();
609}
610
611QString AlternativeMessagePart::text() const
612{
613 if (mChildParts.contains(Util::MultipartPlain)) {
614 return mChildParts[Util::MultipartPlain]->text();
615 }
616 return {};
617}
618
619void AlternativeMessagePart::fix() const
620{
621 if (mChildParts.contains(Util::MultipartPlain)) {
622 mChildParts[Util::MultipartPlain]->fix();
623 }
624
625 const auto mode = preferredMode();
626 if (mode != Util::MultipartPlain && mChildParts.contains(mode)) {
627 mChildParts[mode]->fix();
628 }
629}
630
631const QMap<Util::HtmlMode, MimeMessagePart::Ptr> &AlternativeMessagePart::childParts() const
632{
633 return mChildParts;
634}
635
636bool AlternativeMessagePart::isHtml() const
637{
638 return mChildParts.contains(Util::MultipartHtml);
639}
640
641QString AlternativeMessagePart::plaintextContent() const
642{
643 return text();
644}
645
646QString AlternativeMessagePart::htmlContent() const
647{
648 if (mChildParts.contains(Util::MultipartHtml)) {
649 return mChildParts[Util::MultipartHtml]->text();
650 } else {
651 return plaintextContent();
652 }
653}
654
655//-----CertMessageBlock----------------------
656
657CertMessagePart::CertMessagePart(ObjectTreeParser *otp, KMime::Content *node, const QGpgME::Protocol *cryptoProto, bool autoImport)
658 : MessagePart(otp, QString())
659 , mAutoImport(autoImport)
660 , mCryptoProto(cryptoProto)
661{
662 if (!node) {
663 qCWarning(MIMETREEPARSER_LOG) << "not a valid node";
664 return;
665 }
666 setContent(node);
667
668 if (!mAutoImport) {
669 return;
670 }
671
672 const QByteArray certData = node->decodedContent();
673
674 QGpgME::ImportJob *import = mCryptoProto->importJob();
675 QGpgMEJobExecutor executor;
676 mImportResult = executor.exec(import, certData);
677}
678
679CertMessagePart::~CertMessagePart() = default;
680
681QString CertMessagePart::text() const
682{
683 return {};
684}
685
686const GpgME::ImportResult &CertMessagePart::importResult() const
687{
688 return mImportResult;
689}
690
691//-----SignedMessageBlock---------------------
692SignedMessagePart::SignedMessagePart(ObjectTreeParser *otp,
693 const QString &text,
694 const QGpgME::Protocol *cryptoProto,
695 const QString &fromAddress,
696 KMime::Content *node)
697 : MessagePart(otp, text)
698 , mCryptoProto(cryptoProto)
699 , mFromAddress(fromAddress)
700 , mMementoName("verification")
701{
702 setContent(node);
703 partMetaData()->technicalProblem = (mCryptoProto == nullptr);
704 partMetaData()->isSigned = true;
705 partMetaData()->isGoodSignature = false;
706 partMetaData()->keyTrust = GpgME::Signature::Unknown;
707 partMetaData()->status = i18n("Wrong Crypto Plug-In.");
708 partMetaData()->status_code = GPGME_SIG_STAT_NONE;
709}
710
711SignedMessagePart::~SignedMessagePart() = default;
712
713void SignedMessagePart::setIsSigned(bool isSigned)
714{
715 partMetaData()->isSigned = isSigned;
716}
717
718bool SignedMessagePart::isSigned() const
719{
720 return partMetaData()->isSigned;
721}
722
723QByteArray SignedMessagePart::mementoName() const
724{
725 return mMementoName;
726}
727
728void SignedMessagePart::setMementoName(const QByteArray &name)
729{
730 mMementoName = name;
731}
732
733static GpgME::Protocol toGpgMeProtocol(const QGpgME::Protocol *protocol)
734{
735 if (protocol == QGpgME::openpgp()) {
736 return GpgME::OpenPGP;
737 }
738
739 if (protocol == QGpgME::smime()) {
740 return GpgME::CMS;
741 }
742
743 return GpgME::UnknownProtocol;
744}
745
746bool SignedMessagePart::okVerify(const QByteArray &data, const QByteArray &signature, KMime::Content *textNode)
747{
748 NodeHelper *nodeHelper = mOtp->nodeHelper();
749
750 partMetaData()->isSigned = false;
751 partMetaData()->technicalProblem = (mCryptoProto == nullptr);
752 partMetaData()->keyTrust = GpgME::Signature::Unknown;
753 partMetaData()->status = i18n("Wrong Crypto Plug-In.");
754 partMetaData()->status_code = GPGME_SIG_STAT_NONE;
755
756 const QByteArray _mementoName = mementoName();
757
758 auto m = dynamic_cast<CompositeMemento *>(nodeHelper->bodyPartMemento(content(), _mementoName));
759 Q_ASSERT(!m || mCryptoProto); // No CryptoPlugin and having a bodyPartMemento -> there is something completely wrong
760
761 if (!m && mCryptoProto) {
762 CryptoBodyPartMemento *newM = nullptr;
763 if (!signature.isEmpty()) {
764 QGpgME::VerifyDetachedJob *job = mCryptoProto->verifyDetachedJob();
765 if (job) {
766 newM = new VerifyDetachedBodyPartMemento(job, mCryptoProto->keyListJob(), signature, data);
767 }
768 } else {
769 QGpgME::VerifyOpaqueJob *job = mCryptoProto->verifyOpaqueJob();
770 if (job) {
771 newM = new VerifyOpaqueBodyPartMemento(job, mCryptoProto->keyListJob(), data);
772 }
773 }
774
775 if (newM) {
776 m = new CompositeMemento();
777 m->addMemento(newM);
778 m->addMemento(new KeyCacheMemento(Kleo::KeyCache::mutableInstance(), toGpgMeProtocol(mCryptoProto)));
779 }
780
781 if (m) {
782 if (mOtp->allowAsync()) {
783 QObject::connect(m, &CryptoBodyPartMemento::update, nodeHelper, &NodeHelper::update);
784 if (m->start()) {
785 partMetaData()->inProgress = true;
786 mOtp->mHasPendingAsyncJobs = true;
787 }
788 } else {
789 m->exec();
790 }
791 nodeHelper->setBodyPartMemento(content(), _mementoName, m);
792 }
793 } else if (m && m->isRunning()) {
794 partMetaData()->inProgress = true;
795 mOtp->mHasPendingAsyncJobs = true;
796 } else {
797 partMetaData()->inProgress = false;
798 mOtp->mHasPendingAsyncJobs = false;
799 }
800
801 if (m && !partMetaData()->inProgress) {
802 if (!signature.isEmpty()) {
803 mVerifiedText = data;
804 }
805 setVerificationResult(m, textNode);
806 }
807
808 if (!m && !partMetaData()->inProgress) {
809 QString errorMsg;
810 QString cryptPlugLibName;
811 QString cryptPlugDisplayName;
812 if (mCryptoProto) {
813 cryptPlugLibName = mCryptoProto->name();
814 cryptPlugDisplayName = mCryptoProto->displayName();
815 }
816
817 if (!mCryptoProto) {
818 if (cryptPlugDisplayName.isEmpty()) {
819 errorMsg = i18n("No appropriate crypto plug-in was found.");
820 } else {
821 errorMsg = i18nc("%1 is either 'OpenPGP' or 'S/MIME'", "No %1 plug-in was found.", cryptPlugDisplayName);
822 }
823 } else {
824 errorMsg = i18n("Crypto plug-in \"%1\" cannot verify signatures.", cryptPlugLibName);
825 }
826 partMetaData()->errorText = i18n(
827 "The message is signed, but the "
828 "validity of the signature cannot be "
829 "verified.<br />"
830 "Reason: %1",
831 errorMsg);
832 }
833
834 return partMetaData()->isSigned;
835}
836
837static int signatureToStatus(const GpgME::Signature &sig)
838{
839 switch (sig.status().code()) {
840 case GPG_ERR_NO_ERROR:
841 return GPGME_SIG_STAT_GOOD;
842 case GPG_ERR_BAD_SIGNATURE:
843 return GPGME_SIG_STAT_BAD;
844 case GPG_ERR_NO_PUBKEY:
845 return GPGME_SIG_STAT_NOKEY;
846 case GPG_ERR_NO_DATA:
847 return GPGME_SIG_STAT_NOSIG;
848 case GPG_ERR_SIG_EXPIRED:
849 return GPGME_SIG_STAT_GOOD_EXP;
850 case GPG_ERR_KEY_EXPIRED:
851 return GPGME_SIG_STAT_GOOD_EXPKEY;
852 default:
853 return GPGME_SIG_STAT_ERROR;
854 }
855}
856
857QString prettifyDN(const char *uid)
858{
859 return QGpgME::DN(uid).prettyDN();
860}
861
862void SignedMessagePart::sigStatusToMetaData()
863{
864 GpgME::Key key;
865 if (partMetaData()->isSigned) {
866 GpgME::Signature signature = mSignatures.front();
867 partMetaData()->status_code = signatureToStatus(signature);
868 partMetaData()->isGoodSignature = partMetaData()->status_code == GPGME_SIG_STAT_GOOD;
869 // save extended signature status flags
870 partMetaData()->sigSummary = signature.summary();
871
872 if (partMetaData()->isGoodSignature && !key.keyID()) {
873 // Search for the key by its fingerprint so that we can check for
874 // trust etc.
875 key = mKeyCache->findByFingerprint(signature.fingerprint());
876 if (key.isNull() && signature.fingerprint()) {
877 // try to find a subkey that was used for signing;
878 // assumes that the key ID is the last 16 characters of the fingerprint
879 const auto fpr = std::string_view{signature.fingerprint()};
880 const auto keyID = std::string{fpr, fpr.size() - 16, 16};
881 const auto subkeys = mKeyCache->findSubkeysByKeyID({keyID});
882 if (subkeys.size() > 0) {
883 key = subkeys[0].parent();
884 }
885 }
886 if (key.isNull()) {
887 qCDebug(MIMETREEPARSER_LOG) << "Found no key or subkey for fingerprint" << signature.fingerprint();
888 }
889 }
890
891 if (key.keyID()) {
892 partMetaData()->keyId = key.keyID();
893 }
894 if (partMetaData()->keyId.isEmpty()) {
895 partMetaData()->keyId = signature.fingerprint();
896 }
897 partMetaData()->keyTrust = signature.validity();
898 if (key.numUserIDs() > 0 && key.userID(0).id()) {
899 partMetaData()->signer = prettifyDN(key.userID(0).id());
900 }
901 for (const auto &uid : key.userIDs()) {
902 // The following if /should/ always result in TRUE but we
903 // won't trust implicitly the plugin that gave us these data.
904 if (uid.email()) {
906 mbox.from7BitString(uid.email());
907 if (mbox.hasAddress()) {
908 partMetaData()->signerMailAddresses.append(mbox.addrSpec().asString());
909 }
910 }
911 }
912
913 if (signature.creationTime()) {
914 partMetaData()->creationTime.setSecsSinceEpoch(signature.creationTime());
915 } else {
916 partMetaData()->creationTime = QDateTime();
917 }
918 if (partMetaData()->signer.isEmpty()) {
919 if (key.numUserIDs() > 0 && key.userID(0).name()) {
920 partMetaData()->signer = prettifyDN(key.userID(0).name());
921 }
922 if (!partMetaData()->signerMailAddresses.empty()) {
923 if (partMetaData()->signer.isEmpty()) {
924 partMetaData()->signer = partMetaData()->signerMailAddresses.front();
925 } else {
926 partMetaData()->signer += QLatin1StringView(" <") + partMetaData()->signerMailAddresses.front() + QLatin1Char('>');
927 }
928 }
929 }
930 if (Kleo::DeVSCompliance::isCompliant()) {
931 partMetaData()->isCompliant = signature.isDeVs();
932 partMetaData()->compliance = Kleo::DeVSCompliance::name(signature.isDeVs());
933 } else {
934 partMetaData()->isCompliant = true;
935 }
936 }
937}
938
939void SignedMessagePart::startVerification(const QByteArray &text, QByteArrayView aCodec)
940{
941 startVerificationDetached(text, nullptr, QByteArray());
942
943 if (!content() && partMetaData()->isSigned) {
944 QStringDecoder codec(aCodec.constData());
945 setText(codec.decode(mVerifiedText));
946 }
947}
948
949void SignedMessagePart::startVerificationDetached(const QByteArray &text, KMime::Content *textNode, const QByteArray &signature)
950{
951 partMetaData()->isEncrypted = false;
952 partMetaData()->isDecryptable = false;
953
954 if (textNode) {
955 parseInternal(textNode, false);
956 }
957
958 if (!okVerify(text, signature, textNode)) {
959 partMetaData()->creationTime = QDateTime();
960 }
961}
962
963void SignedMessagePart::setVerificationResult(const CompositeMemento *m, KMime::Content *textNode)
964{
965 {
966 const auto kc = m->memento<KeyCacheMemento>();
967 if (kc) {
968 mKeyCache = kc->keyCache();
969 }
970 }
971 {
972 const auto vm = m->memento<VerifyDetachedBodyPartMemento>();
973 if (vm) {
974 mSignatures = vm->verifyResult().signatures();
975 }
976 }
977 {
978 const auto vm = m->memento<VerifyOpaqueBodyPartMemento>();
979 if (vm) {
980 mVerifiedText = vm->plainText();
981 mSignatures = vm->verifyResult().signatures();
982 }
983 }
984 {
985 const auto vm = m->memento<DecryptVerifyBodyPartMemento>();
986 if (vm) {
987 mVerifiedText = vm->plainText();
988 mSignatures = vm->verifyResult().signatures();
989 }
990 }
991 partMetaData()->auditLogError = m->auditLogError();
992 partMetaData()->auditLog = m->auditLogAsHtml();
993 partMetaData()->isSigned = !mSignatures.empty();
994
995 if (partMetaData()->isSigned) {
996 sigStatusToMetaData();
997 if (content()) {
998 mOtp->nodeHelper()->setSignatureState(content(), KMMsgFullySigned);
999 if (!textNode) {
1000 mOtp->nodeHelper()->setPartMetaData(content(), *partMetaData());
1001
1002 if (!mVerifiedText.isEmpty()) {
1003 auto tempNode = new KMime::Content();
1004 tempNode->setContent(KMime::CRLFtoLF(mVerifiedText.constData()));
1005 tempNode->parse();
1006
1007 if (!tempNode->head().isEmpty()) {
1008 tempNode->contentDescription()->from7BitString("signed data");
1009 }
1010 mOtp->nodeHelper()->attachExtraContent(content(), tempNode);
1011
1012 parseInternal(tempNode, false);
1013 }
1014 }
1015 }
1016 }
1017}
1018
1019QString SignedMessagePart::plaintextContent() const
1020{
1021 if (!content()) {
1022 return MessagePart::text();
1023 } else {
1024 return {};
1025 }
1026}
1027
1028QString SignedMessagePart::htmlContent() const
1029{
1030 if (!content()) {
1031 return MessagePart::text();
1032 } else {
1033 return {};
1034 }
1035}
1036
1037const QGpgME::Protocol *SignedMessagePart::cryptoProto() const
1038{
1039 return mCryptoProto;
1040}
1041
1042QString SignedMessagePart::fromAddress() const
1043{
1044 return mFromAddress;
1045}
1046
1047bool SignedMessagePart::hasHeader(const char *headerType) const
1048{
1049 if (content()) {
1050 return content()->hasHeader(headerType);
1051 }
1052 return false;
1053}
1054
1055const KMime::Headers::Base *MimeTreeParser::SignedMessagePart::header(const char *headerType) const
1056{
1057 if (content()) {
1058 return content()->headerByType(headerType);
1059 }
1060 return nullptr;
1061}
1062
1063QList<KMime::Headers::Base *> SignedMessagePart::headers(const char *headerType) const
1064{
1065 if (content()) {
1066 return content()->headersByType(headerType);
1067 }
1068 return {};
1069}
1070
1071//-----CryptMessageBlock---------------------
1072EncryptedMessagePart::EncryptedMessagePart(ObjectTreeParser *otp,
1073 const QString &text,
1074 const QGpgME::Protocol *cryptoProto,
1075 const QString &fromAddress,
1076 KMime::Content *node)
1077 : MessagePart(otp, text)
1078 , mPassphraseError(false)
1079 , mNoSecKey(false)
1080 , mDecryptMessage(false)
1081 , mCryptoProto(cryptoProto)
1082 , mFromAddress(fromAddress)
1083 , mMementoName("decryptverify")
1084{
1085 setContent(node);
1086 partMetaData()->technicalProblem = (mCryptoProto == nullptr);
1087 partMetaData()->isSigned = false;
1088 partMetaData()->isGoodSignature = false;
1089 partMetaData()->isEncrypted = false;
1090 partMetaData()->isDecryptable = false;
1091 partMetaData()->keyTrust = GpgME::Signature::Unknown;
1092 partMetaData()->status = i18n("Wrong Crypto Plug-In.");
1093 partMetaData()->status_code = GPGME_SIG_STAT_NONE;
1094}
1095
1096EncryptedMessagePart::~EncryptedMessagePart() = default;
1097
1098void EncryptedMessagePart::setDecryptMessage(bool decrypt)
1099{
1100 mDecryptMessage = decrypt;
1101}
1102
1103bool EncryptedMessagePart::decryptMessage() const
1104{
1105 return mDecryptMessage;
1106}
1107
1108void EncryptedMessagePart::setIsEncrypted(bool encrypted)
1109{
1110 partMetaData()->isEncrypted = encrypted;
1111}
1112
1113bool EncryptedMessagePart::isEncrypted() const
1114{
1115 return partMetaData()->isEncrypted;
1116}
1117
1118bool EncryptedMessagePart::isDecryptable() const
1119{
1120 return partMetaData()->isDecryptable;
1121}
1122
1123bool EncryptedMessagePart::isNoSecKey() const
1124{
1125 return mNoSecKey;
1126}
1127
1128bool EncryptedMessagePart::passphraseError() const
1129{
1130 return mPassphraseError;
1131}
1132
1133QByteArray EncryptedMessagePart::mementoName() const
1134{
1135 return mMementoName;
1136}
1137
1138void EncryptedMessagePart::setMementoName(const QByteArray &name)
1139{
1140 mMementoName = name;
1141}
1142
1143void EncryptedMessagePart::startDecryption(const QByteArray &text, QByteArrayView aCodec)
1144{
1145 auto content = new KMime::Content;
1146 content->setBody(text);
1147 content->parse();
1148
1149 startDecryption(content);
1150
1151 if (!partMetaData()->inProgress && partMetaData()->isDecryptable) {
1152 QStringDecoder codec(aCodec.constData());
1153 if (hasSubParts()) {
1154 auto _mp = (subParts()[0]).dynamicCast<SignedMessagePart>();
1155 if (_mp) {
1156 _mp->setText(codec.decode(mDecryptedData));
1157 } else {
1158 setText(codec.decode(mDecryptedData));
1159 }
1160 } else {
1161 setText(codec.decode(mDecryptedData));
1162 }
1163 }
1164 delete content;
1165}
1166
1167bool EncryptedMessagePart::okDecryptMIME(KMime::Content &data)
1168{
1169 mPassphraseError = false;
1170 partMetaData()->inProgress = false;
1171 partMetaData()->errorText.clear();
1172 partMetaData()->auditLogError = GpgME::Error();
1173 partMetaData()->auditLog.clear();
1174 bool bDecryptionOk = false;
1175 bool cannotDecrypt = false;
1176 NodeHelper *nodeHelper = mOtp->nodeHelper();
1177
1178 Q_ASSERT(decryptMessage());
1179
1180 const QByteArray _mementoName = mementoName();
1181 // Check whether the memento contains a result from last time:
1182 const auto *m = dynamic_cast<CompositeMemento *>(nodeHelper->bodyPartMemento(&data, _mementoName));
1183
1184 Q_ASSERT(!m || mCryptoProto); // No CryptoPlugin and having a bodyPartMemento -> there is something completely wrong
1185
1186 if (!m && mCryptoProto) {
1187 QGpgME::DecryptVerifyJob *job = mCryptoProto->decryptVerifyJob();
1188 if (!job) {
1189 cannotDecrypt = true;
1190 } else {
1191 const QByteArray ciphertext = data.decodedContent();
1192 auto newM = new CompositeMemento();
1193 newM->addMemento(new KeyCacheMemento(Kleo::KeyCache::mutableInstance(), toGpgMeProtocol(mCryptoProto)));
1194 newM->addMemento(new DecryptVerifyBodyPartMemento(job, ciphertext));
1195 if (mOtp->allowAsync()) {
1196 QObject::connect(newM, &CryptoBodyPartMemento::update, nodeHelper, &NodeHelper::update);
1197 if (newM->start()) {
1198 partMetaData()->inProgress = true;
1199 mOtp->mHasPendingAsyncJobs = true;
1200 } else {
1201 m = newM;
1202 }
1203 } else {
1204 newM->exec();
1205 m = newM;
1206 }
1207
1208 nodeHelper->setBodyPartMemento(&data, _mementoName, newM);
1209 }
1210 } else if (m && m->isRunning()) {
1211 partMetaData()->inProgress = true;
1212 mOtp->mHasPendingAsyncJobs = true;
1213 m = nullptr;
1214 }
1215
1216 if (m) {
1217 {
1218 const auto *kcm = m->memento<KeyCacheMemento>();
1219 if (kcm) {
1220 mKeyCache = kcm->keyCache();
1221 }
1222 }
1223 auto *decryptMemento = m->memento<DecryptVerifyBodyPartMemento>();
1224 const QByteArray &plainText = decryptMemento->plainText();
1225 const GpgME::DecryptionResult &decryptResult = decryptMemento->decryptResult();
1226 const GpgME::VerificationResult &verifyResult = decryptMemento->verifyResult();
1227 partMetaData()->isSigned = verifyResult.signatures().size() > 0;
1228
1229 if (partMetaData()->isSigned) {
1230 auto subPart = SignedMessagePart::Ptr(new SignedMessagePart(mOtp, MessagePart::text(), mCryptoProto, mFromAddress, content()));
1231 subPart->setVerificationResult(m, nullptr);
1232 appendSubPart(subPart);
1233 }
1234
1235 mDecryptRecipients.clear();
1236 bDecryptionOk = !decryptResult.error();
1237
1238 // std::stringstream ss;
1239 // ss << decryptResult << '\n' << verifyResult;
1240 // qCDebug(MIMETREEPARSER_LOG) << ss.str().c_str();
1241
1242 for (const auto &recipient : decryptResult.recipients()) {
1243 if (!recipient.status()) {
1244 bDecryptionOk = true;
1245 }
1246 GpgME::Key key;
1247 key = mKeyCache->findByKeyIDOrFingerprint(recipient.keyID());
1248 if (key.isNull()) {
1249 auto ret = mKeyCache->findSubkeysByKeyID({recipient.keyID()});
1250 if (ret.size() == 1) {
1251 key = ret.front().parent();
1252 }
1253 if (key.isNull()) {
1254 qCDebug(MIMETREEPARSER_LOG) << "Found no Key for KeyID " << recipient.keyID();
1255 }
1256 }
1257 mDecryptRecipients.emplace_back(recipient, key);
1258 }
1259
1260 if (!bDecryptionOk && partMetaData()->isSigned) {
1261 // Only a signed part
1262 partMetaData()->isEncrypted = false;
1263 bDecryptionOk = true;
1264 mDecryptedData = plainText;
1265 } else {
1266 mPassphraseError = decryptResult.error().isCanceled() || decryptResult.error().code() == GPG_ERR_NO_SECKEY;
1267 partMetaData()->isEncrypted = bDecryptionOk || decryptResult.error().code() != GPG_ERR_NO_DATA;
1268
1269 if (decryptResult.error().isCanceled()) {
1270 setDecryptMessage(false);
1271 }
1272
1273 partMetaData()->errorText = QString::fromLocal8Bit(decryptResult.error().asString());
1274 if (Kleo::DeVSCompliance::isCompliant()) {
1275 partMetaData()->isCompliant = decryptResult.isDeVs();
1276 partMetaData()->compliance = Kleo::DeVSCompliance::name(decryptResult.isDeVs());
1277 } else {
1278 partMetaData()->isCompliant = true;
1279 }
1280 if (partMetaData()->isEncrypted && decryptResult.numRecipients() > 0) {
1281 partMetaData()->keyId = decryptResult.recipient(0).keyID();
1282 }
1283
1284 if (bDecryptionOk) {
1285 mDecryptedData = plainText;
1286 } else {
1287 mNoSecKey = true;
1288 const auto decryRecipients = decryptResult.recipients();
1289 for (const GpgME::DecryptionResult::Recipient &recipient : decryRecipients) {
1290 mNoSecKey &= (recipient.status().code() == GPG_ERR_NO_SECKEY);
1291 }
1292 if (!mPassphraseError && !mNoSecKey) { // GpgME do not detect passphrase error correctly
1293 mPassphraseError = true;
1294 }
1295 }
1296 }
1297 }
1298
1299 if (!bDecryptionOk) {
1300 QString cryptPlugLibName;
1301 if (mCryptoProto) {
1302 cryptPlugLibName = mCryptoProto->name();
1303 }
1304
1305 if (!mCryptoProto) {
1306 partMetaData()->errorText = i18n("No appropriate crypto plug-in was found.");
1307 } else if (cannotDecrypt) {
1308 partMetaData()->errorText = i18n("Crypto plug-in \"%1\" cannot decrypt messages.", cryptPlugLibName);
1309 } else if (!passphraseError()) {
1310 partMetaData()->errorText = i18n("Crypto plug-in \"%1\" could not decrypt the data.", cryptPlugLibName) + QLatin1StringView("<br />")
1311 + i18n("Error: %1", partMetaData()->errorText);
1312 }
1313 }
1314 return bDecryptionOk;
1315}
1316
1317void EncryptedMessagePart::startDecryption(KMime::Content *data)
1318{
1319 if (!content() && !data) {
1320 return;
1321 }
1322
1323 if (!data) {
1324 data = content();
1325 }
1326
1327 partMetaData()->isEncrypted = true;
1328
1329 bool bOkDecrypt = okDecryptMIME(*data);
1330
1331 if (partMetaData()->inProgress) {
1332 return;
1333 }
1334 partMetaData()->isDecryptable = bOkDecrypt;
1335
1336 if (!partMetaData()->isDecryptable) {
1337 setText(QString::fromUtf8(mDecryptedData.constData()));
1338 }
1339
1340 if (partMetaData()->isEncrypted && !decryptMessage()) {
1341 partMetaData()->isDecryptable = true;
1342 }
1343
1344 if (content() && !partMetaData()->isSigned) {
1345 mOtp->nodeHelper()->setPartMetaData(content(), *partMetaData());
1346
1347 if (decryptMessage()) {
1348 auto tempNode = new KMime::Content();
1349 tempNode->setContent(KMime::CRLFtoLF(mDecryptedData.constData()));
1350 tempNode->parse();
1351
1352 if (!tempNode->head().isEmpty()) {
1353 tempNode->contentDescription()->from7BitString("encrypted data");
1354 }
1355 mOtp->nodeHelper()->attachExtraContent(content(), tempNode);
1356
1357 parseInternal(tempNode, false);
1358 }
1359 }
1360}
1361
1362QString EncryptedMessagePart::plaintextContent() const
1363{
1364 if (!content()) {
1365 return MessagePart::text();
1366 } else {
1367 return {};
1368 }
1369}
1370
1371QString EncryptedMessagePart::htmlContent() const
1372{
1373 if (!content()) {
1374 return MessagePart::text();
1375 } else {
1376 return {};
1377 }
1378}
1379
1380QString EncryptedMessagePart::text() const
1381{
1382 if (hasSubParts()) {
1383 auto _mp = (subParts()[0]).dynamicCast<SignedMessagePart>();
1384 if (_mp) {
1385 return _mp->text();
1386 } else {
1387 return MessagePart::text();
1388 }
1389 } else {
1390 return MessagePart::text();
1391 }
1392}
1393
1394const QGpgME::Protocol *EncryptedMessagePart::cryptoProto() const
1395{
1396 return mCryptoProto;
1397}
1398
1399QString EncryptedMessagePart::fromAddress() const
1400{
1401 return mFromAddress;
1402}
1403
1404const std::vector<std::pair<GpgME::DecryptionResult::Recipient, GpgME::Key>> &EncryptedMessagePart::decryptRecipients() const
1405{
1406 return mDecryptRecipients;
1407}
1408
1409bool EncryptedMessagePart::hasHeader(const char *headerType) const
1410{
1411 const auto extraContent = mOtp->nodeHelper()->decryptedNodeForContent(content());
1412 if (extraContent) {
1413 return nodeHelper()->hasMailHeader(headerType, extraContent);
1414 }
1415 return false;
1416}
1417
1418const KMime::Headers::Base *EncryptedMessagePart::header(const char *headerType) const
1419{
1420 const auto extraContent = mOtp->nodeHelper()->decryptedNodeForContent(content());
1421 if (extraContent) {
1422 return nodeHelper()->mailHeaderAsBase(headerType, extraContent);
1423 }
1424 return nullptr;
1425}
1426
1427QList<KMime::Headers::Base *> EncryptedMessagePart::headers(const char *headerType) const
1428{
1429 const auto extraContent = mOtp->nodeHelper()->decryptedNodeForContent(content());
1430 if (extraContent) {
1431 return nodeHelper()->headers(headerType, extraContent);
1432 }
1433 return {};
1434}
1435
1436EncapsulatedRfc822MessagePart::EncapsulatedRfc822MessagePart(ObjectTreeParser *otp, KMime::Content *node, const KMime::Message::Ptr &message)
1437 : MessagePart(otp, QString())
1438 , mMessage(message)
1439{
1440 setContent(node);
1441 partMetaData()->isEncrypted = false;
1442 partMetaData()->isSigned = false;
1443 partMetaData()->isEncapsulatedRfc822Message = true;
1444
1445 mOtp->nodeHelper()->setNodeDisplayedEmbedded(node, true);
1446 mOtp->nodeHelper()->setPartMetaData(node, *partMetaData());
1447
1448 if (!mMessage) {
1449 qCWarning(MIMETREEPARSER_LOG) << "Node is of type message/rfc822 but doesn't have a message!";
1450 return;
1451 }
1452
1453 // The link to "Encapsulated message" is clickable, therefore the temp file needs to exists,
1454 // since the user can click the link and expect to have normal attachment operations there.
1455 mOtp->nodeHelper()->writeNodeToTempFile(message.data());
1456
1457 parseInternal(message.data(), false);
1458}
1459
1460EncapsulatedRfc822MessagePart::~EncapsulatedRfc822MessagePart() = default;
1461
1462QString EncapsulatedRfc822MessagePart::text() const
1463{
1464 return renderInternalText();
1465}
1466
1467void EncapsulatedRfc822MessagePart::fix() const
1468{
1469}
1470
1471const KMime::Message::Ptr EncapsulatedRfc822MessagePart::message() const
1472{
1473 return mMessage;
1474}
1475
1476#include "moc_messagepart.cpp"
QString toString() const
const Headers::ContentType * contentType() 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)
Content * topLevel()
const Headers::ContentDescription * contentDescription() const
QString asUnicodeString() const override
bool hasAddress() const
void from7BitString(QByteArrayView s)
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 QByteArray charset(const KMime::Content *node)
Returns the charset for the given node.
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.
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(GameStandardAction 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
const_pointer constData() 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 * 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 Jul 26 2024 11:54:19 by doxygen 1.11.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.