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

KDE's Doxygen guidelines are available online.