summaryrefslogtreecommitdiffstats
path: root/framework/src/domain/mimetreeparser/otp/messagepart.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'framework/src/domain/mimetreeparser/otp/messagepart.cpp')
-rw-r--r--framework/src/domain/mimetreeparser/otp/messagepart.cpp1352
1 files changed, 1352 insertions, 0 deletions
diff --git a/framework/src/domain/mimetreeparser/otp/messagepart.cpp b/framework/src/domain/mimetreeparser/otp/messagepart.cpp
new file mode 100644
index 00000000..3228a387
--- /dev/null
+++ b/framework/src/domain/mimetreeparser/otp/messagepart.cpp
@@ -0,0 +1,1352 @@
1/*
2 Copyright (c) 2015 Sandro Knauß <sknauss@kde.org>
3
4 This library is free software; you can redistribute it and/or modify it
5 under the terms of the GNU Library General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or (at your
7 option) any later version.
8
9 This library is distributed in the hope that it will be useful, but WITHOUT
10 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
12 License for more details.
13
14 You should have received a copy of the GNU Library General Public License
15 along with this library; see the file COPYING.LIB. If not, write to the
16 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17 02110-1301, USA.
18*/
19
20#include "messagepart.h"
21#include "mimetreeparser_debug.h"
22#include "attachmentstrategy.h"
23#include "cryptohelper.h"
24#include "objecttreeparser.h"
25#include "htmlwriter.h"
26#include "qgpgmejobexecutor.h"
27
28#include "cryptobodypartmemento.h"
29#include "decryptverifybodypartmemento.h"
30#include "verifydetachedbodypartmemento.h"
31#include "verifyopaquebodypartmemento.h"
32
33#include "utils.h"
34
35#include <KMime/Content>
36
37#include <QGpgME/DN>
38#include <QGpgME/Protocol>
39#include <QGpgME/ImportJob>
40#include <QGpgME/KeyListJob>
41#include <QGpgME/VerifyDetachedJob>
42#include <QGpgME/VerifyOpaqueJob>
43
44#include <gpgme++/key.h>
45#include <gpgme++/keylistresult.h>
46#include <gpgme.h>
47
48#include <KLocalizedString>
49
50#include <QTextCodec>
51
52using namespace MimeTreeParser;
53
54//------MessagePart-----------------------
55MessagePart::MessagePart(ObjectTreeParser *otp,
56 const QString &text)
57 : mText(text)
58 , mOtp(otp)
59 , mAttachmentNode(nullptr)
60 , mRoot(false)
61{
62}
63
64MessagePart::~MessagePart()
65{
66}
67
68PartMetaData *MessagePart::partMetaData()
69{
70 return &mMetaData;
71}
72
73void MessagePart::setAttachmentFlag(KMime::Content *node)
74{
75 mAttachmentNode = node;
76}
77
78bool MessagePart::isAttachment() const
79{
80 return mAttachmentNode;
81}
82
83KMime::Content *MessagePart::attachmentNode() const
84{
85 return mAttachmentNode;
86}
87
88void MessagePart::setIsRoot(bool root)
89{
90 mRoot = root;
91}
92
93bool MessagePart::isRoot() const
94{
95 return mRoot;
96}
97
98QString MessagePart::text() const
99{
100 return mText;
101}
102
103void MessagePart::setText(const QString &text)
104{
105 mText = text;
106}
107
108bool MessagePart::isHtml() const
109{
110 return false;
111}
112
113bool MessagePart::isHidden() const
114{
115 return false;
116}
117
118Interface::ObjectTreeSource *MessagePart::source() const
119{
120 Q_ASSERT(mOtp);
121 return mOtp->mSource;
122}
123
124HtmlWriter *MessagePart::htmlWriter() const
125{
126 Q_ASSERT(mOtp);
127 return mOtp->htmlWriter();
128}
129
130void MessagePart::setHtmlWriter(HtmlWriter *htmlWriter) const
131{
132 mOtp->mHtmlWriter = htmlWriter;
133}
134
135void MessagePart::parseInternal(KMime::Content *node, bool onlyOneMimePart)
136{
137 auto subMessagePart = mOtp->parseObjectTreeInternal(node, onlyOneMimePart);
138 mRoot = subMessagePart->isRoot();
139 foreach (const auto &part, subMessagePart->subParts()) {
140 appendSubPart(part);
141 }
142}
143
144QString MessagePart::renderInternalText() const
145{
146 QString text;
147 foreach (const auto &mp, subParts()) {
148 text += mp->text();
149 }
150 return text;
151}
152
153void MessagePart::copyContentFrom() const
154{
155 foreach (const auto &mp, subParts()) {
156 const auto m = mp.dynamicCast<MessagePart>();
157 if (m) {
158 m->copyContentFrom();
159 }
160 }
161}
162
163void MessagePart::fix() const
164{
165 foreach (const auto &mp, subParts()) {
166 const auto m = mp.dynamicCast<MessagePart>();
167 if (m) {
168 m->fix();
169 }
170 }
171}
172
173void MessagePart::appendSubPart(const Interface::MessagePart::Ptr &messagePart)
174{
175 messagePart->setParentPart(this);
176 mBlocks.append(messagePart);
177}
178
179const QVector<Interface::MessagePart::Ptr> &MessagePart::subParts() const
180{
181 return mBlocks;
182}
183
184bool MessagePart::hasSubParts() const
185{
186 return !mBlocks.isEmpty();
187}
188
189//-----MessagePartList----------------------
190MessagePartList::MessagePartList(ObjectTreeParser *otp)
191 : MessagePart(otp, QString())
192{
193}
194
195MessagePartList::~MessagePartList()
196{
197
198}
199
200QString MessagePartList::text() const
201{
202 return renderInternalText();
203}
204
205QString MessagePartList::plaintextContent() const
206{
207 return QString();
208}
209
210QString MessagePartList::htmlContent() const
211{
212 return QString();
213}
214
215//-----TextMessageBlock----------------------
216
217TextMessagePart::TextMessagePart(ObjectTreeParser *otp, KMime::Content *node, bool drawFrame, bool showLink, bool decryptMessage)
218 : MessagePartList(otp)
219 , mNode(node)
220 , mDrawFrame(drawFrame)
221 , mShowLink(showLink)
222 , mDecryptMessage(decryptMessage)
223 , mIsHidden(false)
224{
225 if (!mNode) {
226 qCWarning(MIMETREEPARSER_LOG) << "not a valid node";
227 return;
228 }
229
230 mIsHidden = mOtp->nodeHelper()->isNodeDisplayedHidden(mNode);
231
232 parseContent();
233}
234
235TextMessagePart::~TextMessagePart()
236{
237
238}
239
240bool TextMessagePart::decryptMessage() const
241{
242 return mDecryptMessage;
243}
244
245void TextMessagePart::parseContent()
246{
247 const auto aCodec = mOtp->codecFor(mNode);
248 const QString &fromAddress = mOtp->nodeHelper()->fromAsString(mNode);
249 mSignatureState = KMMsgNotSigned;
250 mEncryptionState = KMMsgNotEncrypted;
251 const auto blocks = prepareMessageForDecryption(mNode->decodedContent());
252
253 const auto cryptProto = QGpgME::openpgp();
254
255 if (!blocks.isEmpty()) {
256
257 /* The (overall) signature/encrypted status is broken
258 * if one unencrypted part is at the beginning or in the middle
259 * because mailmain adds an unencrypted part at the end this should not break the overall status
260 *
261 * That's why we first set the tmp status and if one crypted/signed block comes afterwards, than
262 * the status is set to unencryped
263 */
264 bool fullySignedOrEncrypted = true;
265 bool fullySignedOrEncryptedTmp = true;
266
267 for (const auto &block : blocks) {
268
269 if (!fullySignedOrEncryptedTmp) {
270 fullySignedOrEncrypted = false;
271 }
272
273 if (block.type() == NoPgpBlock && !block.text().trimmed().isEmpty()) {
274 fullySignedOrEncryptedTmp = false;
275 appendSubPart(MessagePart::Ptr(new MessagePart(mOtp, aCodec->toUnicode(block.text()))));
276 } else if (block.type() == PgpMessageBlock) {
277 EncryptedMessagePart::Ptr mp(new EncryptedMessagePart(mOtp, QString(), cryptProto, fromAddress, nullptr));
278 mp->setDecryptMessage(decryptMessage());
279 mp->setIsEncrypted(true);
280 appendSubPart(mp);
281 if (!decryptMessage()) {
282 continue;
283 }
284 mp->startDecryption(block.text(), aCodec);
285 if (mp->partMetaData()->inProgress) {
286 continue;
287 }
288 } else if (block.type() == ClearsignedBlock) {
289 SignedMessagePart::Ptr mp(new SignedMessagePart(mOtp, QString(), cryptProto, fromAddress, nullptr));
290 appendSubPart(mp);
291 mp->startVerification(block.text(), aCodec);
292 } else {
293 continue;
294 }
295
296 const auto mp = subParts().last().staticCast<MessagePart>();
297 const PartMetaData *messagePart(mp->partMetaData());
298
299 if (!messagePart->isEncrypted && !messagePart->isSigned && !block.text().trimmed().isEmpty()) {
300 mp->setText(aCodec->toUnicode(block.text()));
301 }
302
303 if (messagePart->isEncrypted) {
304 mEncryptionState = KMMsgPartiallyEncrypted;
305 }
306
307 if (messagePart->isSigned) {
308 mSignatureState = KMMsgPartiallySigned;
309 }
310 }
311
312 //Do we have an fully Signed/Encrypted Message?
313 if (fullySignedOrEncrypted) {
314 if (mSignatureState == KMMsgPartiallySigned) {
315 mSignatureState = KMMsgFullySigned;
316 }
317 if (mEncryptionState == KMMsgPartiallyEncrypted) {
318 mEncryptionState = KMMsgFullyEncrypted;
319 }
320 }
321 }
322}
323
324KMMsgEncryptionState TextMessagePart::encryptionState() const
325{
326 return mEncryptionState;
327}
328
329KMMsgSignatureState TextMessagePart::signatureState() const
330{
331 return mSignatureState;
332}
333
334bool TextMessagePart::isHidden() const
335{
336 return mIsHidden;
337}
338
339bool TextMessagePart::showLink() const
340{
341 return mShowLink;
342}
343
344bool TextMessagePart::showTextFrame() const
345{
346 return mDrawFrame;
347}
348
349//-----AttachmentMessageBlock----------------------
350
351AttachmentMessagePart::AttachmentMessagePart(ObjectTreeParser *otp, KMime::Content *node, bool drawFrame, bool showLink, bool decryptMessage)
352 : TextMessagePart(otp, node, drawFrame, showLink, decryptMessage)
353 , mIsImage(false)
354 , mNeverDisplayInline(false)
355{
356
357}
358
359AttachmentMessagePart::~AttachmentMessagePart()
360{
361
362}
363
364bool AttachmentMessagePart::neverDisplayInline() const
365{
366 return mNeverDisplayInline;
367}
368
369void AttachmentMessagePart::setNeverDisplayInline(bool displayInline)
370{
371 mNeverDisplayInline = displayInline;
372}
373
374bool AttachmentMessagePart::isImage() const
375{
376 return mIsImage;
377}
378
379void AttachmentMessagePart::setIsImage(bool image)
380{
381 mIsImage = image;
382}
383
384IconType AttachmentMessagePart::asIcon() const
385{
386 const AttachmentStrategy *const as = mOtp->attachmentStrategy();
387 const bool defaultHidden(as && as->defaultDisplay(mNode) == AttachmentStrategy::None);
388 const bool showOnlyOneMimePart(mOtp->showOnlyOneMimePart());
389 auto preferredMode = source()->preferredMode();
390 bool isHtmlPreferred = (preferredMode == Util::Html) || (preferredMode == Util::MultipartHtml);
391
392 QByteArray mediaType("text");
393 QByteArray subType("plain");
394 if (mNode->contentType(false) && !mNode->contentType()->mediaType().isEmpty() &&
395 !mNode->contentType()->subType().isEmpty()) {
396 mediaType = mNode->contentType()->mediaType();
397 subType = mNode->contentType()->subType();
398 }
399 const bool isTextPart = (mediaType == QByteArrayLiteral("text"));
400
401 bool defaultAsIcon = true;
402 if (!neverDisplayInline()) {
403 if (as) {
404 defaultAsIcon = as->defaultDisplay(mNode) == AttachmentStrategy::AsIcon;
405 }
406 }
407 if (isImage() && showOnlyOneMimePart && !neverDisplayInline()) {
408 defaultAsIcon = false;
409 }
410
411 // neither image nor text -> show as icon
412 if (!isImage() && !isTextPart) {
413 defaultAsIcon = true;
414 }
415
416 if (isTextPart) {
417 if (as && as->defaultDisplay(mNode) != AttachmentStrategy::Inline) {
418 return MimeTreeParser::IconExternal;
419 }
420 return MimeTreeParser::NoIcon;
421 } else {
422 if (isImage() && isHtmlPreferred &&
423 mNode->parent() && mNode->parent()->contentType()->subType() == "related") {
424 return MimeTreeParser::IconInline;
425 }
426
427 if (defaultHidden && !showOnlyOneMimePart && mNode->parent()) {
428 return MimeTreeParser::IconInline;
429 }
430
431 if (defaultAsIcon) {
432 return MimeTreeParser::IconExternal;
433 } else if (isImage()) {
434 return MimeTreeParser::IconInline;
435 } else {
436 return MimeTreeParser::NoIcon;
437 }
438 }
439}
440
441bool AttachmentMessagePart::isHidden() const
442{
443 const AttachmentStrategy *const as = mOtp->attachmentStrategy();
444 const bool defaultHidden(as && as->defaultDisplay(mNode) == AttachmentStrategy::None);
445 const bool showOnlyOneMimePart(mOtp->showOnlyOneMimePart());
446 auto preferredMode = source()->preferredMode();
447 bool isHtmlPreferred = (preferredMode == Util::Html) || (preferredMode == Util::MultipartHtml);
448
449 QByteArray mediaType("text");
450 QByteArray subType("plain");
451 if (mNode->contentType(false) && !mNode->contentType()->mediaType().isEmpty() &&
452 !mNode->contentType()->subType().isEmpty()) {
453 mediaType = mNode->contentType()->mediaType();
454 subType = mNode->contentType()->subType();
455 }
456 const bool isTextPart = (mediaType == QByteArrayLiteral("text"));
457
458 bool defaultAsIcon = true;
459 if (!neverDisplayInline()) {
460 if (as) {
461 defaultAsIcon = as->defaultDisplay(mNode) == AttachmentStrategy::AsIcon;
462 }
463 }
464 if (isImage() && showOnlyOneMimePart && !neverDisplayInline()) {
465 defaultAsIcon = false;
466 }
467
468 // neither image nor text -> show as icon
469 if (!isImage() && !isTextPart) {
470 defaultAsIcon = true;
471 }
472
473 bool hidden(false);
474 if (isTextPart) {
475 hidden = defaultHidden && !showOnlyOneMimePart;
476 } else {
477 if (isImage() && isHtmlPreferred &&
478 mNode->parent() && mNode->parent()->contentType()->subType() == "related") {
479 hidden = true;
480 } else {
481 hidden = defaultHidden && !showOnlyOneMimePart && mNode->parent();
482 hidden |= defaultAsIcon && (defaultHidden || showOnlyOneMimePart);
483 }
484 }
485 mOtp->nodeHelper()->setNodeDisplayedHidden(mNode, hidden);
486 return hidden;
487}
488
489//-----HtmlMessageBlock----------------------
490
491HtmlMessagePart::HtmlMessagePart(ObjectTreeParser *otp, KMime::Content *node, Interface::ObjectTreeSource *source)
492 : MessagePart(otp, QString())
493 , mNode(node)
494 , mSource(source)
495{
496 if (!mNode) {
497 qCWarning(MIMETREEPARSER_LOG) << "not a valid node";
498 return;
499 }
500
501 const QByteArray partBody(mNode->decodedContent());
502 mBodyHTML = mOtp->codecFor(mNode)->toUnicode(partBody);
503 mCharset = NodeHelper::charset(mNode);
504}
505
506HtmlMessagePart::~HtmlMessagePart()
507{
508}
509
510void HtmlMessagePart::fix() const
511{
512 mOtp->mHtmlContent += mBodyHTML;
513 mOtp->mHtmlContentCharset = mCharset;
514}
515
516QString HtmlMessagePart::text() const
517{
518 return mBodyHTML;
519}
520
521bool HtmlMessagePart::isHtml() const
522{
523 return true;
524}
525
526//-----MimeMessageBlock----------------------
527
528MimeMessagePart::MimeMessagePart(ObjectTreeParser *otp, KMime::Content *node, bool onlyOneMimePart)
529 : MessagePart(otp, QString())
530 , mNode(node)
531 , mOnlyOneMimePart(onlyOneMimePart)
532{
533 if (!mNode) {
534 qCWarning(MIMETREEPARSER_LOG) << "not a valid node";
535 return;
536 }
537
538 parseInternal(mNode, mOnlyOneMimePart);
539}
540
541MimeMessagePart::~MimeMessagePart()
542{
543
544}
545
546QString MimeMessagePart::text() const
547{
548 return renderInternalText();
549}
550
551QString MimeMessagePart::plaintextContent() const
552{
553 return QString();
554}
555
556QString MimeMessagePart::htmlContent() const
557{
558 return QString();
559}
560
561//-----AlternativeMessagePart----------------------
562
563AlternativeMessagePart::AlternativeMessagePart(ObjectTreeParser *otp, KMime::Content *node, Util::HtmlMode preferredMode)
564 : MessagePart(otp, QString())
565 , mNode(node)
566 , mPreferredMode(preferredMode)
567{
568 KMime::Content *dataIcal = findTypeInDirectChilds(mNode, "text/calendar");
569 KMime::Content *dataHtml = findTypeInDirectChilds(mNode, "text/html");
570 KMime::Content *dataText = findTypeInDirectChilds(mNode, "text/plain");
571
572 if (!dataHtml) {
573 // If we didn't find the HTML part as the first child of the multipart/alternative, it might
574 // be that this is a HTML message with images, and text/plain and multipart/related are the
575 // immediate children of this multipart/alternative node.
576 // In this case, the HTML node is a child of multipart/related.
577 dataHtml = findTypeInDirectChilds(mNode, "multipart/related");
578
579 // Still not found? Stupid apple mail actually puts the attachments inside of the
580 // multipart/alternative, which is wrong. Therefore we also have to look for multipart/mixed
581 // here.
582 // Do this only when prefering HTML mail, though, since otherwise the attachments are hidden
583 // when displaying plain text.
584 if (!dataHtml) {
585 dataHtml = findTypeInDirectChilds(mNode, "multipart/mixed");
586 }
587 }
588
589 if (dataIcal) {
590 mChildNodes[Util::MultipartIcal] = dataIcal;
591 }
592
593 if (dataText) {
594 mChildNodes[Util::MultipartPlain] = dataText;
595 }
596
597 if (dataHtml) {
598 mChildNodes[Util::MultipartHtml] = dataHtml;
599 }
600
601 if (mChildNodes.isEmpty()) {
602 qCWarning(MIMETREEPARSER_LOG) << "no valid nodes";
603 return;
604 }
605
606 QMapIterator<Util::HtmlMode, KMime::Content *> i(mChildNodes);
607 while (i.hasNext()) {
608 i.next();
609 mChildParts[i.key()] = MimeMessagePart::Ptr(new MimeMessagePart(mOtp, i.value(), true));
610 }
611}
612
613AlternativeMessagePart::~AlternativeMessagePart()
614{
615
616}
617
618Util::HtmlMode AlternativeMessagePart::preferredMode() const
619{
620 return mPreferredMode;
621}
622
623QList<Util::HtmlMode> AlternativeMessagePart::availableModes()
624{
625 return mChildParts.keys();
626}
627
628QString AlternativeMessagePart::text() const
629{
630 if (mChildParts.contains(Util::MultipartPlain)) {
631 return mChildParts[Util::MultipartPlain]->text();
632 }
633 return QString();
634}
635
636void AlternativeMessagePart::fix() const
637{
638 if (mChildParts.contains(Util::MultipartPlain)) {
639 mChildParts[Util::MultipartPlain]->fix();
640 }
641
642 const auto mode = preferredMode();
643 if (mode != Util::MultipartPlain && mChildParts.contains(mode)) {
644 mChildParts[mode]->fix();
645 }
646}
647
648void AlternativeMessagePart::copyContentFrom() const
649{
650 if (mChildParts.contains(Util::MultipartPlain)) {
651 mChildParts[Util::MultipartPlain]->copyContentFrom();
652 }
653
654 const auto mode = preferredMode();
655 if (mode != Util::MultipartPlain && mChildParts.contains(mode)) {
656 mChildParts[mode]->copyContentFrom();
657 }
658}
659
660bool AlternativeMessagePart::isHtml() const
661{
662 return mChildParts.contains(Util::MultipartHtml);
663}
664
665QString AlternativeMessagePart::plaintextContent() const
666{
667 return text();
668}
669
670QString AlternativeMessagePart::htmlContent() const
671{
672 if (mChildParts.contains(Util::MultipartHtml)) {
673 return mChildParts[Util::MultipartHtml]->text();
674 } else {
675 return plaintextContent();
676 }
677}
678
679//-----CertMessageBlock----------------------
680
681CertMessagePart::CertMessagePart(ObjectTreeParser *otp, KMime::Content *node, const QGpgME::Protocol *cryptoProto, bool autoImport)
682 : MessagePart(otp, QString())
683 , mNode(node)
684 , mAutoImport(autoImport)
685 , mCryptoProto(cryptoProto)
686{
687 if (!mNode) {
688 qCWarning(MIMETREEPARSER_LOG) << "not a valid node";
689 return;
690 }
691
692 if (!mAutoImport) {
693 return;
694 }
695
696 const QByteArray certData = node->decodedContent();
697
698 QGpgME::ImportJob *import = mCryptoProto->importJob();
699 QGpgMEJobExecutor executor;
700 mImportResult = executor.exec(import, certData);
701}
702
703CertMessagePart::~CertMessagePart()
704{
705
706}
707
708QString CertMessagePart::text() const
709{
710 return QString();
711}
712
713//-----SignedMessageBlock---------------------
714SignedMessagePart::SignedMessagePart(ObjectTreeParser *otp,
715 const QString &text,
716 const QGpgME::Protocol *cryptoProto,
717 const QString &fromAddress,
718 KMime::Content *node)
719 : MessagePart(otp, text)
720 , mCryptoProto(cryptoProto)
721 , mFromAddress(fromAddress)
722 , mNode(node)
723{
724 mMetaData.technicalProblem = (mCryptoProto == nullptr);
725 mMetaData.isSigned = true;
726 mMetaData.isGoodSignature = false;
727 mMetaData.keyTrust = GpgME::Signature::Unknown;
728 mMetaData.status = i18n("Wrong Crypto Plug-In.");
729 mMetaData.status_code = GPGME_SIG_STAT_NONE;
730}
731
732SignedMessagePart::~SignedMessagePart()
733{
734
735}
736
737void SignedMessagePart::setIsSigned(bool isSigned)
738{
739 mMetaData.isSigned = isSigned;
740}
741
742bool SignedMessagePart::isSigned() const
743{
744 return mMetaData.isSigned;
745}
746
747bool SignedMessagePart::okVerify(const QByteArray &data, const QByteArray &signature, KMime::Content *textNode)
748{
749 NodeHelper *nodeHelper = mOtp->nodeHelper();
750 Interface::ObjectTreeSource *_source = source();
751
752 mMetaData.isSigned = false;
753 mMetaData.technicalProblem = (mCryptoProto == nullptr);
754 mMetaData.keyTrust = GpgME::Signature::Unknown;
755 mMetaData.status = i18n("Wrong Crypto Plug-In.");
756 mMetaData.status_code = GPGME_SIG_STAT_NONE;
757
758 const QByteArray mementoName = "verification";
759
760 CryptoBodyPartMemento *m = dynamic_cast<CryptoBodyPartMemento *>(nodeHelper->bodyPartMemento(mNode, mementoName));
761 Q_ASSERT(!m || mCryptoProto); //No CryptoPlugin and having a bodyPartMemento -> there is something completely wrong
762
763 if (!m && mCryptoProto) {
764 if (!signature.isEmpty()) {
765 QGpgME::VerifyDetachedJob *job = mCryptoProto->verifyDetachedJob();
766 if (job) {
767 m = new VerifyDetachedBodyPartMemento(job, mCryptoProto->keyListJob(), signature, data);
768 }
769 } else {
770 QGpgME::VerifyOpaqueJob *job = mCryptoProto->verifyOpaqueJob();
771 if (job) {
772 m = new VerifyOpaqueBodyPartMemento(job, mCryptoProto->keyListJob(), data);
773 }
774 }
775 if (m) {
776 if (mOtp->allowAsync()) {
777 QObject::connect(m, &CryptoBodyPartMemento::update,
778 nodeHelper, &NodeHelper::update);
779 QObject::connect(m, SIGNAL(update(MimeTreeParser::UpdateMode)),
780 _source->sourceObject(), SLOT(update(MimeTreeParser::UpdateMode)));
781
782 if (m->start()) {
783 mMetaData.inProgress = true;
784 mOtp->mHasPendingAsyncJobs = true;
785 }
786 } else {
787 m->exec();
788 }
789 nodeHelper->setBodyPartMemento(mNode, mementoName, m);
790 }
791 } else if (m->isRunning()) {
792 mMetaData.inProgress = true;
793 mOtp->mHasPendingAsyncJobs = true;
794 } else {
795 mMetaData.inProgress = false;
796 mOtp->mHasPendingAsyncJobs = false;
797 }
798
799 if (m && !mMetaData.inProgress) {
800 if (!signature.isEmpty()) {
801 mVerifiedText = data;
802 }
803 setVerificationResult(m, textNode);
804 }
805
806 if (!m && !mMetaData.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'",
820 "No %1 plug-in was found.",
821 cryptPlugDisplayName);
822 }
823 } else {
824 errorMsg = i18n("Crypto plug-in \"%1\" cannot verify signatures.",
825 cryptPlugLibName);
826 }
827 mMetaData.errorText = i18n("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 mMetaData.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 (mMetaData.isSigned) {
866 GpgME::Signature signature = mSignatures.front();
867 mMetaData.status_code = signatureToStatus(signature);
868 mMetaData.isGoodSignature = mMetaData.status_code & GPGME_SIG_STAT_GOOD;
869 // save extended signature status flags
870 mMetaData.sigSummary = signature.summary();
871
872 if (mMetaData.isGoodSignature && !key.keyID()) {
873 // Search for the key by its fingerprint so that we can check for
874 // trust etc.
875 QGpgME::KeyListJob *job = mCryptoProto->keyListJob(false); // local, no sigs
876 if (!job) {
877 qCDebug(MIMETREEPARSER_LOG) << "The Crypto backend does not support listing keys. ";
878 } else {
879 std::vector<GpgME::Key> found_keys;
880 // As we are local it is ok to make this synchronous
881 GpgME::KeyListResult res = job->exec(QStringList(QLatin1String(signature.fingerprint())), false, found_keys);
882 if (res.error()) {
883 qCDebug(MIMETREEPARSER_LOG) << "Error while searching key for Fingerprint: " << signature.fingerprint();
884 }
885 if (found_keys.size() > 1) {
886 // Should not Happen
887 qCDebug(MIMETREEPARSER_LOG) << "Oops: Found more then one Key for Fingerprint: " << signature.fingerprint();
888 }
889 if (found_keys.size() != 1) {
890 // Should not Happen at this point
891 qCDebug(MIMETREEPARSER_LOG) << "Oops: Found no Key for Fingerprint: " << signature.fingerprint();
892 } else {
893 key = found_keys[0];
894 }
895 delete job;
896 }
897 }
898
899 if (key.keyID()) {
900 mMetaData.keyId = key.keyID();
901 }
902 if (mMetaData.keyId.isEmpty()) {
903 mMetaData.keyId = signature.fingerprint();
904 }
905 mMetaData.keyTrust = signature.validity();
906 if (key.numUserIDs() > 0 && key.userID(0).id()) {
907 mMetaData.signer = prettifyDN(key.userID(0).id());
908 }
909 for (uint iMail = 0; iMail < key.numUserIDs(); ++iMail) {
910 // The following if /should/ always result in TRUE but we
911 // won't trust implicitely the plugin that gave us these data.
912 if (key.userID(iMail).email()) {
913 QString email = QString::fromUtf8(key.userID(iMail).email());
914 // ### work around gpgme 0.3.QString text() const Q_DECL_OVERRIDE;x / cryptplug bug where the
915 // ### email addresses are specified as angle-addr, not addr-spec:
916 if (email.startsWith(QLatin1Char('<')) && email.endsWith(QLatin1Char('>'))) {
917 email = email.mid(1, email.length() - 2);
918 }
919 if (!email.isEmpty()) {
920 mMetaData.signerMailAddresses.append(email);
921 }
922 }
923 }
924
925 if (signature.creationTime()) {
926 mMetaData.creationTime.setTime_t(signature.creationTime());
927 } else {
928 mMetaData.creationTime = QDateTime();
929 }
930 if (mMetaData.signer.isEmpty()) {
931 if (key.numUserIDs() > 0 && key.userID(0).name()) {
932 mMetaData.signer = prettifyDN(key.userID(0).name());
933 }
934 if (!mMetaData.signerMailAddresses.empty()) {
935 if (mMetaData.signer.isEmpty()) {
936 mMetaData.signer = mMetaData.signerMailAddresses.front();
937 } else {
938 mMetaData.signer += QLatin1String(" <") + mMetaData.signerMailAddresses.front() + QLatin1Char('>');
939 }
940 }
941 }
942 }
943}
944
945void SignedMessagePart::startVerification(const QByteArray &text, const QTextCodec *aCodec)
946{
947 startVerificationDetached(text, nullptr, QByteArray());
948
949 if (!mNode && mMetaData.isSigned) {
950 setText(aCodec->toUnicode(mVerifiedText));
951 }
952}
953
954void SignedMessagePart::startVerificationDetached(const QByteArray &text, KMime::Content *textNode, const QByteArray &signature)
955{
956 mMetaData.isEncrypted = false;
957 mMetaData.isDecryptable = false;
958
959 if (textNode) {
960 parseInternal(textNode, false);
961 }
962
963 okVerify(text, signature, textNode);
964
965 if (!mMetaData.isSigned) {
966 mMetaData.creationTime = QDateTime();
967 }
968}
969
970void SignedMessagePart::setVerificationResult(const CryptoBodyPartMemento *m, KMime::Content *textNode)
971{
972 {
973 const auto vm = dynamic_cast<const VerifyDetachedBodyPartMemento *>(m);
974 if (vm) {
975 mSignatures = vm->verifyResult().signatures();
976 }
977 }
978 {
979 const auto vm = dynamic_cast<const VerifyOpaqueBodyPartMemento *>(m);
980 if (vm) {
981 mVerifiedText = vm->plainText();
982 mSignatures = vm->verifyResult().signatures();
983 }
984 }
985 {
986 const auto vm = dynamic_cast<const DecryptVerifyBodyPartMemento *>(m);
987 if (vm) {
988 mVerifiedText = vm->plainText();
989 mSignatures = vm->verifyResult().signatures();
990 }
991 }
992 mMetaData.auditLogError = m->auditLogError();
993 mMetaData.auditLog = m->auditLogAsHtml();
994 mMetaData.isSigned = !mSignatures.empty();
995
996 if (mMetaData.isSigned) {
997 sigStatusToMetaData();
998 if (mNode) {
999 mOtp->nodeHelper()->setSignatureState(mNode, KMMsgFullySigned);
1000 if (!textNode) {
1001 mOtp->mNodeHelper->setPartMetaData(mNode, mMetaData);
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->mNodeHelper->attachExtraContent(mNode, tempNode);
1012
1013 parseInternal(tempNode, false);
1014 }
1015 }
1016 }
1017 }
1018}
1019
1020QString SignedMessagePart::plaintextContent() const
1021{
1022 if (!mNode) {
1023 return MessagePart::text();
1024 } else {
1025 return QString();
1026 }
1027}
1028
1029QString SignedMessagePart::htmlContent() const
1030{
1031 if (!mNode) {
1032 return MessagePart::text();
1033 } else {
1034 return QString();
1035 }
1036}
1037
1038//-----CryptMessageBlock---------------------
1039EncryptedMessagePart::EncryptedMessagePart(ObjectTreeParser *otp,
1040 const QString &text,
1041 const QGpgME::Protocol *cryptoProto,
1042 const QString &fromAddress,
1043 KMime::Content *node)
1044 : MessagePart(otp, text)
1045 , mPassphraseError(false)
1046 , mNoSecKey(false)
1047 , mCryptoProto(cryptoProto)
1048 , mFromAddress(fromAddress)
1049 , mNode(node)
1050 , mDecryptMessage(false)
1051{
1052 mMetaData.technicalProblem = (mCryptoProto == nullptr);
1053 mMetaData.isSigned = false;
1054 mMetaData.isGoodSignature = false;
1055 mMetaData.isEncrypted = false;
1056 mMetaData.isDecryptable = false;
1057 mMetaData.keyTrust = GpgME::Signature::Unknown;
1058 mMetaData.status = i18n("Wrong Crypto Plug-In.");
1059 mMetaData.status_code = GPGME_SIG_STAT_NONE;
1060}
1061
1062EncryptedMessagePart::~EncryptedMessagePart()
1063{
1064
1065}
1066
1067void EncryptedMessagePart::setDecryptMessage(bool decrypt)
1068{
1069 mDecryptMessage = decrypt;
1070}
1071
1072bool EncryptedMessagePart::decryptMessage() const
1073{
1074 return mDecryptMessage;
1075}
1076
1077void EncryptedMessagePart::setIsEncrypted(bool encrypted)
1078{
1079 mMetaData.isEncrypted = encrypted;
1080}
1081
1082bool EncryptedMessagePart::isEncrypted() const
1083{
1084 return mMetaData.isEncrypted;
1085}
1086
1087bool EncryptedMessagePart::isDecryptable() const
1088{
1089 return mMetaData.isDecryptable;
1090}
1091
1092bool EncryptedMessagePart::passphraseError() const
1093{
1094 return mPassphraseError;
1095}
1096
1097void EncryptedMessagePart::startDecryption(const QByteArray &text, const QTextCodec *aCodec)
1098{
1099 KMime::Content *content = new KMime::Content;
1100 content->setBody(text);
1101 content->parse();
1102
1103 startDecryption(content);
1104
1105 if (!mMetaData.inProgress && mMetaData.isDecryptable) {
1106 if (hasSubParts()) {
1107 auto _mp = (subParts()[0]).dynamicCast<SignedMessagePart>();
1108 if (_mp) {
1109 _mp->setText(aCodec->toUnicode(mDecryptedData));
1110 } else {
1111 setText(aCodec->toUnicode(mDecryptedData));
1112 }
1113 } else {
1114 setText(aCodec->toUnicode(mDecryptedData));
1115 }
1116 }
1117}
1118
1119bool EncryptedMessagePart::okDecryptMIME(KMime::Content &data)
1120{
1121 mPassphraseError = false;
1122 mMetaData.inProgress = false;
1123 mMetaData.errorText.clear();
1124 mMetaData.auditLogError = GpgME::Error();
1125 mMetaData.auditLog.clear();
1126 bool bDecryptionOk = false;
1127 bool cannotDecrypt = false;
1128 Interface::ObjectTreeSource *_source = source();
1129 NodeHelper *nodeHelper = mOtp->nodeHelper();
1130
1131 Q_ASSERT(decryptMessage());
1132
1133 // Check whether the memento contains a result from last time:
1134 const DecryptVerifyBodyPartMemento *m
1135 = dynamic_cast<DecryptVerifyBodyPartMemento *>(nodeHelper->bodyPartMemento(&data, "decryptverify"));
1136
1137 Q_ASSERT(!m || mCryptoProto); //No CryptoPlugin and having a bodyPartMemento -> there is something completely wrong
1138
1139 if (!m && mCryptoProto) {
1140 QGpgME::DecryptVerifyJob *job = mCryptoProto->decryptVerifyJob();
1141 if (!job) {
1142 cannotDecrypt = true;
1143 } else {
1144 const QByteArray ciphertext = data.decodedContent();
1145 DecryptVerifyBodyPartMemento *newM
1146 = new DecryptVerifyBodyPartMemento(job, ciphertext);
1147 if (mOtp->allowAsync()) {
1148 QObject::connect(newM, &CryptoBodyPartMemento::update,
1149 nodeHelper, &NodeHelper::update);
1150 QObject::connect(newM, SIGNAL(update(MimeTreeParser::UpdateMode)), _source->sourceObject(),
1151 SLOT(update(MimeTreeParser::UpdateMode)));
1152 if (newM->start()) {
1153 mMetaData.inProgress = true;
1154 mOtp->mHasPendingAsyncJobs = true;
1155 } else {
1156 m = newM;
1157 }
1158 } else {
1159 newM->exec();
1160 m = newM;
1161 }
1162 nodeHelper->setBodyPartMemento(&data, "decryptverify", newM);
1163 }
1164 } else if (m->isRunning()) {
1165 mMetaData.inProgress = true;
1166 mOtp->mHasPendingAsyncJobs = true;
1167 m = nullptr;
1168 }
1169
1170 if (m) {
1171 const QByteArray &plainText = m->plainText();
1172 const GpgME::DecryptionResult &decryptResult = m->decryptResult();
1173 const GpgME::VerificationResult &verifyResult = m->verifyResult();
1174 mMetaData.isSigned = verifyResult.signatures().size() > 0;
1175
1176 if (verifyResult.signatures().size() > 0) {
1177 auto subPart = SignedMessagePart::Ptr(new SignedMessagePart(mOtp, MessagePart::text(), mCryptoProto, mFromAddress, mNode));
1178 subPart->setVerificationResult(m, nullptr);
1179 appendSubPart(subPart);
1180 }
1181
1182 mDecryptRecipients = decryptResult.recipients();
1183 bDecryptionOk = !decryptResult.error();
1184// std::stringstream ss;
1185// ss << decryptResult << '\n' << verifyResult;
1186// qCDebug(MIMETREEPARSER_LOG) << ss.str().c_str();
1187
1188 if (!bDecryptionOk && mMetaData.isSigned) {
1189 //Only a signed part
1190 mMetaData.isEncrypted = false;
1191 bDecryptionOk = true;
1192 mDecryptedData = plainText;
1193 } else {
1194 mPassphraseError = decryptResult.error().isCanceled() || decryptResult.error().code() == GPG_ERR_NO_SECKEY;
1195 mMetaData.isEncrypted = decryptResult.error().code() != GPG_ERR_NO_DATA;
1196 mMetaData.errorText = QString::fromLocal8Bit(decryptResult.error().asString());
1197 if (mMetaData.isEncrypted && decryptResult.numRecipients() > 0) {
1198 mMetaData.keyId = decryptResult.recipient(0).keyID();
1199 }
1200
1201 if (bDecryptionOk) {
1202 mDecryptedData = plainText;
1203 } else {
1204 mNoSecKey = true;
1205 foreach (const GpgME::DecryptionResult::Recipient &recipient, decryptResult.recipients()) {
1206 mNoSecKey &= (recipient.status().code() == GPG_ERR_NO_SECKEY);
1207 }
1208 if (!mPassphraseError && !mNoSecKey) { // GpgME do not detect passphrase error correctly
1209 mPassphraseError = true;
1210 }
1211 }
1212 }
1213 }
1214
1215 if (!bDecryptionOk) {
1216 QString cryptPlugLibName;
1217 if (mCryptoProto) {
1218 cryptPlugLibName = mCryptoProto->name();
1219 }
1220
1221 if (!mCryptoProto) {
1222 mMetaData.errorText = i18n("No appropriate crypto plug-in was found.");
1223 } else if (cannotDecrypt) {
1224 mMetaData.errorText = i18n("Crypto plug-in \"%1\" cannot decrypt messages.",
1225 cryptPlugLibName);
1226 } else if (!passphraseError()) {
1227 mMetaData.errorText = i18n("Crypto plug-in \"%1\" could not decrypt the data.", cryptPlugLibName)
1228 + QLatin1String("<br />")
1229 + i18n("Error: %1", mMetaData.errorText);
1230 }
1231 }
1232 return bDecryptionOk;
1233}
1234
1235void EncryptedMessagePart::startDecryption(KMime::Content *data)
1236{
1237 if (!mNode && !data) {
1238 return;
1239 }
1240
1241 if (!data) {
1242 data = mNode;
1243 }
1244
1245 mMetaData.isEncrypted = true;
1246
1247 bool bOkDecrypt = okDecryptMIME(*data);
1248
1249 if (mMetaData.inProgress) {
1250 return;
1251 }
1252 mMetaData.isDecryptable = bOkDecrypt;
1253
1254 if (!mMetaData.isDecryptable) {
1255 setText(QString::fromUtf8(mDecryptedData.constData()));
1256 }
1257
1258 if (mMetaData.isEncrypted && !decryptMessage()) {
1259 mMetaData.isDecryptable = true;
1260 }
1261
1262 if (mNode && !mMetaData.isSigned) {
1263 mOtp->mNodeHelper->setPartMetaData(mNode, mMetaData);
1264
1265 if (decryptMessage()) {
1266 auto tempNode = new KMime::Content();
1267 tempNode->setContent(KMime::CRLFtoLF(mDecryptedData.constData()));
1268 tempNode->parse();
1269
1270 if (!tempNode->head().isEmpty()) {
1271 tempNode->contentDescription()->from7BitString("encrypted data");
1272 }
1273 mOtp->mNodeHelper->attachExtraContent(mNode, tempNode);
1274
1275 parseInternal(tempNode, false);
1276 }
1277 }
1278}
1279
1280QString EncryptedMessagePart::plaintextContent() const
1281{
1282 if (!mNode) {
1283 return MessagePart::text();
1284 } else {
1285 return QString();
1286 }
1287}
1288
1289QString EncryptedMessagePart::htmlContent() const
1290{
1291 if (!mNode) {
1292 return MessagePart::text();
1293 } else {
1294 return QString();
1295 }
1296}
1297
1298QString EncryptedMessagePart::text() const
1299{
1300 if (hasSubParts()) {
1301 auto _mp = (subParts()[0]).dynamicCast<SignedMessagePart>();
1302 if (_mp) {
1303 return _mp->text();
1304 } else {
1305 return MessagePart::text();
1306 }
1307 } else {
1308 return MessagePart::text();
1309 }
1310}
1311
1312EncapsulatedRfc822MessagePart::EncapsulatedRfc822MessagePart(ObjectTreeParser *otp, KMime::Content *node, const KMime::Message::Ptr &message)
1313 : MessagePart(otp, QString())
1314 , mMessage(message)
1315 , mNode(node)
1316{
1317 mMetaData.isEncrypted = false;
1318 mMetaData.isSigned = false;
1319 mMetaData.isEncapsulatedRfc822Message = true;
1320
1321 mOtp->nodeHelper()->setNodeDisplayedEmbedded(mNode, true);
1322 mOtp->nodeHelper()->setPartMetaData(mNode, mMetaData);
1323
1324 if (!mMessage) {
1325 qCWarning(MIMETREEPARSER_LOG) << "Node is of type message/rfc822 but doesn't have a message!";
1326 return;
1327 }
1328
1329 // The link to "Encapsulated message" is clickable, therefore the temp file needs to exists,
1330 // since the user can click the link and expect to have normal attachment operations there.
1331 mOtp->nodeHelper()->writeNodeToTempFile(message.data());
1332
1333 parseInternal(message.data(), false);
1334}
1335
1336EncapsulatedRfc822MessagePart::~EncapsulatedRfc822MessagePart()
1337{
1338
1339}
1340
1341QString EncapsulatedRfc822MessagePart::text() const
1342{
1343 return renderInternalText();
1344}
1345
1346void EncapsulatedRfc822MessagePart::copyContentFrom() const
1347{
1348}
1349
1350void EncapsulatedRfc822MessagePart::fix() const
1351{
1352}