// SPDX-FileCopyrightText: 2015 Sandro Knauß <knauss@kolabsys.com>
// SPDX-FileCopyrightText: 2017 Christian Mollekopf <mollekopf@kolabsys.com>
// SPDX-License-Identifier: LGPL-2.0-or-later

#include "messagepart.h"
#include "cryptohelper.h"
#include "mimetreeparser_core_debug.h"
#include "objecttreeparser.h"

#include "utils.h"

#include <KLocalizedString>
#include <KMime/Content>
#include <Libkleo/Compliance>
#include <Libkleo/KeyCache>

#include <QGpgME/DN>
#include <QGpgME/DecryptVerifyJob>
#include <QGpgME/Protocol>
#include <QGpgME/VerifyDetachedJob>
#include <QGpgME/VerifyOpaqueJob>

#include <QStringDecoder>

#include <gpgme++/key.h>
#include <gpgme++/keylistresult.h>
#include <gpgme.h>

using namespace MimeTreeParser;

//------MessagePart-----------------------
MessagePart::MessagePart(ObjectTreeParser *otp, const QString &text, KMime::Content *node)
    : mText(text)
    , mOtp(otp)
    , mParentPart(nullptr)
    , mNode(node) // only null for messagepartlist
    , mError(NoError)
    , mRoot(false)
{
}

MessagePart::~MessagePart()
{
    for (auto n : std::as_const(mNodesToDelete)) {
        delete n;
    }
}

MessagePart::Disposition MessagePart::disposition() const
{
    if (!mNode) {
        return Invalid;
    }
    const auto cd = mNode->contentDisposition(false);
    if (!cd) {
        return Invalid;
    }
    switch (cd->disposition()) {
    case KMime::Headers::CDinline:
        return Inline;
    case KMime::Headers::CDattachment:
        return Attachment;
    default:
        return Invalid;
    }
}

QString MessagePart::filename() const
{
    if (!mNode) {
        return {};
    }

    if (const auto cd = mNode->contentDisposition(false)) {
        const auto name = cd->filename();
        // Allow for a fallback for mails that have a ContentDisposition header, but don't set the filename anyways.
        // Not the recommended way, but exists.
        if (!name.isEmpty()) {
            return name;
        }
    }
    if (const auto ct = mNode->contentType(false)) {
        return ct->name();
    }
    return {};
}

static KMime::Headers::ContentType *contentType(KMime::Content *node)
{
    if (node) {
        return node->contentType(false);
    }
    return nullptr;
}

QByteArray MessagePart::charset() const
{
    if (!mNode) {
        return QByteArrayLiteral("us-ascii");
    }
    if (auto ct = contentType(mNode)) {
        return ct->charset();
    }
    // Per rfc2045 us-ascii is the default
    return QByteArrayLiteral("us-ascii");
}

QByteArray MessagePart::mimeType() const
{
    if (auto ct = contentType(mNode)) {
        return ct->mimeType();
    }
    return {};
}

bool MessagePart::isText() const
{
    if (auto ct = contentType(mNode)) {
        return ct->isText();
    }
    return false;
}

MessagePart::Error MessagePart::error() const
{
    return mError;
}

QString MessagePart::errorString() const
{
    return mMetaData.errorText;
}

PartMetaData *MessagePart::partMetaData()
{
    return &mMetaData;
}

bool MessagePart::isAttachment() const
{
    if (mNode) {
        return KMime::isAttachment(mNode);
    }
    return false;
}

KMime::Content *MessagePart::node() const
{
    return mNode;
}

void MessagePart::setIsRoot(bool root)
{
    mRoot = root;
}

bool MessagePart::isRoot() const
{
    return mRoot;
}

QString MessagePart::text() const
{
    return mText;
}

void MessagePart::setText(const QString &text)
{
    mText = text;
}

bool MessagePart::isHtml() const
{
    return false;
}

MessagePart *MessagePart::parentPart() const
{
    return mParentPart;
}

void MessagePart::setParentPart(MessagePart *parentPart)
{
    mParentPart = parentPart;
}

