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

KDE's Doxygen guidelines are available online.