Messagelib

defaultrenderer.cpp
1/*
2 SPDX-FileCopyrightText: 2016 Sandro Knauß <sknauss@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#include "defaultrenderer.h"
8
9#include "defaultrenderer_p.h"
10
11#include "utils/messageviewerutil.h"
12
13#include "messageviewer_debug.h"
14
15#include "converthtmltoplaintext.h"
16#include "htmlblock.h"
17#include "messagepartrendererbase.h"
18#include "messagepartrendererfactory.h"
19#include "messagepartrenderermanager.h"
20#include "utils/iconnamecache.h"
21#include "viewer/attachmentstrategy.h"
22#include "viewer/csshelperbase.h"
23
24#include "htmlwriter/bufferedhtmlwriter.h"
25#include <MimeTreeParser/MessagePart>
26#include <MimeTreeParser/ObjectTreeParser>
27
28#include <Libkleo/Formatting>
29#include <QGpgME/Protocol>
30
31#include <Libkleo/Formatting>
32#include <MessageCore/StringUtil>
33
34#include <KEmailAddress>
35#include <KIconLoader>
36#include <KLocalizedString>
37
38#include <KTextTemplate/Context>
39#include <KTextTemplate/Engine>
40#include <KTextTemplate/MetaType>
41#include <KTextTemplate/Template>
42#include <KTextTemplate/TemplateLoader>
43#include <QUrl>
44
45using namespace MimeTreeParser;
46using namespace MessageViewer;
47#ifndef COMPILE_WITH_UNITY_CMAKE_SUPPORT
48Q_DECLARE_METATYPE(GpgME::DecryptionResult::Recipient)
49Q_DECLARE_METATYPE(GpgME::Key)
50Q_DECLARE_METATYPE(const QGpgME::Protocol *)
51#endif
52static const int SIG_FRAME_COL_UNDEF = 99;
53#define SIG_FRAME_COL_RED -1
54#define SIG_FRAME_COL_YELLOW 0
55#define SIG_FRAME_COL_GREEN 1
56QString sigStatusToString(const QGpgME::Protocol *cryptProto, int status_code, GpgME::Signature::Summary summary, int &frameColor, bool &showKeyInfos)
57{
58 // note: At the moment frameColor and showKeyInfos are
59 // used for CMS only but not for PGP signatures
60 // pending(khz): Implement usage of these for PGP sigs as well.
61 showKeyInfos = true;
62 QString result;
63 if (cryptProto) {
64 if (cryptProto == QGpgME::openpgp()) {
65 // process enum according to it's definition to be read in
66 // GNU Privacy Guard CVS repository /gpgme/gpgme/gpgme.h
67 switch (status_code) {
68 case 0: // GPGME_SIG_STAT_NONE
69 result = i18n("Error: Signature not verified");
70 frameColor = SIG_FRAME_COL_YELLOW;
71 break;
72 case 1: // GPGME_SIG_STAT_GOOD
73 result = i18n("Good signature");
74 frameColor = SIG_FRAME_COL_GREEN;
75 break;
76 case 2: // GPGME_SIG_STAT_BAD
77 result = i18n("Bad signature");
78 frameColor = SIG_FRAME_COL_RED;
79 break;
80 case 3: // GPGME_SIG_STAT_NOKEY
81 result = i18n("No public key to verify the signature");
82 frameColor = SIG_FRAME_COL_RED;
83 break;
84 case 4: // GPGME_SIG_STAT_NOSIG
85 result = i18n("No signature found");
86 frameColor = SIG_FRAME_COL_RED;
87 break;
88 case 5: // GPGME_SIG_STAT_ERROR
89 result = i18n("Error verifying the signature");
90 frameColor = SIG_FRAME_COL_RED;
91 break;
92 case 6: // GPGME_SIG_STAT_DIFF
93 result = i18n("Different results for signatures");
94 frameColor = SIG_FRAME_COL_RED;
95 break;
96 /* PENDING(khz) Verify exact meaning of the following values:
97 case 7: // GPGME_SIG_STAT_GOOD_EXP
98 return i18n("Signature certificate is expired");
99 break;
100 case 8: // GPGME_SIG_STAT_GOOD_EXPKEY
101 return i18n("One of the certificate's keys is expired");
102 break;
103 */
104 default:
105 result.clear(); // do *not* return a default text here !
106 break;
107 }
108 } else if (cryptProto == QGpgME::smime()) {
109 // process status bits according to SigStatus_...
110 // definitions in kdenetwork/libkdenetwork/cryptplug.h
111
112 if (summary == GpgME::Signature::None) {
113 result = i18n("No status information available.");
114 frameColor = SIG_FRAME_COL_YELLOW;
115 showKeyInfos = false;
116 return result;
117 }
118
119 if (summary & GpgME::Signature::Valid) {
120 result = i18n("Good signature.");
121 // Note:
122 // Here we are work differently than KMail did before!
123 //
124 // The GOOD case ( == sig matching and the complete
125 // certificate chain was verified and is valid today )
126 // by definition does *not* show any key
127 // information but just states that things are OK.
128 // (khz, according to LinuxTag 2002 meeting)
129 frameColor = SIG_FRAME_COL_GREEN;
130 showKeyInfos = false;
131 return result;
132 }
133
134 // we are still there? OK, let's test the different cases:
135
136 // we assume green, test for yellow or red (in this order!)
137 frameColor = SIG_FRAME_COL_GREEN;
138 QString result2;
139 if (summary & GpgME::Signature::KeyExpired) {
140 // still is green!
141 result2 = i18n("One key has expired.");
142 }
143 if (summary & GpgME::Signature::SigExpired) {
144 // and still is green!
145 result2 += i18n("The signature has expired.");
146 }
147
148 // test for yellow:
149 if (summary & GpgME::Signature::KeyMissing) {
150 result2 += i18n("Unable to verify: key missing.");
151 // if the signature certificate is missing
152 // we cannot show information on it
153 showKeyInfos = false;
154 frameColor = SIG_FRAME_COL_YELLOW;
155 }
156 if (summary & GpgME::Signature::CrlMissing) {
157 result2 += i18n("CRL not available.");
158 frameColor = SIG_FRAME_COL_YELLOW;
159 }
160 if (summary & GpgME::Signature::CrlTooOld) {
161 result2 += i18n("Available CRL is too old.");
162 frameColor = SIG_FRAME_COL_YELLOW;
163 }
164 if (summary & GpgME::Signature::BadPolicy) {
165 result2 += i18n("A policy was not met.");
166 frameColor = SIG_FRAME_COL_YELLOW;
167 }
168 if (summary & GpgME::Signature::SysError) {
169 result2 += i18n("A system error occurred.");
170 // if a system error occurred
171 // we cannot trust any information
172 // that was given back by the plug-in
173 showKeyInfos = false;
174 frameColor = SIG_FRAME_COL_YELLOW;
175 }
176
177 // test for red:
178 if (summary & GpgME::Signature::KeyRevoked) {
179 // this is red!
180 result2 += i18n("One key has been revoked.");
181 frameColor = SIG_FRAME_COL_RED;
182 }
183 if (summary & GpgME::Signature::Red) {
184 if (result2.isEmpty()) {
185 // Note:
186 // Here we are work differently than KMail did before!
187 //
188 // The BAD case ( == sig *not* matching )
189 // by definition does *not* show any key
190 // information but just states that things are BAD.
191 //
192 // The reason for this: In this case ALL information
193 // might be falsificated, we can NOT trust the data
194 // in the body NOT the signature - so we don't show
195 // any key/signature information at all!
196 // (khz, according to LinuxTag 2002 meeting)
197 showKeyInfos = false;
198 }
199 frameColor = SIG_FRAME_COL_RED;
200 } else {
201 result.clear();
202 }
203
204 if (SIG_FRAME_COL_GREEN == frameColor) {
205 result = i18n("Good signature.");
206 } else if (SIG_FRAME_COL_RED == frameColor) {
207 result = i18n("Bad signature.");
208 } else {
209 result.clear();
210 }
211
212 if (!result2.isEmpty()) {
213 if (!result.isEmpty()) {
214 result.append(QLatin1StringView("<br />"));
215 }
216 result.append(result2);
217 }
218 }
219 /*
220 // add i18n support for 3rd party plug-ins here:
221 else if ( cryptPlug->libName().contains( "yetanotherpluginname", Qt::CaseInsensitive )) {
222
223 }
224 */
225 }
226 return result;
227}
228
229DefaultRendererPrivate::DefaultRendererPrivate(CSSHelperBase *cssHelper, const MessagePartRendererFactory *rendererFactory)
230 : mCSSHelper(cssHelper)
231 , mRendererFactory(rendererFactory)
232{
233}
234
235DefaultRendererPrivate::~DefaultRendererPrivate() = default;
236
237CSSHelperBase *DefaultRendererPrivate::cssHelper() const
238{
239 return mCSSHelper;
240}
241
242Interface::ObjectTreeSource *DefaultRendererPrivate::source() const
243{
244 return mMsgPart->source();
245}
246
247void DefaultRendererPrivate::renderSubParts(const MessagePart::Ptr &msgPart, HtmlWriter *htmlWriter)
248{
249 for (const auto &m : msgPart->subParts()) {
250 renderFactory(m, htmlWriter);
251 }
252}
253
254void DefaultRendererPrivate::render(const MessagePartList::Ptr &mp, HtmlWriter *htmlWriter)
255{
256 HTMLBlock::Ptr rBlock;
257 HTMLBlock::Ptr aBlock;
258
259 if (mp->isRoot()) {
260 rBlock = HTMLBlock::Ptr(new RootBlock(htmlWriter));
261 }
262
263 if (mp->isAttachment()) {
264 aBlock = HTMLBlock::Ptr(new AttachmentMarkBlock(htmlWriter, mp->attachmentContent()));
265 }
266
267 renderSubParts(mp, htmlWriter);
268}
269
270void DefaultRendererPrivate::render(const MimeMessagePart::Ptr &mp, HtmlWriter *htmlWriter)
271{
272 HTMLBlock::Ptr aBlock;
273 HTMLBlock::Ptr rBlock;
274 if (mp->isAttachment()) {
275 aBlock = HTMLBlock::Ptr(new AttachmentMarkBlock(htmlWriter, mp->attachmentContent()));
276 }
277 if (mp->isRoot()) {
278 rBlock = HTMLBlock::Ptr(new RootBlock(htmlWriter));
279 }
280
281 renderSubParts(mp, htmlWriter);
282}
283
284void DefaultRendererPrivate::render(const EncapsulatedRfc822MessagePart::Ptr &mp, HtmlWriter *htmlWriter)
285{
286 if (!mp->hasSubParts()) {
287 return;
288 }
289 KTextTemplate::Template t = MessagePartRendererManager::self()->loadByName(QStringLiteral("encapsulatedrfc822messagepart.html"));
290 KTextTemplate::Context c = MessagePartRendererManager::self()->createContext();
291 QObject block;
292
293 c.insert(QStringLiteral("block"), &block);
294 block.setProperty("link", mp->nodeHelper()->asHREF(mp->message().data(), QStringLiteral("body")));
295
296 c.insert(QStringLiteral("msgHeader"), mCreateMessageHeader(mp->message().data()));
297 c.insert(QStringLiteral("content"), QVariant::fromValue<KTextTemplateCallback>([this, mp, htmlWriter](KTextTemplate::OutputStream *) {
298 renderSubParts(mp, htmlWriter);
299 }));
300 HTMLBlock::Ptr aBlock;
301 if (mp->isAttachment()) {
302 aBlock = HTMLBlock::Ptr(new AttachmentMarkBlock(htmlWriter, mp->attachmentContent()));
303 }
304 KTextTemplate::OutputStream s(htmlWriter->stream());
305 t->render(&s, &c);
306}
307
308void DefaultRendererPrivate::render(const HtmlMessagePart::Ptr &mp, HtmlWriter *htmlWriter)
309{
310 KTextTemplate::Template t = MessageViewer::MessagePartRendererManager::self()->loadByName(QStringLiteral("htmlmessagepart.html"));
311 KTextTemplate::Context c = MessageViewer::MessagePartRendererManager::self()->createContext();
312 QObject block;
313
314 c.insert(QStringLiteral("block"), &block);
315
316 auto preferredMode = mp->source()->preferredMode();
317 const bool isHtmlPreferred = (preferredMode == MimeTreeParser::Util::Html) || (preferredMode == MimeTreeParser::Util::MultipartHtml);
318 block.setProperty("htmlMail", isHtmlPreferred);
319 block.setProperty("loadExternal", htmlLoadExternal());
320 block.setProperty("isPrinting", isPrinting());
321 {
322 // laurent: FIXME port to async method webengine
323 const Util::HtmlMessageInfo messageInfo = Util::processHtml(mp->bodyHtml());
324
325 if (isHtmlPreferred) {
326 mp->nodeHelper()->setNodeDisplayedEmbedded(mp->content(), true);
327 htmlWriter->setExtraHead(messageInfo.extraHead);
328 htmlWriter->setStyleBody(Util::parseBodyStyle(messageInfo.bodyStyle));
329 }
330
331 block.setProperty("containsExternalReferences", Util::containsExternalReferences(messageInfo.htmlSource, messageInfo.extraHead));
332 c.insert(QStringLiteral("content"), messageInfo.htmlSource);
333 }
334
335 {
336 ConvertHtmlToPlainText convert;
337 convert.setHtmlString(mp->bodyHtml());
338 QString plaintext = convert.generatePlainText();
339 plaintext.replace(QLatin1Char('\n'), QStringLiteral("<br>"));
340 c.insert(QStringLiteral("plaintext"), plaintext);
341 }
342 mp->source()->setHtmlMode(MimeTreeParser::Util::Html,
344
345 HTMLBlock::Ptr aBlock;
346 if (mp->isAttachment()) {
347 aBlock = HTMLBlock::Ptr(new AttachmentMarkBlock(htmlWriter, mp->attachmentContent()));
348 }
349 KTextTemplate::OutputStream s(htmlWriter->stream());
350 t->render(&s, &c);
351}
352
353void DefaultRendererPrivate::renderEncrypted(const EncryptedMessagePart::Ptr &mp, HtmlWriter *htmlWriter)
354{
355 KMime::Content *node = mp->content();
356 const auto metaData = *mp->partMetaData();
357 KTextTemplate::Template t = MessageViewer::MessagePartRendererManager::self()->loadByName(QStringLiteral("encryptedmessagepart.html"));
358 KTextTemplate::Context c = MessageViewer::MessagePartRendererManager::self()->createContext();
359 QObject block;
360 if (node || mp->hasSubParts()) {
361 c.insert(QStringLiteral("content"), QVariant::fromValue<KTextTemplateCallback>([this, mp, htmlWriter](KTextTemplate::OutputStream *) {
362 HTMLBlock::Ptr rBlock;
363 if (mp->content() && mp->isRoot()) {
364 rBlock = HTMLBlock::Ptr(new RootBlock(htmlWriter));
365 }
366 renderSubParts(mp, htmlWriter);
367 }));
368 } else if (!metaData.inProgress) {
369 c.insert(QStringLiteral("content"), QVariant::fromValue<KTextTemplateCallback>([this, mp, htmlWriter](KTextTemplate::OutputStream *) {
370 renderWithFactory<MimeTreeParser::MessagePart>(mp, htmlWriter);
371 }));
372 }
373 c.insert(QStringLiteral("cryptoProto"), QVariant::fromValue(mp->cryptoProto()));
374 if (!mp->decryptRecipients().empty()) {
375 c.insert(QStringLiteral("decryptedRecipients"), QVariant::fromValue(mp->decryptRecipients()));
376 }
377 c.insert(QStringLiteral("block"), &block);
378
379 block.setProperty("isPrinting", isPrinting());
380 block.setProperty("detailHeader", showEncryptionDetails());
381 block.setProperty("inProgress", metaData.inProgress);
382 block.setProperty("isDecrypted", mp->decryptMessage());
383 block.setProperty("isDecryptable", metaData.isDecryptable);
384 block.setProperty("decryptIcon", QUrl::fromLocalFile(IconNameCache::instance()->iconPath(QStringLiteral("document-decrypt"), KIconLoader::Small)).url());
385 block.setProperty("errorText", metaData.errorText);
386 block.setProperty("noSecKey", mp->isNoSecKey());
387 block.setProperty("isCompliant", metaData.isCompliant);
388 block.setProperty("compliance", metaData.compliance);
389 KTextTemplate::OutputStream s(htmlWriter->stream());
390 t->render(&s, &c);
391}
392
393void DefaultRendererPrivate::renderSigned(const SignedMessagePart::Ptr &mp, HtmlWriter *htmlWriter)
394{
395 KMime::Content *node = mp->content();
396 const auto metaData = *mp->partMetaData();
397 auto cryptoProto = mp->cryptoProto();
398
399 const bool isSMIME = cryptoProto && (cryptoProto == QGpgME::smime());
400 KTextTemplate::Template t = MessageViewer::MessagePartRendererManager::self()->loadByName(QStringLiteral("signedmessagepart.html"));
401 KTextTemplate::Context c = MessageViewer::MessagePartRendererManager::self()->createContext();
402 QObject block;
403
404 if (node) {
405 c.insert(QStringLiteral("content"), QVariant::fromValue<KTextTemplateCallback>([this, mp, htmlWriter](KTextTemplate::OutputStream *) {
406 HTMLBlock::Ptr rBlock;
407 if (mp->isRoot()) {
408 rBlock = HTMLBlock::Ptr(new RootBlock(htmlWriter));
409 }
410 renderSubParts(mp, htmlWriter);
411 }));
412 } else if (!metaData.inProgress) {
413 c.insert(QStringLiteral("content"), QVariant::fromValue<KTextTemplateCallback>([this, mp, htmlWriter](KTextTemplate::OutputStream *) {
414 renderWithFactory<MimeTreeParser::MessagePart>(mp, htmlWriter);
415 }));
416 }
417 c.insert(QStringLiteral("cryptoProto"), QVariant::fromValue(cryptoProto));
418 c.insert(QStringLiteral("block"), &block);
419
420 block.setProperty("inProgress", metaData.inProgress);
421 block.setProperty("errorText", metaData.errorText);
422
423 block.setProperty("detailHeader", showSignatureDetails());
424 block.setProperty("isPrinting", isPrinting());
425 block.setProperty("technicalProblem", metaData.technicalProblem);
426 block.setProperty("keyId", metaData.keyId);
427 if (metaData.creationTime.isValid()) { // should be handled inside grantlee but currently not possible see: https://bugs.kde.org/363475
428 block.setProperty("creationTime", QLocale().toString(metaData.creationTime, QLocale::ShortFormat));
429 }
430 block.setProperty("isGoodSignature", metaData.isGoodSignature);
431 block.setProperty("isCompliant", metaData.isCompliant);
432 block.setProperty("compliance", metaData.compliance);
433 block.setProperty("isSMIME", isSMIME);
434
435 QString startKeyHREF;
436 {
437 QString keyWithWithoutURL;
438 if (cryptoProto) {
439 startKeyHREF = QStringLiteral("<a href=\"key:#%1\">").arg(QString::fromLatin1(metaData.keyId));
440
441 keyWithWithoutURL = QStringLiteral("%1%2</a>").arg(startKeyHREF, QString::fromLatin1(QByteArray(QByteArrayLiteral("0x") + metaData.keyId)));
442 } else {
443 keyWithWithoutURL = QLatin1StringView("0x") + QString::fromUtf8(metaData.keyId);
444 }
445 block.setProperty("keyWithWithoutURL", keyWithWithoutURL);
446 }
447
448 QString statusStr;
449 QString mClass;
450
451 const auto signatures = metaData.verificationResult.signatures();
452 if (metaData.inProgress) {
453 mClass = QStringLiteral("signInProgress");
454 } else {
455 Q_ASSERT(!signatures.empty());
456 const auto signature = signatures.front(); // TODO add support for multiple signature
457 const auto summary = signature.summary();
458
459 statusStr = Kleo::Formatting::prettySignature(signature, {});
460
461 if (summary & GpgME::Signature::Summary::Red) {
462 mClass = QStringLiteral("signErr");
463 } else if (summary & GpgME::Signature::Summary::Valid) {
464 mClass = QStringLiteral("signOkKeyOk");
465 } else {
466 mClass = QStringLiteral("signWarn");
467 }
468 }
469
470 block.setProperty("statusStr", statusStr);
471 block.setProperty("signClass", mClass);
472 KTextTemplate::OutputStream s(htmlWriter->stream());
473 t->render(&s, &c);
474}
475
476void DefaultRendererPrivate::render(const SignedMessagePart::Ptr &mp, HtmlWriter *htmlWriter)
477{
478 const auto metaData = *mp->partMetaData();
479 if (metaData.isSigned || metaData.inProgress) {
480 HTMLBlock::Ptr aBlock;
481 if (mp->isAttachment()) {
482 aBlock = HTMLBlock::Ptr(new AttachmentMarkBlock(htmlWriter, mp->attachmentContent()));
483 }
484 renderSigned(mp, htmlWriter);
485 return;
486 }
487
488 HTMLBlock::Ptr aBlock;
489 if (mp->isAttachment()) {
490 aBlock = HTMLBlock::Ptr(new AttachmentMarkBlock(htmlWriter, mp->attachmentContent()));
491 }
492 if (mp->hasSubParts()) {
493 renderSubParts(mp, htmlWriter);
494 } else if (!metaData.inProgress) {
495 renderWithFactory<MimeTreeParser::MessagePart>(mp, htmlWriter);
496 }
497}
498
499void DefaultRendererPrivate::render(const EncryptedMessagePart::Ptr &mp, HtmlWriter *htmlWriter)
500{
501 const auto metaData = *mp->partMetaData();
502
503 if (metaData.isEncrypted || metaData.inProgress) {
504 HTMLBlock::Ptr aBlock;
505 if (mp->isAttachment()) {
506 aBlock = HTMLBlock::Ptr(new AttachmentMarkBlock(htmlWriter, mp->attachmentContent()));
507 }
508 renderEncrypted(mp, htmlWriter);
509 return;
510 }
511
512 HTMLBlock::Ptr aBlock;
513 if (mp->isAttachment()) {
514 aBlock = HTMLBlock::Ptr(new AttachmentMarkBlock(htmlWriter, mp->attachmentContent()));
515 }
516
517 if (mp->hasSubParts()) {
518 renderSubParts(mp, htmlWriter);
519 } else if (!metaData.inProgress) {
520 renderWithFactory<MimeTreeParser::MessagePart>(mp, htmlWriter);
521 }
522}
523
524void DefaultRendererPrivate::render(const AlternativeMessagePart::Ptr &mp, HtmlWriter *htmlWriter)
525{
526 HTMLBlock::Ptr aBlock;
527 if (mp->isAttachment()) {
528 aBlock = HTMLBlock::Ptr(new AttachmentMarkBlock(htmlWriter, mp->attachmentContent()));
529 }
530
531 auto mode = mp->preferredMode();
532 if (mode == MimeTreeParser::Util::MultipartPlain && mp->text().trimmed().isEmpty()) {
533 const auto availableModes = mp->availableModes();
534 for (const auto m : availableModes) {
536 mode = m;
537 break;
538 }
539 }
540 }
541 MimeMessagePart::Ptr part(mp->childParts().first());
542 if (mp->childParts().contains(mode)) {
543 part = mp->childParts()[mode];
544 }
545
546 render(part, htmlWriter);
547}
548
549void DefaultRendererPrivate::render(const CertMessagePart::Ptr &mp, HtmlWriter *htmlWriter)
550{
551 const GpgME::ImportResult &importResult(mp->importResult());
552 KTextTemplate::Template t = MessageViewer::MessagePartRendererManager::self()->loadByName(QStringLiteral("certmessagepart.html"));
553 KTextTemplate::Context c = MessageViewer::MessagePartRendererManager::self()->createContext();
554 QObject block;
555
556 c.insert(QStringLiteral("block"), &block);
557 block.setProperty("importError", Kleo::Formatting::errorAsString(importResult.error()));
558 block.setProperty("nImp", importResult.numImported());
559 block.setProperty("nUnc", importResult.numUnchanged());
560 block.setProperty("nSKImp", importResult.numSecretKeysImported());
561 block.setProperty("nSKUnc", importResult.numSecretKeysUnchanged());
562
563 QVariantList keylist;
564 const auto imports = importResult.imports();
565
566 auto end(imports.end());
567 for (auto it = imports.begin(); it != end; ++it) {
568 auto key(new QObject(mp.data()));
569 key->setProperty("error", Kleo::Formatting::errorAsString(it->error()));
570 key->setProperty("status", (*it).status());
571 key->setProperty("fingerprint", QLatin1StringView((*it).fingerprint()));
572 keylist << QVariant::fromValue(key);
573 }
574
575 HTMLBlock::Ptr aBlock;
576 if (mp->isAttachment()) {
577 aBlock = HTMLBlock::Ptr(new AttachmentMarkBlock(htmlWriter, mp->attachmentContent()));
578 }
579 KTextTemplate::OutputStream s(htmlWriter->stream());
580 t->render(&s, &c);
581}
582
583bool DefaultRendererPrivate::renderWithFactory(const QMetaObject *mo, const MessagePart::Ptr &msgPart, HtmlWriter *htmlWriter)
584{
585 if (!mRendererFactory) {
586 return false;
587 }
588 for (auto r : mRendererFactory->renderersForPart(mo, msgPart)) {
589 if (r->render(msgPart, htmlWriter, this)) {
590 return true;
591 }
592 }
593 return false;
594}
595
596void DefaultRendererPrivate::renderFactory(const MessagePart::Ptr &msgPart, HtmlWriter *htmlWriter)
597{
598 const QString className = QString::fromUtf8(msgPart->metaObject()->className());
599
600 if (isHiddenHint(msgPart)) {
601 const QByteArray cid = msgPart->content()->contentID()->identifier();
602 auto mp = msgPart.dynamicCast<MimeTreeParser::TextMessagePart>();
603 if (!cid.isEmpty() && mp) {
604 const QString fileName = mp->temporaryFilePath();
605 const QString href = QUrl::fromLocalFile(fileName).url();
606 htmlWriter->embedPart(cid, href);
607 }
608 }
609
610 if (renderWithFactory(msgPart, htmlWriter)) {
611 return;
612 }
613
614 if (className == QLatin1StringView("MimeTreeParser::MessagePartList")) {
615 auto mp = msgPart.dynamicCast<MessagePartList>();
616 if (mp) {
617 render(mp, htmlWriter);
618 }
619 } else if (className == QLatin1StringView("MimeTreeParser::MimeMessagePart")) {
620 auto mp = msgPart.dynamicCast<MimeMessagePart>();
621 if (mp) {
622 render(mp, htmlWriter);
623 }
624 } else if (className == QLatin1StringView("MimeTreeParser::EncapsulatedRfc822MessagePart")) {
625 auto mp = msgPart.dynamicCast<EncapsulatedRfc822MessagePart>();
626 if (mp) {
627 render(mp, htmlWriter);
628 }
629 } else if (className == QLatin1StringView("MimeTreeParser::HtmlMessagePart")) {
630 auto mp = msgPart.dynamicCast<HtmlMessagePart>();
631 if (mp) {
632 render(mp, htmlWriter);
633 }
634 } else if (className == QLatin1StringView("MimeTreeParser::SignedMessagePart")) {
635 auto mp = msgPart.dynamicCast<SignedMessagePart>();
636 if (mp) {
637 render(mp, htmlWriter);
638 }
639 } else if (className == QLatin1StringView("MimeTreeParser::EncryptedMessagePart")) {
640 auto mp = msgPart.dynamicCast<EncryptedMessagePart>();
641 if (mp) {
642 render(mp, htmlWriter);
643 }
644 } else if (className == QLatin1StringView("MimeTreeParser::AlternativeMessagePart")) {
645 auto mp = msgPart.dynamicCast<AlternativeMessagePart>();
646 if (mp) {
647 render(mp, htmlWriter);
648 }
649 } else if (className == QLatin1StringView("MimeTreeParser::CertMessagePart")) {
650 auto mp = msgPart.dynamicCast<CertMessagePart>();
651 if (mp) {
652 render(mp, htmlWriter);
653 }
654 } else {
655 qCWarning(MESSAGEVIEWER_LOG) << "We got a unknown classname, using default behaviour for " << className;
656 }
657}
658
659bool DefaultRendererPrivate::isHiddenHint(const MimeTreeParser::MessagePart::Ptr &msgPart)
660{
661 auto mp = msgPart.dynamicCast<MimeTreeParser::MessagePart>();
662 auto content = msgPart->content();
663
664 if (!mp || !content) {
665 return false;
666 }
667
668 if (mShowOnlyOneMimePart && mMsgPart.data() == msgPart->parentPart()) {
669 if (mMsgPart->subParts().at(0) == msgPart.data()) {
670 return false;
671 }
672 }
673
674 if (msgPart->nodeHelper()->isNodeDisplayedHidden(content)) {
675 return true;
676 }
677
678 const AttachmentStrategy *const as = mAttachmentStrategy;
679 const bool defaultHidden(as && as->defaultDisplay(content) == AttachmentStrategy::None);
680 auto preferredMode = source()->preferredMode();
681 bool isHtmlPreferred = (preferredMode == MimeTreeParser::Util::Html) || (preferredMode == MimeTreeParser::Util::MultipartHtml);
682
683 QByteArray mediaType("text");
684 if (content->contentType(false) && !content->contentType(false)->mediaType().isEmpty() && !content->contentType(false)->subType().isEmpty()) {
685 mediaType = content->contentType(false)->mediaType();
686 }
687 const bool isTextPart = (mediaType == QByteArrayLiteral("text"));
688
689 bool defaultAsIcon = true;
690 if (!mp->neverDisplayInline()) {
691 if (as) {
692 defaultAsIcon = as->defaultDisplay(content) == AttachmentStrategy::AsIcon;
693 }
694 }
695
696 // neither image nor text -> show as icon
697 if (!mp->isImage() && !isTextPart) {
698 defaultAsIcon = true;
699 }
700
701 bool hidden(false);
702 if (isTextPart) {
703 hidden = defaultHidden;
704 } else {
705 if (mp->isImage() && isHtmlPreferred && content->parent() && content->parent()->contentType(false)->subType() == "related") {
706 hidden = true;
707 } else {
708 hidden = defaultHidden && content->parent();
709 hidden |= defaultAsIcon && defaultHidden;
710 }
711 }
712 msgPart->nodeHelper()->setNodeDisplayedHidden(content, hidden);
713 return hidden;
714}
715
716MimeTreeParser::IconType DefaultRendererPrivate::displayHint(const MimeTreeParser::MessagePart::Ptr &msgPart)
717{
718 auto mp = msgPart.dynamicCast<MimeTreeParser::TextMessagePart>();
719 auto content = msgPart->content();
720
721 if (!content || !mp) {
722 return MimeTreeParser::IconType::NoIcon;
723 }
724
725 const AttachmentStrategy *const as = mAttachmentStrategy;
726 const bool defaultDisplayHidden(as && as->defaultDisplay(content) == AttachmentStrategy::None);
727 const bool defaultDisplayInline(as && as->defaultDisplay(content) == AttachmentStrategy::Inline);
728 const bool defaultDisplayAsIcon(as && as->defaultDisplay(content) == AttachmentStrategy::AsIcon);
729 const bool showOnlyOneMimePart(mShowOnlyOneMimePart);
730 auto preferredMode = source()->preferredMode();
731 bool isHtmlPreferred = (preferredMode == MimeTreeParser::Util::Html) || (preferredMode == MimeTreeParser::Util::MultipartHtml);
732
733 QByteArray mediaType("text");
734 if (content->contentType(false) && !content->contentType(false)->mediaType().isEmpty() && !content->contentType(false)->subType().isEmpty()) {
735 mediaType = content->contentType(false)->mediaType();
736 }
737 const bool isTextPart = (mediaType == QByteArrayLiteral("text"));
738
739 bool defaultAsIcon = true;
740 if (!mp->neverDisplayInline()) {
741 if (as) {
742 defaultAsIcon = defaultDisplayAsIcon;
743 }
744 }
745 if (mp->isImage() && showOnlyOneMimePart && !mp->neverDisplayInline()) {
746 defaultAsIcon = false;
747 }
748
749 // neither image nor text -> show as icon
750 if (!mp->isImage() && !isTextPart) {
751 defaultAsIcon = true;
752 }
753
754 if (isTextPart) {
755 if (as && !defaultDisplayInline) {
756 return MimeTreeParser::IconExternal;
757 }
758 return MimeTreeParser::NoIcon;
759 } else {
760 if (mp->isImage() && isHtmlPreferred && content->parent() && content->parent()->contentType(false)->subType() == "related") {
761 return MimeTreeParser::IconInline;
762 }
763
764 if (defaultDisplayHidden && !showOnlyOneMimePart && content->parent()) {
765 return MimeTreeParser::IconInline;
766 }
767
768 if (defaultAsIcon) {
769 return MimeTreeParser::IconExternal;
770 } else if (mp->isImage()) {
771 return MimeTreeParser::IconInline;
772 }
773 }
774
775 return MimeTreeParser::NoIcon;
776}
777
778bool DefaultRendererPrivate::showEmoticons() const
779{
780 return mShowEmoticons;
781}
782
783bool DefaultRendererPrivate::isPrinting() const
784{
785 return mIsPrinting;
786}
787
788bool DefaultRendererPrivate::htmlLoadExternal() const
789{
790 return mHtmlLoadExternal;
791}
792
793bool DefaultRendererPrivate::showExpandQuotesMark() const
794{
795 return mShowExpandQuotesMark;
796}
797
798bool DefaultRendererPrivate::showOnlyOneMimePart() const
799{
800 return mShowOnlyOneMimePart;
801}
802
803bool DefaultRendererPrivate::showSignatureDetails() const
804{
805 return mShowSignatureDetails;
806}
807
808bool DefaultRendererPrivate::showEncryptionDetails() const
809{
810 return mShowEncryptionDetails;
811}
812
813int DefaultRendererPrivate::levelQuote() const
814{
815 return mLevelQuote;
816}
817
818DefaultRenderer::DefaultRenderer(CSSHelperBase *cssHelper)
819 : d(new DefaultRendererPrivate(cssHelper, MessagePartRendererFactory::instance()))
820{
821}
822
823DefaultRenderer::~DefaultRenderer() = default;
824
825void DefaultRenderer::setShowOnlyOneMimePart(bool onlyOneMimePart)
826{
827 d->mShowOnlyOneMimePart = onlyOneMimePart;
828}
829
830void DefaultRenderer::setAttachmentStrategy(const AttachmentStrategy *strategy)
831{
832 d->mAttachmentStrategy = strategy;
833}
834
835void DefaultRenderer::setShowEmoticons(bool showEmoticons)
836{
837 d->mShowEmoticons = showEmoticons;
838}
839
840void DefaultRenderer::setIsPrinting(bool isPrinting)
841{
842 d->mIsPrinting = isPrinting;
843}
844
845void DefaultRenderer::setShowExpandQuotesMark(bool showExpandQuotesMark)
846{
847 d->mShowExpandQuotesMark = showExpandQuotesMark;
848}
849
850void DefaultRenderer::setShowEncryptionDetails(bool showEncryptionDetails)
851{
852 d->mShowEncryptionDetails = showEncryptionDetails;
853}
854
855void DefaultRenderer::setShowSignatureDetails(bool showSignatureDetails)
856{
857 d->mShowSignatureDetails = showSignatureDetails;
858}
859
860void DefaultRenderer::setLevelQuote(int levelQuote)
861{
862 d->mLevelQuote = levelQuote;
863}
864
865void DefaultRenderer::setHtmlLoadExternal(bool htmlLoadExternal)
866{
867 d->mHtmlLoadExternal = htmlLoadExternal;
868}
869
870void DefaultRenderer::setCreateMessageHeader(const std::function<QString(KMime::Message *)> &createMessageHeader)
871{
872 d->mCreateMessageHeader = createMessageHeader;
873}
874
875QString renderTreeHelper(const MimeTreeParser::MessagePart::Ptr &messagePart, QString indent)
876{
877 QString ret = QStringLiteral("%1 * %3\n").arg(indent, QString::fromUtf8(messagePart->metaObject()->className()));
878 indent += QLatin1Char(' ');
879 for (const auto &subPart : messagePart->subParts()) {
880 ret += renderTreeHelper(subPart, indent);
881 }
882 return ret;
883}
884
885void DefaultRenderer::render(const MimeTreeParser::MessagePart::Ptr &msgPart, HtmlWriter *writer)
886{
887 qCDebug(MESSAGEVIEWER_LOG) << "MimeTreeParser structure:";
888 qCDebug(MESSAGEVIEWER_LOG) << qPrintable(renderTreeHelper(msgPart, QString()));
889 d->mMsgPart = msgPart;
890 d->renderFactory(d->mMsgPart, writer);
891}
void insert(const QString &name, const QVariant &variant)
QString render(Context *c) const
The AttachmentMarkBlock class.
Definition htmlblock.h:53
The AttachmentStrategy class.
The CSSHelperBase class.
An interface for HTML sinks.
Definition htmlwriter.h:29
virtual void embedPart(const QByteArray &contentId, const QString &url)=0
Embed a part with Content-ID contentId, using url url.
QTextStream * stream() const
Returns a QTextStream on device().
The MessagePartRendererFactory class.
Interface for object tree sources.
QString i18n(const char *text, const TYPE &arg...)
char * toString(const EngineQuery &query)
T convert(const QVariant &value)
const QList< QKeySequence > & end()
MESSAGEVIEWER_EXPORT bool containsExternalReferences(const QString &str, const QString &extraHead)
Checks whether str contains external references.
@ Html
A HTML message, non-multipart.
@ Normal
A normal plaintext message, non-multipart.
@ MultipartPlain
A multipart/alternative message, the plain text part is currently displayed.
@ MultipartHtml
A multipart/alternative message, the HTML part is currently displayed.
bool isEmpty() const const
QObject * parent() const const
bool setProperty(const char *name, QVariant &&value)
T * data() const const
QSharedPointer< X > dynamicCast() const const
QString & append(QChar ch)
QString arg(Args &&... args) const const
void clear()
QString fromLatin1(QByteArrayView str)
QString fromUtf8(QByteArrayView str)
bool isEmpty() const const
QString & replace(QChar before, QChar after, Qt::CaseSensitivity cs)
QUrl fromLocalFile(const QString &localFile)
QString url(FormattingOptions options) const const
QVariant fromValue(T &&value)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Oct 11 2024 12:08:46 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.