QString MessagePart::htmlContent() const
{
    return text();
}

QString MessagePart::plaintextContent() const
{
    return text();
}

void MessagePart::parseInternal(KMime::Content *node, bool onlyOneMimePart)
{
    auto subMessagePart = mOtp->parseObjectTreeInternal(node, onlyOneMimePart);
    mRoot = subMessagePart->isRoot();
    for (const auto &part : std::as_const(subMessagePart->subParts())) {
        appendSubPart(part);
    }
}

void MessagePart::parseInternal(const QByteArray &data)
{
    auto tempNode = new KMime::Content();

    const auto lfData = KMime::CRLFtoLF(data);
    // We have to deal with both bodies and full parts. In inline encrypted/signed parts we can have nested parts,
    // or just plain-text, and both ends up here. setContent defaults to setting only the header, so we have to avoid this.
    if (lfData.contains("\n\n")) {
        tempNode->setContent(lfData);
    } else {
        tempNode->setBody(lfData);
    }
    tempNode->parse();
    tempNode->contentType()->setCharset(charset());
    bindLifetime(tempNode);

    if (!tempNode->head().isEmpty()) {
        tempNode->contentDescription()->from7BitString(QByteArrayLiteral("temporary node"));
    }

    parseInternal(tempNode);
}

QString MessagePart::renderInternalText() const
{
    QString text;
    for (const auto &mp : subParts()) {
        text += mp->text();
    }
    return text;
}

void MessagePart::appendSubPart(const MessagePart::Ptr &messagePart)
{
    messagePart->setParentPart(this);
    mBlocks.append(messagePart);
}

const QList<MessagePart::Ptr> &MessagePart::subParts() const
{
    return mBlocks;
}

bool MessagePart::hasSubParts() const
{
    return !mBlocks.isEmpty();
}

QList<SignedMessagePart *> MessagePart::signatures() const
{
    QList<SignedMessagePart *> list;
    if (auto sig = dynamic_cast<SignedMessagePart *>(const_cast<MessagePart *>(this))) {
        list << sig;
    }
    auto parent = parentPart();
    while (parent) {
        if (auto sig = dynamic_cast<SignedMessagePart *>(parent)) {
            list << sig;
        }
        parent = parent->parentPart();
    }
    return list;
}

QList<EncryptedMessagePart *> MessagePart::encryptions() const
{
    QList<EncryptedMessagePart *> list;
    if (auto sig = dynamic_cast<EncryptedMessagePart *>(const_cast<MessagePart *>(this))) {
        list << sig;
    }
    auto parent = parentPart();
    while (parent) {
        if (auto sig = dynamic_cast<EncryptedMessagePart *>(parent)) {
            list << sig;
        }
        parent = parent->parentPart();
    }
    return list;
}

KMMsgEncryptionState MessagePart::encryptionState() const
{
    if (!encryptions().isEmpty()) {
        return KMMsgFullyEncrypted;
    }
    return KMMsgNotEncrypted;
}

KMMsgSignatureState MessagePart::signatureState() const
{
    if (!signatures().isEmpty()) {
        return KMMsgFullySigned;
    }
    return KMMsgNotSigned;
}

void MessagePart::bindLifetime(KMime::Content *node)
{
    mNodesToDelete << node;
}

KMime::Headers::Base *MimeTreeParser::MessagePart::header(const char *header) const
{
    if (node() && node()->hasHeader(header)) {
        return node()->headerByType(header);
    }
    if (auto parent = parentPart()) {
        return parent->header(header);
    }

    return nullptr;
}

//-----MessagePartList----------------------
MessagePartList::MessagePartList(ObjectTreeParser *otp, KMime::Content *node)
    : MessagePart(otp, QString(), node)
{
}

QString MessagePartList::text() const
{
    return renderInternalText();
}

QString MessagePartList::plaintextContent() const
{
    return QString();
}

QString MessagePartList::htmlContent() const
{
    return QString();
}

//-----TextMessageBlock----------------------

