// SPDX-FileCopyrightText: 2009 Constantin Berzan <exit3219@gmail.com>
// SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
// SPDX-FileCopyrightText: 2010 Leo Franchi <lfranchi@kde.org>
// SPDX-FileCopyrightText: 2017 Christian Mollekopf <mollekopf@kolabsys.com>
// SPDX-License-Identifier: LGPL-2.0-or-later
//
#include "mailcrypto.h"

#include <QDebug>

#include <future>
#include <utility>

using namespace MailCrypto;
using namespace Crypto;

#ifdef _WIN32
Expected<Error, std::unique_ptr<KMime::Content>>
MailCrypto::processCrypto(std::unique_ptr<KMime::Content> content, const std::vector<Key> &signingKeys, const std::vector<Key> &encryptionKeys)
{
    Q_UNUSED(content)
    Q_UNUSED(signingKeys)
    Q_UNUSED(encryptionKeys)
    return makeUnexpected(Error{});
}
#else
static QByteArray canonicalizeContent(KMime::Content *content)
{
    // if (d->format & Kleo::InlineOpenPGPFormat) {
    //     return d->content->body();
    // } else if (!(d->format & Kleo::SMIMEOpaqueFormat)) {

    // replace "From " and "--" at the beginning of lines
    // with encoded versions according to RfC 3156, 3
    // Note: If any line begins with the string "From ", it is strongly
    //   suggested that either the Quoted-Printable or Base64 MIME encoding
    //   be applied.
    const auto encoding = content->contentTransferEncoding()->encoding();
    if ((encoding == KMime::Headers::CEquPr || encoding == KMime::Headers::CE7Bit) && !content->contentType(false)) {
        QByteArray body = content->encodedBody();
        bool changed = false;
        QList<QByteArray> search;
        QList<QByteArray> replacements;

        search << "From "
               << "from "
               << "-";
        replacements << "From=20"
                     << "from=20"
                     << "=2D";

        if (content->contentTransferEncoding()->encoding() == KMime::Headers::CE7Bit) {
            for (int i = 0; i < search.size(); ++i) {
                const auto pos = body.indexOf(search[i]);
                if (pos == 0 || (pos > 0 && body.at(pos - 1) == '\n')) {
                    changed = true;
                    break;
                }
            }
            if (changed) {
                content->contentTransferEncoding()->setEncoding(KMime::Headers::CEquPr);
                content->assemble();
                body = content->encodedBody();
            }
        }

        for (int i = 0; i < search.size(); ++i) {
            const auto pos = body.indexOf(search[i]);
            if (pos == 0 || (pos > 0 && body.at(pos - 1) == '\n')) {
                changed = true;
                body.replace(pos, search[i].size(), replacements[i]);
            }
        }

        if (changed) {
            qDebug() << "Content changed";
            content->setBody(body);
            content->contentTransferEncoding()->setDecoded(false);
        }
    }

    return KMime::LFtoCRLF(content->encodedContent());
    // } else {                    // SMimeOpaque doesn't need LFtoCRLF, else it gets munged
    //     return content->encodedContent();
    // }
}

/**
 * Create a message part like this (according to RFC 3156 Section 4):
 *
 * - multipart/encrypted
 *   - application/pgp-encrypted (version information)
 *   - application/octet-stream (given encrypted data)
 *
 * The encrypted data can be generated by the `encrypt` or `signAndEncrypt` functions.
 */
std::unique_ptr<KMime::Content> createEncryptedPart(QByteArray encryptedData)
{
    auto result = std::unique_ptr<KMime::Content>(new KMime::Content);

    result->contentType()->setMimeType("multipart/encrypted");
    result->contentType()->setBoundary(KMime::multiPartBoundary());
    result->contentType()->setParameter(QStringLiteral("protocol"), QStringLiteral("application/pgp-encrypted"));

    KMime::Content *controlInformation = new KMime::Content;
    {
        controlInformation->contentType()->setMimeType("application/pgp-encrypted");
        controlInformation->contentDescription()->from7BitString("PGP/MIME version identification");
        controlInformation->setBody("Version: 1");

        result->addContent(controlInformation);
    }

    KMime::Content *encryptedPartPart = new KMime::Content;
    {
        const QString filename = QStringLiteral("msg.asc");

        encryptedPartPart->contentType()->setMimeType("application/octet-stream");
        encryptedPartPart->contentType()->setName(filename, "utf-8");

        encryptedPartPart->contentDescription()->from7BitString("OpenPGP encrypted message");

        encryptedPartPart->contentDisposition()->setDisposition(KMime::Headers::CDinline);
        encryptedPartPart->contentDisposition()->setFilename(filename);

        encryptedPartPart->setBody(encryptedData);

        result->addContent(encryptedPartPart);
    }

    return result;
}

/**
 * Create a message part like this (according to RFC 3156 Section 5):
 *
 * + `multipart/signed`
 *   - whatever the given original `message` is (should be canonicalized)
 *   - `application/octet-stream` (the given `signature`)
 *
 * The signature can be generated by the `sign` function.
 */
std::unique_ptr<KMime::Content> createSignedPart(std::unique_ptr<KMime::Content> message, const QByteArray &signature, const QString &micAlg)
{
    auto result = std::unique_ptr<KMime::Content>(new KMime::Content);

    result->contentType()->setMimeType("multipart/signed");
    result->contentType()->setBoundary(KMime::multiPartBoundary());
    result->contentType()->setParameter(QStringLiteral("micalg"), micAlg);
    result->contentType()->setParameter(QStringLiteral("protocol"), QStringLiteral("application/pgp-signature"));

    result->addContent(message.release());

    KMime::Content *signedPartPart = new KMime::Content;
    signedPartPart->contentType()->setMimeType("application/pgp-signature");
    signedPartPart->contentType()->setName(QStringLiteral("signature.asc"), "utf-8");
    signedPartPart->contentDisposition(true)->setDisposition(KMime::Headers::CDattachment);
    signedPartPart->contentDisposition(true)->setFilename(QStringLiteral("signature.asc"));
    signedPartPart->contentDescription()->from7BitString("OpenPGP digital signature");
    signedPartPart->setBody(signature);
    result->addContent(signedPartPart);

    return result;
}

Expected<Error, std::unique_ptr<KMime::Content>>
MailCrypto::processCrypto(std::unique_ptr<KMime::Content> content, const std::vector<Key> &signingKeys, const std::vector<Key> &encryptionKeys)
{
    if (!encryptionKeys.empty()) {
        auto encryptionResult = signAndEncrypt(canonicalizeContent(content.get()), encryptionKeys, signingKeys);
        if (!encryptionResult) {
            return makeUnexpected(Error{encryptionResult.error()});
        }
        return createEncryptedPart(encryptionResult.value());
    } else if (!signingKeys.empty()) {
        auto signingResult = sign(canonicalizeContent(content.get()), signingKeys);
        if (!signingResult) {
            return makeUnexpected(Error{signingResult.error()});
        }
        QByteArray signingData;
        QString micAlg;
        std::tie(signingData, micAlg) = signingResult.value();
        return createSignedPart(std::move(content), signingData, micAlg);
    } else {
        qWarning() << "Processing cryptography, but neither signing nor encrypting";
        Q_ASSERT(false);
        return content;
    }
}
#endif
