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 /> ")); 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 }