TextMessagePart::TextMessagePart(ObjectTreeParser *otp, KMime::Content *node)
    : MessagePartList(otp, node)
    , mSignatureState(KMMsgSignatureStateUnknown)
    , mEncryptionState(KMMsgEncryptionStateUnknown)
{
    if (!mNode) {
        qCWarning(MIMETREEPARSER_CORE_LOG) << "not a valid node";
        return;
    }

    parseContent();
}

void TextMessagePart::parseContent()
{
    mSignatureState = KMMsgNotSigned;
    mEncryptionState = KMMsgNotEncrypted;
    const auto blocks = prepareMessageForDecryption(mNode->decodedContent());
    // We also get blocks for unencrypted messages
    if (!blocks.isEmpty()) {
        auto aCodec = QStringDecoder(mOtp->codecNameFor(mNode).constData());
        const auto cryptProto = QGpgME::openpgp();

        /* The (overall) signature/encrypted status is broken
         * if one unencrypted part is at the beginning or in the middle
         * because mailmain adds an unencrypted part at the end this should not break the overall status
         *
         * That's why we first set the tmp status and if one crypted/signed block comes afterwards, than
         * the status is set to unencryped
         */
        bool fullySignedOrEncrypted = true;
        bool fullySignedOrEncryptedTmp = true;

        for (const auto &block : blocks) {
            if (!fullySignedOrEncryptedTmp) {
                fullySignedOrEncrypted = false;
            }

            if (block.type() == NoPgpBlock && !block.text().trimmed().isEmpty()) {
                fullySignedOrEncryptedTmp = false;
                appendSubPart(MessagePart::Ptr(new MessagePart(mOtp, aCodec.decode(KMime::CRLFtoLF(block.text())))));
            } else if (block.type() == PgpMessageBlock) {
                auto content = new KMime::Content;
                content->setBody(block.text());
                content->parse();
                content->contentType()->setCharset(charset());
                EncryptedMessagePart::Ptr mp(new EncryptedMessagePart(mOtp, QString(), cryptProto, content, content, false));
                mp->bindLifetime(content);
                mp->setIsEncrypted(true);
                appendSubPart(mp);
            } else if (block.type() == ClearsignedBlock) {
                auto content = new KMime::Content;
                content->setBody(block.text());
                content->parse();
                content->contentType()->setCharset(charset());
                SignedMessagePart::Ptr mp(new SignedMessagePart(mOtp, cryptProto, nullptr, content, false));
                mp->bindLifetime(content);
                mp->setIsSigned(true);
                appendSubPart(mp);
            } else {
                continue;
            }

            const auto mp = subParts().last().staticCast<MessagePart>();
            const PartMetaData *messagePart(mp->partMetaData());

            if (!messagePart->isEncrypted && !messagePart->isSigned && !block.text().trimmed().isEmpty()) {
                mp->setText(aCodec.decode(KMime::CRLFtoLF(block.text())));
            }

            if (messagePart->isEncrypted) {
                mEncryptionState = KMMsgPartiallyEncrypted;
            }

            if (messagePart->isSigned) {
                mSignatureState = KMMsgPartiallySigned;
            }
        }

        // Do we have an fully Signed/Encrypted Message?
        if (fullySignedOrEncrypted) {
            if (mSignatureState == KMMsgPartiallySigned) {
                mSignatureState = KMMsgFullySigned;
            }
            if (mEncryptionState == KMMsgPartiallyEncrypted) {
                mEncryptionState = KMMsgFullyEncrypted;
            }
        }
    }
}

KMMsgEncryptionState TextMessagePart::encryptionState() const
{
    if (mEncryptionState == KMMsgNotEncrypted) {
        return MessagePart::encryptionState();
    }
    return mEncryptionState;
}

KMMsgSignatureState TextMessagePart::signatureState() const
{
    if (mSignatureState == KMMsgNotSigned) {
        return MessagePart::signatureState();
    }
    return mSignatureState;
}

//-----AttachmentMessageBlock----------------------

AttachmentMessagePart::AttachmentMessagePart(ObjectTreeParser *otp, KMime::Content *node)
    : TextMessagePart(otp, node)
{
}

//-----HtmlMessageBlock----------------------

