diff --git a/messageviewer/src/viewer/messagepart.cpp b/messageviewer/src/viewer/messagepart.cpp index 5b8f994f..1d8b1823 100644 --- a/messageviewer/src/viewer/messagepart.cpp +++ b/messageviewer/src/viewer/messagepart.cpp @@ -1,902 +1,994 @@ /* 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 "messageviewer_debug.h" #include #include "objecttreeparser.h" #include "converthtmltoplaintext.h" #include "htmlquotecolorer.h" #include "csshelper.h" +#include "cryptohelper.h" #include #include +#include #include #include #include #include #include #include #include #include #include #include using namespace MessageViewer; /** 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; } //--------CryptoBlock------------------- CryptoBlock::CryptoBlock(ObjectTreeParser *otp, PartMetaData *block, const Kleo::CryptoBackend::Protocol *cryptoProto, const QString &fromAddress, KMime::Content *node) : HTMLBlock() , mOtp(otp) , mMetaData(block) , mCryptoProto(cryptoProto) , mFromAddress(fromAddress) , mNode(node) { internalEnter(); } CryptoBlock::~CryptoBlock() { internalExit(); } void CryptoBlock::internalEnter() { MessageViewer::HtmlWriter *writer = mOtp->htmlWriter(); if (writer && !entered) { entered = true; writer->queue(mOtp->writeSigstatHeader(*mMetaData, mCryptoProto, mFromAddress, mNode)); } } void CryptoBlock::internalExit() { if (!entered) { return; } MessageViewer::HtmlWriter *writer = mOtp->htmlWriter(); writer->queue(mOtp->writeSigstatFooter(*mMetaData)); entered = false; } AttachmentMarkBlock::AttachmentMarkBlock(MessageViewer::HtmlWriter *writer, KMime::Content *node) : mNode(node) , mWriter(writer) { internalEnter(); } AttachmentMarkBlock::~AttachmentMarkBlock() { internalExit(); } void AttachmentMarkBlock::internalEnter() { if (mWriter && !entered) { const QString index = mNode->index().toString(); mWriter->queue(QStringLiteral("").arg(index)); mWriter->queue(QStringLiteral("
\n").arg(index)); entered = true; } } void AttachmentMarkBlock::internalExit() { if (!entered) { return; } mWriter->queue(QStringLiteral("
")); entered = false; } TextBlock::TextBlock(MessageViewer::HtmlWriter *writer, MessageViewer::NodeHelper *nodeHelper, KMime::Content *node, bool link) : mWriter(writer) , mNodeHelper(nodeHelper) , mNode(node) , mLink(link) { internalEnter(); } TextBlock::~TextBlock() { internalExit(); } void TextBlock::internalEnter() { if (!mWriter || entered) { return; } entered = true; const QString label = MessageCore::StringUtil::quoteHtmlChars(NodeHelper::fileName(mNode), true); const QString comment = MessageCore::StringUtil::quoteHtmlChars(mNode->contentDescription()->asUnicodeString(), true); const QString dir = QApplication::isRightToLeft() ? QStringLiteral("rtl") : QStringLiteral("ltr"); mWriter->queue(QLatin1String("" "
")); if (!mLink) mWriter->queue(QLatin1String("asHREF(mNode, QStringLiteral("body")) + QLatin1String("\">") + label + QLatin1String("")); else { mWriter->queue(label); } if (!comment.isEmpty()) { mWriter->queue(QLatin1String("
") + comment); } mWriter->queue(QLatin1String("
")); } void TextBlock::internalExit() { if (!entered) { return; } entered = false; mWriter->queue(QStringLiteral("
")); } HTMLWarnBlock::HTMLWarnBlock(HtmlWriter* writer, const QString& msg) : mWriter(writer) , mMsg(msg) { internalEnter(); } HTMLWarnBlock::~HTMLWarnBlock() { internalExit(); } void HTMLWarnBlock::internalEnter() { if (!mWriter || entered) { return; } entered = true; if (!mMsg.isEmpty()) { mWriter->queue(QStringLiteral("
\n")); mWriter->queue(mMsg); mWriter->queue(QStringLiteral("


")); } mWriter->queue(QStringLiteral("
\n")); } void HTMLWarnBlock::internalExit() { if (!entered) { return; } entered = false; mWriter->queue(QStringLiteral("
\n")); } //------MessagePart----------------------- MessagePart::MessagePart(ObjectTreeParser *otp, const QString &text) : mText(text) , mOtp(otp) , mSubOtp(Q_NULLPTR) { } MessagePart::~MessagePart() { if (mSubOtp) { delete mSubOtp->htmlWriter(); delete mSubOtp; mSubOtp = Q_NULLPTR; } } PartMetaData *MessagePart::partMetaData() { return &mMetaData; } QString MessagePart::text() const { return mText; } void MessagePart::setText(const QString &text) { mText = text; } void MessagePart::html(bool decorate) { MessageViewer::HtmlWriter *writer = mOtp->htmlWriter(); if (!writer) { return; } const CryptoBlock block(mOtp, &mMetaData, Q_NULLPTR, QString(), Q_NULLPTR); writer->queue(mOtp->quotedHTML(text(), decorate)); } void MessagePart::parseInternal(KMime::Content *node, bool onlyOneMimePart) { mSubOtp = new ObjectTreeParser(mOtp, onlyOneMimePart); mSubOtp->setAllowAsync(mOtp->allowAsync()); if (mOtp->htmlWriter()) { mSubOtp->mHtmlWriter = new QueueHtmlWriter(mOtp->htmlWriter()); } mSubOtp->parseObjectTreeInternal(node); } void MessagePart::renderInternalHtml() const { if (mSubOtp && mOtp->htmlWriter()) { static_cast(mSubOtp->htmlWriter())->replay(); } } void MessagePart::copyContentFrom() const { if (mSubOtp) { mOtp->copyContentFrom(mSubOtp); } } QString MessagePart::renderInternalText() const { if (!mSubOtp) { return QString(); } return mSubOtp->plainTextContent(); } //-----TextMessageBlock---------------------- -TextMessagePart::TextMessagePart(ObjectTreeParser *otp, KMime::Content *node, bool drawFrame, bool showLink) +TextMessagePart::TextMessagePart(ObjectTreeParser *otp, KMime::Content *node, bool drawFrame, bool showLink, bool decryptMessage) : MessagePart(otp, QString()) , mNode(node) , mDrawFrame(drawFrame) , mShowLink(showLink) + , mDecryptMessage(decryptMessage) { if (!mNode) { qCWarning(MESSAGEVIEWER_LOG) << "not a valid node"; return; } - mBlocks = mOtp->writeBodyStr2(mNode->decodedContent(), mOtp->codecFor(mNode), NodeHelper::fromAsString(mNode), mSignatureState, mEncryptionState); + parseContent(); } TextMessagePart::~TextMessagePart() { } +bool TextMessagePart::decryptMessage() const +{ + return mDecryptMessage; +} + +void TextMessagePart::parseContent() +{ + const auto aCodec = mOtp->codecFor(mNode); + const QString &fromAddress = NodeHelper::fromAsString(mNode); + mSignatureState = KMMsgNotSigned; + mEncryptionState = KMMsgNotEncrypted; + const auto blocks = prepareMessageForDecryption(mNode->decodedContent()); + + const auto cryptProto = Kleo::CryptoBackendFactory::instance()->openpgp(); + + if (!blocks.isEmpty()) { + + if (blocks.count() > 1 || blocks.at(0).type() != MessageViewer::NoPgpBlock) { + mOtp->setCryptoProtocol(cryptProto); + } + + + QString htmlStr; + QString plainTextStr; + + /* 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; + + Q_FOREACH (const auto &block, blocks) { + + if (!fullySignedOrEncryptedTmp) { + fullySignedOrEncrypted = false; + } + + if (block.type() == NoPgpBlock && !block.text().trimmed().isEmpty()) { + fullySignedOrEncryptedTmp = false; + mBlocks.append(MessagePart::Ptr(new MessagePart(mOtp, aCodec->toUnicode(block.text())))); + } else if (block.type() == PgpMessageBlock) { + CryptoMessagePart::Ptr mp(new CryptoMessagePart(mOtp, QString(), cryptProto, fromAddress, 0)); + mBlocks.append(mp); + if (!decryptMessage()) { + continue; + } + mp->startDecryption(block.text(), aCodec); + if (mp->partMetaData()->inProgress) { + continue; + } + } else if (block.type() == ClearsignedBlock) { + CryptoMessagePart::Ptr mp(new CryptoMessagePart(mOtp, QString(), cryptProto, fromAddress, 0)); + mBlocks.append(mp); + mp->startVerification(block.text(), aCodec); + } else { + continue; + } + + const PartMetaData *messagePart(mBlocks.last()->partMetaData()); + + if (!messagePart->isEncrypted && !messagePart->isSigned && !block.text().trimmed().isEmpty()) { + mBlocks.last()->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; + } + } + } +} + void TextMessagePart::html(bool decorate) { HTMLBlock::Ptr block; MessageViewer::HtmlWriter *writer = mOtp->htmlWriter(); if (mDrawFrame) { block = HTMLBlock::Ptr(new TextBlock(writer, mOtp->nodeHelper(), mNode, mShowLink)); } foreach (const MessagePart::Ptr &mp, mBlocks) { mp->html(decorate); } } QString TextMessagePart::text() const { QString text; foreach (const MessagePart::Ptr &mp, mBlocks) { text += mp->text(); } return text; } KMMsgEncryptionState TextMessagePart::encryptionState() const { return mEncryptionState; } KMMsgSignatureState TextMessagePart::signatureState() const { return mSignatureState; } //-----HtmlMessageBlock---------------------- HtmlMessagePart::HtmlMessagePart(ObjectTreeParser* otp, KMime::Content* node, ObjectTreeSourceIf *source) : MessagePart(otp, QString()) , mNode(node) , mSource(source) { if (!mNode) { qCWarning(MESSAGEVIEWER_LOG) << "not a valid node"; return; } const QByteArray partBody(mNode->decodedContent()); mBodyHTML = mOtp->codecFor(mNode)->toUnicode(partBody); mCharset = NodeHelper::charset(mNode); } HtmlMessagePart::~HtmlMessagePart() { } void HtmlMessagePart::fix() { mOtp->mHtmlContent += mBodyHTML; mOtp->mHtmlContentCharset = mCharset; } void HtmlMessagePart::html(bool decorate) { fix(); MessageViewer::HtmlWriter *writer = mOtp->htmlWriter(); if (!writer) { return; } HTMLBlock::Ptr block; if (mSource->htmlMail()) { QString bodyText = mBodyHTML; HTMLQuoteColorer colorer; colorer.setEnableHtmlQuoteColorer(MessageViewer::MessageViewerSettings::self()->htmlQuoteColorerEnabled()); QString extraHead; for (int i = 0; i < 3; ++i) { colorer.setQuoteColor(i, mSource->cssHelper()->quoteColor(i)); } bodyText = colorer.process(bodyText, extraHead); mOtp->mNodeHelper->setNodeDisplayedEmbedded(mNode, true); writer->extraHead(extraHead); // Show the "external references" warning (with possibility to load // external references only if loading external references is disabled // and the HTML code contains obvious external references). For // messages where the external references are obfuscated the user won't // have an easy way to load them but that shouldn't be a problem // because only spam contains obfuscated external references. if (!mSource->htmlLoadExternal() && containsExternalReferences(bodyText, extraHead)) { block = HTMLBlock::Ptr(new HTMLWarnBlock(writer, i18n("Note: This HTML message may contain external " "references to images etc. For security/privacy reasons " "external references are not loaded. If you trust the " "sender of this message then you can load the external " "references for this message " "by clicking here."))); } else { block = HTMLBlock::Ptr(new HTMLWarnBlock(writer, QString())); } // Make sure the body is relative, so that nothing is painted over above "Note: ..." // if a malicious message uses absolute positioning. #137643 writer->queue(bodyText); } else { block = HTMLBlock::Ptr(new HTMLWarnBlock(writer, i18n("Note: This is an HTML message. For " "security reasons, only the raw HTML code " "is shown. If you trust the sender of this " "message then you can activate formatted " "HTML display for this message " "by clicking here."))); // Make sure the body is relative, so that nothing is painted over above "Note: ..." // if a malicious message uses absolute positioning. #137643 ConvertHtmlToPlainText convert; convert.setHtmlString(mBodyHTML); QString result = convert.generatePlainText(); result.replace(QLatin1String("\n"), QStringLiteral("
")); writer->queue(result); } mSource->setHtmlMode(Util::Html); } QString HtmlMessagePart::text() const { return mBodyHTML; } //-----MimeMessageBlock---------------------- MimeMessagePart::MimeMessagePart(ObjectTreeParser *otp, KMime::Content *node, bool onlyOneMimePart) : MessagePart(otp, QString()) , mNode(node) , mOnlyOneMimePart(onlyOneMimePart) { if (!mNode) { qCWarning(MESSAGEVIEWER_LOG) << "not a valid node"; return; } parseInternal(mNode, mOnlyOneMimePart); } MimeMessagePart::~MimeMessagePart() { } void MimeMessagePart::html(bool decorate) { copyContentFrom(); renderInternalHtml(); } QString MimeMessagePart::text() const { return renderInternalText(); } //-----AlternativeMessagePart---------------------- AlternativeMessagePart::AlternativeMessagePart(ObjectTreeParser* otp, KMime::Content* textNode, KMime::Content* htmlNode) : MessagePart(otp, QString()) , mTextNode(textNode) , mHTMLNode(htmlNode) , mViewHtml(false) { if (!mTextNode && !mHTMLNode) { qCWarning(MESSAGEVIEWER_LOG) << "not a valid nodes"; return; } if (mTextNode) { mTextPart = MimeMessagePart::Ptr(new MimeMessagePart(mOtp, mTextNode, true)); } if (mHTMLNode) { mHTMLPart = MimeMessagePart::Ptr(new MimeMessagePart(mOtp, mHTMLNode, true)); } } AlternativeMessagePart::~AlternativeMessagePart() { } void AlternativeMessagePart::setViewHtml(bool html) { mViewHtml = html; } bool AlternativeMessagePart::viewHtml() { return mViewHtml; } void AlternativeMessagePart::html(bool decorate) { MessageViewer::HtmlWriter *writer = mOtp->htmlWriter(); if (!writer) { // If there is no HTML writer, process both the HTML and the plain text nodes, as we're collecting // the plainTextContent and the htmlContent if (mTextPart) { mTextPart->copyContentFrom(); } if (mHTMLPart) { mHTMLPart->copyContentFrom(); } return; } if (viewHtml() && mHTMLPart) { mHTMLPart->copyContentFrom(); mHTMLPart->html(decorate); } else if (mTextNode) { mTextPart->html(decorate); } } QString AlternativeMessagePart::text() const { if (mTextPart) { return mTextPart->text(); } return QString(); } //-----CertMessageBlock---------------------- CertMessagePart::CertMessagePart(ObjectTreeParser* otp, KMime::Content* node, const Kleo::CryptoBackend::Protocol *cryptoProto, bool autoImport) : MessagePart(otp, QString()) , mAutoImport(autoImport) , mCryptoProto(cryptoProto) { if (!mNode) { qCWarning(MESSAGEVIEWER_LOG) << "not a valid node"; return; } if (!mAutoImport) { return; } const QByteArray certData = node->decodedContent(); Kleo::ImportJob *import = mCryptoProto->importJob(); KleoJobExecutor executor; mImportResult = executor.exec(import, certData); } CertMessagePart::~CertMessagePart() { } void CertMessagePart::html(bool decorate) { MessageViewer::HtmlWriter *writer = mOtp->htmlWriter(); if (!writer) { return; } mOtp->writeCertificateImportResult(mImportResult); } QString CertMessagePart::text() const { return QString(); } //-----CryptMessageBlock--------------------- CryptoMessagePart::CryptoMessagePart(ObjectTreeParser *otp, const QString &text, const Kleo::CryptoBackend::Protocol *cryptoProto, const QString &fromAddress, KMime::Content *node) : MessagePart(otp, text) , mCryptoProto(cryptoProto) , mFromAddress(fromAddress) , mNode(node) , mDecryptMessage(false) { mMetaData.technicalProblem = (mCryptoProto == 0); mMetaData.isSigned = false; mMetaData.isGoodSignature = false; mMetaData.isEncrypted = false; mMetaData.isDecryptable = false; mMetaData.keyTrust = GpgME::Signature::Unknown; mMetaData.status = i18n("Wrong Crypto Plug-In."); mMetaData.status_code = GPGME_SIG_STAT_NONE; } CryptoMessagePart::~CryptoMessagePart() { } void CryptoMessagePart::startDecryption(const QByteArray &text, const QTextCodec *aCodec) { mDecryptMessage = true; KMime::Content *content = new KMime::Content; content->setBody(text); content->parse(); startDecryption(content); if (!mMetaData.inProgress && mMetaData.isDecryptable) { setText(aCodec->toUnicode(mDecryptedData)); } } void CryptoMessagePart::startDecryption(KMime::Content *data) { if (!mNode && !data) { return; } if (!data) { data = mNode; } mDecryptMessage = true; bool signatureFound; bool actuallyEncrypted = true; bool decryptionStarted; bool bOkDecrypt = mOtp->okDecryptMIME(*data, mDecryptedData, signatureFound, mSignatures, true, mPassphraseError, actuallyEncrypted, decryptionStarted, mMetaData); if (decryptionStarted) { mMetaData.inProgress = true; return; } mMetaData.isDecryptable = bOkDecrypt; mMetaData.isEncrypted = actuallyEncrypted; mMetaData.isSigned = signatureFound; if (!mMetaData.isDecryptable) { setText(QString::fromUtf8(mDecryptedData.constData())); } if (mMetaData.isSigned) { mOtp->sigStatusToMetaData(mSignatures, mCryptoProto, mMetaData, GpgME::Key()); mVerifiedText = mDecryptedData; } if (mMetaData.isEncrypted && !mDecryptMessage) { mMetaData.isDecryptable = true; } if (mNode) { mOtp->mNodeHelper->setPartMetaData(mNode, mMetaData); if (mDecryptMessage) { auto tempNode = new KMime::Content(); tempNode->setContent(KMime::CRLFtoLF(mDecryptedData.constData())); tempNode->parse(); if (!tempNode->head().isEmpty()) { tempNode->contentDescription()->from7BitString("encrypted data"); } mOtp->mNodeHelper->attachExtraContent(mNode, tempNode); parseInternal(tempNode, false); } } } void CryptoMessagePart::startVerification(const QByteArray &text, const QTextCodec *aCodec) { startVerificationDetached(text, 0, QByteArray()); if (!mNode && mMetaData.isSigned) { setText(aCodec->toUnicode(mVerifiedText)); } } void CryptoMessagePart::startVerificationDetached(const QByteArray &text, KMime::Content *textNode, const QByteArray &signature) { mMetaData.isEncrypted = false; mMetaData.isDecryptable = false; mOtp->okVerify(text, mCryptoProto, mMetaData, mVerifiedText, mSignatures, signature, mNode); if (mMetaData.isSigned) { mOtp->sigStatusToMetaData(mSignatures, mCryptoProto, mMetaData, GpgME::Key()); } else { mMetaData.creationTime = QDateTime(); } if (mNode) { if (textNode && !signature.isEmpty()) { mVerifiedText = text; } else if (!mVerifiedText.isEmpty()) { textNode = new KMime::Content(); textNode->setContent(KMime::CRLFtoLF(mVerifiedText.constData())); textNode->parse(); if (!textNode->head().isEmpty()) { textNode->contentDescription()->from7BitString("opaque signed data"); } mOtp->mNodeHelper->attachExtraContent(mNode, textNode); } if (!mVerifiedText.isEmpty() && textNode) { parseInternal(textNode, false); } } } void CryptoMessagePart::writeDeferredDecryptionBlock() const { Q_ASSERT(!mMetaData.isEncrypted); Q_ASSERT(mDecryptMessage); MessageViewer::HtmlWriter *writer = mOtp->htmlWriter(); if (!writer) { return; } const QString iconName = QLatin1String("file:///") + KIconLoader::global()->iconPath(QStringLiteral("document-decrypt"), KIconLoader::Small); writer->queue(QLatin1String("
") + i18n("This message is encrypted.") + QLatin1String("
" "")); } void CryptoMessagePart::html(bool decorate) { bool hideErrors = false; MessageViewer::HtmlWriter *writer = mOtp->htmlWriter(); //TODO: still the following part should not be here copyContentFrom(); if (!writer) { return; } if (mMetaData.isEncrypted && !mDecryptMessage) { const CryptoBlock block(mOtp, &mMetaData, mCryptoProto, mFromAddress, mNode); writeDeferredDecryptionBlock(); } else if (mMetaData.inProgress) { const CryptoBlock block(mOtp, &mMetaData, mCryptoProto, mFromAddress, mNode); // In progress has no special body } else if (mMetaData.isEncrypted && !mMetaData.isDecryptable) { const CryptoBlock block(mOtp, &mMetaData, mCryptoProto, mFromAddress, mNode); writer->queue(text()); //Do not quote ErrorText } else { if (mMetaData.isSigned && mVerifiedText.isEmpty() && !hideErrors) { const CryptoBlock block(mOtp, &mMetaData, mCryptoProto, mFromAddress, mNode); writer->queue(QStringLiteral("

")); writer->queue(i18n("The crypto engine returned no cleartext data.")); writer->queue(QStringLiteral("

")); writer->queue(QStringLiteral("
 
")); writer->queue(i18n("Status: ")); if (!mMetaData.status.isEmpty()) { writer->queue(QStringLiteral("")); writer->queue(mMetaData.status); writer->queue(QStringLiteral("")); } else { writer->queue(i18nc("Status of message unknown.", "(unknown)")); } } else if (mNode) { const CryptoBlock block(mOtp, &mMetaData, mCryptoProto, mFromAddress, mNode); renderInternalHtml(); } else { MessagePart::html(decorate); } } } EncapsulatedRfc822MessagePart::EncapsulatedRfc822MessagePart(ObjectTreeParser *otp, KMime::Content *node, const KMime::Message::Ptr &message) : MessagePart(otp, QString()) , mMessage(message) , mNode(node) { mMetaData.isEncrypted = false; mMetaData.isSigned = false; mMetaData.isEncapsulatedRfc822Message = true; mOtp->nodeHelper()->setNodeDisplayedEmbedded(mNode, true); mOtp->nodeHelper()->setPartMetaData(mNode, mMetaData); if (!mMessage) { qCWarning(MESSAGEVIEWER_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() { } void EncapsulatedRfc822MessagePart::html(bool decorate) { Q_UNUSED(decorate) if (!mSubOtp) { return; } MessageViewer::HtmlWriter *writer = mOtp->htmlWriter(); if (!writer) { return; } const CryptoBlock block(mOtp, &mMetaData, Q_NULLPTR, mMessage->from()->asUnicodeString(), mMessage.data()); writer->queue(mOtp->mSource->createMessageHeader(mMessage.data())); renderInternalHtml(); mOtp->nodeHelper()->setPartMetaData(mNode, mMetaData); } QString EncapsulatedRfc822MessagePart::text() const { return renderInternalText(); } diff --git a/messageviewer/src/viewer/messagepart.h b/messageviewer/src/viewer/messagepart.h index 48e6d387..9c71132e 100644 --- a/messageviewer/src/viewer/messagepart.h +++ b/messageviewer/src/viewer/messagepart.h @@ -1,309 +1,312 @@ /* 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 _MESSAGEVIEWER_MESSAGEPART_H_ #define _MESSAGEVIEWER_MESSAGEPART_H_ #include "partmetadata.h" #include "nodehelper.h" #include #include #include #include #include #include class QTextCodec; namespace GpgME { class ImportResult; } namespace KMime { class Content; } namespace MessageViewer { class ObjectTreeParser; class ObjectTreeSourceIf; class HtmlWriter; class NodeHelper; class HTMLBlock { public: typedef QSharedPointer Ptr; HTMLBlock() : entered(false) { } virtual ~HTMLBlock() { } protected: bool entered; }; class CryptoBlock: public HTMLBlock { public: CryptoBlock(ObjectTreeParser *otp, PartMetaData *block, const Kleo::CryptoBackend::Protocol *cryptoProto, const QString &fromAddress, KMime::Content *node); virtual ~CryptoBlock(); private: void internalEnter(); void internalExit(); ObjectTreeParser *mOtp; PartMetaData *mMetaData; const Kleo::CryptoBackend::Protocol *mCryptoProto; QString mFromAddress; KMime::Content *mNode; }; // The attachment mark is a div that is placed around the attchment. It is used for drawing // a yellow border around the attachment when scrolling to it. When scrolling to it, the border // color of the div is changed, see KMReaderWin::scrollToAttachment(). class AttachmentMarkBlock : public HTMLBlock { public: AttachmentMarkBlock(MessageViewer::HtmlWriter *writer, KMime::Content *node); virtual ~AttachmentMarkBlock(); private: void internalEnter(); void internalExit(); KMime::Content *mNode; HtmlWriter *mWriter; }; class TextBlock : public HTMLBlock { public: TextBlock(MessageViewer::HtmlWriter *writer, MessageViewer::NodeHelper *nodeHelper, KMime::Content *node, bool link); virtual ~TextBlock(); private: void internalEnter(); void internalExit(); private: HtmlWriter *mWriter; NodeHelper *mNodeHelper; KMime::Content *mNode; QString mFileName; bool mLink; }; class HTMLWarnBlock : public HTMLBlock { public: HTMLWarnBlock(MessageViewer::HtmlWriter* writer, const QString &msg); virtual ~HTMLWarnBlock(); private: void internalEnter(); void internalExit(); private: HtmlWriter* mWriter; const QString &mMsg; }; class MessagePart { public: typedef QSharedPointer Ptr; MessagePart(ObjectTreeParser *otp, const QString &text); virtual ~MessagePart(); virtual QString text() const; void setText(const QString &text); virtual void html(bool decorate); PartMetaData *partMetaData(); protected: void parseInternal(KMime::Content *node, bool onlyOneMimePart); void renderInternalHtml() const; void copyContentFrom() const; QString renderInternalText() const; QString mText; ObjectTreeParser *mOtp; ObjectTreeParser *mSubOtp; PartMetaData mMetaData; }; class MimeMessagePart : public MessagePart { public: typedef QSharedPointer Ptr; MimeMessagePart(MessageViewer::ObjectTreeParser *otp, KMime::Content *node, bool onlyOneMimePart); virtual ~MimeMessagePart(); QString text() const Q_DECL_OVERRIDE; void html(bool decorate) Q_DECL_OVERRIDE; private: KMime::Content *mNode; bool mOnlyOneMimePart; friend class AlternativeMessagePart; }; class TextMessagePart : public MessagePart { public: typedef QSharedPointer Ptr; - TextMessagePart(MessageViewer::ObjectTreeParser *otp, KMime::Content *node, bool drawFrame, bool showLink); + TextMessagePart(MessageViewer::ObjectTreeParser *otp, KMime::Content *node, bool drawFrame, bool showLink, bool decryptMessage); virtual ~TextMessagePart(); QString text() const Q_DECL_OVERRIDE; void html(bool decorate) Q_DECL_OVERRIDE; KMMsgSignatureState signatureState() const; KMMsgEncryptionState encryptionState() const; + bool decryptMessage() const; private: + void parseContent(); KMime::Content *mNode; KMMsgSignatureState mSignatureState; KMMsgEncryptionState mEncryptionState; QVector mBlocks; bool mDrawFrame; bool mShowLink; + bool mDecryptMessage; }; class HtmlMessagePart : public MessagePart { public: typedef QSharedPointer Ptr; HtmlMessagePart(MessageViewer::ObjectTreeParser* otp, KMime::Content* node, MessageViewer::ObjectTreeSourceIf* source); virtual ~HtmlMessagePart(); QString text() const Q_DECL_OVERRIDE; void html(bool decorate) Q_DECL_OVERRIDE; /* only a function that should be removed if the refactiring is over */ void fix(); private: KMime::Content* mNode; ObjectTreeSourceIf *mSource; QString mBodyHTML; QByteArray mCharset; }; class AlternativeMessagePart : public MessagePart { public: typedef QSharedPointer Ptr; AlternativeMessagePart (MessageViewer::ObjectTreeParser* otp, KMime::Content* textNode, KMime::Content* htmlNode); virtual ~AlternativeMessagePart(); QString text() const Q_DECL_OVERRIDE; void html(bool decorate) Q_DECL_OVERRIDE; void setViewHtml(bool html); bool viewHtml(); private: KMime::Content* mTextNode; KMime::Content* mHTMLNode; MimeMessagePart::Ptr mTextPart; MimeMessagePart::Ptr mHTMLPart; bool mViewHtml; }; class CertMessagePart : public MessagePart { public: typedef QSharedPointer Ptr; CertMessagePart(MessageViewer::ObjectTreeParser* otp, KMime::Content* node, const Kleo::CryptoBackend::Protocol *cryptoProto, bool autoImport); virtual ~CertMessagePart(); QString text() const Q_DECL_OVERRIDE; void html(bool decorate) Q_DECL_OVERRIDE; private: KMime::Content* mNode; bool mAutoImport; GpgME::ImportResult mImportResult; const Kleo::CryptoBackend::Protocol *mCryptoProto; }; class EncapsulatedRfc822MessagePart : public MessagePart { public: typedef QSharedPointer Ptr; EncapsulatedRfc822MessagePart(MessageViewer::ObjectTreeParser *otp, KMime::Content *node, const KMime::Message::Ptr &message); virtual ~EncapsulatedRfc822MessagePart(); QString text() const Q_DECL_OVERRIDE; void html(bool decorate) Q_DECL_OVERRIDE; private: const KMime::Message::Ptr mMessage; KMime::Content *mNode; }; class CryptoMessagePart : public MessagePart { public: typedef QSharedPointer Ptr; CryptoMessagePart(ObjectTreeParser *otp, const QString &text, const Kleo::CryptoBackend::Protocol *cryptoProto, const QString &fromAddress, KMime::Content *node); virtual ~CryptoMessagePart(); void startDecryption(const QByteArray &text, const QTextCodec *aCodec); void startDecryption(KMime::Content *data = 0); void startVerification(const QByteArray &text, const QTextCodec *aCodec); void startVerificationDetached(const QByteArray &text, KMime::Content *textNode, const QByteArray &signature); void html(bool decorate) Q_DECL_OVERRIDE; bool mPassphraseError; QByteArray mDecryptedData; std::vector mSignatures; private: /** Writes out the block that we use when the node is encrypted, but we're deferring decryption for later. */ void writeDeferredDecryptionBlock() const; protected: const Kleo::CryptoBackend::Protocol *mCryptoProto; QString mFromAddress; KMime::Content *mNode; bool mDecryptMessage; QByteArray mVerifiedText; }; } #endif //_MESSAGEVIEWER_MESSAGEPART_H_ diff --git a/messageviewer/src/viewer/objecttreeparser.cpp b/messageviewer/src/viewer/objecttreeparser.cpp index b1df556c..2a4773d1 100644 --- a/messageviewer/src/viewer/objecttreeparser.cpp +++ b/messageviewer/src/viewer/objecttreeparser.cpp @@ -1,2739 +1,2610 @@ /* objecttreeparser.cpp This file is part of KMail, the KDE mail client. Copyright (c) 2003 Marc Mutz Copyright (C) 2002-2004 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net Copyright (c) 2009 Andras Mantia Copyright (c) 2015 Sandro Knauß 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. */ // MessageViewer includes #include "objecttreeparser.h" #include "memento/verifydetachedbodypartmemento.h" #include "memento/verifyopaquebodypartmemento.h" #include "memento/cryptobodypartmemento.h" #include "memento/decryptverifybodypartmemento.h" #include "messagepart.h" #include "objecttreesourceif.h" #include "viewer/viewer_p.h" #include "partmetadata.h" #include "attachmentstrategy.h" #include "interfaces/htmlwriter.h" #include "widgets/htmlstatusbar.h" #include "csshelper.h" #include "viewer/bodypartformatterfactory.h" #include "viewer/partnodebodypart.h" #include "interfaces/bodypartformatter.h" #include "settings/messageviewersettings.h" #include "messageviewer/messageviewerutil.h" #include "job/kleojobexecutor.h" #include "messageviewer/nodehelper.h" #include "utils/iconnamecache.h" #include "viewer/htmlquotecolorer.h" #include "messageviewer_debug.h" #include "converthtmltoplaintext.h" // KDEPIM includes #include #include #include #include #include #include #include #include #include #include "cryptohelper.h" // KDEPIMLIBS includes #include #include #include #include #include #include #include // KDE includes #include #include #include #include #include #include // Qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include // other includes #include #include #include #include #include #include #include using namespace MessageViewer; using namespace MessageCore; // A small class that eases temporary CryptPlugWrapper changes: class ObjectTreeParser::CryptoProtocolSaver { ObjectTreeParser *otp; const Kleo::CryptoBackend::Protocol *protocol; public: CryptoProtocolSaver(ObjectTreeParser *_otp, const Kleo::CryptoBackend::Protocol *_w) : otp(_otp), protocol(_otp ? _otp->cryptoProtocol() : 0) { if (otp) { otp->setCryptoProtocol(_w); } } ~CryptoProtocolSaver() { if (otp) { otp->setCryptoProtocol(protocol); } } }; ObjectTreeParser::ObjectTreeParser(const ObjectTreeParser *topLevelParser, bool showOnlyOneMimePart, const AttachmentStrategy *strategy) : mSource(topLevelParser->mSource), mNodeHelper(topLevelParser->mNodeHelper), mHtmlWriter(topLevelParser->mHtmlWriter), mTopLevelContent(topLevelParser->mTopLevelContent), mCryptoProtocol(topLevelParser->mCryptoProtocol), mShowOnlyOneMimePart(showOnlyOneMimePart), mHasPendingAsyncJobs(false), mAllowAsync(topLevelParser->mAllowAsync), mAttachmentStrategy(strategy), mPrinting(false) { init(); } ObjectTreeParser::ObjectTreeParser(ObjectTreeSourceIf *source, MessageViewer::NodeHelper *nodeHelper, const Kleo::CryptoBackend::Protocol *protocol, bool showOnlyOneMimePart, const AttachmentStrategy *strategy) : mSource(source), mNodeHelper(nodeHelper), mHtmlWriter(0), mTopLevelContent(0), mCryptoProtocol(protocol), mShowOnlyOneMimePart(showOnlyOneMimePart), mHasPendingAsyncJobs(false), mAllowAsync(false), mAttachmentStrategy(strategy), mPrinting(false) { init(); } void ObjectTreeParser::init() { assert(mSource); if (!attachmentStrategy()) { mAttachmentStrategy = mSource->attachmentStrategy(); } if (!mNodeHelper) { mNodeHelper = new NodeHelper(); mDeleteNodeHelper = true; } else { mDeleteNodeHelper = false; } } ObjectTreeParser::ObjectTreeParser(const ObjectTreeParser &other) : mSource(other.mSource), mNodeHelper(other.nodeHelper()), //TODO(Andras) hm, review what happens if mDeleteNodeHelper was true in the source mHtmlWriter(other.mHtmlWriter), mTopLevelContent(other.mTopLevelContent), mCryptoProtocol(other.cryptoProtocol()), mShowOnlyOneMimePart(other.showOnlyOneMimePart()), mHasPendingAsyncJobs(other.hasPendingAsyncJobs()), mAllowAsync(other.allowAsync()), mAttachmentStrategy(other.attachmentStrategy()), mDeleteNodeHelper(false) // TODO see above { } ObjectTreeParser::~ObjectTreeParser() { if (mDeleteNodeHelper) { delete mNodeHelper; mNodeHelper = 0; } } void ObjectTreeParser::setAllowAsync(bool allow) { assert(!mHasPendingAsyncJobs); mAllowAsync = allow; } bool ObjectTreeParser::allowAsync() const { return mAllowAsync; } bool ObjectTreeParser::hasPendingAsyncJobs() const { return mHasPendingAsyncJobs; } QString ObjectTreeParser::plainTextContent() const { return mPlainTextContent; } QString ObjectTreeParser::htmlContent() const { return mHtmlContent; } void ObjectTreeParser::copyContentFrom(const ObjectTreeParser *other) { mPlainTextContent += other->plainTextContent(); mHtmlContent += other->htmlContent(); if (!other->plainTextContentCharset().isEmpty()) { mPlainTextContentCharset = other->plainTextContentCharset(); } if (!other->htmlContentCharset().isEmpty()) { mHtmlContentCharset = other->htmlContentCharset(); } } void ObjectTreeParser::createAndParseTempNode(KMime::Content *parentNode, const char *content, const char *cntDesc) { // qCDebug(MESSAGEVIEWER_LOG) << "CONTENT: " << QByteArray( content ).left( 100 ) << " CNTDESC: " << cntDesc; KMime::Content *newNode = new KMime::Content(); newNode->setContent(KMime::CRLFtoLF(content)); newNode->parse(); /* qCDebug(MESSAGEVIEWER_LOG) << "MEDIATYPE: " << newNode->contentType()->mediaType() << newNode->contentType()->mimeType() ; qCDebug(MESSAGEVIEWER_LOG) << "DECODEDCONTENT: " << newNode->decodedContent().left(400); qCDebug(MESSAGEVIEWER_LOG) << "ENCODEDCONTENT: " << newNode->encodedContent().left(400); qCDebug(MESSAGEVIEWER_LOG) << "BODY: " << newNode->body().left(400); */ if (!newNode->head().isEmpty()) { newNode->contentDescription()->from7BitString(cntDesc); } mNodeHelper->attachExtraContent(parentNode, newNode); ObjectTreeParser otp(this); otp.parseObjectTreeInternal(newNode); copyContentFrom(&otp); } //----------------------------------------------------------------------------- void ObjectTreeParser::parseObjectTree(KMime::Content *node) { mTopLevelContent = node; parseObjectTreeInternal(node); } void ObjectTreeParser::setPrinting(bool printing) { mPrinting = printing; } void ObjectTreeParser::parseObjectTreeInternal(KMime::Content *node) { if (!node) { return; } // reset pending async jobs state (we'll rediscover pending jobs as we go) mHasPendingAsyncJobs = false; // reset "processed" flags for... if (showOnlyOneMimePart()) { // ... this node and all descendants mNodeHelper->setNodeUnprocessed(node, false); if (MessageCore::NodeHelper::firstChild(node)) { mNodeHelper->setNodeUnprocessed(node, true); } } else if (!node->parent()) { // ...this node and all it's siblings and descendants mNodeHelper->setNodeUnprocessed(node, true); } // Make sure the whole content is relative, so that nothing is painted over the header // if a malicious message uses absolute positioning. // Also force word wrapping, which is useful for printing, see https://issues.kolab.org/issue3992. bool isRoot = node->isTopLevel(); if (isRoot && htmlWriter()) { htmlWriter()->queue(QStringLiteral("
\n")); } for (; node; node = MessageCore::NodeHelper::nextSibling(node)) { if (mNodeHelper->nodeProcessed(node)) { continue; } ProcessResult processResult(mNodeHelper); QByteArray mediaType("text"); QByteArray subType("plain"); if (node->contentType(false) && !node->contentType()->mediaType().isEmpty() && !node->contentType()->subType().isEmpty()) { mediaType = node->contentType()->mediaType(); subType = node->contentType()->subType(); } // First, try if an external plugin can handle this MIME part if (const Interface::BodyPartFormatter * formatter = BodyPartFormatterFactory::instance()->createFor(mediaType, subType)) { PartNodeBodyPart part(this, &processResult, mTopLevelContent, node, mNodeHelper, codecFor(node)); // Set the default display strategy for this body part relying on the // identity of Interface::BodyPart::Display and AttachmentStrategy::Display part.setDefaultDisplay((Interface::BodyPart::Display) attachmentStrategy()->defaultDisplay(node)); mNodeHelper->setNodeDisplayedEmbedded(node, true); AttachmentMarkBlock block(htmlWriter(), node); QObject *asyncResultObserver = allowAsync() ? mSource->sourceObject() : 0; const Interface::BodyPartFormatter::Result result = formatter->format(&part, htmlWriter(), asyncResultObserver); switch (result) { case Interface::BodyPartFormatter::AsIcon: processResult.setNeverDisplayInline(true); mNodeHelper->setNodeDisplayedEmbedded(node, false); // fall through: case Interface::BodyPartFormatter::Failed: { const auto mp = defaultHandling(node, processResult); if (mp) { mp->html(false); } break; } case Interface::BodyPartFormatter::Ok: case Interface::BodyPartFormatter::NeedContent: // FIXME: incomplete content handling ; } // No external plugin can handle the MIME part, handle it internally } else { qCCritical(MESSAGEVIEWER_LOG) << "THIS SHOULD NO LONGER HAPPEN:" << mediaType << '/' << subType; AttachmentMarkBlock block(htmlWriter(), node); const auto mp = defaultHandling(node, processResult); if (mp) { mp->html(false); } } mNodeHelper->setNodeProcessed(node, false); // adjust signed/encrypted flags if inline PGP was found processResult.adjustCryptoStatesOfNode(node); if (showOnlyOneMimePart()) { break; } } if (isRoot && htmlWriter()) { htmlWriter()->queue(QStringLiteral("
\n")); } } MessagePart::Ptr ObjectTreeParser::defaultHandling(KMime::Content *node, ProcessResult &result) { // ### (mmutz) default handling should go into the respective // ### bodypartformatters. if (!htmlWriter()) { qCWarning(MESSAGEVIEWER_LOG) << "no htmlWriter()"; return MessagePart::Ptr(); } // always show images in multipart/related when showing in html, not with an additional icon if (result.isImage() && node->parent() && node->parent()->contentType()->subType() == "related" && mSource->htmlMail() && !showOnlyOneMimePart()) { QString fileName = mNodeHelper->writeNodeToTempFile(node); QString href = QLatin1String("file:///") + fileName; QByteArray cid = node->contentID()->identifier(); htmlWriter()->embedPart(cid, href); nodeHelper()->setNodeDisplayedEmbedded(node, true); return MessagePart::Ptr(); } MessagePart::Ptr mp; if (node->contentType()->mimeType() == QByteArray("application/octet-stream") && (node->contentType()->name().endsWith(QLatin1String("p7m")) || node->contentType()->name().endsWith(QLatin1String("p7s")) || node->contentType()->name().endsWith(QLatin1String("p7c")) ) && (mp = processApplicationPkcs7MimeSubtype(node, result))) { return mp; } const AttachmentStrategy *const as = attachmentStrategy(); if (as && as->defaultDisplay(node) == AttachmentStrategy::None && !showOnlyOneMimePart() && node->parent() /* message is not an attachment */) { mNodeHelper->setNodeDisplayedHidden(node, true); return MessagePart::Ptr(); } bool asIcon = true; if (!result.neverDisplayInline()) if (as) { asIcon = as->defaultDisplay(node) == AttachmentStrategy::AsIcon; } // Show it inline if showOnlyOneMimePart(), which means the user clicked the image // in the message structure viewer manually, and therefore wants to see the full image if (result.isImage() && showOnlyOneMimePart() && !result.neverDisplayInline()) { asIcon = false; } // neither image nor text -> show as icon if (!result.isImage() && !node->contentType()->isText()) { asIcon = true; } /*FIXME(Andras) port it // if the image is not complete do not try to show it inline if ( result.isImage() && !node->msgPart().isComplete() ) asIcon = true; */ if (asIcon) { if (!(as && as->defaultDisplay(node) == AttachmentStrategy::None) || showOnlyOneMimePart()) { // Write the node as icon only writePartIcon(node); } else { mNodeHelper->setNodeDisplayedHidden(node, true); } } else if (result.isImage()) { // Embed the image mNodeHelper->setNodeDisplayedEmbedded(node, true); writePartIcon(node, true); } else { mNodeHelper->setNodeDisplayedEmbedded(node, true); - const auto mp = TextMessagePart::Ptr(new TextMessagePart(this, node, false, false)); + const auto mp = TextMessagePart::Ptr(new TextMessagePart(this, node, false, false, mSource->decryptMessage())); result.setInlineSignatureState(mp->signatureState()); result.setInlineEncryptionState(mp->encryptionState()); return mp; } return MessagePart::Ptr(); } KMMsgSignatureState ProcessResult::inlineSignatureState() const { return mInlineSignatureState; } void ProcessResult::setInlineSignatureState(KMMsgSignatureState state) { mInlineSignatureState = state; } KMMsgEncryptionState ProcessResult::inlineEncryptionState() const { return mInlineEncryptionState; } void ProcessResult::setInlineEncryptionState(KMMsgEncryptionState state) { mInlineEncryptionState = state; } bool ProcessResult::neverDisplayInline() const { return mNeverDisplayInline; } void ProcessResult::setNeverDisplayInline(bool display) { mNeverDisplayInline = display; } bool ProcessResult::isImage() const { return mIsImage; } void ProcessResult::setIsImage(bool image) { mIsImage = image; } void ProcessResult::adjustCryptoStatesOfNode(KMime::Content *node) const { if ((inlineSignatureState() != KMMsgNotSigned) || (inlineEncryptionState() != KMMsgNotEncrypted)) { mNodeHelper->setSignatureState(node, inlineSignatureState()); mNodeHelper->setEncryptionState(node, inlineEncryptionState()); } } ////////////////// ////////////////// ////////////////// 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; } } void ObjectTreeParser::writeCertificateImportResult(const GpgME::ImportResult &res) { if (res.error()) { htmlWriter()->queue(i18n("Sorry, certificate could not be imported.
" "Reason: %1", QString::fromLocal8Bit(res.error().asString()))); return; } const int nImp = res.numImported(); const int nUnc = res.numUnchanged(); const int nSKImp = res.numSecretKeysImported(); const int nSKUnc = res.numSecretKeysUnchanged(); if (!nImp && !nSKImp && !nUnc && !nSKUnc) { htmlWriter()->queue(i18n("Sorry, no certificates were found in this message.")); return; } QString comment = QLatin1String("") + i18n("Certificate import status:") + QLatin1String("
 
