File indexing completed on 2025-03-09 04:54:35

0001 /*
0002    SPDX-FileCopyrightText: 2016 Sandro Knauß <sknauss@kde.org>
0003 
0004    SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "defaultrenderer.h"
0008 
0009 #include "defaultrenderer_p.h"
0010 
0011 #include "utils/messageviewerutil.h"
0012 
0013 #include "messageviewer_debug.h"
0014 
0015 #include "converthtmltoplaintext.h"
0016 #include "htmlblock.h"
0017 #include "messagepartrendererbase.h"
0018 #include "messagepartrendererfactory.h"
0019 #include "messagepartrenderermanager.h"
0020 #include "utils/iconnamecache.h"
0021 #include "viewer/attachmentstrategy.h"
0022 #include "viewer/csshelperbase.h"
0023 
0024 #include "htmlwriter/bufferedhtmlwriter.h"
0025 #include <MimeTreeParser/MessagePart>
0026 #include <MimeTreeParser/ObjectTreeParser>
0027 
0028 #include <QGpgME/Protocol>
0029 
0030 #include <MessageCore/StringUtil>
0031 
0032 #include <KEmailAddress>
0033 #include <KIconLoader>
0034 #include <KLocalizedString>
0035 
0036 #include <KTextTemplate/Context>
0037 #include <KTextTemplate/Engine>
0038 #include <KTextTemplate/MetaType>
0039 #include <KTextTemplate/Template>
0040 #include <KTextTemplate/TemplateLoader>
0041 #include <QUrl>
0042 
0043 using namespace MimeTreeParser;
0044 using namespace MessageViewer;
0045 #ifndef COMPILE_WITH_UNITY_CMAKE_SUPPORT
0046 Q_DECLARE_METATYPE(GpgME::DecryptionResult::Recipient)
0047 Q_DECLARE_METATYPE(GpgME::Key)
0048 Q_DECLARE_METATYPE(const QGpgME::Protocol *)
0049 #endif
0050 static const int SIG_FRAME_COL_UNDEF = 99;
0051 #define SIG_FRAME_COL_RED -1
0052 #define SIG_FRAME_COL_YELLOW 0
0053 #define SIG_FRAME_COL_GREEN 1
0054 QString sigStatusToString(const QGpgME::Protocol *cryptProto, int status_code, GpgME::Signature::Summary summary, int &frameColor, bool &showKeyInfos)
0055 {
0056     // note: At the moment frameColor and showKeyInfos are
0057     //       used for CMS only but not for PGP signatures
0058     // pending(khz): Implement usage of these for PGP sigs as well.
0059     showKeyInfos = true;
0060     QString result;
0061     if (cryptProto) {
0062         if (cryptProto == QGpgME::openpgp()) {
0063             // process enum according to it's definition to be read in
0064             // GNU Privacy Guard CVS repository /gpgme/gpgme/gpgme.h
0065             switch (status_code) {
0066             case 0: // GPGME_SIG_STAT_NONE
0067                 result = i18n("Error: Signature not verified");
0068                 frameColor = SIG_FRAME_COL_YELLOW;
0069                 break;
0070             case 1: // GPGME_SIG_STAT_GOOD
0071                 result = i18n("Good signature");
0072                 frameColor = SIG_FRAME_COL_GREEN;
0073                 break;
0074             case 2: // GPGME_SIG_STAT_BAD
0075                 result = i18n("Bad signature");
0076                 frameColor = SIG_FRAME_COL_RED;
0077                 break;
0078             case 3: // GPGME_SIG_STAT_NOKEY
0079                 result = i18n("No public key to verify the signature");
0080                 frameColor = SIG_FRAME_COL_RED;
0081                 break;
0082             case 4: // GPGME_SIG_STAT_NOSIG
0083                 result = i18n("No signature found");
0084                 frameColor = SIG_FRAME_COL_RED;
0085                 break;
0086             case 5: // GPGME_SIG_STAT_ERROR
0087                 result = i18n("Error verifying the signature");
0088                 frameColor = SIG_FRAME_COL_RED;
0089                 break;
0090             case 6: // GPGME_SIG_STAT_DIFF
0091                 result = i18n("Different results for signatures");
0092                 frameColor = SIG_FRAME_COL_RED;
0093                 break;
0094             /* PENDING(khz) Verify exact meaning of the following values:
0095             case 7: // GPGME_SIG_STAT_GOOD_EXP
0096             return i18n("Signature certificate is expired");
0097             break;
0098             case 8: // GPGME_SIG_STAT_GOOD_EXPKEY
0099             return i18n("One of the certificate's keys is expired");
0100             break;
0101             */
0102             default:
0103                 result.clear(); // do *not* return a default text here !
0104                 break;
0105             }
0106         } else if (cryptProto == QGpgME::smime()) {
0107             // process status bits according to SigStatus_...
0108             // definitions in kdenetwork/libkdenetwork/cryptplug.h
0109 
0110             if (summary == GpgME::Signature::None) {
0111                 result = i18n("No status information available.");
0112                 frameColor = SIG_FRAME_COL_YELLOW;
0113                 showKeyInfos = false;
0114                 return result;
0115             }
0116 
0117             if (summary & GpgME::Signature::Valid) {
0118                 result = i18n("Good signature.");
0119                 // Note:
0120                 // Here we are work differently than KMail did before!
0121                 //
0122                 // The GOOD case ( == sig matching and the complete
0123                 // certificate chain was verified and is valid today )
0124                 // by definition does *not* show any key
0125                 // information but just states that things are OK.
0126                 //           (khz, according to LinuxTag 2002 meeting)
0127                 frameColor = SIG_FRAME_COL_GREEN;
0128                 showKeyInfos = false;
0129                 return result;
0130             }
0131 
0132             // we are still there?  OK, let's test the different cases:
0133 
0134             // we assume green, test for yellow or red (in this order!)
0135             frameColor = SIG_FRAME_COL_GREEN;
0136             QString result2;
0137             if (summary & GpgME::Signature::KeyExpired) {
0138                 // still is green!
0139                 result2 = i18n("One key has expired.");
0140             }
0141             if (summary & GpgME::Signature::SigExpired) {
0142                 // and still is green!
0143                 result2 += i18n("The signature has expired.");
0144             }
0145 
0146             // test for yellow:
0147             if (summary & GpgME::Signature::KeyMissing) {
0148                 result2 += i18n("Unable to verify: key missing.");
0149                 // if the signature certificate is missing
0150                 // we cannot show information on it
0151                 showKeyInfos = false;
0152                 frameColor = SIG_FRAME_COL_YELLOW;
0153             }
0154             if (summary & GpgME::Signature::CrlMissing) {
0155                 result2 += i18n("CRL not available.");
0156                 frameColor = SIG_FRAME_COL_YELLOW;
0157             }
0158             if (summary & GpgME::Signature::CrlTooOld) {
0159                 result2 += i18n("Available CRL is too old.");
0160                 frameColor = SIG_FRAME_COL_YELLOW;
0161             }
0162             if (summary & GpgME::Signature::BadPolicy) {
0163                 result2 += i18n("A policy was not met.");
0164                 frameColor = SIG_FRAME_COL_YELLOW;
0165             }
0166             if (summary & GpgME::Signature::SysError) {
0167                 result2 += i18n("A system error occurred.");
0168                 // if a system error occurred
0169                 // we cannot trust any information
0170                 // that was given back by the plug-in
0171                 showKeyInfos = false;
0172                 frameColor = SIG_FRAME_COL_YELLOW;
0173             }
0174 
0175             // test for red:
0176             if (summary & GpgME::Signature::KeyRevoked) {
0177                 // this is red!
0178                 result2 += i18n("One key has been revoked.");
0179                 frameColor = SIG_FRAME_COL_RED;
0180             }
0181             if (summary & GpgME::Signature::Red) {
0182                 if (result2.isEmpty()) {
0183                     // Note:
0184                     // Here we are work differently than KMail did before!
0185                     //
0186                     // The BAD case ( == sig *not* matching )
0187                     // by definition does *not* show any key
0188                     // information but just states that things are BAD.
0189                     //
0190                     // The reason for this: In this case ALL information
0191                     // might be falsificated, we can NOT trust the data
0192                     // in the body NOT the signature - so we don't show
0193                     // any key/signature information at all!
0194                     //         (khz, according to LinuxTag 2002 meeting)
0195                     showKeyInfos = false;
0196                 }
0197                 frameColor = SIG_FRAME_COL_RED;
0198             } else {
0199                 result.clear();
0200             }
0201 
0202             if (SIG_FRAME_COL_GREEN == frameColor) {
0203                 result = i18n("Good signature.");
0204             } else if (SIG_FRAME_COL_RED == frameColor) {
0205                 result = i18n("Bad signature.");
0206             } else {
0207                 result.clear();
0208             }
0209 
0210             if (!result2.isEmpty()) {
0211                 if (!result.isEmpty()) {
0212                     result.append(QLatin1StringView("<br />"));
0213                 }
0214                 result.append(result2);
0215             }
0216         }
0217         /*
0218         // add i18n support for 3rd party plug-ins here:
0219         else if ( cryptPlug->libName().contains( "yetanotherpluginname", Qt::CaseInsensitive )) {
0220 
0221         }
0222         */
0223     }
0224     return result;
0225 }
0226 
0227 DefaultRendererPrivate::DefaultRendererPrivate(CSSHelperBase *cssHelper, const MessagePartRendererFactory *rendererFactory)
0228     : mCSSHelper(cssHelper)
0229     , mRendererFactory(rendererFactory)
0230 {
0231 }
0232 
0233 DefaultRendererPrivate::~DefaultRendererPrivate() = default;
0234 
0235 CSSHelperBase *DefaultRendererPrivate::cssHelper() const
0236 {
0237     return mCSSHelper;
0238 }
0239 
0240 Interface::ObjectTreeSource *DefaultRendererPrivate::source() const
0241 {
0242     return mMsgPart->source();
0243 }
0244 
0245 void DefaultRendererPrivate::renderSubParts(const MessagePart::Ptr &msgPart, HtmlWriter *htmlWriter)
0246 {
0247     for (const auto &m : msgPart->subParts()) {
0248         renderFactory(m, htmlWriter);
0249     }
0250 }
0251 
0252 void DefaultRendererPrivate::render(const MessagePartList::Ptr &mp, HtmlWriter *htmlWriter)
0253 {
0254     HTMLBlock::Ptr rBlock;
0255     HTMLBlock::Ptr aBlock;
0256 
0257     if (mp->isRoot()) {
0258         rBlock = HTMLBlock::Ptr(new RootBlock(htmlWriter));
0259     }
0260 
0261     if (mp->isAttachment()) {
0262         aBlock = HTMLBlock::Ptr(new AttachmentMarkBlock(htmlWriter, mp->attachmentContent()));
0263     }
0264 
0265     renderSubParts(mp, htmlWriter);
0266 }
0267 
0268 void DefaultRendererPrivate::render(const MimeMessagePart::Ptr &mp, HtmlWriter *htmlWriter)
0269 {
0270     HTMLBlock::Ptr aBlock;
0271     HTMLBlock::Ptr rBlock;
0272     if (mp->isAttachment()) {
0273         aBlock = HTMLBlock::Ptr(new AttachmentMarkBlock(htmlWriter, mp->attachmentContent()));
0274     }
0275     if (mp->isRoot()) {
0276         rBlock = HTMLBlock::Ptr(new RootBlock(htmlWriter));
0277     }
0278 
0279     renderSubParts(mp, htmlWriter);
0280 }
0281 
0282 void DefaultRendererPrivate::render(const EncapsulatedRfc822MessagePart::Ptr &mp, HtmlWriter *htmlWriter)
0283 {
0284     if (!mp->hasSubParts()) {
0285         return;
0286     }
0287     KTextTemplate::Template t = MessagePartRendererManager::self()->loadByName(QStringLiteral("encapsulatedrfc822messagepart.html"));
0288     KTextTemplate::Context c = MessagePartRendererManager::self()->createContext();
0289     QObject block;
0290 
0291     c.insert(QStringLiteral("block"), &block);
0292     block.setProperty("link", mp->nodeHelper()->asHREF(mp->message().data(), QStringLiteral("body")));
0293 
0294     c.insert(QStringLiteral("msgHeader"), mCreateMessageHeader(mp->message().data()));
0295     c.insert(QStringLiteral("content"), QVariant::fromValue<KTextTemplateCallback>([this, mp, htmlWriter](KTextTemplate::OutputStream *) {
0296                  renderSubParts(mp, htmlWriter);
0297              }));
0298     HTMLBlock::Ptr aBlock;
0299     if (mp->isAttachment()) {
0300         aBlock = HTMLBlock::Ptr(new AttachmentMarkBlock(htmlWriter, mp->attachmentContent()));
0301     }
0302     KTextTemplate::OutputStream s(htmlWriter->stream());
0303     t->render(&s, &c);
0304 }
0305 
0306 void DefaultRendererPrivate::render(const HtmlMessagePart::Ptr &mp, HtmlWriter *htmlWriter)
0307 {
0308     KTextTemplate::Template t = MessageViewer::MessagePartRendererManager::self()->loadByName(QStringLiteral("htmlmessagepart.html"));
0309     KTextTemplate::Context c = MessageViewer::MessagePartRendererManager::self()->createContext();
0310     QObject block;
0311 
0312     c.insert(QStringLiteral("block"), &block);
0313 
0314     auto preferredMode = mp->source()->preferredMode();
0315     const bool isHtmlPreferred = (preferredMode == MimeTreeParser::Util::Html) || (preferredMode == MimeTreeParser::Util::MultipartHtml);
0316     block.setProperty("htmlMail", isHtmlPreferred);
0317     block.setProperty("loadExternal", htmlLoadExternal());
0318     block.setProperty("isPrinting", isPrinting());
0319     {
0320         // laurent: FIXME port to async method webengine
0321         const Util::HtmlMessageInfo messageInfo = Util::processHtml(mp->bodyHtml());
0322 
0323         if (isHtmlPreferred) {
0324             mp->nodeHelper()->setNodeDisplayedEmbedded(mp->content(), true);
0325             htmlWriter->setExtraHead(messageInfo.extraHead);
0326             htmlWriter->setStyleBody(Util::parseBodyStyle(messageInfo.bodyStyle));
0327         }
0328 
0329         block.setProperty("containsExternalReferences", Util::containsExternalReferences(messageInfo.htmlSource, messageInfo.extraHead));
0330         c.insert(QStringLiteral("content"), messageInfo.htmlSource);
0331     }
0332 
0333     {
0334         ConvertHtmlToPlainText convert;
0335         convert.setHtmlString(mp->bodyHtml());
0336         QString plaintext = convert.generatePlainText();
0337         plaintext.replace(QLatin1Char('\n'), QStringLiteral("<br>"));
0338         c.insert(QStringLiteral("plaintext"), plaintext);
0339     }
0340     mp->source()->setHtmlMode(MimeTreeParser::Util::Html,
0341                               QList<MimeTreeParser::Util::HtmlMode>() << MimeTreeParser::Util::Html << MimeTreeParser::Util::Normal);
0342 
0343     HTMLBlock::Ptr aBlock;
0344     if (mp->isAttachment()) {
0345         aBlock = HTMLBlock::Ptr(new AttachmentMarkBlock(htmlWriter, mp->attachmentContent()));
0346     }
0347     KTextTemplate::OutputStream s(htmlWriter->stream());
0348     t->render(&s, &c);
0349 }
0350 
0351 void DefaultRendererPrivate::renderEncrypted(const EncryptedMessagePart::Ptr &mp, HtmlWriter *htmlWriter)
0352 {
0353     KMime::Content *node = mp->content();
0354     const auto metaData = *mp->partMetaData();
0355     KTextTemplate::Template t = MessageViewer::MessagePartRendererManager::self()->loadByName(QStringLiteral("encryptedmessagepart.html"));
0356     KTextTemplate::Context c = MessageViewer::MessagePartRendererManager::self()->createContext();
0357     QObject block;
0358     if (node || mp->hasSubParts()) {
0359         c.insert(QStringLiteral("content"), QVariant::fromValue<KTextTemplateCallback>([this, mp, htmlWriter](KTextTemplate::OutputStream *) {
0360                      HTMLBlock::Ptr rBlock;
0361                      if (mp->content() && mp->isRoot()) {
0362                          rBlock = HTMLBlock::Ptr(new RootBlock(htmlWriter));
0363                      }
0364                      renderSubParts(mp, htmlWriter);
0365                  }));
0366     } else if (!metaData.inProgress) {
0367         c.insert(QStringLiteral("content"), QVariant::fromValue<KTextTemplateCallback>([this, mp, htmlWriter](KTextTemplate::OutputStream *) {
0368                      renderWithFactory<MimeTreeParser::MessagePart>(mp, htmlWriter);
0369                  }));
0370     }
0371     c.insert(QStringLiteral("cryptoProto"), QVariant::fromValue(mp->cryptoProto()));
0372     if (!mp->decryptRecipients().empty()) {
0373         c.insert(QStringLiteral("decryptedRecipients"), QVariant::fromValue(mp->decryptRecipients()));
0374     }
0375     c.insert(QStringLiteral("block"), &block);
0376 
0377     block.setProperty("isPrinting", isPrinting());
0378     block.setProperty("detailHeader", showEncryptionDetails());
0379     block.setProperty("inProgress", metaData.inProgress);
0380     block.setProperty("isDecrypted", mp->decryptMessage());
0381     block.setProperty("isDecryptable", metaData.isDecryptable);
0382     block.setProperty("decryptIcon", QUrl::fromLocalFile(IconNameCache::instance()->iconPath(QStringLiteral("document-decrypt"), KIconLoader::Small)).url());
0383     block.setProperty("errorText", metaData.errorText);
0384     block.setProperty("noSecKey", mp->isNoSecKey());
0385     block.setProperty("isCompliant", metaData.isCompliant);
0386     block.setProperty("compliance", metaData.compliance);
0387     KTextTemplate::OutputStream s(htmlWriter->stream());
0388     t->render(&s, &c);
0389 }
0390 
0391 void DefaultRendererPrivate::renderSigned(const SignedMessagePart::Ptr &mp, HtmlWriter *htmlWriter)
0392 {
0393     KMime::Content *node = mp->content();
0394     const auto metaData = *mp->partMetaData();
0395     auto cryptoProto = mp->cryptoProto();
0396 
0397     const bool isSMIME = cryptoProto && (cryptoProto == QGpgME::smime());
0398     KTextTemplate::Template t = MessageViewer::MessagePartRendererManager::self()->loadByName(QStringLiteral("signedmessagepart.html"));
0399     KTextTemplate::Context c = MessageViewer::MessagePartRendererManager::self()->createContext();
0400     QObject block;
0401 
0402     if (node) {
0403         c.insert(QStringLiteral("content"), QVariant::fromValue<KTextTemplateCallback>([this, mp, htmlWriter](KTextTemplate::OutputStream *) {
0404                      HTMLBlock::Ptr rBlock;
0405                      if (mp->isRoot()) {
0406                          rBlock = HTMLBlock::Ptr(new RootBlock(htmlWriter));
0407                      }
0408                      renderSubParts(mp, htmlWriter);
0409                  }));
0410     } else if (!metaData.inProgress) {
0411         c.insert(QStringLiteral("content"), QVariant::fromValue<KTextTemplateCallback>([this, mp, htmlWriter](KTextTemplate::OutputStream *) {
0412                      renderWithFactory<MimeTreeParser::MessagePart>(mp, htmlWriter);
0413                  }));
0414     }
0415     c.insert(QStringLiteral("cryptoProto"), QVariant::fromValue(cryptoProto));
0416     c.insert(QStringLiteral("block"), &block);
0417 
0418     block.setProperty("inProgress", metaData.inProgress);
0419     block.setProperty("errorText", metaData.errorText);
0420 
0421     block.setProperty("detailHeader", showSignatureDetails());
0422     block.setProperty("isPrinting", isPrinting());
0423     QStringList endodedEmails;
0424     for (const auto &addr : metaData.signerMailAddresses) {
0425         endodedEmails.append(MessageCore::StringUtil::quoteHtmlChars(addr, true));
0426     }
0427     block.setProperty("addr", endodedEmails.join(QLatin1Char(',')));
0428     block.setProperty("technicalProblem", metaData.technicalProblem);
0429     block.setProperty("keyId", metaData.keyId);
0430     if (metaData.creationTime.isValid()) { // should be handled inside grantlee but currently not possible see: https://bugs.kde.org/363475
0431         block.setProperty("creationTime", QLocale().toString(metaData.creationTime, QLocale::ShortFormat));
0432     }
0433     block.setProperty("isGoodSignature", metaData.isGoodSignature);
0434     block.setProperty("isCompliant", metaData.isCompliant);
0435     block.setProperty("compliance", metaData.compliance);
0436     block.setProperty("isSMIME", isSMIME);
0437 
0438     if (metaData.keyTrust == GpgME::Signature::Unknown) {
0439         block.setProperty("keyTrust", QStringLiteral("unknown"));
0440     } else if (metaData.keyTrust == GpgME::Signature::Marginal) {
0441         block.setProperty("keyTrust", QStringLiteral("marginal"));
0442     } else if (metaData.keyTrust == GpgME::Signature::Full) {
0443         block.setProperty("keyTrust", QStringLiteral("full"));
0444     } else if (metaData.keyTrust == GpgME::Signature::Ultimate) {
0445         block.setProperty("keyTrust", QStringLiteral("ultimate"));
0446     } else {
0447         block.setProperty("keyTrust", QStringLiteral("untrusted"));
0448     }
0449 
0450     QString startKeyHREF;
0451     {
0452         QString keyWithWithoutURL;
0453         if (cryptoProto) {
0454             startKeyHREF = QStringLiteral("<a href=\"kmail:showCertificate#%1 ### %2 ### %3\">")
0455                                .arg(cryptoProto->displayName(), cryptoProto->name(), QString::fromLatin1(metaData.keyId));
0456 
0457             keyWithWithoutURL = QStringLiteral("%1%2</a>").arg(startKeyHREF, QString::fromLatin1(QByteArray(QByteArrayLiteral("0x") + metaData.keyId)));
0458         } else {
0459             keyWithWithoutURL = QLatin1StringView("0x") + QString::fromUtf8(metaData.keyId);
0460         }
0461         block.setProperty("keyWithWithoutURL", keyWithWithoutURL);
0462     }
0463 
0464     bool onlyShowKeyURL = false;
0465     bool showKeyInfos = false;
0466     bool cannotCheckSignature = true;
0467     QString signer = metaData.signer;
0468     QString statusStr;
0469     QString mClass;
0470     QString greenCaseWarning;
0471 
0472     if (metaData.inProgress) {
0473         mClass = QStringLiteral("signInProgress");
0474     } else {
0475         const QStringList &blockAddrs(metaData.signerMailAddresses);
0476         // note: At the moment frameColor and showKeyInfos are
0477         //       used for CMS only but not for PGP signatures
0478         // pending(khz): Implement usage of these for PGP sigs as well.
0479         int frameColor = SIG_FRAME_COL_UNDEF;
0480         statusStr = sigStatusToString(cryptoProto, metaData.status_code, metaData.sigSummary, frameColor, showKeyInfos);
0481         // if needed fallback to english status text
0482         // that was reported by the plugin
0483         if (statusStr.isEmpty()) {
0484             statusStr = metaData.status;
0485         }
0486         if (metaData.technicalProblem) {
0487             frameColor = SIG_FRAME_COL_YELLOW;
0488         }
0489 
0490         switch (frameColor) {
0491         case SIG_FRAME_COL_RED:
0492             cannotCheckSignature = false;
0493             break;
0494         case SIG_FRAME_COL_YELLOW:
0495             cannotCheckSignature = true;
0496             break;
0497         case SIG_FRAME_COL_GREEN:
0498             cannotCheckSignature = false;
0499             break;
0500         }
0501 
0502         // temporary hack: always show key information!
0503         showKeyInfos = true;
0504 
0505         if (isSMIME && (SIG_FRAME_COL_UNDEF != frameColor)) {
0506             switch (frameColor) {
0507             case SIG_FRAME_COL_RED:
0508                 mClass = QStringLiteral("signErr");
0509                 onlyShowKeyURL = true;
0510                 break;
0511             case SIG_FRAME_COL_YELLOW:
0512                 if (metaData.technicalProblem) {
0513                     mClass = QStringLiteral("signWarn");
0514                 } else {
0515                     mClass = QStringLiteral("signOkKeyBad");
0516                 }
0517                 break;
0518             case SIG_FRAME_COL_GREEN:
0519                 mClass = QStringLiteral("signOkKeyOk");
0520                 // extra hint for green case
0521                 // that email addresses in DN do not match fromAddress
0522                 QString msgFrom(KEmailAddress::extractEmailAddress(mp->fromAddress()));
0523                 QString certificate;
0524                 if (metaData.keyId.isEmpty()) {
0525                     certificate = i18n("certificate");
0526                 } else {
0527                     certificate = startKeyHREF + i18n("certificate") + QStringLiteral("</a>");
0528                 }
0529 
0530                 if (!blockAddrs.empty()) {
0531                     QStringList::ConstIterator end(blockAddrs.constEnd());
0532                     QStringList extractedEmails;
0533                     for (QStringList::ConstIterator it = blockAddrs.constBegin(); it != end; ++it) {
0534                         extractedEmails.append(KEmailAddress::extractEmailAddress(*it));
0535                     }
0536                     if (!extractedEmails.contains(msgFrom, Qt::CaseInsensitive)) {
0537                         greenCaseWarning = QStringLiteral("<u>") + i18nc("Start of warning message.", "Warning:") + QStringLiteral("</u> ")
0538                             + i18n("Sender's mail address is not stored in the %1 used for signing.", certificate) + QStringLiteral("<br />") + i18n("sender: ")
0539                             + msgFrom + QStringLiteral("<br />") + i18n("stored: ");
0540                         // We cannot use Qt's join() function here but
0541                         // have to join the addresses manually to
0542                         // extract the mail addresses (without '<''>')
0543                         // before including it into our string:
0544                         bool bStart = true;
0545                         QStringList::ConstIterator end(extractedEmails.constEnd());
0546                         for (QStringList::ConstIterator it = extractedEmails.constBegin(); it != end; ++it) {
0547                             if (!bStart) {
0548                                 greenCaseWarning.append(QLatin1StringView(", <br />&nbsp; &nbsp;"));
0549                             }
0550 
0551                             bStart = false;
0552                             greenCaseWarning.append(*it);
0553                         }
0554                     }
0555                 } else {
0556                     greenCaseWarning = QStringLiteral("<u>") + i18nc("Start of warning message.", "Warning:") + QStringLiteral("</u> ")
0557                         + i18n("No mail address is stored in the %1 used for signing, "
0558                                "so we cannot compare it to the sender's address %2.",
0559                                certificate,
0560                                msgFrom);
0561                 }
0562                 break;
0563             }
0564 
0565             if (showKeyInfos && !cannotCheckSignature) {
0566                 if (metaData.signer.isEmpty()) {
0567                     signer.clear();
0568                 } else {
0569                     if (!blockAddrs.empty()) {
0570                         const QUrl address = KEmailAddress::encodeMailtoUrl(blockAddrs.first());
0571                         signer = QStringLiteral("<a href=\"mailto:%1\">%2</a>")
0572                                      .arg(QLatin1StringView(QUrl ::toPercentEncoding(address.path())), MessageCore::StringUtil::quoteHtmlChars(signer, true));
0573                     }
0574                 }
0575             }
0576         } else {
0577             if (metaData.signer.isEmpty() || metaData.technicalProblem || !metaData.isCompliant) {
0578                 mClass = QStringLiteral("signWarn");
0579             } else {
0580                 // HTMLize the signer's user id and create mailto: link
0581                 signer = MessageCore::StringUtil::quoteHtmlChars(signer, true);
0582                 signer = QStringLiteral("<a href=\"mailto:%1\">%1</a>").arg(signer);
0583 
0584                 if (metaData.isGoodSignature) {
0585                     if (metaData.keyTrust < GpgME::Signature::Marginal) {
0586                         mClass = QStringLiteral("signOkKeyBad");
0587                     } else {
0588                         mClass = QStringLiteral("signOkKeyOk");
0589                     }
0590                 } else {
0591                     mClass = QStringLiteral("signErr");
0592                 }
0593             }
0594         }
0595     }
0596 
0597     block.setProperty("onlyShowKeyURL", onlyShowKeyURL);
0598     block.setProperty("showKeyInfos", showKeyInfos);
0599     block.setProperty("cannotCheckSignature", cannotCheckSignature);
0600     block.setProperty("signer", signer);
0601     block.setProperty("statusStr", statusStr);
0602     block.setProperty("signClass", mClass);
0603     block.setProperty("greenCaseWarning", greenCaseWarning);
0604     KTextTemplate::OutputStream s(htmlWriter->stream());
0605     t->render(&s, &c);
0606 }
0607 
0608 void DefaultRendererPrivate::render(const SignedMessagePart::Ptr &mp, HtmlWriter *htmlWriter)
0609 {
0610     const auto metaData = *mp->partMetaData();
0611     if (metaData.isSigned || metaData.inProgress) {
0612         HTMLBlock::Ptr aBlock;
0613         if (mp->isAttachment()) {
0614             aBlock = HTMLBlock::Ptr(new AttachmentMarkBlock(htmlWriter, mp->attachmentContent()));
0615         }
0616         renderSigned(mp, htmlWriter);
0617         return;
0618     }
0619 
0620     HTMLBlock::Ptr aBlock;
0621     if (mp->isAttachment()) {
0622         aBlock = HTMLBlock::Ptr(new AttachmentMarkBlock(htmlWriter, mp->attachmentContent()));
0623     }
0624     if (mp->hasSubParts()) {
0625         renderSubParts(mp, htmlWriter);
0626     } else if (!metaData.inProgress) {
0627         renderWithFactory<MimeTreeParser::MessagePart>(mp, htmlWriter);
0628     }
0629 }
0630 
0631 void DefaultRendererPrivate::render(const EncryptedMessagePart::Ptr &mp, HtmlWriter *htmlWriter)
0632 {
0633     const auto metaData = *mp->partMetaData();
0634 
0635     if (metaData.isEncrypted || metaData.inProgress) {
0636         HTMLBlock::Ptr aBlock;
0637         if (mp->isAttachment()) {
0638             aBlock = HTMLBlock::Ptr(new AttachmentMarkBlock(htmlWriter, mp->attachmentContent()));
0639         }
0640         renderEncrypted(mp, htmlWriter);
0641         return;
0642     }
0643 
0644     HTMLBlock::Ptr aBlock;
0645     if (mp->isAttachment()) {
0646         aBlock = HTMLBlock::Ptr(new AttachmentMarkBlock(htmlWriter, mp->attachmentContent()));
0647     }
0648 
0649     if (mp->hasSubParts()) {
0650         renderSubParts(mp, htmlWriter);
0651     } else if (!metaData.inProgress) {
0652         renderWithFactory<MimeTreeParser::MessagePart>(mp, htmlWriter);
0653     }
0654 }
0655 
0656 void DefaultRendererPrivate::render(const AlternativeMessagePart::Ptr &mp, HtmlWriter *htmlWriter)
0657 {
0658     HTMLBlock::Ptr aBlock;
0659     if (mp->isAttachment()) {
0660         aBlock = HTMLBlock::Ptr(new AttachmentMarkBlock(htmlWriter, mp->attachmentContent()));
0661     }
0662 
0663     auto mode = mp->preferredMode();
0664     if (mode == MimeTreeParser::Util::MultipartPlain && mp->text().trimmed().isEmpty()) {
0665         const auto availableModes = mp->availableModes();
0666         for (const auto m : availableModes) {
0667             if (m != MimeTreeParser::Util::MultipartPlain) {
0668                 mode = m;
0669                 break;
0670             }
0671         }
0672     }
0673     MimeMessagePart::Ptr part(mp->childParts().first());
0674     if (mp->childParts().contains(mode)) {
0675         part = mp->childParts()[mode];
0676     }
0677 
0678     render(part, htmlWriter);
0679 }
0680 
0681 void DefaultRendererPrivate::render(const CertMessagePart::Ptr &mp, HtmlWriter *htmlWriter)
0682 {
0683     const GpgME::ImportResult &importResult(mp->importResult());
0684     KTextTemplate::Template t = MessageViewer::MessagePartRendererManager::self()->loadByName(QStringLiteral("certmessagepart.html"));
0685     KTextTemplate::Context c = MessageViewer::MessagePartRendererManager::self()->createContext();
0686     QObject block;
0687 
0688     c.insert(QStringLiteral("block"), &block);
0689     block.setProperty("importError", QString::fromLocal8Bit(importResult.error().asString()));
0690     block.setProperty("nImp", importResult.numImported());
0691     block.setProperty("nUnc", importResult.numUnchanged());
0692     block.setProperty("nSKImp", importResult.numSecretKeysImported());
0693     block.setProperty("nSKUnc", importResult.numSecretKeysUnchanged());
0694 
0695     QVariantList keylist;
0696     const auto imports = importResult.imports();
0697 
0698     auto end(imports.end());
0699     for (auto it = imports.begin(); it != end; ++it) {
0700         auto key(new QObject(mp.data()));
0701         key->setProperty("error", QString::fromLocal8Bit((*it).error().asString()));
0702         key->setProperty("status", (*it).status());
0703         key->setProperty("fingerprint", QLatin1StringView((*it).fingerprint()));
0704         keylist << QVariant::fromValue(key);
0705     }
0706 
0707     HTMLBlock::Ptr aBlock;
0708     if (mp->isAttachment()) {
0709         aBlock = HTMLBlock::Ptr(new AttachmentMarkBlock(htmlWriter, mp->attachmentContent()));
0710     }
0711     KTextTemplate::OutputStream s(htmlWriter->stream());
0712     t->render(&s, &c);
0713 }
0714 
0715 bool DefaultRendererPrivate::renderWithFactory(const QMetaObject *mo, const MessagePart::Ptr &msgPart, HtmlWriter *htmlWriter)
0716 {
0717     if (!mRendererFactory) {
0718         return false;
0719     }
0720     for (auto r : mRendererFactory->renderersForPart(mo, msgPart)) {
0721         if (r->render(msgPart, htmlWriter, this)) {
0722             return true;
0723         }
0724     }
0725     return false;
0726 }
0727 
0728 void DefaultRendererPrivate::renderFactory(const MessagePart::Ptr &msgPart, HtmlWriter *htmlWriter)
0729 {
0730     const QString className = QString::fromUtf8(msgPart->metaObject()->className());
0731 
0732     if (isHiddenHint(msgPart)) {
0733         const QByteArray cid = msgPart->content()->contentID()->identifier();
0734         auto mp = msgPart.dynamicCast<MimeTreeParser::TextMessagePart>();
0735         if (!cid.isEmpty() && mp) {
0736             const QString fileName = mp->temporaryFilePath();
0737             const QString href = QUrl::fromLocalFile(fileName).url();
0738             htmlWriter->embedPart(cid, href);
0739         }
0740     }
0741 
0742     if (renderWithFactory(msgPart, htmlWriter)) {
0743         return;
0744     }
0745 
0746     if (className == QLatin1StringView("MimeTreeParser::MessagePartList")) {
0747         auto mp = msgPart.dynamicCast<MessagePartList>();
0748         if (mp) {
0749             render(mp, htmlWriter);
0750         }
0751     } else if (className == QLatin1StringView("MimeTreeParser::MimeMessagePart")) {
0752         auto mp = msgPart.dynamicCast<MimeMessagePart>();
0753         if (mp) {
0754             render(mp, htmlWriter);
0755         }
0756     } else if (className == QLatin1StringView("MimeTreeParser::EncapsulatedRfc822MessagePart")) {
0757         auto mp = msgPart.dynamicCast<EncapsulatedRfc822MessagePart>();
0758         if (mp) {
0759             render(mp, htmlWriter);
0760         }
0761     } else if (className == QLatin1StringView("MimeTreeParser::HtmlMessagePart")) {
0762         auto mp = msgPart.dynamicCast<HtmlMessagePart>();
0763         if (mp) {
0764             render(mp, htmlWriter);
0765         }
0766     } else if (className == QLatin1StringView("MimeTreeParser::SignedMessagePart")) {
0767         auto mp = msgPart.dynamicCast<SignedMessagePart>();
0768         if (mp) {
0769             render(mp, htmlWriter);
0770         }
0771     } else if (className == QLatin1StringView("MimeTreeParser::EncryptedMessagePart")) {
0772         auto mp = msgPart.dynamicCast<EncryptedMessagePart>();
0773         if (mp) {
0774             render(mp, htmlWriter);
0775         }
0776     } else if (className == QLatin1StringView("MimeTreeParser::AlternativeMessagePart")) {
0777         auto mp = msgPart.dynamicCast<AlternativeMessagePart>();
0778         if (mp) {
0779             render(mp, htmlWriter);
0780         }
0781     } else if (className == QLatin1StringView("MimeTreeParser::CertMessagePart")) {
0782         auto mp = msgPart.dynamicCast<CertMessagePart>();
0783         if (mp) {
0784             render(mp, htmlWriter);
0785         }
0786     } else {
0787         qCWarning(MESSAGEVIEWER_LOG) << "We got a unknown classname, using default behaviour for " << className;
0788     }
0789 }
0790 
0791 bool DefaultRendererPrivate::isHiddenHint(const MimeTreeParser::MessagePart::Ptr &msgPart)
0792 {
0793     auto mp = msgPart.dynamicCast<MimeTreeParser::MessagePart>();
0794     auto content = msgPart->content();
0795 
0796     if (!mp || !content) {
0797         return false;
0798     }
0799 
0800     if (mShowOnlyOneMimePart && mMsgPart.data() == msgPart->parentPart()) {
0801         if (mMsgPart->subParts().at(0) == msgPart.data()) {
0802             return false;
0803         }
0804     }
0805 
0806     if (msgPart->nodeHelper()->isNodeDisplayedHidden(content)) {
0807         return true;
0808     }
0809 
0810     const AttachmentStrategy *const as = mAttachmentStrategy;
0811     const bool defaultHidden(as && as->defaultDisplay(content) == AttachmentStrategy::None);
0812     auto preferredMode = source()->preferredMode();
0813     bool isHtmlPreferred = (preferredMode == MimeTreeParser::Util::Html) || (preferredMode == MimeTreeParser::Util::MultipartHtml);
0814 
0815     QByteArray mediaType("text");
0816     if (content->contentType(false) && !content->contentType(false)->mediaType().isEmpty() && !content->contentType(false)->subType().isEmpty()) {
0817         mediaType = content->contentType(false)->mediaType();
0818     }
0819     const bool isTextPart = (mediaType == QByteArrayLiteral("text"));
0820 
0821     bool defaultAsIcon = true;
0822     if (!mp->neverDisplayInline()) {
0823         if (as) {
0824             defaultAsIcon = as->defaultDisplay(content) == AttachmentStrategy::AsIcon;
0825         }
0826     }
0827 
0828     // neither image nor text -> show as icon
0829     if (!mp->isImage() && !isTextPart) {
0830         defaultAsIcon = true;
0831     }
0832 
0833     bool hidden(false);
0834     if (isTextPart) {
0835         hidden = defaultHidden;
0836     } else {
0837         if (mp->isImage() && isHtmlPreferred && content->parent() && content->parent()->contentType(false)->subType() == "related") {
0838             hidden = true;
0839         } else {
0840             hidden = defaultHidden && content->parent();
0841             hidden |= defaultAsIcon && defaultHidden;
0842         }
0843     }
0844     msgPart->nodeHelper()->setNodeDisplayedHidden(content, hidden);
0845     return hidden;
0846 }
0847 
0848 MimeTreeParser::IconType DefaultRendererPrivate::displayHint(const MimeTreeParser::MessagePart::Ptr &msgPart)
0849 {
0850     auto mp = msgPart.dynamicCast<MimeTreeParser::TextMessagePart>();
0851     auto content = msgPart->content();
0852 
0853     if (!content || !mp) {
0854         return MimeTreeParser::IconType::NoIcon;
0855     }
0856 
0857     const AttachmentStrategy *const as = mAttachmentStrategy;
0858     const bool defaultDisplayHidden(as && as->defaultDisplay(content) == AttachmentStrategy::None);
0859     const bool defaultDisplayInline(as && as->defaultDisplay(content) == AttachmentStrategy::Inline);
0860     const bool defaultDisplayAsIcon(as && as->defaultDisplay(content) == AttachmentStrategy::AsIcon);
0861     const bool showOnlyOneMimePart(mShowOnlyOneMimePart);
0862     auto preferredMode = source()->preferredMode();
0863     bool isHtmlPreferred = (preferredMode == MimeTreeParser::Util::Html) || (preferredMode == MimeTreeParser::Util::MultipartHtml);
0864 
0865     QByteArray mediaType("text");
0866     if (content->contentType(false) && !content->contentType(false)->mediaType().isEmpty() && !content->contentType(false)->subType().isEmpty()) {
0867         mediaType = content->contentType(false)->mediaType();
0868     }
0869     const bool isTextPart = (mediaType == QByteArrayLiteral("text"));
0870 
0871     bool defaultAsIcon = true;
0872     if (!mp->neverDisplayInline()) {
0873         if (as) {
0874             defaultAsIcon = defaultDisplayAsIcon;
0875         }
0876     }
0877     if (mp->isImage() && showOnlyOneMimePart && !mp->neverDisplayInline()) {
0878         defaultAsIcon = false;
0879     }
0880 
0881     // neither image nor text -> show as icon
0882     if (!mp->isImage() && !isTextPart) {
0883         defaultAsIcon = true;
0884     }
0885 
0886     if (isTextPart) {
0887         if (as && !defaultDisplayInline) {
0888             return MimeTreeParser::IconExternal;
0889         }
0890         return MimeTreeParser::NoIcon;
0891     } else {
0892         if (mp->isImage() && isHtmlPreferred && content->parent() && content->parent()->contentType(false)->subType() == "related") {
0893             return MimeTreeParser::IconInline;
0894         }
0895 
0896         if (defaultDisplayHidden && !showOnlyOneMimePart && content->parent()) {
0897             return MimeTreeParser::IconInline;
0898         }
0899 
0900         if (defaultAsIcon) {
0901             return MimeTreeParser::IconExternal;
0902         } else if (mp->isImage()) {
0903             return MimeTreeParser::IconInline;
0904         }
0905     }
0906 
0907     return MimeTreeParser::NoIcon;
0908 }
0909 
0910 bool DefaultRendererPrivate::showEmoticons() const
0911 {
0912     return mShowEmoticons;
0913 }
0914 
0915 bool DefaultRendererPrivate::isPrinting() const
0916 {
0917     return mIsPrinting;
0918 }
0919 
0920 bool DefaultRendererPrivate::htmlLoadExternal() const
0921 {
0922     return mHtmlLoadExternal;
0923 }
0924 
0925 bool DefaultRendererPrivate::showExpandQuotesMark() const
0926 {
0927     return mShowExpandQuotesMark;
0928 }
0929 
0930 bool DefaultRendererPrivate::showOnlyOneMimePart() const
0931 {
0932     return mShowOnlyOneMimePart;
0933 }
0934 
0935 bool DefaultRendererPrivate::showSignatureDetails() const
0936 {
0937     return mShowSignatureDetails;
0938 }
0939 
0940 bool DefaultRendererPrivate::showEncryptionDetails() const
0941 {
0942     return mShowEncryptionDetails;
0943 }
0944 
0945 int DefaultRendererPrivate::levelQuote() const
0946 {
0947     return mLevelQuote;
0948 }
0949 
0950 DefaultRenderer::DefaultRenderer(CSSHelperBase *cssHelper)
0951     : d(new DefaultRendererPrivate(cssHelper, MessagePartRendererFactory::instance()))
0952 {
0953 }
0954 
0955 DefaultRenderer::~DefaultRenderer() = default;
0956 
0957 void DefaultRenderer::setShowOnlyOneMimePart(bool onlyOneMimePart)
0958 {
0959     d->mShowOnlyOneMimePart = onlyOneMimePart;
0960 }
0961 
0962 void DefaultRenderer::setAttachmentStrategy(const AttachmentStrategy *strategy)
0963 {
0964     d->mAttachmentStrategy = strategy;
0965 }
0966 
0967 void DefaultRenderer::setShowEmoticons(bool showEmoticons)
0968 {
0969     d->mShowEmoticons = showEmoticons;
0970 }
0971 
0972 void DefaultRenderer::setIsPrinting(bool isPrinting)
0973 {
0974     d->mIsPrinting = isPrinting;
0975 }
0976 
0977 void DefaultRenderer::setShowExpandQuotesMark(bool showExpandQuotesMark)
0978 {
0979     d->mShowExpandQuotesMark = showExpandQuotesMark;
0980 }
0981 
0982 void DefaultRenderer::setShowEncryptionDetails(bool showEncryptionDetails)
0983 {
0984     d->mShowEncryptionDetails = showEncryptionDetails;
0985 }
0986 
0987 void DefaultRenderer::setShowSignatureDetails(bool showSignatureDetails)
0988 {
0989     d->mShowSignatureDetails = showSignatureDetails;
0990 }
0991 
0992 void DefaultRenderer::setLevelQuote(int levelQuote)
0993 {
0994     d->mLevelQuote = levelQuote;
0995 }
0996 
0997 void DefaultRenderer::setHtmlLoadExternal(bool htmlLoadExternal)
0998 {
0999     d->mHtmlLoadExternal = htmlLoadExternal;
1000 }
1001 
1002 void DefaultRenderer::setCreateMessageHeader(const std::function<QString(KMime::Message *)> &createMessageHeader)
1003 {
1004     d->mCreateMessageHeader = createMessageHeader;
1005 }
1006 
1007 QString renderTreeHelper(const MimeTreeParser::MessagePart::Ptr &messagePart, QString indent)
1008 {
1009     QString ret = QStringLiteral("%1 * %3\n").arg(indent, QString::fromUtf8(messagePart->metaObject()->className()));
1010     indent += QLatin1Char(' ');
1011     for (const auto &subPart : messagePart->subParts()) {
1012         ret += renderTreeHelper(subPart, indent);
1013     }
1014     return ret;
1015 }
1016 
1017 void DefaultRenderer::render(const MimeTreeParser::MessagePart::Ptr &msgPart, HtmlWriter *writer)
1018 {
1019     qCDebug(MESSAGEVIEWER_LOG) << "MimeTreeParser structure:";
1020     qCDebug(MESSAGEVIEWER_LOG) << qPrintable(renderTreeHelper(msgPart, QString()));
1021     d->mMsgPart = msgPart;
1022     d->renderFactory(d->mMsgPart, writer);
1023 }