HtmlMessagePart::HtmlMessagePart(ObjectTreeParser *otp, KMime::Content *node)
    : MessagePart(otp, QString(), node)
{
    if (!mNode) {
        qCWarning(MIMETREEPARSER_CORE_LOG) << "not a valid node";
        return;
    }

    setText(QStringDecoder(mOtp->codecNameFor(mNode).constData()).decode(KMime::CRLFtoLF(mNode->decodedContent())));
}

//-----MimeMessageBlock----------------------

MimeMessagePart::MimeMessagePart(ObjectTreeParser *otp, KMime::Content *node, bool onlyOneMimePart)
    : MessagePart(otp, QString(), node)
{
    if (!mNode) {
        qCWarning(MIMETREEPARSER_CORE_LOG) << "not a valid node";
        return;
    }

    parseInternal(mNode, onlyOneMimePart);
}

MimeMessagePart::~MimeMessagePart()
{
}

QString MimeMessagePart::text() const
{
    return renderInternalText();
}

QString MimeMessagePart::plaintextContent() const
{
    return QString();
}

QString MimeMessagePart::htmlContent() const
{
    return QString();
}

//-----AlternativeMessagePart----------------------

AlternativeMessagePart::AlternativeMessagePart(ObjectTreeParser *otp, KMime::Content *node)
    : MessagePart(otp, QString(), node)
{
    if (auto dataIcal = findTypeInDirectChildren(mNode, "text/calendar")) {
        mChildParts[MultipartIcal] = MimeMessagePart::Ptr(new MimeMessagePart(mOtp, dataIcal, true));
    }

    if (auto dataText = findTypeInDirectChildren(mNode, "text/plain")) {
        mChildParts[MultipartPlain] = MimeMessagePart::Ptr(new MimeMessagePart(mOtp, dataText, true));
    }

    if (auto dataHtml = findTypeInDirectChildren(mNode, "text/html")) {
        mChildParts[MultipartHtml] = MimeMessagePart::Ptr(new MimeMessagePart(mOtp, dataHtml, true));
    } else {
        // 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.
        // In the case of multipart/related we don't expect multiple html parts, it is usually used to group attachments
        // with html content.
        //
        // In any case, this is not a complete implementation of MIME, but an approximation for the kind of mails we actually see in the wild.
        auto data = [&] {
            if (auto d = findTypeInDirectChildren(mNode, "multipart/related")) {
                return d;
            }
            return findTypeInDirectChildren(mNode, "multipart/mixed");
        }();
        if (data) {
            QString htmlContent;
            const auto parts = data->contents();
            for (auto p : parts) {
                if ((!p->contentType()->isEmpty()) && (p->contentType()->mimeType() == "text/html")) {
                    htmlContent += MimeMessagePart(mOtp, p, true).text();
                } else if (KMime::isAttachment(p)) {
                    appendSubPart(MimeMessagePart::Ptr(new MimeMessagePart(otp, p, true)));
                }
            }
            mChildParts[MultipartHtml] = MessagePart::Ptr(new MessagePart(mOtp, htmlContent, nullptr));
        }
    }
}

AlternativeMessagePart::~AlternativeMessagePart()
{
}

QList<AlternativeMessagePart::HtmlMode> AlternativeMessagePart::availableModes()
{
    return mChildParts.keys();
}

QString AlternativeMessagePart::text() const
{
    if (mChildParts.contains(MultipartPlain)) {
        return mChildParts[MultipartPlain]->text();
    }
    return QString();
}

bool AlternativeMessagePart::isHtml() const
{
    return mChildParts.contains(MultipartHtml);
}

QString AlternativeMessagePart::plaintextContent() const
{
    return text();
}

QString AlternativeMessagePart::htmlContent() const
{
    if (mChildParts.contains(MultipartHtml)) {
        return mChildParts[MultipartHtml]->text();
    } else {
        return plaintextContent();
    }
}

QString AlternativeMessagePart::icalContent() const
{
    if (mChildParts.contains(MultipartIcal)) {
        return mChildParts[MultipartIcal]->text();
    }
    return {};
}

