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 
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 
45 using namespace MimeTreeParser;
46 using namespace MessageViewer;
47 
48 Q_DECLARE_METATYPE(GpgME::DecryptionResult::Recipient)
49 Q_DECLARE_METATYPE(GpgME::Key)
50 Q_DECLARE_METATYPE(const QGpgME::Protocol *)
51 
52 static 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
56 QString 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(QLatin1String("<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 
229 DefaultRendererPrivate::DefaultRendererPrivate(CSSHelperBase *cssHelper, const MessagePartRendererFactory *rendererFactory)
230  : mCSSHelper(cssHelper)
231  , mRendererFactory(rendererFactory)
232 {
233 }
234 
235 DefaultRendererPrivate::~DefaultRendererPrivate() = default;
236 
237 CSSHelperBase *DefaultRendererPrivate::cssHelper() const
238 {
239  return mCSSHelper;
240 }
241 
242 Interface::ObjectTreeSource *DefaultRendererPrivate::source() const
243 {
244  return mMsgPart->source();
245 }
246 
247 void DefaultRendererPrivate::renderSubParts(const MessagePart::Ptr &msgPart, HtmlWriter *htmlWriter)
248 {
249  for (const auto &m : msgPart->subParts()) {
250  renderFactory(m, htmlWriter);
251  }
252 }
253 
254 void 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 
270 void 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 
284 void DefaultRendererPrivate::render(const EncapsulatedRfc822MessagePart::Ptr &mp, HtmlWriter *htmlWriter)
285 {
286  if (!mp->hasSubParts()) {
287  return;
288  }
289  Grantlee::Template t = MessagePartRendererManager::self()->loadByName(QStringLiteral("encapsulatedrfc822messagepart.html"));
290  Grantlee::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<GrantleeCallback>([this, mp, htmlWriter](Grantlee::OutputStream *) {
298  renderSubParts(mp, htmlWriter);
299  }));
300 
301  HTMLBlock::Ptr aBlock;
302  if (mp->isAttachment()) {
303  aBlock = HTMLBlock::Ptr(new AttachmentMarkBlock(htmlWriter, mp->attachmentContent()));
304  }
305  Grantlee::OutputStream s(htmlWriter->stream());
306  t->render(&s, &c);
307 }
308 
309 void DefaultRendererPrivate::render(const HtmlMessagePart::Ptr &mp, HtmlWriter *htmlWriter)
310 {
311  Grantlee::Template t = MessageViewer::MessagePartRendererManager::self()->loadByName(QStringLiteral("htmlmessagepart.html"));
312  Grantlee::Context c = MessageViewer::MessagePartRendererManager::self()->createContext();
313  QObject block;
314 
315  c.insert(QStringLiteral("block"), &block);
316 
317  auto preferredMode = mp->source()->preferredMode();
318  const bool isHtmlPreferred = (preferredMode == MimeTreeParser::Util::Html) || (preferredMode == MimeTreeParser::Util::MultipartHtml);
319  block.setProperty("htmlMail", isHtmlPreferred);
320  block.setProperty("loadExternal", htmlLoadExternal());
321  block.setProperty("isPrinting", isPrinting());
322  {
323  // laurent: FIXME port to async method webengine
324  Util::HtmlMessageInfo messageInfo = Util::processHtml(mp->bodyHtml());
325 
326  if (isHtmlPreferred) {
327  mp->nodeHelper()->setNodeDisplayedEmbedded(mp->content(), true);
328  htmlWriter->setExtraHead(messageInfo.extraHead);
329  htmlWriter->setStyleBody(Util::parseBodyStyle(messageInfo.bodyStyle));
330  }
331 
332  block.setProperty("containsExternalReferences", Util::containsExternalReferences(messageInfo.htmlSource, messageInfo.extraHead));
333  c.insert(QStringLiteral("content"), messageInfo.htmlSource);
334  }
335 
336  {
337  ConvertHtmlToPlainText convert;
338  convert.setHtmlString(mp->bodyHtml());
339  QString plaintext = convert.generatePlainText();
340  plaintext.replace(QLatin1Char('\n'), QStringLiteral("<br>"));
341  c.insert(QStringLiteral("plaintext"), plaintext);
342  }
343  mp->source()->setHtmlMode(MimeTreeParser::Util::Html,
345 
346  HTMLBlock::Ptr aBlock;
347  if (mp->isAttachment()) {
348  aBlock = HTMLBlock::Ptr(new AttachmentMarkBlock(htmlWriter, mp->attachmentContent()));
349  }
350  Grantlee::OutputStream s(htmlWriter->stream());
351  t->render(&s, &c);
352 }
353 
354 void DefaultRendererPrivate::renderEncrypted(const EncryptedMessagePart::Ptr &mp, HtmlWriter *htmlWriter)
355 {
356  KMime::Content *node = mp->content();
357  const auto metaData = *mp->partMetaData();
358 
359  Grantlee::Template t = MessageViewer::MessagePartRendererManager::self()->loadByName(QStringLiteral("encryptedmessagepart.html"));
360  Grantlee::Context c = MessageViewer::MessagePartRendererManager::self()->createContext();
361  QObject block;
362 
363  if (node || mp->hasSubParts()) {
364  c.insert(QStringLiteral("content"), QVariant::fromValue<GrantleeCallback>([this, mp, htmlWriter](Grantlee::OutputStream *) {
365  HTMLBlock::Ptr rBlock;
366  if (mp->content() && mp->isRoot()) {
367  rBlock = HTMLBlock::Ptr(new RootBlock(htmlWriter));
368  }
369  renderSubParts(mp, htmlWriter);
370  }));
371  } else if (!metaData.inProgress) {
372  c.insert(QStringLiteral("content"), QVariant::fromValue<GrantleeCallback>([this, mp, htmlWriter](Grantlee::OutputStream *) {
373  renderWithFactory<MimeTreeParser::MessagePart>(mp, htmlWriter);
374  }));
375  }
376 
377  c.insert(QStringLiteral("cryptoProto"), QVariant::fromValue(mp->cryptoProto()));
378  if (!mp->decryptRecipients().empty()) {
379  c.insert(QStringLiteral("decryptedRecipients"), QVariant::fromValue(mp->decryptRecipients()));
380  }
381  c.insert(QStringLiteral("block"), &block);
382 
383  block.setProperty("isPrinting", isPrinting());
384  block.setProperty("detailHeader", showEncryptionDetails());
385  block.setProperty("inProgress", metaData.inProgress);
386  block.setProperty("isDecrypted", mp->decryptMessage());
387  block.setProperty("isDecryptable", metaData.isDecryptable);
388  block.setProperty("decryptIcon", QUrl::fromLocalFile(IconNameCache::instance()->iconPath(QStringLiteral("document-decrypt"), KIconLoader::Small)).url());
389  block.setProperty("errorText", metaData.errorText);
390  block.setProperty("noSecKey", mp->isNoSecKey());
391 
392  Grantlee::OutputStream s(htmlWriter->stream());
393  t->render(&s, &c);
394 }
395 
396 void DefaultRendererPrivate::renderSigned(const SignedMessagePart::Ptr &mp, HtmlWriter *htmlWriter)
397 {
398  KMime::Content *node = mp->content();
399  const auto metaData = *mp->partMetaData();
400  auto cryptoProto = mp->cryptoProto();
401 
402  const bool isSMIME = cryptoProto && (cryptoProto == QGpgME::smime());
403 
404  Grantlee::Template t = MessageViewer::MessagePartRendererManager::self()->loadByName(QStringLiteral("signedmessagepart.html"));
405  Grantlee::Context c = MessageViewer::MessagePartRendererManager::self()->createContext();
406  QObject block;
407 
408  if (node) {
409  c.insert(QStringLiteral("content"), QVariant::fromValue<GrantleeCallback>([this, mp, htmlWriter](Grantlee::OutputStream *) {
410  HTMLBlock::Ptr rBlock;
411  if (mp->isRoot()) {
412  rBlock = HTMLBlock::Ptr(new RootBlock(htmlWriter));
413  }
414  renderSubParts(mp, htmlWriter);
415  }));
416  } else if (!metaData.inProgress) {
417  c.insert(QStringLiteral("content"), QVariant::fromValue<GrantleeCallback>([this, mp, htmlWriter](Grantlee::OutputStream *) {
418  renderWithFactory<MimeTreeParser::MessagePart>(mp, htmlWriter);
419  }));
420  }
421 
422  c.insert(QStringLiteral("cryptoProto"), QVariant::fromValue(cryptoProto));
423  c.insert(QStringLiteral("block"), &block);
424 
425  block.setProperty("inProgress", metaData.inProgress);
426  block.setProperty("errorText", metaData.errorText);
427 
428  block.setProperty("detailHeader", showSignatureDetails());
429  block.setProperty("isPrinting", isPrinting());
430  block.setProperty("addr", metaData.signerMailAddresses.join(QLatin1Char(',')));
431  block.setProperty("technicalProblem", metaData.technicalProblem);
432  block.setProperty("keyId", metaData.keyId);
433  if (metaData.creationTime.isValid()) { // should be handled inside grantlee but currently not possible see: https://bugs.kde.org/363475
434  block.setProperty("creationTime", QLocale().toString(metaData.creationTime, QLocale::ShortFormat));
435  }
436  block.setProperty("isGoodSignature", metaData.isGoodSignature);
437  block.setProperty("isSMIME", isSMIME);
438 
439  if (metaData.keyTrust == GpgME::Signature::Unknown) {
440  block.setProperty("keyTrust", QStringLiteral("unknown"));
441  } else if (metaData.keyTrust == GpgME::Signature::Marginal) {
442  block.setProperty("keyTrust", QStringLiteral("marginal"));
443  } else if (metaData.keyTrust == GpgME::Signature::Full) {
444  block.setProperty("keyTrust", QStringLiteral("full"));
445  } else if (metaData.keyTrust == GpgME::Signature::Ultimate) {
446  block.setProperty("keyTrust", QStringLiteral("ultimate"));
447  } else {
448  block.setProperty("keyTrust", QStringLiteral("untrusted"));
449  }
450 
451  QString startKeyHREF;
452  {
453  QString keyWithWithoutURL;
454  if (cryptoProto) {
455  startKeyHREF = QStringLiteral("<a href=\"kmail:showCertificate#%1 ### %2 ### %3\">")
456  .arg(cryptoProto->displayName(), cryptoProto->name(), QString::fromLatin1(metaData.keyId));
457 
458  keyWithWithoutURL = QStringLiteral("%1%2</a>").arg(startKeyHREF, QString::fromLatin1(QByteArray(QByteArrayLiteral("0x") + metaData.keyId)));
459  } else {
460  keyWithWithoutURL = QLatin1String("0x") + QString::fromUtf8(metaData.keyId);
461  }
462  block.setProperty("keyWithWithoutURL", keyWithWithoutURL);
463  }
464 
465  bool onlyShowKeyURL = false;
466  bool showKeyInfos = false;
467  bool cannotCheckSignature = true;
468  QString signer = metaData.signer;
469  QString statusStr;
470  QString mClass;
471  QString greenCaseWarning;
472 
473  if (metaData.inProgress) {
474  mClass = QStringLiteral("signInProgress");
475  } else {
476  const QStringList &blockAddrs(metaData.signerMailAddresses);
477  // note: At the moment frameColor and showKeyInfos are
478  // used for CMS only but not for PGP signatures
479  // pending(khz): Implement usage of these for PGP sigs as well.
480  int frameColor = SIG_FRAME_COL_UNDEF;
481  statusStr = sigStatusToString(cryptoProto, metaData.status_code, metaData.sigSummary, frameColor, showKeyInfos);
482  // if needed fallback to english status text
483  // that was reported by the plugin
484  if (statusStr.isEmpty()) {
485  statusStr = metaData.status;
486  }
487  if (metaData.technicalProblem) {
488  frameColor = SIG_FRAME_COL_YELLOW;
489  }
490 
491  switch (frameColor) {
492  case SIG_FRAME_COL_RED:
493  cannotCheckSignature = false;
494  break;
495  case SIG_FRAME_COL_YELLOW:
496  cannotCheckSignature = true;
497  break;
498  case SIG_FRAME_COL_GREEN:
499  cannotCheckSignature = false;
500  break;
501  }
502 
503  // temporary hack: always show key information!
504  showKeyInfos = true;
505 
506  if (isSMIME && (SIG_FRAME_COL_UNDEF != frameColor)) {
507  switch (frameColor) {
508  case SIG_FRAME_COL_RED:
509  mClass = QStringLiteral("signErr");
510  onlyShowKeyURL = true;
511  break;
512  case SIG_FRAME_COL_YELLOW:
513  if (metaData.technicalProblem) {
514  mClass = QStringLiteral("signWarn");
515  } else {
516  mClass = QStringLiteral("signOkKeyBad");
517  }
518  break;
519  case SIG_FRAME_COL_GREEN:
520  mClass = QStringLiteral("signOkKeyOk");
521  // extra hint for green case
522  // that email addresses in DN do not match fromAddress
523  QString msgFrom(KEmailAddress::extractEmailAddress(mp->fromAddress()));
524  QString certificate;
525  if (metaData.keyId.isEmpty()) {
526  certificate = i18n("certificate");
527  } else {
528  certificate = startKeyHREF + i18n("certificate") + QStringLiteral("</a>");
529  }
530 
531  if (!blockAddrs.empty()) {
532  if (!blockAddrs.contains(msgFrom, Qt::CaseInsensitive)) {
533  greenCaseWarning = QStringLiteral("<u>") + i18nc("Start of warning message.", "Warning:") + QStringLiteral("</u> ")
534  + i18n("Sender's mail address is not stored in the %1 used for signing.", certificate) + QStringLiteral("<br />") + i18n("sender: ")
535  + msgFrom + QStringLiteral("<br />") + i18n("stored: ");
536  // We cannot use Qt's join() function here but
537  // have to join the addresses manually to
538  // extract the mail addresses (without '<''>')
539  // before including it into our string:
540  bool bStart = true;
541  QStringList::ConstIterator end(blockAddrs.constEnd());
542  for (QStringList::ConstIterator it = blockAddrs.constBegin(); it != end; ++it) {
543  if (!bStart) {
544  greenCaseWarning.append(QLatin1String(", <br />&nbsp; &nbsp;"));
545  }
546 
547  bStart = false;
548  greenCaseWarning.append(KEmailAddress::extractEmailAddress(*it));
549  }
550  }
551  } else {
552  greenCaseWarning = QStringLiteral("<u>") + i18nc("Start of warning message.", "Warning:") + QStringLiteral("</u> ")
553  + i18n("No mail address is stored in the %1 used for signing, "
554  "so we cannot compare it to the sender's address %2.",
555  certificate,
556  msgFrom);
557  }
558  break;
559  }
560 
561  if (showKeyInfos && !cannotCheckSignature) {
562  if (metaData.signer.isEmpty()) {
563  signer.clear();
564  } else {
565  if (!blockAddrs.empty()) {
566  const QUrl address = KEmailAddress::encodeMailtoUrl(blockAddrs.first());
567  signer = QStringLiteral("<a href=\"mailto:%1\">%2</a>").arg(QLatin1String(QUrl ::toPercentEncoding(address.path())), signer);
568  }
569  }
570  }
571  } else {
572  if (metaData.signer.isEmpty() || metaData.technicalProblem) {
573  mClass = QStringLiteral("signWarn");
574  } else {
575  // HTMLize the signer's user id and create mailto: link
576  signer = MessageCore::StringUtil::quoteHtmlChars(signer, true);
577  signer = QStringLiteral("<a href=\"mailto:%1\">%1</a>").arg(signer);
578 
579  if (metaData.isGoodSignature) {
580  if (metaData.keyTrust < GpgME::Signature::Marginal) {
581  mClass = QStringLiteral("signOkKeyBad");
582  } else {
583  mClass = QStringLiteral("signOkKeyOk");
584  }
585  } else {
586  mClass = QStringLiteral("signErr");
587  }
588  }
589  }
590  }
591 
592  block.setProperty("onlyShowKeyURL", onlyShowKeyURL);
593  block.setProperty("showKeyInfos", showKeyInfos);
594  block.setProperty("cannotCheckSignature", cannotCheckSignature);
595  block.setProperty("signer", signer);
596  block.setProperty("statusStr", statusStr);
597  block.setProperty("signClass", mClass);
598  block.setProperty("greenCaseWarning", greenCaseWarning);
599 
600  Grantlee::OutputStream s(htmlWriter->stream());
601  t->render(&s, &c);
602 }
603 
604 void DefaultRendererPrivate::render(const SignedMessagePart::Ptr &mp, HtmlWriter *htmlWriter)
605 {
606  const auto metaData = *mp->partMetaData();
607  if (metaData.isSigned || metaData.inProgress) {
608  HTMLBlock::Ptr aBlock;
609  if (mp->isAttachment()) {
610  aBlock = HTMLBlock::Ptr(new AttachmentMarkBlock(htmlWriter, mp->attachmentContent()));
611  }
612  renderSigned(mp, htmlWriter);
613  return;
614  }
615 
616  HTMLBlock::Ptr aBlock;
617  if (mp->isAttachment()) {
618  aBlock = HTMLBlock::Ptr(new AttachmentMarkBlock(htmlWriter, mp->attachmentContent()));
619  }
620  if (mp->hasSubParts()) {
621  renderSubParts(mp, htmlWriter);
622  } else if (!metaData.inProgress) {
623  renderWithFactory<MimeTreeParser::MessagePart>(mp, htmlWriter);
624  }
625 }
626 
627 void DefaultRendererPrivate::render(const EncryptedMessagePart::Ptr &mp, HtmlWriter *htmlWriter)
628 {
629  const auto metaData = *mp->partMetaData();
630 
631  if (metaData.isEncrypted || metaData.inProgress) {
632  HTMLBlock::Ptr aBlock;
633  if (mp->isAttachment()) {
634  aBlock = HTMLBlock::Ptr(new AttachmentMarkBlock(htmlWriter, mp->attachmentContent()));
635  }
636  renderEncrypted(mp, htmlWriter);
637  return;
638  }
639 
640  HTMLBlock::Ptr aBlock;
641  if (mp->isAttachment()) {
642  aBlock = HTMLBlock::Ptr(new AttachmentMarkBlock(htmlWriter, mp->attachmentContent()));
643  }
644 
645  if (mp->hasSubParts()) {
646  renderSubParts(mp, htmlWriter);
647  } else if (!metaData.inProgress) {
648  renderWithFactory<MimeTreeParser::MessagePart>(mp, htmlWriter);
649  }
650 }
651 
652 void DefaultRendererPrivate::render(const AlternativeMessagePart::Ptr &mp, HtmlWriter *htmlWriter)
653 {
654  HTMLBlock::Ptr aBlock;
655  if (mp->isAttachment()) {
656  aBlock = HTMLBlock::Ptr(new AttachmentMarkBlock(htmlWriter, mp->attachmentContent()));
657  }
658 
659  auto mode = mp->preferredMode();
660  if (mode == MimeTreeParser::Util::MultipartPlain && mp->text().trimmed().isEmpty()) {
661  const auto availableModes = mp->availableModes();
662  for (const auto m : availableModes) {
664  mode = m;
665  break;
666  }
667  }
668  }
669  MimeMessagePart::Ptr part(mp->childParts().first());
670  if (mp->childParts().contains(mode)) {
671  part = mp->childParts()[mode];
672  }
673 
674  render(part, htmlWriter);
675 }
676 
677 void DefaultRendererPrivate::render(const CertMessagePart::Ptr &mp, HtmlWriter *htmlWriter)
678 {
679  const GpgME::ImportResult &importResult(mp->importResult());
680  Grantlee::Template t = MessageViewer::MessagePartRendererManager::self()->loadByName(QStringLiteral("certmessagepart.html"));
681  Grantlee::Context c = MessageViewer::MessagePartRendererManager::self()->createContext();
682  QObject block;
683 
684  c.insert(QStringLiteral("block"), &block);
685  block.setProperty("importError", QString::fromLocal8Bit(importResult.error().asString()));
686  block.setProperty("nImp", importResult.numImported());
687  block.setProperty("nUnc", importResult.numUnchanged());
688  block.setProperty("nSKImp", importResult.numSecretKeysImported());
689  block.setProperty("nSKUnc", importResult.numSecretKeysUnchanged());
690 
691  QVariantList keylist;
692  const auto imports = importResult.imports();
693 
694  auto end(imports.end());
695  for (auto it = imports.begin(); it != end; ++it) {
696  auto key(new QObject(mp.data()));
697  key->setProperty("error", QString::fromLocal8Bit((*it).error().asString()));
698  key->setProperty("status", (*it).status());
699  key->setProperty("fingerprint", QLatin1String((*it).fingerprint()));
700  keylist << QVariant::fromValue(key);
701  }
702 
703  HTMLBlock::Ptr aBlock;
704  if (mp->isAttachment()) {
705  aBlock = HTMLBlock::Ptr(new AttachmentMarkBlock(htmlWriter, mp->attachmentContent()));
706  }
707  Grantlee::OutputStream s(htmlWriter->stream());
708  t->render(&s, &c);
709 }
710 
711 bool DefaultRendererPrivate::renderWithFactory(const QMetaObject *mo, const MessagePart::Ptr &msgPart, HtmlWriter *htmlWriter)
712 {
713  if (!mRendererFactory) {
714  return false;
715  }
716  for (auto r : mRendererFactory->renderersForPart(mo, msgPart)) {
717  if (r->render(msgPart, htmlWriter, this)) {
718  return true;
719  }
720  }
721  return false;
722 }
723 
724 void DefaultRendererPrivate::renderFactory(const MessagePart::Ptr &msgPart, HtmlWriter *htmlWriter)
725 {
726  const QString className = QString::fromUtf8(msgPart->metaObject()->className());
727 
728  if (isHiddenHint(msgPart)) {
729  const QByteArray cid = msgPart->content()->contentID()->identifier();
730  auto mp = msgPart.dynamicCast<MimeTreeParser::TextMessagePart>();
731  if (!cid.isEmpty() && mp) {
732  QString fileName = mp->temporaryFilePath();
733  QString href = QUrl::fromLocalFile(fileName).url();
734  htmlWriter->embedPart(cid, href);
735  }
736  }
737 
738  if (renderWithFactory(msgPart, htmlWriter)) {
739  return;
740  }
741 
742  if (className == QLatin1String("MimeTreeParser::MessagePartList")) {
743  auto mp = msgPart.dynamicCast<MessagePartList>();
744  if (mp) {
745  render(mp, htmlWriter);
746  }
747  } else if (className == QLatin1String("MimeTreeParser::MimeMessagePart")) {
748  auto mp = msgPart.dynamicCast<MimeMessagePart>();
749  if (mp) {
750  render(mp, htmlWriter);
751  }
752  } else if (className == QLatin1String("MimeTreeParser::EncapsulatedRfc822MessagePart")) {
753  auto mp = msgPart.dynamicCast<EncapsulatedRfc822MessagePart>();
754  if (mp) {
755  render(mp, htmlWriter);
756  }
757  } else if (className == QLatin1String("MimeTreeParser::HtmlMessagePart")) {
758  auto mp = msgPart.dynamicCast<HtmlMessagePart>();
759  if (mp) {
760  render(mp, htmlWriter);
761  }
762  } else if (className == QLatin1String("MimeTreeParser::SignedMessagePart")) {
763  auto mp = msgPart.dynamicCast<SignedMessagePart>();
764  if (mp) {
765  render(mp, htmlWriter);
766  }
767  } else if (className == QLatin1String("MimeTreeParser::EncryptedMessagePart")) {
768  auto mp = msgPart.dynamicCast<EncryptedMessagePart>();
769  if (mp) {
770  render(mp, htmlWriter);
771  }
772  } else if (className == QLatin1String("MimeTreeParser::AlternativeMessagePart")) {
773  auto mp = msgPart.dynamicCast<AlternativeMessagePart>();
774  if (mp) {
775  render(mp, htmlWriter);
776  }
777  } else if (className == QLatin1String("MimeTreeParser::CertMessagePart")) {
778  auto mp = msgPart.dynamicCast<CertMessagePart>();
779  if (mp) {
780  render(mp, htmlWriter);
781  }
782  } else {
783  qCWarning(MESSAGEVIEWER_LOG) << "We got a unknown classname, using default behaviour for " << className;
784  }
785 }
786 
787 bool DefaultRendererPrivate::isHiddenHint(const MimeTreeParser::MessagePart::Ptr &msgPart)
788 {
789  auto mp = msgPart.dynamicCast<MimeTreeParser::MessagePart>();
790  auto content = msgPart->content();
791 
792  if (!mp || !content) {
793  return false;
794  }
795 
796  if (mShowOnlyOneMimePart && mMsgPart.data() == msgPart->parentPart()) {
797  if (mMsgPart->subParts().at(0) == msgPart.data()) {
798  return false;
799  }
800  }
801 
802  if (msgPart->nodeHelper()->isNodeDisplayedHidden(content)) {
803  return true;
804  }
805 
806  const AttachmentStrategy *const as = mAttachmentStrategy;
807  const bool defaultHidden(as && as->defaultDisplay(content) == AttachmentStrategy::None);
808  auto preferredMode = source()->preferredMode();
809  bool isHtmlPreferred = (preferredMode == MimeTreeParser::Util::Html) || (preferredMode == MimeTreeParser::Util::MultipartHtml);
810 
811  QByteArray mediaType("text");
812  if (content->contentType(false) && !content->contentType(false)->mediaType().isEmpty() && !content->contentType(false)->subType().isEmpty()) {
813  mediaType = content->contentType(false)->mediaType();
814  }
815  const bool isTextPart = (mediaType == QByteArrayLiteral("text"));
816 
817  bool defaultAsIcon = true;
818  if (!mp->neverDisplayInline()) {
819  if (as) {
820  defaultAsIcon = as->defaultDisplay(content) == AttachmentStrategy::AsIcon;
821  }
822  }
823 
824  // neither image nor text -> show as icon
825  if (!mp->isImage() && !isTextPart) {
826  defaultAsIcon = true;
827  }
828 
829  bool hidden(false);
830  if (isTextPart) {
831  hidden = defaultHidden;
832  } else {
833  if (mp->isImage() && isHtmlPreferred && content->parent() && content->parent()->contentType(false)->subType() == "related") {
834  hidden = true;
835  } else {
836  hidden = defaultHidden && content->parent();
837  hidden |= defaultAsIcon && defaultHidden;
838  }
839  }
840  msgPart->nodeHelper()->setNodeDisplayedHidden(content, hidden);
841  return hidden;
842 }
843 
844 MimeTreeParser::IconType DefaultRendererPrivate::displayHint(const MimeTreeParser::MessagePart::Ptr &msgPart)
845 {
846  auto mp = msgPart.dynamicCast<MimeTreeParser::TextMessagePart>();
847  auto content = msgPart->content();
848 
849  if (!content || !mp) {
850  return MimeTreeParser::IconType::NoIcon;
851  }
852 
853  const AttachmentStrategy *const as = mAttachmentStrategy;
854  const bool defaultDisplayHidden(as && as->defaultDisplay(content) == AttachmentStrategy::None);
855  const bool defaultDisplayInline(as && as->defaultDisplay(content) == AttachmentStrategy::Inline);
856  const bool defaultDisplayAsIcon(as && as->defaultDisplay(content) == AttachmentStrategy::AsIcon);
857  const bool showOnlyOneMimePart(mShowOnlyOneMimePart);
858  auto preferredMode = source()->preferredMode();
859  bool isHtmlPreferred = (preferredMode == MimeTreeParser::Util::Html) || (preferredMode == MimeTreeParser::Util::MultipartHtml);
860 
861  QByteArray mediaType("text");
862  if (content->contentType(false) && !content->contentType(false)->mediaType().isEmpty() && !content->contentType(false)->subType().isEmpty()) {
863  mediaType = content->contentType(false)->mediaType();
864  }
865  const bool isTextPart = (mediaType == QByteArrayLiteral("text"));
866 
867  bool defaultAsIcon = true;
868  if (!mp->neverDisplayInline()) {
869  if (as) {
870  defaultAsIcon = defaultDisplayAsIcon;
871  }
872  }
873  if (mp->isImage() && showOnlyOneMimePart && !mp->neverDisplayInline()) {
874  defaultAsIcon = false;
875  }
876 
877  // neither image nor text -> show as icon
878  if (!mp->isImage() && !isTextPart) {
879  defaultAsIcon = true;
880  }
881 
882  if (isTextPart) {
883  if (as && !defaultDisplayInline) {
884  return MimeTreeParser::IconExternal;
885  }
886  return MimeTreeParser::NoIcon;
887  } else {
888  if (mp->isImage() && isHtmlPreferred && content->parent() && content->parent()->contentType(false)->subType() == "related") {
889  return MimeTreeParser::IconInline;
890  }
891 
892  if (defaultDisplayHidden && !showOnlyOneMimePart && content->parent()) {
893  return MimeTreeParser::IconInline;
894  }
895 
896  if (defaultAsIcon) {
897  return MimeTreeParser::IconExternal;
898  } else if (mp->isImage()) {
899  return MimeTreeParser::IconInline;
900  }
901  }
902 
903  return MimeTreeParser::NoIcon;
904 }
905 
906 bool DefaultRendererPrivate::showEmoticons() const
907 {
908  return mShowEmoticons;
909 }
910 
911 bool DefaultRendererPrivate::isPrinting() const
912 {
913  return mIsPrinting;
914 }
915 
916 bool DefaultRendererPrivate::htmlLoadExternal() const
917 {
918  return mHtmlLoadExternal;
919 }
920 
921 bool DefaultRendererPrivate::showExpandQuotesMark() const
922 {
923  return mShowExpandQuotesMark;
924 }
925 
926 bool DefaultRendererPrivate::showOnlyOneMimePart() const
927 {
928  return mShowOnlyOneMimePart;
929 }
930 
931 bool DefaultRendererPrivate::showSignatureDetails() const
932 {
933  return mShowSignatureDetails;
934 }
935 
936 bool DefaultRendererPrivate::showEncryptionDetails() const
937 {
938  return mShowEncryptionDetails;
939 }
940 
941 int DefaultRendererPrivate::levelQuote() const
942 {
943  return mLevelQuote;
944 }
945 
946 DefaultRenderer::DefaultRenderer(CSSHelperBase *cssHelper)
947  : d(new DefaultRendererPrivate(cssHelper, MessagePartRendererFactory::instance()))
948 {
949 }
950 
951 DefaultRenderer::~DefaultRenderer() = default;
952 
953 void DefaultRenderer::setShowOnlyOneMimePart(bool onlyOneMimePart)
954 {
955  d->mShowOnlyOneMimePart = onlyOneMimePart;
956 }
957 
958 void DefaultRenderer::setAttachmentStrategy(const AttachmentStrategy *strategy)
959 {
960  d->mAttachmentStrategy = strategy;
961 }
962 
963 void DefaultRenderer::setShowEmoticons(bool showEmoticons)
964 {
965  d->mShowEmoticons = showEmoticons;
966 }
967 
968 void DefaultRenderer::setIsPrinting(bool isPrinting)
969 {
970  d->mIsPrinting = isPrinting;
971 }
972 
973 void DefaultRenderer::setShowExpandQuotesMark(bool showExpandQuotesMark)
974 {
975  d->mShowExpandQuotesMark = showExpandQuotesMark;
976 }
977 
978 void DefaultRenderer::setShowEncryptionDetails(bool showEncryptionDetails)
979 {
980  d->mShowEncryptionDetails = showEncryptionDetails;
981 }
982 
983 void DefaultRenderer::setShowSignatureDetails(bool showSignatureDetails)
984 {
985  d->mShowSignatureDetails = showSignatureDetails;
986 }
987 
988 void DefaultRenderer::setLevelQuote(int levelQuote)
989 {
990  d->mLevelQuote = levelQuote;
991 }
992 
993 void DefaultRenderer::setHtmlLoadExternal(bool htmlLoadExternal)
994 {
995  d->mHtmlLoadExternal = htmlLoadExternal;
996 }
997 
998 void DefaultRenderer::setCreateMessageHeader(const std::function<QString(KMime::Message *)> &createMessageHeader)
999 {
1000  d->mCreateMessageHeader = createMessageHeader;
1001 }
1002 
1003 QString renderTreeHelper(const MimeTreeParser::MessagePart::Ptr &messagePart, QString indent)
1004 {
1005  QString ret = QStringLiteral("%1 * %3\n").arg(indent, QString::fromUtf8(messagePart->metaObject()->className()));
1006  indent += QLatin1Char(' ');
1007  for (const auto &subPart : messagePart->subParts()) {
1008  ret += renderTreeHelper(subPart, indent);
1009  }
1010  return ret;
1011 }
1012 
1013 void DefaultRenderer::render(const MimeTreeParser::MessagePart::Ptr &msgPart, HtmlWriter *writer)
1014 {
1015  qCDebug(MESSAGEVIEWER_LOG) << "MimeTreeParser structure:";
1016  qCDebug(MESSAGEVIEWER_LOG) << qPrintable(renderTreeHelper(msgPart, QString()));
1017  d->mMsgPart = msgPart;
1018  d->renderFactory(d->mMsgPart, writer);
1019 }
QString url(QUrl::FormattingOptions options) const const
QString & append(QChar ch)
KCODECS_EXPORT QUrl encodeMailtoUrl(const QString &mailbox)
T convert(const QVariant &value)
A HTML message, non-multipart.
bool isEmpty() const const
A multipart/alternative message, the HTML part is currently displayed.
T * data() const const
The AttachmentMarkBlock class.
Definition: htmlblock.h:52
QString quoteHtmlChars(const QString &str, bool removeLineBreaks)
Quotes the following characters which have a special meaning in HTML: &#39;<&#39; &#39;>&#39; &#39;&&#39; &#39;"&#39;...
Definition: stringutil.cpp:317
void clear()
The MessagePartRendererFactory class.
QString fromLocal8Bit(const char *str, int size)
QString fromUtf8(const char *str, int size)
PostalAddress address(const QVariant &location)
QString & insert(int position, QChar ch)
QString i18nc(const char *context, const char *text, const TYPE &arg...)
The CSSHelperBase class.
Definition: csshelperbase.h:26
CaseInsensitive
bool isEmpty() const const
QString path(QUrl::ComponentFormattingOptions options) const const
KCODECS_EXPORT QByteArray extractEmailAddress(const QByteArray &address)
QVariant fromValue(const T &value)
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().
Definition: htmlwriter.cpp:53
QString i18n(const char *text, const TYPE &arg...)
QString & replace(int position, int n, QChar after)
const QList< QKeySequence > & end()
QObject(QObject *parent)
The AttachmentStrategy class.
QSharedPointer< X > dynamicCast() const const
char * toString(const T &value)
Interface for object tree sources.
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
typedef ConstIterator
QByteArray toPercentEncoding(const QString &input, const QByteArray &exclude, const QByteArray &include)
QString fromLatin1(const char *str, int size)
bool setProperty(const char *name, const QVariant &value)
An interface for HTML sinks.
Definition: htmlwriter.h:28
QObject * parent() const const
A normal plaintext message, non-multipart.
A multipart/alternative message, the plain text part is currently displayed.
QUrl fromLocalFile(const QString &localFile)
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Sun Dec 5 2021 23:04:53 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.