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()->status = i18n("Wrong Crypto Plug-In.");
707 partMetaData()->status_code = GPGME_SIG_STAT_NONE;
708}
709
710SignedMessagePart::~SignedMessagePart() = default;
711
712void SignedMessagePart::setIsSigned(bool isSigned)
713{
714 partMetaData()->isSigned = isSigned;
715}
716
717bool SignedMessagePart::isSigned() const
718{
719 return partMetaData()->isSigned;
720}
721
722QByteArray SignedMessagePart::mementoName() const
723{
724 return mMementoName;
725}
726
727void SignedMessagePart::setMementoName(const QByteArray &name)
728{
729 mMementoName = name;
730}
731
732static GpgME::Protocol toGpgMeProtocol(const QGpgME::Protocol *protocol)
733{
734 if (protocol == QGpgME::openpgp()) {
735 return GpgME::OpenPGP;
736 }
737
738 if (protocol == QGpgME::smime()) {
739 return GpgME::CMS;
740 }
741
742 return GpgME::UnknownProtocol;
743}
744
745bool SignedMessagePart::okVerify(const QByteArray &data, const QByteArray &signature, KMime::Content *textNode)
746{
747 NodeHelper *nodeHelper = mOtp->nodeHelper();
748
749 partMetaData()->isSigned = false;
750 partMetaData()->technicalProblem = (mCryptoProto == nullptr);
751 partMetaData()->status = i18n("Wrong Crypto Plug-In.");
752 partMetaData()->status_code = GPGME_SIG_STAT_NONE;
753
754 const QByteArray _mementoName = mementoName();
755
756 auto m = dynamic_cast<CompositeMemento *>(nodeHelper->bodyPartMemento(content(), _mementoName));
757 Q_ASSERT(!m || mCryptoProto); // No CryptoPlugin and having a bodyPartMemento -> there is something completely wrong
758
759 if (!m && mCryptoProto) {
760 CryptoBodyPartMemento *newM = nullptr;
761 if (!signature.isEmpty()) {
762 QGpgME::VerifyDetachedJob *job = mCryptoProto->verifyDetachedJob();
763 if (job) {
764 newM = new VerifyDetachedBodyPartMemento(job, mCryptoProto->keyListJob(), signature, data);
765 }
766 } else {
767 QGpgME::VerifyOpaqueJob *job = mCryptoProto->verifyOpaqueJob();
768 if (job) {
769 newM = new VerifyOpaqueBodyPartMemento(job, mCryptoProto->keyListJob(), data);
770 }
771 }
772
773 if (newM) {
774 m = new CompositeMemento();
775 m->addMemento(newM);
776 m->addMemento(new KeyCacheMemento(Kleo::KeyCache::mutableInstance(), toGpgMeProtocol(mCryptoProto)));
777 }
778
779 if (m) {
780 if (mOtp->allowAsync()) {
781 QObject::connect(m, &CryptoBodyPartMemento::update, nodeHelper, &NodeHelper::update);
782 if (m->start()) {
783 partMetaData()->inProgress = true;
784 mOtp->mHasPendingAsyncJobs = true;
785 }
786 } else {
787 m->exec();
788 }
789 nodeHelper->setBodyPartMemento(content(), _mementoName, m);
790 }
791 } else if (m && m->isRunning()) {
792 partMetaData()->inProgress = true;
793 mOtp->mHasPendingAsyncJobs = true;
794 } else {
795 partMetaData()->inProgress = false;
796 mOtp->mHasPendingAsyncJobs = false;
797 }
798
799 if (m && !partMetaData()->inProgress) {
800 if (!signature.isEmpty()) {
801 mVerifiedText = data;
802 }
803 setVerificationResult(m, textNode);
804 }
805
806 if (!m && !partMetaData()->inProgress) {
807 QString errorMsg;
808 QString cryptPlugLibName;
809 QString cryptPlugDisplayName;
810 if (mCryptoProto) {
811 cryptPlugLibName = mCryptoProto->name();
812 cryptPlugDisplayName = mCryptoProto->displayName();
813 }
814
815 if (!mCryptoProto) {
816 if (cryptPlugDisplayName.isEmpty()) {
817 errorMsg = i18n("No appropriate crypto plug-in was found.");
818 } else {
819 errorMsg = i18nc("%1 is either 'OpenPGP' or 'S/MIME'", "No %1 plug-in was found.", cryptPlugDisplayName);
820 }
821 } else {
822 errorMsg = i18n("Crypto plug-in \"%1\" cannot verify signatures.", cryptPlugLibName);
823 }
824 partMetaData()->errorText = i18n(
825 "The message is signed, but the "
826 "validity of the signature cannot be "
827 "verified.<br />"
828 "Reason: %1",
829 errorMsg);
830 }
831
832 return partMetaData()->isSigned;
833}
834
835QString prettifyDN(const char *uid)
836{
837 return QGpgME::DN(uid).prettyDN();
838}
839
840void SignedMessagePart::startVerification(const QByteArray &text, QByteArrayView aCodec)
841{
842 startVerificationDetached(text, nullptr, QByteArray());
843
844 if (!content() && partMetaData()->isSigned) {
845 QStringDecoder codec(aCodec.constData());
846 setText(codec.decode(mVerifiedText));
847 }
848}
849
850void SignedMessagePart::startVerificationDetached(const QByteArray &text, KMime::Content *textNode, const QByteArray &signature)
851{
852 partMetaData()->isEncrypted = false;
853 partMetaData()->isDecryptable = false;
854
855 if (textNode) {
856 parseInternal(textNode, false);
857 }
858
859 if (!okVerify(text, signature, textNode)) {
860 partMetaData()->creationTime = QDateTime();
861 }
862}
863
864void SignedMessagePart::setVerificationResult(const CompositeMemento *m, KMime::Content *textNode)
865{
866 {
867 const auto kc = m->memento<KeyCacheMemento>();
868 if (kc) {
869 mKeyCache = kc->keyCache();
870 }
871 }
872 {
873 const auto vm = m->memento<VerifyDetachedBodyPartMemento>();
874 if (vm) {
875 mSignatures = vm->verifyResult().signatures();
876 partMetaData()->verificationResult = vm->verifyResult();
877 }
878 }
879 {
880 const auto vm = m->memento<VerifyOpaqueBodyPartMemento>();
881 if (vm) {
882 mVerifiedText = vm->plainText();
883 mSignatures = vm->verifyResult().signatures();
884 partMetaData()->verificationResult = vm->verifyResult();
885 }
886 }
887 {
888 const auto vm = m->memento<DecryptVerifyBodyPartMemento>();
889 if (vm) {
890 mVerifiedText = vm->plainText();
891 mSignatures = vm->verifyResult().signatures();
892 partMetaData()->verificationResult = vm->verifyResult();
893 }
894 }
895 partMetaData()->auditLogError = m->auditLogError();
896 partMetaData()->auditLog = m->auditLogAsHtml();
897 partMetaData()->isSigned = !mSignatures.empty();
898
899 if (partMetaData()->isSigned && content()) {
900 mOtp->nodeHelper()->setSignatureState(content(), KMMsgFullySigned);
901 if (textNode) {
902 return;
903 }
904
905 mOtp->nodeHelper()->setPartMetaData(content(), *partMetaData());
906
907 if (!mVerifiedText.isEmpty()) {
908 auto tempNode = new KMime::Content();
909 tempNode->setContent(KMime::CRLFtoLF(mVerifiedText.constData()));
910 tempNode->parse();
911
912 if (!tempNode->head().isEmpty()) {
913 tempNode->contentDescription()->from7BitString("signed data");
914 }
915 mOtp->nodeHelper()->attachExtraContent(content(), tempNode);
916
917 parseInternal(tempNode, false);
918 }
919 }
920}
921
922QString SignedMessagePart::plaintextContent() const
923{
924 if (!content()) {
925 return MessagePart::text();
926 } else {
927 return {};
928 }
929}
930
931QString SignedMessagePart::htmlContent() const
932{
933 if (!content()) {
934 return MessagePart::text();
935 } else {
936 return {};
937 }
938}
939
940const QGpgME::Protocol *SignedMessagePart::cryptoProto() const
941{
942 return mCryptoProto;
943}
944
945QString SignedMessagePart::fromAddress() const
946{
947 return mFromAddress;
948}
949
950bool SignedMessagePart::hasHeader(const char *headerType) const
951{
952 if (content()) {
953 return content()->hasHeader(headerType);
954 }
955 return false;
956}
957
958const KMime::Headers::Base *MimeTreeParser::SignedMessagePart::header(const char *headerType) const
959{
960 if (content()) {
961 return content()->headerByType(headerType);
962 }
963 return nullptr;
964}
965
966QList<KMime::Headers::Base *> SignedMessagePart::headers(const char *headerType) const
967{
968 if (content()) {
969 return content()->headersByType(headerType);
970 }
971 return {};
972}
973
974//-----CryptMessageBlock---------------------
975EncryptedMessagePart::EncryptedMessagePart(ObjectTreeParser *otp,
976 const QString &text,
977 const QGpgME::Protocol *cryptoProto,
978 const QString &fromAddress,
979 KMime::Content *node)
980 : MessagePart(otp, text)
981 , mPassphraseError(false)
982 , mNoSecKey(false)
983 , mDecryptMessage(false)
984 , mCryptoProto(cryptoProto)
985 , mFromAddress(fromAddress)
986 , mMementoName("decryptverify")
987{
988 setContent(node);
989 partMetaData()->technicalProblem = (mCryptoProto == nullptr);
990 partMetaData()->isSigned = false;
991 partMetaData()->isGoodSignature = false;
992 partMetaData()->isEncrypted = false;
993 partMetaData()->isDecryptable = false;
994 partMetaData()->status = i18n("Wrong Crypto Plug-In.");
995 partMetaData()->status_code = GPGME_SIG_STAT_NONE;
996}
997
998EncryptedMessagePart::~EncryptedMessagePart() = default;
999
1000void EncryptedMessagePart::setDecryptMessage(bool decrypt)
1001{
1002 mDecryptMessage = decrypt;
1003}
1004
1005bool EncryptedMessagePart::decryptMessage() const
1006{
1007 return mDecryptMessage;
1008}
1009
1010void EncryptedMessagePart::setIsEncrypted(bool encrypted)
1011{
1012 partMetaData()->isEncrypted = encrypted;
1013}
1014
1015bool EncryptedMessagePart::isEncrypted() const
1016{
1017 return partMetaData()->isEncrypted;
1018}
1019
1020bool EncryptedMessagePart::isDecryptable() const
1021{
1022 return partMetaData()->isDecryptable;
1023}
1024
1025bool EncryptedMessagePart::isNoSecKey() const
1026{
1027 return mNoSecKey;
1028}
1029
1030bool EncryptedMessagePart::passphraseError() const
1031{
1032 return mPassphraseError;
1033}
1034
1035QByteArray EncryptedMessagePart::mementoName() const
1036{
1037 return mMementoName;
1038}
1039
1040void EncryptedMessagePart::setMementoName(const QByteArray &name)
1041{
1042 mMementoName = name;
1043}
1044
1045void EncryptedMessagePart::startDecryption(const QByteArray &text, QByteArrayView aCodec)
1046{
1047 auto content = new KMime::Content;
1048 content->setBody(text);
1049 content->parse();
1050
1051 startDecryption(content);
1052
1053 if (!partMetaData()->inProgress && partMetaData()->isDecryptable) {
1054 QStringDecoder codec(aCodec.constData());
1055 if (hasSubParts()) {
1056 auto _mp = (subParts()[0]).dynamicCast<SignedMessagePart>();
1057 if (_mp) {
1058 _mp->setText(codec.decode(mDecryptedData));
1059 } else {
1060 setText(codec.decode(mDecryptedData));
1061 }
1062 } else {
1063 setText(codec.decode(mDecryptedData));
1064 }
1065 }
1066 delete content;
1067}
1068
1069bool EncryptedMessagePart::okDecryptMIME(KMime::Content &data)
1070{
1071 mPassphraseError = false;
1072 partMetaData()->inProgress = false;
1073 partMetaData()->errorText.clear();
1074 partMetaData()->auditLogError = GpgME::Error();
1075 partMetaData()->auditLog.clear();
1076 bool bDecryptionOk = false;
1077 bool cannotDecrypt = false;
1078 NodeHelper *nodeHelper = mOtp->nodeHelper();
1079
1080 Q_ASSERT(decryptMessage());
1081
1082 const QByteArray _mementoName = mementoName();
1083 // Check whether the memento contains a result from last time:
1084 const auto *m = dynamic_cast<CompositeMemento *>(nodeHelper->bodyPartMemento(&data, _mementoName));
1085
1086 Q_ASSERT(!m || mCryptoProto); // No CryptoPlugin and having a bodyPartMemento -> there is something completely wrong
1087
1088 if (!m && mCryptoProto) {
1089 QGpgME::DecryptVerifyJob *job = mCryptoProto->decryptVerifyJob();
1090 if (!job) {
1091 cannotDecrypt = true;
1092 } else {
1093 const QByteArray ciphertext = data.decodedContent();
1094 auto newM = new CompositeMemento();
1095 newM->addMemento(new KeyCacheMemento(Kleo::KeyCache::mutableInstance(), toGpgMeProtocol(mCryptoProto)));
1096 newM->addMemento(new DecryptVerifyBodyPartMemento(job, ciphertext));
1097 if (mOtp->allowAsync()) {
1098 QObject::connect(newM, &CryptoBodyPartMemento::update, nodeHelper, &NodeHelper::update);
1099 if (newM->start()) {
1100 partMetaData()->inProgress = true;
1101 mOtp->mHasPendingAsyncJobs = true;
1102 } else {
1103 m = newM;
1104 }
1105 } else {
1106 newM->exec();
1107 m = newM;
1108 }
1109
1110 nodeHelper->setBodyPartMemento(&data, _mementoName, newM);
1111 }
1112 } else if (m && m->isRunning()) {
1113 partMetaData()->inProgress = true;
1114 mOtp->mHasPendingAsyncJobs = true;
1115 m = nullptr;
1116 }
1117
1118 if (m) {
1119 {
1120 const auto *kcm = m->memento<KeyCacheMemento>();
1121 if (kcm) {
1122 mKeyCache = kcm->keyCache();
1123 }
1124 }
1125 auto *decryptMemento = m->memento<DecryptVerifyBodyPartMemento>();
1126 const QByteArray &plainText = decryptMemento->plainText();
1127 const GpgME::DecryptionResult &decryptResult = decryptMemento->decryptResult();
1128 const GpgME::VerificationResult &verifyResult = decryptMemento->verifyResult();
1129 partMetaData()->isSigned = verifyResult.signatures().size() > 0;
1130
1131 if (partMetaData()->isSigned) {
1132 auto subPart = SignedMessagePart::Ptr(new SignedMessagePart(mOtp, MessagePart::text(), mCryptoProto, mFromAddress, content()));
1133 subPart->setVerificationResult(m, nullptr);
1134 appendSubPart(subPart);
1135 }
1136
1137 mDecryptRecipients.clear();
1138 bDecryptionOk = !decryptResult.error();
1139
1140 // std::stringstream ss;
1141 // ss << decryptResult << '\n' << verifyResult;
1142 // qCDebug(MIMETREEPARSER_LOG) << ss.str().c_str();
1143
1144 for (const auto &recipient : decryptResult.recipients()) {
1145 if (!recipient.status()) {
1146 bDecryptionOk = true;
1147 }
1148 GpgME::Key key;
1149 key = mKeyCache->findByKeyIDOrFingerprint(recipient.keyID());
1150 if (key.isNull()) {
1151 auto ret = mKeyCache->findSubkeysByKeyID({recipient.keyID()});
1152 if (ret.size() == 1) {
1153 key = ret.front().parent();
1154 }
1155 if (key.isNull()) {
1156 qCDebug(MIMETREEPARSER_LOG) << "Found no Key for KeyID " << recipient.keyID();
1157 }
1158 }
1159 mDecryptRecipients.emplace_back(recipient, key);
1160 }
1161
1162 if (!bDecryptionOk && partMetaData()->isSigned) {
1163 // Only a signed part
1164 partMetaData()->isEncrypted = false;
1165 bDecryptionOk = true;
1166 mDecryptedData = plainText;
1167 } else {
1168 mPassphraseError = decryptResult.error().isCanceled() || decryptResult.error().code() == GPG_ERR_NO_SECKEY;
1169 partMetaData()->isEncrypted = bDecryptionOk || decryptResult.error().code() != GPG_ERR_NO_DATA;
1170
1171 if (decryptResult.error().isCanceled()) {
1172 setDecryptMessage(false);
1173 }
1174
1175 partMetaData()->errorText = QString::fromLocal8Bit(decryptResult.error().asString());
1176 if (Kleo::DeVSCompliance::isCompliant()) {
1177 partMetaData()->isCompliant = decryptResult.isDeVs();
1178 partMetaData()->compliance = Kleo::DeVSCompliance::name(decryptResult.isDeVs());
1179 } else {
1180 partMetaData()->isCompliant = true;
1181 }
1182 if (partMetaData()->isEncrypted && decryptResult.numRecipients() > 0) {
1183 partMetaData()->keyId = decryptResult.recipient(0).keyID();
1184 }
1185
1186 if (bDecryptionOk) {
1187 mDecryptedData = plainText;
1188 } else {
1189 mNoSecKey = true;
1190 const auto decryRecipients = decryptResult.recipients();
1191 for (const GpgME::DecryptionResult::Recipient &recipient : decryRecipients) {
1192 mNoSecKey &= (recipient.status().code() == GPG_ERR_NO_SECKEY);
1193 }
1194 if (!mPassphraseError && !mNoSecKey) { // GpgME do not detect passphrase error correctly
1195 mPassphraseError = true;
1196 }
1197 }
1198 }
1199 }
1200
1201 if (!bDecryptionOk) {
1202 QString cryptPlugLibName;
1203 if (mCryptoProto) {
1204 cryptPlugLibName = mCryptoProto->name();
1205 }
1206
1207 if (!mCryptoProto) {
1208 partMetaData()->errorText = i18n("No appropriate crypto plug-in was found.");
1209 } else if (cannotDecrypt) {
1210 partMetaData()->errorText = i18n("Crypto plug-in \"%1\" cannot decrypt messages.", cryptPlugLibName);
1211 } else if (!passphraseError()) {
1212 partMetaData()->errorText = i18n("Crypto plug-in \"%1\" could not decrypt the data.", cryptPlugLibName) + QLatin1StringView("<br />")
1213 + i18n("Error: %1", partMetaData()->errorText);
1214 }
1215 }
1216 return bDecryptionOk;
1217}
1218
1219void EncryptedMessagePart::startDecryption(KMime::Content *data)
1220{
1221 if (!content() && !data) {
1222 return;
1223 }
1224
1225 if (!data) {
1226 data = content();
1227 }
1228
1229 partMetaData()->isEncrypted = true;
1230
1231 bool bOkDecrypt = okDecryptMIME(*data);
1232
1233 if (partMetaData()->inProgress) {
1234 return;
1235 }
1236 partMetaData()->isDecryptable = bOkDecrypt;
1237
1238 if (!partMetaData()->isDecryptable) {
1239 setText(QString::fromUtf8(mDecryptedData.constData()));
1240 }
1241
1242 if (partMetaData()->isEncrypted && !decryptMessage()) {
1243 partMetaData()->isDecryptable = true;
1244 }
1245
1246 if (content() && !partMetaData()->isSigned) {
1247 mOtp->nodeHelper()->setPartMetaData(content(), *partMetaData());
1248
1249 if (decryptMessage()) {
1250 auto tempNode = new KMime::Content();
1251 tempNode->setContent(KMime::CRLFtoLF(mDecryptedData.constData()));
1252 tempNode->parse();
1253
1254 if (!tempNode->head().isEmpty()) {
1255 tempNode->contentDescription()->from7BitString("encrypted data");
1256 }
1257 mOtp->nodeHelper()->attachExtraContent(content(), tempNode);
1258
1259 parseInternal(tempNode, false);
1260 }
1261 }
1262}
1263
1264QString EncryptedMessagePart::plaintextContent() const
1265{
1266 if (!content()) {
1267 return MessagePart::text();
1268 } else {
1269 return {};
1270 }
1271}
1272
1273QString EncryptedMessagePart::htmlContent() const
1274{
1275 if (!content()) {
1276 return MessagePart::text();
1277 } else {
1278 return {};
1279 }
1280}
1281
1282QString EncryptedMessagePart::text() const
1283{
1284 if (hasSubParts()) {
1285 auto _mp = (subParts()[0]).dynamicCast<SignedMessagePart>();
1286 if (_mp) {
1287 return _mp->text();
1288 } else {
1289 return MessagePart::text();
1290 }
1291 } else {
1292 return MessagePart::text();
1293 }
1294}
1295
1296const QGpgME::Protocol *EncryptedMessagePart::cryptoProto() const
1297{
1298 return mCryptoProto;
1299}
1300
1301QString EncryptedMessagePart::fromAddress() const
1302{
1303 return mFromAddress;
1304}
1305
1306const std::vector<std::pair<GpgME::DecryptionResult::Recipient, GpgME::Key>> &EncryptedMessagePart::decryptRecipients() const
1307{
1308 return mDecryptRecipients;
1309}
1310
1311bool EncryptedMessagePart::hasHeader(const char *headerType) const
1312{
1313 const auto extraContent = mOtp->nodeHelper()->decryptedNodeForContent(content());
1314 if (extraContent) {
1315 return nodeHelper()->hasMailHeader(headerType, extraContent);
1316 }
1317 return false;
1318}
1319
1320const KMime::Headers::Base *EncryptedMessagePart::header(const char *headerType) const
1321{
1322 const auto extraContent = mOtp->nodeHelper()->decryptedNodeForContent(content());
1323 if (extraContent) {
1324 return nodeHelper()->mailHeaderAsBase(headerType, extraContent);
1325 }
1326 return nullptr;
1327}
1328
1329QList<KMime::Headers::Base *> EncryptedMessagePart::headers(const char *headerType) const
1330{
1331 const auto extraContent = mOtp->nodeHelper()->decryptedNodeForContent(content());
1332 if (extraContent) {
1333 return nodeHelper()->headers(headerType, extraContent);
1334 }
1335 return {};
1336}
1337
1338EncapsulatedRfc822MessagePart::EncapsulatedRfc822MessagePart(ObjectTreeParser *otp, KMime::Content *node, const KMime::Message::Ptr &message)
1339 : MessagePart(otp, QString())
1340 , mMessage(message)
1341{
1342 setContent(node);
1343 partMetaData()->isEncrypted = false;
1344 partMetaData()->isSigned = false;
1345 partMetaData()->isEncapsulatedRfc822Message = true;
1346
1347 mOtp->nodeHelper()->setNodeDisplayedEmbedded(node, true);
1348 mOtp->nodeHelper()->setPartMetaData(node, *partMetaData());
1349
1350 if (!mMessage) {
1351 qCWarning(MIMETREEPARSER_LOG) << "Node is of type message/rfc822 but doesn't have a message!";
1352 return;
1353 }
1354
1355 // The link to "Encapsulated message" is clickable, therefore the temp file needs to exists,
1356 // since the user can click the link and expect to have normal attachment operations there.
1357 mOtp->nodeHelper()->writeNodeToTempFile(message.data());
1358
1359 parseInternal(message.data(), false);
1360}
1361
1362EncapsulatedRfc822MessagePart::~EncapsulatedRfc822MessagePart() = default;
1363
1364QString EncapsulatedRfc822MessagePart::text() const
1365{
1366 return renderInternalText();
1367}
1368
1369void EncapsulatedRfc822MessagePart::fix() const
1370{
1371}
1372
1373const KMime::Message::Ptr EncapsulatedRfc822MessagePart::message() const
1374{
1375 return mMessage;
1376}
1377
1378#include "moc_messagepart.cpp"
QString toString() const
const Headers::ContentType * contentType() const
ContentIndex index() const
Content * textContent()
QByteArray decodedContent() const
QList< Headers::Base * > headersByType(QByteArrayView type) const
bool hasHeader(QByteArrayView type) const
void setBody(const QByteArray &body)
Content * topLevel()
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 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(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
const_pointer constData() const const
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 Mon Nov 18 2024 12:07:24 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.