//-----CertMessageBlock----------------------

CertMessagePart::CertMessagePart(ObjectTreeParser *otp, KMime::Content *node, QGpgME::Protocol *cryptoProto)
    : MessagePart(otp, QString(), node)
    , mCryptoProto(cryptoProto)
{
    if (!mNode) {
        qCWarning(MIMETREEPARSER_CORE_LOG) << "not a valid node";
        return;
    }
}

CertMessagePart::~CertMessagePart()
{
}

QString CertMessagePart::text() const
{
    return QString();
}

//-----SignedMessageBlock---------------------
SignedMessagePart::SignedMessagePart(ObjectTreeParser *otp,
                                     const QGpgME::Protocol *cryptoProto,
                                     KMime::Content *node,
                                     KMime::Content *signedData,
                                     bool parseAfterDecryption)
    : MessagePart(otp, {}, node)
    , mParseAfterDecryption(parseAfterDecryption)
    , mCryptoProto(cryptoProto)
    , mSignedData(signedData)
{
    mMetaData.isSigned = true;
    mMetaData.isGoodSignature = false;
    mMetaData.status = i18ndc("mimetreeparser", "@info:status", "Wrong Crypto Plug-In.");
}

SignedMessagePart::~SignedMessagePart()
{
}

void SignedMessagePart::setIsSigned(bool isSigned)
{
    mMetaData.isSigned = isSigned;
}

bool SignedMessagePart::isSigned() const
{
    return mMetaData.isSigned;
}

static QString prettifyDN(const char *uid)
{
    // We used to use QGpgME::DN::prettyDN here. But I'm not sure what we actually need it for.
    return QString::fromUtf8(uid);
}

const QGpgME::Protocol *SignedMessagePart::cryptoProto() const
{
    return mCryptoProto;
}

void SignedMessagePart::startVerification()
{
    if (!mSignedData) {
        return;
    }

    mMetaData.isSigned = false;
    mMetaData.status = i18ndc("mimetreeparser", "@info:status", "Wrong Crypto Plug-In.");
    mMetaData.isEncrypted = false;
    mMetaData.isDecryptable = false;

    auto codec = QStringDecoder(mOtp->codecNameFor(mSignedData).constData());

    // If we have a mNode, this is a detached signature
    if (mNode) {
        const auto signature = mNode->decodedContent();

        // This is necessary in case the original data contained CRLF's. Otherwise the signature will not match the data (since KMIME normalizes to LF)
        const QByteArray signedData = KMime::LFtoCRLF(mSignedData->encodedContent());

        const auto job = mCryptoProto->verifyDetachedJob();
        setVerificationResult(job->exec(signature, signedData), signedData);
        job->deleteLater();
        setText(codec.decode(KMime::CRLFtoLF(signedData)));
    } else {
        QByteArray outdata;
        const auto job = mCryptoProto->verifyOpaqueJob();
        setVerificationResult(job->exec(mSignedData->decodedContent(), outdata), outdata);
        job->deleteLater();
        setText(codec.decode(KMime::CRLFtoLF(outdata)));
    }

    if (!mMetaData.isSigned) {
        mMetaData.creationTime = QDateTime();
    }
}

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 SignedMessagePart::sigStatusToMetaData()
{
    GpgME::Key key;
    if (partMetaData()->isSigned) {
        GpgME::Signature signature = mSignatures.front();
        mMetaData.status_code = signatureToStatus(signature);
        mMetaData.isGoodSignature = partMetaData()->status_code == GPGME_SIG_STAT_GOOD;
        // save extended signature status flags
        mMetaData.sigSummary = signature.summary();

        if (partMetaData()->isGoodSignature && !key.keyID()) {
            // Search for the key by its fingerprint so that we can check for
            // trust etc.
            key = Kleo::KeyCache::instance()->findByFingerprint(signature.fingerprint());
            if (key.isNull() && signature.fingerprint()) {
                // try to find a subkey that was used for signing;
                // assumes that the key ID is the last 16 characters of the fingerprint
                const auto fpr = std::string_view{signature.fingerprint()};
                const auto keyID = std::string{fpr, fpr.size() - 16, 16};
                const auto subkeys = Kleo::KeyCache::instance()->findSubkeysByKeyID({keyID});
                if (subkeys.size() > 0) {
                    key = subkeys[0].parent();
                }
            }
            if (key.isNull()) {
                qCDebug(MIMETREEPARSER_CORE_LOG) << "Found no key or subkey for fingerprint" << signature.fingerprint();
            }
        }

        if (key.keyID()) {
            partMetaData()->keyId = key.keyID();
        }
        if (partMetaData()->keyId.isEmpty()) {
            partMetaData()->keyId = signature.fingerprint();
        }
        partMetaData()->keyTrust = signature.validity();
        if (key.numUserIDs() > 0 && key.userID(0).id()) {
            partMetaData()->signer = prettifyDN(key.userID(0).id());
        }
        for (const auto &uid : key.userIDs()) {
            // The following if /should/ always result in TRUE but we
            // won't trust implicitly the plugin that gave us these data.
            if (uid.email()) {
                QString email = QString::fromUtf8(uid.email());
                if (!email.isEmpty()) {
                    partMetaData()->signerMailAddresses.append(email);
                }
            }
        }

        if (signature.creationTime()) {
            partMetaData()->creationTime.setSecsSinceEpoch(signature.creationTime());
        } else {
            partMetaData()->creationTime = QDateTime();
        }
        if (partMetaData()->signer.isEmpty()) {
            if (key.numUserIDs() > 0 && key.userID(0).name()) {
                partMetaData()->signer = prettifyDN(key.userID(0).name());
            }
            if (!partMetaData()->signerMailAddresses.empty()) {
                if (partMetaData()->signer.isEmpty()) {
                    partMetaData()->signer = partMetaData()->signerMailAddresses.front();
                } else {
                    partMetaData()->signer += QLatin1StringView(" <") + partMetaData()->signerMailAddresses.front() + QLatin1Char('>');
                }
            }
        }
        if (Kleo::DeVSCompliance::isCompliant()) {
            partMetaData()->isCompliant = signature.isDeVs();
            partMetaData()->compliance = Kleo::DeVSCompliance::name(signature.isDeVs());
        } else {
            partMetaData()->isCompliant = true;
        }
    }
}