"); if (nImp) comment += i18np("1 new certificate was imported.", "%1 new certificates were imported.", nImp) + QLatin1String("
"); if (nUnc) comment += i18np("1 certificate was unchanged.", "%1 certificates were unchanged.", nUnc) + QLatin1String("
"); if (nSKImp) comment += i18np("1 new secret key was imported.", "%1 new secret keys were imported.", nSKImp) + QLatin1String("
"); if (nSKUnc) comment += i18np("1 secret key was unchanged.", "%1 secret keys were unchanged.", nSKUnc) + QLatin1String("
"); comment += QLatin1String(" 
"); htmlWriter()->queue(comment); if (!nImp && !nSKImp) { htmlWriter()->queue(QStringLiteral("
")); return; } const std::vector imports = res.imports(); if (imports.empty()) { htmlWriter()->queue(i18n("Sorry, no details on certificate import available.") + QLatin1String("
")); return; } htmlWriter()->queue(QLatin1String("") + i18n("Certificate import details:") + QLatin1String("
")); std::vector::const_iterator end(imports.end()); for (std::vector::const_iterator it = imports.begin(); it != end; ++it) { if ((*it).error()) { htmlWriter()->queue(i18nc("Certificate import failed.", "Failed: %1 (%2)", QLatin1String((*it).fingerprint()), QString::fromLocal8Bit((*it).error().asString()))); } else if ((*it).status() & ~GpgME::Import::ContainedSecretKey) { if ((*it).status() & GpgME::Import::ContainedSecretKey) { htmlWriter()->queue(i18n("New or changed: %1 (secret key available)", QLatin1String((*it).fingerprint()))); } else { htmlWriter()->queue(i18n("New or changed: %1", QLatin1String((*it).fingerprint()))); } } htmlWriter()->queue(QStringLiteral("
")); } htmlWriter()->queue(QStringLiteral("
")); } bool ObjectTreeParser::okDecryptMIME(KMime::Content &data, QByteArray &decryptedData, bool &signatureFound, std::vector &signatures, bool showWarning, bool &passphraseError, bool &actuallyEncrypted, bool &decryptionStarted, PartMetaData &partMetaData) { passphraseError = false; decryptionStarted = false; partMetaData.errorText.clear(); partMetaData.auditLogError = GpgME::Error(); partMetaData.auditLog.clear(); bool bDecryptionOk = false; enum { NO_PLUGIN, NOT_INITIALIZED, CANT_DECRYPT } cryptPlugError = NO_PLUGIN; const Kleo::CryptoBackend::Protocol *cryptProto = cryptoProtocol(); QString cryptPlugLibName; if (cryptProto) { cryptPlugLibName = cryptProto->name(); } assert(mSource->decryptMessage()); const QString errorMsg = i18n("Could not decrypt the data."); if (cryptProto) { QByteArray ciphertext = data.decodedContent(); #ifdef MARCS_DEBUG QString cipherStr = QString::fromLatin1(ciphertext); bool cipherIsBinary = (!cipherStr.contains(QStringLiteral("BEGIN ENCRYPTED MESSAGE"), Qt::CaseInsensitive)) && (!cipherStr.contains(QStringLiteral("BEGIN PGP ENCRYPTED MESSAGE"), Qt::CaseInsensitive)) && (!cipherStr.contains(QStringLiteral("BEGIN PGP MESSAGE"), Qt::CaseInsensitive)); dumpToFile("dat_04_reader.encrypted", ciphertext.data(), ciphertext.size()); QString deb; deb = QLatin1String("\n\nE N C R Y P T E D D A T A = "); if (cipherIsBinary) { deb += QLatin1String("[binary data]"); } else { deb += QLatin1String("\""); deb += cipherStr; deb += QLatin1String("\""); } deb += "\n\n"; qCDebug(MESSAGEVIEWER_LOG) << deb; #endif qCDebug(MESSAGEVIEWER_LOG) << "going to call CRYPTPLUG" << cryptPlugLibName; // Check whether the memento contains a result from last time: const DecryptVerifyBodyPartMemento *m = dynamic_cast(mNodeHelper->bodyPartMemento(&data, "decryptverify")); if (!m) { Kleo::DecryptVerifyJob *job = cryptProto->decryptVerifyJob(); if (!job) { cryptPlugError = CANT_DECRYPT; cryptProto = 0; } else { DecryptVerifyBodyPartMemento *newM = new DecryptVerifyBodyPartMemento(job, ciphertext); if (allowAsync()) { QObject::connect(newM, &CryptoBodyPartMemento::update, nodeHelper(), &NodeHelper::update); QObject::connect(newM, SIGNAL(update(MessageViewer::Viewer::UpdateMode)), mSource->sourceObject(), SLOT(update(MessageViewer::Viewer::UpdateMode))); if (newM->start()) { decryptionStarted = true; mHasPendingAsyncJobs = true; } else { m = newM; } } else { newM->exec(); m = newM; } mNodeHelper->setBodyPartMemento(&data, "decryptverify", newM); } } else if (m->isRunning()) { decryptionStarted = true; mHasPendingAsyncJobs = true; m = 0; } if (m) { const QByteArray &plainText = m->plainText(); const GpgME::DecryptionResult &decryptResult = m->decryptResult(); const GpgME::VerificationResult &verifyResult = m->verifyResult(); std::stringstream ss; ss << decryptResult << '\n' << verifyResult; qCDebug(MESSAGEVIEWER_LOG) << ss.str().c_str(); signatureFound = verifyResult.signatures().size() > 0; signatures = verifyResult.signatures(); bDecryptionOk = !decryptResult.error(); passphraseError = decryptResult.error().isCanceled() || decryptResult.error().code() == GPG_ERR_NO_SECKEY; actuallyEncrypted = decryptResult.error().code() != GPG_ERR_NO_DATA; partMetaData.errorText = QString::fromLocal8Bit(decryptResult.error().asString()); partMetaData.auditLogError = m->auditLogError(); partMetaData.auditLog = m->auditLogAsHtml(); partMetaData.isEncrypted = actuallyEncrypted; if (actuallyEncrypted && decryptResult.numRecipients() > 0) { partMetaData.keyId = decryptResult.recipient(0).keyID(); } qCDebug(MESSAGEVIEWER_LOG) << "ObjectTreeParser::decryptMIME: returned from CRYPTPLUG"; if (bDecryptionOk) { decryptedData = plainText; } else if (htmlWriter() && showWarning) { decryptedData = "
" + errorMsg.toUtf8() + "
"; if (!passphraseError) partMetaData.errorText = i18n("Crypto plug-in \"%1\" could not decrypt the data.", cryptPlugLibName) + QLatin1String("
") + i18n("Error: %1", partMetaData.errorText); } } } if (!cryptProto) { decryptedData = "
" + errorMsg.toUtf8() + "
"; switch (cryptPlugError) { case NOT_INITIALIZED: partMetaData.errorText = i18n("Crypto plug-in \"%1\" is not initialized.", cryptPlugLibName); break; case CANT_DECRYPT: partMetaData.errorText = i18n("Crypto plug-in \"%1\" cannot decrypt messages.", cryptPlugLibName); break; case NO_PLUGIN: partMetaData.errorText = i18n("No appropriate crypto plug-in was found."); break; } } dumpToFile("dat_05_reader.decrypted", decryptedData.data(), decryptedData.size()); return bDecryptionOk; } MessagePart::Ptr ObjectTreeParser::processTextHtmlSubtype(KMime::Content *curNode, ProcessResult &) { HtmlMessagePart::Ptr mp(new HtmlMessagePart(this, curNode, mSource)); if (curNode->topLevel()->textContent() == curNode || attachmentStrategy()->defaultDisplay(curNode) == AttachmentStrategy::Inline || showOnlyOneMimePart()) { } else { // we need the copy of htmlcontent and charset in anycase for the current design of otp. //Should be not neeed if otp don't hold any status anymore mp->fix(); } return mp; } bool ObjectTreeParser::isMailmanMessage(KMime::Content *curNode) { if (!curNode || curNode->head().isEmpty()) { return false; } if (curNode->hasHeader("X-Mailman-Version")) { return true; } if (curNode->hasHeader("X-Mailer")) { KMime::Headers::Base *header = curNode->headerByType("X-Mailer"); if (header->asUnicodeString().contains(QStringLiteral("MAILMAN"), Qt::CaseInsensitive)) { return true; } } return false; } bool ObjectTreeParser::processMailmanMessage(KMime::Content *curNode) { const QString str = QString::fromLatin1(curNode->decodedContent()); //### const QLatin1String delim1("--__--__--\n\nMessage:"); const QLatin1String delim2("--__--__--\r\n\r\nMessage:"); const QLatin1String delimZ2("--__--__--\n\n_____________"); const QLatin1String delimZ1("--__--__--\r\n\r\n_____________"); QString partStr, digestHeaderStr; int thisDelim = str.indexOf(delim1, Qt::CaseInsensitive); if (thisDelim == -1) { thisDelim = str.indexOf(delim2, Qt::CaseInsensitive); } if (thisDelim == -1) { return false; } int nextDelim = str.indexOf(delim1, thisDelim + 1, Qt::CaseInsensitive); if (-1 == nextDelim) { nextDelim = str.indexOf(delim2, thisDelim + 1, Qt::CaseInsensitive); } if (-1 == nextDelim) { nextDelim = str.indexOf(delimZ1, thisDelim + 1, Qt::CaseInsensitive); } if (-1 == nextDelim) { nextDelim = str.indexOf(delimZ2, thisDelim + 1, Qt::CaseInsensitive); } if (nextDelim < 0) { return false; } //if ( curNode->mRoot ) // curNode = curNode->mRoot; // at least one message found: build a mime tree digestHeaderStr = QStringLiteral("Content-Type: text/plain\nContent-Description: digest header\n\n"); digestHeaderStr += str.midRef(0, thisDelim); createAndParseTempNode(mTopLevelContent, digestHeaderStr.toLatin1(), "Digest Header"); //mReader->queueHtml("


"); // temporarily change curent node's Content-Type // to get our embedded RfC822 messages properly inserted curNode->contentType()->setMimeType("multipart/digest"); while (-1 < nextDelim) { int thisEoL = str.indexOf(QLatin1String("\nMessage:"), thisDelim, Qt::CaseInsensitive); if (-1 < thisEoL) { thisDelim = thisEoL + 1; } else { thisEoL = str.indexOf(QLatin1String("\n_____________"), thisDelim, Qt::CaseInsensitive); if (-1 < thisEoL) { thisDelim = thisEoL + 1; } } thisEoL = str.indexOf(QLatin1Char('\n'), thisDelim); if (-1 < thisEoL) { thisDelim = thisEoL + 1; } else { thisDelim = thisDelim + 1; } //while( thisDelim < cstr.size() && '\n' == cstr[thisDelim] ) // ++thisDelim; partStr = QStringLiteral("Content-Type: message/rfc822\nContent-Description: embedded message\n\n"); partStr += QLatin1String("Content-Type: text/plain\n"); partStr += str.midRef(thisDelim, nextDelim - thisDelim); QString subject = QStringLiteral("embedded message"); QString subSearch = QStringLiteral("\nSubject:"); int subPos = partStr.indexOf(subSearch, 0, Qt::CaseInsensitive); if (-1 < subPos) { subject = partStr.mid(subPos + subSearch.length()); thisEoL = subject.indexOf(QLatin1Char('\n')); if (-1 < thisEoL) { subject.truncate(thisEoL); } } qCDebug(MESSAGEVIEWER_LOG) << " embedded message found: \"" << subject; createAndParseTempNode(mTopLevelContent, partStr.toLatin1(), subject.toLatin1()); //mReader->queueHtml("


"); thisDelim = nextDelim + 1; nextDelim = str.indexOf(delim1, thisDelim, Qt::CaseInsensitive); if (-1 == nextDelim) { nextDelim = str.indexOf(delim2, thisDelim, Qt::CaseInsensitive); } if (-1 == nextDelim) { nextDelim = str.indexOf(delimZ1, thisDelim, Qt::CaseInsensitive); } if (-1 == nextDelim) { nextDelim = str.indexOf(delimZ2, thisDelim, Qt::CaseInsensitive); } } // reset curent node's Content-Type curNode->contentType()->setMimeType("text/plain"); int thisEoL = str.indexOf(QLatin1String("_____________"), thisDelim); if (-1 < thisEoL) { thisDelim = thisEoL; thisEoL = str.indexOf(QLatin1Char('\n'), thisDelim); if (-1 < thisEoL) { thisDelim = thisEoL + 1; } } else { thisDelim = thisDelim + 1; } partStr = QStringLiteral("Content-Type: text/plain\nContent-Description: digest footer\n\n"); partStr += str.midRef(thisDelim); createAndParseTempNode(mTopLevelContent, partStr.toLatin1(), "Digest Footer"); return true; } void ObjectTreeParser::extractNodeInfos(KMime::Content *curNode, bool isFirstTextPart) { if (isFirstTextPart) { mPlainTextContent += curNode->decodedText(); mPlainTextContentCharset += NodeHelper::charset(curNode); } } MessagePart::Ptr ObjectTreeParser::processTextPlainSubtype(KMime::Content *curNode, ProcessResult &result) { const bool isFirstTextPart = (curNode->topLevel()->textContent() == curNode); if (!isFirstTextPart && attachmentStrategy()->defaultDisplay(curNode) != AttachmentStrategy::Inline && !showOnlyOneMimePart()) { return MessagePart::Ptr(); } extractNodeInfos(curNode, isFirstTextPart); QString label = NodeHelper::fileName(curNode); const bool bDrawFrame = !isFirstTextPart && !showOnlyOneMimePart() && !label.isEmpty(); const QString fileName = mNodeHelper->writeNodeToTempFile(curNode); // process old style not-multipart Mailman messages to // enable verification of the embedded messages' signatures //if (!isMailmanMessage(curNode) || // !processMailmanMessage(curNode)) { - TextMessagePart::Ptr mp(new TextMessagePart(this, curNode, bDrawFrame, !fileName.isEmpty())); + TextMessagePart::Ptr mp(new TextMessagePart(this, curNode, bDrawFrame, !fileName.isEmpty(), mSource->decryptMessage())); result.setInlineSignatureState(mp->signatureState()); result.setInlineEncryptionState(mp->encryptionState()); if (isFirstTextPart) { mPlainTextContent = mp->text(); } mNodeHelper->setNodeDisplayedEmbedded(curNode, true); //} return mp; } void ObjectTreeParser::standardChildHandling(KMime::Content *child) { if (!child) { return; } MimeMessagePart mp(this, child, false); mp.html(false); } MessagePart::Ptr ObjectTreeParser::processMultiPartMixedSubtype(KMime::Content *node, ProcessResult &) { KMime::Content *child = MessageCore::NodeHelper::firstChild(node); if (!child) { return MessagePart::Ptr(); } // normal treatment of the parts in the mp/mixed container MimeMessagePart::Ptr mp(new MimeMessagePart(this, child, false)); return mp; } MessagePart::Ptr ObjectTreeParser::processMultiPartAlternativeSubtype(KMime::Content *node, ProcessResult &) { KMime::Content *child = MessageCore::NodeHelper::firstChild(node); if (!child) { return MessagePart::Ptr(); } KMime::Content *dataHtml = findType(child, "text/html", false, true); KMime::Content *dataPlain = findType(child, "text/plain", false, true); 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 = findType(child, "multipart/related", false, true); // 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 && mSource->htmlMail()) { dataHtml = findType(child, "multipart/mixed", false, true); } } if (dataPlain || dataHtml) { AlternativeMessagePart::Ptr mp(new AlternativeMessagePart(this, dataPlain, dataHtml)); if ((mSource->htmlMail() && dataHtml) || (dataHtml && dataPlain && dataPlain->body().isEmpty())) { if (dataPlain) { mNodeHelper->setNodeProcessed(dataPlain, false); } mSource->setHtmlMode(Util::MultipartHtml); mp->setViewHtml(true); } if (!mSource->htmlMail() && dataPlain) { mNodeHelper->setNodeProcessed(dataHtml, false); mSource->setHtmlMode(Util::MultipartPlain); mp->setViewHtml(false); } return mp; } MimeMessagePart::Ptr mp(new MimeMessagePart(this, child, false)); return mp; } MessagePart::Ptr ObjectTreeParser::processMultiPartSignedSubtype(KMime::Content *node, ProcessResult &) { KMime::Content *signedData = MessageCore::NodeHelper::firstChild(node); assert(signedData); if (node->contents().size() != 2) { qCDebug(MESSAGEVIEWER_LOG) << "mulitpart/signed must have exactly two child parts!" << endl << "processing as multipart/mixed"; return MessagePart::Ptr(new MimeMessagePart(this, signedData, false)); } KMime::Content *signature = node->contents().at(1); assert(signature); QString protocolContentType = node->contentType()->parameter(QStringLiteral("protocol")).toLower(); const QString signatureContentType = QLatin1String(signature->contentType()->mimeType().toLower()); if (protocolContentType.isEmpty()) { qCWarning(MESSAGEVIEWER_LOG) << "Message doesn't set the protocol for the multipart/signed content-type, " "using content-type of the signature:" << signatureContentType; protocolContentType = signatureContentType; } const Kleo::CryptoBackend::Protocol *protocol = 0; if (protocolContentType == QLatin1String("application/pkcs7-signature") || protocolContentType == QLatin1String("application/x-pkcs7-signature")) { protocol = Kleo::CryptoBackendFactory::instance()->smime(); } else if (protocolContentType == QLatin1String("application/pgp-signature") || protocolContentType == QLatin1String("application/x-pgp-signature")) { protocol = Kleo::CryptoBackendFactory::instance()->openpgp(); } if (!protocol) { return MessagePart::Ptr(new MimeMessagePart(this, signedData, false)); } mNodeHelper->setNodeProcessed(signature, true); CryptoProtocolSaver saver(this, protocol); mNodeHelper->setSignatureState(node, KMMsgFullySigned); const QByteArray cleartext = KMime::LFtoCRLF(signedData->encodedContent()); const QTextCodec *aCodec(codecFor(signedData)); CryptoMessagePart::Ptr mp(new CryptoMessagePart(this, aCodec->toUnicode(cleartext), cryptoProtocol(), NodeHelper::fromAsString(node), signature)); PartMetaData *messagePart(mp->partMetaData()); messagePart->isSigned = true; if (cryptoProtocol()) { mp->startVerificationDetached(cleartext, signedData, signature->decodedContent()); } else { messagePart->auditLogError = GpgME::Error(GPG_ERR_NOT_IMPLEMENTED); } return mp; } MessagePart::Ptr ObjectTreeParser::processMultiPartEncryptedSubtype(KMime::Content *node, ProcessResult &result) { KMime::Content *child = MessageCore::NodeHelper::firstChild(node); if (!child) { Q_ASSERT(false); return MessagePart::Ptr(); } const Kleo::CryptoBackend::Protocol *useThisCryptProto = Q_NULLPTR; /* ATTENTION: This code is to be replaced by the new 'auto-detect' feature. -------------------------------------- */ KMime::Content *data = findType(child, "application/octet-stream", false, true); if (data) { useThisCryptProto = Kleo::CryptoBackendFactory::instance()->openpgp(); } if (!data) { data = findType(child, "application/pkcs7-mime", false, true); if (data) { useThisCryptProto = Kleo::CryptoBackendFactory::instance()->smime(); } } /* --------------------------------------------------------------------------------------------------------------- */ if (!data) { return MessagePart::Ptr(new MimeMessagePart(this, child, false)); } CryptoProtocolSaver cpws(this, useThisCryptProto); KMime::Content *dataChild = MessageCore::NodeHelper::firstChild(data); if (dataChild) { Q_ASSERT(false); return MessagePart::Ptr(new MimeMessagePart(this, dataChild, false)); } mNodeHelper->setEncryptionState(node, KMMsgFullyEncrypted); CryptoMessagePart::Ptr mp(new CryptoMessagePart(this, data->decodedText(), Kleo::CryptoBackendFactory::instance()->openpgp(), NodeHelper::fromAsString(data), node)); PartMetaData *messagePart(mp->partMetaData()); if (!mSource->decryptMessage()) { mNodeHelper->setNodeProcessed(data, false); // Set the data node to done to prevent it from being processed } else if (KMime::Content *newNode = mNodeHelper->decryptedNodeForContent(data)) { // if we already have a decrypted node for this encrypted node, don't do the decryption again return MessagePart::Ptr(new MimeMessagePart(this, newNode, mShowOnlyOneMimePart)); } else { mp->startDecryption(data); qCDebug(MESSAGEVIEWER_LOG) << "decrypted, signed?:" << messagePart->isSigned; if (!messagePart->inProgress) { mNodeHelper->setNodeProcessed(data, false); // Set the data node to done to prevent it from being processed if (messagePart->isDecryptable && messagePart->isSigned) { // Note: Multipart/Encrypted might also be signed // without encapsulating a nicely formatted // ~~~~~~~ Multipart/Signed part. // (see RFC 3156 --> 6.2) // In this case we paint a _2nd_ frame inside the // encryption frame, but we do _not_ show a respective // encapsulated MIME part in the Mime Tree Viewer // since we do want to show the _true_ structure of the // message there - not the structure that the sender's // MUA 'should' have sent. :-D (khz, 12.09.2002) mNodeHelper->setSignatureState(node, KMMsgFullySigned); qCDebug(MESSAGEVIEWER_LOG) << "setting FULLY SIGNED to:" << node; } } } return mp; } MessagePart::Ptr ObjectTreeParser::processApplicationPkcs7MimeSubtype(KMime::Content *node, ProcessResult &result) { if (node->head().isEmpty()) { return MessagePart::Ptr(); } const Kleo::CryptoBackend::Protocol *smimeCrypto = Kleo::CryptoBackendFactory::instance()->smime(); if (!smimeCrypto) { return MessagePart::Ptr(); } const QString smimeType = node->contentType()->parameter(QStringLiteral("smime-type")).toLower(); if (smimeType == QLatin1String("certs-only")) { result.setNeverDisplayInline(true); CertMessagePart::Ptr mp(new CertMessagePart(this, node, smimeCrypto, MessageViewer::MessageViewerSettings::self()->autoImportKeys())); return mp; } CryptoProtocolSaver cpws(this, smimeCrypto); bool isSigned = (smimeType == QLatin1String("signed-data")); bool isEncrypted = (smimeType == QLatin1String("enveloped-data")); // Analyze "signTestNode" node to find/verify a signature. // If zero this verification was successfully done after // decrypting via recursion by insertAndParseNewChildNode(). KMime::Content *signTestNode = isEncrypted ? 0 : node; // We try decrypting the content // if we either *know* that it is an encrypted message part // or there is neither signed nor encrypted parameter. CryptoMessagePart::Ptr mp; if (!isSigned) { if (isEncrypted) { qCDebug(MESSAGEVIEWER_LOG) << "pkcs7 mime == S/MIME TYPE: enveloped (encrypted) data"; } else { qCDebug(MESSAGEVIEWER_LOG) << "pkcs7 mime - type unknown - enveloped (encrypted) data ?"; } mp = CryptoMessagePart::Ptr(new CryptoMessagePart(this, node->decodedText(), cryptoProtocol(), NodeHelper::fromAsString(node), node)); PartMetaData *messagePart(mp->partMetaData()); if (!mSource->decryptMessage()) { isEncrypted = true; signTestNode = 0; // PENDING(marc) to be abs. sure, we'd need to have to look at the content } else { mp->startDecryption(); if (messagePart->isDecryptable) { qCDebug(MESSAGEVIEWER_LOG) << "pkcs7 mime - encryption found - enveloped (encrypted) data !"; isEncrypted = true; mNodeHelper->setEncryptionState(node, KMMsgFullyEncrypted); if (messagePart->isSigned) { mNodeHelper->setSignatureState(node, KMMsgFullySigned); } signTestNode = 0; } else { // decryption failed, which could be because the part was encrypted but // decryption failed, or because we didn't know if it was encrypted, tried, // and failed. If the message was not actually encrypted, we continue // assuming it's signed if (mp->mPassphraseError || (smimeType.isEmpty() && messagePart->isEncrypted)) { isEncrypted = true; signTestNode = 0; } if (isEncrypted) { qCDebug(MESSAGEVIEWER_LOG) << "pkcs7 mime - ERROR: COULD NOT DECRYPT enveloped data !"; } else { qCDebug(MESSAGEVIEWER_LOG) << "pkcs7 mime - NO encryption found"; } } } if (isEncrypted) { mNodeHelper->setEncryptionState(node, KMMsgFullyEncrypted); } } // We now try signature verification if necessarry. if (signTestNode) { if (isSigned) { qCDebug(MESSAGEVIEWER_LOG) << "pkcs7 mime == S/MIME TYPE: opaque signed data"; } else { qCDebug(MESSAGEVIEWER_LOG) << "pkcs7 mime - type unknown - opaque signed data ?"; } const QTextCodec *aCodec(codecFor(signTestNode)); const QByteArray signaturetext = signTestNode->decodedContent(); mp = CryptoMessagePart::Ptr(new CryptoMessagePart(this, aCodec->toUnicode(signaturetext), cryptoProtocol(), NodeHelper::fromAsString(node), signTestNode)); PartMetaData *messagePart(mp->partMetaData()); if (cryptoProtocol()) { mp->startVerificationDetached(signaturetext, 0, QByteArray()); } else { messagePart->auditLogError = GpgME::Error(GPG_ERR_NOT_IMPLEMENTED); } if (messagePart->isSigned) { if (!isSigned) { qCDebug(MESSAGEVIEWER_LOG) << "pkcs7 mime - signature found - opaque signed data !"; isSigned = true; } mNodeHelper->setSignatureState(signTestNode, KMMsgFullySigned); if (signTestNode != node) { mNodeHelper->setSignatureState(node, KMMsgFullySigned); } } else { qCDebug(MESSAGEVIEWER_LOG) << "pkcs7 mime - NO signature found :-("; } } return mp; } -void ObjectTreeParser::writeBodyString(const QByteArray &bodyString, - const QString &fromAddress, - const QTextCodec *codec, - ProcessResult &result, - bool decorate) -{ - assert(codec); - KMMsgSignatureState inlineSignatureState = result.inlineSignatureState(); - KMMsgEncryptionState inlineEncryptionState = result.inlineEncryptionState(); - writeBodyStr(bodyString, codec, fromAddress, - inlineSignatureState, inlineEncryptionState, decorate); - result.setInlineSignatureState(inlineSignatureState); - result.setInlineEncryptionState(inlineEncryptionState); -} - void ObjectTreeParser::writePartIcon(KMime::Content *msgPart, bool inlineImage) { if (!htmlWriter() || !msgPart) { return; } const QString name = msgPart->contentType()->name(); QString label = name.isEmpty() ? NodeHelper::fileName(msgPart) : name; if (label.isEmpty()) { label = i18nc("display name for an unnamed attachment", "Unnamed"); } label = StringUtil::quoteHtmlChars(label, true); QString comment = msgPart->contentDescription()->asUnicodeString(); comment = StringUtil::quoteHtmlChars(comment, true); if (label == comment) { comment.clear(); } QString href = mNodeHelper->asHREF(msgPart, QStringLiteral("body")); if (inlineImage) { const QString fileName = mNodeHelper->writeNodeToTempFile(msgPart); // show the filename of the image below the embedded image htmlWriter()->queue(QLatin1String("
" "" "
" "" "
") + comment + QLatin1String("

")); } else { // show the filename next to the image const QString iconName = mNodeHelper->iconName(msgPart); if (iconName.right(14) == QLatin1String("mime_empty.png")) { mNodeHelper->magicSetType(msgPart); //iconName = mNodeHelper->iconName( msgPart ); } htmlWriter()->queue(QLatin1String("" "
") + comment + QLatin1String("

")); } } 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 ObjectTreeParser::sigStatusToString(const Kleo::CryptoBackend::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 == Kleo::CryptoBackendFactory::instance()->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 == Kleo::CryptoBackendFactory::instance()->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; } static QString writeSimpleSigstatHeader(const PartMetaData &block, bool printing) { QString html; html += QLatin1String(""); if (!printing) { html += QLatin1String(""); } html += QLatin1String("
"); if (block.signClass == QLatin1String("signErr")) { html += i18n("Invalid signature."); } else if (block.signClass == QLatin1String("signOkKeyBad") || block.signClass == QLatin1String("signWarn")) { html += i18n("Not enough information to check signature validity."); } else if (block.signClass == QLatin1String("signOkKeyOk")) { QString addr; if (!block.signerMailAddresses.isEmpty()) { addr = block.signerMailAddresses.first(); } QString name = addr; if (name.isEmpty()) { name = block.signer; } if (addr.isEmpty()) { html += i18n("Signature is valid."); } else { html += i18n("Signed by %2.", addr, name); } } else { // should not happen html += i18n("Unknown signature state"); } html += QLatin1String(""); html += QLatin1String(""); html += i18n("Show Details"); html += QLatin1String("
"); return html; } static QString beginVerboseSigstatHeader() { return QStringLiteral(""); html += QLatin1String("
"); } static QString makeShowAuditLogLink(const GpgME::Error &err, const QString &auditLog) { // more or less the same as // kleopatra/utils/auditlog.cpp:formatLink(), so any bug fixed here // equally applies there: if (const unsigned int code = err.code()) { if (code == GPG_ERR_NOT_IMPLEMENTED) { qCDebug(MESSAGEVIEWER_LOG) << "not showing link (not implemented)"; return QString(); } else if (code == GPG_ERR_NO_DATA) { qCDebug(MESSAGEVIEWER_LOG) << "not showing link (not available)"; return i18n("No Audit Log available"); } else { return i18n("Error Retrieving Audit Log: %1", QString::fromLocal8Bit(err.asString())); } } if (!auditLog.isEmpty()) { QUrl url; url.setScheme(QStringLiteral("kmail")); url.setPath(QStringLiteral("showAuditLog")); QUrlQuery urlquery(url); urlquery.addQueryItem(QStringLiteral("log"), auditLog); url.setQuery(urlquery); return QLatin1String("") + i18nc("The Audit Log is a detailed error log from the gnupg backend", "Show Audit Log") + QLatin1String(""); } return QString(); } static QString endVerboseSigstatHeader(const PartMetaData &pmd) { QString html; html += QLatin1String(""); html += QLatin1String(""); html += i18n("Hide Details"); html += QLatin1String("
"); html += makeShowAuditLogLink(pmd.auditLogError, pmd.auditLog); html += QLatin1String("
"); return html; } QString ObjectTreeParser::writeSigstatHeader(PartMetaData &block, const Kleo::CryptoBackend::Protocol *cryptProto, const QString &fromAddress, KMime::Content *node) { const bool isSMIME = cryptProto && (cryptProto == Kleo::CryptoBackendFactory::instance()->smime()); QString signer = block.signer; QString htmlStr, simpleHtmlStr; const QString dir = QApplication::isRightToLeft() ? QStringLiteral("rtl") : QStringLiteral("ltr"); QString cellPadding(QStringLiteral("cellpadding=\"1\"")); if (block.isEncapsulatedRfc822Message) { htmlStr += QLatin1String("" "
"); if (node) { htmlStr += QLatin1String("asHREF(node, QStringLiteral("body")) + QLatin1String("\">") + i18n("Encapsulated message") + QLatin1String(""); } else { htmlStr += i18n("Encapsulated message"); } htmlStr += QLatin1String("
"); } if (block.isEncrypted) { htmlStr += QLatin1String("" "
"); if (block.inProgress) { htmlStr += i18n("Please wait while the message is being decrypted..."); } else if (block.isDecryptable) { htmlStr += i18n("Encrypted message"); } else { htmlStr += i18n("Encrypted message (decryption not possible)"); if (!block.errorText.isEmpty()) { htmlStr += QLatin1String("
") + i18n("Reason: %1", block.errorText); } } htmlStr += QLatin1String("
"); } if (block.isSigned && block.inProgress) { block.signClass = QStringLiteral("signInProgress"); htmlStr += QLatin1String("" "
"); htmlStr += i18n("Please wait while the signature is being verified..."); htmlStr += QLatin1String("
"); } simpleHtmlStr = htmlStr; if (block.isSigned && !block.inProgress) { QStringList &blockAddrs(block.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; bool showKeyInfos; bool onlyShowKeyURL = false; bool cannotCheckSignature = true; QString statusStr = sigStatusToString(cryptProto, block.status_code, block.sigSummary, frameColor, showKeyInfos); // if needed fallback to english status text // that was reported by the plugin if (statusStr.isEmpty()) { statusStr = block.status; } if (block.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; } // compose the string for displaying the key ID // either as URL or not linked (for unknown crypto) // note: Once we can start PGP key manager programs // from within KMail we could change this and // always show the URL. (khz, 2002/06/27) QString startKeyHREF; QString keyWithWithoutURL; if (cryptProto) { startKeyHREF = QStringLiteral("") .arg(cryptProto->displayName(), cryptProto->name(), QString::fromLatin1(block.keyId)); keyWithWithoutURL = QStringLiteral("%1%2").arg(startKeyHREF, QString::fromLatin1(QByteArray(QByteArray("0x") + block.keyId))); } else { keyWithWithoutURL = QLatin1String("0x") + QString::fromUtf8(block.keyId); } // temporary hack: always show key information! showKeyInfos = true; // Sorry for using 'black' as null color but .isValid() // checking with QColor default c'tor did not work for // some reason. if (isSMIME && (SIG_FRAME_COL_UNDEF != frameColor)) { // new frame settings for CMS: // beautify the status string if (!statusStr.isEmpty()) { statusStr.prepend(QLatin1String("")); statusStr.append(QLatin1String("")); } // special color handling: S/MIME uses only green/yellow/red. switch (frameColor) { case SIG_FRAME_COL_RED: block.signClass = QStringLiteral("signErr");//"signCMSRed"; onlyShowKeyURL = true; break; case SIG_FRAME_COL_YELLOW: if (block.technicalProblem) { block.signClass = QStringLiteral("signWarn"); } else { block.signClass = QStringLiteral("signOkKeyBad"); //"signCMSYellow"; } break; case SIG_FRAME_COL_GREEN: block.signClass = QStringLiteral("signOkKeyOk");//"signCMSGreen"; // extra hint for green case // that email addresses in DN do not match fromAddress QString greenCaseWarning; QString msgFrom(KEmailAddress::extractEmailAddress(fromAddress)); QString certificate; if (block.keyId.isEmpty()) { certificate = i18n("certificate"); } else { certificate = startKeyHREF + i18n("certificate") + QLatin1String(""); } if (!blockAddrs.empty()) { if (!blockAddrs.contains(msgFrom, Qt::CaseInsensitive)) { greenCaseWarning = QLatin1String("") + i18nc("Start of warning message." , "Warning:") + QLatin1String(" ") + i18n("Sender's mail address is not stored " "in the %1 used for signing.", certificate) + QLatin1String("
") + i18n("sender: ") + msgFrom + QLatin1String("
") + 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; for (QStringList::ConstIterator it = blockAddrs.constBegin(); it != blockAddrs.constEnd(); ++it) { if (!bStart) { greenCaseWarning.append(QLatin1String(",
   ")); } bStart = false; greenCaseWarning.append(KEmailAddress::extractEmailAddress(*it)); } } } else { greenCaseWarning = QLatin1String("") + i18nc("Start of warning message.", "Warning:") + QLatin1String(" ") + 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); } if (!greenCaseWarning.isEmpty()) { if (!statusStr.isEmpty()) { statusStr.append(QLatin1String("
 
")); } statusStr.append(greenCaseWarning); } break; } QString frame = QLatin1String("" "
"); htmlStr += frame + beginVerboseSigstatHeader(); simpleHtmlStr += frame; simpleHtmlStr += writeSimpleSigstatHeader(block, mPrinting); if (block.technicalProblem) { htmlStr += block.errorText; } else if (showKeyInfos) { if (cannotCheckSignature) { htmlStr += i18n("Not enough information to check " "signature. %1", keyWithWithoutURL); } else { if (block.signer.isEmpty()) { signer.clear(); } else { if (!blockAddrs.empty()) { const QUrl address = KEmailAddress::encodeMailtoUrl(blockAddrs.first()); signer = QLatin1String("") + signer + QLatin1String(""); } } if (block.keyId.isEmpty()) { if (signer.isEmpty() || onlyShowKeyURL) { htmlStr += i18n("Message was signed with unknown key."); } else htmlStr += i18n("Message was signed by %1.", signer); } else { QDateTime created = block.creationTime; if (created.isValid()) { if (signer.isEmpty()) { if (onlyShowKeyURL) htmlStr += i18n("Message was signed with key %1.", keyWithWithoutURL); else htmlStr += i18n("Message was signed on %1 with key %2.", QLocale::system().toString(created, QLocale::ShortFormat), keyWithWithoutURL); } else { if (onlyShowKeyURL) htmlStr += i18n("Message was signed with key %1.", keyWithWithoutURL); else htmlStr += i18n("Message was signed by %3 on %1 with key %2", QLocale::system().toString(created, QLocale::ShortFormat), keyWithWithoutURL, signer); } } else { if (signer.isEmpty() || onlyShowKeyURL) htmlStr += i18n("Message was signed with key %1.", keyWithWithoutURL); else htmlStr += i18n("Message was signed by %2 with key %1.", keyWithWithoutURL, signer); } } } htmlStr += QLatin1String("
"); if (!statusStr.isEmpty()) { htmlStr += QLatin1String(" 
"); htmlStr += i18n("Status: "); htmlStr += statusStr; } } else { htmlStr += statusStr; } frame = QLatin1String("
"); htmlStr += endVerboseSigstatHeader(block) + frame; simpleHtmlStr += frame; } else { // old frame settings for PGP: if (block.signer.isEmpty() || block.technicalProblem) { block.signClass = QStringLiteral("signWarn"); QString frame = QLatin1String("" "
"); htmlStr += frame + beginVerboseSigstatHeader(); simpleHtmlStr += frame; simpleHtmlStr += writeSimpleSigstatHeader(block, mPrinting); if (block.technicalProblem) { htmlStr += block.errorText; } else { if (!block.keyId.isEmpty()) { QDateTime created = block.creationTime; if (created.isValid()) htmlStr += i18n("Message was signed on %1 with unknown key %2.", QLocale::system().toString(created, QLocale::ShortFormat), keyWithWithoutURL); else htmlStr += i18n("Message was signed with unknown key %1.", keyWithWithoutURL); } else { htmlStr += i18n("Message was signed with unknown key."); } htmlStr += QLatin1String("
"); htmlStr += i18n("The validity of the signature cannot be " "verified."); if (!statusStr.isEmpty()) { htmlStr += QLatin1String("
"); htmlStr += i18n("Status: "); htmlStr += QLatin1String(""); htmlStr += statusStr; htmlStr += QLatin1String(""); } } frame = QLatin1String("
"); htmlStr += endVerboseSigstatHeader(block) + frame; simpleHtmlStr += frame; } else { // HTMLize the signer's user id and create mailto: link signer = StringUtil::quoteHtmlChars(signer, true); signer = QLatin1String("") + signer + QLatin1String(""); if (block.isGoodSignature) { if (block.keyTrust < GpgME::Signature::Marginal) { block.signClass = QStringLiteral("signOkKeyBad"); } else { block.signClass = QStringLiteral("signOkKeyOk"); } QString frame = QLatin1String("" "" "
"); htmlStr += frame + beginVerboseSigstatHeader(); simpleHtmlStr += frame; simpleHtmlStr += writeSimpleSigstatHeader(block, mPrinting); if (!block.keyId.isEmpty()) htmlStr += i18n("Message was signed by %2 (Key ID: %1).", keyWithWithoutURL, signer); else { htmlStr += i18n("Message was signed by %1.", signer); } htmlStr += QLatin1String("
"); switch (block.keyTrust) { case GpgME::Signature::Unknown: htmlStr += i18n("The signature is valid, but the key's " "validity is unknown."); break; case GpgME::Signature::Marginal: htmlStr += i18n("The signature is valid and the key is " "marginally trusted."); break; case GpgME::Signature::Full: htmlStr += i18n("The signature is valid and the key is " "fully trusted."); break; case GpgME::Signature::Ultimate: htmlStr += i18n("The signature is valid and the key is " "ultimately trusted."); break; default: htmlStr += i18n("The signature is valid, but the key is " "untrusted."); } frame = QLatin1String("
"); htmlStr += endVerboseSigstatHeader(block) + frame; simpleHtmlStr += frame; } else { block.signClass = QStringLiteral("signErr"); QString frame = QLatin1String("" "" ""); htmlStr += QLatin1String("
"); htmlStr += frame + beginVerboseSigstatHeader(); simpleHtmlStr += frame; simpleHtmlStr += writeSimpleSigstatHeader(block, mPrinting); if (!block.keyId.isEmpty()) htmlStr += i18n("Message was signed by %2 (Key ID: %1).", keyWithWithoutURL, signer); else { htmlStr += i18n("Message was signed by %1.", signer); } htmlStr += QLatin1String("
"); htmlStr += i18n("Warning: The signature is bad."); frame = QLatin1String("
"); htmlStr += endVerboseSigstatHeader(block) + frame; simpleHtmlStr += frame; } } } } if (mSource->showSignatureDetails()) { return htmlStr; } return simpleHtmlStr; } QString ObjectTreeParser::writeSigstatFooter(PartMetaData &block) { const QString dir = (QApplication::isRightToLeft() ? QStringLiteral("rtl") : QStringLiteral("ltr")); QString htmlStr; if (block.isSigned) { htmlStr += QLatin1String("
") + i18n("End of signed message") + QLatin1String("
"); } if (block.isEncrypted) { htmlStr += QLatin1String("
") + i18n("End of encrypted message") + QLatin1String("
"); } if (block.isEncapsulatedRfc822Message) { htmlStr += QLatin1String("
") + i18n("End of encapsulated message") + QLatin1String("
"); } return htmlStr; } //----------------------------------------------------------------------------- bool ObjectTreeParser::okVerify(const QByteArray &data, const Kleo::CryptoBackend::Protocol *cryptProto, PartMetaData &messagePart, QByteArray &verifiedText, std::vector &signatures, const QByteArray &signature, KMime::Content *sign) { enum { NO_PLUGIN, NOT_INITIALIZED, CANT_VERIFY_SIGNATURES } cryptPlugError = NO_PLUGIN; QString cryptPlugLibName; QString cryptPlugDisplayName; if (cryptProto) { cryptPlugLibName = cryptProto->name(); cryptPlugDisplayName = cryptProto->displayName(); } messagePart.isSigned = false; messagePart.technicalProblem = (cryptProto == 0); messagePart.keyTrust = GpgME::Signature::Unknown; messagePart.status = i18n("Wrong Crypto Plug-In."); messagePart.status_code = GPGME_SIG_STAT_NONE; const QByteArray mementoName = "verification"; CryptoBodyPartMemento *m = dynamic_cast(mNodeHelper->bodyPartMemento(sign, mementoName)); if (!m) { if (!signature.isEmpty()) { Kleo::VerifyDetachedJob *job = cryptProto->verifyDetachedJob(); if (job) { m = new VerifyDetachedBodyPartMemento(job, cryptProto->keyListJob(), signature, data); } else { cryptPlugError = CANT_VERIFY_SIGNATURES; cryptProto = 0; } } else { Kleo::VerifyOpaqueJob *job = cryptProto->verifyOpaqueJob(); if (job) { m = new VerifyOpaqueBodyPartMemento(job, cryptProto->keyListJob(), data); } else { cryptPlugError = CANT_VERIFY_SIGNATURES; cryptProto = 0; } } if (m) { if (allowAsync()) { QObject::connect(m, &CryptoBodyPartMemento::update, mNodeHelper, &NodeHelper::update); QObject::connect(m, SIGNAL(update(MessageViewer::Viewer::UpdateMode)), mSource->sourceObject(), SLOT(update(MessageViewer::Viewer::UpdateMode))); if (m->start()) { messagePart.inProgress = true; mHasPendingAsyncJobs = true; } } else { m->exec(); } if (sign) { mNodeHelper->setBodyPartMemento(sign, mementoName, m); } } } else if (m->isRunning()) { messagePart.inProgress = true; mHasPendingAsyncJobs = true; m = 0; } else { messagePart.inProgress = false; mHasPendingAsyncJobs = false; } if (m && !messagePart.inProgress) { if (!signature.isEmpty()) { VerifyDetachedBodyPartMemento *vm = dynamic_cast(m); verifiedText = data; signatures = vm->verifyResult().signatures(); } else { VerifyOpaqueBodyPartMemento *vm = dynamic_cast(m); verifiedText = vm->plainText(); signatures = vm->verifyResult().signatures(); } messagePart.auditLogError = m->auditLogError(); messagePart.auditLog = m->auditLogAsHtml(); messagePart.isSigned = !signatures.empty(); } if (!cryptProto) { QString errorMsg; switch (cryptPlugError) { case NOT_INITIALIZED: errorMsg = i18n("Crypto plug-in \"%1\" is not initialized.", cryptPlugLibName); break; case CANT_VERIFY_SIGNATURES: errorMsg = i18n("Crypto plug-in \"%1\" cannot verify signatures.", cryptPlugLibName); break; case NO_PLUGIN: 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); } break; } messagePart.errorText = i18n("The message is signed, but the " "validity of the signature cannot be " "verified.
" "Reason: %1", errorMsg); } return messagePart.isSigned; } void ObjectTreeParser::sigStatusToMetaData(const std::vector &signatures, const Kleo::CryptoBackend::Protocol *cryptProto, PartMetaData &messagePart, GpgME::Key key) { if (messagePart.isSigned) { GpgME::Signature signature = signatures.front(); messagePart.status_code = signatureToStatus(signature); messagePart.isGoodSignature = messagePart.status_code & GPGME_SIG_STAT_GOOD; // save extended signature status flags messagePart.sigSummary = signature.summary(); if (messagePart.isGoodSignature && !key.keyID()) { // Search for the key by it's fingerprint so that we can check for // trust etc. Kleo::KeyListJob *job = cryptProto->keyListJob(false); // local, no sigs if (!job) { qCDebug(MESSAGEVIEWER_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(MESSAGEVIEWER_LOG) << "Error while searching key for Fingerprint: " << signature.fingerprint(); } if (found_keys.size() > 1) { // Should not Happen qCDebug(MESSAGEVIEWER_LOG) << "Oops: Found more then one Key for Fingerprint: " << signature.fingerprint(); } if (found_keys.size() != 1) { // Should not Happen at this point qCDebug(MESSAGEVIEWER_LOG) << "Oops: Found no Key for Fingerprint: " << signature.fingerprint(); } else { key = found_keys[0]; } } } if (key.keyID()) { messagePart.keyId = key.keyID(); } if (messagePart.keyId.isEmpty()) { messagePart.keyId = signature.fingerprint(); } messagePart.keyTrust = signature.validity(); if (key.numUserIDs() > 0 && key.userID(0).id()) { messagePart.signer = Kleo::DN(key.userID(0).id()).prettyDN(); } 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.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()) { messagePart.signerMailAddresses.append(email); } } } if (signature.creationTime()) { messagePart.creationTime.setTime_t(signature.creationTime()); } else { messagePart.creationTime = QDateTime(); } if (messagePart.signer.isEmpty()) { if (key.numUserIDs() > 0 && key.userID(0).name()) { messagePart.signer = Kleo::DN(key.userID(0).name()).prettyDN(); } if (!messagePart.signerMailAddresses.empty()) { if (messagePart.signer.isEmpty()) { messagePart.signer = messagePart.signerMailAddresses.front(); } else { messagePart.signer += QLatin1String(" <") + messagePart.signerMailAddresses.front() + QLatin1Char('>'); } } } } } -//----------------------------------------------------------------------------- -QVector ObjectTreeParser::writeBodyStr2(const QByteArray &aStr, const QTextCodec *aCodec, - const QString &fromAddress, - KMMsgSignatureState &inlineSignatureState, - KMMsgEncryptionState &inlineEncryptionState) -{ - inlineSignatureState = KMMsgNotSigned; - inlineEncryptionState = KMMsgNotEncrypted; - QList blocks = prepareMessageForDecryption(aStr); - - QVector mpl; - - if (!blocks.isEmpty()) { - - if (blocks.count() > 1 || blocks.at(0).type() != MessageViewer::NoPgpBlock) { - const Kleo::CryptoBackend::Protocol *cryptProto = Kleo::CryptoBackendFactory::instance()->openpgp(); - setCryptoProtocol(cryptProto); - } - - QString htmlStr; - QString plainTextStr; - - /* 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; - - Q_FOREACH (const Block &block, blocks) { - - if (!fullySignedOrEncryptedTmp) { - fullySignedOrEncrypted = false; - } - - if (block.type() == NoPgpBlock && !block.text().trimmed().isEmpty()) { - fullySignedOrEncryptedTmp = false; - mpl.append(MessagePart::Ptr(new MessagePart(this, aCodec->toUnicode(block.text())))); - } else if (block.type() == PgpMessageBlock) { - CryptoMessagePart::Ptr mp(new CryptoMessagePart(this, QString(), cryptoProtocol(), fromAddress, 0)); - mpl.append(mp); - if (!mSource->decryptMessage()) { - continue; - } - mp->startDecryption(block.text(), aCodec); - if (mp->partMetaData()->inProgress) { - continue; - } - } else if (block.type() == ClearsignedBlock) { - CryptoMessagePart::Ptr mp(new CryptoMessagePart(this, QString(), cryptoProtocol(), fromAddress, 0)); - mpl.append(mp); - mp->startVerification(block.text(), aCodec); - } else { - continue; - } - - const PartMetaData *messagePart(mpl.last()->partMetaData()); - - if (!messagePart->isEncrypted && !messagePart->isSigned && !block.text().trimmed().isEmpty()) { - mpl.last()->setText(aCodec->toUnicode(block.text())); - } - - if (messagePart->isEncrypted) { - inlineEncryptionState = KMMsgPartiallyEncrypted; - } - - if (messagePart->isSigned) { - inlineSignatureState = KMMsgPartiallySigned; - } - } - - //Do we have an fully Signed/Encrypted Message? - if (fullySignedOrEncrypted) { - if (inlineSignatureState == KMMsgPartiallySigned) { - inlineSignatureState = KMMsgFullySigned; - } - if (inlineEncryptionState == KMMsgPartiallyEncrypted) { - inlineEncryptionState = KMMsgFullyEncrypted; - } - } - } - return mpl; -} - -void ObjectTreeParser::writeBodyStr(const QByteArray &aStr, const QTextCodec *aCodec, - const QString &fromAddress, - KMMsgSignatureState &inlineSignatureState, - KMMsgEncryptionState &inlineEncryptionState, - bool decorate) -{ - const auto mpl = writeBodyStr2(aStr, aCodec, fromAddress, inlineSignatureState, inlineEncryptionState); - - if (!mpl.isEmpty()) { - if (htmlWriter()) { - foreach (const MessagePart::Ptr &mp, mpl) { - mp->html(decorate); - } - } - - const bool updatePlainText = (inlineSignatureState != KMMsgNotSigned - || inlineEncryptionState != KMMsgNotEncrypted); - if (updatePlainText || mPlainTextContent.isEmpty()) { - mPlainTextContent.clear(); - foreach (const MessagePart::Ptr &mp, mpl) { - mPlainTextContent += mp->text(); - } - mPlainTextContentCharset = aCodec->name(); - } - } -} - static QString iconToDataUrl(const QString &iconPath) { QFile f(iconPath); if (!f.open(QIODevice::ReadOnly)) { return QString(); } const QByteArray ba = f.readAll(); return QStringLiteral("data:image/png;base64,%1").arg(QLatin1String(ba.toBase64().constData())); } QString ObjectTreeParser::quotedHTML(const QString &s, bool decorate) { assert(cssHelper()); KTextToHTML::Options convertFlags = KTextToHTML::PreserveSpaces | KTextToHTML::HighlightText; if (decorate && MessageViewer::MessageViewerSettings::self()->showEmoticons()) { convertFlags |= KTextToHTML::ReplaceSmileys; } QString htmlStr; const QString normalStartTag = cssHelper()->nonQuotedFontTag(); QString quoteFontTag[3]; QString deepQuoteFontTag[3]; for (int i = 0; i < 3; ++i) { quoteFontTag[i] = cssHelper()->quoteFontTag(i); deepQuoteFontTag[i] = cssHelper()->quoteFontTag(i + 3); } const QString normalEndTag = QStringLiteral(""); const QString quoteEnd = QStringLiteral(""); const unsigned int length = s.length(); bool paraIsRTL = false; bool startNewPara = true; unsigned int pos, beg; // skip leading empty lines for (pos = 0; pos < length && s[pos] <= QLatin1Char(' '); ++pos) ; while (pos > 0 && (s[pos - 1] == QLatin1Char(' ') || s[pos - 1] == QLatin1Char('\t'))) { pos--; } beg = pos; int currQuoteLevel = -2; // -2 == no previous lines bool curHidden = false; // no hide any block if (MessageViewer::MessageViewerSettings::self()->showExpandQuotesMark()) { // Cache Icons if (mCollapseIcon.isEmpty()) { mCollapseIcon = iconToDataUrl(IconNameCache::instance()->iconPath(QStringLiteral("quotecollapse"), 0)); } if (mExpandIcon.isEmpty()) { mExpandIcon = iconToDataUrl(IconNameCache::instance()->iconPath(QStringLiteral("quoteexpand"), 0)); } } while (beg < length) { /* search next occurrence of '\n' */ pos = s.indexOf(QLatin1Char('\n'), beg, Qt::CaseInsensitive); if (pos == (unsigned int)(-1)) { pos = length; } QString line(s.mid(beg, pos - beg)); beg = pos + 1; /* calculate line's current quoting depth */ int actQuoteLevel = -1; const int numberOfCaracters(line.length()); for (int p = 0; p < numberOfCaracters; ++p) { switch (line[p].toLatin1()) { case '>': case '|': actQuoteLevel++; break; case ' ': // spaces and tabs are allowed between the quote markers case '\t': case '\r': break; default: // stop quoting depth calculation p = numberOfCaracters; break; } } /* for() */ bool actHidden = false; // This quoted line needs be hidden if (MessageViewer::MessageViewerSettings::self()->showExpandQuotesMark() && mSource->levelQuote() >= 0 && mSource->levelQuote() <= (actQuoteLevel)) { actHidden = true; } if (actQuoteLevel != currQuoteLevel) { /* finish last quotelevel */ if (currQuoteLevel == -1) { htmlStr.append(normalEndTag); } else if (currQuoteLevel >= 0 && !curHidden) { htmlStr.append(quoteEnd); } /* start new quotelevel */ if (actQuoteLevel == -1) { htmlStr += normalStartTag; } else { if (MessageViewer::MessageViewerSettings::self()->showExpandQuotesMark()) { if (actHidden) { //only show the QuoteMark when is the first line of the level hidden if (!curHidden) { //Expand all quotes htmlStr += QLatin1String("
"); htmlStr += QStringLiteral("" "\"\"") .arg(-1) .arg(mExpandIcon); htmlStr += QLatin1String("

"); htmlStr += quoteEnd; } } else { htmlStr += QLatin1String("
"); htmlStr += QStringLiteral("" "\"\"") .arg(actQuoteLevel) .arg(mCollapseIcon); htmlStr += QLatin1String("
"); if (actQuoteLevel < 3) { htmlStr += quoteFontTag[actQuoteLevel]; } else { htmlStr += deepQuoteFontTag[actQuoteLevel % 3]; } } } else { if (actQuoteLevel < 3) { htmlStr += quoteFontTag[actQuoteLevel]; } else { htmlStr += deepQuoteFontTag[actQuoteLevel % 3]; } } } currQuoteLevel = actQuoteLevel; } curHidden = actHidden; if (!actHidden) { // don't write empty
blocks (they have zero height) // ignore ^M DOS linebreaks if (!line.remove(QLatin1Char('\015')).isEmpty()) { if (startNewPara) { paraIsRTL = line.isRightToLeft(); } htmlStr += QStringLiteral("
").arg(paraIsRTL ? QStringLiteral("rtl") : QStringLiteral("ltr")); htmlStr += KTextToHTML::convertToHtml(line, convertFlags); htmlStr += QLatin1String("
"); startNewPara = looksLikeParaBreak(s, pos); } else { htmlStr += QLatin1String("
"); // after an empty line, always start a new paragraph startNewPara = true; } } } /* while() */ /* really finish the last quotelevel */ if (currQuoteLevel == -1) { htmlStr.append(normalEndTag); } else { htmlStr.append(quoteEnd); } qCDebug(MESSAGEVIEWER_LOG) << "========================================\n" << htmlStr << "\n======================================\n"; return htmlStr; } const QTextCodec *ObjectTreeParser::codecFor(KMime::Content *node) const { assert(node); if (mSource->overrideCodec()) { return mSource->overrideCodec(); } return mNodeHelper->codec(node); } // Guesstimate if the newline at newLinePos actually separates paragraphs in the text s // We use several heuristics: // 1. If newLinePos points after or before (=at the very beginning of) text, it is not between paragraphs // 2. If the previous line was longer than the wrap size, we want to consider it a paragraph on its own // (some clients, notably Outlook, send each para as a line in the plain-text version). // 3. Otherwise, we check if the newline could have been inserted for wrapping around; if this // was the case, then the previous line will be shorter than the wrap size (which we already // know because of item 2 above), but adding the first word from the next line will make it // longer than the wrap size. bool ObjectTreeParser::looksLikeParaBreak(const QString &s, unsigned int newLinePos) const { const unsigned int WRAP_COL = 78; unsigned int length = s.length(); // 1. Is newLinePos at an end of the text? if (newLinePos >= length - 1 || newLinePos == 0) { return false; } // 2. Is the previous line really a paragraph -- longer than the wrap size? // First char of prev line -- works also for first line unsigned prevStart = s.lastIndexOf(QLatin1Char('\n'), newLinePos - 1) + 1; unsigned prevLineLength = newLinePos - prevStart; if (prevLineLength > WRAP_COL) { return true; } // find next line to delimit search for first word unsigned int nextStart = newLinePos + 1; int nextEnd = s.indexOf(QLatin1Char('\n'), nextStart); if (nextEnd == -1) { nextEnd = length; } QString nextLine = s.mid(nextStart, nextEnd - nextStart); length = nextLine.length(); // search for first word in next line unsigned int wordStart; bool found = false; for (wordStart = 0; !found && wordStart < length; wordStart++) { switch (nextLine[wordStart].toLatin1()) { case '>': case '|': case ' ': // spaces, tabs and quote markers don't count case '\t': case '\r': break; default: found = true; break; } } /* for() */ if (!found) { // next line is essentially empty, it seems -- empty lines are // para separators return true; } //Find end of first word. //Note: flowText (in kmmessage.cpp) separates words for wrap by //spaces only. This should be consistent, which calls for some //refactoring. int wordEnd = nextLine.indexOf(QLatin1Char(' '), wordStart); if (wordEnd == (-1)) { wordEnd = length; } int wordLength = wordEnd - wordStart; // 3. If adding a space and the first word to the prev line don't // make it reach the wrap column, then the break was probably // meaningful return prevLineLength + wordLength + 1 < WRAP_COL; } #ifdef MARCS_DEBUG void ObjectTreeParser::dumpToFile(const char *filename, const char *start, size_t len) { assert(filename); QFile f(QString::fromAscii(filename)); if (f.open(QIODevice::WriteOnly)) { if (start) { QDataStream ds(&f); ds.writeRawData(start, len); } f.close(); // If data is 0 we just create a zero length file. } } #endif // !NDEBUG KMime::Content *ObjectTreeParser::findType(KMime::Content *content, const QByteArray &mimeType, bool deep, bool wide) { if ((!content->contentType()->isEmpty()) && (mimeType.isEmpty() || (mimeType == content->contentType()->mimeType()))) { return content; } KMime::Content *child = MessageCore::NodeHelper::firstChild(content); if (child && deep) { //first child return findType(child, mimeType, deep, wide); } KMime::Content *next = MessageCore::NodeHelper::nextSibling(content); if (next && wide) { //next on the same level return findType(next, mimeType, deep, wide); } return 0; } KMime::Content *ObjectTreeParser::findType(KMime::Content *content, const QByteArray &mediaType, const QByteArray &subType, bool deep, bool wide) { if (!content->contentType()->isEmpty()) { if ((mediaType.isEmpty() || mediaType == content->contentType()->mediaType()) && (subType.isEmpty() || subType == content->contentType()->subType())) { return content; } } KMime::Content *child = MessageCore::NodeHelper::firstChild(content); if (child && deep) { //first child return findType(child, mediaType, subType, deep, wide); } KMime::Content *next = MessageCore::NodeHelper::nextSibling(content); if (next && wide) { //next on the same level return findType(next, mediaType, subType, deep, wide); } return 0; } KMime::Content *ObjectTreeParser::findTypeNot(KMime::Content *content, const QByteArray &mediaType, const QByteArray &subType, bool deep, bool wide) { if ((!content->contentType()->isEmpty()) && (mediaType.isEmpty() || content->contentType()->mediaType() != mediaType) && (subType.isEmpty() || content->contentType()->subType() != subType) ) { return content; } KMime::Content *child = MessageCore::NodeHelper::firstChild(content); if (child && deep) { return findTypeNot(child, mediaType, subType, deep, wide); } KMime::Content *next = MessageCore::NodeHelper::nextSibling(content); if (next && wide) { return findTypeNot(next, mediaType, subType, deep, wide); } return 0; } QString ObjectTreeParser::convertedTextContent() const { QString plainTextContent = mPlainTextContent; if (plainTextContent.isEmpty()) { QWebPage doc; doc.mainFrame()->setHtml(mHtmlContent); plainTextContent = doc.mainFrame()->toPlainText(); } return plainTextContent.append(QLatin1Char('\n')); } QString ObjectTreeParser::convertedHtmlContent() const { QString htmlContent = mHtmlContent; if (htmlContent.isEmpty()) { QString convertedHtml = mPlainTextContent.toHtmlEscaped(); convertedHtml.append(QStringLiteral("")); convertedHtml.prepend(QStringLiteral("")); htmlContent = convertedHtml.replace(QStringLiteral("\n"), QStringLiteral("
")); } return htmlContent.append(QLatin1Char('\n')); } QByteArray ObjectTreeParser::plainTextContentCharset() const { return mPlainTextContentCharset; } QByteArray ObjectTreeParser::htmlContentCharset() const { return mHtmlContentCharset; } void ObjectTreeParser::setCryptoProtocol(const Kleo::CryptoBackend::Protocol *protocol) { mCryptoProtocol = protocol; } const Kleo::CryptoBackend::Protocol *ObjectTreeParser::cryptoProtocol() const { return mCryptoProtocol; } bool ObjectTreeParser::showOnlyOneMimePart() const { return mShowOnlyOneMimePart; } void ObjectTreeParser::setShowOnlyOneMimePart(bool show) { mShowOnlyOneMimePart = show; } const AttachmentStrategy *ObjectTreeParser::attachmentStrategy() const { return mAttachmentStrategy; } HtmlWriter *ObjectTreeParser::htmlWriter() const { if (mHtmlWriter) { return mHtmlWriter; } return mSource->htmlWriter(); } CSSHelper *ObjectTreeParser::cssHelper() const { return mSource->cssHelper(); } MessageViewer::NodeHelper *ObjectTreeParser::nodeHelper() const { return mNodeHelper; } diff --git a/messageviewer/src/viewer/objecttreeparser.h b/messageviewer/src/viewer/objecttreeparser.h index 1aec4ce1..560caa9b 100644 --- a/messageviewer/src/viewer/objecttreeparser.h +++ b/messageviewer/src/viewer/objecttreeparser.h @@ -1,519 +1,502 @@ /* objecttreeparser.h 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. */ #ifndef _MESSAGEVIEWER_OBJECTTREEPARSER_H_ #define _MESSAGEVIEWER_OBJECTTREEPARSER_H_ #include "messageviewer_export.h" #include "messageviewer/nodehelper.h" #include "objecttreesourceif.h" #include #include #include #include "objecttreeemptysource.h" #include "messagepart.h" class QString; namespace KMime { class Content; } namespace GpgME { class Error; class ImportResult; } namespace MessageViewer { class PartMetaData; class ViewerPrivate; class HtmlWriter; class CSSHelper; class AttachmentStrategy; class NodeHelper; class MESSAGEVIEWER_EXPORT ProcessResult { public: explicit ProcessResult(NodeHelper *nodeHelper, KMMsgSignatureState inlineSignatureState = KMMsgNotSigned, KMMsgEncryptionState inlineEncryptionState = KMMsgNotEncrypted, bool neverDisplayInline = false, bool isImage = false) : mInlineSignatureState(inlineSignatureState), mInlineEncryptionState(inlineEncryptionState), mNeverDisplayInline(neverDisplayInline), mIsImage(isImage), mNodeHelper(nodeHelper) {} KMMsgSignatureState inlineSignatureState() const; void setInlineSignatureState(KMMsgSignatureState state); KMMsgEncryptionState inlineEncryptionState() const; void setInlineEncryptionState(KMMsgEncryptionState state); bool neverDisplayInline() const; void setNeverDisplayInline(bool display); bool isImage() const; void setIsImage(bool image); void adjustCryptoStatesOfNode(KMime::Content *node) const; private: KMMsgSignatureState mInlineSignatureState; KMMsgEncryptionState mInlineEncryptionState; bool mNeverDisplayInline : 1; bool mIsImage : 1; NodeHelper *mNodeHelper; }; /** \brief Parses messages and generates HTML display code out of them \par Introduction First, have a look at the documentation in Mainpage.dox and at the documentation of ViewerPrivate to understand the broader picture. Just a note on the terminology: 'Node' refers to a MIME part here, which in KMime is a KMime::Content. \par Basics The ObjectTreeParser basically has two modes: Generating the HTML code for the Viewer, or only extracting the plainTextContent() for situations where only the message text is needed, for example when inline forwarding a message. The mode depends on the ObjectTreeSourceIf passed to the constructor: If ObjectTreeSourceIf::htmlWriter() is not 0, then the HTML code generation mode is used. Basically, all the ObjectTreeParser does is going through the tree of MIME parts and operating on those nodes. Operating here means creating the HTML code for the node or extracting the textual content from it. This process is started with parseObjectTree(), where we loop over the subnodes of the current root node. For each of those subnodes, we try to find a BodyPartFormatter that can handle the type of the node. This can either be an internal function, such as processMultiPartAlternativeSubtype() or processTextHtmlSubtype(), or it can be an external plugin. More on external plugins later. When no matching formatter is found, defaultHandling() is called for that node. \par Multipart Nodes Those nodes that are of type multipart have subnodes. If one of those children needs to be processed normally, the processMultipartXXX() functions call stdChildHandling() for the node that should be handled normally. stdChildHandling() creates its own ObjectTreeParser, which is a clone of the current ObjectTreeParser, and processes the node. stdChildHandling() is not called for all children of the multipart node, for example processMultiPartAlternativeSubtype() only calls it on one of the children, as the other one doesn't need to be displayed. Similary, processMultiPartSignedSubtype() doesn't call stdChildHandling() for the signature node, only for the signed node. \par Processed and Unprocessed Nodes When a BodyPartFormatter has finished processing a node, it is processed. Nodes are set to being not processed at the beginning of parseObjectTree(). The processed state of a node is saved in a list in NodeHelper, see NodeHelper::setNodeProcessed(), NodeHelper::nodeProcessed() and the other related helper functions. It is the responsibility of the BodyPartFormatter to correctly call setNodeProcessed() and the related functions. This is important so that processing the same node twice can be prevented. The check that prevents duplicate processing is in parseObjectTree(). An example where duplicate processing would happen if we didn't check for it is in stdChildHandling(), which is for example called from processMultiPartAlternativeSubtype(). Let's say the setting is to prefer HTML over plain text. In this case, processMultiPartAlternativeSubtype() would call stdChildHandling() on the HTML node, which would create a new ObjectTreeParser and call parseObjectTree() on it. parseObjectTree() processes the node and all its siblings, and one of the siblings is the plain text node, which shouldn't be processed! Therefore processMultiPartAlternativeSubtype() sets the plain text node as been processed already. \par Plain Text Output Various nodes have plain text that should be displayed. This plain text is usually processed though writeBodyString() first. That method checks if the provided text is an inline PGP text and decrypts it if necessary. It also pushes the text through quotedHTML(), which does a number of things like coloring quoted lines or detecting links and creating real link tags for them. \par Modifying the Message The ObjectTreeParser does not only parse its message, in some circumstances it also modifies it before displaying. This is for example the case when displaying a decrypted message: The original message only contains a binary blob of crypto data, and processMultiPartEncryptedSubtype() decrypts that blob. After decryption, the current node is replaced with the decrypted node, which happens in insertAndParseNewChildNode(). \par Crypto Operations For signature and decryption handling, there are functions which help with generating the HTML code for the signature header and footer. These are writeDeferredDecryptionBlock(), writeSigstatFooter() and writeSigstatHeader(). As the name writeDeferredDecryptionBlock() suggests, a setting can cause the message to not be decrypted unless the user clicks a link. Whether the message should be decrypted or not can be controlled by ObjectTreeSourceIf::decryptMessage(). When the user clicks the decryption link, the URLHandler for 'kmail:' URLs sets that variable to true and triggers an update of the Viewer, which will cause parseObjectTree() to be called again. \par Async Crypto Operations The above case describes decryption the message in place. However, decryption and also verifying of the signature can take a long time, so synchronous decryption and verifing would cause the Viewer to block. Therefore it is possible to run these operations in async mode, see allowAsync(). In the first run of the async mode, all the ObjectTreeParser does is starting the decrypt or the verify job, and informing the user that the operation is in progress with writeDecryptionInProgressBlock() or with writeSigstatHeader(). Then, it creates and associates a BodyPartMemento with the current node, for example a VerifyDetachedBodyPartMemento. Each node can have multiple mementos associated with it, which are differeniated by name. NodeHelper::setBodyPartMemento() and NodeHelper::bodyPartMemento() provide means to store and retrieve these mementos. A memento is basically a thin wrapper around the crypto job, it stores the job pointer, the job input data and the job result. Mementos can be used for any async situation, not just for crypto jobs, but I'll describe crypto jobs here. So in the first run of decrypting or verifying a message, the BodyPartFormatter only starts the crypto job, creates the BodyPartMemento and writes the HTML code that tells the user that the operation is in progress. parseObjectTree() thus finishes without waiting for anything, and the message is displayed. At some point, the crypto jobs then finish, which will cause slotResult() of the BodyPartMemento to be called. slotResult() then saves the result to some member variable and calls BodyPartMemento::notify(), which in the end will trigger an update of the Viewer. That update will, in ViewerPrivate::parseMsg(), create a new ObjectTreeParser and call parseObjectTree() on it. This is where the second run begins. The functions that deal with decrypting of verifying, like processMultiPartSignedSubtype() or processMultiPartEncryptedSubtype() will look if they find a BodyPartMemento that is associated with the current node. Now it finds that memento, since it was created in the first run. It checks if the memento's job has finished, and if so, the result can be written out (either the decrypted data or the verified signature). When dealing with encrypted nodes, new nodes are created with the decrypted data. It is important to note that the original MIME tree is never modified, and remains the same as the original one. The method createAndParseTempNode is called with the newly decrypted data, and it generates a new temporary node to store the decrypted data. When these nodes are created, it is important to keep track of them as otherwise some mementos that are added to the newly created temporary nodes will be constantly regenerated. As the regeneration triggers a viewer update when complete, it results in an infinite refresh loop. The function NodeHelper::linkAsPermanentDecrypted will create a link between the newly created node and the original parent. Conversely, the function NodeHelper::attachExtraContent will create a link in the other direction, from the parent node to the newly created temporary node. When generating some mementos for nodes that may be temporary nodes (for example, contact photo mementos), the function NodeHelper::setBodyPartMementoForPermanentParent is used. This will save the given body part memento for the closest found permanent parent node, rather than the transient node itself. Then when checking for the existence of a certain memento in a node, NodeHelper::findPermanentParentBodyPartMemento will check to see if any parent of the given temporary node is a permanent (encrypted) node that has been used to generate the asked-for node. To conclude: For async operations, parseObjectTree() is called twice: The first call starts the crypto operation and creates the BodyPartMemento, the second calls sees that the BodyPartMemento is there and can use its result for writing out the HTML. \par PartMetaData and ProcessResult For crypto operations, the class PartMetaData is used a lot, mainly to pass around info about the crypto state of a node. A PartMetaData can also be associated with a node by using NodeHelper::setPartMetaData(). The only user of that however is MessageAnalyzer::processPart() of the Nepomuk E-Mail Feeder, which also uses the ObjectTreeParser to analyze the message. You'll notice that a ProcessResult is passed to each formatter. The formatter is supposed to modify the ProcessResult to tell the callers something about the state of the nodes that were processed. One example for its use is to tell the caller about the crypto state of the node. \par BodyPartFormatter Plugins As mentioned way earlier, BodyPartFormatter can either be plugins or be internal. bodypartformatter.cpp contains some trickery so that the processXXX() methods of the ObjectTreeParser are called from a BodyPartFormatter associated with them, see the CREATE_BODY_PART_FORMATTER macro. The BodyPartFormatter code is work in progress, it was supposed to be refactored, but that has not yet happened at the time of writing. Therefore the code can seem a bit chaotic. External plugins are loaded with loadPlugins() in bodypartformatterfactory.cpp. External plugins can only use the classes in the interfaces/ directory, they include BodyPart, BodyPartMemento, BodyPartFormatterPlugin, BodyPartFormatter, BodyPartURLHandler, HtmlWriter and URLHandler. Therefore external plugins have powerful capabilities, which are needed for example in the iCal formatter or in the vCard formatter. \par Special HTML tags As also mentioned in the documentation of ViewerPrivate, the ObjectTreeParser writes out special links that are only understood by the viewer, for example 'kmail:' URLs or 'attachment:' URLs. Also, some special HTML tags are created, which the Viewer later uses for post-processing. For example a div with the id 'attachmentInjectionPoint', or a div with the id 'attachmentDiv', which is used to mark an attachment in the body with a yellow border when the user clicks the attachment in the header. Finally, parseObjectTree() creates an anchor with the id 'att%1', which is used in the Viewer to scroll to the attachment. */ class MESSAGEVIEWER_EXPORT ObjectTreeParser { class CryptoProtocolSaver; /** * @internal * Copies the context of @p other, but not it's rawDecryptedBody, plainTextContent or htmlContent. */ ObjectTreeParser(const ObjectTreeParser &other); public: explicit ObjectTreeParser(ObjectTreeSourceIf *source, NodeHelper *nodeHelper = 0, const Kleo::CryptoBackend::Protocol *protocol = Q_NULLPTR, bool showOneMimePart = false, const AttachmentStrategy *attachmentStrategy = Q_NULLPTR); explicit ObjectTreeParser(const ObjectTreeParser *topLevelParser, bool showOneMimePart = false, const AttachmentStrategy *attachmentStrategy = Q_NULLPTR); virtual ~ObjectTreeParser(); void setAllowAsync(bool allow); bool allowAsync() const; bool hasPendingAsyncJobs() const; /** * The text of the message, ie. what would appear in the * composer's text editor if this was edited or replied to. * This is usually the content of the first text/plain MIME part. */ QString plainTextContent() const; /** * Similar to plainTextContent(), but returns the HTML source of the first text/html MIME part. * * Not to be consfused with the HTML code that the message viewer widget displays, that HTML * is written out by htmlWriter() and a totally different pair of shoes. */ QString htmlContent() const; /** * Returns a plain text version of the content, which is either plainTextContent() if that exists, * or htmlContent() converted to plain text otherwise. */ QString convertedTextContent() const; /** Returns a HTML version of the plain text mail. If the HTML content is already available, it * returns the HTML content as it is. */ QString convertedHtmlContent() const; /** * The original charset of MIME part the plain text was extracted from. * * If there were more than one text/plain MIME parts in the mail, the this is the charset * of the last MIME part processed. */ QByteArray plainTextContentCharset() const; QByteArray htmlContentCharset() const; void setCryptoProtocol(const Kleo::CryptoBackend::Protocol *protocol); const Kleo::CryptoBackend::Protocol *cryptoProtocol() const; bool showOnlyOneMimePart() const; void setShowOnlyOneMimePart(bool show); const AttachmentStrategy *attachmentStrategy() const; HtmlWriter *htmlWriter() const; CSSHelper *cssHelper() const; NodeHelper *nodeHelper() const; /** Parse beginning at a given node and recursively parsing the children of that node and it's next sibling. */ void parseObjectTree(KMime::Content *node); void setPrinting(bool printing); private: void extractNodeInfos(KMime::Content *curNode, bool isFirstTextPart); /** * Does the actual work for parseObjectTree. Unlike parseObjectTree(), this does not change the * top-level content. */ void parseObjectTreeInternal(KMime::Content *node); /** Standard children handling a.k.a. multipart/mixed (w/o kroupware hacks) */ void standardChildHandling(KMime::Content *child); MessagePart::Ptr defaultHandling(KMime::Content *node, ProcessResult &result); /** 1. Create a new partNode using 'content' data and Content-Description found in 'cntDesc'. 2. Parse the 'node' to display the content. */ void createAndParseTempNode(KMime::Content *parentNode, const char *content, const char *cntDesc); /** Writes out the information contained in a GpgME::ImportResult */ void writeCertificateImportResult(const GpgME::ImportResult &res); /** Returns the contents of the given multipart/encrypted object. Data is decypted. May contain body parts. */ bool okDecryptMIME(KMime::Content &data, QByteArray &decryptedData, bool &signatureFound, std::vector &signatures, bool showWarning, bool &passphraseError, bool &actuallyEncrypted, bool &decryptionStarted, PartMetaData &partMetaData); bool okVerify(const QByteArray &data, const Kleo::CryptoBackend::Protocol *cryptProto, MessageViewer::PartMetaData &messagePart, QByteArray &verifiedText, std::vector &signatures, const QByteArray &signature, KMime::Content *sign); void sigStatusToMetaData(const std::vector &signatures, const Kleo::CryptoBackend::Protocol *cryptoProtocol, PartMetaData &messagePart, GpgME::Key key); bool processMailmanMessage(KMime::Content *node); public:// (during refactoring) MessagePart::Ptr processTextHtmlSubtype(KMime::Content *node, ProcessResult &result); MessagePart::Ptr processTextPlainSubtype(KMime::Content *node, ProcessResult &result); MessagePart::Ptr processMultiPartMixedSubtype(KMime::Content *node, ProcessResult &result); MessagePart::Ptr processMultiPartAlternativeSubtype(KMime::Content *node, ProcessResult &result); MessagePart::Ptr processMultiPartSignedSubtype(KMime::Content *node, ProcessResult &result); MessagePart::Ptr processMultiPartEncryptedSubtype(KMime::Content *node, ProcessResult &result); MessagePart::Ptr processApplicationPkcs7MimeSubtype(KMime::Content *node, ProcessResult &result); - void writeBodyString(const QByteArray &bodyString, - const QString &fromAddress, - const QTextCodec *codec, - ProcessResult &result, bool decorate); - void writePartIcon(KMime::Content *msgPart, bool inlineImage = false); QString sigStatusToString(const Kleo::CryptoBackend::Protocol *cryptProto, int status_code, GpgME::Signature::Summary summary, int &frameColor, bool &showKeyInfos); QString writeSigstatHeader(PartMetaData &part, const Kleo::CryptoBackend::Protocol *cryptProto, const QString &fromAddress, KMime::Content *node = 0); QString writeSigstatFooter(PartMetaData &part); - void writeBodyStr(const QByteArray &bodyString, - const QTextCodec *aCodec, - const QString &fromAddress, - KMMsgSignatureState &inlineSignatureState, - KMMsgEncryptionState &inlineEncryptionState, - bool decorate); - - QVector writeBodyStr2(const QByteArray &aStr, const QTextCodec *aCodec, - const QString &fromAddress, - KMMsgSignatureState &inlineSignatureState, - KMMsgEncryptionState &inlineEncryptionState); - bool isMailmanMessage(KMime::Content *curNode); public: static KMime::Content *findType(KMime::Content *content, const QByteArray &mimeType, bool deep, bool wide); static KMime::Content *findType(KMime::Content *content, const QByteArray &mediaType, const QByteArray &subType, bool deep, bool wide); static KMime::Content *findTypeNot(KMime::Content *content, const QByteArray &mediaType, const QByteArray &subType, bool deep = true, bool wide = true); private: /** ctor helper */ void init(); /** Change the string to `quoted' html (meaning, that the quoted part of the message get italized */ QString quotedHTML(const QString &pos, bool decorate); const QTextCodec *codecFor(KMime::Content *node) const; /** Check if the newline at position @p newLinePos in string @p s seems to separate two paragraphs (important for correct BiDi behavior, but is heuristic because paragraphs are not well-defined) */ bool looksLikeParaBreak(const QString &s, unsigned int newLinePos) const; #ifdef MARCS_DEBUG void dumpToFile(const char *filename, const char *dataStart, size_t dataLen); #else void dumpToFile(const char *, const char *, size_t) {} #endif void copyContentFrom(const ObjectTreeParser *other); private: ObjectTreeSourceIf *mSource; NodeHelper *mNodeHelper; HtmlWriter *mHtmlWriter; QByteArray mPlainTextContentCharset; QByteArray mHtmlContentCharset; QString mPlainTextContent; QString mHtmlContent; KMime::Content *mTopLevelContent; const Kleo::CryptoBackend::Protocol *mCryptoProtocol; /// Show only one mime part means that the user has selected some node in the message structure /// viewer that is not the root, which means the user wants to only see the selected node and its /// children. If that is the case, this variable is set to true. /// The code needs to behave differently if this is set. For example, it should not process the /// siblings. Also, consider inline images: Normally, those nodes are completely hidden, as the /// HTML node embedds them. However, when showing only the node of the image, one has to show them, /// as their is no HTML node in which they are displayed. There are many more cases where this /// variable needs to be obeyed. /// This variable is set to false again when processing the children in stdChildHandling(), as /// the children can be completely displayed again. bool mShowOnlyOneMimePart; bool mHasPendingAsyncJobs; bool mAllowAsync; const AttachmentStrategy *mAttachmentStrategy; // DataUrl Icons cache QString mCollapseIcon; QString mExpandIcon; bool mDeleteNodeHelper; bool mPrinting; friend class MessagePart; friend class CryptoMessagePart; friend class CertMessagePart; friend class EncapsulatedRfc822MessagePart; friend class TextMessagePart; friend class HtmlMessagePart; }; } #endif // _KMAIL_OBJECTTREEPARSER_H_