Messagelib

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

KDE's Doxygen guidelines are available online.