void SignedMessagePart::setVerificationResult(const GpgME::VerificationResult &result, const QByteArray &signedData)
{
    mSignatures = result.signatures();
    // FIXME
    // mMetaData.auditLogError = result.error;
    mMetaData.isSigned = !mSignatures.empty();
    if (mMetaData.isSigned) {
        sigStatusToMetaData();
        if (!signedData.isEmpty() && mParseAfterDecryption) {
            parseInternal(signedData);
        }
    }
}

QString SignedMessagePart::plaintextContent() const
{
    if (!mNode) {
        return MessagePart::text();
    } else {
        return QString();
    }
}

QString SignedMessagePart::htmlContent() const
{
    if (!mNode) {
        return MessagePart::text();
    } else {
        return QString();
    }
}

//-----CryptMessageBlock---------------------
EncryptedMessagePart::EncryptedMessagePart(ObjectTreeParser *otp,
                                           const QString &text,
                                           const QGpgME::Protocol *cryptoProto,
                                           KMime::Content *node,
                                           KMime::Content *encryptedNode,
                                           bool parseAfterDecryption)
    : MessagePart(otp, text, node)
    , mParseAfterDecryption(parseAfterDecryption)
    , mPassphraseError(false)
    , mNoSecKey(false)
    , mDecryptMessage(false)
    , mCryptoProto(cryptoProto)
    , mEncryptedNode(encryptedNode)
{
    mMetaData.isSigned = false;
    mMetaData.isGoodSignature = false;
    mMetaData.isEncrypted = false;
    mMetaData.isDecryptable = false;
    mMetaData.status = i18ndc("mimetreeparser", "@info:status", "Wrong Crypto Plug-In.");
}

void EncryptedMessagePart::setIsEncrypted(bool encrypted)
{
    mMetaData.isEncrypted = encrypted;
}

