diff --git a/messagecore/src/messagecoreutil.cpp b/messagecore/src/messagecoreutil.cpp index 11f044a3..61287c90 100644 --- a/messagecore/src/messagecoreutil.cpp +++ b/messagecore/src/messagecoreutil.cpp @@ -1,138 +1,143 @@ /* * Copyright (C) 2015 Daniel Vrátil * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #include "messagecoreutil.h" #include #include using namespace MessageCore; static bool isLightTheme() { return qApp->palette().color(QPalette::Background).value() >= 128; } Q_GLOBAL_STATIC(ColorUtil, s_self) ColorUtil *ColorUtil::self() { return s_self; } ColorUtil::ColorUtil() { initializeColors(); } void ColorUtil::updateColors() { initializeColors(); } void ColorUtil::initializeColors() { KColorScheme scheme(QPalette::Active, KColorScheme::View); mMisspelledDefaultTextColor = scheme.foreground(KColorScheme::NegativeText).color().lighter(); auto base = scheme.foreground(KColorScheme::PositiveText).color(); if (isLightTheme()) { mQuoteLevel1DefaultTextColor = base.darker(120); mQuoteLevel2DefaultTextColor = base.darker(150); mQuoteLevel3DefaultTextColor = base.darker(200); } else { mQuoteLevel1DefaultTextColor = base.lighter(200); mQuoteLevel2DefaultTextColor = base.lighter(170); mQuoteLevel3DefaultTextColor = base.lighter(140); } - mPgpEncryptedMessageColor = QColor(0x00, 0x80, 0xFF); - mPgpEncryptedTextColor = QColor(0xFF, 0xFF, 0xFF); // white + if (isLightTheme()) { + mPgpEncryptedMessageColor = QColor(0x00, 0x80, 0xFF).lighter(180); + mPgpEncryptedTextColor = QColor(0x00, 0x80, 0xFF).darker(200); + } else { + mPgpEncryptedMessageColor = QColor(0x00, 0x80, 0xFF).darker(300); + mPgpEncryptedTextColor = QColor(0x00, 0x80, 0xFF).lighter(170); + } mPgpSignedTrustedMessageColor = scheme.background(KColorScheme::PositiveBackground).color(); mPgpSignedTrustedTextColor = scheme.foreground(KColorScheme::PositiveText).color(); mPgpSignedUntrustedMessageColor = scheme.background(KColorScheme::NeutralBackground).color(); mPgpSignedUntrustedTextColor = scheme.foreground(KColorScheme::NeutralText).color(); mPgpSignedBadMessageColor = scheme.background(KColorScheme::NegativeBackground).color(); mPgpSignedBadTextColor = scheme.foreground(KColorScheme::NegativeText).color(); mLinkColor = scheme.foreground(KColorScheme::LinkText).color(); } QColor ColorUtil::misspelledDefaultTextColor() const { return mMisspelledDefaultTextColor; } QColor ColorUtil::quoteLevel1DefaultTextColor() const { return mQuoteLevel1DefaultTextColor; } QColor ColorUtil::quoteLevel2DefaultTextColor() const { return mQuoteLevel2DefaultTextColor; } QColor ColorUtil::quoteLevel3DefaultTextColor() const { return mQuoteLevel3DefaultTextColor; } QColor ColorUtil::pgpSignedTrustedMessageColor() const { return mPgpSignedTrustedMessageColor; } QColor ColorUtil::pgpSignedTrustedTextColor() const { return mPgpSignedTrustedTextColor; } QColor ColorUtil::pgpSignedUntrustedMessageColor() const { return mPgpSignedUntrustedMessageColor; } QColor ColorUtil::pgpSignedUntrustedTextColor() const { return mPgpSignedUntrustedTextColor; } QColor ColorUtil::pgpSignedBadMessageColor() const { return mPgpSignedBadMessageColor; } QColor ColorUtil::pgpSignedBadTextColor() const { return mPgpSignedBadTextColor; } QColor ColorUtil::pgpEncryptedMessageColor() const { return mPgpEncryptedMessageColor; } QColor ColorUtil::pgpEncryptedTextColor() const { return mPgpEncryptedTextColor; } QColor ColorUtil::linkColor() const { return mLinkColor; } diff --git a/messageviewer/src/messagepartthemes/default/defaultrenderer.cpp b/messageviewer/src/messagepartthemes/default/defaultrenderer.cpp index 31b0f1a9..83638b86 100644 --- a/messageviewer/src/messagepartthemes/default/defaultrenderer.cpp +++ b/messageviewer/src/messagepartthemes/default/defaultrenderer.cpp @@ -1,911 +1,912 @@ /* Copyright (c) 2016 Sandro Knauß This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "defaultrenderer.h" #include "defaultrenderer_p.h" #include "messageviewer_debug.h" #include "converthtmltoplaintext.h" #include "messagepartrendererbase.h" #include "messagepartrendererfactory.h" #include "htmlblock.h" #include "utils/iconnamecache.h" #include "utils/mimetype.h" #include "viewer/csshelperbase.h" #include "messagepartrenderermanager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace MimeTreeParser; using namespace MessageViewer; Q_DECLARE_METATYPE(GpgME::DecryptionResult::Recipient) +Q_DECLARE_METATYPE(GpgME::Key) Q_DECLARE_METATYPE(const QGpgME::Protocol *) static const int SIG_FRAME_COL_UNDEF = 99; #define SIG_FRAME_COL_RED -1 #define SIG_FRAME_COL_YELLOW 0 #define SIG_FRAME_COL_GREEN 1 QString sigStatusToString(const QGpgME::Protocol *cryptProto, int status_code, GpgME::Signature::Summary summary, int &frameColor, bool &showKeyInfos) { // note: At the moment frameColor and showKeyInfos are // used for CMS only but not for PGP signatures // pending(khz): Implement usage of these for PGP sigs as well. showKeyInfos = true; QString result; if (cryptProto) { if (cryptProto == QGpgME::openpgp()) { // process enum according to it's definition to be read in // GNU Privacy Guard CVS repository /gpgme/gpgme/gpgme.h switch (status_code) { case 0: // GPGME_SIG_STAT_NONE result = i18n("Error: Signature not verified"); break; case 1: // GPGME_SIG_STAT_GOOD result = i18n("Good signature"); break; case 2: // GPGME_SIG_STAT_BAD result = i18n("Bad signature"); break; case 3: // GPGME_SIG_STAT_NOKEY result = i18n("No public key to verify the signature"); break; case 4: // GPGME_SIG_STAT_NOSIG result = i18n("No signature found"); break; case 5: // GPGME_SIG_STAT_ERROR result = i18n("Error verifying the signature"); break; case 6: // GPGME_SIG_STAT_DIFF result = i18n("Different results for signatures"); break; /* PENDING(khz) Verify exact meaning of the following values: case 7: // GPGME_SIG_STAT_GOOD_EXP return i18n("Signature certificate is expired"); break; case 8: // GPGME_SIG_STAT_GOOD_EXPKEY return i18n("One of the certificate's keys is expired"); break; */ default: result.clear(); // do *not* return a default text here ! break; } } else if (cryptProto == QGpgME::smime()) { // process status bits according to SigStatus_... // definitions in kdenetwork/libkdenetwork/cryptplug.h if (summary == GpgME::Signature::None) { result = i18n("No status information available."); frameColor = SIG_FRAME_COL_YELLOW; showKeyInfos = false; return result; } if (summary & GpgME::Signature::Valid) { result = i18n("Good signature."); // Note: // Here we are work differently than KMail did before! // // The GOOD case ( == sig matching and the complete // certificate chain was verified and is valid today ) // by definition does *not* show any key // information but just states that things are OK. // (khz, according to LinuxTag 2002 meeting) frameColor = SIG_FRAME_COL_GREEN; showKeyInfos = false; return result; } // we are still there? OK, let's test the different cases: // we assume green, test for yellow or red (in this order!) frameColor = SIG_FRAME_COL_GREEN; QString result2; if (summary & GpgME::Signature::KeyExpired) { // still is green! result2 = i18n("One key has expired."); } if (summary & GpgME::Signature::SigExpired) { // and still is green! result2 += i18n("The signature has expired."); } // test for yellow: if (summary & GpgME::Signature::KeyMissing) { result2 += i18n("Unable to verify: key missing."); // if the signature certificate is missing // we cannot show information on it showKeyInfos = false; frameColor = SIG_FRAME_COL_YELLOW; } if (summary & GpgME::Signature::CrlMissing) { result2 += i18n("CRL not available."); frameColor = SIG_FRAME_COL_YELLOW; } if (summary & GpgME::Signature::CrlTooOld) { result2 += i18n("Available CRL is too old."); frameColor = SIG_FRAME_COL_YELLOW; } if (summary & GpgME::Signature::BadPolicy) { result2 += i18n("A policy was not met."); frameColor = SIG_FRAME_COL_YELLOW; } if (summary & GpgME::Signature::SysError) { result2 += i18n("A system error occurred."); // if a system error occurred // we cannot trust any information // that was given back by the plug-in showKeyInfos = false; frameColor = SIG_FRAME_COL_YELLOW; } // test for red: if (summary & GpgME::Signature::KeyRevoked) { // this is red! result2 += i18n("One key has been revoked."); frameColor = SIG_FRAME_COL_RED; } if (summary & GpgME::Signature::Red) { if (result2.isEmpty()) { // Note: // Here we are work differently than KMail did before! // // The BAD case ( == sig *not* matching ) // by definition does *not* show any key // information but just states that things are BAD. // // The reason for this: In this case ALL information // might be falsificated, we can NOT trust the data // in the body NOT the signature - so we don't show // any key/signature information at all! // (khz, according to LinuxTag 2002 meeting) showKeyInfos = false; } frameColor = SIG_FRAME_COL_RED; } else { result.clear(); } if (SIG_FRAME_COL_GREEN == frameColor) { result = i18n("Good signature."); } else if (SIG_FRAME_COL_RED == frameColor) { result = i18n("Bad signature."); } else { result.clear(); } if (!result2.isEmpty()) { if (!result.isEmpty()) { result.append(QLatin1String("
")); } result.append(result2); } } /* // add i18n support for 3rd party plug-ins here: else if ( cryptPlug->libName().contains( "yetanotherpluginname", Qt::CaseInsensitive )) { } */ } return result; } /** Checks whether @p str contains external references. To be precise, we only check whether @p str contains 'xxx="http[s]:' where xxx is not href. Obfuscated external references are ignored on purpose. */ bool containsExternalReferences(const QString &str, const QString &extraHead) { const bool hasBaseInHeader = extraHead.contains(QStringLiteral( "= 0 || httpsPos >= 0) { // pos = index of next occurrence of "http: or "https: whichever comes first int pos = (httpPos < httpsPos) ? ((httpPos >= 0) ? httpPos : httpsPos) : ((httpsPos >= 0) ? httpsPos : httpPos); // look backwards for "href" if (pos > 5) { int hrefPos = str.lastIndexOf(QLatin1String("href"), pos - 5, Qt::CaseInsensitive); // if no 'href' is found or the distance between 'href' and '"http[s]:' // is larger than 7 (7 is the distance in 'href = "http[s]:') then // we assume that we have found an external reference if ((hrefPos == -1) || (pos - hrefPos > 7)) { // HTML messages created by KMail itself for now contain the following: // // Make sure not to show an external references warning for this string int dtdPos = str.indexOf(QLatin1String( "http://www.w3.org/TR/html4/loose.dtd"), pos + 1); if (dtdPos != (pos + 1)) { return true; } } } // find next occurrence of "http: or "https: if (pos == httpPos) { httpPos = str.indexOf(QLatin1String("\"http:"), httpPos + 6, Qt::CaseInsensitive); } else { httpsPos = str.indexOf(QLatin1String("\"https:"), httpsPos + 7, Qt::CaseInsensitive); } } return false; } // FIXME this used to go through the full webkit parser to extract the body and head blocks // until we have that back, at least attempt to fix some of the damage // yes, "parsing" HTML with regexps is very very wrong, but it's still better than not filtering // this at all... QString processHtml(const QString &htmlSource, QString &extraHead) { auto s = htmlSource.trimmed(); s = s.replace(QRegExp(QStringLiteral("^]*>"), Qt::CaseInsensitive), QString()).trimmed(); s = s.replace(QRegExp(QStringLiteral("^]*>"), Qt::CaseInsensitive), QString()).trimmed(); // head s = s.replace(QRegExp(QStringLiteral("^"), Qt::CaseInsensitive), QString()).trimmed(); if (s.startsWith(QLatin1String("", Qt::CaseInsensitive))) { const auto idx = s.indexOf(QLatin1String(""), Qt::CaseInsensitive); if (idx < 0) { return htmlSource; } extraHead = s.mid(6, idx - 6); s = s.mid(idx + 7).trimmed(); } // body s = s.replace(QRegExp(QStringLiteral("]*>"), Qt::CaseInsensitive), QString()).trimmed(); s = s.replace(QRegExp(QStringLiteral("$"), Qt::CaseInsensitive), QString()).trimmed(); s = s.replace(QRegExp(QStringLiteral("$"), Qt::CaseInsensitive), QString()).trimmed(); return s; } DefaultRendererPrivate::DefaultRendererPrivate(const MessagePart::Ptr &msgPart, CSSHelperBase *cssHelper, HtmlWriter *writer, const MessagePartRendererFactory *rendererFactory) : mMsgPart(msgPart) , mCSSHelper(cssHelper) , mRendererFactory(rendererFactory) { renderFactory(mMsgPart, writer); } DefaultRendererPrivate::~DefaultRendererPrivate() { } CSSHelperBase *DefaultRendererPrivate::cssHelper() const { return mCSSHelper; } Interface::ObjectTreeSource *DefaultRendererPrivate::source() const { return mMsgPart->source(); } void DefaultRendererPrivate::renderSubParts(const MessagePart::Ptr &msgPart, HtmlWriter *htmlWriter) { foreach (const auto &m, msgPart->subParts()) { renderFactory(m, htmlWriter); } } void DefaultRendererPrivate::render(const MessagePartList::Ptr &mp, HtmlWriter *htmlWriter) { HTMLBlock::Ptr rBlock; HTMLBlock::Ptr aBlock; if (mp->isRoot()) { rBlock = HTMLBlock::Ptr(new RootBlock(htmlWriter)); } if (mp->isAttachment()) { aBlock = HTMLBlock::Ptr(new AttachmentMarkBlock(htmlWriter, mp->attachmentContent())); } renderSubParts(mp, htmlWriter); } void DefaultRendererPrivate::render(const MimeMessagePart::Ptr &mp, HtmlWriter *htmlWriter) { HTMLBlock::Ptr aBlock; HTMLBlock::Ptr rBlock; if (mp->isAttachment()) { aBlock = HTMLBlock::Ptr(new AttachmentMarkBlock(htmlWriter, mp->attachmentContent())); } if (mp->isRoot()) { rBlock = HTMLBlock::Ptr(new RootBlock(htmlWriter)); } renderSubParts(mp, htmlWriter); } void DefaultRendererPrivate::render(const EncapsulatedRfc822MessagePart::Ptr &mp, HtmlWriter *htmlWriter) { if (!mp->hasSubParts()) { return; } Grantlee::Template t = MessagePartRendererManager::self()->loadByName(QStringLiteral(":/encapsulatedrfc822messagepart.html")); Grantlee::Context c = MessagePartRendererManager::self()->createContext(); QObject block; c.insert(QStringLiteral("block"), &block); block.setProperty("link", mp->nodeHelper()->asHREF(mp->mMessage.data(), QStringLiteral("body"))); c.insert(QStringLiteral("msgHeader"), mp->source()->createMessageHeader(mp->mMessage.data())); c.insert(QStringLiteral("content"), QVariant::fromValue([this, mp, htmlWriter](Grantlee::OutputStream *) { renderSubParts(mp, htmlWriter); })); HTMLBlock::Ptr aBlock; if (mp->isAttachment()) { aBlock = HTMLBlock::Ptr(new AttachmentMarkBlock(htmlWriter, mp->attachmentContent())); } Grantlee::OutputStream s(htmlWriter->stream()); t->render(&s, &c); } void DefaultRendererPrivate::render(const HtmlMessagePart::Ptr &mp, HtmlWriter *htmlWriter) { Grantlee::Template t = MessageViewer::MessagePartRendererManager::self()->loadByName(QStringLiteral( ":/htmlmessagepart.html")); Grantlee::Context c = MessageViewer::MessagePartRendererManager::self()->createContext(); QObject block; c.insert(QStringLiteral("block"), &block); auto preferredMode = mp->source()->preferredMode(); bool isHtmlPreferred = (preferredMode == Util::Html) || (preferredMode == Util::MultipartHtml); const bool isPrinting = mp->source()->isPrinting(); block.setProperty("htmlMail", isHtmlPreferred); block.setProperty("loadExternal", mp->source()->htmlLoadExternal()); block.setProperty("isPrinting", isPrinting); { QString extraHead; //laurent: FIXME port to async method webengine QString bodyText = processHtml(mp->mBodyHTML, extraHead); if (isHtmlPreferred) { mp->nodeHelper()->setNodeDisplayedEmbedded(mp->content(), true); htmlWriter->extraHead(extraHead); } block.setProperty("containsExternalReferences", containsExternalReferences(bodyText, extraHead)); c.insert(QStringLiteral("content"), bodyText); } { ConvertHtmlToPlainText convert; convert.setHtmlString(mp->mBodyHTML); QString plaintext = convert.generatePlainText(); plaintext.replace(QLatin1Char('\n'), QStringLiteral("
")); c.insert(QStringLiteral("plaintext"), plaintext); } mp->source()->setHtmlMode(Util::Html, QList() << Util::Html); HTMLBlock::Ptr aBlock; if (mp->isAttachment()) { aBlock = HTMLBlock::Ptr(new AttachmentMarkBlock(htmlWriter, mp->attachmentContent())); } Grantlee::OutputStream s(htmlWriter->stream()); t->render(&s, &c); } void DefaultRendererPrivate::renderEncrypted(const EncryptedMessagePart::Ptr &mp, HtmlWriter *htmlWriter) { KMime::Content *node = mp->content(); const auto metaData = *mp->partMetaData(); Grantlee::Template t = MessageViewer::MessagePartRendererManager::self()->loadByName(QStringLiteral( ":/encryptedmessagepart.html")); Grantlee::Context c = MessageViewer::MessagePartRendererManager::self()->createContext(); QObject block; if (node || mp->hasSubParts()) { c.insert(QStringLiteral("content"), QVariant::fromValue([this, mp, htmlWriter](Grantlee::OutputStream *) { HTMLBlock::Ptr rBlock; if (mp->content() && mp->isRoot()) { rBlock = HTMLBlock::Ptr(new RootBlock(htmlWriter)); } renderSubParts(mp, htmlWriter); })); } else if (!metaData.inProgress) { c.insert(QStringLiteral("content"), QVariant::fromValue([this, mp, htmlWriter](Grantlee::OutputStream *) { renderWithFactory(mp, htmlWriter); })); } c.insert(QStringLiteral("cryptoProto"), QVariant::fromValue(mp->mCryptoProto)); - if (mp->mDecryptRecipients.size() > 0) { + if (!mp->mDecryptRecipients.empty()) { c.insert(QStringLiteral("decryptedRecipients"), QVariant::fromValue(mp->mDecryptRecipients)); } c.insert(QStringLiteral("block"), &block); block.setProperty("inProgress", metaData.inProgress); block.setProperty("isDecrypted", mp->decryptMessage()); block.setProperty("isDecryptable", metaData.isDecryptable); block.setProperty("decryptIcon", QUrl::fromLocalFile(IconNameCache::instance()->iconPath(QStringLiteral( "document-decrypt"), KIconLoader::Small)).url()); block.setProperty("errorText", metaData.errorText); block.setProperty("noSecKey", mp->mNoSecKey); Grantlee::OutputStream s(htmlWriter->stream()); t->render(&s, &c); } void DefaultRendererPrivate::renderSigned(const SignedMessagePart::Ptr &mp, HtmlWriter *htmlWriter) { KMime::Content *node = mp->content(); const auto metaData = *mp->partMetaData(); auto cryptoProto = mp->mCryptoProto; const bool isSMIME = cryptoProto && (cryptoProto == QGpgME::smime()); Grantlee::Template t = MessageViewer::MessagePartRendererManager::self()->loadByName(QStringLiteral( ":/signedmessagepart.html")); Grantlee::Context c = MessageViewer::MessagePartRendererManager::self()->createContext(); QObject block; if (node) { c.insert(QStringLiteral("content"), QVariant::fromValue([this, mp, htmlWriter](Grantlee::OutputStream *) { HTMLBlock::Ptr rBlock; if (mp->isRoot()) { rBlock = HTMLBlock::Ptr(new RootBlock(htmlWriter)); } renderSubParts(mp, htmlWriter); })); } else if (!metaData.inProgress) { c.insert(QStringLiteral("content"), QVariant::fromValue([this, mp, htmlWriter](Grantlee::OutputStream *) { renderWithFactory(mp, htmlWriter); })); } c.insert(QStringLiteral("cryptoProto"), QVariant::fromValue(cryptoProto)); c.insert(QStringLiteral("block"), &block); block.setProperty("inProgress", metaData.inProgress); block.setProperty("errorText", metaData.errorText); block.setProperty("detailHeader", mp->source()->showSignatureDetails()); block.setProperty("printing", false); block.setProperty("addr", metaData.signerMailAddresses.join(QLatin1Char(','))); block.setProperty("technicalProblem", metaData.technicalProblem); block.setProperty("keyId", metaData.keyId); if (metaData.creationTime.isValid()) { //should be handled inside grantlee but currently not possible see: https://bugs.kde.org/363475 block.setProperty("creationTime", QLocale().toString(metaData.creationTime, QLocale::ShortFormat)); } block.setProperty("isGoodSignature", metaData.isGoodSignature); block.setProperty("isSMIME", isSMIME); if (metaData.keyTrust == GpgME::Signature::Unknown) { block.setProperty("keyTrust", QStringLiteral("unknown")); } else if (metaData.keyTrust == GpgME::Signature::Marginal) { block.setProperty("keyTrust", QStringLiteral("marginal")); } else if (metaData.keyTrust == GpgME::Signature::Full) { block.setProperty("keyTrust", QStringLiteral("full")); } else if (metaData.keyTrust == GpgME::Signature::Ultimate) { block.setProperty("keyTrust", QStringLiteral("ultimate")); } else { block.setProperty("keyTrust", QStringLiteral("untrusted")); } QString startKeyHREF; { QString keyWithWithoutURL; if (cryptoProto) { startKeyHREF = QStringLiteral("") .arg(cryptoProto->displayName(), cryptoProto->name(), QString::fromLatin1(metaData.keyId)); keyWithWithoutURL = QStringLiteral("%1%2").arg(startKeyHREF, QString::fromLatin1(QByteArray(QByteArrayLiteral( "0x") + metaData.keyId))); } else { keyWithWithoutURL = QStringLiteral("0x") + QString::fromUtf8(metaData.keyId); } block.setProperty("keyWithWithoutURL", keyWithWithoutURL); } bool onlyShowKeyURL = false; bool showKeyInfos = false; bool cannotCheckSignature = true; QString signer = metaData.signer; QString statusStr; QString mClass; QString greenCaseWarning; if (metaData.inProgress) { mClass = QStringLiteral("signInProgress"); } else { const QStringList &blockAddrs(metaData.signerMailAddresses); // note: At the moment frameColor and showKeyInfos are // used for CMS only but not for PGP signatures // pending(khz): Implement usage of these for PGP sigs as well. int frameColor = SIG_FRAME_COL_UNDEF; statusStr = sigStatusToString(cryptoProto, metaData.status_code, metaData.sigSummary, frameColor, showKeyInfos); // if needed fallback to english status text // that was reported by the plugin if (statusStr.isEmpty()) { statusStr = metaData.status; } if (metaData.technicalProblem) { frameColor = SIG_FRAME_COL_YELLOW; } switch (frameColor) { case SIG_FRAME_COL_RED: cannotCheckSignature = false; break; case SIG_FRAME_COL_YELLOW: cannotCheckSignature = true; break; case SIG_FRAME_COL_GREEN: cannotCheckSignature = false; break; } // temporary hack: always show key information! showKeyInfos = true; if (isSMIME && (SIG_FRAME_COL_UNDEF != frameColor)) { switch (frameColor) { case SIG_FRAME_COL_RED: mClass = QStringLiteral("signErr"); onlyShowKeyURL = true; break; case SIG_FRAME_COL_YELLOW: if (metaData.technicalProblem) { mClass = QStringLiteral("signWarn"); } else { mClass = QStringLiteral("signOkKeyBad"); } break; case SIG_FRAME_COL_GREEN: mClass = QStringLiteral("signOkKeyOk"); // extra hint for green case // that email addresses in DN do not match fromAddress QString msgFrom(KEmailAddress::extractEmailAddress(mp->mFromAddress)); QString certificate; if (metaData.keyId.isEmpty()) { certificate = i18n("certificate"); } else { certificate = startKeyHREF + i18n("certificate") + QStringLiteral(""); } if (!blockAddrs.empty()) { if (!blockAddrs.contains(msgFrom, Qt::CaseInsensitive)) { greenCaseWarning = QStringLiteral("") +i18nc("Start of warning message.", "Warning:") +QStringLiteral(" ") +i18n( "Sender's mail address is not stored in the %1 used for signing.", certificate) +QStringLiteral("
") +i18n("sender: ") +msgFrom +QStringLiteral("
") +i18n("stored: "); // We cannot use Qt's join() function here but // have to join the addresses manually to // extract the mail addresses (without '<''>') // before including it into our string: bool bStart = true; QStringList::ConstIterator end(blockAddrs.constEnd()); for (QStringList::ConstIterator it = blockAddrs.constBegin(); it != end; ++it) { if (!bStart) { greenCaseWarning.append(QStringLiteral(",
   ")); } bStart = false; greenCaseWarning.append(KEmailAddress::extractEmailAddress(*it)); } } } else { greenCaseWarning = QStringLiteral("") +i18nc("Start of warning message.", "Warning:") +QStringLiteral(" ") +i18n("No mail address is stored in the %1 used for signing, " "so we cannot compare it to the sender's address %2.", certificate, msgFrom); } break; } if (showKeyInfos && !cannotCheckSignature) { if (metaData.signer.isEmpty()) { signer.clear(); } else { if (!blockAddrs.empty()) { const QUrl address = KEmailAddress::encodeMailtoUrl(blockAddrs.first()); signer = QStringLiteral("%2").arg(QLatin1String(QUrl :: toPercentEncoding( address . path())), signer); } } } } else { if (metaData.signer.isEmpty() || metaData.technicalProblem) { mClass = QStringLiteral("signWarn"); } else { // HTMLize the signer's user id and create mailto: link signer = MessageCore::StringUtil::quoteHtmlChars(signer, true); signer = QStringLiteral("%1").arg(signer); if (metaData.isGoodSignature) { if (metaData.keyTrust < GpgME::Signature::Marginal) { mClass = QStringLiteral("signOkKeyBad"); } else { mClass = QStringLiteral("signOkKeyOk"); } } else { mClass = QStringLiteral("signErr"); } } } } block.setProperty("onlyShowKeyURL", onlyShowKeyURL); block.setProperty("showKeyInfos", showKeyInfos); block.setProperty("cannotCheckSignature", cannotCheckSignature); block.setProperty("signer", signer); block.setProperty("statusStr", statusStr); block.setProperty("signClass", mClass); block.setProperty("greenCaseWarning", greenCaseWarning); Grantlee::OutputStream s(htmlWriter->stream()); t->render(&s, &c); } void DefaultRendererPrivate::render(const SignedMessagePart::Ptr &mp, HtmlWriter *htmlWriter) { const auto metaData = *mp->partMetaData(); if (metaData.isSigned || metaData.inProgress) { HTMLBlock::Ptr aBlock; if (mp->isAttachment()) { aBlock = HTMLBlock::Ptr(new AttachmentMarkBlock(htmlWriter, mp->attachmentContent())); } renderSigned(mp, htmlWriter); return; } HTMLBlock::Ptr aBlock; if (mp->isAttachment()) { aBlock = HTMLBlock::Ptr(new AttachmentMarkBlock(htmlWriter, mp->attachmentContent())); } if (mp->hasSubParts()) { renderSubParts(mp, htmlWriter); } else if (!metaData.inProgress) { renderWithFactory(mp, htmlWriter); } } void DefaultRendererPrivate::render(const EncryptedMessagePart::Ptr &mp, HtmlWriter *htmlWriter) { const auto metaData = *mp->partMetaData(); if (metaData.isEncrypted || metaData.inProgress) { HTMLBlock::Ptr aBlock; if (mp->isAttachment()) { aBlock = HTMLBlock::Ptr(new AttachmentMarkBlock(htmlWriter, mp->attachmentContent())); } renderEncrypted(mp, htmlWriter); return; } HTMLBlock::Ptr aBlock; if (mp->isAttachment()) { aBlock = HTMLBlock::Ptr(new AttachmentMarkBlock(htmlWriter, mp->attachmentContent())); } if (mp->hasSubParts()) { renderSubParts(mp, htmlWriter); } else if (!metaData.inProgress) { renderWithFactory(mp, htmlWriter); } } void DefaultRendererPrivate::render(const AlternativeMessagePart::Ptr &mp, HtmlWriter *htmlWriter) { HTMLBlock::Ptr aBlock; if (mp->isAttachment()) { aBlock = HTMLBlock::Ptr(new AttachmentMarkBlock(htmlWriter, mp->attachmentContent())); } auto mode = mp->preferredMode(); if (mode == MimeTreeParser::Util::MultipartPlain && mp->text().trimmed().isEmpty()) { foreach (const auto m, mp->availableModes()) { if (m != MimeTreeParser::Util::MultipartPlain) { mode = m; break; } } } MimeMessagePart::Ptr part(mp->mChildParts.first()); if (mp->mChildParts.contains(mode)) { part = mp->mChildParts[mode]; } render(part, htmlWriter); } void DefaultRendererPrivate::render(const CertMessagePart::Ptr &mp, HtmlWriter *htmlWriter) { const GpgME::ImportResult &importResult(mp->mImportResult); Grantlee::Template t = MessageViewer::MessagePartRendererManager::self()->loadByName(QStringLiteral( ":/certmessagepart.html")); Grantlee::Context c = MessageViewer::MessagePartRendererManager::self()->createContext(); QObject block; c.insert(QStringLiteral("block"), &block); block.setProperty("importError", QString::fromLocal8Bit(importResult.error().asString())); block.setProperty("nImp", importResult.numImported()); block.setProperty("nUnc", importResult.numUnchanged()); block.setProperty("nSKImp", importResult.numSecretKeysImported()); block.setProperty("nSKUnc", importResult.numSecretKeysUnchanged()); QVariantList keylist; const auto imports = importResult.imports(); auto end(imports.end()); for (auto it = imports.begin(); it != end; ++it) { QObject *key(new QObject(mp.data())); key->setProperty("error", QString::fromLocal8Bit((*it).error().asString())); key->setProperty("status", (*it).status()); key->setProperty("fingerprint", QLatin1String((*it).fingerprint())); keylist << QVariant::fromValue(key); } HTMLBlock::Ptr aBlock; if (mp->isAttachment()) { aBlock = HTMLBlock::Ptr(new AttachmentMarkBlock(htmlWriter, mp->attachmentContent())); } Grantlee::OutputStream s(htmlWriter->stream()); t->render(&s, &c); } bool DefaultRendererPrivate::renderWithFactory(const QMetaObject *mo, const MessagePart::Ptr &msgPart, HtmlWriter *htmlWriter) { if (!mRendererFactory) { return false; } for (auto r : mRendererFactory->renderersForPart(mo, msgPart)) { if (r->render(msgPart, htmlWriter, this)) { return true; } } return false; } void DefaultRendererPrivate::renderFactory(const MessagePart::Ptr &msgPart, HtmlWriter *htmlWriter) { const QString className = QString::fromUtf8(msgPart->metaObject()->className()); if (renderWithFactory(msgPart, htmlWriter)) { return; } if (className == QStringLiteral("MimeTreeParser::MessagePartList")) { auto mp = msgPart.dynamicCast(); if (mp) { render(mp, htmlWriter); } } else if (className == QStringLiteral("MimeTreeParser::MimeMessagePart")) { auto mp = msgPart.dynamicCast(); if (mp) { render(mp, htmlWriter); } } else if (className == QStringLiteral("MimeTreeParser::EncapsulatedRfc822MessagePart")) { auto mp = msgPart.dynamicCast(); if (mp) { render(mp, htmlWriter); } } else if (className == QStringLiteral("MimeTreeParser::HtmlMessagePart")) { auto mp = msgPart.dynamicCast(); if (mp) { render(mp, htmlWriter); } } else if (className == QStringLiteral("MimeTreeParser::SignedMessagePart")) { auto mp = msgPart.dynamicCast(); if (mp) { render(mp, htmlWriter); } } else if (className == QStringLiteral("MimeTreeParser::EncryptedMessagePart")) { auto mp = msgPart.dynamicCast(); if (mp) { render(mp, htmlWriter); } } else if (className == QStringLiteral("MimeTreeParser::AlternativeMessagePart")) { auto mp = msgPart.dynamicCast(); if (mp) { render(mp, htmlWriter); } } else if (className == QStringLiteral("MimeTreeParser::CertMessagePart")) { auto mp = msgPart.dynamicCast(); if (mp) { render(mp, htmlWriter); } } else { qCWarning(MESSAGEVIEWER_LOG) << "We got a unkonwn classname, using default behaviour for " << className; } } DefaultRenderer::DefaultRenderer(const MimeTreeParser::MessagePart::Ptr &msgPart, CSSHelperBase *cssHelper, MimeTreeParser::HtmlWriter *writer) : d(new MimeTreeParser::DefaultRendererPrivate(msgPart, cssHelper, writer, MessagePartRendererFactory::instance())) { } DefaultRenderer::~DefaultRenderer() { delete d; } diff --git a/messageviewer/src/messagepartthemes/default/messagepartrenderermanager.cpp b/messageviewer/src/messagepartthemes/default/messagepartrenderermanager.cpp index d1c6fcdd..13a39c37 100644 --- a/messageviewer/src/messagepartthemes/default/messagepartrenderermanager.cpp +++ b/messageviewer/src/messagepartthemes/default/messagepartrenderermanager.cpp @@ -1,145 +1,167 @@ /* Copyright (C) 2016-2017 Laurent Montel This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "messagepartrenderermanager.h" #include "messageviewer_debug.h" #include #include #include #include #include #include #include +#include #include #include #include #include #include #include Q_DECLARE_METATYPE(GpgME::DecryptionResult::Recipient) Q_DECLARE_METATYPE(const QGpgME::Protocol *) +Q_DECLARE_METATYPE(GpgME::Key) + // Read-only introspection of GpgME::DecryptionResult::Recipient object. GRANTLEE_BEGIN_LOOKUP(GpgME::DecryptionResult::Recipient) if (property == QStringLiteral("keyID")) { return QString::fromLatin1(object.keyID()); } GRANTLEE_END_LOOKUP // Read-only introspection of QGpgME::Protocol object. namespace Grantlee { template<> inline QVariant TypeAccessor::lookUp(const QGpgME::Protocol *const object, const QString &property) { if (property == QStringLiteral("name")) { return object->name(); } else if (property == QStringLiteral("displayName")) { return object->displayName(); } return QVariant(); } } +// Read-only introspection of std::pair object. +namespace Grantlee { +template<> +inline QVariant TypeAccessor&>::lookUp(std::pair const &object, const QString &property) +{ + if (property == QStringLiteral("keyID")) { + return QString::fromLatin1(object.first.keyID()); + } + if (property == QStringLiteral("id")) { + return QString::fromLatin1(object.second.userID(0).id()); + } + if (property == QStringLiteral("mainID")) { + return QString::fromLatin1(object.second.keyID()); + } + return QVariant(); +} +} + namespace MessageViewer { class GlobalContext : public QObject { Q_OBJECT Q_PROPERTY(QString dir READ layoutDirection CONSTANT) Q_PROPERTY(int iconSize READ iconSize CONSTANT) public: explicit GlobalContext(QObject *parent) : QObject(parent) { } QString layoutDirection() const { return QGuiApplication::isRightToLeft() ? QStringLiteral("rtl") : QStringLiteral("ltr"); } int iconSize() const { return KIconLoader::global()->currentSize(KIconLoader::Desktop); } }; } using namespace MessageViewer; MessagePartRendererManager::MessagePartRendererManager(QObject *parent) : QObject(parent) , m_engine(nullptr) , m_globalContext(new GlobalContext(this)) { initializeRenderer(); } MessagePartRendererManager::~MessagePartRendererManager() { delete m_engine; } MessagePartRendererManager *MessagePartRendererManager::self() { static MessagePartRendererManager s_self; return &s_self; } void MessagePartRendererManager::initializeRenderer() { Grantlee::registerMetaType(); Grantlee::registerMetaType(); + Grantlee::registerMetaType>(); m_engine = new GrantleeTheme::Engine; foreach (const auto &p, QCoreApplication::libraryPaths()) { m_engine->addPluginPath(p + QStringLiteral("/messageviewer")); } m_engine->addDefaultLibrary(QStringLiteral("messageviewer_grantlee_extension")); m_engine->localizer()->setApplicationDomain(QByteArrayLiteral("libmessageviewer")); auto loader = QSharedPointer( new GrantleeTheme::QtResourceTemplateLoader()); m_engine->addTemplateLoader(loader); } Grantlee::Template MessagePartRendererManager::loadByName(const QString &name) { Grantlee::Template t = m_engine->loadByName(name); if (t->error()) { qCWarning(MESSAGEVIEWER_LOG) << t->errorString() << ". Searched in subdir mimetreeparser/themes/default in these locations" << QStandardPaths::standardLocations( QStandardPaths::GenericDataLocation); } return t; } Grantlee::Context MessagePartRendererManager::createContext() { Grantlee::Context c; m_engine->localizer()->setApplicationDomain(QByteArrayLiteral("libmessageviewer")); c.setLocalizer(m_engine->localizer()); c.insert(QStringLiteral("global"), m_globalContext); return c; } #include "messagepartrenderermanager.moc" diff --git a/messageviewer/src/messagepartthemes/default/templates/encryptedmessagepart.html b/messageviewer/src/messagepartthemes/default/templates/encryptedmessagepart.html index 44ca497b..9f11042e 100644 --- a/messageviewer/src/messagepartthemes/default/templates/encryptedmessagepart.html +++ b/messageviewer/src/messagepartthemes/default/templates/encryptedmessagepart.html @@ -1,49 +1,72 @@ {% if not block.isDecrypted %}
{% i18n "This message is encrypted." %}
{% else %}
{% if block.inProgress %} {% i18n "Please wait while the message is being decrypted..." %} {% elif block.isDecryptable %} - {% i18n "Encrypted message" %} +
+ {% i18n "Encrypted message" %} + {% i18n "Show Details" %} +
+ {% else %} {% i18n "Encrypted message (decryption not possible)" %} {% if block.errorText %}
{% i18n "Reason: " %}{{block.errorText|safe}} {% endif %} {% endif %}
{% if block.isDecryptable %} {% callback content %} {% else %}
{% if block.noSecKey %} - {% i18n "No secret key found to encrypt the message. It is encrypted for following keys:" %} + {% i18n "No secret key found to decrypt the message. The message is encrypted for the following keys:" %} + {% elif not block.inProgress %} {% i18n "Could not decrypt the data." %} {% endif %}
{% endif %}
{% i18n "End of encrypted message" %}
{% endif %} diff --git a/messageviewer/src/viewer/urlhandlermanager.cpp b/messageviewer/src/viewer/urlhandlermanager.cpp index 2e2bcaed..817be6f0 100644 --- a/messageviewer/src/viewer/urlhandlermanager.cpp +++ b/messageviewer/src/viewer/urlhandlermanager.cpp @@ -1,1204 +1,1214 @@ /* -*- c++ -*- urlhandlermanager.cpp This file is part of KMail, the KDE mail client. Copyright (c) 2003 Marc Mutz Copyright (C) 2002-2003, 2009 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net Copyright (c) 2009 Andras Mantia KMail is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as published by the Free Software Foundation. KMail is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA In addition, as a special exception, the copyright holders give permission to link the code of this program with any edition of the Qt library by Trolltech AS, Norway (or with modified versions of Qt that use the same license as Qt), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than Qt. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ #include "urlhandlermanager.h" #include "messageviewer_debug.h" #include "messageviewer/urlhandler.h" #include "interfaces/bodyparturlhandler.h" #include "utils/mimetype.h" #include "viewer/viewer_p.h" #include "messageviewer/messageviewerutil.h" #include "../utils/messageviewerutil_p.h" #include "stl_util.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using std::for_each; using std::remove; using std::find; using namespace MessageViewer; using namespace MessageCore; URLHandlerManager *URLHandlerManager::self = nullptr; namespace { class KMailProtocolURLHandler : public MimeTreeParser::URLHandler { public: KMailProtocolURLHandler() : MimeTreeParser::URLHandler() { } ~KMailProtocolURLHandler() { } bool handleClick(const QUrl &, ViewerPrivate *) const override; bool handleContextMenuRequest(const QUrl &url, const QPoint &, ViewerPrivate *) const override { return url.scheme() == QLatin1String("kmail"); } QString statusBarMessage(const QUrl &, ViewerPrivate *) const override; }; class ExpandCollapseQuoteURLManager : public MimeTreeParser::URLHandler { public: ExpandCollapseQuoteURLManager() : MimeTreeParser::URLHandler() { } ~ExpandCollapseQuoteURLManager() { } bool handleClick(const QUrl &, ViewerPrivate *) const override; bool handleDrag(const QUrl &url, ViewerPrivate *window) const override; bool handleContextMenuRequest(const QUrl &, const QPoint &, ViewerPrivate *) const override { return false; } QString statusBarMessage(const QUrl &, ViewerPrivate *) const override; }; class SMimeURLHandler : public MimeTreeParser::URLHandler { public: SMimeURLHandler() : MimeTreeParser::URLHandler() { } ~SMimeURLHandler() { } bool handleClick(const QUrl &, ViewerPrivate *) const override; bool handleContextMenuRequest(const QUrl &, const QPoint &, ViewerPrivate *) const override { return false; } QString statusBarMessage(const QUrl &, ViewerPrivate *) const override; }; class MailToURLHandler : public MimeTreeParser::URLHandler { public: MailToURLHandler() : MimeTreeParser::URLHandler() { } ~MailToURLHandler() { } bool handleClick(const QUrl &, ViewerPrivate *) const override { return false; } bool handleContextMenuRequest(const QUrl &, const QPoint &, ViewerPrivate *) const override { return false; } QString statusBarMessage(const QUrl &, ViewerPrivate *) const override; }; class ContactUidURLHandler : public MimeTreeParser::URLHandler { public: ContactUidURLHandler() : MimeTreeParser::URLHandler() { } ~ContactUidURLHandler() { } bool handleClick(const QUrl &, ViewerPrivate *) const override; bool handleContextMenuRequest(const QUrl &url, const QPoint &p, ViewerPrivate *) const override; QString statusBarMessage(const QUrl &, ViewerPrivate *) const override; }; class HtmlAnchorHandler : public MimeTreeParser::URLHandler { public: HtmlAnchorHandler() : MimeTreeParser::URLHandler() { } ~HtmlAnchorHandler() { } bool handleClick(const QUrl &, ViewerPrivate *) const override; bool handleContextMenuRequest(const QUrl &, const QPoint &, ViewerPrivate *) const override { return false; } QString statusBarMessage(const QUrl &, ViewerPrivate *) const override { return QString(); } }; class AttachmentURLHandler : public MimeTreeParser::URLHandler { public: AttachmentURLHandler() : MimeTreeParser::URLHandler() { } ~AttachmentURLHandler() { } bool handleClick(const QUrl &, ViewerPrivate *) const override; bool handleShiftClick(const QUrl &, ViewerPrivate *window) const override; bool handleContextMenuRequest(const QUrl &, const QPoint &, ViewerPrivate *) const override; bool handleDrag(const QUrl &url, ViewerPrivate *window) const override; bool willHandleDrag(const QUrl &url, ViewerPrivate *window) const override; QString statusBarMessage(const QUrl &, ViewerPrivate *) const override; private: KMime::Content *nodeForUrl(const QUrl &url, ViewerPrivate *w) const; bool attachmentIsInHeader(const QUrl &url) const; }; class ShowAuditLogURLHandler : public MimeTreeParser::URLHandler { public: ShowAuditLogURLHandler() : MimeTreeParser::URLHandler() { } ~ShowAuditLogURLHandler() { } bool handleClick(const QUrl &, ViewerPrivate *) const override; bool handleContextMenuRequest(const QUrl &, const QPoint &, ViewerPrivate *) const override; QString statusBarMessage(const QUrl &, ViewerPrivate *) const override; bool handleDrag(const QUrl &url, ViewerPrivate *window) const override; }; // Handler that prevents dragging of internal images added by KMail, such as the envelope image // in the enterprise header class InternalImageURLHandler : public MimeTreeParser::URLHandler { public: InternalImageURLHandler() : MimeTreeParser::URLHandler() { } ~InternalImageURLHandler() { } bool handleDrag(const QUrl &url, ViewerPrivate *window) const override; bool willHandleDrag(const QUrl &url, ViewerPrivate *window) const override; bool handleClick(const QUrl &, ViewerPrivate *) const override { return false; } bool handleContextMenuRequest(const QUrl &, const QPoint &, ViewerPrivate *) const override { return false; } QString statusBarMessage(const QUrl &, ViewerPrivate *) const override { return QString(); } }; class EmbeddedImageURLHandler : public MimeTreeParser::URLHandler { public: EmbeddedImageURLHandler() : MimeTreeParser::URLHandler() { } ~EmbeddedImageURLHandler() { } bool handleDrag(const QUrl &url, ViewerPrivate *window) const override; bool willHandleDrag(const QUrl &url, ViewerPrivate *window) const override; bool handleClick(const QUrl &, ViewerPrivate *) const override { return false; } bool handleContextMenuRequest(const QUrl &, const QPoint &, ViewerPrivate *) const override { return false; } QString statusBarMessage(const QUrl &url, ViewerPrivate *) const override { Q_UNUSED(url); return QString(); } }; class KRunURLHandler : public MimeTreeParser::URLHandler { public: KRunURLHandler() : MimeTreeParser::URLHandler() { } ~KRunURLHandler() { } bool handleClick(const QUrl &, ViewerPrivate *) const override; bool handleContextMenuRequest(const QUrl &, const QPoint &, ViewerPrivate *) const override { return false; } QString statusBarMessage(const QUrl &, ViewerPrivate *) const override { return QString(); } }; } // anon namespace // // // BodyPartURLHandlerManager // // class URLHandlerManager::BodyPartURLHandlerManager : public MimeTreeParser::URLHandler { public: BodyPartURLHandlerManager() : MimeTreeParser::URLHandler() { } ~BodyPartURLHandlerManager(); bool handleClick(const QUrl &, ViewerPrivate *) const override; bool handleContextMenuRequest(const QUrl &, const QPoint &, ViewerPrivate *) const override; QString statusBarMessage(const QUrl &, ViewerPrivate *) const override; void registerHandler(const Interface::BodyPartURLHandler *handler, const QString &mimeType); void unregisterHandler(const Interface::BodyPartURLHandler *handler); private: QVector handlersForPart(KMime::Content *node) const; typedef QHash> BodyPartHandlerList; BodyPartHandlerList mHandlers; }; URLHandlerManager::BodyPartURLHandlerManager::~BodyPartURLHandlerManager() { for_each(mHandlers.begin(), mHandlers.end(), [](QVector &handlers) { for_each(handlers.begin(), handlers.end(), DeleteAndSetToZero()); }); } void URLHandlerManager::BodyPartURLHandlerManager::registerHandler( const Interface::BodyPartURLHandler *handler, const QString &mimeType) { if (!handler) { return; } unregisterHandler(handler); // don't produce duplicates const auto mt = mimeType.toLatin1(); auto it = mHandlers.find(mt); if (it == mHandlers.end()) { it = mHandlers.insert(mt, {}); } it->push_back(handler); } void URLHandlerManager::BodyPartURLHandlerManager::unregisterHandler( const Interface::BodyPartURLHandler *handler) { // don't delete them, only remove them from the list! auto it = mHandlers.begin(); while (it != mHandlers.end()) { it->erase(remove(it->begin(), it->end(), handler), it->end()); if (it->isEmpty()) { it = mHandlers.erase(it); } else { ++it; } } } static KMime::Content *partNodeFromXKMailUrl(const QUrl &url, ViewerPrivate *w, QString *path) { Q_ASSERT(path); if (!w || url.scheme() != QLatin1String("x-kmail")) { return nullptr; } const QString urlPath = url.path(); // urlPath format is: /bodypart/// qCDebug(MESSAGEVIEWER_LOG) << "BodyPartURLHandler: urlPath ==" << urlPath; if (!urlPath.startsWith(QStringLiteral("/bodypart/"))) { return nullptr; } const QStringList urlParts = urlPath.mid(10).split(QLatin1Char('/')); if (urlParts.size() != 3) { return nullptr; } //KMime::ContentIndex index( urlParts[1] ); *path = QUrl::fromPercentEncoding(urlParts.at(2).toLatin1()); return w->nodeFromUrl(QUrl(urlParts.at(1))); } QVector URLHandlerManager::BodyPartURLHandlerManager::handlersForPart(KMime::Content *node) const { if (auto ct = node->contentType(false)) { const auto mimeType = ct->mimeType(); if (!mimeType.isEmpty()) { return mHandlers.value(mimeType); } } return {}; } bool URLHandlerManager::BodyPartURLHandlerManager::handleClick(const QUrl &url, ViewerPrivate *w) const { QString path; KMime::Content *node = partNodeFromXKMailUrl(url, w, &path); if (!node) { return false; } MimeTreeParser::PartNodeBodyPart part(nullptr, nullptr, w->message().data(), node, w->nodeHelper()); for (const auto &handlers : { handlersForPart(node), mHandlers.value({}) }) { for (auto it = handlers.cbegin(), end = handlers.cend(); it != end; ++it) { if ((*it)->handleClick(w->viewer(), &part, path)) { return true; } } } return false; } bool URLHandlerManager::BodyPartURLHandlerManager::handleContextMenuRequest(const QUrl &url, const QPoint &p, ViewerPrivate *w) const { QString path; KMime::Content *node = partNodeFromXKMailUrl(url, w, &path); if (!node) { return false; } MimeTreeParser::PartNodeBodyPart part(nullptr, nullptr, w->message().data(), node, w->nodeHelper()); for (const auto &handlers : { handlersForPart(node), mHandlers.value({}) }) { for (auto it = handlers.cbegin(), end = handlers.cend(); it != end; ++it) { if ((*it)->handleContextMenuRequest(&part, path, p)) { return true; } } } return false; } QString URLHandlerManager::BodyPartURLHandlerManager::statusBarMessage(const QUrl &url, ViewerPrivate *w) const { QString path; KMime::Content *node = partNodeFromXKMailUrl(url, w, &path); if (!node) { return QString(); } MimeTreeParser::PartNodeBodyPart part(nullptr, nullptr, w->message().data(), node, w->nodeHelper()); for (const auto &handlers : { handlersForPart(node), mHandlers.value({}) }) { for (auto it = handlers.cbegin(), end = handlers.cend(); it != end; ++it) { const QString msg = (*it)->statusBarMessage(&part, path); if (!msg.isEmpty()) { return msg; } } } return QString(); } // // // URLHandlerManager // // URLHandlerManager::URLHandlerManager() { registerHandler(new KMailProtocolURLHandler()); registerHandler(new ExpandCollapseQuoteURLManager()); registerHandler(new SMimeURLHandler()); registerHandler(new MailToURLHandler()); registerHandler(new ContactUidURLHandler()); registerHandler(new HtmlAnchorHandler()); registerHandler(new AttachmentURLHandler()); registerHandler(mBodyPartURLHandlerManager = new BodyPartURLHandlerManager()); registerHandler(new ShowAuditLogURLHandler()); registerHandler(new InternalImageURLHandler); registerHandler(new KRunURLHandler()); //registerHandler(new EmbeddedImageURLHandler()); } URLHandlerManager::~URLHandlerManager() { for_each(mHandlers.begin(), mHandlers.end(), DeleteAndSetToZero()); } URLHandlerManager *URLHandlerManager::instance() { if (!self) { self = new URLHandlerManager(); } return self; } void URLHandlerManager::registerHandler(const MimeTreeParser::URLHandler *handler) { if (!handler) { return; } unregisterHandler(handler); // don't produce duplicates mHandlers.push_back(handler); } void URLHandlerManager::unregisterHandler(const MimeTreeParser::URLHandler *handler) { // don't delete them, only remove them from the list! mHandlers.erase(remove(mHandlers.begin(), mHandlers.end(), handler), mHandlers.end()); } void URLHandlerManager::registerHandler(const Interface::BodyPartURLHandler *handler, const QString &mimeType) { if (mBodyPartURLHandlerManager) { mBodyPartURLHandlerManager->registerHandler(handler, mimeType); } } void URLHandlerManager::unregisterHandler(const Interface::BodyPartURLHandler *handler) { if (mBodyPartURLHandlerManager) { mBodyPartURLHandlerManager->unregisterHandler(handler); } } bool URLHandlerManager::handleClick(const QUrl &url, ViewerPrivate *w) const { HandlerList::const_iterator end(mHandlers.constEnd()); for (HandlerList::const_iterator it = mHandlers.constBegin(); it != end; ++it) { if ((*it)->handleClick(url, w)) { return true; } } return false; } bool URLHandlerManager::handleShiftClick(const QUrl &url, ViewerPrivate *window) const { HandlerList::const_iterator end(mHandlers.constEnd()); for (HandlerList::const_iterator it = mHandlers.constBegin(); it != end; ++it) { if ((*it)->handleShiftClick(url, window)) { return true; } } return false; } bool URLHandlerManager::willHandleDrag(const QUrl &url, ViewerPrivate *window) const { HandlerList::const_iterator end(mHandlers.constEnd()); for (HandlerList::const_iterator it = mHandlers.constBegin(); it != end; ++it) { if ((*it)->willHandleDrag(url, window)) { return true; } } return false; } bool URLHandlerManager::handleDrag(const QUrl &url, ViewerPrivate *window) const { HandlerList::const_iterator end(mHandlers.constEnd()); for (HandlerList::const_iterator it = mHandlers.constBegin(); it != end; ++it) { if ((*it)->handleDrag(url, window)) { return true; } } return false; } bool URLHandlerManager::handleContextMenuRequest(const QUrl &url, const QPoint &p, ViewerPrivate *w) const { HandlerList::const_iterator end(mHandlers.constEnd()); for (HandlerList::const_iterator it = mHandlers.constBegin(); it != end; ++it) { if ((*it)->handleContextMenuRequest(url, p, w)) { return true; } } return false; } QString URLHandlerManager::statusBarMessage(const QUrl &url, ViewerPrivate *w) const { HandlerList::const_iterator end(mHandlers.constEnd()); for (HandlerList::const_iterator it = mHandlers.constBegin(); it != end; ++it) { const QString msg = (*it)->statusBarMessage(url, w); if (!msg.isEmpty()) { return msg; } } return QString(); } // // // URLHandler // // namespace { bool KMailProtocolURLHandler::handleClick(const QUrl &url, ViewerPrivate *w) const { if (url.scheme() == QLatin1String("kmail")) { if (!w) { return false; } const QString urlPath(url.path()); if (urlPath == QLatin1String("showHTML")) { w->setDisplayFormatMessageOverwrite(MessageViewer::Viewer::Html); w->update(MimeTreeParser::Force); return true; } else if (urlPath == QLatin1String("goOnline")) { w->goOnline(); return true; } else if (urlPath == QLatin1String("goResourceOnline")) { w->goResourceOnline(); return true; } else if (urlPath == QLatin1String("loadExternal")) { w->setHtmlLoadExtOverride(!w->htmlLoadExtOverride()); w->update(MimeTreeParser::Force); return true; } else if (urlPath == QLatin1String("decryptMessage")) { w->setDecryptMessageOverwrite(true); w->update(MimeTreeParser::Force); return true; } else if (urlPath == QLatin1String("showSignatureDetails")) { w->setShowSignatureDetails(true); w->update(MimeTreeParser::Force); return true; } else if (urlPath == QLatin1String("hideSignatureDetails")) { w->setShowSignatureDetails(false); w->update(MimeTreeParser::Force); return true; + } else if (urlPath == QLatin1String("showEncryptionDetails")) { + w->setHideEncryptionDetails(false); + return true; + } else if (urlPath == QLatin1String("hideEncryptionDetails")) { + w->setHideEncryptionDetails(true); + return true; } else if (urlPath == QLatin1String("showAttachmentQuicklist")) { w->setShowAttachmentQuicklist(false); return true; } else if (urlPath == QLatin1String("hideAttachmentQuicklist")) { w->setShowAttachmentQuicklist(true); return true; } else if (urlPath == QLatin1String("showFullToAddressList")) { w->setFullToAddressList(false); return true; } else if (urlPath == QLatin1String("hideFullToAddressList")) { w->setFullToAddressList(true); return true; } else if (urlPath == QLatin1String("showFullCcAddressList")) { w->setFullCcAddressList(false); return true; } else if (urlPath == QLatin1String("hideFullCcAddressList")) { w->setFullCcAddressList(true); return true; } } return false; } QString KMailProtocolURLHandler::statusBarMessage(const QUrl &url, ViewerPrivate *) const { if (url.scheme() == QLatin1String("kmail")) { const QString urlPath(url.path()); if (urlPath == QLatin1String("showHTML")) { return i18n("Turn on HTML rendering for this message."); } else if (urlPath == QLatin1String("loadExternal")) { return i18n("Load external references from the Internet for this message."); } else if (urlPath == QLatin1String("goOnline")) { return i18n("Work online."); } else if (urlPath == QLatin1String("goResourceOnline")) { return i18n("Make account online."); } else if (urlPath == QLatin1String("decryptMessage")) { return i18n("Decrypt message."); } else if (urlPath == QLatin1String("showSignatureDetails")) { return i18n("Show signature details."); } else if (urlPath == QLatin1String("hideSignatureDetails")) { return i18n("Hide signature details."); + } else if (urlPath == QLatin1String("showEncryptionDetails")) { + return i18n("Show encryption details."); + } else if (urlPath == QLatin1String("hideEncryptionDetails")) { + return i18n("Hide encryption details."); } else if (urlPath == QLatin1String("showAttachmentQuicklist")) { return i18n("Hide attachment list."); } else if (urlPath == QLatin1String("hideAttachmentQuicklist")) { return i18n("Show attachment list."); } else if (urlPath == QLatin1String("showFullToAddressList")) { return i18n("Hide full \"To\" list"); } else if (urlPath == QLatin1String("hideFullToAddressList")) { return i18n("Show full \"To\" list"); } else if (urlPath == QLatin1String("showFullCcAddressList")) { return i18n("Hide full \"Cc\" list"); } else if (urlPath == QLatin1String("hideFullCcAddressList")) { return i18n("Show full \"Cc\" list"); } else { return QString(); } } else if (url.scheme() == QLatin1String("help")) { return i18n("Open Documentation"); } return QString(); } } namespace { bool ExpandCollapseQuoteURLManager::handleClick(const QUrl &url, ViewerPrivate *w) const { // kmail:levelquote/?num -> the level quote to collapse. // kmail:levelquote/?-num -> expand all levels quote. if (url.scheme() == QLatin1String("kmail") && url.path() == QLatin1String("levelquote")) { const QString levelStr = url.query(); bool isNumber = false; const int levelQuote = levelStr.toInt(&isNumber); if (isNumber) { w->slotLevelQuote(levelQuote); } return true; } return false; } bool ExpandCollapseQuoteURLManager::handleDrag(const QUrl &url, ViewerPrivate *window) const { Q_UNUSED(url); Q_UNUSED(window); return false; } QString ExpandCollapseQuoteURLManager::statusBarMessage(const QUrl &url, ViewerPrivate *) const { if (url.scheme() == QLatin1String("kmail") && url.path() == QLatin1String("levelquote")) { const QString query = url.query(); if (query.length() >= 1) { if (query[ 0 ] == QLatin1Char('-')) { return i18n("Expand all quoted text."); } else { return i18n("Collapse quoted text."); } } } return QString(); } } bool foundSMIMEData(const QString &aUrl, QString &displayName, QString &libName, QString &keyId) { static QString showCertMan(QStringLiteral("showCertificate#")); displayName.clear(); libName.clear(); keyId.clear(); int i1 = aUrl.indexOf(showCertMan); if (-1 < i1) { i1 += showCertMan.length(); int i2 = aUrl.indexOf(QLatin1String(" ### "), i1); if (i1 < i2) { displayName = aUrl.mid(i1, i2 - i1); i1 = i2 + 5; i2 = aUrl.indexOf(QLatin1String(" ### "), i1); if (i1 < i2) { libName = aUrl.mid(i1, i2 - i1); i2 += 5; keyId = aUrl.mid(i2); /* int len = aUrl.length(); if( len > i2+1 ) { keyId = aUrl.mid( i2, 2 ); i2 += 2; while( len > i2+1 ) { keyId += ':'; keyId += aUrl.mid( i2, 2 ); i2 += 2; } } */ } } } return !keyId.isEmpty(); } namespace { bool SMimeURLHandler::handleClick(const QUrl &url, ViewerPrivate *w) const { if (!url.hasFragment()) { return false; } QString displayName, libName, keyId; if (!foundSMIMEData(url.path() + QLatin1Char('#') +QUrl::fromPercentEncoding(url.fragment().toLatin1()), displayName, libName, keyId)) { return false; } QStringList lst; lst << QStringLiteral("--parent-windowid") << QString::number((qlonglong)w->viewer()->mainWindow()->winId()) << QStringLiteral("--query") << keyId; if (!QProcess::startDetached(QStringLiteral("kleopatra"), lst)) { KMessageBox::error(w->mMainWindow, i18n("Could not start certificate manager. " "Please check your installation."), i18n("KMail Error")); } return true; } QString SMimeURLHandler::statusBarMessage(const QUrl &url, ViewerPrivate *) const { QString displayName, libName, keyId; if (!foundSMIMEData(url.path() + QLatin1Char('#') +QUrl::fromPercentEncoding(url.fragment().toLatin1()), displayName, libName, keyId)) { return QString(); } return i18n("Show certificate 0x%1", keyId); } } namespace { bool HtmlAnchorHandler::handleClick(const QUrl &url, ViewerPrivate *w) const { if (!url.host().isEmpty() || !url.hasFragment()) { return false; } w->scrollToAnchor(url.fragment()); return true; } } namespace { QString MailToURLHandler::statusBarMessage(const QUrl &url, ViewerPrivate *) const { if (url.scheme() == QLatin1String("mailto")) { return KEmailAddress::decodeMailtoUrl(url); } return QString(); } } namespace { static QString searchFullEmailByUid(const QString &uid) { QString fullEmail; Akonadi::ContactSearchJob *job = new Akonadi::ContactSearchJob(); job->setLimit(1); job->setQuery(Akonadi::ContactSearchJob::ContactUid, uid, Akonadi::ContactSearchJob::ExactMatch); job->exec(); const KContacts::Addressee::List res = job->contacts(); if (!res.isEmpty()) { KContacts::Addressee addr = res.at(0); fullEmail = addr.fullEmail(); } return fullEmail; } static void runKAddressBook(const QUrl &url) { KPIM::OpenEmailAddressJob *job = new KPIM::OpenEmailAddressJob(url.path(), nullptr); job->start(); } bool ContactUidURLHandler::handleClick(const QUrl &url, ViewerPrivate *) const { if (url.scheme() == QLatin1String("uid")) { runKAddressBook(url); return true; } else { return false; } } bool ContactUidURLHandler::handleContextMenuRequest(const QUrl &url, const QPoint &p, ViewerPrivate *) const { if (url.scheme() != QLatin1String("uid") || url.path().isEmpty()) { return false; } QMenu *menu = new QMenu(); QAction *open = menu->addAction(QIcon::fromTheme(QStringLiteral("view-pim-contacts")), i18n("&Open in Address Book")); #ifndef QT_NO_CLIPBOARD QAction *copy = menu->addAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18n("&Copy Email Address")); #endif QAction *a = menu->exec(p); if (a == open) { runKAddressBook(url); #ifndef QT_NO_CLIPBOARD } else if (a == copy) { const QString fullEmail = searchFullEmailByUid(url.path()); if (!fullEmail.isEmpty()) { QClipboard *clip = QApplication::clipboard(); clip->setText(fullEmail, QClipboard::Clipboard); clip->setText(fullEmail, QClipboard::Selection); KPIM::BroadcastStatus::instance()->setStatusMsg(i18n("Address copied to clipboard.")); } #endif } delete menu; return true; } QString ContactUidURLHandler::statusBarMessage(const QUrl &url, ViewerPrivate *) const { if (url.scheme() == QLatin1String("uid")) { return i18n("Lookup the contact in KAddressbook"); } else { return QString(); } } } namespace { KMime::Content *AttachmentURLHandler::nodeForUrl(const QUrl &url, ViewerPrivate *w) const { if (!w || !w->mMessage) { return nullptr; } if (url.scheme() == QLatin1String("attachment")) { KMime::Content *node = w->nodeFromUrl(url); return node; } return nullptr; } bool AttachmentURLHandler::attachmentIsInHeader(const QUrl &url) const { bool inHeader = false; QUrlQuery query(url); const QString place = query.queryItemValue(QStringLiteral("place")).toLower(); if (!place.isNull()) { inHeader = (place == QLatin1String("header")); } return inHeader; } bool AttachmentURLHandler::handleClick(const QUrl &url, ViewerPrivate *w) const { KMime::Content *node = nodeForUrl(url, w); if (!node) { return false; } const bool inHeader = attachmentIsInHeader(url); const bool shouldShowDialog = !w->nodeHelper()->isNodeDisplayedEmbedded(node) || !inHeader; if (inHeader) { w->scrollToAttachment(node); } if (shouldShowDialog) { w->openAttachment(node, w->nodeHelper()->tempFileUrlFromNode(node)); } return true; } bool AttachmentURLHandler::handleShiftClick(const QUrl &url, ViewerPrivate *window) const { KMime::Content *node = nodeForUrl(url, window); if (!node) { return false; } if (!window) { return false; } QUrl currentUrl; if (Util::saveContents(window->viewer(), KMime::Content::List() << node, currentUrl)) { window->viewer()->showOpenAttachmentFolderWidget(currentUrl); } return true; } bool AttachmentURLHandler::willHandleDrag(const QUrl &url, ViewerPrivate *window) const { return nodeForUrl(url, window) != nullptr; } bool AttachmentURLHandler::handleDrag(const QUrl &url, ViewerPrivate *window) const { #ifndef QT_NO_DRAGANDDROP KMime::Content *node = nodeForUrl(url, window); if (!node) { return false; } if (node->header()) { if (!node->contents().isEmpty()) { node = node->contents().constFirst(); window->nodeHelper()->writeNodeToTempFile(node); } } const QUrl tUrl = window->nodeHelper()->tempFileUrlFromNode(node); const QString fileName = tUrl.path(); if (!fileName.isEmpty()) { QFile f(fileName); f.setPermissions( QFile::ReadOwner | QFile::WriteOwner | QFile::ReadUser | QFile::ReadGroup | QFile::ReadOther); const QString icon = Util::iconPathForContent(node, KIconLoader::Small); QDrag *drag = new QDrag(window->viewer()); QMimeData *mimeData = new QMimeData(); mimeData->setUrls(QList() << tUrl); drag->setMimeData(mimeData); if (!icon.isEmpty()) { drag->setPixmap(QIcon::fromTheme(icon).pixmap(16, 16)); } drag->start(); return true; } else #endif return false; } bool AttachmentURLHandler::handleContextMenuRequest(const QUrl &url, const QPoint &p, ViewerPrivate *w) const { KMime::Content *node = nodeForUrl(url, w); if (!node) { return false; } // PENDING(romain_kdab) : replace with toLocalFile() ? w->showAttachmentPopup(node, w->nodeHelper()->tempFileUrlFromNode(node).path(), p); return true; } QString AttachmentURLHandler::statusBarMessage(const QUrl &url, ViewerPrivate *w) const { KMime::Content *node = nodeForUrl(url, w); if (!node) { return QString(); } const QString name = MimeTreeParser::NodeHelper::fileName(node); if (!name.isEmpty()) { return i18n("Attachment: %1", name); } else if (dynamic_cast(node)) { if (node->header()) { return i18n("Encapsulated Message (Subject: %1)", node->header()->asUnicodeString()); } else { return i18n("Encapsulated Message"); } } return i18n("Unnamed attachment"); } } namespace { static QString extractAuditLog(const QUrl &url) { if (url.scheme() != QLatin1String("kmail") || url.path() != QLatin1String("showAuditLog")) { return QString(); } QUrlQuery query(url); Q_ASSERT(!query.queryItemValue(QStringLiteral("log")).isEmpty()); return query.queryItemValue(QStringLiteral("log")); } bool ShowAuditLogURLHandler::handleClick(const QUrl &url, ViewerPrivate *w) const { const QString auditLog = extractAuditLog(url); if (auditLog.isEmpty()) { return false; } Kleo::MessageBox::auditLog(w->mMainWindow, auditLog); return true; } bool ShowAuditLogURLHandler::handleContextMenuRequest(const QUrl &url, const QPoint &, ViewerPrivate *w) const { Q_UNUSED(w); // disable RMB for my own links: return !extractAuditLog(url).isEmpty(); } QString ShowAuditLogURLHandler::statusBarMessage(const QUrl &url, ViewerPrivate *) const { if (extractAuditLog(url).isEmpty()) { return QString(); } else { return i18n("Show GnuPG Audit Log for this operation"); } } bool ShowAuditLogURLHandler::handleDrag(const QUrl &url, ViewerPrivate *window) const { Q_UNUSED(url); Q_UNUSED(window); return true; } } namespace { bool InternalImageURLHandler::handleDrag(const QUrl &url, ViewerPrivate *window) const { Q_UNUSED(window); Q_UNUSED(url); // This will only be called when willHandleDrag() was true. Return false here, that will // notify ViewerPrivate::eventFilter() that no drag was started. return false; } bool InternalImageURLHandler::willHandleDrag(const QUrl &url, ViewerPrivate *window) const { Q_UNUSED(window); if (url.scheme() == QLatin1String("data") && url.path().startsWith(QStringLiteral("image"))) { return true; } const QString imagePath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral( "libmessageviewer/pics/"), QStandardPaths::LocateDirectory); return url.path().contains(imagePath); } } namespace { bool KRunURLHandler::handleClick(const QUrl &url, ViewerPrivate *w) const { const QString scheme(url.scheme()); if ((scheme == QLatin1String("http")) || (scheme == QLatin1String("https")) || (scheme == QLatin1String("ftp")) || (scheme == QLatin1String("file")) || (scheme == QLatin1String("ftps")) || (scheme == QLatin1String("sftp")) || (scheme == QLatin1String("help")) || (scheme == QLatin1String("vnc")) || (scheme == QLatin1String("smb")) || (scheme == QLatin1String("fish")) || (scheme == QLatin1String("news"))) { KPIM::BroadcastStatus::instance()->setTransientStatusMsg(i18n("Opening URL...")); QTimer::singleShot(2000, KPIM::BroadcastStatus::instance(), &KPIM::BroadcastStatus::reset); QMimeDatabase mimeDb; auto mime = mimeDb.mimeTypeForUrl(url); if (mime.name() == QLatin1String("application/x-desktop") || mime.name() == QLatin1String("application/x-executable") || mime.name() == QLatin1String("application/x-ms-dos-executable") || mime.name() == QLatin1String("application/x-shellscript")) { if (KMessageBox::warningYesNo(nullptr, xi18nc("@info", "Do you really want to execute %1?", url.toDisplayString(QUrl::PreferLocalFile)), QString(), KGuiItem(i18n("Execute")), KStandardGuiItem::cancel()) != KMessageBox::Yes) { return true; } } w->checkPhishingUrl(); return true; } else { return false; } } } bool EmbeddedImageURLHandler::handleDrag(const QUrl &url, ViewerPrivate *window) const { Q_UNUSED(url); Q_UNUSED(window); return false; } bool EmbeddedImageURLHandler::willHandleDrag(const QUrl &url, ViewerPrivate *window) const { Q_UNUSED(window); return url.scheme() == QLatin1String("cid"); } diff --git a/messageviewer/src/viewer/viewer_p.cpp b/messageviewer/src/viewer/viewer_p.cpp index 9c1c2ae7..712f937d 100644 --- a/messageviewer/src/viewer/viewer_p.cpp +++ b/messageviewer/src/viewer/viewer_p.cpp @@ -1,3323 +1,3328 @@ /* Copyright (c) 1997 Markus Wuebben Copyright (C) 2009 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net Copyright (c) 2009 Andras Mantia Copyright (c) 2010 Torgny Nyblom Copyright (C) 2011-2017 Laurent Montel This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "viewer_p.h" #include "viewer.h" #include "messageviewer_debug.h" #include "utils/mimetype.h" #include "viewer/objecttreeemptysource.h" #include "viewer/objecttreeviewersource.h" #include "messagedisplayformatattribute.h" #include "utils/iconnamecache.h" #include "scamdetection/scamdetectionwarningwidget.h" #include "scamdetection/scamattribute.h" #include "viewer/mimeparttree/mimeparttreeview.h" #include "widgets/openattachmentfolderwidget.h" #include "messageviewer/headerstyle.h" #include "messageviewer/headerstrategy.h" #include "kpimtextedit/slidecontainer.h" #include "Gravatar/GravatarCache" #include "gravatarsettings.h" #include "job/attachmenteditjob.h" #include "job/modifymessagedisplayformatjob.h" #include "config-messageviewer.h" #include "webengine/mailwebenginescript.h" #include "viewerplugins/viewerplugintoolmanager.h" #include #include "htmlwriter/webengineembedpart.h" #include #include // link() //KDE includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //Qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include //libkdepim #include "Libkdepim/BroadcastStatus" #include #include #include #include #include #include #include #include //own includes #include "widgets/attachmentdialog.h" #include "viewer/attachmentstrategy.h" #include "csshelper.h" #include "settings/messageviewersettings.h" #include "widgets/htmlstatusbar.h" #include "viewer/mimeparttree/mimetreemodel.h" #include #include #include "viewer/urlhandlermanager.h" #include "messageviewer/messageviewerutil.h" #include "utils/messageviewerutil_p.h" #include "widgets/vcardviewer.h" #include #include "viewer/webengine/mailwebengineview.h" #include "htmlwriter/webengineparthtmlwriter.h" #include #include #include "header/headerstylemenumanager.h" #include "widgets/submittedformwarningwidget.h" #include #include #include #include #include #include "MessageCore/MessageCoreSettings" #include #include #include #include #include #include #include #include #include #include
#include #include #include #include #include using namespace boost; using namespace MailTransport; using namespace MessageViewer; using namespace MessageCore; static QAtomicInt _k_attributeInitialized; template struct InvokeWrapper { R *receiver; void (C::*memberFun)(Arg); void operator()(Arg result) { (receiver->*memberFun)(result); } }; template InvokeWrapper invoke(R *receiver, void (C::*memberFun)(Arg)) { InvokeWrapper wrapper = {receiver, memberFun}; return wrapper; } ViewerPrivate::ViewerPrivate(Viewer *aParent, QWidget *mainWindow, KActionCollection *actionCollection) : QObject(aParent) , mNodeHelper(new MimeTreeParser::NodeHelper) , mViewer(nullptr) , mFindBar(nullptr) , mAttachmentStrategy(nullptr) , mUpdateReaderWinTimer(nullptr) , mResizeTimer(nullptr) , mOldGlobalOverrideEncoding(QStringLiteral("---")) , mMsgDisplay(true) , // init with dummy value mCSSHelper(nullptr) , mMainWindow(mainWindow) , mActionCollection(actionCollection) , mCopyAction(nullptr) , mCopyURLAction(nullptr) , mUrlOpenAction(nullptr) , mSelectAllAction(nullptr) , mScrollUpAction(nullptr) , mScrollDownAction(nullptr) , mScrollUpMoreAction(nullptr) , mScrollDownMoreAction(nullptr) , mHeaderOnlyAttachmentsAction(nullptr) , mSelectEncodingAction(nullptr) , mToggleFixFontAction(nullptr) , mToggleDisplayModeAction(nullptr) , mToggleMimePartTreeAction(nullptr) , mSpeakTextAction(nullptr) , mCanStartDrag(false) , mHtmlWriter(nullptr) , mDecrytMessageOverwrite(false) , mShowSignatureDetails(false) , mShowAttachmentQuicklist(true) , mForceEmoticons(true) , mRecursionCountForDisplayMessage(0) , mCurrentContent(nullptr) , mMessagePartNode(nullptr) , q(aParent) , mSession(new Akonadi::Session("MessageViewer-" + QByteArray::number(reinterpret_cast(this)), this)) , mPreviouslyViewedItem(-1) , mScamDetectionWarning(nullptr) , mOpenAttachmentFolderWidget(nullptr) , mSliderContainer(nullptr) , mShareServiceManager(nullptr) , mHeaderStylePlugin(nullptr) , mHeaderStyleMenuManager(nullptr) , mViewerPluginToolManager(nullptr) , mZoomActionMenu(nullptr) , mCurrentPrinter(nullptr) , mPhishingDatabase(nullptr) { mMimePartTree = nullptr; if (!mainWindow) { mMainWindow = aParent; } if (_k_attributeInitialized.testAndSetAcquire(0, 1)) { Akonadi::AttributeFactory::registerAttribute(); Akonadi::AttributeFactory::registerAttribute(); } mPhishingDatabase = new WebEngineViewer::LocalDataBaseManager(this); mPhishingDatabase->initialize(); connect(mPhishingDatabase, &WebEngineViewer::LocalDataBaseManager::checkUrlFinished, this, &ViewerPrivate::slotCheckedUrlFinished); mShareServiceManager = new PimCommon::ShareServiceUrlManager(this); mDisplayFormatMessageOverwrite = MessageViewer::Viewer::UseGlobalSetting; mHtmlLoadExtOverride = false; mHtmlLoadExternalGlobalSetting = false; mHtmlMailGlobalSetting = false; mUpdateReaderWinTimer.setObjectName(QStringLiteral("mUpdateReaderWinTimer")); mResizeTimer.setObjectName(QStringLiteral("mResizeTimer")); mPrinting = false; createWidgets(); createActions(); initHtmlWidget(); readConfig(); mLevelQuote = MessageViewer::MessageViewerSettings::self()->collapseQuoteLevelSpin() - 1; mResizeTimer.setSingleShot(true); connect(&mResizeTimer, &QTimer::timeout, this, &ViewerPrivate::slotDelayedResize); mUpdateReaderWinTimer.setSingleShot(true); connect(&mUpdateReaderWinTimer, &QTimer::timeout, this, &ViewerPrivate::updateReaderWin); connect(mNodeHelper, &MimeTreeParser::NodeHelper::update, this, &ViewerPrivate::update); connect(mColorBar, &HtmlStatusBar::clicked, this, &ViewerPrivate::slotToggleHtmlMode); // FIXME: Don't use the full payload here when attachment loading on demand is used, just // like in KMMainWidget::slotMessageActivated(). mMonitor.setObjectName(QStringLiteral("MessageViewerMonitor")); mMonitor.setSession(mSession); Akonadi::ItemFetchScope fs; fs.fetchFullPayload(); fs.fetchAttribute(); fs.fetchAttribute(); fs.fetchAttribute(); mMonitor.setItemFetchScope(fs); connect(&mMonitor, &Akonadi::Monitor::itemChanged, this, &ViewerPrivate::slotItemChanged); connect(&mMonitor, &Akonadi::Monitor::itemRemoved, this, &ViewerPrivate::slotClear); connect(&mMonitor, &Akonadi::Monitor::itemMoved, this, &ViewerPrivate::slotItemMoved); } ViewerPrivate::~ViewerPrivate() { MessageViewer::MessageViewerSettings::self()->save(); delete mHtmlWriter; mHtmlWriter = nullptr; delete mViewer; mViewer = nullptr; delete mCSSHelper; mNodeHelper->forceCleanTempFiles(); qDeleteAll(mListMailSourceViewer); delete mNodeHelper; } //----------------------------------------------------------------------------- KMime::Content *ViewerPrivate::nodeFromUrl(const QUrl &url) const { return mNodeHelper->fromHREF(mMessage, url); } void ViewerPrivate::openAttachment(KMime::Content *node, const QUrl &url) { if (!node) { return; } if (node->contentType(false)) { if (node->contentType()->mimeType() == "text/x-moz-deleted") { return; } if (node->contentType()->mimeType() == "message/external-body") { if (node->contentType()->hasParameter(QStringLiteral("url"))) { KRun::RunFlags flags; flags |= KRun::RunExecutables; const QString url = node->contentType()->parameter(QStringLiteral("url")); KRun::runUrl(QUrl(url), QStringLiteral("text/html"), q, flags); return; } } } const bool isEncapsulatedMessage = node->parent() && node->parent()->bodyIsMessage(); if (isEncapsulatedMessage) { // the viewer/urlhandlermanager expects that the message (mMessage) it is passed is the root when doing index calculation // in urls. Simply passing the result of bodyAsMessage() does not cut it as the resulting pointer is a child in its tree. KMime::Message::Ptr m = KMime::Message::Ptr(new KMime::Message); m->setContent(node->parent()->bodyAsMessage()->encodedContent()); m->parse(); atmViewMsg(m); return; } // determine the MIME type of the attachment // prefer the value of the Content-Type header QMimeDatabase mimeDb; auto mimetype = mimeDb.mimeTypeForName(QString::fromLatin1(node->contentType()->mimeType().toLower())); if (mimetype.isValid() && mimetype.inherits(KContacts::Addressee::mimeType())) { showVCard(node); return; } // special case treatment on mac and windows QUrl atmUrl = url; if (url.isEmpty()) { atmUrl = mNodeHelper->tempFileUrlFromNode(node); } if (Util::handleUrlWithQDesktopServices(atmUrl)) { return; } if (!mimetype.isValid() || mimetype.name() == QLatin1String("application/octet-stream")) { mimetype = MimeTreeParser::Util::mimetype( url.isLocalFile() ? url.toLocalFile() : url.fileName()); } KService::Ptr offer = KMimeTypeTrader::self()->preferredService(mimetype.name(), QStringLiteral("Application")); const QString filenameText = MimeTreeParser::NodeHelper::fileName(node); QPointer dialog = new AttachmentDialog(mMainWindow, filenameText, offer, QLatin1String( "askSave_") + mimetype.name()); const int choice = dialog->exec(); delete dialog; if (choice == AttachmentDialog::Save) { QUrl currentUrl; if (Util::saveContents(mMainWindow, KMime::Content::List() << node, currentUrl)) { showOpenAttachmentFolderWidget(currentUrl); } } else if (choice == AttachmentDialog::Open) { // Open if (offer) { attachmentOpenWith(node, offer); } else { attachmentOpen(node); } } else if (choice == AttachmentDialog::OpenWith) { attachmentOpenWith(node); } else { // Cancel qCDebug(MESSAGEVIEWER_LOG) << "Canceled opening attachment"; } } bool ViewerPrivate::deleteAttachment(KMime::Content *node, bool showWarning) { if (!node) { return true; } KMime::Content *parent = node->parent(); if (!parent) { return true; } QList extraNodes = mNodeHelper->extraContents(mMessage.data()); if (extraNodes.contains(node->topLevel())) { KMessageBox::error(mMainWindow, i18n( "Deleting an attachment from an encrypted or old-style mailman message is not supported."), i18n("Delete Attachment")); return true; //cancelled } if (showWarning && KMessageBox::warningContinueCancel(mMainWindow, i18n( "Deleting an attachment might invalidate any digital signature on this message."), i18n("Delete Attachment"), KStandardGuiItem::del(), KStandardGuiItem::cancel(), QStringLiteral( "DeleteAttachmentSignatureWarning")) != KMessageBox::Continue) { return false; //cancelled } //don't confuse the model #ifndef QT_NO_TREEVIEW mMimePartTree->clearModel(); #endif QString filename; QString name; QByteArray mimetype; if (node->contentDisposition(false)) { filename = node->contentDisposition()->filename(); } if (node->contentType(false)) { name = node->contentType()->name(); mimetype = node->contentType()->mimeType(); } // text/plain part: KMime::Content *deletePart = new KMime::Content(parent); deletePart->contentType()->setMimeType("text/x-moz-deleted"); deletePart->contentType()->setName(QStringLiteral("Deleted: %1").arg(name), "utf8"); deletePart->contentDisposition()->setDisposition(KMime::Headers::CDattachment); deletePart->contentDisposition()->setFilename(QStringLiteral("Deleted: %1").arg(name)); deletePart->contentType()->setCharset("utf-8"); deletePart->contentTransferEncoding()->setEncoding(KMime::Headers::CE7Bit); QByteArray bodyMessage = QByteArrayLiteral( "\nYou deleted an attachment from this message. The original MIME headers for the attachment were:"); bodyMessage += ("\nContent-Type: ") + mimetype; bodyMessage += ("\nname=\"") + name.toUtf8() + "\""; bodyMessage += ("\nfilename=\"") + filename.toUtf8() + "\""; deletePart->setBody(bodyMessage); parent->replaceContent(node, deletePart); parent->assemble(); KMime::Message *modifiedMessage = mNodeHelper->messageWithExtraContent(mMessage.data()); #ifndef QT_NO_TREEVIEW mMimePartTree->mimePartModel()->setRoot(modifiedMessage); #endif mMessageItem.setPayloadFromData(modifiedMessage->encodedContent()); Akonadi::ItemModifyJob *job = new Akonadi::ItemModifyJob(mMessageItem, mSession); job->disableRevisionCheck(); connect(job, &KJob::result, this, &ViewerPrivate::itemModifiedResult); return true; } void ViewerPrivate::itemModifiedResult(KJob *job) { if (job->error()) { qCDebug(MESSAGEVIEWER_LOG) << "Item update failed:" << job->errorString(); } else { setMessageItem(mMessageItem, MimeTreeParser::Force); } } void ViewerPrivate::editAttachment(KMime::Content *node, bool showWarning) { MessageViewer::AttachmentEditJob *job = new MessageViewer::AttachmentEditJob(mSession, this); connect(job, &AttachmentEditJob::refreshMessage, this, &ViewerPrivate::slotRefreshMessage); job->setMainWindow(mMainWindow); job->setMessageItem(mMessageItem); job->setMessage(mMessage); job->addAttachment(node, showWarning); job->canDeleteJob(); } void ViewerPrivate::scrollToAnchor(const QString &anchor) { mViewer->scrollToAnchor(anchor); } void ViewerPrivate::createOpenWithMenu(QMenu *topMenu, const QString &contentTypeStr, bool fromCurrentContent) { const KService::List offers = KFileItemActions::associatedApplications( QStringList() << contentTypeStr, QString()); if (!offers.isEmpty()) { QMenu *menu = topMenu; QActionGroup *actionGroup = new QActionGroup(menu); if (fromCurrentContent) { connect(actionGroup, &QActionGroup::triggered, this, &ViewerPrivate::slotOpenWithActionCurrentContent); } else { connect(actionGroup, &QActionGroup::triggered, this, &ViewerPrivate::slotOpenWithAction); } if (offers.count() > 1) { // submenu 'open with' menu = new QMenu(i18nc("@title:menu", "&Open With"), topMenu); menu->menuAction()->setObjectName(QStringLiteral("openWith_submenu")); // for the unittest topMenu->addMenu(menu); } //qCDebug(MESSAGEVIEWER_LOG) << offers.count() << "offers" << topMenu << menu; KService::List::ConstIterator it = offers.constBegin(); KService::List::ConstIterator end = offers.constEnd(); for (; it != end; ++it) { QAction *act = MessageViewer::Util::createAppAction(*it, // no submenu -> prefix single offer menu == topMenu, actionGroup, menu); menu->addAction(act); } QString openWithActionName; if (menu != topMenu) { // submenu menu->addSeparator(); openWithActionName = i18nc("@action:inmenu Open With", "&Other..."); } else { openWithActionName = i18nc("@title:menu", "&Open With..."); } QAction *openWithAct = new QAction(menu); openWithAct->setText(openWithActionName); if (fromCurrentContent) { connect(openWithAct, &QAction::triggered, this, &ViewerPrivate::slotOpenWithDialogCurrentContent); } else { connect(openWithAct, &QAction::triggered, this, &ViewerPrivate::slotOpenWithDialog); } menu->addAction(openWithAct); } else { // no app offers -> Open With... QAction *act = new QAction(topMenu); act->setText(i18nc("@title:menu", "&Open With...")); if (fromCurrentContent) { connect(act, &QAction::triggered, this, &ViewerPrivate::slotOpenWithDialogCurrentContent); } else { connect(act, &QAction::triggered, this, &ViewerPrivate::slotOpenWithDialog); } topMenu->addAction(act); } } void ViewerPrivate::slotOpenWithDialogCurrentContent() { if (!mCurrentContent) { return; } attachmentOpenWith(mCurrentContent); } void ViewerPrivate::slotOpenWithDialog() { auto contents = selectedContents(); if (contents.count() == 1) { attachmentOpenWith(contents.first()); } } void ViewerPrivate::slotOpenWithActionCurrentContent(QAction *act) { if (!mCurrentContent) { return; } KService::Ptr app = act->data().value(); attachmentOpenWith(mCurrentContent, app); } void ViewerPrivate::slotOpenWithAction(QAction *act) { KService::Ptr app = act->data().value(); auto contents = selectedContents(); if (contents.count() == 1) { attachmentOpenWith(contents.first(), app); } } void ViewerPrivate::showAttachmentPopup(KMime::Content *node, const QString &name, const QPoint &globalPos) { Q_UNUSED(name); prepareHandleAttachment(node); QMenu *menu = new QMenu(); bool deletedAttachment = false; if (node->contentType(false)) { deletedAttachment = (node->contentType()->mimeType() == "text/x-moz-deleted"); } const QString contentTypeStr = QLatin1String(node->contentType()->mimeType()); QSignalMapper *attachmentMapper = new QSignalMapper(menu); connect(attachmentMapper, SIGNAL(mapped(int)), this, SLOT(slotHandleAttachment(int))); QAction *action = menu->addAction(QIcon::fromTheme(QStringLiteral("document-open")), i18nc("to open", "Open")); action->setEnabled(!deletedAttachment); connect(action, SIGNAL(triggered(bool)), attachmentMapper, SLOT(map())); attachmentMapper->setMapping(action, Viewer::Open); if (!deletedAttachment) { createOpenWithMenu(menu, contentTypeStr, true); } QMimeDatabase mimeDb; auto mimetype = mimeDb.mimeTypeForName(contentTypeStr); if (mimetype.isValid()) { const QStringList parentMimeType = mimetype.parentMimeTypes(); if ((contentTypeStr == QLatin1String("text/plain")) || (contentTypeStr == QLatin1String("image/png")) || (contentTypeStr == QLatin1String("image/jpeg")) || parentMimeType.contains(QStringLiteral("text/plain")) || parentMimeType.contains(QStringLiteral("image/png")) || parentMimeType.contains(QStringLiteral("image/jpeg")) ) { action = menu->addAction(i18nc("to view something", "View")); action->setEnabled(!deletedAttachment); connect(action, SIGNAL(triggered(bool)), attachmentMapper, SLOT(map())); attachmentMapper->setMapping(action, Viewer::View); } } #if 0 //Reimplement in the future const bool attachmentInHeader = mViewer->isAttachmentInjectionPoint(globalPos); const bool hasScrollbar = mViewer->hasVerticalScrollBar(); if (attachmentInHeader && hasScrollbar) { action = menu->addAction(i18n("Scroll To")); connect(action, SIGNAL(triggered(bool)), attachmentMapper, SLOT(map())); attachmentMapper->setMapping(action, Viewer::ScrollTo); } #endif action = menu->addAction(QIcon::fromTheme(QStringLiteral("document-save-as")), i18n( "Save As...")); action->setEnabled(!deletedAttachment); connect(action, SIGNAL(triggered(bool)), attachmentMapper, SLOT(map())); attachmentMapper->setMapping(action, Viewer::Save); action = menu->addAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18n("Copy")); action->setEnabled(!deletedAttachment); connect(action, SIGNAL(triggered(bool)), attachmentMapper, SLOT(map())); attachmentMapper->setMapping(action, Viewer::Copy); const bool isEncapsulatedMessage = node->parent() && node->parent()->bodyIsMessage(); const bool canChange = mMessageItem.isValid() && mMessageItem.parentCollection().isValid() && (mMessageItem.parentCollection().rights() != Akonadi::Collection::ReadOnly) && !isEncapsulatedMessage; if (MessageViewer::MessageViewerSettings::self()->allowAttachmentEditing()) { action = menu->addAction(QIcon::fromTheme(QStringLiteral("document-properties")), i18n( "Edit Attachment")); connect(action, SIGNAL(triggered()), attachmentMapper, SLOT(map())); attachmentMapper->setMapping(action, Viewer::Edit); action->setEnabled(canChange); } action = menu->addAction(QIcon::fromTheme(QStringLiteral("edit-delete")), i18n("Delete Attachment")); connect(action, SIGNAL(triggered()), attachmentMapper, SLOT(map())); attachmentMapper->setMapping(action, Viewer::Delete); action->setEnabled(canChange && !deletedAttachment); #if 0 menu->addSeparator(); action = menu->addAction(QIcon::fromTheme(QStringLiteral("mail-reply-sender")), i18n("Reply To Author")); connect(action, SIGNAL(triggered()), attachmentMapper, SLOT(map())); attachmentMapper->setMapping(action, Viewer::ReplyMessageToAuthor); menu->addSeparator(); action = menu->addAction(QIcon::fromTheme(QStringLiteral("mail-reply-all")), i18n( "Reply To All")); connect(action, SIGNAL(triggered()), attachmentMapper, SLOT(map())); attachmentMapper->setMapping(action, Viewer::ReplyMessageToAll); #endif menu->addSeparator(); action = menu->addAction(i18n("Properties")); connect(action, SIGNAL(triggered(bool)), attachmentMapper, SLOT(map())); attachmentMapper->setMapping(action, Viewer::Properties); menu->exec(globalPos); delete menu; } void ViewerPrivate::prepareHandleAttachment(KMime::Content *node) { mCurrentContent = node; } QString ViewerPrivate::createAtmFileLink(const QString &atmFileName) const { QFileInfo atmFileInfo(atmFileName); // tempfile name is /TMP/attachmentsRANDOM/atmFileInfo.fileName()" const QString tmpPath = QDir::tempPath() + QLatin1Char('/') + QLatin1String("attachments"); QDir().mkpath(tmpPath); QTemporaryDir *linkDir = new QTemporaryDir(tmpPath); QString linkPath = linkDir->path() + QLatin1Char('/') + atmFileInfo.fileName(); QFile *linkFile = new QFile(linkPath); linkFile->open(QIODevice::ReadWrite); const QString linkName = linkFile->fileName(); delete linkFile; delete linkDir; if (::link(QFile::encodeName(atmFileName).constData(), QFile::encodeName(linkName).constData()) == 0) { return linkName; // success } return QString(); } KService::Ptr ViewerPrivate::getServiceOffer(KMime::Content *content) { const QString fileName = mNodeHelper->writeNodeToTempFile(content); const QString contentTypeStr = QLatin1String(content->contentType()->mimeType()); // determine the MIME type of the attachment // prefer the value of the Content-Type header QMimeDatabase mimeDb; auto mimetype = mimeDb.mimeTypeForName(contentTypeStr); if (mimetype.isValid() && mimetype.inherits(KContacts::Addressee::mimeType())) { attachmentView(content); return KService::Ptr(nullptr); } if (!mimetype.isValid() || mimetype.name() == QLatin1String("application/octet-stream")) { /*TODO(Andris) port when on-demand loading is done && msgPart.isComplete() */ mimetype = MimeTreeParser::Util::mimetype(fileName); } return KMimeTypeTrader::self()->preferredService(mimetype.name(), QStringLiteral("Application")); } KMime::Content::List ViewerPrivate::selectedContents() { return mMimePartTree->selectedContents(); } void ViewerPrivate::attachmentOpenWith(KMime::Content *node, const KService::Ptr &offer) { QString name = mNodeHelper->writeNodeToTempFile(node); // Make sure that it will not deleted when we switch from message. QTemporaryDir *tmpDir = new QTemporaryDir(QDir::tempPath() + QLatin1String("/messageviewer_attachment_XXXXXX")); if (tmpDir->isValid()) { tmpDir->setAutoRemove(false); const QString path = tmpDir->path(); delete tmpDir; QFile f(name); const QUrl tmpFileName = QUrl::fromLocalFile(name); const QString newPath = path + QLatin1Char('/') + tmpFileName.fileName(); if (!f.copy(newPath)) { qCDebug(MESSAGEVIEWER_LOG) << " File was not able to copy: filename: " << name << " to " << path; } else { name = newPath; } f.close(); } else { delete tmpDir; } QList lst; const QFileDevice::Permissions perms = QFile::permissions(name); QFile::setPermissions(name, perms | QFileDevice::ReadUser | QFileDevice::WriteUser); const QUrl url = QUrl::fromLocalFile(name); lst.append(url); if (offer) { if ((!KRun::runService(*offer, lst, nullptr, true))) { QFile::remove(url.toLocalFile()); } } else { if ((!KRun::displayOpenWithDialog(lst, mMainWindow, true))) { QFile::remove(url.toLocalFile()); } } } void ViewerPrivate::attachmentOpen(KMime::Content *node) { KService::Ptr offer = getServiceOffer(node); if (!offer) { qCDebug(MESSAGEVIEWER_LOG) << "got no offer"; return; } attachmentOpenWith(node, offer); } bool ViewerPrivate::showEmoticons() const { return mForceEmoticons; } MimeTreeParser::HtmlWriter *ViewerPrivate::htmlWriter() const { return mHtmlWriter; } CSSHelper *ViewerPrivate::cssHelper() const { return mCSSHelper; } MimeTreeParser::NodeHelper *ViewerPrivate::nodeHelper() const { return mNodeHelper; } Viewer *ViewerPrivate::viewer() const { return q; } Akonadi::Item ViewerPrivate::messageItem() const { return mMessageItem; } KMime::Message::Ptr ViewerPrivate::message() const { return mMessage; } bool ViewerPrivate::decryptMessage() const { if (!MessageViewer::MessageViewerSettings::self()->alwaysDecrypt()) { return mDecrytMessageOverwrite; } else { return true; } } void ViewerPrivate::displaySplashPage(const QString &message) { displaySplashPage(QStringLiteral("status.html"), { { QStringLiteral("icon"), QStringLiteral("kmail") }, { QStringLiteral("name"), i18n("KMail") }, { QStringLiteral("subtitle"), i18n("The KDE Mail Client") }, { QStringLiteral("message"), message } }); } void ViewerPrivate::displaySplashPage(const QString &templateName, const QVariantHash &data, const QByteArray &domain) { mMsgDisplay = false; adjustLayout(); GrantleeTheme::ThemeManager manager(QStringLiteral("splashPage"), QStringLiteral("splash.theme"), nullptr, QStringLiteral("messageviewer/about/")); GrantleeTheme::Theme theme = manager.theme(QStringLiteral("default")); if (theme.isValid()) { mViewer->setHtml(theme.render(templateName, data, domain), QUrl::fromLocalFile(theme.absolutePath() + QLatin1Char('/'))); } else { qCDebug(MESSAGEVIEWER_LOG) << "Theme error: failed to find splash theme"; } mViewer->show(); } void ViewerPrivate::enableMessageDisplay() { if (mMsgDisplay) { return; } mMsgDisplay = true; adjustLayout(); } void ViewerPrivate::displayMessage() { showHideMimeTree(); mNodeHelper->setOverrideCodec(mMessage.data(), overrideCodec()); if (mMessageItem.hasAttribute()) { const MessageViewer::MessageDisplayFormatAttribute *const attr = mMessageItem.attribute(); setHtmlLoadExtOverride(attr->remoteContent()); setDisplayFormatMessageOverwrite(attr->messageFormat()); } htmlWriter()->begin(); htmlWriter()->write(mCSSHelper->htmlHead(mUseFixedFont)); if (!mMainWindow) { q->setWindowTitle(mMessage->subject()->asUnicodeString()); } // Don't update here, parseMsg() can overwrite the HTML mode, which would lead to flicker. // It is updated right after parseMsg() instead. mColorBar->setMode(MimeTreeParser::Util::Normal, HtmlStatusBar::NoUpdate); if (mMessageItem.hasAttribute()) { //TODO: Insert link to clear error so that message might be resent const ErrorAttribute *const attr = mMessageItem.attribute(); Q_ASSERT(attr); if (!mForegroundError.isValid()) { const KColorScheme scheme = KColorScheme(QPalette::Active, KColorScheme::View); mForegroundError = scheme.foreground(KColorScheme::NegativeText).color(); mBackgroundError = scheme.background(KColorScheme::NegativeBackground).color(); } htmlWriter()->write(QStringLiteral( "
%4
").arg( mBackgroundError. name(), mForegroundError . name(), mForegroundError . name(), attr->message().toHtmlEscaped())); htmlWriter()->write(QStringLiteral("

")); } parseContent(mMessage.data()); #ifndef QT_NO_TREEVIEW mMimePartTree->setRoot(mNodeHelper->messageWithExtraContent(mMessage.data())); #endif mColorBar->update(); htmlWriter()->write(QStringLiteral("")); connect(mViewer, &MailWebEngineView::loadFinished, this, &ViewerPrivate::executeCustomScriptsAfterLoading, Qt::UniqueConnection); connect( mPartHtmlWriter.data(), &WebEnginePartHtmlWriter::finished, this, &ViewerPrivate::slotMessageRendered, Qt::UniqueConnection); const QString html = attachmentInjectionHtml(); const QString js = html.isEmpty() ? QString() : MessageViewer::MailWebEngineScript::injectAttachments(html, QStringLiteral( "attachmentInjectionPoint")); mViewer->addScript(js, QStringLiteral("attachment_injection"), QWebEngineScript::DocumentReady); htmlWriter()->end(); } void ViewerPrivate::collectionFetchedForStoringDecryptedMessage(KJob *job) { if (job->error()) { return; } Akonadi::Collection col; const Akonadi::Collection::List lstCol = static_cast(job)->collections(); for (const Akonadi::Collection &c : lstCol) { if (c == mMessageItem.parentCollection()) { col = c; break; } } if (!col.isValid()) { return; } const Akonadi::AgentInstance::List instances = Akonadi::AgentManager::self()->instances(); const QString itemResource = col.resource(); Akonadi::AgentInstance resourceInstance; for (const Akonadi::AgentInstance &instance : instances) { if (instance.identifier() == itemResource) { resourceInstance = instance; break; } } bool isInOutbox = true; Akonadi::Collection outboxCollection = Akonadi::SpecialMailCollections::self()->collection( Akonadi::SpecialMailCollections::Outbox, resourceInstance); if (resourceInstance.isValid() && outboxCollection != col) { isInOutbox = false; } if (!isInOutbox) { KMime::Message::Ptr unencryptedMessage = mNodeHelper->unencryptedMessage(mMessage); if (unencryptedMessage) { mMessageItem.setPayload(unencryptedMessage); Akonadi::ItemModifyJob *job = new Akonadi::ItemModifyJob(mMessageItem, mSession); connect(job, &KJob::result, this, &ViewerPrivate::itemModifiedResult); } } } void ViewerPrivate::postProcessMessage(MimeTreeParser::ObjectTreeParser *otp, MimeTreeParser::KMMsgEncryptionState encryptionState) { if (MessageViewer::MessageViewerSettings::self()->storeDisplayedMessagesUnencrypted()) { // Hack to make sure the S/MIME CryptPlugs follows the strict requirement // of german government: // --> All received encrypted messages *must* be stored in unencrypted form // after they have been decrypted once the user has read them. // ( "Aufhebung der Verschluesselung nach dem Lesen" ) // // note: Since there is no configuration option for this, we do that for // all kinds of encryption now - *not* just for S/MIME. // This could be changed in the objectTreeToDecryptedMsg() function // by deciding when (or when not, resp.) to set the 'dataNode' to // something different than 'curNode'. const bool messageAtLeastPartiallyEncrypted = (MimeTreeParser::KMMsgFullyEncrypted == encryptionState) || (MimeTreeParser::KMMsgPartiallyEncrypted == encryptionState); // only proceed if we were called the normal way - not by // double click on the message (==not running in a separate window) if (decryptMessage() // only proceed if the message has actually been decrypted && !otp->hasPendingAsyncJobs() // only proceed if no pending async jobs are running: && messageAtLeastPartiallyEncrypted) { //check if the message is in the outbox folder //FIXME: using root() is too much, but using mMessageItem.parentCollection() returns no collections in job->collections() //FIXME: this is done async, which means it is possible that the user selects another message while // this job is running. In that case, collectionFetchedForStoringDecryptedMessage() will work // on the wrong item! Akonadi::CollectionFetchJob *job = new Akonadi::CollectionFetchJob( Akonadi::Collection::root(), Akonadi::CollectionFetchJob::Recursive, mSession); connect(job, &KJob::result, this, &ViewerPrivate::collectionFetchedForStoringDecryptedMessage); } } } void ViewerPrivate::parseContent(KMime::Content *content) { assert(content != nullptr); // Check if any part of this message is a v-card // v-cards can be either text/x-vcard or text/directory, so we need to check // both. KMime::Content *vCardContent = findContentByType(content, "text/x-vcard"); if (!vCardContent) { vCardContent = findContentByType(content, "text/directory"); } bool hasVCard = false; if (vCardContent) { // ### FIXME: We should only do this if the vCard belongs to the sender, // ### i.e. if the sender's email address is contained in the vCard. const QByteArray vCard = vCardContent->decodedContent(); KContacts::VCardConverter t; if (!t.parseVCards(vCard).isEmpty()) { hasVCard = true; mNodeHelper->writeNodeToTempFile(vCardContent); } } KMime::Message *message = dynamic_cast(content); if (message) { htmlWriter()->write(writeMsgHeader(message, hasVCard ? vCardContent : nullptr, true)); } // Pass control to the OTP now, which does the real work mNodeHelper->removeTempFiles(); mNodeHelper->setNodeUnprocessed(mMessage.data(), true); MailViewerSource otpSource(this); MimeTreeParser::ObjectTreeParser otp(&otpSource, mNodeHelper, mMessage.data() != content /* show only single node */); otp.setAllowAsync(!mPrinting); otp.parseObjectTree(content); // TODO: Setting the signature state to nodehelper is not enough, it should actually // be added to the store, so that the message list correctly displays the signature state // of messages that were parsed at least once // store encrypted/signed status information in the KMMessage // - this can only be done *after* calling parseObjectTree() MimeTreeParser::KMMsgEncryptionState encryptionState = mNodeHelper->overallEncryptionState( content); MimeTreeParser::KMMsgSignatureState signatureState = mNodeHelper->overallSignatureState(content); mNodeHelper->setEncryptionState(content, encryptionState); // Don't reset the signature state to "not signed" (e.g. if one canceled the // decryption of a signed messages which has already been decrypted before). if (signatureState != MimeTreeParser::KMMsgNotSigned || mNodeHelper->signatureState(content) == MimeTreeParser::KMMsgSignatureStateUnknown) { mNodeHelper->setSignatureState(content, signatureState); } postProcessMessage(&otp, encryptionState); showHideMimeTree(); } QString ViewerPrivate::writeMsgHeader(KMime::Message *aMsg, KMime::Content *vCardNode, bool topLevel) { if (!headerStylePlugin()) { qCCritical(MESSAGEVIEWER_LOG) << "trying to writeMsgHeader() without a header style set!"; return {}; } QString href; if (vCardNode) { href = mNodeHelper->asHREF(vCardNode, QStringLiteral("body")); } headerStylePlugin()->headerStyle()->setHeaderStrategy(headerStylePlugin()->headerStrategy()); headerStylePlugin()->headerStyle()->setVCardName(href); headerStylePlugin()->headerStyle()->setPrinting(mPrinting); headerStylePlugin()->headerStyle()->setTopLevel(topLevel); headerStylePlugin()->headerStyle()->setAllowAsync(true); headerStylePlugin()->headerStyle()->setSourceObject(this); headerStylePlugin()->headerStyle()->setNodeHelper(mNodeHelper); headerStylePlugin()->headerStyle()->setMessagePath(mMessagePath); if (mMessageItem.isValid()) { Akonadi::MessageStatus status; status.setStatusFromFlags(mMessageItem.flags()); headerStylePlugin()->headerStyle()->setMessageStatus(status); headerStylePlugin()->headerStyle()->setCollectionName( mMessageItem.parentCollection().displayName()); } else { headerStylePlugin()->headerStyle()->setCollectionName(QString()); headerStylePlugin()->headerStyle()->setReadOnlyMessage(true); } return headerStylePlugin()->headerStyle()->format(aMsg); } void ViewerPrivate::showVCard(KMime::Content *msgPart) { const QByteArray vCard = msgPart->decodedContent(); VCardViewer *vcv = new VCardViewer(mMainWindow, vCard); vcv->setAttribute(Qt::WA_DeleteOnClose); vcv->show(); } void ViewerPrivate::initHtmlWidget() { if (!htmlWriter()) { mPartHtmlWriter = new WebEnginePartHtmlWriter(mViewer, nullptr); mHtmlWriter = mPartHtmlWriter; } connect(mViewer->page(), &QWebEnginePage::linkHovered, this, &ViewerPrivate::slotUrlOn); connect(mViewer, &MailWebEngineView::openUrl, this, &ViewerPrivate::slotUrlOpen, Qt::QueuedConnection); connect(mViewer, &MailWebEngineView::popupMenu, this, &ViewerPrivate::slotUrlPopup); connect(mViewer, &MailWebEngineView::wheelZoomChanged, this, &ViewerPrivate::slotWheelZoomChanged); connect(mViewer, &MailWebEngineView::messageMayBeAScam, this, &ViewerPrivate::slotMessageMayBeAScam); connect(mViewer, &MailWebEngineView::formSubmittedForbidden, this, &ViewerPrivate::slotFormSubmittedForbidden); connect(mScamDetectionWarning, &ScamDetectionWarningWidget::showDetails, mViewer, &MailWebEngineView::slotShowDetails); connect(mScamDetectionWarning, &ScamDetectionWarningWidget::moveMessageToTrash, this, &ViewerPrivate::moveMessageToTrash); connect(mScamDetectionWarning, &ScamDetectionWarningWidget::messageIsNotAScam, this, &ViewerPrivate::slotMessageIsNotAScam); connect(mScamDetectionWarning, &ScamDetectionWarningWidget::addToWhiteList, this, &ViewerPrivate::slotAddToWhiteList); connect(mViewer, &MailWebEngineView::pageIsScrolledToBottom, this, &ViewerPrivate::pageIsScrolledToBottom); } void ViewerPrivate::slotWheelZoomChanged(int numSteps) { if (mZoomActionMenu) { const qreal factor = mZoomActionMenu->zoomFactor() + numSteps * 10; if (factor >= 10 && factor <= 300) { mZoomActionMenu->setZoomFactor(factor); mZoomActionMenu->setWebViewerZoomFactor(factor / 100.0); } } } void ViewerPrivate::readConfig() { delete mCSSHelper; mCSSHelper = new CSSHelper(mViewer); mUseFixedFont = MessageViewer::MessageViewerSettings::self()->useFixedFont(); if (mToggleFixFontAction) { mToggleFixFontAction->setChecked(mUseFixedFont); } mHtmlMailGlobalSetting = MessageViewer::MessageViewerSettings::self()->htmlMail(); mHtmlLoadExternalGlobalSetting = MessageViewer::MessageViewerSettings::self()->htmlLoadExternal(); readGravatarConfig(); if (mHeaderStyleMenuManager) { mHeaderStyleMenuManager->readConfig(); } setAttachmentStrategy(MimeTreeParser::AttachmentStrategy::create(MessageViewer:: MessageViewerSettings::self()-> attachmentStrategy())); KToggleAction *raction = actionForAttachmentStrategy(attachmentStrategy()); if (raction) { raction->setChecked(true); } adjustLayout(); readGlobalOverrideCodec(); mViewer->settings()->setFontSize(QWebEngineSettings::MinimumFontSize, MessageViewer::MessageViewerSettings::self()->minimumFontSize()); mViewer->settings()->setFontSize(QWebEngineSettings::MinimumLogicalFontSize, MessageViewer::MessageViewerSettings::self()->minimumFontSize()); if (mMessage) { update(); } mColorBar->update(); } void ViewerPrivate::readGravatarConfig() { Gravatar::GravatarCache::self()->setMaximumSize( Gravatar::GravatarSettings::self()->gravatarCacheSize()); if (!Gravatar::GravatarSettings::self()->gravatarSupportEnabled()) { Gravatar::GravatarCache::self()->clear(); } } void ViewerPrivate::slotGeneralFontChanged() { delete mCSSHelper; mCSSHelper = new CSSHelper(mViewer); if (mMessage) { update(); } } void ViewerPrivate::writeConfig(bool sync) { MessageViewer::MessageViewerSettings::self()->setUseFixedFont(mUseFixedFont); if (attachmentStrategy()) { MessageViewer::MessageViewerSettings::self()->setAttachmentStrategy(QLatin1String( attachmentStrategy() ->name())); } saveSplitterSizes(); if (sync) { Q_EMIT requestConfigSync(); } } const MimeTreeParser::AttachmentStrategy *ViewerPrivate::attachmentStrategy() const { return mAttachmentStrategy; } void ViewerPrivate::setAttachmentStrategy(const MimeTreeParser::AttachmentStrategy *strategy) { if (mAttachmentStrategy == strategy) { return; } mAttachmentStrategy = strategy ? strategy : MimeTreeParser::AttachmentStrategy::smart(); update(MimeTreeParser::Force); } QString ViewerPrivate::overrideEncoding() const { return mOverrideEncoding; } void ViewerPrivate::setOverrideEncoding(const QString &encoding) { if (encoding == mOverrideEncoding) { return; } mOverrideEncoding = encoding; if (mSelectEncodingAction) { if (encoding.isEmpty()) { mSelectEncodingAction->setCurrentItem(0); } else { const QStringList encodings = mSelectEncodingAction->items(); int i = 0; for (QStringList::const_iterator it = encodings.constBegin(), end = encodings.constEnd(); it != end; ++it, ++i) { if (MimeTreeParser::NodeHelper::encodingForName(*it) == encoding) { mSelectEncodingAction->setCurrentItem(i); break; } } if (i == encodings.size()) { // the value of encoding is unknown => use Auto qCWarning(MESSAGEVIEWER_LOG) << "Unknown override character encoding" << encoding << ". Using Auto instead."; mSelectEncodingAction->setCurrentItem(0); mOverrideEncoding.clear(); } } } update(MimeTreeParser::Force); } void ViewerPrivate::setPrinting(bool enable) { mPrinting = enable; } bool ViewerPrivate::printingMode() const { return mPrinting; } void ViewerPrivate::printMessage(const Akonadi::Item &message) { disconnect( mPartHtmlWriter.data(), &WebEnginePartHtmlWriter::finished, this, &ViewerPrivate::slotPrintMessage); connect( mPartHtmlWriter.data(), &WebEnginePartHtmlWriter::finished, this, &ViewerPrivate::slotPrintMessage); setMessageItem(message, MimeTreeParser::Force); } void ViewerPrivate::printPreviewMessage(const Akonadi::Item &message) { disconnect( mPartHtmlWriter.data(), &WebEnginePartHtmlWriter::finished, this, &ViewerPrivate::slotPrintPreview); connect( mPartHtmlWriter.data(), &WebEnginePartHtmlWriter::finished, this, &ViewerPrivate::slotPrintPreview); setMessageItem(message, MimeTreeParser::Force); } void ViewerPrivate::resetStateForNewMessage() { mClickedUrl.clear(); mImageUrl.clear(); enableMessageDisplay(); // just to make sure it's on mMessage.reset(); mNodeHelper->clear(); mMessagePartNode = nullptr; #ifndef QT_NO_TREEVIEW mMimePartTree->clearModel(); #endif mViewer->clearRelativePosition(); mViewer->hideAccessKeys(); setShowSignatureDetails(false); mFindBar->closeBar(); mViewerPluginToolManager->closeAllTools(); mScamDetectionWarning->setVisible(false); mOpenAttachmentFolderWidget->setVisible(false); if (mPrinting) { if (MessageViewer::MessageViewerSettings::self()->respectExpandCollapseSettings()) { if (MessageViewer::MessageViewerSettings::self()->showExpandQuotesMark()) { mLevelQuote = MessageViewer::MessageViewerSettings::self()->collapseQuoteLevelSpin() - 1; } else { mLevelQuote = -1; } } else { mLevelQuote = -1; } } else { mDisplayFormatMessageOverwrite = (mDisplayFormatMessageOverwrite == MessageViewer::Viewer::UseGlobalSetting) ? MessageViewer::Viewer::UseGlobalSetting : MessageViewer::Viewer::Unknown; } } void ViewerPrivate::setMessageInternal(const KMime::Message::Ptr &message, MimeTreeParser::UpdateMode updateMode) { mViewerPluginToolManager->updateActions(mMessageItem); mMessage = message; if (message) { mNodeHelper->setOverrideCodec(mMessage.data(), overrideCodec()); } #ifndef QT_NO_TREEVIEW mMimePartTree->setRoot(mNodeHelper->messageWithExtraContent(message.data())); update(updateMode); #endif } void ViewerPrivate::setMessageItem(const Akonadi::Item &item, MimeTreeParser::UpdateMode updateMode) { resetStateForNewMessage(); foreach (const Akonadi::Item::Id monitoredId, mMonitor.itemsMonitoredEx()) { mMonitor.setItemMonitored(Akonadi::Item(monitoredId), false); } Q_ASSERT(mMonitor.itemsMonitoredEx().isEmpty()); mMessageItem = item; if (mMessageItem.isValid()) { mMonitor.setItemMonitored(mMessageItem, true); } if (!mMessageItem.hasPayload()) { if (mMessageItem.isValid()) { qCWarning(MESSAGEVIEWER_LOG) << "Payload is not a MessagePtr!"; } return; } setMessageInternal(mMessageItem.payload(), updateMode); } void ViewerPrivate::setMessage(const KMime::Message::Ptr &aMsg, MimeTreeParser::UpdateMode updateMode) { resetStateForNewMessage(); Akonadi::Item item; item.setMimeType(KMime::Message::mimeType()); item.setPayload(aMsg); mMessageItem = item; setMessageInternal(aMsg, updateMode); } void ViewerPrivate::setMessagePart(KMime::Content *node) { // Cancel scheduled updates of the reader window, as that would stop the // timer of the HTML writer, which would make viewing attachment not work // anymore as not all HTML is written to the HTML part. // We're updating the reader window here ourselves anyway. mUpdateReaderWinTimer.stop(); if (node) { mMessagePartNode = node; if (node->bodyIsMessage()) { mMainWindow->setWindowTitle(node->bodyAsMessage()->subject()->asUnicodeString()); } else { QString windowTitle = MimeTreeParser::NodeHelper::fileName(node); if (windowTitle.isEmpty()) { windowTitle = node->contentDescription()->asUnicodeString(); } if (!windowTitle.isEmpty()) { mMainWindow->setWindowTitle(i18n("View Attachment: %1", windowTitle)); } } htmlWriter()->begin(); htmlWriter()->write(mCSSHelper->htmlHead(mUseFixedFont)); parseContent(node); htmlWriter()->write(QStringLiteral("")); htmlWriter()->end(); } } void ViewerPrivate::showHideMimeTree() { #ifndef QT_NO_TREEVIEW if (mimePartTreeIsEmpty()) { mMimePartTree->hide(); return; } bool showMimeTree = false; if (MessageViewer::MessageViewerSettings::self()->mimeTreeMode() == MessageViewer::MessageViewerSettings::EnumMimeTreeMode::Always) { mMimePartTree->show(); showMimeTree = true; } else { // don't rely on QSplitter maintaining sizes for hidden widgets: saveSplitterSizes(); mMimePartTree->hide(); showMimeTree = false; } if (mToggleMimePartTreeAction && (mToggleMimePartTreeAction->isChecked() != showMimeTree)) { mToggleMimePartTreeAction->setChecked(showMimeTree); } #endif } void ViewerPrivate::atmViewMsg(const KMime::Message::Ptr &message) { Q_ASSERT(message); Q_EMIT showMessage(message, overrideEncoding()); } void ViewerPrivate::adjustLayout() { #ifndef QT_NO_TREEVIEW const int mimeH = MessageViewer::MessageViewerSettings::self()->mimePaneHeight(); const int messageH = MessageViewer::MessageViewerSettings::self()->messagePaneHeight(); QList splitterSizes; splitterSizes << messageH << mimeH; mSplitter->addWidget(mMimePartTree); mSplitter->setSizes(splitterSizes); if (MessageViewer::MessageViewerSettings::self()->mimeTreeMode() == MessageViewer::MessageViewerSettings::EnumMimeTreeMode::Always && mMsgDisplay) { mMimePartTree->show(); } else { mMimePartTree->hide(); } #endif if (MessageViewer::MessageViewerSettings::self()->showColorBar() && mMsgDisplay) { mColorBar->show(); } else { mColorBar->hide(); } } void ViewerPrivate::saveSplitterSizes() const { #ifndef QT_NO_TREEVIEW if (!mSplitter || !mMimePartTree) { return; } if (mMimePartTree->isHidden()) { return; // don't rely on QSplitter maintaining sizes for hidden widgets. } MessageViewer::MessageViewerSettings::self()->setMimePaneHeight(mSplitter->sizes().at(1)); MessageViewer::MessageViewerSettings::self()->setMessagePaneHeight(mSplitter->sizes().at(0)); #endif } void ViewerPrivate::createWidgets() { //TODO: Make a MDN bar similar to Mozillas password bar and show MDNs here as soon as a // MDN enabled message is shown. QVBoxLayout *vlay = new QVBoxLayout(q); vlay->setMargin(0); mSplitter = new QSplitter(Qt::Vertical, q); connect(mSplitter, &QSplitter::splitterMoved, this, &ViewerPrivate::saveSplitterSizes); mSplitter->setObjectName(QStringLiteral("mSplitter")); mSplitter->setChildrenCollapsible(false); vlay->addWidget(mSplitter); #ifndef QT_NO_TREEVIEW mMimePartTree = new MimePartTreeView(mSplitter); connect(mMimePartTree, &QAbstractItemView::activated, this, &ViewerPrivate::slotMimePartSelected); connect(mMimePartTree, &QWidget::customContextMenuRequested, this, &ViewerPrivate::slotMimeTreeContextMenuRequested); #endif mBox = new QWidget(mSplitter); QHBoxLayout *mBoxHBoxLayout = new QHBoxLayout(mBox); mBoxHBoxLayout->setMargin(0); mColorBar = new HtmlStatusBar(mBox); mBoxHBoxLayout->addWidget(mColorBar); QWidget *readerBox = new QWidget(mBox); QVBoxLayout *readerBoxVBoxLayout = new QVBoxLayout(readerBox); readerBoxVBoxLayout->setMargin(0); mBoxHBoxLayout->addWidget(readerBox); mColorBar->setObjectName(QStringLiteral("mColorBar")); mColorBar->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); mSubmittedFormWarning = new SubmittedFormWarningWidget(readerBox); mSubmittedFormWarning->setObjectName(QStringLiteral("submittedformwarning")); readerBoxVBoxLayout->addWidget(mSubmittedFormWarning); mScamDetectionWarning = new ScamDetectionWarningWidget(readerBox); mScamDetectionWarning->setObjectName(QStringLiteral("scandetectionwarning")); readerBoxVBoxLayout->addWidget(mScamDetectionWarning); mOpenAttachmentFolderWidget = new OpenAttachmentFolderWidget(readerBox); mOpenAttachmentFolderWidget->setObjectName(QStringLiteral("openattachementfolderwidget")); readerBoxVBoxLayout->addWidget(mOpenAttachmentFolderWidget); mTextToSpeechWidget = new KPIMTextEdit::TextToSpeechWidget(readerBox); mTextToSpeechWidget->setObjectName(QStringLiteral("texttospeechwidget")); readerBoxVBoxLayout->addWidget(mTextToSpeechWidget); mViewer = new MailWebEngineView(mActionCollection, readerBox); mViewer->setViewer(this); readerBoxVBoxLayout->addWidget(mViewer); mViewer->setObjectName(QStringLiteral("mViewer")); mViewerPluginToolManager = new MessageViewer::ViewerPluginToolManager(readerBox, this); mViewerPluginToolManager->setActionCollection(mActionCollection); mViewerPluginToolManager->setPluginName(QStringLiteral("messageviewer")); mViewerPluginToolManager->setServiceTypeName(QStringLiteral("MessageViewer/ViewerPlugin")); if (!mViewerPluginToolManager->initializePluginList()) { qCDebug(MESSAGEVIEWER_LOG) << " Impossible to initialize plugins"; } mViewerPluginToolManager->createView(); connect(mViewerPluginToolManager, &MessageViewer::ViewerPluginToolManager::activatePlugin, this, &ViewerPrivate::slotActivatePlugin); mSliderContainer = new KPIMTextEdit::SlideContainer(readerBox); mSliderContainer->setObjectName(QStringLiteral("slidercontainer")); readerBoxVBoxLayout->addWidget(mSliderContainer); mFindBar = new WebEngineViewer::FindBarWebEngineView(mViewer, q); connect(mFindBar, &WebEngineViewer::FindBarWebEngineView::hideFindBar, mSliderContainer, &KPIMTextEdit::SlideContainer::slideOut); mSliderContainer->setContent(mFindBar); #ifndef QT_NO_TREEVIEW mSplitter->setStretchFactor(mSplitter->indexOf(mMimePartTree), 0); #endif } void ViewerPrivate::slotStyleChanged(MessageViewer::HeaderStylePlugin *plugin) { mHeaderStylePlugin = plugin; update(MimeTreeParser::Force); } void ViewerPrivate::slotStyleUpdated() { update(MimeTreeParser::Force); } void ViewerPrivate::createActions() { KActionCollection *ac = mActionCollection; mHeaderStyleMenuManager = new MessageViewer::HeaderStyleMenuManager(ac, this); connect(mHeaderStyleMenuManager, &MessageViewer::HeaderStyleMenuManager::styleChanged, this, &ViewerPrivate::slotStyleChanged); connect(mHeaderStyleMenuManager, &MessageViewer::HeaderStyleMenuManager::styleUpdated, this, &ViewerPrivate::slotStyleUpdated); if (!ac) { return; } mZoomActionMenu = new WebEngineViewer::ZoomActionMenu(this); connect(mZoomActionMenu, &WebEngineViewer::ZoomActionMenu::zoomChanged, mViewer, &MailWebEngineView::slotZoomChanged); mZoomActionMenu->setActionCollection(ac); mZoomActionMenu->createZoomActions(); // attachment style KActionMenu *attachmentMenu = new KActionMenu(i18nc("View->", "&Attachments"), this); ac->addAction(QStringLiteral("view_attachments"), attachmentMenu); addHelpTextAction(attachmentMenu, i18n("Choose display style of attachments")); QActionGroup *group = new QActionGroup(this); KToggleAction *raction = new KToggleAction(i18nc("View->attachments->", "&As Icons"), this); ac->addAction(QStringLiteral("view_attachments_as_icons"), raction); connect(raction, &QAction::triggered, this, &ViewerPrivate::slotIconicAttachments); addHelpTextAction(raction, i18n("Show all attachments as icons. Click to see them.")); group->addAction(raction); attachmentMenu->addAction(raction); raction = new KToggleAction(i18nc("View->attachments->", "&Smart"), this); ac->addAction(QStringLiteral("view_attachments_smart"), raction); connect(raction, &QAction::triggered, this, &ViewerPrivate::slotSmartAttachments); addHelpTextAction(raction, i18n("Show attachments as suggested by sender.")); group->addAction(raction); attachmentMenu->addAction(raction); raction = new KToggleAction(i18nc("View->attachments->", "&Inline"), this); ac->addAction(QStringLiteral("view_attachments_inline"), raction); connect(raction, &QAction::triggered, this, &ViewerPrivate::slotInlineAttachments); addHelpTextAction(raction, i18n("Show all attachments inline (if possible)")); group->addAction(raction); attachmentMenu->addAction(raction); raction = new KToggleAction(i18nc("View->attachments->", "&Hide"), this); ac->addAction(QStringLiteral("view_attachments_hide"), raction); connect(raction, &QAction::triggered, this, &ViewerPrivate::slotHideAttachments); addHelpTextAction(raction, i18n("Do not show attachments in the message viewer")); group->addAction(raction); attachmentMenu->addAction(raction); mHeaderOnlyAttachmentsAction = new KToggleAction(i18nc("View->attachments->", "In Header Only"), this); ac->addAction(QStringLiteral("view_attachments_headeronly"), mHeaderOnlyAttachmentsAction); connect(mHeaderOnlyAttachmentsAction, &QAction::triggered, this, &ViewerPrivate::slotHeaderOnlyAttachments); addHelpTextAction(mHeaderOnlyAttachmentsAction, i18n("Show Attachments only in the header of the mail")); group->addAction(mHeaderOnlyAttachmentsAction); attachmentMenu->addAction(mHeaderOnlyAttachmentsAction); // Set Encoding submenu mSelectEncodingAction = new KSelectAction(QIcon::fromTheme(QStringLiteral( "character-set")), i18n("&Set Encoding"), this); mSelectEncodingAction->setToolBarMode(KSelectAction::MenuMode); ac->addAction(QStringLiteral("encoding"), mSelectEncodingAction); connect(mSelectEncodingAction, SIGNAL(triggered(int)), SLOT(slotSetEncoding())); QStringList encodings = MimeTreeParser::NodeHelper::supportedEncodings(false); encodings.prepend(i18n("Auto")); mSelectEncodingAction->setItems(encodings); mSelectEncodingAction->setCurrentItem(0); // // Message Menu // // copy selected text to clipboard mCopyAction = ac->addAction(KStandardAction::Copy, QStringLiteral("kmail_copy")); mCopyAction->setText(i18n("Copy Text")); connect(mCopyAction, &QAction::triggered, this, &ViewerPrivate::slotCopySelectedText); connect(mViewer, &MailWebEngineView::selectionChanged, this, &ViewerPrivate::viewerSelectionChanged); viewerSelectionChanged(); // copy all text to clipboard mSelectAllAction = new QAction(i18n("Select All Text"), this); ac->addAction(QStringLiteral("mark_all_text"), mSelectAllAction); connect(mSelectAllAction, &QAction::triggered, this, &ViewerPrivate::selectAll); ac->setDefaultShortcut(mSelectAllAction, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_A)); // copy Email address to clipboard mCopyURLAction = new QAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18n("Copy Link Address"), this); ac->addAction(QStringLiteral("copy_url"), mCopyURLAction); connect(mCopyURLAction, &QAction::triggered, this, &ViewerPrivate::slotUrlCopy); // open URL mUrlOpenAction = new QAction(QIcon::fromTheme(QStringLiteral("document-open")), i18n( "Open URL"), this); ac->addAction(QStringLiteral("open_url"), mUrlOpenAction); connect(mUrlOpenAction, &QAction::triggered, this, &ViewerPrivate::slotOpenUrl); // use fixed font mToggleFixFontAction = new KToggleAction(i18n("Use Fi&xed Font"), this); ac->addAction(QStringLiteral("toggle_fixedfont"), mToggleFixFontAction); connect(mToggleFixFontAction, &QAction::triggered, this, &ViewerPrivate::slotToggleFixedFont); ac->setDefaultShortcut(mToggleFixFontAction, QKeySequence(Qt::Key_X)); // Show message structure viewer mToggleMimePartTreeAction = new KToggleAction(i18n("Show Message Structure"), this); ac->addAction(QStringLiteral("toggle_mimeparttree"), mToggleMimePartTreeAction); connect(mToggleMimePartTreeAction, &QAction::toggled, this, &ViewerPrivate::slotToggleMimePartTree); mViewSourceAction = new QAction(i18n("&View Source"), this); ac->addAction(QStringLiteral("view_source"), mViewSourceAction); connect(mViewSourceAction, &QAction::triggered, this, &ViewerPrivate::slotShowMessageSource); ac->setDefaultShortcut(mViewSourceAction, QKeySequence(Qt::Key_V)); mSaveMessageAction = new QAction(QIcon::fromTheme(QStringLiteral("document-save-as")), i18n( "&Save message..."), this); ac->addAction(QStringLiteral("save_message"), mSaveMessageAction); connect(mSaveMessageAction, &QAction::triggered, this, &ViewerPrivate::slotSaveMessage); //Laurent: conflict with kmail shortcut //mSaveMessageAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_S)); mSaveMessageDisplayFormat = new QAction(i18n("&Save Display Format"), this); ac->addAction(QStringLiteral("save_message_display_format"), mSaveMessageDisplayFormat); connect(mSaveMessageDisplayFormat, &QAction::triggered, this, &ViewerPrivate::slotSaveMessageDisplayFormat); mResetMessageDisplayFormat = new QAction(i18n("&Reset Display Format"), this); ac->addAction(QStringLiteral("reset_message_display_format"), mResetMessageDisplayFormat); connect(mResetMessageDisplayFormat, &QAction::triggered, this, &ViewerPrivate::slotResetMessageDisplayFormat); // // Scroll actions // mScrollUpAction = new QAction(i18n("Scroll Message Up"), this); ac->setDefaultShortcut(mScrollUpAction, QKeySequence(Qt::Key_Up)); ac->addAction(QStringLiteral("scroll_up"), mScrollUpAction); connect(mScrollUpAction, &QAction::triggered, q, &Viewer::slotScrollUp); mScrollDownAction = new QAction(i18n("Scroll Message Down"), this); ac->setDefaultShortcut(mScrollDownAction, QKeySequence(Qt::Key_Down)); ac->addAction(QStringLiteral("scroll_down"), mScrollDownAction); connect(mScrollDownAction, &QAction::triggered, q, &Viewer::slotScrollDown); mScrollUpMoreAction = new QAction(i18n("Scroll Message Up (More)"), this); ac->setDefaultShortcut(mScrollUpMoreAction, QKeySequence(Qt::Key_PageUp)); ac->addAction(QStringLiteral("scroll_up_more"), mScrollUpMoreAction); connect(mScrollUpMoreAction, &QAction::triggered, q, &Viewer::slotScrollPrior); mScrollDownMoreAction = new QAction(i18n("Scroll Message Down (More)"), this); ac->setDefaultShortcut(mScrollDownMoreAction, QKeySequence(Qt::Key_PageDown)); ac->addAction(QStringLiteral("scroll_down_more"), mScrollDownMoreAction); connect(mScrollDownMoreAction, &QAction::triggered, q, &Viewer::slotScrollNext); // // Actions not in menu // // Toggle HTML display mode. mToggleDisplayModeAction = new KToggleAction(i18n("Toggle HTML Display Mode"), this); ac->addAction(QStringLiteral("toggle_html_display_mode"), mToggleDisplayModeAction); ac->setDefaultShortcut(mToggleDisplayModeAction, QKeySequence(Qt::SHIFT + Qt::Key_H)); connect(mToggleDisplayModeAction, &QAction::triggered, this, &ViewerPrivate::slotToggleHtmlMode); addHelpTextAction(mToggleDisplayModeAction, i18n("Toggle display mode between HTML and plain text")); // Load external reference QAction *loadExternalReferenceAction = new QAction(i18n("Load external references"), this); ac->addAction(QStringLiteral("load_external_reference"), loadExternalReferenceAction); ac->setDefaultShortcut(loadExternalReferenceAction, QKeySequence(Qt::SHIFT + Qt::CTRL + Qt::Key_R)); connect(loadExternalReferenceAction, &QAction::triggered, this, &ViewerPrivate::slotLoadExternalReference); addHelpTextAction(loadExternalReferenceAction, i18n("Load external references from the Internet for this message.")); mSpeakTextAction = new QAction(i18n("Speak Text"), this); mSpeakTextAction->setIcon(QIcon::fromTheme(QStringLiteral("preferences-desktop-text-to-speech"))); ac->addAction(QStringLiteral("speak_text"), mSpeakTextAction); connect(mSpeakTextAction, &QAction::triggered, this, &ViewerPrivate::slotSpeakText); mCopyImageLocation = new QAction(i18n("Copy Image Location"), this); mCopyImageLocation->setIcon(QIcon::fromTheme(QStringLiteral("view-media-visualization"))); ac->addAction(QStringLiteral("copy_image_location"), mCopyImageLocation); ac->setShortcutsConfigurable(mCopyImageLocation, false); connect(mCopyImageLocation, &QAction::triggered, this, &ViewerPrivate::slotCopyImageLocation); mFindInMessageAction = new QAction(QIcon::fromTheme(QStringLiteral("edit-find")), i18n( "&Find in Message..."), this); ac->addAction(QStringLiteral("find_in_messages"), mFindInMessageAction); connect(mFindInMessageAction, &QAction::triggered, this, &ViewerPrivate::slotFind); ac->setDefaultShortcut(mFindInMessageAction, KStandardShortcut::find().first()); mShareServiceUrlMenu = mShareServiceManager->menu(); ac->addAction(QStringLiteral("shareservice_menu"), mShareServiceUrlMenu); connect(mShareServiceManager, &PimCommon::ShareServiceUrlManager::serviceUrlSelected, this, &ViewerPrivate::slotServiceUrlSelected); mDisableEmoticonAction = new KToggleAction(i18n("Disable Emoticon"), this); ac->addAction(QStringLiteral("disable_emoticon"), mDisableEmoticonAction); connect(mDisableEmoticonAction, &QAction::triggered, this, &ViewerPrivate::slotToggleEmoticons); ac->setDefaultShortcut(mFindInMessageAction, KStandardShortcut::find().first()); } void ViewerPrivate::showContextMenu(KMime::Content *content, const QPoint &pos) { #ifndef QT_NO_TREEVIEW if (!content) { return; } if (content->contentType(false)) { if (content->contentType()->mimeType() == "text/x-moz-deleted") { return; } } const bool isAttachment = !content->contentType()->isMultipart() && !content->isTopLevel(); const bool isRoot = (content == mMessage.data()); const auto hasAttachments = KMime::hasAttachment(mMessage.data()); QMenu popup; if (!isRoot) { popup.addAction(QIcon::fromTheme(QStringLiteral("document-save-as")), i18n("Save &As..."), this, &ViewerPrivate::slotAttachmentSaveAs); if (isAttachment) { popup.addAction(QIcon::fromTheme(QStringLiteral("document-open")), i18nc("to open", "Open"), this, &ViewerPrivate::slotAttachmentOpen); if (selectedContents().count() == 1) { createOpenWithMenu(&popup, QLatin1String(content->contentType()->mimeType()), false); } else { popup.addAction(i18n("Open With..."), this, &ViewerPrivate::slotAttachmentOpenWith); } popup.addAction(i18nc("to view something", "View"), this, &ViewerPrivate::slotAttachmentView); } } if (hasAttachments) { popup.addAction(i18n("Save All Attachments..."), this, &ViewerPrivate::slotAttachmentSaveAll); } // edit + delete only for attachments if (!isRoot) { if (isAttachment) { popup.addAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18n("Copy"), this, &ViewerPrivate::slotAttachmentCopy); #if 0 //FIXME Laurent Comment for the moment it crash see Bug 287177 popup.addAction(QIcon::fromTheme("edit-delete"), i18n("Delete Attachment"), this, SLOT(slotAttachmentDelete())); #endif if (MessageViewer::MessageViewerSettings::self()->allowAttachmentEditing()) { popup.addAction(QIcon::fromTheme(QStringLiteral("document-properties")), i18n("Edit Attachment"), this, &ViewerPrivate::slotAttachmentEdit); } } if (!content->isTopLevel()) { popup.addAction(i18n("Properties"), this, &ViewerPrivate::slotAttachmentProperties); } } popup.exec(mMimePartTree->viewport()->mapToGlobal(pos)); #endif } KToggleAction *ViewerPrivate::actionForAttachmentStrategy( const MimeTreeParser::AttachmentStrategy *as) { if (!mActionCollection) { return nullptr; } QString actionName; if (as == MimeTreeParser::AttachmentStrategy::iconic()) { actionName = QStringLiteral("view_attachments_as_icons"); } else if (as == MimeTreeParser::AttachmentStrategy::smart()) { actionName = QStringLiteral("view_attachments_smart"); } else if (as == MimeTreeParser::AttachmentStrategy::inlined()) { actionName = QStringLiteral("view_attachments_inline"); } else if (as == MimeTreeParser::AttachmentStrategy::hidden()) { actionName = QStringLiteral("view_attachments_hide"); } else if (as == MimeTreeParser::AttachmentStrategy::headerOnly()) { actionName = QStringLiteral("view_attachments_headeronly"); } if (actionName.isEmpty()) { return nullptr; } else { return static_cast(mActionCollection->action(actionName)); } } void ViewerPrivate::readGlobalOverrideCodec() { // if the global character encoding wasn't changed then there's nothing to do if (MessageCore::MessageCoreSettings::self()->overrideCharacterEncoding() == mOldGlobalOverrideEncoding) { return; } setOverrideEncoding(MessageCore::MessageCoreSettings::self()->overrideCharacterEncoding()); mOldGlobalOverrideEncoding = MessageCore::MessageCoreSettings::self()->overrideCharacterEncoding(); } const QTextCodec *ViewerPrivate::overrideCodec() const { if (mOverrideEncoding.isEmpty() || mOverrideEncoding == QLatin1String("Auto")) { // Auto return nullptr; } else { return ViewerPrivate::codecForName(mOverrideEncoding.toLatin1()); } } static QColor nextColor(const QColor &c) { int h, s, v; c.getHsv(&h, &s, &v); return QColor::fromHsv((h + 50) % 360, qMax(s, 64), v); } QString ViewerPrivate::renderAttachments(KMime::Content *node, const QColor &bgColor) const { if (!node) { return QString(); } QString html; KMime::Content *child = MessageCore::NodeHelper::firstChild(node); if (child) { QString subHtml = renderAttachments(child, nextColor(bgColor)); if (!subHtml.isEmpty()) { QString margin; if (node != mMessage.data() || headerStylePlugin()->hasMargin()) { margin = QStringLiteral("padding:2px; margin:2px; "); } QString align = headerStylePlugin()->alignment(); const QByteArray mediaTypeLower = node->contentType()->mediaType().toLower(); const bool result = (mediaTypeLower == "message" || mediaTypeLower == "multipart" || node == mMessage.data()); if (result) { html += QStringLiteral("
").arg(bgColor.name()). arg(margin).arg(align); } html += subHtml; if (result) { html += QLatin1String("
"); } } } else { Util::AttachmentDisplayInfo info = Util::attachmentDisplayInfo(node); if (info.displayInHeader) { html += QLatin1String(" "); } } Q_FOREACH (KMime::Content *extraNode, mNodeHelper->extraContents(node)) { html += renderAttachments(extraNode, bgColor); } KMime::Content *next = MessageCore::NodeHelper::nextSibling(node); if (next) { html += renderAttachments(next, nextColor(bgColor)); } return html; } KMime::Content *ViewerPrivate::findContentByType(KMime::Content *content, const QByteArray &type) { const auto list = content->contents(); for (KMime::Content *c : list) { if (c->contentType()->mimeType() == type) { return c; } } return nullptr; } //----------------------------------------------------------------------------- const QTextCodec *ViewerPrivate::codecForName(const QByteArray &_str) { if (_str.isEmpty()) { return nullptr; } QByteArray codec = _str.toLower(); return KCharsets::charsets()->codecForName(QLatin1String(codec)); } void ViewerPrivate::update(MimeTreeParser::UpdateMode updateMode) { // Avoid flicker, somewhat of a cludge if (updateMode == MimeTreeParser::Force) { // stop the timer to avoid calling updateReaderWin twice mUpdateReaderWinTimer.stop(); saveRelativePosition(); updateReaderWin(); } else if (mUpdateReaderWinTimer.isActive()) { mUpdateReaderWinTimer.setInterval(150); } else { mUpdateReaderWinTimer.start(0); } } void ViewerPrivate::slotOpenUrl() { slotUrlOpen(); } void ViewerPrivate::slotUrlOpen(const QUrl &url) { if (!url.isEmpty()) { mClickedUrl = url; } // First, let's see if the URL handler manager can handle the URL. If not, try KRun for some // known URLs, otherwise fallback to emitting a signal. // That signal is caught by KMail, and in case of mailto URLs, a composer is shown. if (URLHandlerManager::instance()->handleClick(mClickedUrl, this)) { return; } Q_EMIT urlClicked(mMessageItem, mClickedUrl); } void ViewerPrivate::checkPhishingUrl() { if (!PimCommon::NetworkUtil::self()->lowBandwidth() && MessageViewer::MessageViewerSettings::self()->checkPhishingUrl() && (mClickedUrl.scheme() != QLatin1String("mailto"))) { mPhishingDatabase->checkUrl(mClickedUrl); } else { executeRunner(mClickedUrl); } } void ViewerPrivate::executeRunner(const QUrl &url) { if (!MessageViewer::Util::handleUrlWithQDesktopServices(url)) { KRun *runner = new KRun(url, viewer()); // will delete itself runner->setRunExecutables(false); } } void ViewerPrivate::slotCheckedUrlFinished(const QUrl &url, WebEngineViewer::CheckPhishingUrlUtil::UrlStatus status) { switch (status) { case WebEngineViewer::CheckPhishingUrlUtil::BrokenNetwork: KMessageBox::error(mMainWindow, i18n("The network is broken."), i18n("Check Phishing URL")); break; case WebEngineViewer::CheckPhishingUrlUtil::InvalidUrl: KMessageBox::error(mMainWindow, i18n("The URL %1 is not valid.", url.toString()), i18n("Check Phishing URL")); break; case WebEngineViewer::CheckPhishingUrlUtil::Ok: break; case WebEngineViewer::CheckPhishingUrlUtil::MalWare: if (!urlIsAMalwareButContinue()) { return; } break; case WebEngineViewer::CheckPhishingUrlUtil::Unknown: qCWarning(MESSAGEVIEWER_LOG) << "WebEngineViewer::slotCheckedUrlFinished unknown error "; break; } executeRunner(url); } bool ViewerPrivate::urlIsAMalwareButContinue() { if (KMessageBox::No == KMessageBox::warningYesNo(mMainWindow, i18n( "This web site is a malware, do you want to continue to show it?"), i18n("Malware"))) { return false; } return true; } void ViewerPrivate::slotUrlOn(const QString &link) { // The "link" we get here is not URL-encoded, and therefore there is no way QUrl could // parse it correctly. To workaround that, we use QWebFrame::hitTestContent() on the mouse position // to get the URL before WebKit managed to mangle it. QUrl url(link); const QString protocol = url.scheme(); if (protocol == QLatin1String("kmail") || protocol == QLatin1String("x-kmail") || protocol == QLatin1String("attachment") || (protocol.isEmpty() && url.path().isEmpty())) { mViewer->setAcceptDrops(false); } else { mViewer->setAcceptDrops(true); } mViewer->setLinkHovered(url); if (link.trimmed().isEmpty()) { KPIM::BroadcastStatus::instance()->reset(); Q_EMIT showStatusBarMessage(QString()); return; } QString msg = URLHandlerManager::instance()->statusBarMessage(url, this); if (msg.isEmpty()) { msg = link; } KPIM::BroadcastStatus::instance()->setTransientStatusMsg(msg); Q_EMIT showStatusBarMessage(msg); } void ViewerPrivate::slotUrlPopup(const WebEngineViewer::WebHitTestResult &result) { if (!mMsgDisplay) { return; } mClickedUrl = result.linkUrl(); mImageUrl = result.imageUrl(); const QPoint aPos = mViewer->mapToGlobal(result.pos()); if (URLHandlerManager::instance()->handleContextMenuRequest(mClickedUrl, aPos, this)) { return; } if (!mActionCollection) { return; } if (mClickedUrl.scheme() == QLatin1String("mailto")) { mCopyURLAction->setText(i18n("Copy Email Address")); } else { mCopyURLAction->setText(i18n("Copy Link Address")); } Q_EMIT displayPopupMenu(mMessageItem, result, aPos); Q_EMIT popupMenu(mMessageItem, mClickedUrl, mImageUrl, aPos); } void ViewerPrivate::slotLoadExternalReference() { if (mColorBar->isNormal() || htmlLoadExtOverride()) { return; } setHtmlLoadExtOverride(true); update(MimeTreeParser::Force); } Viewer::DisplayFormatMessage translateToDisplayFormat(MimeTreeParser::Util::HtmlMode mode) { switch (mode) { case MimeTreeParser::Util::Normal: return Viewer::Unknown; case MimeTreeParser::Util::Html: return Viewer::Html; case MimeTreeParser::Util::MultipartPlain: return Viewer::Text; case MimeTreeParser::Util::MultipartHtml: return Viewer::Html; case MimeTreeParser::Util::MultipartIcal: return Viewer::ICal; } return Viewer::Unknown; } void ViewerPrivate::slotToggleHtmlMode() { const auto availableModes = mColorBar->availableModes(); const int availableModeSize(availableModes.size()); if (mColorBar->isNormal() || availableModeSize < 2) { return; } mScamDetectionWarning->setVisible(false); const MimeTreeParser::Util::HtmlMode mode = mColorBar->mode(); const int pos = (availableModes.indexOf(mode) + 1) % availableModeSize; setDisplayFormatMessageOverwrite(translateToDisplayFormat(availableModes[pos])); update(MimeTreeParser::Force); mColorBar->setAvailableModes(availableModes); } void ViewerPrivate::slotFind() { if (mViewer->hasSelection()) { mFindBar->setText(mViewer->selectedText()); } mSliderContainer->slideIn(); mFindBar->focusAndSetCursor(); } void ViewerPrivate::slotToggleFixedFont() { mUseFixedFont = !mUseFixedFont; update(MimeTreeParser::Force); } void ViewerPrivate::slotToggleMimePartTree() { if (mToggleMimePartTreeAction->isChecked()) { MessageViewer::MessageViewerSettings::self()->setMimeTreeMode( MessageViewer::MessageViewerSettings::EnumMimeTreeMode::Always); } else { MessageViewer::MessageViewerSettings::self()->setMimeTreeMode( MessageViewer::MessageViewerSettings::EnumMimeTreeMode::Never); } showHideMimeTree(); } void ViewerPrivate::slotShowMessageSource() { if (!mMessage) { return; } mNodeHelper->messageWithExtraContent(mMessage.data()); QPointer viewer = new MailSourceWebEngineViewer; // deletes itself upon close mListMailSourceViewer.append(viewer); viewer->setWindowTitle(i18n("Message as Plain Text")); const QString rawMessage = QString::fromLatin1(mMessage->encodedContent()); viewer->setRawSource(rawMessage); viewer->setDisplayedSource(mViewer->page()); if (mUseFixedFont) { viewer->setFixedFont(); } // Well, there is no widget to be seen here, so we have to use QCursor::pos() // Update: (GS) I'm not going to make this code behave according to Xinerama // configuration because this is quite the hack. if (QApplication::desktop()->isVirtualDesktop()) { #ifndef QT_NO_CURSOR int scnum = QApplication::desktop()->screenNumber(QCursor::pos()); #else int scnum = 0; #endif viewer->resize(QApplication::desktop()->screenGeometry(scnum).width() / 2, 2 * QApplication::desktop()->screenGeometry(scnum).height() / 3); } else { viewer->resize(QApplication::desktop()->geometry().width() / 2, 2 * QApplication::desktop()->geometry().height() / 3); } viewer->show(); } void ViewerPrivate::updateReaderWin() { if (!mMsgDisplay) { return; } if (mRecursionCountForDisplayMessage + 1 > 1) { // This recursion here can happen because the ObjectTreeParser in parseMsg() can exec() an // eventloop. // This happens in two cases: // 1) The ContactSearchJob started by FancyHeaderStyle::format // 2) Various modal passphrase dialogs for decryption of a message (bug 96498) // // While the exec() eventloop is running, it is possible that a timer calls updateReaderWin(), // and not aborting here would confuse the state terribly. qCWarning(MESSAGEVIEWER_LOG) << "Danger, recursion while displaying a message!"; return; } mRecursionCountForDisplayMessage++; mViewer->setAllowExternalContent(htmlLoadExternal()); htmlWriter()->reset(); //TODO: if the item doesn't have the payload fetched, try to fetch it? Maybe not here, but in setMessageItem. if (mMessage) { if (MessageViewer::MessageViewerSettings::self()->showColorBar()) { mColorBar->show(); } else { mColorBar->hide(); } displayMessage(); } else if (mMessagePartNode) { setMessagePart(mMessagePartNode); } else { mColorBar->hide(); #ifndef QT_NO_TREEVIEW mMimePartTree->hide(); #endif htmlWriter()->begin(); htmlWriter()->write(mCSSHelper->htmlHead(mUseFixedFont) + QLatin1String("")); htmlWriter()->end(); } mRecursionCountForDisplayMessage--; } void ViewerPrivate::slotMimePartSelected(const QModelIndex &index) { #ifndef QT_NO_TREEVIEW KMime::Content *content = static_cast(index.internalPointer()); if (!mMimePartTree->mimePartModel()->parent(index).isValid() && index.row() == 0) { update(MimeTreeParser::Force); } else { setMessagePart(content); } #endif } void ViewerPrivate::slotIconicAttachments() { setAttachmentStrategy(MimeTreeParser::AttachmentStrategy::iconic()); } void ViewerPrivate::slotSmartAttachments() { setAttachmentStrategy(MimeTreeParser::AttachmentStrategy::smart()); } void ViewerPrivate::slotInlineAttachments() { setAttachmentStrategy(MimeTreeParser::AttachmentStrategy::inlined()); } void ViewerPrivate::slotHideAttachments() { setAttachmentStrategy(MimeTreeParser::AttachmentStrategy::hidden()); } void ViewerPrivate::slotHeaderOnlyAttachments() { setAttachmentStrategy(MimeTreeParser::AttachmentStrategy::headerOnly()); } void ViewerPrivate::attachmentView(KMime::Content *atmNode) { if (atmNode) { const bool isEncapsulatedMessage = atmNode->parent() && atmNode->parent()->bodyIsMessage(); if (isEncapsulatedMessage) { atmViewMsg(atmNode->parent()->bodyAsMessage()); } else if ((qstricmp(atmNode->contentType()->mediaType().constData(), "text") == 0) && ((qstricmp(atmNode->contentType()->subType().constData(), "x-vcard") == 0) || (qstricmp(atmNode->contentType()->subType().constData(), "directory") == 0))) { setMessagePart(atmNode); } else { Q_EMIT showReader(atmNode, htmlMail(), overrideEncoding()); } } } void ViewerPrivate::slotDelayedResize() { mSplitter->setGeometry(0, 0, q->width(), q->height()); } void ViewerPrivate::slotPrintPreview() { disconnect( mPartHtmlWriter.data(), &WebEnginePartHtmlWriter::finished, this, &ViewerPrivate::slotPrintPreview); if (!mMessage) { return; } //Need to delay QTimer::singleShot(1 * 1000, this, &ViewerPrivate::slotDelayPrintPreview); } void ViewerPrivate::slotDelayPrintPreview() { QPrintPreviewDialog *dialog = new QPrintPreviewDialog(q); dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->resize(800, 750); connect(dialog, &QPrintPreviewDialog::paintRequested, this, [=](QPrinter *printing) { QApplication::setOverrideCursor(Qt::WaitCursor); mViewer->execPrintPreviewPage(printing, 10*1000); QApplication::restoreOverrideCursor(); }); dialog->open(this, SIGNAL(printingFinished())); } void ViewerPrivate::slotOpenInBrowser() { WebEngineViewer::WebEngineExportHtmlPageJob *job = new WebEngineViewer::WebEngineExportHtmlPageJob; job->setEngineView(mViewer); connect(job, &WebEngineViewer::WebEngineExportHtmlPageJob::failed, this, &ViewerPrivate::slotExportHtmlPageFailed); connect(job, &WebEngineViewer::WebEngineExportHtmlPageJob::success, this, &ViewerPrivate::slotExportHtmlPageSuccess); job->start(); } void ViewerPrivate::slotExportHtmlPageSuccess(const QString &filename) { const QUrl url(QUrl::fromLocalFile(filename)); KRun::RunFlags flags; flags |= KRun::DeleteTemporaryFiles; KRun::runUrl(url, QStringLiteral("text/html"), q, flags); Q_EMIT printingFinished(); } void ViewerPrivate::slotExportHtmlPageFailed() { qCDebug(MESSAGEVIEWER_LOG) << " Export HTML failed"; Q_EMIT printingFinished(); } void ViewerPrivate::slotPrintMessage() { disconnect( mPartHtmlWriter.data(), &WebEnginePartHtmlWriter::finished, this, &ViewerPrivate::slotPrintMessage); if (!mMessage) { return; } if (mCurrentPrinter) { return; } mCurrentPrinter = new QPrinter(); QPointer dialog = new QPrintDialog(mCurrentPrinter, mMainWindow); dialog->setWindowTitle(i18n("Print Document")); if (dialog->exec() != QDialog::Accepted) { slotHandlePagePrinted(false); delete dialog; return; } delete dialog; mViewer->page()->print(mCurrentPrinter, invoke(this, &ViewerPrivate::slotHandlePagePrinted)); } void ViewerPrivate::slotHandlePagePrinted(bool result) { Q_UNUSED(result); delete mCurrentPrinter; mCurrentPrinter = nullptr; Q_EMIT printingFinished(); } void ViewerPrivate::slotSetEncoding() { if (mSelectEncodingAction) { if (mSelectEncodingAction->currentItem() == 0) { // Auto mOverrideEncoding.clear(); } else { mOverrideEncoding = MimeTreeParser::NodeHelper::encodingForName( mSelectEncodingAction->currentText()); } update(MimeTreeParser::Force); } } HeaderStylePlugin *ViewerPrivate::headerStylePlugin() const { return mHeaderStylePlugin; } QString ViewerPrivate::attachmentInjectionHtml() { const QColor background = KColorScheme(QPalette::Active, KColorScheme::View).background().color(); QString html = renderAttachments(mMessage.data(), background); if (html.isEmpty()) { return QString(); } const QString listVisibility = !mShowAttachmentQuicklist ? QStringLiteral( "style=\"display:none;\"") : QString(); html = QStringLiteral("
").arg(listVisibility) + html + QStringLiteral("
"); const QString urlHandleShow = QStringLiteral("kmail:showAttachmentQuicklist"); const QString imgSrcShow = QStringLiteral("quicklistClosed.png"); const QString urlHandleHide = QStringLiteral("kmail:hideAttachmentQuicklist"); const QString imgSrcHide = QStringLiteral("quicklistOpened.png"); //TODO make it as a virtual method QString link; QString textAlign = QStringLiteral("right"); const bool isFancyTheme = (headerStylePlugin()->name() == QStringLiteral("fancy")); if (isFancyTheme) { textAlign = QStringLiteral("left"); } const QString visibility = QStringLiteral("style=\"display:none;\""); link += QStringLiteral(""); html.prepend(link); if (isFancyTheme) { html.prepend(QStringLiteral("
%1 
").arg(i18n( "Attachments:"))); } return html; } void ViewerPrivate::executeCustomScriptsAfterLoading() { disconnect(mViewer, &MailWebEngineView::loadFinished, this, &ViewerPrivate::executeCustomScriptsAfterLoading); // inject attachments in header view // we have to do that after the otp has run so we also see encrypted parts toggleFullAddressList(); mViewer->scrollToRelativePosition(mViewer->relativePosition()); mViewer->clearRelativePosition(); } void ViewerPrivate::slotSettingsChanged() { update(MimeTreeParser::Force); } void ViewerPrivate::slotMimeTreeContextMenuRequested(const QPoint &pos) { #ifndef QT_NO_TREEVIEW QModelIndex index = mMimePartTree->indexAt(pos); if (index.isValid()) { KMime::Content *content = static_cast(index.internalPointer()); showContextMenu(content, pos); } #endif } void ViewerPrivate::slotAttachmentOpenWith() { #ifndef QT_NO_TREEVIEW QItemSelectionModel *selectionModel = mMimePartTree->selectionModel(); const QModelIndexList selectedRows = selectionModel->selectedRows(); for (const QModelIndex &index : selectedRows) { KMime::Content *content = static_cast(index.internalPointer()); attachmentOpenWith(content); } #endif } void ViewerPrivate::slotAttachmentOpen() { #ifndef QT_NO_TREEVIEW QItemSelectionModel *selectionModel = mMimePartTree->selectionModel(); const QModelIndexList selectedRows = selectionModel->selectedRows(); for (const QModelIndex &index : selectedRows) { KMime::Content *content = static_cast(index.internalPointer()); attachmentOpen(content); } #endif } void ViewerPrivate::showOpenAttachmentFolderWidget(const QUrl &url) { mOpenAttachmentFolderWidget->setFolder(url); mOpenAttachmentFolderWidget->slotShowWarning(); } bool ViewerPrivate::mimePartTreeIsEmpty() const { #ifndef QT_NO_TREEVIEW return mMimePartTree->model()->rowCount() == 0; #else return false; #endif } void ViewerPrivate::setPluginName(const QString &pluginName) { mHeaderStyleMenuManager->setPluginName(pluginName); } QList ViewerPrivate::viewerPluginActionList( ViewerPluginInterface::SpecificFeatureTypes features) { if (mViewerPluginToolManager) { return mViewerPluginToolManager->viewerPluginActionList(features); } return QList(); } void ViewerPrivate::slotActivatePlugin(ViewerPluginInterface *interface) { interface->setMessage(mMessage); interface->setMessageItem(mMessageItem); interface->setUrl(mClickedUrl); interface->setCurrentCollection(mMessageItem.parentCollection()); const QString text = mViewer->selectedText(); if (!text.isEmpty()) { interface->setText(text); } interface->execute(); } void ViewerPrivate::slotAttachmentSaveAs() { const auto contents = selectedContents(); QUrl currentUrl; if (Util::saveAttachments(contents, mMainWindow, currentUrl)) { showOpenAttachmentFolderWidget(currentUrl); } } void ViewerPrivate::slotAttachmentSaveAll() { const auto contents = mMessage->attachments(); QUrl currentUrl; if (Util::saveAttachments(contents, mMainWindow, currentUrl)) { showOpenAttachmentFolderWidget(currentUrl); } } void ViewerPrivate::slotAttachmentView() { const auto contents = selectedContents(); for (KMime::Content *content : contents) { attachmentView(content); } } void ViewerPrivate::slotAttachmentProperties() { const auto contents = selectedContents(); if (contents.isEmpty()) { return; } for (KMime::Content *content : contents) { attachmentProperties(content); } } void ViewerPrivate::attachmentProperties(KMime::Content *content) { MessageCore::AttachmentPropertiesDialog *dialog = new MessageCore::AttachmentPropertiesDialog( content, mMainWindow); dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->show(); } void ViewerPrivate::slotAttachmentCopy() { #ifndef QT_NO_CLIPBOARD attachmentCopy(selectedContents()); #endif } void ViewerPrivate::attachmentCopy(const KMime::Content::List &contents) { #ifndef QT_NO_CLIPBOARD if (contents.isEmpty()) { return; } QList urls; for (KMime::Content *content : contents) { auto url = QUrl::fromLocalFile(mNodeHelper->writeNodeToTempFile(content)); if (!url.isValid()) { continue; } urls.append(url); } if (urls.isEmpty()) { return; } QMimeData *mimeData = new QMimeData; mimeData->setUrls(urls); QApplication::clipboard()->setMimeData(mimeData, QClipboard::Clipboard); #endif } void ViewerPrivate::slotAttachmentDelete() { const auto contents = selectedContents(); if (contents.isEmpty()) { return; } bool showWarning = true; for (KMime::Content *content : contents) { if (!deleteAttachment(content, showWarning)) { return; } showWarning = false; } update(); } void ViewerPrivate::slotAttachmentEdit() { const auto contents = selectedContents(); if (contents.isEmpty()) { return; } MessageViewer::AttachmentEditJob *job = new MessageViewer::AttachmentEditJob(mSession, this); connect(job, &AttachmentEditJob::refreshMessage, this, &ViewerPrivate::slotRefreshMessage); job->setMainWindow(mMainWindow); job->setMessageItem(mMessageItem); job->setMessage(mMessage); bool showWarning = true; for (KMime::Content *content : contents) { if (!job->addAttachment(content, showWarning)) { break; } showWarning = false; } job->canDeleteJob(); } void ViewerPrivate::slotLevelQuote(int l) { if (mLevelQuote != l) { mLevelQuote = l; update(MimeTreeParser::Force); } } void ViewerPrivate::slotHandleAttachment(int choice) { if (!mCurrentContent) { return; } switch (choice) { case Viewer::Delete: deleteAttachment(mCurrentContent); break; case Viewer::Edit: editAttachment(mCurrentContent); break; case Viewer::Properties: attachmentProperties(mCurrentContent); break; case Viewer::Save: { QUrl currentUrl; if (Util::saveContents(mMainWindow, KMime::Content::List() << mCurrentContent, currentUrl)) { showOpenAttachmentFolderWidget(currentUrl); } break; } case Viewer::OpenWith: attachmentOpenWith(mCurrentContent); break; case Viewer::Open: attachmentOpen(mCurrentContent); break; case Viewer::View: attachmentView(mCurrentContent); break; case Viewer::Copy: attachmentCopy(KMime::Content::List() << mCurrentContent); break; case Viewer::ScrollTo: scrollToAttachment(mCurrentContent); break; case Viewer::ReplyMessageToAuthor: replyMessageToAuthor(mCurrentContent); break; case Viewer::ReplyMessageToAll: replyMessageToAll(mCurrentContent); break; } } void ViewerPrivate::replyMessageToAuthor(KMime::Content *atmNode) { if (atmNode) { const bool isEncapsulatedMessage = atmNode->parent() && atmNode->parent()->bodyIsMessage(); if (isEncapsulatedMessage) { Q_EMIT replyMessageTo(atmNode->parent()->bodyAsMessage(), false); } } } void ViewerPrivate::replyMessageToAll(KMime::Content *atmNode) { if (atmNode) { const bool isEncapsulatedMessage = atmNode->parent() && atmNode->parent()->bodyIsMessage(); if (isEncapsulatedMessage) { Q_EMIT replyMessageTo(atmNode->parent()->bodyAsMessage(), true); } } } void ViewerPrivate::slotSpeakText() { const QString text = mViewer->selectedText(); if (!text.isEmpty()) { mTextToSpeechWidget->say(text); } } QUrl ViewerPrivate::imageUrl() const { QUrl url; if (mImageUrl.scheme() == QLatin1String("cid")) { url = QUrl(MessageViewer::WebEngineEmbedPart::self()->contentUrl(mImageUrl.path())); } else { url = mImageUrl; } return url; } void ViewerPrivate::slotCopyImageLocation() { #ifndef QT_NO_CLIPBOARD QApplication::clipboard()->setText(imageUrl().url()); #endif } void ViewerPrivate::slotCopySelectedText() { #ifndef QT_NO_CLIPBOARD QString selection = mViewer->selectedText(); selection.replace(QChar::Nbsp, QLatin1Char(' ')); QApplication::clipboard()->setText(selection); #endif } void ViewerPrivate::viewerSelectionChanged() { mActionCollection->action(QStringLiteral("kmail_copy"))->setEnabled( !mViewer->selectedText().isEmpty()); } void ViewerPrivate::selectAll() { mViewer->selectAll(); } void ViewerPrivate::slotUrlCopy() { #ifndef QT_NO_CLIPBOARD QClipboard *clip = QApplication::clipboard(); if (mClickedUrl.scheme() == QLatin1String("mailto")) { // put the url into the mouse selection and the clipboard const QString address = KEmailAddress::decodeMailtoUrl(mClickedUrl); clip->setText(address, QClipboard::Clipboard); clip->setText(address, QClipboard::Selection); KPIM::BroadcastStatus::instance()->setStatusMsg(i18n("Address copied to clipboard.")); } else { // put the url into the mouse selection and the clipboard clip->setText(mClickedUrl.url(), QClipboard::Clipboard); clip->setText(mClickedUrl.url(), QClipboard::Selection); KPIM::BroadcastStatus::instance()->setStatusMsg(i18n("URL copied to clipboard.")); } #endif } void ViewerPrivate::slotSaveMessage() { if (!mMessageItem.hasPayload()) { if (mMessageItem.isValid()) { qCWarning(MESSAGEVIEWER_LOG) << "Payload is not a MessagePtr!"; } return; } Util::saveMessageInMbox(Akonadi::Item::List() << mMessageItem, mMainWindow); } void ViewerPrivate::saveRelativePosition() { mViewer->saveRelativePosition(); } //TODO(Andras) inline them bool ViewerPrivate::htmlMail() const { if (mDisplayFormatMessageOverwrite == Viewer::UseGlobalSetting) { return mHtmlMailGlobalSetting; } else { return mDisplayFormatMessageOverwrite == Viewer::Html; } } bool ViewerPrivate::htmlLoadExternal() const { return (mHtmlLoadExternalGlobalSetting && !mHtmlLoadExtOverride) || (!mHtmlLoadExternalGlobalSetting && mHtmlLoadExtOverride); } void ViewerPrivate::setDisplayFormatMessageOverwrite(Viewer::DisplayFormatMessage format) { mDisplayFormatMessageOverwrite = format; // keep toggle display mode action state in sync. if (mToggleDisplayModeAction) { mToggleDisplayModeAction->setChecked(htmlMail()); } } bool ViewerPrivate::htmlMailGlobalSetting() const { return mHtmlMailGlobalSetting; } Viewer::DisplayFormatMessage ViewerPrivate::displayFormatMessageOverwrite() const { return mDisplayFormatMessageOverwrite; } void ViewerPrivate::setHtmlLoadExtOverride(bool override) { mHtmlLoadExtOverride = override; } bool ViewerPrivate::htmlLoadExtOverride() const { return mHtmlLoadExtOverride; } void ViewerPrivate::setDecryptMessageOverwrite(bool overwrite) { mDecrytMessageOverwrite = overwrite; } bool ViewerPrivate::showSignatureDetails() const { return mShowSignatureDetails; } void ViewerPrivate::setShowSignatureDetails(bool showDetails) { mShowSignatureDetails = showDetails; } void ViewerPrivate::setFullToAddressList(bool showFullTo) { mViewer->executeHideShowToAddressScripts(showFullTo); } void ViewerPrivate::setFullCcAddressList(bool showFullCc) { mViewer->executeHideShowCcAddressScripts(showFullCc); } void ViewerPrivate::setShowAttachmentQuicklist(bool showAttachmentQuicklist) { mShowAttachmentQuicklist = showAttachmentQuicklist; mViewer->executeHideShowAttachmentsScripts(mShowAttachmentQuicklist); } +void ViewerPrivate::setHideEncryptionDetails(bool encDetails) +{ + mViewer->executeHideShowEncryptionDetails(encDetails); +} + void ViewerPrivate::scrollToAttachment(KMime::Content *node) { const QString indexStr = node->index().toString(); // The anchors for this are created in ObjectTreeParser::parseObjectTree() mViewer->scrollToAnchor(QLatin1String("att") + indexStr); // Remove any old color markings which might be there const KMime::Content *root = node->topLevel(); const int totalChildCount = Util::allContents(root).size(); for (int i = 0; i < totalChildCount + 1; ++i) { mViewer->removeAttachmentMarking(QStringLiteral("attachmentDiv%1").arg(i + 1)); } // Don't mark hidden nodes, that would just produce a strange yellow line if (mNodeHelper->isNodeDisplayedHidden(node)) { return; } // Now, color the div of the attachment in yellow, so that the user sees what happened. // We created a special marked div for this in writeAttachmentMarkHeader() in ObjectTreeParser, // find and modify that now. mViewer->markAttachment(QLatin1String("attachmentDiv") + indexStr, QStringLiteral("border:2px solid %1").arg(cssHelper()->pgpWarnColor(). name())); } void ViewerPrivate::setUseFixedFont(bool useFixedFont) { mUseFixedFont = useFixedFont; if (mToggleFixFontAction) { mToggleFixFontAction->setChecked(mUseFixedFont); } } void ViewerPrivate::toggleFullAddressList() { toggleFullAddressList(QStringLiteral("To")); toggleFullAddressList(QStringLiteral("Cc")); } QString ViewerPrivate::recipientsQuickListLinkHtml(const QString &field) { const QString urlHandleShow = QLatin1String("kmail:hideFull") + field + QLatin1String( "AddressList"); const QString imgSrcShow = QStringLiteral("quicklistOpened.png"); const QString urlHandleHide = QLatin1String("kmail:showFull") + field + QLatin1String( "AddressList"); const QString imgSrcHide = QStringLiteral("quicklistClosed.png"); const QString visibility = QStringLiteral("style=\"display:none;\""); return QStringLiteral("") +QStringLiteral("").arg(urlHandleShow).arg(field) +QStringLiteral("\"%2\"").arg(QUrl::fromLocalFile(MessageViewer:: IconNameCache:: instance()-> iconPathFromLocal( imgSrcShow)).url(), /*altTextShow*/ QString()) +QStringLiteral("") +QStringLiteral("").arg(urlHandleHide).arg(field). arg(visibility) +QStringLiteral("\"%2\"").arg(QUrl::fromLocalFile(MessageViewer:: IconNameCache:: instance()-> iconPathFromLocal( imgSrcHide)).url(), /*altTextHide*/ QString()) +QStringLiteral("") +QStringLiteral(""); } void ViewerPrivate::toggleFullAddressList(const QString &field) { if (field == QLatin1String("To") || (field == QLatin1String("Cc"))) { mViewer->toggleFullAddressList(field, bind(&ViewerPrivate::recipientsQuickListLinkHtml, this, field)); } } void ViewerPrivate::itemFetchResult(KJob *job) { if (job->error()) { displaySplashPage(i18n("Message loading failed: %1.", job->errorText())); } else { Akonadi::ItemFetchJob *fetch = qobject_cast(job); Q_ASSERT(fetch); if (fetch->items().isEmpty()) { displaySplashPage(i18n("Message not found.")); } else { setMessageItem(fetch->items().constFirst()); } } } void ViewerPrivate::slotItemChanged(const Akonadi::Item &item, const QSet &parts) { if (item.id() != messageItem().id()) { qCDebug(MESSAGEVIEWER_LOG) << "Update for an already forgotten item. Weird."; return; } if (parts.contains("PLD:RFC822")) { setMessageItem(item, MimeTreeParser::Force); } } void ViewerPrivate::slotItemMoved(const Akonadi::Item &item, const Akonadi::Collection &, const Akonadi::Collection &) { // clear the view after the current item has been moved somewhere else (e.g. to trash) if (item.id() == messageItem().id()) { slotClear(); } } void ViewerPrivate::slotClear() { q->clear(MimeTreeParser::Force); Q_EMIT itemRemoved(); } void ViewerPrivate::slotMessageRendered() { if (!mMessageItem.isValid()) { return; } /** * This slot might be called multiple times for the same message if * some asynchronous mementos are involved in rendering. Therefor we * have to make sure we execute the MessageLoadedHandlers only once. */ if (mMessageItem.id() == mPreviouslyViewedItem) { return; } mPreviouslyViewedItem = mMessageItem.id(); for (AbstractMessageLoadedHandler *handler : qAsConst(mMessageLoadedHandlers)) { handler->setItem(mMessageItem); } } void ViewerPrivate::setZoomFactor(qreal zoomFactor) { mZoomActionMenu->setWebViewerZoomFactor(zoomFactor); } void ViewerPrivate::goOnline() { Q_EMIT makeResourceOnline(Viewer::AllResources); } void ViewerPrivate::goResourceOnline() { Q_EMIT makeResourceOnline(Viewer::SelectedResource); } void ViewerPrivate::slotSaveMessageDisplayFormat() { if (mMessageItem.isValid()) { MessageViewer::ModifyMessageDisplayFormatJob *job = new MessageViewer::ModifyMessageDisplayFormatJob(mSession, this); job->setMessageFormat(displayFormatMessageOverwrite()); job->setMessageItem(mMessageItem); job->setRemoteContent(htmlLoadExtOverride()); job->start(); } } void ViewerPrivate::slotResetMessageDisplayFormat() { if (mMessageItem.isValid()) { if (mMessageItem.hasAttribute()) { MessageViewer::ModifyMessageDisplayFormatJob *job = new MessageViewer::ModifyMessageDisplayFormatJob(mSession, this); job->setMessageItem(mMessageItem); job->setResetFormat(true); job->start(); } } } void ViewerPrivate::slotMessageMayBeAScam() { if (mMessageItem.isValid()) { if (mMessageItem.hasAttribute()) { const MessageViewer::ScamAttribute *const attr = mMessageItem.attribute(); if (attr && !attr->isAScam()) { return; } } if (mMessageItem.hasPayload()) { KMime::Message::Ptr message = mMessageItem.payload(); const QString email = QLatin1String(KEmailAddress::firstEmailAddress(message->from()->as7BitString( false))); const QStringList lst = MessageViewer::MessageViewerSettings::self()->scamDetectionWhiteList(); if (lst.contains(email)) { return; } } } mScamDetectionWarning->slotShowWarning(); } void ViewerPrivate::slotMessageIsNotAScam() { if (mMessageItem.isValid()) { MessageViewer::ScamAttribute *attr = mMessageItem.attribute( Akonadi::Item::AddIfMissing); attr->setIsAScam(false); Akonadi::ItemModifyJob *modify = new Akonadi::ItemModifyJob(mMessageItem, mSession); modify->setIgnorePayload(true); modify->disableRevisionCheck(); connect(modify, &KJob::result, this, &ViewerPrivate::slotModifyItemDone); } } void ViewerPrivate::slotModifyItemDone(KJob *job) { if (job && job->error()) { qCWarning(MESSAGEVIEWER_LOG) << " Error trying to change attribute:" << job->errorText(); } } void ViewerPrivate::saveMainFrameScreenshotInFile(const QString &filename) { mViewer->saveMainFrameScreenshotInFile(filename); } void ViewerPrivate::slotAddToWhiteList() { if (mMessageItem.isValid()) { if (mMessageItem.hasPayload()) { KMime::Message::Ptr message = mMessageItem.payload(); const QString email = QLatin1String(KEmailAddress::firstEmailAddress(message->from()->as7BitString( false))); QStringList lst = MessageViewer::MessageViewerSettings::self()->scamDetectionWhiteList(); if (lst.contains(email)) { return; } lst << email; MessageViewer::MessageViewerSettings::self()->setScamDetectionWhiteList(lst); MessageViewer::MessageViewerSettings::self()->save(); } } } void ViewerPrivate::slotFormSubmittedForbidden() { mSubmittedFormWarning->showWarning(); } void ViewerPrivate::addHelpTextAction(QAction *act, const QString &text) { act->setStatusTip(text); act->setToolTip(text); act->setWhatsThis(text); } void ViewerPrivate::slotRefreshMessage(const Akonadi::Item &item) { if (item.id() == mMessageItem.id()) { setMessageItem(item, MimeTreeParser::Force); } } void ViewerPrivate::slotServiceUrlSelected( PimCommon::ShareServiceUrlManager::ServiceType serviceType) { const QUrl url = mShareServiceManager->generateServiceUrl(mClickedUrl.toString(), QString(), serviceType); mShareServiceManager->openUrl(url); } QList ViewerPrivate::interceptorUrlActions( const WebEngineViewer::WebHitTestResult &result) const { return mViewer->interceptorUrlActions(result); } void ViewerPrivate::setPrintElementBackground(bool printElementBackground) { mViewer->setPrintElementBackground(printElementBackground); } void ViewerPrivate::slotToggleEmoticons() { mForceEmoticons = !mForceEmoticons; headerStylePlugin()->headerStyle()->setShowEmoticons(mForceEmoticons); update(MimeTreeParser::Force); } diff --git a/messageviewer/src/viewer/viewer_p.h b/messageviewer/src/viewer/viewer_p.h index ef4e9bc2..9a9474ee 100644 --- a/messageviewer/src/viewer/viewer_p.h +++ b/messageviewer/src/viewer/viewer_p.h @@ -1,695 +1,698 @@ /* Copyright (c) 1997 Markus Wuebben Copyright (C) 2009 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net Copyright (c) 2009 Andras Mantia This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef MAILVIEWER_P_H #define MAILVIEWER_P_H #include #include "config-messageviewer.h" #include "viewer.h" //not so nice, it is actually for the enums from MailViewer #include "PimCommon/ShareServiceUrlManager" #include "messageviewer/viewerplugininterface.h" #include #include #include #include #include #include #include #include #include #include #include #include namespace KIO { class Job; } class QAction; class KActionCollection; class KSelectAction; class KToggleAction; class QMenu; class KActionMenu; class QPoint; class QSplitter; class QModelIndex; class QPrinter; namespace KPIMTextEdit { class SlideContainer; class TextToSpeechWidget; } namespace PimCommon { class ShareServiceUrlManager; } namespace MimeTreeParser { class AttachmentStrategy; class HtmlWriter; class ObjectTreeParser; } namespace WebEngineViewer { class WebHitTestResult; class FindBarWebEngineView; class ZoomActionMenu; class LocalDataBaseManager; } namespace MessageViewer { class HeaderStylePlugin; class CSSHelper; class MailWebEngineView; class WebEnginePartHtmlWriter; class HtmlStatusBar; class ScamDetectionWarningWidget; class MimePartTreeView; class OpenAttachmentFolderWidget; class HeaderStyleMenuManager; class ViewerPluginToolManager; class ViewerPluginInterface; class SubmittedFormWarningWidget; class MailSourceWebEngineViewer; /** \brief Private class for the Viewer, the main widget in the messageviewer library. This class creates all subwidgets, like the MailWebView, the HtmlStatusBar and the FindBarMailWebView. Also, ViewerPrivate creates and exposes all actions. \par Displaying a message Before displaying a message, a message needs to be set. This can be done in two ways, with setMessageItem() and with setMessage(). setMessageItem() is the preferred way, as the viewer can then remember the Akonadi::Item belonging to the message. The Akonadi::Item is needed when modifying the message, for example when editing or deleting an attachment. Sometimes passing an Akonadi::Item to the viewer is not possible, for example when double-clicking an attached message, in which case a new KMime::Message is constructed out of the attachment, and a separate window is opened for it. In this case, the KMime::Message has no associated Akonadi::Item. If there is an Akonadi::Item available, it will be monitored for changes and the viewer automatically updated on external changes. Once a message is set, update() is called. update() can also be called after the message has already been displayed. As an example, this is the case when the user decides to decrypt the message. The decryption can happen async, and once the decryption is finished, update() is called to display the now decrypted content. See the documentation of MimeTreeParser::ObjectTreeParser on how exactly decryption is handled. update() is just a thin wrapper that calls updateReaderWin(). The only difference is that update() has a timer that prevents too many slow calls to updateReaderWin() in a short time frame. updateReaderWin() again is only a thin wrapper that resets some state and then calls displayMessage(). displayMessage() itself is again a thin wrapper, which starts the MimeTreeParser::HtmlWriter and then calls parseMsg(). Finally, parseMsg() does the real work. It uses MimeTreeParser::ObjectTreeParser ::parseObjectTree() to let the MimeTreeParser::ObjectTreeParser parse the message and generate the HTML code for it. As mentioned before, it can happen that the MimeTreeParser::ObjectTreeParser needs to do some operation that happens async, for example decrypting. In this case, the MimeTreeParser::ObjectTreeParser will create a BodyPartMemento, which basically is a wrapper around the job that does the async operation. Once the async operation is finished. the BodyPartMemento will trigger an update() of ViewerPrivate, so that MimeTreeParser::ObjectTreeParser ::parseObjectTree() gets called again and the MimeTreeParser::ObjectTreeParser then can generate HTML which has the decrypted content of the message. Again, see the documentation of MimeTreeParser::ObjectTreeParser for the details. Additionally, parseMsg() does some evil hack for saving unencrypted messages should the config option for that be set. \par Displaying a MIME part of the message The viewer can show only a part of the message, for example by clicking on a MIME part in the message structure viewer or by double-clicking an attached message. In this case, setMessagePart() is called. There are two of these functions. One even has special handling for images, special handling for binary attachments and special handling of attached messages. In the last case, a new KMime::Message is constructed and set as the main message with setMessage(). \par Attachment Handling Some of those actions are actions that operate on a single attachment. For those, there is usually a slot, like slotAttachmentCopy(). These actions are triggered from the attachment context menu, which is shown in showAttachmentPopup(). The actions are connected to slotHandleAttachment() when they are activated. The action to edit an attachment uses the EditorWatcher to detect when editing with an external editor is finished. Upon finishing, slotAttachmentEditDone() is called, which then creates an ItemModifyJob to store the changes of the attachment. A map of currently active EditorWatcher and their KMime::Content is available in mEditorWatchers. For most attachment actions, the attachment is first written to a temp file. The action is then executed on this temp file. Writing the attachment to a temp file is done with MimeTreeParser::NodeHelper::writeNodeToTempFile(). This method is called before opening or copying an attachment or when rendering the attachment list. The MimeTreeParser::ObjectTreeParser also calls MimeTreeParser::NodeHelper::writeNodeToTempFile() in some places. Once the temp file is written, MimeTreeParser::NodeHelper::tempFileUrlFromNode() can be used to get the file name of the temp file for a specific MIME part. This is for example used by the handler for 'attachment:' URLs, AttachmentURLHandler. Since URLs for attachments are in the "attachment:" scheme, dragging them as-is to outside applications wouldn't work, since other applications don't understand this scheme. Therefore, the viewer has special handling for dragging URLs: In eventFilter(), drags are detected, and the URL handler is called to deal with the drag. The attachment URL handler then starts a drag with the file:// URL of the temp file of the attachment, which it gets with MimeTreeParser::NodeHelper::tempFileUrlFromNode(). TODO: How are attachment handled that are loaded on demand? How does prepareHandleAttachment() work? TODO: This temp file handling is a big mess and could use a rewrite, especially in the face of load on demand. There shouldn't be the need to write out tempfiles until really needed. Some header styles display an attachment list in the header. The HTML code for the attachment list cannot be generated by the HeaderStyle itself, since that does not know about all attachments. Therefore, the attachment list needs to be created by ViewerPrivate. For this, the HeaderStyle writes out a placeholder for the attachment list when it creates the HTML for the header. Once the MimeTreeParser::ObjectTreeParser is finished with the message, injectAttachments() is called. injectAttachments() searches for the placeholder and replaces that with the real HTML code for the attachments. One of the attachment actions is to scoll to the attachment. That action is only available when right-clicking the header. The action scrolls to the attachment in the body and draws a yellow frame around the attachment. This is done in scrollToAttachment(). The attachment in the body and the div which is used for the colored frame are both created by the MimeTreeParser::ObjectTreeParser . \par Misc ViewerPrivate holds the MimeTreeParser::NodeHelper, which is passed on to the MimeTreeParser::ObjectTreeParser when it needs it. It also holds the HeaderStyle, HeaderStrategy, MimeTreeParser::AttachmentStrategy, CSSHelper, MimeTreeParser::HtmlWriter and more, some of them again passed to the MimeTreeParser::ObjectTreeParser when it needs it. @author andras@kdab.net */ class ViewerPrivate : public QObject { Q_OBJECT public: ViewerPrivate(Viewer *aParent, QWidget *mainWindow, KActionCollection *actionCollection); virtual ~ViewerPrivate(); /** Returns message part from given URL or null if invalid. The URL's path is a KMime::ContentIndex path, or an index for the extra nodes, followed by : and the ContentIndex path. */ KMime::Content *nodeFromUrl(const QUrl &url) const; /** Open the attachment pointed to the node. * @param fileName - if not empty, use this file to load the attachment content */ void openAttachment(KMime::Content *node, const QUrl &url); /** Delete the attachment the @param node points to. Returns false if the user cancelled the deletion, true in all other cases (including failure to delete the attachment!) */ bool deleteAttachment(KMime::Content *node, bool showWarning = true); void attachmentProperties(KMime::Content *node); void attachmentCopy(const KMime::Content::List &contents); /** Edit the attachment the @param node points to. Returns false if the user cancelled the editing, true in all other cases! */ void editAttachment(KMime::Content *node, bool showWarning = true); void scrollToAnchor(const QString &anchor); void showAttachmentPopup(KMime::Content *node, const QString &name, const QPoint &p); /** * Sets the current attachment ID and the current attachment temporary filename * to the given values. * Call this so that slotHandleAttachment() knows which attachment to handle. */ void prepareHandleAttachment(KMime::Content *node); void postProcessMessage(MimeTreeParser::ObjectTreeParser *otp, MimeTreeParser::KMMsgEncryptionState encryptionState); QString createAtmFileLink(const QString &atmFileName) const; KService::Ptr getServiceOffer(KMime::Content *content); KMime::Content::List selectedContents(); void attachmentOpenWith(KMime::Content *node, const KService::Ptr &offer = KService::Ptr()); void attachmentOpen(KMime::Content *node); /** Return the MimeTreeParser::HtmlWriter connected to the MailWebView we use */ MimeTreeParser::HtmlWriter *htmlWriter() const; HeaderStylePlugin *headerStylePlugin() const; CSSHelper *cssHelper() const; MimeTreeParser::NodeHelper *nodeHelper() const; Viewer *viewer() const; Akonadi::Item messageItem() const; KMime::Message::Ptr message() const; /** Returns whether the message should be decryted. */ bool decryptMessage() const; /** Display a generic HTML splash page instead of a message. */ void displaySplashPage(const QString &templateName, const QVariantHash &data, const QByteArray &domain = QByteArray()); void displaySplashPage(const QString &message); /** Enable the displaying of messages again after an splash (or other) page was displayed */ void enableMessageDisplay(); /** Feeds the HTML viewer with the contents of the given message. HTML begin/end parts are written around the message. */ void displayMessage(); /** Parse the given content and generate HTML out of it for display */ void parseContent(KMime::Content *content); /** Creates a nice mail header depending on the current selected header style. */ QString writeMsgHeader(KMime::Message *aMsg, KMime::Content *vCardNode = nullptr, bool topLevel = false); /** show window containing information about a vCard. */ void showVCard(KMime::Content *msgPart); void saveMainFrameScreenshotInFile(const QString &filename); private: /** HTML initialization. */ void initHtmlWidget(); void createOpenWithMenu(QMenu *topMenu, const QString &contentTypeStr, bool fromCurrentContent); public: /** Read settings from app's config file. */ void readConfig(); /** Write settings to app's config file. Calls sync() if withSync is true. */ void writeConfig(bool withSync = true); /** Get/set the message attachment strategy. */ const MimeTreeParser::AttachmentStrategy *attachmentStrategy() const; void setAttachmentStrategy(const MimeTreeParser::AttachmentStrategy *strategy); /** Get selected override character encoding. @return The encoding selected by the user or an empty string if auto-detection is selected. */ QString overrideEncoding() const; /** Set the override character encoding. */ void setOverrideEncoding(const QString &encoding); /** Set printing mode */ void setPrinting(bool enable); bool printingMode() const; /** Print message. */ void printMessage(const Akonadi::Item &msg); void printPreviewMessage(const Akonadi::Item &message); void resetStateForNewMessage(); void setMessageInternal(const KMime::Message::Ptr &message, MimeTreeParser::UpdateMode updateMode); /** Set the Akonadi item that will be displayed. * @param item - the Akonadi item to be displayed. If it doesn't hold a mail (KMime::Message::Ptr as payload data), * an empty page is shown. * @param updateMode - update the display immediately or not. See MailViewer::UpdateMode. */ void setMessageItem(const Akonadi::Item &item, MimeTreeParser::UpdateMode updateMode = MimeTreeParser::Delayed); /** Set the message that shall be shown. * @param msg - the message to be shown. If 0, an empty page is displayed. * @param updateMode - update the display immediately or not. See MailViewer::UpdateMode. */ void setMessage(const KMime::Message::Ptr &msg, MimeTreeParser::UpdateMode updateMode = MimeTreeParser::Delayed); /** Instead of settings a message to be shown sets a message part to be shown */ void setMessagePart(KMime::Content *node); /** Show or hide the Mime Tree Viewer if configuration is set to smart mode. */ void showHideMimeTree(); /** View message part of type message/RFC822 in extra viewer window. */ void atmViewMsg(const KMime::Message::Ptr &message); void adjustLayout(); void createWidgets(); void createActions(); void showContextMenu(KMime::Content *content, const QPoint &point); KToggleAction *actionForAttachmentStrategy(const MimeTreeParser::AttachmentStrategy *); /** Read override codec from configuration */ void readGlobalOverrideCodec(); /** Get codec corresponding to the currently selected override character encoding. @return The override codec or 0 if auto-detection is selected. */ const QTextCodec *overrideCodec() const; QString renderAttachments(KMime::Content *node, const QColor &bgColor) const; KMime::Content *findContentByType(KMime::Content *content, const QByteArray &type); //TODO(Andras) move to MimeTreeParser::NodeHelper /** Return a QTextCodec for the specified charset. * This function is a bit more tolerant, than QTextCodec::codecForName */ static const QTextCodec *codecForName(const QByteArray &_str); //TODO(Andras) move to a utility class? /** Saves the relative position of the scroll view. Call this before calling update() if you want to preserve the current view. */ void saveRelativePosition(); bool htmlMail() const; bool htmlLoadExternal() const; bool htmlMailGlobalSetting() const; /** Get the html override setting */ Viewer::DisplayFormatMessage displayFormatMessageOverwrite() const; /** Override default html mail setting */ void setDisplayFormatMessageOverwrite(Viewer::DisplayFormatMessage format); /** Get the load external references override setting */ bool htmlLoadExtOverride() const; /** Override default load external references setting */ void setHtmlLoadExtOverride(bool override); /** Enforce message decryption. */ void setDecryptMessageOverwrite(bool overwrite = true); /** Show signature details. */ bool showSignatureDetails() const; /** Show signature details. */ void setShowSignatureDetails(bool showDetails = true); /* show or hide the list that points to the attachments */ void setShowAttachmentQuicklist(bool showAttachmentQuicklist = true); + /* show or hide encryption details */ + void setHideEncryptionDetails(bool encDetails = true); + void scrollToAttachment(KMime::Content *node); void setUseFixedFont(bool useFixedFont); void attachmentView(KMime::Content *atmNode); void setFullToAddressList(bool showFullTo); void setFullCcAddressList(bool showFullCc); /** Show/Hide the field with id "field" */ void toggleFullAddressList(const QString &field); void setZoomFactor(qreal zoomFactor); void goOnline(); void goResourceOnline(); void showOpenAttachmentFolderWidget(const QUrl &url); bool mimePartTreeIsEmpty() const; void setPluginName(const QString &pluginName); QList viewerPluginActionList( MessageViewer::ViewerPluginInterface::SpecificFeatureTypes features); QList interceptorUrlActions(const WebEngineViewer::WebHitTestResult &result) const; void setPrintElementBackground(bool printElementBackground); bool showEmoticons() const; void checkPhishingUrl(); void executeRunner(const QUrl &url); QUrl imageUrl() const; private Q_SLOTS: void slotActivatePlugin(MessageViewer::ViewerPluginInterface *interface); void slotModifyItemDone(KJob *job); void slotMessageMayBeAScam(); void slotMessageIsNotAScam(); void slotAddToWhiteList(); void slotFormSubmittedForbidden(); /** Show hide all fields specified inside this function */ void toggleFullAddressList(); void itemFetchResult(KJob *job); void slotItemChanged(const Akonadi::Item &item, const QSet &partIdentifiers); void slotItemMoved(const Akonadi::Item &, const Akonadi::Collection &, const Akonadi::Collection &); void itemModifiedResult(KJob *job); void collectionFetchedForStoringDecryptedMessage(KJob *job); void slotClear(); void slotMessageRendered(); void slotOpenWithAction(QAction *act); void slotOpenWithActionCurrentContent(QAction *act); void slotOpenWithDialog(); void slotOpenWithDialogCurrentContent(); void saveSplitterSizes() const; void slotRefreshMessage(const Akonadi::Item &item); void slotServiceUrlSelected(PimCommon::ShareServiceUrlManager::ServiceType serviceType); void slotStyleChanged(MessageViewer::HeaderStylePlugin *plugin); void slotStyleUpdated(); void slotWheelZoomChanged(int numSteps); void slotOpenInBrowser(); void slotExportHtmlPageFailed(); void slotExportHtmlPageSuccess(const QString &filename); void slotHandlePagePrinted(bool result); void slotToggleEmoticons(); public Q_SLOTS: /** An URL has been activate with a click. */ void slotUrlOpen(const QUrl &url = QUrl()); void slotOpenUrl(); /** The mouse has moved on or off an URL. */ void slotUrlOn(const QString &link); /** The user presses the right mouse button on an URL. */ void slotUrlPopup(const WebEngineViewer::WebHitTestResult &result); /** The user selected "Find" from the menu. */ void slotFind(); /** The user toggled the "Fixed Font" flag from the view menu. */ void slotToggleFixedFont(); void slotToggleMimePartTree(); /** Show the message source */ void slotShowMessageSource(); /** Refresh the reader window */ void updateReaderWin(); void slotMimePartSelected(const QModelIndex &index); void slotIconicAttachments(); void slotSmartAttachments(); void slotInlineAttachments(); void slotHideAttachments(); void slotHeaderOnlyAttachments(); /** Some attachment operations. */ void slotDelayedResize(); /** Print message. Called on as a response of finished() signal of mPartHtmlWriter after rendering is finished. In the very end it deletes the KMReaderWin window that was created for the purpose of rendering. */ void slotPrintMessage(); void slotPrintPreview(); void slotSetEncoding(); void executeCustomScriptsAfterLoading(); void slotSettingsChanged(); void slotMimeTreeContextMenuRequested(const QPoint &pos); void slotAttachmentOpenWith(); void slotAttachmentOpen(); void slotAttachmentSaveAs(); void slotAttachmentSaveAll(); void slotAttachmentView(); void slotAttachmentProperties(); void slotAttachmentCopy(); void slotAttachmentDelete(); void slotAttachmentEdit(); void slotLevelQuote(int l); /** Toggle display mode between HTML and plain text. */ void slotToggleHtmlMode(); void slotLoadExternalReference(); /** * Does an action for the current attachment. * The action is defined by the KMHandleAttachmentCommand::AttachmentAction * enum. * prepareHandleAttachment() needs to be called before calling this to set the * correct attachment ID. */ void slotHandleAttachment(int action); /** Copy the selected text to the clipboard */ void slotCopySelectedText(); void viewerSelectionChanged(); /** Select message body. */ void selectAll(); /** Copy URL in mUrlCurrent to clipboard. Removes "mailto:" at beginning of URL before copying. */ void slotUrlCopy(); void slotSaveMessage(); /** Re-parse the current message. */ void update(MimeTreeParser::UpdateMode updateMode = MimeTreeParser::Delayed); void slotSpeakText(); void slotCopyImageLocation(); void slotSaveMessageDisplayFormat(); void slotResetMessageDisplayFormat(); void slotGeneralFontChanged(); Q_SIGNALS: void showStatusBarMessage(const QString &message); void popupMenu(const Akonadi::Item &msg, const QUrl &url, const QUrl &imageUrl, const QPoint &mousePos); void displayPopupMenu(const Akonadi::Item &msg, const WebEngineViewer::WebHitTestResult &result, const QPoint &mousePos); void urlClicked(const Akonadi::Item &msg, const QUrl &url); void requestConfigSync(); void showReader(KMime::Content *aMsgPart, bool aHTML, const QString &encoding); void showMessage(const KMime::Message::Ptr &message, const QString &encoding); void replyMessageTo(const KMime::Message::Ptr &message, bool replyToAll); void itemRemoved(); void makeResourceOnline(MessageViewer::Viewer::ResourceOnlineMode mode); void changeDisplayMail(Viewer::DisplayFormatMessage, bool); void moveMessageToTrash(); void pageIsScrolledToBottom(bool); void printingFinished(); private: QString attachmentInjectionHtml(); QString recipientsQuickListLinkHtml(const QString &); Akonadi::Relation relatedNoteRelation() const; void addHelpTextAction(QAction *act, const QString &text); void readGravatarConfig(); void replyMessageToAuthor(KMime::Content *atmNode); void replyMessageToAll(KMime::Content *atmNode); bool urlIsAMalwareButContinue(); void slotCheckedUrlFinished(const QUrl &url, WebEngineViewer::CheckPhishingUrlUtil::UrlStatus status); MimeTreeParser::NodeHelper *mNodeHelper = nullptr; void slotDelayPrintPreview(); public: bool mHtmlMailGlobalSetting; bool mHtmlLoadExternalGlobalSetting; bool mHtmlLoadExtOverride; KMime::Message::Ptr mMessage; //the current message, if it was set manually Akonadi::Item mMessageItem; //the message item from Akonadi // widgets: QSplitter *mSplitter = nullptr; QWidget *mBox = nullptr; HtmlStatusBar *mColorBar = nullptr; #ifndef QT_NO_TREEVIEW MimePartTreeView *mMimePartTree = nullptr; #endif MailWebEngineView *mViewer = nullptr; WebEngineViewer::FindBarWebEngineView *mFindBar = nullptr; const MimeTreeParser::AttachmentStrategy *mAttachmentStrategy = nullptr; QTimer mUpdateReaderWinTimer; QTimer mResizeTimer; QString mOverrideEncoding; QString mOldGlobalOverrideEncoding; // used to detect changes of the global override character encoding QString mPicsPath; /// This is true if the viewer currently is displaying a message. Can be false, for example when /// the splash/busy page is displayed. bool mMsgDisplay; CSSHelper *mCSSHelper = nullptr; bool mUseFixedFont; bool mPrinting; QWidget *mMainWindow = nullptr; KActionCollection *mActionCollection = nullptr; QAction *mCopyAction = nullptr; QAction *mCopyURLAction = nullptr; QAction *mUrlOpenAction = nullptr; QAction *mSelectAllAction = nullptr; QAction *mScrollUpAction = nullptr; QAction *mScrollDownAction = nullptr; QAction *mScrollUpMoreAction = nullptr; QAction *mScrollDownMoreAction = nullptr; QAction *mViewSourceAction = nullptr; QAction *mSaveMessageAction = nullptr; QAction *mFindInMessageAction = nullptr; QAction *mSaveMessageDisplayFormat = nullptr; QAction *mResetMessageDisplayFormat = nullptr; KToggleAction *mDisableEmoticonAction = nullptr; KToggleAction *mHeaderOnlyAttachmentsAction = nullptr; KSelectAction *mSelectEncodingAction = nullptr; KToggleAction *mToggleFixFontAction = nullptr; KToggleAction *mToggleDisplayModeAction = nullptr; KToggleAction *mToggleMimePartTreeAction = nullptr; QAction *mSpeakTextAction = nullptr; QAction *mCopyImageLocation = nullptr; QUrl mHoveredUrl; QUrl mClickedUrl; QUrl mImageUrl; QPoint mLastClickPosition; bool mCanStartDrag; MimeTreeParser::HtmlWriter *mHtmlWriter; /** Used only to be able to connect and disconnect finished() signal in printMsg() and slotPrintMsg() since mHtmlWriter points only to abstract non-QObject class. */ QPointer mPartHtmlWriter; int mLevelQuote; bool mDecrytMessageOverwrite; bool mShowSignatureDetails; bool mShowAttachmentQuicklist; bool mForceEmoticons; int mRecursionCountForDisplayMessage; KMime::Content *mCurrentContent = nullptr; KMime::Content *mMessagePartNode = nullptr; QString mMessagePath; QColor mForegroundError; QColor mBackgroundError; Viewer *const q; Akonadi::Session *mSession = nullptr; Akonadi::Monitor mMonitor; QSet mMessageLoadedHandlers; Akonadi::Item::Id mPreviouslyViewedItem; MessageViewer::ScamDetectionWarningWidget *mScamDetectionWarning = nullptr; MessageViewer::OpenAttachmentFolderWidget *mOpenAttachmentFolderWidget = nullptr; MessageViewer::SubmittedFormWarningWidget *mSubmittedFormWarning = nullptr; KPIMTextEdit::TextToSpeechWidget *mTextToSpeechWidget = nullptr; Viewer::DisplayFormatMessage mDisplayFormatMessageOverwrite; KPIMTextEdit::SlideContainer *mSliderContainer = nullptr; PimCommon::ShareServiceUrlManager *mShareServiceManager = nullptr; KActionMenu *mShareServiceUrlMenu = nullptr; MessageViewer::HeaderStylePlugin *mHeaderStylePlugin = nullptr; MessageViewer::HeaderStyleMenuManager *mHeaderStyleMenuManager = nullptr; MessageViewer::ViewerPluginToolManager *mViewerPluginToolManager = nullptr; WebEngineViewer::ZoomActionMenu *mZoomActionMenu = nullptr; QPrinter *mCurrentPrinter = nullptr; QList > mListMailSourceViewer; WebEngineViewer::LocalDataBaseManager *mPhishingDatabase = nullptr; }; } #endif diff --git a/messageviewer/src/viewer/webengine/mailwebenginescript.cpp b/messageviewer/src/viewer/webengine/mailwebenginescript.cpp index dc99aa9b..f6b257e9 100644 --- a/messageviewer/src/viewer/webengine/mailwebenginescript.cpp +++ b/messageviewer/src/viewer/webengine/mailwebenginescript.cpp @@ -1,88 +1,101 @@ /* Copyright (C) 2016-2017 Laurent Montel This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "mailwebenginescript.h" #include "webengineviewer/webenginescript.h" using namespace MessageViewer; static QString checkJQuery(const char *scriptName) { return QStringLiteral( "if (!qt) { console.warn(\"%1 executed too early, 'qt' variable unknown\"); };\n").arg(QString::fromLatin1( scriptName)); } QString MailWebEngineScript::createShowHideAddressScript(const QString &field, bool hide) { QString source = checkJQuery("createShowHideAddressScript"); if (hide) { source += QString::fromLatin1("qt.jQuery(\"#kmail%1show\").hide();" "qt.jQuery(\"#kmail%1hide\").show();" "qt.jQuery(\"#dotsFull%1AddressList\").hide();" "qt.jQuery(\"#hiddenFull%1AddressList\").show();").arg(field); } else { source += QString::fromLatin1("qt.jQuery(\"#kmail%1hide\").hide();" "qt.jQuery(\"#kmail%1show\").show();" "qt.jQuery(\"#dotsFull%1AddressList\").show();" "qt.jQuery(\"#hiddenFull%1AddressList\").hide();").arg(field); } return source; } QString MailWebEngineScript::manageShowHideToAddress(bool hide) { return MailWebEngineScript::createShowHideAddressScript(QStringLiteral("To"), hide); } QString MailWebEngineScript::manageShowHideCcAddress(bool hide) { return MailWebEngineScript::createShowHideAddressScript(QStringLiteral("Cc"), hide); } QString MailWebEngineScript::manageShowHideAttachments(bool hide) { QString source = checkJQuery("manageShowHideAttachments"); if (hide) { source += QString::fromLatin1("qt.jQuery(\"#kmailhideattachment\").hide();" "qt.jQuery(\"#kmailshowattachment\").show();" "if (!qt.jQuery(\"#attachmentlist\")) { console.warn('attachmentlist not found'); } else { qt.jQuery(\"#attachmentlist\").show(); }"); } else { source += QString::fromLatin1("qt.jQuery('#kmailshowattachment').hide();" "qt.jQuery(\"#kmailhideattachment\").show();" "if (!qt.jQuery(\"#attachmentlist\")) { console.warn('attachmentlist not found'); } else { qt.jQuery(\"#attachmentlist\").hide(); }"); } return source; } +QString MailWebEngineScript::manageShowHideEncryptionDetails(bool hide) +{ + QString source = checkJQuery("manageShowHideEncryptionDetails"); + if (hide) { + source += QString::fromLatin1("qt.jQuery(\".enc-details\").hide();" + "qt.jQuery(\".enc-simple\").show();"); + } else { + source += QString::fromLatin1("qt.jQuery('.enc-simple').hide();" + "qt.jQuery(\".enc-details\").show();"); + } + return source; +} + QString MailWebEngineScript::injectAttachments(const QString &delayedHtml, const QString &elementStr) { const QString source = checkJQuery("injectAttachments") + QString::fromLatin1( "if (!document.getElementById('%1')) { console.warn('NOT FOUND: %1'); };\n" "qt.jQuery('#%1').append('%2')").arg(elementStr, delayedHtml); return source; } QString MailWebEngineScript::replaceInnerHtml(const QString &field, const QString &html) { const QString replaceInnerHtmlStr = QLatin1String("iconFull") + field + QLatin1String( "AddressList"); const QString source = checkJQuery("replaceInnerHtml") + QString::fromLatin1( "qt.jQuery('#%1').append('%2')").arg(replaceInnerHtmlStr, html); return source; } diff --git a/messageviewer/src/viewer/webengine/mailwebenginescript.h b/messageviewer/src/viewer/webengine/mailwebenginescript.h index 001e35f2..69ede26b 100644 --- a/messageviewer/src/viewer/webengine/mailwebenginescript.h +++ b/messageviewer/src/viewer/webengine/mailwebenginescript.h @@ -1,35 +1,36 @@ /* Copyright (C) 2016-2017 Laurent Montel This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef MAILWEBENGINESCRIPT_H #define MAILWEBENGINESCRIPT_H #include "messageviewer_export.h" #include namespace MessageViewer { namespace MailWebEngineScript { MESSAGEVIEWER_EXPORT QString injectAttachments(const QString &delayedHtml, const QString &element); MESSAGEVIEWER_EXPORT QString replaceInnerHtml(const QString &field, const QString &html); MESSAGEVIEWER_EXPORT QString manageShowHideAttachments(bool hide); MESSAGEVIEWER_EXPORT QString manageShowHideToAddress(bool hide); MESSAGEVIEWER_EXPORT QString manageShowHideCcAddress(bool hide); +MESSAGEVIEWER_EXPORT QString manageShowHideEncryptionDetails(bool hide); MESSAGEVIEWER_EXPORT QString createShowHideAddressScript(const QString &field, bool hide); } } #endif // MAILWEBENGINESCRIPT_H diff --git a/messageviewer/src/viewer/webengine/mailwebengineview.cpp b/messageviewer/src/viewer/webengine/mailwebengineview.cpp index 503676ef..69403cce 100644 --- a/messageviewer/src/viewer/webengine/mailwebengineview.cpp +++ b/messageviewer/src/viewer/webengine/mailwebengineview.cpp @@ -1,405 +1,411 @@ /* Copyright (C) 2016-2017 Laurent Montel This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "mailwebengineview.h" #include "mailwebenginepage.h" #include "webengineviewer/webengineaccesskey.h" #include "webengineviewer/webenginescript.h" #include "mailwebenginescript.h" #include "messageviewer/messageviewersettings.h" #include "../urlhandlermanager.h" #include "loadexternalreferencesurlinterceptor/loadexternalreferencesurlinterceptor.h" #include "blockexternalresourcesurlinterceptor/blockexternalresourcesurlinterceptor.h" #include "cidreferencesurlinterceptor/cidreferencesurlinterceptor.h" #include #include #include "scamdetection/scamdetectionwebengine.h" #include "scamdetection/scamcheckshorturl.h" #include #include #include #include #include using namespace MessageViewer; template struct InvokeWrapper { R *receiver; void (C::*memberFunction)(Arg); void operator()(Arg result) { (receiver->*memberFunction)(result); } }; template InvokeWrapper invoke(R *receiver, void (C::*memberFunction)(Arg)) { InvokeWrapper wrapper = {receiver, memberFunction}; return wrapper; } class MessageViewer::MailWebEngineViewPrivate { public: MailWebEngineViewPrivate() { } QUrl mHoveredUrl; QPoint mLastClickPosition; ScamDetectionWebEngine *mScamDetection = nullptr; WebEngineViewer::WebEngineAccessKey *mWebViewAccessKey = nullptr; MessageViewer::LoadExternalReferencesUrlInterceptor *mExternalReference = nullptr; MailWebEnginePage *mPageEngine = nullptr; WebEngineViewer::InterceptorManager *mNetworkAccessManager = nullptr; MessageViewer::ViewerPrivate *mViewer = nullptr; bool mCanStartDrag = false; }; MailWebEngineView::MailWebEngineView(KActionCollection *ac, QWidget *parent) : WebEngineViewer::WebEngineView(parent) , d(new MessageViewer::MailWebEngineViewPrivate) { d->mPageEngine = new MailWebEnginePage(new QWebEngineProfile(this), this); setPage(d->mPageEngine); d->mWebViewAccessKey = new WebEngineViewer::WebEngineAccessKey(this, this); d->mWebViewAccessKey->setActionCollection(ac); d->mScamDetection = new ScamDetectionWebEngine(this); connect(d->mScamDetection, &ScamDetectionWebEngine::messageMayBeAScam, this, &MailWebEngineView::messageMayBeAScam); connect(d->mWebViewAccessKey, &WebEngineViewer::WebEngineAccessKey::openUrl, this, &MailWebEngineView::openUrl); connect(this, &MailWebEngineView::loadFinished, this, &MailWebEngineView::slotLoadFinished); d->mNetworkAccessManager = new WebEngineViewer::InterceptorManager(this, ac, this); d->mExternalReference = new MessageViewer::LoadExternalReferencesUrlInterceptor(this); d->mNetworkAccessManager->addInterceptor(d->mExternalReference); MessageViewer::CidReferencesUrlInterceptor *cidReference = new MessageViewer::CidReferencesUrlInterceptor(this); d->mNetworkAccessManager->addInterceptor(cidReference); MessageViewer::BlockExternalResourcesUrlInterceptor *blockExternalUrl = new MessageViewer::BlockExternalResourcesUrlInterceptor(this); connect(blockExternalUrl, &BlockExternalResourcesUrlInterceptor::formSubmittedForbidden, this, &MailWebEngineView::formSubmittedForbidden); d->mNetworkAccessManager->addInterceptor(blockExternalUrl); setFocusPolicy(Qt::WheelFocus); connect(d->mPageEngine, &MailWebEnginePage::urlClicked, this, &MailWebEngineView::openUrl); connect( page(), &QWebEnginePage::scrollPositionChanged, d->mWebViewAccessKey, &WebEngineViewer::WebEngineAccessKey::hideAccessKeys); initializeScripts(); } MailWebEngineView::~MailWebEngineView() { delete d; } void MailWebEngineView::setLinkHovered(const QUrl &url) { //TODO we need to detect image url too. d->mHoveredUrl = url; } void MailWebEngineView::runJavaScriptInWordId(const QString &script) { page()->runJavaScript(script, WebEngineViewer::WebEngineManageScript::scriptWordId()); } void MailWebEngineView::setViewer(MessageViewer::ViewerPrivate *viewer) { d->mViewer = viewer; } void MailWebEngineView::initializeScripts() { initializeJQueryScript(); } void MailWebEngineView::contextMenuEvent(QContextMenuEvent *e) { WebEngineViewer::WebHitTest *webHit = d->mPageEngine->hitTestContent(e->pos()); connect(webHit, &WebEngineViewer::WebHitTest::finished, this, &MailWebEngineView::slotWebHitFinished); } void MailWebEngineView::slotWebHitFinished(const WebEngineViewer::WebHitTestResult &result) { Q_EMIT popupMenu(result); } void MailWebEngineView::scrollUp(int pixels) { runJavaScriptInWordId(WebEngineViewer::WebEngineScript::scrollUp(pixels)); } void MailWebEngineView::scrollDown(int pixels) { runJavaScriptInWordId(WebEngineViewer::WebEngineScript::scrollDown(pixels)); } void MailWebEngineView::selectAll() { page()->triggerAction(QWebEnginePage::SelectAll); } void MailWebEngineView::slotZoomChanged(qreal zoom) { setZoomFactor(zoom); } void MailWebEngineView::scamCheck() { d->mScamDetection->scanPage(page()); } void MailWebEngineView::slotShowDetails() { d->mScamDetection->showDetails(); } void MailWebEngineView::forwardKeyReleaseEvent(QKeyEvent *e) { if (MessageViewer::MessageViewerSettings::self()->accessKeyEnabled()) { d->mWebViewAccessKey->keyReleaseEvent(e); } } void MailWebEngineView::forwardMousePressEvent(QMouseEvent *event) { if (d->mViewer && !d->mHoveredUrl.isEmpty()) { if (event->button() == Qt::LeftButton && (event->modifiers() & Qt::ShiftModifier)) { // special processing for shift+click URLHandlerManager::instance()->handleShiftClick(d->mHoveredUrl, d->mViewer); event->accept(); return; } if (event->button() == Qt::LeftButton) { d->mCanStartDrag = URLHandlerManager::instance()->willHandleDrag(d->mHoveredUrl, d->mViewer); d->mLastClickPosition = event->pos(); } } } void MailWebEngineView::forwardMouseMoveEvent(QMouseEvent *event) { if (d->mViewer && !d->mHoveredUrl.isEmpty()) { // If we are potentially handling a drag, deal with that. if (d->mCanStartDrag && (event->buttons() & Qt::LeftButton)) { if ((d->mLastClickPosition - event->pos()).manhattanLength() > QApplication::startDragDistance()) { if (URLHandlerManager::instance()->handleDrag(d->mHoveredUrl, d->mViewer)) { // If the URL handler manager started a drag, don't handle this in the future d->mCanStartDrag = false; } } event->accept(); } } } void MailWebEngineView::forwardMouseReleaseEvent(QMouseEvent *event) { Q_UNUSED(event); d->mCanStartDrag = false; } void MailWebEngineView::forwardKeyPressEvent(QKeyEvent *e) { if (e && hasFocus()) { if (MessageViewer::MessageViewerSettings::self()->accessKeyEnabled()) { d->mWebViewAccessKey->keyPressEvent(e); } } } void MailWebEngineView::forwardWheelEvent(QWheelEvent *e) { if (MessageViewer::MessageViewerSettings::self()->accessKeyEnabled()) { d->mWebViewAccessKey->wheelEvent(e); } if (QApplication::keyboardModifiers() & Qt::ControlModifier) { const int numDegrees = e->delta() / 8; const int numSteps = numDegrees / 15; Q_EMIT wheelZoomChanged(numSteps); e->accept(); } } void MailWebEngineView::resizeEvent(QResizeEvent *e) { if (MessageViewer::MessageViewerSettings::self()->accessKeyEnabled()) { d->mWebViewAccessKey->resizeEvent(e); } QWebEngineView::resizeEvent(e); } void MailWebEngineView::saveMainFrameScreenshotInFile(const QString &filename) { //TODO need to verify it QImage image(size(), QImage::Format_ARGB32_Premultiplied); image.fill(Qt::transparent); QPainter painter(&image); painter.setRenderHint(QPainter::Antialiasing, true); painter.setRenderHint(QPainter::TextAntialiasing, true); painter.setRenderHint(QPainter::SmoothPixmapTransform, true); render(&painter); painter.end(); image.save(filename); } void MailWebEngineView::showAccessKeys() { d->mWebViewAccessKey->showAccessKeys(); } void MailWebEngineView::hideAccessKeys() { d->mWebViewAccessKey->hideAccessKeys(); } void MailWebEngineView::isScrolledToBottom() { page()->runJavaScript(WebEngineViewer::WebEngineScript::isScrolledToBottom(), WebEngineViewer::WebEngineManageScript::scriptWordId(), invoke(this, &MailWebEngineView::handleIsScrolledToBottom)); } void MailWebEngineView::setElementByIdVisible(const QString &id, bool visible) { runJavaScriptInWordId(WebEngineViewer::WebEngineScript::setElementByIdVisible(id, visible)); } void MailWebEngineView::removeAttachmentMarking(const QString &id) { runJavaScriptInWordId(WebEngineViewer::WebEngineScript::removeStyleToElement(QLatin1String("*#") + id)); } void MailWebEngineView::markAttachment(const QString &id, const QString &style) { //TODO verify "*#" + id runJavaScriptInWordId(WebEngineViewer::WebEngineScript::setStyleToElement(QLatin1String("*#") + id, style)); } void MailWebEngineView::scrollToAnchor(const QString &anchor) { page()->runJavaScript(WebEngineViewer::WebEngineScript::searchElementPosition(anchor), WebEngineViewer::WebEngineManageScript::scriptWordId(), invoke(this, &MailWebEngineView::handleScrollToAnchor)); } void MailWebEngineView::handleIsScrolledToBottom(const QVariant &result) { bool scrolledToBottomResult = false; if (result.isValid()) { scrolledToBottomResult = result.toBool(); } Q_EMIT pageIsScrolledToBottom(scrolledToBottomResult); } void MailWebEngineView::handleScrollToAnchor(const QVariant &result) { if (result.isValid()) { const QList lst = result.toList(); if (lst.count() == 2) { const QPoint pos(lst.at(0).toInt(), lst.at(1).toInt()); runJavaScriptInWordId(WebEngineViewer::WebEngineScript::scrollToPosition(pos)); } } } void MailWebEngineView::scrollPageDown(int percent) { runJavaScriptInWordId(WebEngineViewer::WebEngineScript::scrollPercentage(percent)); } void MailWebEngineView::scrollPageUp(int percent) { scrollPageDown(-percent); } void MailWebEngineView::executeHideShowToAddressScripts(bool hide) { const QString source = MessageViewer::MailWebEngineScript::manageShowHideToAddress(hide); runJavaScriptInWordId(source); } +void MailWebEngineView::executeHideShowEncryptionDetails(bool hide) +{ + const QString source = MessageViewer::MailWebEngineScript::manageShowHideEncryptionDetails(hide); + runJavaScriptInWordId(source); +} + void MailWebEngineView::executeHideShowCcAddressScripts(bool hide) { const QString source = MessageViewer::MailWebEngineScript::manageShowHideCcAddress(hide); runJavaScriptInWordId(source); } void MailWebEngineView::executeHideShowAttachmentsScripts(bool hide) { const QString source = MessageViewer::MailWebEngineScript::manageShowHideAttachments(hide); runJavaScriptInWordId(source); } void MailWebEngineView::toggleFullAddressList(const QString &field, const boost::function &delayedHtml) { const QString html = delayedHtml(); if (html.isEmpty()) { return; } runJavaScriptInWordId(MessageViewer::MailWebEngineScript::replaceInnerHtml(field, html)); } void MailWebEngineView::scrollToRelativePosition(qreal pos) { runJavaScriptInWordId(WebEngineViewer::WebEngineScript::scrollToRelativePosition(pos)); } void MailWebEngineView::setAllowExternalContent(bool b) { if (d->mExternalReference->allowExternalContent() != b) { d->mExternalReference->setAllowExternalContent(b); reload(); } } QList MailWebEngineView::interceptorUrlActions( const WebEngineViewer::WebHitTestResult &result) const { return d->mNetworkAccessManager->interceptorUrlActions(result); } void MailWebEngineView::slotLoadFinished() { scamCheck(); } void MailWebEngineView::setPrintElementBackground(bool printElementBackground) { d->mPageEngine->setPrintElementBackground(printElementBackground); } bool MailWebEngineView::execPrintPreviewPage(QPrinter *printer, int timeout) { return d->mPageEngine->execPrintPreviewPage(printer, timeout); } diff --git a/messageviewer/src/viewer/webengine/mailwebengineview.h b/messageviewer/src/viewer/webengine/mailwebengineview.h index 60aad661..6f51d43e 100644 --- a/messageviewer/src/viewer/webengine/mailwebengineview.h +++ b/messageviewer/src/viewer/webengine/mailwebengineview.h @@ -1,112 +1,113 @@ /* Copyright (C) 2016-2017 Laurent Montel This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef MAILWEBENGINE_H #define MAILWEBENGINE_H #include "messageviewer_export.h" #include #include class QPrinter; class KActionCollection; namespace WebEngineViewer { class WebHitTestResult; } namespace MessageViewer { class ViewerPrivate; class MailWebEngineViewPrivate; class MESSAGEVIEWER_EXPORT MailWebEngineView : public WebEngineViewer::WebEngineView { Q_OBJECT public: explicit MailWebEngineView(KActionCollection *ac, QWidget *parent = nullptr); ~MailWebEngineView(); void scrollUp(int pixels); void scrollDown(int pixels); void selectAll(); void scamCheck(); void saveMainFrameScreenshotInFile(const QString &filename); void showAccessKeys(); void hideAccessKeys(); void isScrolledToBottom(); void setElementByIdVisible(const QString &id, bool visible); void removeAttachmentMarking(const QString &id); void markAttachment(const QString &id, const QString &style); void scrollToAnchor(const QString &anchor); void scrollPageDown(int percent); void scrollPageUp(int percent); void scrollToRelativePosition(qreal pos); void setAllowExternalContent(bool b); QList interceptorUrlActions(const WebEngineViewer::WebHitTestResult &result) const; void toggleFullAddressList(const QString &field, const boost::function &delayedHtml); void setPrintElementBackground(bool printElementBackground); void executeHideShowAttachmentsScripts(bool hide); void executeHideShowToAddressScripts(bool hide); void executeHideShowCcAddressScripts(bool hide); + void executeHideShowEncryptionDetails(bool hide); void setLinkHovered(const QUrl &url); void setViewer(MessageViewer::ViewerPrivate *viewer); bool execPrintPreviewPage(QPrinter *printer, int timeout); public Q_SLOTS: void slotZoomChanged(qreal zoom); void slotShowDetails(); protected: void forwardWheelEvent(QWheelEvent *event) override; void forwardKeyPressEvent(QKeyEvent *event) override; void forwardKeyReleaseEvent(QKeyEvent *event) override; void forwardMousePressEvent(QMouseEvent *event) override; void forwardMouseMoveEvent(QMouseEvent *event) override; void forwardMouseReleaseEvent(QMouseEvent *event) override; void resizeEvent(QResizeEvent *e) override; void contextMenuEvent(QContextMenuEvent *e) override; Q_SIGNALS: void wheelZoomChanged(int numSteps); void openUrl(const QUrl &url); void messageMayBeAScam(); void formSubmittedForbidden(); /// Emitted when the user right-clicks somewhere /// @param url if an URL was under the cursor, this parameter contains it. Otherwise empty /// @param point position where the click happened, in local coordinates void popupMenu(const WebEngineViewer::WebHitTestResult &result); void pageIsScrolledToBottom(bool); private Q_SLOTS: void handleScrollToAnchor(const QVariant &result); void handleIsScrolledToBottom(const QVariant &result); void slotWebHitFinished(const WebEngineViewer::WebHitTestResult &result); void slotLoadFinished(); private: void initializeScripts(); void runJavaScriptInWordId(const QString &script); MailWebEngineViewPrivate *const d; }; } #endif // MAILWEBENGINE_H diff --git a/mimetreeparser/autotests/data/details/forward-openpgp-signed-encrypted.mbox.html b/mimetreeparser/autotests/data/details/forward-openpgp-signed-encrypted.mbox.html index 9a81f103..8df14524 100644 --- a/mimetreeparser/autotests/data/details/forward-openpgp-signed-encrypted.mbox.html +++ b/mimetreeparser/autotests/data/details/forward-openpgp-signed-encrypted.mbox.html @@ -1,84 +1,87 @@ diff --git a/mimetreeparser/autotests/data/details/openpgp-encrypted+signed.mbox.html b/mimetreeparser/autotests/data/details/openpgp-encrypted+signed.mbox.html index 96361c30..d8ff1a21 100644 --- a/mimetreeparser/autotests/data/details/openpgp-encrypted+signed.mbox.html +++ b/mimetreeparser/autotests/data/details/openpgp-encrypted+signed.mbox.html @@ -1,55 +1,58 @@ diff --git a/mimetreeparser/autotests/data/details/openpgp-encrypted-attachment-and-non-encrypted-attachment.mbox.html b/mimetreeparser/autotests/data/details/openpgp-encrypted-attachment-and-non-encrypted-attachment.mbox.html index cc6bf03e..2aa18ccb 100644 --- a/mimetreeparser/autotests/data/details/openpgp-encrypted-attachment-and-non-encrypted-attachment.mbox.html +++ b/mimetreeparser/autotests/data/details/openpgp-encrypted-attachment-and-non-encrypted-attachment.mbox.html @@ -1,80 +1,83 @@
diff --git a/mimetreeparser/autotests/data/details/openpgp-encrypted-attachment.mbox.html b/mimetreeparser/autotests/data/details/openpgp-encrypted-attachment.mbox.html index 61bf5d28..d3e17e50 100644 --- a/mimetreeparser/autotests/data/details/openpgp-encrypted-attachment.mbox.html +++ b/mimetreeparser/autotests/data/details/openpgp-encrypted-attachment.mbox.html @@ -1,69 +1,72 @@ diff --git a/mimetreeparser/autotests/data/details/openpgp-encrypted-non-encrypted-attachment.mbox.html b/mimetreeparser/autotests/data/details/openpgp-encrypted-non-encrypted-attachment.mbox.html index 8d8bde0d..83fd9807 100644 --- a/mimetreeparser/autotests/data/details/openpgp-encrypted-non-encrypted-attachment.mbox.html +++ b/mimetreeparser/autotests/data/details/openpgp-encrypted-non-encrypted-attachment.mbox.html @@ -1,72 +1,75 @@
diff --git a/mimetreeparser/autotests/data/details/openpgp-encrypted-partially-signed-attachments.mbox.html b/mimetreeparser/autotests/data/details/openpgp-encrypted-partially-signed-attachments.mbox.html index e68835d4..d9444c8c 100644 --- a/mimetreeparser/autotests/data/details/openpgp-encrypted-partially-signed-attachments.mbox.html +++ b/mimetreeparser/autotests/data/details/openpgp-encrypted-partially-signed-attachments.mbox.html @@ -1,104 +1,110 @@ diff --git a/mimetreeparser/autotests/data/details/openpgp-inline-charset-encrypted.mbox.html b/mimetreeparser/autotests/data/details/openpgp-inline-charset-encrypted.mbox.html index c2fa2fee..db6ac13b 100644 --- a/mimetreeparser/autotests/data/details/openpgp-inline-charset-encrypted.mbox.html +++ b/mimetreeparser/autotests/data/details/openpgp-inline-charset-encrypted.mbox.html @@ -1,50 +1,53 @@ diff --git a/mimetreeparser/autotests/data/details/openpgp-signed-encrypted-two-attachments.mbox.html b/mimetreeparser/autotests/data/details/openpgp-signed-encrypted-two-attachments.mbox.html index 9746cc93..70c53319 100644 --- a/mimetreeparser/autotests/data/details/openpgp-signed-encrypted-two-attachments.mbox.html +++ b/mimetreeparser/autotests/data/details/openpgp-signed-encrypted-two-attachments.mbox.html @@ -1,95 +1,98 @@ diff --git a/mimetreeparser/autotests/data/details/openpgp-signed-encrypted.mbox.html b/mimetreeparser/autotests/data/details/openpgp-signed-encrypted.mbox.html index d3a7a0ce..1d01de95 100644 --- a/mimetreeparser/autotests/data/details/openpgp-signed-encrypted.mbox.html +++ b/mimetreeparser/autotests/data/details/openpgp-signed-encrypted.mbox.html @@ -1,58 +1,61 @@ diff --git a/mimetreeparser/autotests/data/details/signed-forward-openpgp-signed-encrypted.mbox.html b/mimetreeparser/autotests/data/details/signed-forward-openpgp-signed-encrypted.mbox.html index b960f318..7ef4d956 100644 --- a/mimetreeparser/autotests/data/details/signed-forward-openpgp-signed-encrypted.mbox.html +++ b/mimetreeparser/autotests/data/details/signed-forward-openpgp-signed-encrypted.mbox.html @@ -1,111 +1,114 @@ diff --git a/mimetreeparser/autotests/data/details/smime-opaque-enc+sign.mbox.html b/mimetreeparser/autotests/data/details/smime-opaque-enc+sign.mbox.html index d9e5a6a7..3ec23699 100644 --- a/mimetreeparser/autotests/data/details/smime-opaque-enc+sign.mbox.html +++ b/mimetreeparser/autotests/data/details/smime-opaque-enc+sign.mbox.html @@ -1,60 +1,63 @@ diff --git a/mimetreeparser/autotests/data/details/smime-signed-encrypted.mbox.html b/mimetreeparser/autotests/data/details/smime-signed-encrypted.mbox.html index ee27e154..b0e712e2 100644 --- a/mimetreeparser/autotests/data/details/smime-signed-encrypted.mbox.html +++ b/mimetreeparser/autotests/data/details/smime-signed-encrypted.mbox.html @@ -1,58 +1,61 @@ diff --git a/mimetreeparser/autotests/data/forward-openpgp-signed-encrypted.mbox.html b/mimetreeparser/autotests/data/forward-openpgp-signed-encrypted.mbox.html index 7632ec39..47e265c9 100644 --- a/mimetreeparser/autotests/data/forward-openpgp-signed-encrypted.mbox.html +++ b/mimetreeparser/autotests/data/forward-openpgp-signed-encrypted.mbox.html @@ -1,81 +1,84 @@ diff --git a/mimetreeparser/autotests/data/headeronly/openpgp-encrypted-attachment-and-non-encrypted-attachment.mbox.html b/mimetreeparser/autotests/data/headeronly/openpgp-encrypted-attachment-and-non-encrypted-attachment.mbox.html index 73c4d2d7..6ef3ca94 100644 --- a/mimetreeparser/autotests/data/headeronly/openpgp-encrypted-attachment-and-non-encrypted-attachment.mbox.html +++ b/mimetreeparser/autotests/data/headeronly/openpgp-encrypted-attachment-and-non-encrypted-attachment.mbox.html @@ -1,61 +1,64 @@ diff --git a/mimetreeparser/autotests/data/headeronly/openpgp-encrypted-attachment.mbox.html b/mimetreeparser/autotests/data/headeronly/openpgp-encrypted-attachment.mbox.html index d5e4550e..ed617a15 100644 --- a/mimetreeparser/autotests/data/headeronly/openpgp-encrypted-attachment.mbox.html +++ b/mimetreeparser/autotests/data/headeronly/openpgp-encrypted-attachment.mbox.html @@ -1,58 +1,61 @@ diff --git a/mimetreeparser/autotests/data/headeronly/openpgp-encrypted-non-encrypted-attachment.mbox.html b/mimetreeparser/autotests/data/headeronly/openpgp-encrypted-non-encrypted-attachment.mbox.html index 73c4d2d7..6ef3ca94 100644 --- a/mimetreeparser/autotests/data/headeronly/openpgp-encrypted-non-encrypted-attachment.mbox.html +++ b/mimetreeparser/autotests/data/headeronly/openpgp-encrypted-non-encrypted-attachment.mbox.html @@ -1,61 +1,64 @@ diff --git a/mimetreeparser/autotests/data/headeronly/openpgp-encrypted-partially-signed-attachments.mbox.html b/mimetreeparser/autotests/data/headeronly/openpgp-encrypted-partially-signed-attachments.mbox.html index ebad7354..a977d93b 100644 --- a/mimetreeparser/autotests/data/headeronly/openpgp-encrypted-partially-signed-attachments.mbox.html +++ b/mimetreeparser/autotests/data/headeronly/openpgp-encrypted-partially-signed-attachments.mbox.html @@ -1,84 +1,90 @@ diff --git a/mimetreeparser/autotests/data/headeronly/openpgp-encrypted-two-attachments.mbox.html b/mimetreeparser/autotests/data/headeronly/openpgp-encrypted-two-attachments.mbox.html index 4cdeaa63..d4db6601 100644 --- a/mimetreeparser/autotests/data/headeronly/openpgp-encrypted-two-attachments.mbox.html +++ b/mimetreeparser/autotests/data/headeronly/openpgp-encrypted-two-attachments.mbox.html @@ -1,34 +1,37 @@ diff --git a/mimetreeparser/autotests/data/headeronly/openpgp-signed-encrypted-two-attachments.mbox.html b/mimetreeparser/autotests/data/headeronly/openpgp-signed-encrypted-two-attachments.mbox.html index 83b7a66c..d753df6a 100644 --- a/mimetreeparser/autotests/data/headeronly/openpgp-signed-encrypted-two-attachments.mbox.html +++ b/mimetreeparser/autotests/data/headeronly/openpgp-signed-encrypted-two-attachments.mbox.html @@ -1,58 +1,61 @@ diff --git a/mimetreeparser/autotests/data/hidden/openpgp-encrypted-attachment-and-non-encrypted-attachment.mbox.html b/mimetreeparser/autotests/data/hidden/openpgp-encrypted-attachment-and-non-encrypted-attachment.mbox.html index 73c4d2d7..6ef3ca94 100644 --- a/mimetreeparser/autotests/data/hidden/openpgp-encrypted-attachment-and-non-encrypted-attachment.mbox.html +++ b/mimetreeparser/autotests/data/hidden/openpgp-encrypted-attachment-and-non-encrypted-attachment.mbox.html @@ -1,61 +1,64 @@ diff --git a/mimetreeparser/autotests/data/hidden/openpgp-encrypted-attachment.mbox.html b/mimetreeparser/autotests/data/hidden/openpgp-encrypted-attachment.mbox.html index d5e4550e..ed617a15 100644 --- a/mimetreeparser/autotests/data/hidden/openpgp-encrypted-attachment.mbox.html +++ b/mimetreeparser/autotests/data/hidden/openpgp-encrypted-attachment.mbox.html @@ -1,58 +1,61 @@ diff --git a/mimetreeparser/autotests/data/hidden/openpgp-encrypted-non-encrypted-attachment.mbox.html b/mimetreeparser/autotests/data/hidden/openpgp-encrypted-non-encrypted-attachment.mbox.html index 73c4d2d7..6ef3ca94 100644 --- a/mimetreeparser/autotests/data/hidden/openpgp-encrypted-non-encrypted-attachment.mbox.html +++ b/mimetreeparser/autotests/data/hidden/openpgp-encrypted-non-encrypted-attachment.mbox.html @@ -1,61 +1,64 @@ diff --git a/mimetreeparser/autotests/data/hidden/openpgp-encrypted-partially-signed-attachments.mbox.html b/mimetreeparser/autotests/data/hidden/openpgp-encrypted-partially-signed-attachments.mbox.html index ebad7354..a977d93b 100644 --- a/mimetreeparser/autotests/data/hidden/openpgp-encrypted-partially-signed-attachments.mbox.html +++ b/mimetreeparser/autotests/data/hidden/openpgp-encrypted-partially-signed-attachments.mbox.html @@ -1,84 +1,90 @@ diff --git a/mimetreeparser/autotests/data/hidden/openpgp-encrypted-two-attachments.mbox.html b/mimetreeparser/autotests/data/hidden/openpgp-encrypted-two-attachments.mbox.html index 4cdeaa63..d4db6601 100644 --- a/mimetreeparser/autotests/data/hidden/openpgp-encrypted-two-attachments.mbox.html +++ b/mimetreeparser/autotests/data/hidden/openpgp-encrypted-two-attachments.mbox.html @@ -1,34 +1,37 @@ diff --git a/mimetreeparser/autotests/data/hidden/openpgp-signed-encrypted-two-attachments.mbox.html b/mimetreeparser/autotests/data/hidden/openpgp-signed-encrypted-two-attachments.mbox.html index 83b7a66c..d753df6a 100644 --- a/mimetreeparser/autotests/data/hidden/openpgp-signed-encrypted-two-attachments.mbox.html +++ b/mimetreeparser/autotests/data/hidden/openpgp-signed-encrypted-two-attachments.mbox.html @@ -1,58 +1,61 @@ diff --git a/mimetreeparser/autotests/data/iconic/openpgp-encrypted-partially-signed-attachments.mbox.html b/mimetreeparser/autotests/data/iconic/openpgp-encrypted-partially-signed-attachments.mbox.html index b6f734c2..f1495ec4 100644 --- a/mimetreeparser/autotests/data/iconic/openpgp-encrypted-partially-signed-attachments.mbox.html +++ b/mimetreeparser/autotests/data/iconic/openpgp-encrypted-partially-signed-attachments.mbox.html @@ -1,92 +1,98 @@ diff --git a/mimetreeparser/autotests/data/iconic/openpgp-encrypted-two-attachments.mbox.html b/mimetreeparser/autotests/data/iconic/openpgp-encrypted-two-attachments.mbox.html index 7f0b7abd..3f5237e0 100644 --- a/mimetreeparser/autotests/data/iconic/openpgp-encrypted-two-attachments.mbox.html +++ b/mimetreeparser/autotests/data/iconic/openpgp-encrypted-two-attachments.mbox.html @@ -1,50 +1,53 @@ diff --git a/mimetreeparser/autotests/data/iconic/openpgp-signed-encrypted-two-attachments.mbox.html b/mimetreeparser/autotests/data/iconic/openpgp-signed-encrypted-two-attachments.mbox.html index 8d6b5814..86f8f68c 100644 --- a/mimetreeparser/autotests/data/iconic/openpgp-signed-encrypted-two-attachments.mbox.html +++ b/mimetreeparser/autotests/data/iconic/openpgp-signed-encrypted-two-attachments.mbox.html @@ -1,74 +1,77 @@ diff --git a/mimetreeparser/autotests/data/inlined/openpgp-encrypted-attachment-and-non-encrypted-attachment.mbox.html b/mimetreeparser/autotests/data/inlined/openpgp-encrypted-attachment-and-non-encrypted-attachment.mbox.html index b617a550..c8029ddd 100644 --- a/mimetreeparser/autotests/data/inlined/openpgp-encrypted-attachment-and-non-encrypted-attachment.mbox.html +++ b/mimetreeparser/autotests/data/inlined/openpgp-encrypted-attachment-and-non-encrypted-attachment.mbox.html @@ -1,92 +1,95 @@
diff --git a/mimetreeparser/autotests/data/inlined/openpgp-encrypted-attachment.mbox.html b/mimetreeparser/autotests/data/inlined/openpgp-encrypted-attachment.mbox.html index bf6c8282..d53b6516 100644 --- a/mimetreeparser/autotests/data/inlined/openpgp-encrypted-attachment.mbox.html +++ b/mimetreeparser/autotests/data/inlined/openpgp-encrypted-attachment.mbox.html @@ -1,76 +1,79 @@ diff --git a/mimetreeparser/autotests/data/inlined/openpgp-encrypted-non-encrypted-attachment.mbox.html b/mimetreeparser/autotests/data/inlined/openpgp-encrypted-non-encrypted-attachment.mbox.html index 6091ee5a..333866d5 100644 --- a/mimetreeparser/autotests/data/inlined/openpgp-encrypted-non-encrypted-attachment.mbox.html +++ b/mimetreeparser/autotests/data/inlined/openpgp-encrypted-non-encrypted-attachment.mbox.html @@ -1,74 +1,77 @@
diff --git a/mimetreeparser/autotests/data/inlinepgpencrypted-appendix.mbox.html b/mimetreeparser/autotests/data/inlinepgpencrypted-appendix.mbox.html index 8af2b1c6..10489d60 100644 --- a/mimetreeparser/autotests/data/inlinepgpencrypted-appendix.mbox.html +++ b/mimetreeparser/autotests/data/inlinepgpencrypted-appendix.mbox.html @@ -1,36 +1,39 @@ diff --git a/mimetreeparser/autotests/data/inlinepgpencrypted-error.mbox.html b/mimetreeparser/autotests/data/inlinepgpencrypted-error.mbox.html index a4427e01..db0db6aa 100644 --- a/mimetreeparser/autotests/data/inlinepgpencrypted-error.mbox.html +++ b/mimetreeparser/autotests/data/inlinepgpencrypted-error.mbox.html @@ -1,24 +1,24 @@ diff --git a/mimetreeparser/autotests/data/inlinepgpencrypted.mbox.html b/mimetreeparser/autotests/data/inlinepgpencrypted.mbox.html index 1f695bdf..951bc725 100644 --- a/mimetreeparser/autotests/data/inlinepgpencrypted.mbox.html +++ b/mimetreeparser/autotests/data/inlinepgpencrypted.mbox.html @@ -1,26 +1,29 @@ diff --git a/mimetreeparser/autotests/data/openpgp-encrypted+signed.mbox.html b/mimetreeparser/autotests/data/openpgp-encrypted+signed.mbox.html index 54c58b85..9504e6d8 100644 --- a/mimetreeparser/autotests/data/openpgp-encrypted+signed.mbox.html +++ b/mimetreeparser/autotests/data/openpgp-encrypted+signed.mbox.html @@ -1,52 +1,55 @@ diff --git a/mimetreeparser/autotests/data/openpgp-encrypted-applemail.mbox.html b/mimetreeparser/autotests/data/openpgp-encrypted-applemail.mbox.html index ca8d7fbb..6dbe2ccf 100644 --- a/mimetreeparser/autotests/data/openpgp-encrypted-applemail.mbox.html +++ b/mimetreeparser/autotests/data/openpgp-encrypted-applemail.mbox.html @@ -1,39 +1,42 @@
diff --git a/mimetreeparser/autotests/data/openpgp-encrypted-attachment-and-non-encrypted-attachment.mbox.html b/mimetreeparser/autotests/data/openpgp-encrypted-attachment-and-non-encrypted-attachment.mbox.html index 092a3440..06a87994 100644 --- a/mimetreeparser/autotests/data/openpgp-encrypted-attachment-and-non-encrypted-attachment.mbox.html +++ b/mimetreeparser/autotests/data/openpgp-encrypted-attachment-and-non-encrypted-attachment.mbox.html @@ -1,77 +1,80 @@
diff --git a/mimetreeparser/autotests/data/openpgp-encrypted-attachment.mbox.html b/mimetreeparser/autotests/data/openpgp-encrypted-attachment.mbox.html index 2b266b02..06bb8cfe 100644 --- a/mimetreeparser/autotests/data/openpgp-encrypted-attachment.mbox.html +++ b/mimetreeparser/autotests/data/openpgp-encrypted-attachment.mbox.html @@ -1,66 +1,69 @@ diff --git a/mimetreeparser/autotests/data/openpgp-encrypted-enigmail1.6.mbox.html b/mimetreeparser/autotests/data/openpgp-encrypted-enigmail1.6.mbox.html index 09d904bb..6f73d465 100644 --- a/mimetreeparser/autotests/data/openpgp-encrypted-enigmail1.6.mbox.html +++ b/mimetreeparser/autotests/data/openpgp-encrypted-enigmail1.6.mbox.html @@ -1,34 +1,37 @@ diff --git a/mimetreeparser/autotests/data/openpgp-encrypted-non-encrypted-attachment.mbox.html b/mimetreeparser/autotests/data/openpgp-encrypted-non-encrypted-attachment.mbox.html index e20a9568..38a460bd 100644 --- a/mimetreeparser/autotests/data/openpgp-encrypted-non-encrypted-attachment.mbox.html +++ b/mimetreeparser/autotests/data/openpgp-encrypted-non-encrypted-attachment.mbox.html @@ -1,69 +1,72 @@
diff --git a/mimetreeparser/autotests/data/openpgp-encrypted-partially-signed-attachments.mbox.html b/mimetreeparser/autotests/data/openpgp-encrypted-partially-signed-attachments.mbox.html index b4072b69..2779643d 100644 --- a/mimetreeparser/autotests/data/openpgp-encrypted-partially-signed-attachments.mbox.html +++ b/mimetreeparser/autotests/data/openpgp-encrypted-partially-signed-attachments.mbox.html @@ -1,101 +1,107 @@ diff --git a/mimetreeparser/autotests/data/openpgp-encrypted-two-attachments.mbox.html b/mimetreeparser/autotests/data/openpgp-encrypted-two-attachments.mbox.html index 0421f8aa..675a1eed 100644 --- a/mimetreeparser/autotests/data/openpgp-encrypted-two-attachments.mbox.html +++ b/mimetreeparser/autotests/data/openpgp-encrypted-two-attachments.mbox.html @@ -1,68 +1,71 @@ diff --git a/mimetreeparser/autotests/data/openpgp-encrypted.mbox.html b/mimetreeparser/autotests/data/openpgp-encrypted.mbox.html index ba0976cd..9ae3ac8a 100644 --- a/mimetreeparser/autotests/data/openpgp-encrypted.mbox.html +++ b/mimetreeparser/autotests/data/openpgp-encrypted.mbox.html @@ -1,31 +1,34 @@ diff --git a/mimetreeparser/autotests/data/openpgp-inline-charset-encrypted.mbox.html b/mimetreeparser/autotests/data/openpgp-inline-charset-encrypted.mbox.html index 344dc237..b506fc36 100644 --- a/mimetreeparser/autotests/data/openpgp-inline-charset-encrypted.mbox.html +++ b/mimetreeparser/autotests/data/openpgp-inline-charset-encrypted.mbox.html @@ -1,47 +1,50 @@ diff --git a/mimetreeparser/autotests/data/openpgp-inline-wrong-charset-encrypted.mbox.html b/mimetreeparser/autotests/data/openpgp-inline-wrong-charset-encrypted.mbox.html index 3ed4e0fb..5b48ba34 100644 --- a/mimetreeparser/autotests/data/openpgp-inline-wrong-charset-encrypted.mbox.html +++ b/mimetreeparser/autotests/data/openpgp-inline-wrong-charset-encrypted.mbox.html @@ -1,47 +1,50 @@ diff --git a/mimetreeparser/autotests/data/openpgp-signed-encrypted-two-attachments.mbox.html b/mimetreeparser/autotests/data/openpgp-signed-encrypted-two-attachments.mbox.html index 67c69ae3..f4dfb2ce 100644 --- a/mimetreeparser/autotests/data/openpgp-signed-encrypted-two-attachments.mbox.html +++ b/mimetreeparser/autotests/data/openpgp-signed-encrypted-two-attachments.mbox.html @@ -1,92 +1,95 @@ diff --git a/mimetreeparser/autotests/data/openpgp-signed-encrypted.mbox.html b/mimetreeparser/autotests/data/openpgp-signed-encrypted.mbox.html index 86a964b8..391311b5 100644 --- a/mimetreeparser/autotests/data/openpgp-signed-encrypted.mbox.html +++ b/mimetreeparser/autotests/data/openpgp-signed-encrypted.mbox.html @@ -1,55 +1,58 @@ diff --git a/mimetreeparser/autotests/data/signed-forward-openpgp-signed-encrypted.mbox.html b/mimetreeparser/autotests/data/signed-forward-openpgp-signed-encrypted.mbox.html index b91772b7..517b57dc 100644 --- a/mimetreeparser/autotests/data/signed-forward-openpgp-signed-encrypted.mbox.html +++ b/mimetreeparser/autotests/data/signed-forward-openpgp-signed-encrypted.mbox.html @@ -1,105 +1,108 @@ diff --git a/mimetreeparser/autotests/data/smime-encrypted-octet-stream.mbox.html b/mimetreeparser/autotests/data/smime-encrypted-octet-stream.mbox.html index 6b08c47e..0a4db6d6 100644 --- a/mimetreeparser/autotests/data/smime-encrypted-octet-stream.mbox.html +++ b/mimetreeparser/autotests/data/smime-encrypted-octet-stream.mbox.html @@ -1,31 +1,34 @@ diff --git a/mimetreeparser/autotests/data/smime-encrypted.mbox.html b/mimetreeparser/autotests/data/smime-encrypted.mbox.html index 6b08c47e..0a4db6d6 100644 --- a/mimetreeparser/autotests/data/smime-encrypted.mbox.html +++ b/mimetreeparser/autotests/data/smime-encrypted.mbox.html @@ -1,31 +1,34 @@ diff --git a/mimetreeparser/autotests/data/smime-opaque-enc+sign.mbox.html b/mimetreeparser/autotests/data/smime-opaque-enc+sign.mbox.html index 7f60c04f..e38ab483 100644 --- a/mimetreeparser/autotests/data/smime-opaque-enc+sign.mbox.html +++ b/mimetreeparser/autotests/data/smime-opaque-enc+sign.mbox.html @@ -1,57 +1,60 @@ diff --git a/mimetreeparser/autotests/data/smime-signed-encrypted.mbox.html b/mimetreeparser/autotests/data/smime-signed-encrypted.mbox.html index e13d6841..b96d5871 100644 --- a/mimetreeparser/autotests/data/smime-signed-encrypted.mbox.html +++ b/mimetreeparser/autotests/data/smime-signed-encrypted.mbox.html @@ -1,55 +1,58 @@ diff --git a/mimetreeparser/src/viewer/messagepart.cpp b/mimetreeparser/src/viewer/messagepart.cpp index f335f54f..8bcf28d9 100644 --- a/mimetreeparser/src/viewer/messagepart.cpp +++ b/mimetreeparser/src/viewer/messagepart.cpp @@ -1,1388 +1,1413 @@ /* Copyright (c) 2015 Sandro Knauß This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "messagepart.h" #include "mimetreeparser_debug.h" #include "attachmentstrategy.h" #include "cryptohelper.h" #include "objecttreeparser.h" #include "job/qgpgmejobexecutor.h" #include "memento/cryptobodypartmemento.h" #include "memento/decryptverifybodypartmemento.h" #include "memento/verifydetachedbodypartmemento.h" #include "memento/verifyopaquebodypartmemento.h" #include "bodyformatter/utils.h" #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace MimeTreeParser; //------MessagePart----------------------- namespace MimeTreeParser { class MessagePartPrivate { public: MessagePart *mParentPart = nullptr; QVector mBlocks; KMime::Content *mNode = nullptr; KMime::Content *mAttachmentNode = nullptr; QString mText; PartMetaData mMetaData; bool mRoot = false; }; } MessagePart::MessagePart(ObjectTreeParser *otp, const QString &text) : mOtp(otp) , d(new MessagePartPrivate) { d->mText = text; } MessagePart::~MessagePart() = default; MessagePart *MessagePart::parentPart() const { return d->mParentPart; } void MessagePart::setParentPart(MessagePart *parentPart) { d->mParentPart = parentPart; } QString MessagePart::htmlContent() const { return text(); } QString MessagePart::plaintextContent() const { return text(); } PartMetaData *MessagePart::partMetaData() const { return &d->mMetaData; } Interface::BodyPartMemento* MessagePart::memento() const { return nodeHelper()->bodyPartMemento(content(), "__plugin__"); } void MessagePart::setMemento(Interface::BodyPartMemento *memento) { nodeHelper()->setBodyPartMemento(content(), "__plugin__", memento); } KMime::Content *MessagePart::content() const { return d->mNode; } void MessagePart::setContent(KMime::Content *node) { d->mNode = node; } KMime::Content *MessagePart::attachmentContent() const { return d->mAttachmentNode; } void MessagePart::setAttachmentContent(KMime::Content *node) { d->mAttachmentNode = node; } bool MessagePart::isAttachment() const { return d->mAttachmentNode; } QString MessagePart::attachmentIndex() const { return attachmentContent()->index().toString(); } QString MessagePart::attachmentLink() const { return mOtp->nodeHelper()->asHREF(content(), QStringLiteral("body")); } QString MessagePart::makeLink(const QString &path) const { // FIXME: use a PRNG for the first arg, instead of a serial number static int serial = 0; if (path.isEmpty()) { return {}; } return QStringLiteral("x-kmail:/bodypart/%1/%2/%3") .arg(serial++).arg(content()->index().toString()) .arg(QString::fromLatin1(QUrl::toPercentEncoding(path, "/"))); } void MessagePart::setIsRoot(bool root) { d->mRoot = root; } bool MessagePart::isRoot() const { return d->mRoot; } QString MessagePart::text() const { return d->mText; } void MessagePart::setText(const QString &text) { d->mText = text; } bool MessagePart::isHtml() const { return false; } bool MessagePart::isHidden() const { return false; } Interface::ObjectTreeSource *MessagePart::source() const { Q_ASSERT(mOtp); return mOtp->mSource; } NodeHelper* MessagePart::nodeHelper() const { Q_ASSERT(mOtp); return mOtp->nodeHelper(); } void MessagePart::parseInternal(KMime::Content *node, bool onlyOneMimePart) { auto subMessagePart = mOtp->parseObjectTreeInternal(node, onlyOneMimePart); d->mRoot = subMessagePart->isRoot(); foreach (const auto &part, subMessagePart->subParts()) { appendSubPart(part); } } QString MessagePart::renderInternalText() const { QString text; foreach (const auto &mp, subParts()) { text += mp->text(); } return text; } void MessagePart::fix() const { foreach (const auto &mp, subParts()) { const auto m = mp.dynamicCast(); if (m) { m->fix(); } } } void MessagePart::appendSubPart(const MessagePart::Ptr &messagePart) { messagePart->setParentPart(this); d->mBlocks.append(messagePart); } const QVector &MessagePart::subParts() const { return d->mBlocks; } bool MessagePart::hasSubParts() const { return !d->mBlocks.isEmpty(); } //-----MessagePartList---------------------- MessagePartList::MessagePartList(ObjectTreeParser *otp) : MessagePart(otp, QString()) { } MessagePartList::~MessagePartList() { } QString MessagePartList::text() const { return renderInternalText(); } QString MessagePartList::plaintextContent() const { return QString(); } QString MessagePartList::htmlContent() const { return QString(); } //-----TextMessageBlock---------------------- TextMessagePart::TextMessagePart(ObjectTreeParser *otp, KMime::Content *node, bool drawFrame, bool decryptMessage) : MessagePartList(otp) , mDrawFrame(drawFrame) , mDecryptMessage(decryptMessage) , mIsHidden(false) { if (!node) { qCWarning(MIMETREEPARSER_LOG) << "not a valid node"; return; } setContent(node); mIsHidden = mOtp->nodeHelper()->isNodeDisplayedHidden(node); parseContent(); } TextMessagePart::~TextMessagePart() { } bool TextMessagePart::decryptMessage() const { return mDecryptMessage; } void TextMessagePart::parseContent() { const auto aCodec = mOtp->codecFor(content()); const QString &fromAddress = mOtp->nodeHelper()->fromAsString(content()); mSignatureState = KMMsgNotSigned; mEncryptionState = KMMsgNotEncrypted; const auto blocks = prepareMessageForDecryption(content()->decodedContent()); const auto cryptProto = QGpgME::openpgp(); if (!blocks.isEmpty()) { /* The (overall) signature/encrypted status is broken * if one unencrypted part is at the beginning or in the middle * because mailmain adds an unencrypted part at the end this should not break the overall status * * That's why we first set the tmp status and if one crypted/signed block comes afterwards, than * the status is set to unencryped */ bool fullySignedOrEncrypted = true; bool fullySignedOrEncryptedTmp = true; for (const auto &block : blocks) { if (!fullySignedOrEncryptedTmp) { fullySignedOrEncrypted = false; } if (block.type() == NoPgpBlock && !block.text().trimmed().isEmpty()) { fullySignedOrEncryptedTmp = false; appendSubPart(MessagePart::Ptr(new MessagePart(mOtp, aCodec->toUnicode(block.text())))); } else if (block.type() == PgpMessageBlock) { EncryptedMessagePart::Ptr mp(new EncryptedMessagePart(mOtp, QString(), cryptProto, fromAddress, nullptr)); mp->setDecryptMessage(decryptMessage()); mp->setIsEncrypted(true); appendSubPart(mp); if (!decryptMessage()) { continue; } mp->startDecryption(block.text(), aCodec); if (mp->partMetaData()->inProgress) { continue; } } else if (block.type() == ClearsignedBlock) { SignedMessagePart::Ptr mp(new SignedMessagePart(mOtp, QString(), cryptProto, fromAddress, nullptr)); appendSubPart(mp); mp->startVerification(block.text(), aCodec); } else { continue; } const auto mp = subParts().last().staticCast(); const PartMetaData *messagePart(mp->partMetaData()); if (!messagePart->isEncrypted && !messagePart->isSigned && !block.text().trimmed().isEmpty()) { mp->setText(aCodec->toUnicode(block.text())); } if (messagePart->isEncrypted) { mEncryptionState = KMMsgPartiallyEncrypted; } if (messagePart->isSigned) { mSignatureState = KMMsgPartiallySigned; } } //Do we have an fully Signed/Encrypted Message? if (fullySignedOrEncrypted) { if (mSignatureState == KMMsgPartiallySigned) { mSignatureState = KMMsgFullySigned; } if (mEncryptionState == KMMsgPartiallyEncrypted) { mEncryptionState = KMMsgFullyEncrypted; } } } } KMMsgEncryptionState TextMessagePart::encryptionState() const { return mEncryptionState; } KMMsgSignatureState TextMessagePart::signatureState() const { return mSignatureState; } bool TextMessagePart::isHidden() const { return mIsHidden; } bool TextMessagePart::showLink() const { return !temporaryFilePath().isEmpty(); } bool TextMessagePart::showTextFrame() const { return mDrawFrame; } void TextMessagePart::setShowTextFrame(bool showFrame) { mDrawFrame = showFrame; } QString TextMessagePart::label() const { const QString name = content()->contentType()->name(); QString label = name.isEmpty() ? NodeHelper::fileName(content()) : name; if (label.isEmpty()) { label = i18nc("display name for an unnamed attachment", "Unnamed"); } return label; } QString TextMessagePart::comment() const { const QString comment = content()->contentDescription()->asUnicodeString(); if (comment == label()) { return {}; } return comment; } QString TextMessagePart::temporaryFilePath() const { return nodeHelper()->writeNodeToTempFile(content()); } //-----AttachmentMessageBlock---------------------- AttachmentMessagePart::AttachmentMessagePart(ObjectTreeParser *otp, KMime::Content *node, bool drawFrame, bool decryptMessage) : TextMessagePart(otp, node, drawFrame, decryptMessage) , mIsImage(false) , mNeverDisplayInline(false) { } AttachmentMessagePart::~AttachmentMessagePart() { } bool AttachmentMessagePart::neverDisplayInline() const { return mNeverDisplayInline; } void AttachmentMessagePart::setNeverDisplayInline(bool displayInline) { mNeverDisplayInline = displayInline; } bool AttachmentMessagePart::isImage() const { return mIsImage; } void AttachmentMessagePart::setIsImage(bool image) { mIsImage = image; } IconType AttachmentMessagePart::asIcon() const { const AttachmentStrategy *const as = mOtp->attachmentStrategy(); const bool defaultHidden(as && as->defaultDisplay(content()) == AttachmentStrategy::None); const bool showOnlyOneMimePart(mOtp->showOnlyOneMimePart()); auto preferredMode = source()->preferredMode(); bool isHtmlPreferred = (preferredMode == Util::Html) || (preferredMode == Util::MultipartHtml); QByteArray mediaType("text"); if (content()->contentType(false) && !content()->contentType()->mediaType().isEmpty() && !content()->contentType()->subType().isEmpty()) { mediaType = content()->contentType()->mediaType(); } const bool isTextPart = (mediaType == QByteArrayLiteral("text")); bool defaultAsIcon = true; if (!neverDisplayInline()) { if (as) { defaultAsIcon = as->defaultDisplay(content()) == AttachmentStrategy::AsIcon; } } if (isImage() && showOnlyOneMimePart && !neverDisplayInline()) { defaultAsIcon = false; } // neither image nor text -> show as icon if (!isImage() && !isTextPart) { defaultAsIcon = true; } if (isTextPart) { if (as && as->defaultDisplay(content()) != AttachmentStrategy::Inline) { return MimeTreeParser::IconExternal; } return MimeTreeParser::NoIcon; } else { if (isImage() && isHtmlPreferred && content()->parent() && content()->parent()->contentType()->subType() == "related") { return MimeTreeParser::IconInline; } if (defaultHidden && !showOnlyOneMimePart && content()->parent()) { return MimeTreeParser::IconInline; } if (defaultAsIcon) { return MimeTreeParser::IconExternal; } else if (isImage()) { return MimeTreeParser::IconInline; } else { return MimeTreeParser::NoIcon; } } } bool AttachmentMessagePart::isHidden() const { if (mOtp->showOnlyOneMimePart()) { return false; // never hide when only showing one part, otherwise you'll see nothing } const AttachmentStrategy *const as = mOtp->attachmentStrategy(); const bool defaultHidden(as && as->defaultDisplay(content()) == AttachmentStrategy::None); auto preferredMode = source()->preferredMode(); bool isHtmlPreferred = (preferredMode == Util::Html) || (preferredMode == Util::MultipartHtml); QByteArray mediaType("text"); if (content()->contentType(false) && !content()->contentType()->mediaType().isEmpty() && !content()->contentType()->subType().isEmpty()) { mediaType = content()->contentType()->mediaType(); } const bool isTextPart = (mediaType == QByteArrayLiteral("text")); bool defaultAsIcon = true; if (!neverDisplayInline()) { if (as) { defaultAsIcon = as->defaultDisplay(content()) == AttachmentStrategy::AsIcon; } } // neither image nor text -> show as icon if (!isImage() && !isTextPart) { defaultAsIcon = true; } bool hidden(false); if (isTextPart) { hidden = defaultHidden; } else { if (isImage() && isHtmlPreferred && content()->parent() && content()->parent()->contentType()->subType() == "related") { hidden = true; } else { hidden = defaultHidden && content()->parent(); hidden |= defaultAsIcon && defaultHidden; } } mOtp->nodeHelper()->setNodeDisplayedHidden(content(), hidden); return hidden; } //-----HtmlMessageBlock---------------------- HtmlMessagePart::HtmlMessagePart(ObjectTreeParser *otp, KMime::Content *node, Interface::ObjectTreeSource *source) : MessagePart(otp, QString()) , mSource(source) { if (!node) { qCWarning(MIMETREEPARSER_LOG) << "not a valid node"; return; } setContent(node); const QByteArray partBody(node->decodedContent()); mBodyHTML = mOtp->codecFor(node)->toUnicode(partBody); mCharset = NodeHelper::charset(node); } HtmlMessagePart::~HtmlMessagePart() { } void HtmlMessagePart::fix() const { mOtp->mHtmlContent += mBodyHTML; mOtp->mHtmlContentCharset = mCharset; } QString HtmlMessagePart::text() const { return mBodyHTML; } bool HtmlMessagePart::isHtml() const { return true; } //-----MimeMessageBlock---------------------- MimeMessagePart::MimeMessagePart(ObjectTreeParser *otp, KMime::Content *node, bool onlyOneMimePart) : MessagePart(otp, QString()) , mOnlyOneMimePart(onlyOneMimePart) { if (!node) { qCWarning(MIMETREEPARSER_LOG) << "not a valid node"; return; } setContent(node); parseInternal(node, mOnlyOneMimePart); } MimeMessagePart::~MimeMessagePart() { } QString MimeMessagePart::text() const { return renderInternalText(); } QString MimeMessagePart::plaintextContent() const { return QString(); } QString MimeMessagePart::htmlContent() const { return QString(); } //-----AlternativeMessagePart---------------------- AlternativeMessagePart::AlternativeMessagePart(ObjectTreeParser *otp, KMime::Content *node, Util::HtmlMode preferredMode) : MessagePart(otp, QString()) , mPreferredMode(preferredMode) { setContent(node); KMime::Content *dataIcal = findTypeInDirectChilds(node, "text/calendar"); KMime::Content *dataHtml = findTypeInDirectChilds(node, "text/html"); KMime::Content *dataText = findTypeInDirectChilds(node, "text/plain"); if (!dataHtml) { // If we didn't find the HTML part as the first child of the multipart/alternative, it might // be that this is a HTML message with images, and text/plain and multipart/related are the // immediate children of this multipart/alternative node. // In this case, the HTML node is a child of multipart/related. dataHtml = findTypeInDirectChilds(node, "multipart/related"); // Still not found? Stupid apple mail actually puts the attachments inside of the // multipart/alternative, which is wrong. Therefore we also have to look for multipart/mixed // here. // Do this only when prefering HTML mail, though, since otherwise the attachments are hidden // when displaying plain text. if (!dataHtml) { dataHtml = findTypeInDirectChilds(node, "multipart/mixed"); } } if (dataIcal) { mChildNodes[Util::MultipartIcal] = dataIcal; } if (dataText) { mChildNodes[Util::MultipartPlain] = dataText; } if (dataHtml) { mChildNodes[Util::MultipartHtml] = dataHtml; } if (mChildNodes.isEmpty()) { qCWarning(MIMETREEPARSER_LOG) << "no valid nodes"; return; } QMapIterator i(mChildNodes); while (i.hasNext()) { i.next(); mChildParts[i.key()] = MimeMessagePart::Ptr(new MimeMessagePart(mOtp, i.value(), true)); } } AlternativeMessagePart::~AlternativeMessagePart() { } Util::HtmlMode AlternativeMessagePart::preferredMode() const { return mPreferredMode; } QList AlternativeMessagePart::availableModes() { return mChildParts.keys(); } QString AlternativeMessagePart::text() const { if (mChildParts.contains(Util::MultipartPlain)) { return mChildParts[Util::MultipartPlain]->text(); } return QString(); } void AlternativeMessagePart::fix() const { if (mChildParts.contains(Util::MultipartPlain)) { mChildParts[Util::MultipartPlain]->fix(); } const auto mode = preferredMode(); if (mode != Util::MultipartPlain && mChildParts.contains(mode)) { mChildParts[mode]->fix(); } } bool AlternativeMessagePart::isHtml() const { return mChildParts.contains(Util::MultipartHtml); } QString AlternativeMessagePart::plaintextContent() const { return text(); } QString AlternativeMessagePart::htmlContent() const { if (mChildParts.contains(Util::MultipartHtml)) { return mChildParts[Util::MultipartHtml]->text(); } else { return plaintextContent(); } } //-----CertMessageBlock---------------------- CertMessagePart::CertMessagePart(ObjectTreeParser *otp, KMime::Content *node, const QGpgME::Protocol *cryptoProto, bool autoImport) : MessagePart(otp, QString()) , mAutoImport(autoImport) , mCryptoProto(cryptoProto) { if (!node) { qCWarning(MIMETREEPARSER_LOG) << "not a valid node"; return; } setContent(node); if (!mAutoImport) { return; } const QByteArray certData = node->decodedContent(); QGpgME::ImportJob *import = mCryptoProto->importJob(); QGpgMEJobExecutor executor; mImportResult = executor.exec(import, certData); } CertMessagePart::~CertMessagePart() { } QString CertMessagePart::text() const { return QString(); } //-----SignedMessageBlock--------------------- SignedMessagePart::SignedMessagePart(ObjectTreeParser *otp, const QString &text, const QGpgME::Protocol *cryptoProto, const QString &fromAddress, KMime::Content *node) : MessagePart(otp, text) , mCryptoProto(cryptoProto) , mFromAddress(fromAddress) { setContent(node); partMetaData()->technicalProblem = (mCryptoProto == nullptr); partMetaData()->isSigned = true; partMetaData()->isGoodSignature = false; partMetaData()->keyTrust = GpgME::Signature::Unknown; partMetaData()->status = i18n("Wrong Crypto Plug-In."); partMetaData()->status_code = GPGME_SIG_STAT_NONE; } SignedMessagePart::~SignedMessagePart() { } void SignedMessagePart::setIsSigned(bool isSigned) { partMetaData()->isSigned = isSigned; } bool SignedMessagePart::isSigned() const { return partMetaData()->isSigned; } bool SignedMessagePart::okVerify(const QByteArray &data, const QByteArray &signature, KMime::Content *textNode) { NodeHelper *nodeHelper = mOtp->nodeHelper(); partMetaData()->isSigned = false; partMetaData()->technicalProblem = (mCryptoProto == nullptr); partMetaData()->keyTrust = GpgME::Signature::Unknown; partMetaData()->status = i18n("Wrong Crypto Plug-In."); partMetaData()->status_code = GPGME_SIG_STAT_NONE; const QByteArray mementoName = "verification"; CryptoBodyPartMemento *m = dynamic_cast(nodeHelper->bodyPartMemento(content(), mementoName)); Q_ASSERT(!m || mCryptoProto); //No CryptoPlugin and having a bodyPartMemento -> there is something completely wrong if (!m && mCryptoProto) { if (!signature.isEmpty()) { QGpgME::VerifyDetachedJob *job = mCryptoProto->verifyDetachedJob(); if (job) { m = new VerifyDetachedBodyPartMemento(job, mCryptoProto->keyListJob(), signature, data); } } else { QGpgME::VerifyOpaqueJob *job = mCryptoProto->verifyOpaqueJob(); if (job) { m = new VerifyOpaqueBodyPartMemento(job, mCryptoProto->keyListJob(), data); } } if (m) { if (mOtp->allowAsync()) { QObject::connect(m, &CryptoBodyPartMemento::update, nodeHelper, &NodeHelper::update); if (m->start()) { partMetaData()->inProgress = true; mOtp->mHasPendingAsyncJobs = true; } } else { m->exec(); } nodeHelper->setBodyPartMemento(content(), mementoName, m); } } else if (m && m->isRunning()) { partMetaData()->inProgress = true; mOtp->mHasPendingAsyncJobs = true; } else { partMetaData()->inProgress = false; mOtp->mHasPendingAsyncJobs = false; } if (m && !partMetaData()->inProgress) { if (!signature.isEmpty()) { mVerifiedText = data; } setVerificationResult(m, textNode); } if (!m && !partMetaData()->inProgress) { QString errorMsg; QString cryptPlugLibName; QString cryptPlugDisplayName; if (mCryptoProto) { cryptPlugLibName = mCryptoProto->name(); cryptPlugDisplayName = mCryptoProto->displayName(); } if (!mCryptoProto) { if (cryptPlugDisplayName.isEmpty()) { errorMsg = i18n("No appropriate crypto plug-in was found."); } else { errorMsg = i18nc("%1 is either 'OpenPGP' or 'S/MIME'", "No %1 plug-in was found.", cryptPlugDisplayName); } } else { errorMsg = i18n("Crypto plug-in \"%1\" cannot verify signatures.", cryptPlugLibName); } partMetaData()->errorText = i18n("The message is signed, but the " "validity of the signature cannot be " "verified.
" "Reason: %1", errorMsg); } return partMetaData()->isSigned; } static int signatureToStatus(const GpgME::Signature &sig) { switch (sig.status().code()) { case GPG_ERR_NO_ERROR: return GPGME_SIG_STAT_GOOD; case GPG_ERR_BAD_SIGNATURE: return GPGME_SIG_STAT_BAD; case GPG_ERR_NO_PUBKEY: return GPGME_SIG_STAT_NOKEY; case GPG_ERR_NO_DATA: return GPGME_SIG_STAT_NOSIG; case GPG_ERR_SIG_EXPIRED: return GPGME_SIG_STAT_GOOD_EXP; case GPG_ERR_KEY_EXPIRED: return GPGME_SIG_STAT_GOOD_EXPKEY; default: return GPGME_SIG_STAT_ERROR; } } QString prettifyDN(const char *uid) { return QGpgME::DN(uid).prettyDN(); } void SignedMessagePart::sigStatusToMetaData() { GpgME::Key key; if (partMetaData()->isSigned) { GpgME::Signature signature = mSignatures.front(); partMetaData()->status_code = signatureToStatus(signature); partMetaData()->isGoodSignature = partMetaData()->status_code & GPGME_SIG_STAT_GOOD; // save extended signature status flags partMetaData()->sigSummary = signature.summary(); if (partMetaData()->isGoodSignature && !key.keyID()) { // Search for the key by its fingerprint so that we can check for // trust etc. - QGpgME::KeyListJob *job = mCryptoProto->keyListJob(false); // local, no sigs + QGpgME::KeyListJob *job = mCryptoProto->keyListJob(false, false, false); // local, no sigs if (!job) { qCDebug(MIMETREEPARSER_LOG) << "The Crypto backend does not support listing keys. "; } else { std::vector found_keys; // As we are local it is ok to make this synchronous GpgME::KeyListResult res = job->exec(QStringList(QLatin1String(signature.fingerprint())), false, found_keys); if (res.error()) { qCDebug(MIMETREEPARSER_LOG) << "Error while searching key for Fingerprint: " << signature.fingerprint(); } if (found_keys.size() > 1) { // Should not Happen qCDebug(MIMETREEPARSER_LOG) << "Oops: Found more then one Key for Fingerprint: " << signature.fingerprint(); } if (found_keys.size() != 1) { // Should not Happen at this point qCDebug(MIMETREEPARSER_LOG) << "Oops: Found no Key for Fingerprint: " << signature.fingerprint(); } else { key = found_keys[0]; } delete job; } } if (key.keyID()) { partMetaData()->keyId = key.keyID(); } if (partMetaData()->keyId.isEmpty()) { partMetaData()->keyId = signature.fingerprint(); } partMetaData()->keyTrust = signature.validity(); if (key.numUserIDs() > 0 && key.userID(0).id()) { partMetaData()->signer = prettifyDN(key.userID(0).id()); } for (uint iMail = 0; iMail < key.numUserIDs(); ++iMail) { // The following if /should/ always result in TRUE but we // won't trust implicitely the plugin that gave us these data. if (key.userID(iMail).email()) { QString email = QString::fromUtf8(key.userID(iMail).email()); // ### work around gpgme 0.3.QString text() const override;x / cryptplug bug where the // ### email addresses are specified as angle-addr, not addr-spec: if (email.startsWith(QLatin1Char('<')) && email.endsWith(QLatin1Char('>'))) { email = email.mid(1, email.length() - 2); } if (!email.isEmpty()) { partMetaData()->signerMailAddresses.append(email); } } } if (signature.creationTime()) { partMetaData()->creationTime.setTime_t(signature.creationTime()); } else { partMetaData()->creationTime = QDateTime(); } if (partMetaData()->signer.isEmpty()) { if (key.numUserIDs() > 0 && key.userID(0).name()) { partMetaData()->signer = prettifyDN(key.userID(0).name()); } if (!partMetaData()->signerMailAddresses.empty()) { if (partMetaData()->signer.isEmpty()) { partMetaData()->signer = partMetaData()->signerMailAddresses.front(); } else { partMetaData()->signer += QLatin1String(" <") + partMetaData()->signerMailAddresses.front() + QLatin1Char('>'); } } } } } void SignedMessagePart::startVerification(const QByteArray &text, const QTextCodec *aCodec) { startVerificationDetached(text, nullptr, QByteArray()); if (!content() && partMetaData()->isSigned) { setText(aCodec->toUnicode(mVerifiedText)); } } void SignedMessagePart::startVerificationDetached(const QByteArray &text, KMime::Content *textNode, const QByteArray &signature) { partMetaData()->isEncrypted = false; partMetaData()->isDecryptable = false; if (textNode) { parseInternal(textNode, false); } okVerify(text, signature, textNode); if (!partMetaData()->isSigned) { partMetaData()->creationTime = QDateTime(); } } void SignedMessagePart::setVerificationResult(const CryptoBodyPartMemento *m, KMime::Content *textNode) { { const auto vm = dynamic_cast(m); if (vm) { mSignatures = vm->verifyResult().signatures(); } } { const auto vm = dynamic_cast(m); if (vm) { mVerifiedText = vm->plainText(); mSignatures = vm->verifyResult().signatures(); } } { const auto vm = dynamic_cast(m); if (vm) { mVerifiedText = vm->plainText(); mSignatures = vm->verifyResult().signatures(); } } partMetaData()->auditLogError = m->auditLogError(); partMetaData()->auditLog = m->auditLogAsHtml(); partMetaData()->isSigned = !mSignatures.empty(); if (partMetaData()->isSigned) { sigStatusToMetaData(); if (content()) { mOtp->nodeHelper()->setSignatureState(content(), KMMsgFullySigned); if (!textNode) { mOtp->nodeHelper()->setPartMetaData(content(), *partMetaData()); if (!mVerifiedText.isEmpty()) { auto tempNode = new KMime::Content(); tempNode->setContent(KMime::CRLFtoLF(mVerifiedText.constData())); tempNode->parse(); if (!tempNode->head().isEmpty()) { tempNode->contentDescription()->from7BitString("signed data"); } mOtp->nodeHelper()->attachExtraContent(content(), tempNode); parseInternal(tempNode, false); } } } } } QString SignedMessagePart::plaintextContent() const { if (!content()) { return MessagePart::text(); } else { return QString(); } } QString SignedMessagePart::htmlContent() const { if (!content()) { return MessagePart::text(); } else { return QString(); } } //-----CryptMessageBlock--------------------- EncryptedMessagePart::EncryptedMessagePart(ObjectTreeParser *otp, const QString &text, const QGpgME::Protocol *cryptoProto, const QString &fromAddress, KMime::Content *node) : MessagePart(otp, text) , mPassphraseError(false) , mNoSecKey(false) , mCryptoProto(cryptoProto) , mFromAddress(fromAddress) , mDecryptMessage(false) { setContent(node); partMetaData()->technicalProblem = (mCryptoProto == nullptr); partMetaData()->isSigned = false; partMetaData()->isGoodSignature = false; partMetaData()->isEncrypted = false; partMetaData()->isDecryptable = false; partMetaData()->keyTrust = GpgME::Signature::Unknown; partMetaData()->status = i18n("Wrong Crypto Plug-In."); partMetaData()->status_code = GPGME_SIG_STAT_NONE; } EncryptedMessagePart::~EncryptedMessagePart() { } void EncryptedMessagePart::setDecryptMessage(bool decrypt) { mDecryptMessage = decrypt; } bool EncryptedMessagePart::decryptMessage() const { return mDecryptMessage; } void EncryptedMessagePart::setIsEncrypted(bool encrypted) { partMetaData()->isEncrypted = encrypted; } bool EncryptedMessagePart::isEncrypted() const { return partMetaData()->isEncrypted; } bool EncryptedMessagePart::isDecryptable() const { return partMetaData()->isDecryptable; } bool EncryptedMessagePart::passphraseError() const { return mPassphraseError; } void EncryptedMessagePart::startDecryption(const QByteArray &text, const QTextCodec *aCodec) { KMime::Content *content = new KMime::Content; content->setBody(text); content->parse(); startDecryption(content); if (!partMetaData()->inProgress && partMetaData()->isDecryptable) { if (hasSubParts()) { auto _mp = (subParts()[0]).dynamicCast(); if (_mp) { _mp->setText(aCodec->toUnicode(mDecryptedData)); } else { setText(aCodec->toUnicode(mDecryptedData)); } } else { setText(aCodec->toUnicode(mDecryptedData)); } } } bool EncryptedMessagePart::okDecryptMIME(KMime::Content &data) { mPassphraseError = false; partMetaData()->inProgress = false; partMetaData()->errorText.clear(); partMetaData()->auditLogError = GpgME::Error(); partMetaData()->auditLog.clear(); bool bDecryptionOk = false; bool cannotDecrypt = false; NodeHelper *nodeHelper = mOtp->nodeHelper(); Q_ASSERT(decryptMessage()); // Check whether the memento contains a result from last time: const DecryptVerifyBodyPartMemento *m = dynamic_cast(nodeHelper->bodyPartMemento(&data, "decryptverify")); Q_ASSERT(!m || mCryptoProto); //No CryptoPlugin and having a bodyPartMemento -> there is something completely wrong if (!m && mCryptoProto) { QGpgME::DecryptVerifyJob *job = mCryptoProto->decryptVerifyJob(); if (!job) { cannotDecrypt = true; } else { const QByteArray ciphertext = data.decodedContent(); DecryptVerifyBodyPartMemento *newM = new DecryptVerifyBodyPartMemento(job, ciphertext); if (mOtp->allowAsync()) { QObject::connect(newM, &CryptoBodyPartMemento::update, nodeHelper, &NodeHelper::update); if (newM->start()) { partMetaData()->inProgress = true; mOtp->mHasPendingAsyncJobs = true; } else { m = newM; } } else { newM->exec(); m = newM; } nodeHelper->setBodyPartMemento(&data, "decryptverify", newM); } } else if (m && m->isRunning()) { partMetaData()->inProgress = true; mOtp->mHasPendingAsyncJobs = true; m = nullptr; } if (m) { const QByteArray &plainText = m->plainText(); const GpgME::DecryptionResult &decryptResult = m->decryptResult(); const GpgME::VerificationResult &verifyResult = m->verifyResult(); partMetaData()->isSigned = verifyResult.signatures().size() > 0; if (verifyResult.signatures().size() > 0) { auto subPart = SignedMessagePart::Ptr(new SignedMessagePart(mOtp, MessagePart::text(), mCryptoProto, mFromAddress, content())); subPart->setVerificationResult(m, nullptr); appendSubPart(subPart); } - mDecryptRecipients = decryptResult.recipients(); + mDecryptRecipients.clear(); + for (const auto &recipient : decryptResult.recipients()) { + GpgME::Key key; + QGpgME::KeyListJob *job = mCryptoProto->keyListJob(false, false, false); // local, no sigs + if (!job) { + qCDebug(MIMETREEPARSER_LOG) << "The Crypto backend does not support listing keys. "; + } else { + std::vector found_keys; + // As we are local it is ok to make this synchronous + GpgME::KeyListResult res = job->exec(QStringList(QLatin1String(recipient.keyID())), false, found_keys); + if (res.error()) { + qCDebug(MIMETREEPARSER_LOG) << "Error while searching key for Fingerprint: " << recipient.keyID(); + } + if (found_keys.size() > 1) { + // Should not Happen + qCDebug(MIMETREEPARSER_LOG) << "Oops: Found more then one Key for Fingerprint: " << recipient.keyID(); + } + if (found_keys.size() != 1) { + // Should not Happen at this point + qCDebug(MIMETREEPARSER_LOG) << "Oops: Found no Key for Fingerprint: " << recipient.keyID(); + } else { + key = found_keys[0]; + } + } + mDecryptRecipients.push_back(std::make_pair(recipient, key)); + } bDecryptionOk = !decryptResult.error(); // std::stringstream ss; // ss << decryptResult << '\n' << verifyResult; // qCDebug(MIMETREEPARSER_LOG) << ss.str().c_str(); if (!bDecryptionOk && partMetaData()->isSigned) { //Only a signed part partMetaData()->isEncrypted = false; bDecryptionOk = true; mDecryptedData = plainText; } else { mPassphraseError = decryptResult.error().isCanceled() || decryptResult.error().code() == GPG_ERR_NO_SECKEY; partMetaData()->isEncrypted = decryptResult.error().code() != GPG_ERR_NO_DATA; partMetaData()->errorText = QString::fromLocal8Bit(decryptResult.error().asString()); if (partMetaData()->isEncrypted && decryptResult.numRecipients() > 0) { partMetaData()->keyId = decryptResult.recipient(0).keyID(); } if (bDecryptionOk) { mDecryptedData = plainText; } else { mNoSecKey = true; foreach (const GpgME::DecryptionResult::Recipient &recipient, decryptResult.recipients()) { mNoSecKey &= (recipient.status().code() == GPG_ERR_NO_SECKEY); } if (!mPassphraseError && !mNoSecKey) { // GpgME do not detect passphrase error correctly mPassphraseError = true; } } } } if (!bDecryptionOk) { QString cryptPlugLibName; if (mCryptoProto) { cryptPlugLibName = mCryptoProto->name(); } if (!mCryptoProto) { partMetaData()->errorText = i18n("No appropriate crypto plug-in was found."); } else if (cannotDecrypt) { partMetaData()->errorText = i18n("Crypto plug-in \"%1\" cannot decrypt messages.", cryptPlugLibName); } else if (!passphraseError()) { partMetaData()->errorText = i18n("Crypto plug-in \"%1\" could not decrypt the data.", cryptPlugLibName) + QLatin1String("
") + i18n("Error: %1", partMetaData()->errorText); } } return bDecryptionOk; } void EncryptedMessagePart::startDecryption(KMime::Content *data) { if (!content() && !data) { return; } if (!data) { data = content(); } partMetaData()->isEncrypted = true; bool bOkDecrypt = okDecryptMIME(*data); if (partMetaData()->inProgress) { return; } partMetaData()->isDecryptable = bOkDecrypt; if (!partMetaData()->isDecryptable) { setText(QString::fromUtf8(mDecryptedData.constData())); } if (partMetaData()->isEncrypted && !decryptMessage()) { partMetaData()->isDecryptable = true; } if (content() && !partMetaData()->isSigned) { mOtp->nodeHelper()->setPartMetaData(content(), *partMetaData()); if (decryptMessage()) { auto tempNode = new KMime::Content(); tempNode->setContent(KMime::CRLFtoLF(mDecryptedData.constData())); tempNode->parse(); if (!tempNode->head().isEmpty()) { tempNode->contentDescription()->from7BitString("encrypted data"); } mOtp->nodeHelper()->attachExtraContent(content(), tempNode); parseInternal(tempNode, false); } } } QString EncryptedMessagePart::plaintextContent() const { if (!content()) { return MessagePart::text(); } else { return QString(); } } QString EncryptedMessagePart::htmlContent() const { if (!content()) { return MessagePart::text(); } else { return QString(); } } QString EncryptedMessagePart::text() const { if (hasSubParts()) { auto _mp = (subParts()[0]).dynamicCast(); if (_mp) { return _mp->text(); } else { return MessagePart::text(); } } else { return MessagePart::text(); } } EncapsulatedRfc822MessagePart::EncapsulatedRfc822MessagePart(ObjectTreeParser *otp, KMime::Content *node, const KMime::Message::Ptr &message) : MessagePart(otp, QString()) , mMessage(message) { setContent(node); partMetaData()->isEncrypted = false; partMetaData()->isSigned = false; partMetaData()->isEncapsulatedRfc822Message = true; mOtp->nodeHelper()->setNodeDisplayedEmbedded(node, true); mOtp->nodeHelper()->setPartMetaData(node, *partMetaData()); if (!mMessage) { qCWarning(MIMETREEPARSER_LOG) << "Node is of type message/rfc822 but doesn't have a message!"; return; } // The link to "Encapsulated message" is clickable, therefore the temp file needs to exists, // since the user can click the link and expect to have normal attachment operations there. mOtp->nodeHelper()->writeNodeToTempFile(message.data()); parseInternal(message.data(), false); } EncapsulatedRfc822MessagePart::~EncapsulatedRfc822MessagePart() { } QString EncapsulatedRfc822MessagePart::text() const { return renderInternalText(); } void EncapsulatedRfc822MessagePart::fix() const { } diff --git a/mimetreeparser/src/viewer/messagepart.h b/mimetreeparser/src/viewer/messagepart.h index 77bd533d..38cbfef4 100644 --- a/mimetreeparser/src/viewer/messagepart.h +++ b/mimetreeparser/src/viewer/messagepart.h @@ -1,422 +1,423 @@ /* Copyright (c) 2015 Sandro Knauß This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __MIMETREEPARSER_MESSAGEPART_H__ #define __MIMETREEPARSER_MESSAGEPART_H__ #include "mimetreeparser_export.h" #include "mimetreeparser/bodypartformatter.h" #include "mimetreeparser/util.h" #include #include #include #include +#include #include #include #include class QTextCodec; namespace GpgME { class ImportResult; } namespace QGpgME { class Protocol; } namespace KMime { class Content; } namespace MimeTreeParser { class ObjectTreeParser; class CryptoBodyPartMemento; class MessagePartPrivate; class MultiPartAlternativeBodyPartFormatter; namespace Interface { class ObjectTreeSource; } class MIMETREEPARSER_EXPORT MessagePart : public QObject { Q_OBJECT Q_PROPERTY(QString plaintextContent READ plaintextContent) Q_PROPERTY(QString htmlContent READ htmlContent) Q_PROPERTY(bool isAttachment READ isAttachment) Q_PROPERTY(bool root READ isRoot) Q_PROPERTY(bool isHtml READ isHtml) Q_PROPERTY(bool isHidden READ isHidden) Q_PROPERTY(QString attachmentIndex READ attachmentIndex CONSTANT) Q_PROPERTY(QString link READ attachmentLink CONSTANT) public: typedef QSharedPointer Ptr; MessagePart(ObjectTreeParser *otp, const QString &text); ~MessagePart(); void setParentPart(MessagePart *parentPart); MessagePart *parentPart() const; virtual QString text() const; void setText(const QString &text); virtual QString plaintextContent() const; virtual QString htmlContent() const; /** The KMime::Content* node that's represented by this part. * Can be @c nullptr, e.g. for sub-parts of an inline signed body part. */ KMime::Content *content() const; void setContent(KMime::Content *node); /** The KMime::Content* node that's the source of this part. * This is not necessarily the same as content(), for example for * broken-up multipart nodes. */ KMime::Content *attachmentContent() const; void setAttachmentContent(KMime::Content *node); bool isAttachment() const; /** @see KMime::Content::index() */ QString attachmentIndex() const; /** @see NodeHelper::asHREF */ QString attachmentLink() const; /** Returns a string respresentation of an URL that can be used * to invoke a BodyPartURLHandler for this body part. */ QString makeLink(const QString &path) const; void setIsRoot(bool root); bool isRoot() const; virtual bool isHtml() const; virtual bool isHidden() const; PartMetaData *partMetaData() const; Interface::BodyPartMemento *memento() const; void setMemento(Interface::BodyPartMemento *memento); /* only a function that should be removed if the refactoring is over */ virtual void fix() const; void appendSubPart(const MessagePart::Ptr &messagePart); const QVector &subParts() const; bool hasSubParts() const; Interface::ObjectTreeSource *source() const; NodeHelper* nodeHelper() const; protected: void parseInternal(KMime::Content *node, bool onlyOneMimePart); QString renderInternalText() const; ObjectTreeParser *mOtp = nullptr; private: std::unique_ptr d; }; class MIMETREEPARSER_EXPORT MimeMessagePart : public MessagePart { Q_OBJECT public: typedef QSharedPointer Ptr; MimeMessagePart(MimeTreeParser::ObjectTreeParser *otp, KMime::Content *node, bool onlyOneMimePart); virtual ~MimeMessagePart(); QString text() const override; QString plaintextContent() const override; QString htmlContent() const override; private: bool mOnlyOneMimePart; friend class AlternativeMessagePart; }; class MIMETREEPARSER_EXPORT MessagePartList : public MessagePart { Q_OBJECT public: typedef QSharedPointer Ptr; MessagePartList(MimeTreeParser::ObjectTreeParser *otp); virtual ~MessagePartList(); QString text() const override; QString plaintextContent() const override; QString htmlContent() const override; }; enum IconType { NoIcon = 0, IconExternal, IconInline }; class MIMETREEPARSER_EXPORT TextMessagePart : public MessagePartList { Q_OBJECT Q_PROPERTY(bool showTextFrame READ showTextFrame CONSTANT) Q_PROPERTY(bool showLink READ showLink CONSTANT) Q_PROPERTY(QString label READ label CONSTANT) Q_PROPERTY(QString comment READ comment CONSTANT) public: typedef QSharedPointer Ptr; TextMessagePart(MimeTreeParser::ObjectTreeParser *otp, KMime::Content *node, bool drawFrame, bool decryptMessage); virtual ~TextMessagePart(); KMMsgSignatureState signatureState() const; KMMsgEncryptionState encryptionState() const; bool decryptMessage() const; bool isHidden() const override; bool showLink() const; bool showTextFrame() const; void setShowTextFrame(bool showFrame); /** The attachment filename, or the closest approximation thereof we have. */ QString label() const; /** A description of this attachment, if provided. */ QString comment() const; /** Temporary file containing the part content. */ QString temporaryFilePath() const; private: void parseContent(); KMMsgSignatureState mSignatureState; KMMsgEncryptionState mEncryptionState; bool mDrawFrame; bool mDecryptMessage; bool mIsHidden; friend class ObjectTreeParser; }; class MIMETREEPARSER_EXPORT AttachmentMessagePart : public TextMessagePart { Q_OBJECT public: typedef QSharedPointer Ptr; AttachmentMessagePart(MimeTreeParser::ObjectTreeParser *otp, KMime::Content *node, bool drawFrame, bool decryptMessage); virtual ~AttachmentMessagePart(); IconType asIcon() const; bool neverDisplayInline() const; void setNeverDisplayInline(bool displayInline); bool isImage() const; void setIsImage(bool image); bool isHidden() const override; private: bool mIsImage; bool mNeverDisplayInline; }; class MIMETREEPARSER_EXPORT HtmlMessagePart : public MessagePart { Q_OBJECT public: typedef QSharedPointer Ptr; HtmlMessagePart(MimeTreeParser::ObjectTreeParser *otp, KMime::Content *node, MimeTreeParser::Interface::ObjectTreeSource *source); virtual ~HtmlMessagePart(); QString text() const override; void fix() const override; bool isHtml() const override; private: Interface::ObjectTreeSource *mSource; QString mBodyHTML; QByteArray mCharset; friend class DefaultRendererPrivate; }; class MIMETREEPARSER_EXPORT AlternativeMessagePart : public MessagePart { Q_OBJECT public: typedef QSharedPointer Ptr; AlternativeMessagePart(MimeTreeParser::ObjectTreeParser *otp, KMime::Content *node, Util::HtmlMode preferredMode); virtual ~AlternativeMessagePart(); QString text() const override; Util::HtmlMode preferredMode() const; bool isHtml() const override; QString plaintextContent() const override; QString htmlContent() const override; QList availableModes(); void fix() const override; private: Util::HtmlMode mPreferredMode; QMap mChildNodes; QMap mChildParts; friend class DefaultRendererPrivate; friend class ObjectTreeParser; friend class MultiPartAlternativeBodyPartFormatter; }; class MIMETREEPARSER_EXPORT CertMessagePart : public MessagePart { Q_OBJECT public: typedef QSharedPointer Ptr; CertMessagePart(MimeTreeParser::ObjectTreeParser *otp, KMime::Content *node, const QGpgME::Protocol *cryptoProto, bool autoImport); virtual ~CertMessagePart(); QString text() const override; private: bool mAutoImport; GpgME::ImportResult mImportResult; const QGpgME::Protocol *mCryptoProto; friend class DefaultRendererPrivate; }; class MIMETREEPARSER_EXPORT EncapsulatedRfc822MessagePart : public MessagePart { Q_OBJECT public: typedef QSharedPointer Ptr; EncapsulatedRfc822MessagePart(MimeTreeParser::ObjectTreeParser *otp, KMime::Content *node, const KMime::Message::Ptr &message); virtual ~EncapsulatedRfc822MessagePart(); QString text() const override; void fix() const override; private: const KMime::Message::Ptr mMessage; friend class DefaultRendererPrivate; }; class MIMETREEPARSER_EXPORT EncryptedMessagePart : public MessagePart { Q_OBJECT Q_PROPERTY(bool decryptMessage READ decryptMessage WRITE setDecryptMessage) Q_PROPERTY(bool isEncrypted READ isEncrypted) Q_PROPERTY(bool passphraseError READ passphraseError) public: typedef QSharedPointer Ptr; EncryptedMessagePart(ObjectTreeParser *otp, const QString &text, const QGpgME::Protocol *cryptoProto, const QString &fromAddress, KMime::Content *node); virtual ~EncryptedMessagePart(); QString text() const override; void setDecryptMessage(bool decrypt); bool decryptMessage() const; void setIsEncrypted(bool encrypted); bool isEncrypted() const; bool isDecryptable() const; bool passphraseError() const; void startDecryption(const QByteArray &text, const QTextCodec *aCodec); void startDecryption(KMime::Content *data = nullptr); QByteArray mDecryptedData; QString plaintextContent() const override; QString htmlContent() const override; private: /** Handles the dectyptioon of a given content * returns true if the decryption was successfull * if used in async mode, check if mMetaData.inProgress is true, it inicates a running decryption process. */ bool okDecryptMIME(KMime::Content &data); protected: bool mPassphraseError; bool mNoSecKey; const QGpgME::Protocol *mCryptoProto; QString mFromAddress; bool mDecryptMessage; QByteArray mVerifiedText; - std::vector mDecryptRecipients; + std::vector> mDecryptRecipients; friend class DefaultRendererPrivate; }; class MIMETREEPARSER_EXPORT SignedMessagePart : public MessagePart { Q_OBJECT Q_PROPERTY(bool isSigned READ isSigned) public: typedef QSharedPointer Ptr; SignedMessagePart(ObjectTreeParser *otp, const QString &text, const QGpgME::Protocol *cryptoProto, const QString &fromAddress, KMime::Content *node); virtual ~SignedMessagePart(); void setIsSigned(bool isSigned); bool isSigned() const; void startVerification(const QByteArray &text, const QTextCodec *aCodec); void startVerificationDetached(const QByteArray &text, KMime::Content *textNode, const QByteArray &signature); QByteArray mDecryptedData; std::vector mSignatures; QString plaintextContent() const override; QString htmlContent() const override; private: /** Handles the verification of data * If signature is empty it is handled as inline signature otherwise as detached signature mode. * Returns true if the verfication was successfull and the block is signed. * If used in async mode, check if mMetaData.inProgress is true, it inicates a running verification process. */ bool okVerify(const QByteArray &data, const QByteArray &signature, KMime::Content *textNode); void sigStatusToMetaData(); void setVerificationResult(const CryptoBodyPartMemento *m, KMime::Content *textNode); protected: const QGpgME::Protocol *mCryptoProto; QString mFromAddress; QByteArray mVerifiedText; friend EncryptedMessagePart; friend class DefaultRendererPrivate; }; } #endif //__MIMETREEPARSER_MESSAGEPART_H__