bool EncryptedMessagePart::isEncrypted() const
{
    return mMetaData.isEncrypted;
}

const QGpgME::Protocol *EncryptedMessagePart::cryptoProto() const
{
    return mCryptoProto;
}

void EncryptedMessagePart::setDecryptMessage(bool decrypt)
{
    mDecryptMessage = decrypt;
}

bool EncryptedMessagePart::decryptMessage() const
{
    return mDecryptMessage;
}

bool EncryptedMessagePart::isDecryptable() const
{
    return mMetaData.isDecryptable;
}

bool EncryptedMessagePart::isNoSecKey() const
{
    return mNoSecKey;
}

bool EncryptedMessagePart::passphraseError() const
{
    return mPassphraseError;
}

bool EncryptedMessagePart::decrypt(KMime::Content &data)
{
    mError = NoError;
    mMetaData.errorText.clear();
    // FIXME
    //  mMetaData.auditLogError = GpgME::Error();
    mMetaData.auditLog.clear();

    const QByteArray ciphertext = data.decodedContent();
    QByteArray plainText;
    auto job = mCryptoProto->decryptVerifyJob();
    const std::pair<GpgME::DecryptionResult, GpgME::VerificationResult> p = job->exec(ciphertext, plainText);
    job->deleteLater();
    auto decryptResult = p.first;
    auto verifyResult = p.second;
    mMetaData.isSigned = verifyResult.signatures().size() > 0;

    // Normalize CRLF's
    plainText = KMime::CRLFtoLF(plainText);
    auto codec = QStringDecoder(mOtp->codecNameFor(&data).constData());
    const auto decoded = codec.decode(plainText);

    partMetaData()->isSigned = verifyResult.signatures().size() > 0;

    if (partMetaData()->isSigned) {
        // We simply attach a signed message part to indicate that this content is also signed
        // We're forwarding mNode to not loose the encoding information
        auto subPart = SignedMessagePart::Ptr(new SignedMessagePart(mOtp, mCryptoProto, mNode, nullptr));
        subPart->setText(decoded);
        subPart->setVerificationResult(verifyResult, plainText);
        appendSubPart(subPart);
    }

    mDecryptRecipients.clear();
    bool cannotDecrypt = false;
    bool bDecryptionOk = !decryptResult.error();

    for (const auto &recipient : decryptResult.recipients()) {
        if (!recipient.status()) {
            bDecryptionOk = true;
        }
        GpgME::Key key;
        key = Kleo::KeyCache::instance()->findByKeyIDOrFingerprint(recipient.keyID());
        if (key.isNull()) {
            auto ret = Kleo::KeyCache::instance()->findSubkeysByKeyID({recipient.keyID()});
            if (ret.size() == 1) {
                key = ret.front().parent();
            }
            if (key.isNull()) {
                qCDebug(MIMETREEPARSER_CORE_LOG) << "Found no Key for KeyID " << recipient.keyID();
            }
        }
        mDecryptRecipients.emplace_back(recipient, key);
    }

    if (!bDecryptionOk && partMetaData()->isSigned) {
        // Only a signed part
        partMetaData()->isEncrypted = false;
        bDecryptionOk = true;
        mDecryptedData = plainText;
    } else {
        mPassphraseError = decryptResult.error().isCanceled() || decryptResult.error().code() == GPG_ERR_BAD_PASSPHRASE;
        mMetaData.isEncrypted = bDecryptionOk || decryptResult.error().code() != GPG_ERR_NO_DATA;

        if (decryptResult.error().isCanceled()) {
            setDecryptMessage(false);
        }

        partMetaData()->errorText = QString::fromLocal8Bit(decryptResult.error().asString());
        if (Kleo::DeVSCompliance::isCompliant()) {
            partMetaData()->isCompliant = decryptResult.isDeVs();
            partMetaData()->compliance = Kleo::DeVSCompliance::name(decryptResult.isDeVs());
        } else {
            partMetaData()->isCompliant = true;
        }
        if (partMetaData()->isEncrypted && decryptResult.numRecipients() > 0) {
            partMetaData()->keyId = decryptResult.recipient(0).keyID();
        }

        if (bDecryptionOk) {
            mDecryptedData = plainText;
        } else {
            mNoSecKey = true;
            const auto decryRecipients = decryptResult.recipients();
            for (const GpgME::DecryptionResult::Recipient &recipient : decryRecipients) {
                mNoSecKey &= (recipient.status().code() == GPG_ERR_NO_SECKEY);
            }
            if (!mPassphraseError && !mNoSecKey) { // GpgME do not detect passphrase error correctly
                mPassphraseError = true;
            }
        }
    }

    if (!bDecryptionOk) {
        QString cryptPlugLibName;
        mError = UnknownError;
        if (mCryptoProto) {
            cryptPlugLibName = mCryptoProto->name();
        }

        if (mNoSecKey) {
            mError = NoKeyError;
        }

        if (mPassphraseError) {
            mError = PassphraseError;
        }

        if (!mCryptoProto) {
            partMetaData()->errorText = i18n("No appropriate crypto plug-in was found.");
        } else if (cannotDecrypt) {
            partMetaData()->errorText = i18n("Crypto plug-in \"%1\" cannot decrypt messages.", cryptPlugLibName);
        } else if (!passphraseError()) {
            partMetaData()->errorText = i18n("Crypto plug-in \"%1\" could not decrypt the data.", cryptPlugLibName) + QLatin1StringView("<br />")
                + i18n("Error: %1", partMetaData()->errorText);
        }
    }
    return bDecryptionOk;
}

void EncryptedMessagePart::startDecryption(KMime::Content *data)
{
    mMetaData.isEncrypted = true;
    mMetaData.isDecryptable = decrypt(*data);

    if (mParseAfterDecryption && !mMetaData.isSigned) {
        parseInternal(mDecryptedData);
    } else {
        setText(QString::fromUtf8(mDecryptedData.constData()));
    }
}

void EncryptedMessagePart::startDecryption()
{
    if (mEncryptedNode) {
        startDecryption(mEncryptedNode);
    } else {
        startDecryption(mNode);
    }
}

std::vector<std::pair<GpgME::DecryptionResult::Recipient, GpgME::Key>> EncryptedMessagePart::decryptRecipients() const
{
    return mDecryptRecipients;
}

QString EncryptedMessagePart::plaintextContent() const
{
    if (!mNode) {
        return MessagePart::text();
    } else {
        return QString();
    }
}

QString EncryptedMessagePart::htmlContent() const
{
    if (!mNode) {
        return MessagePart::text();
    } else {
        return QString();
    }
}

QString EncryptedMessagePart::text() const
{
    if (hasSubParts()) {
        auto _mp = (subParts()[0]).dynamicCast<SignedMessagePart>();
        if (_mp) {
            return _mp->text();
        }
    }

    return MessagePart::text();
}

EncapsulatedRfc822MessagePart::EncapsulatedRfc822MessagePart(ObjectTreeParser *otp, KMime::Content *node, const KMime::Message::Ptr &message)
    : MessagePart(otp, QString(), node)
    , mMessage(message)
{
    mMetaData.isEncrypted = false;
    mMetaData.isSigned = false;
    mMetaData.isEncapsulatedRfc822Message = true;

    if (!mMessage) {
        qCWarning(MIMETREEPARSER_CORE_LOG) << "Node is of type message/rfc822 but doesn't have a message!";
        return;
    }

    parseInternal(message.data());
}

QString EncapsulatedRfc822MessagePart::text() const
{
    return renderInternalText();
}

QString EncapsulatedRfc822MessagePart::from() const
{
    if (auto from = mMessage->from(false)) {
        return from->asUnicodeString();
    }
    return {};
}

QDateTime EncapsulatedRfc822MessagePart::date() const
{
    if (auto date = mMessage->date(false)) {
        return date->dateTime();
    }
    return {};
}

HeadersPart::HeadersPart(ObjectTreeParser *otp, KMime::Content *node)
    : MessagePart(otp, QString(), node)
{
}

#include "moc_messagepart.cpp"
