/*  -*- c++ -*-
    keyresolver.cpp

    This file is part of libkleopatra, the KDE keymanagement library
    SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB

    Based on kpgp.cpp
    Copyright (C) 2001,2002 the KPGP authors
    See file libkdenetwork/AUTHORS.kpgp for details

    SPDX-License-Identifier: GPL-2.0-or-later
*/

#include "composer/keyresolver.h"
#include "job/savecontactpreferencejob.h"

#ifndef QT_NO_CURSOR
#include <Libkdepim/KCursorSaver>
#endif
#include "utils/kleo_util.h"

#include <KEmailAddress>
#include <Libkleo/KeySelectionDialog>
#include <Libkleo/Dn>

#include <QGpgME/Protocol>
#include <QGpgME/KeyListJob>

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

#include <Akonadi/Contact/ContactSearchJob>
#include <KLocalizedString>
#include "messagecomposer_debug.h"
#include <KMessageBox>

#include <QStringList>
#include <QPointer>

#include <algorithm>
#include <cassert>
#include <ctime>
#include <functional>
#include <iostream>
#include <iterator>
#include <map>
#include <memory>
#include <set>

// this should go into stl_util.h, which has since moved into messageviewer.
// for lack of a better place put it in here for now.
namespace kdtools {
template<typename Iterator, typename UnaryPredicate>
bool any(Iterator first, Iterator last, UnaryPredicate p)
{
    while (first != last) {
        if (p(*first)) {
            return true;
        } else {
            ++first;
        }
    }
    return false;
}
} // namespace kdtools

//
// some predicates to be used in STL algorithms:
//

static inline bool EmptyKeyList(const Kleo::KeyApprovalDialog::Item &item)
{
    return item.keys.empty();
}

static inline QString ItemDotAddress(const Kleo::KeyResolver::Item &item)
{
    return item.address;
}

static inline bool ApprovalNeeded(const Kleo::KeyResolver::Item &item)
{
    return item.pref == Kleo::UnknownPreference || item.pref == Kleo::NeverEncrypt || item.keys.empty();
}

static inline Kleo::KeyResolver::Item
CopyKeysAndEncryptionPreferences(const Kleo::KeyResolver::Item &oldItem, const Kleo::KeyApprovalDialog::Item &newItem)
{
    return Kleo::KeyResolver::Item(oldItem.address, newItem.keys, newItem.pref, oldItem.signPref, oldItem.format);
}

static bool ValidOpenPGPEncryptionKey(const GpgME::Key &key)
{
    if (key.protocol() != GpgME::OpenPGP) {
        return false;
    }
    if (key.isRevoked()) {
        qCWarning(MESSAGECOMPOSER_LOG) << "is revoked";
    }
    if (key.isExpired()) {
        qCWarning(MESSAGECOMPOSER_LOG) << "is expired";
    }
    if (key.isDisabled()) {
        qCWarning(MESSAGECOMPOSER_LOG) << "is disabled";
    }
    if (!key.canEncrypt()) {
        qCWarning(MESSAGECOMPOSER_LOG) << "can't encrypt";
    }
    if (key.isRevoked() || key.isExpired() || key.isDisabled() || !key.canEncrypt()) {
        return false;
    }
    return true;
}

static bool ValidTrustedOpenPGPEncryptionKey(const GpgME::Key &key)
{
    if (!ValidOpenPGPEncryptionKey(key)) {
        return false;
    }
    const std::vector<GpgME::UserID> uids = key.userIDs();
    std::vector<GpgME::UserID>::const_iterator end(uids.end());
    for (std::vector<GpgME::UserID>::const_iterator it = uids.begin(); it != end; ++it) {
        if (!it->isRevoked() && it->validity() >= GpgME::UserID::Marginal) {
            return true;
        } else if (it->isRevoked()) {
            qCWarning(MESSAGECOMPOSER_LOG) << "a userid is revoked";
        } else {
            qCWarning(MESSAGECOMPOSER_LOG) << "bad validity" << int(it->validity());
        }
    }
    return false;
}

static bool ValidSMIMEEncryptionKey(const GpgME::Key &key)
{
    if (key.protocol() != GpgME::CMS) {
        return false;
    }
    if (key.isRevoked() || key.isExpired() || key.isDisabled() || !key.canEncrypt()) {
        return false;
    }
    return true;
}

static bool ValidTrustedSMIMEEncryptionKey(const GpgME::Key &key)
{
    if (!ValidSMIMEEncryptionKey(key)) {
        return false;
    }
    return true;
}

static inline bool ValidTrustedEncryptionKey(const GpgME::Key &key)
{
    switch (key.protocol()) {
    case GpgME::OpenPGP:
        return ValidTrustedOpenPGPEncryptionKey(key);
    case GpgME::CMS:
        return ValidTrustedSMIMEEncryptionKey(key);
    default:
        return false;
    }
}

static inline bool ValidEncryptionKey(const GpgME::Key &key)
{
    switch (key.protocol()) {
    case GpgME::OpenPGP:
        return ValidOpenPGPEncryptionKey(key);
    case GpgME::CMS:
        return ValidSMIMEEncryptionKey(key);
    default:
        return false;
    }
}

static inline bool ValidSigningKey(const GpgME::Key &key)
{
    if (key.isRevoked() || key.isExpired() || key.isDisabled() || !key.canSign()) {
        return false;
    }
    return key.hasSecret();
}

static inline bool ValidOpenPGPSigningKey(const GpgME::Key &key)
{
    return key.protocol() == GpgME::OpenPGP && ValidSigningKey(key);
}

static inline bool ValidSMIMESigningKey(const GpgME::Key &key)
{
    return key.protocol() == GpgME::CMS && ValidSigningKey(key);
}

static inline bool NotValidTrustedOpenPGPEncryptionKey(const GpgME::Key &key)
{
    return !ValidTrustedOpenPGPEncryptionKey(key);
}

static inline bool NotValidTrustedSMIMEEncryptionKey(const GpgME::Key &key)
{
    return !ValidTrustedSMIMEEncryptionKey(key);
}

static inline bool NotValidTrustedEncryptionKey(const GpgME::Key &key)
{
    return !ValidTrustedEncryptionKey(key);
}

static inline bool NotValidEncryptionKey(const GpgME::Key &key)
{
    return !ValidEncryptionKey(key);
}

static inline bool NotValidOpenPGPSigningKey(const GpgME::Key &key)
{
    return !ValidOpenPGPSigningKey(key);
}

static inline bool NotValidSMIMESigningKey(const GpgME::Key &key)
{
    return !ValidSMIMESigningKey(key);
}

namespace {
struct ByTrustScore {
    static int score(const GpgME::UserID &uid)
    {
        return uid.isRevoked() || uid.isInvalid() ? -1 : uid.validity();
    }

    bool operator()(const GpgME::UserID &lhs, const GpgME::UserID &rhs) const
    {
        return score(lhs) < score(rhs);
    }
};
}

static std::vector<GpgME::UserID> matchingUIDs(const std::vector<GpgME::UserID> &uids, const QString &address)
{
    if (address.isEmpty()) {
        return std::vector<GpgME::UserID>();
    }

    std::vector<GpgME::UserID> result;
    result.reserve(uids.size());

    for (std::vector<GpgME::UserID>::const_iterator it = uids.begin(), end = uids.end(); it != end; ++it) {
        // PENDING(marc) check DN for an EMAIL, too, in case of X.509 certs... :/
        if (const char *email = it->email()) {
            if (*email && QString::fromUtf8(email).simplified().toLower() == address) {
                result.push_back(*it);
            }
        }
    }
    return result;
}

static GpgME::UserID findBestMatchUID(const GpgME::Key &key, const QString &address)
{
    const std::vector<GpgME::UserID> all = key.userIDs();
    if (all.empty()) {
        return GpgME::UserID();
    }
    const std::vector<GpgME::UserID> matching = matchingUIDs(all, address.toLower());
    const std::vector<GpgME::UserID> &v = matching.empty() ? all : matching;
    return *std::max_element(v.begin(), v.end(), ByTrustScore());
}

static QStringList keysAsStrings(const std::vector<GpgME::Key> &keys)
{
    QStringList strings;
    strings.reserve(keys.size());
    for (std::vector<GpgME::Key>::const_iterator it = keys.begin(); it != keys.end(); ++it) {
        assert(!(*it).userID(0).isNull());
        const auto userID = (*it).userID(0);
        QString keyLabel = QString::fromUtf8(userID.email());
        if (keyLabel.isEmpty()) {
            keyLabel = QString::fromUtf8(userID.name());
        }
        if (keyLabel.isEmpty()) {
            keyLabel = QString::fromUtf8(userID.id());
        }
        strings.append(keyLabel);
    }
    return strings;
}

static std::vector<GpgME::Key> trustedOrConfirmed(const std::vector<GpgME::Key> &keys, const QString &address, bool &canceled)
{
    // PENDING(marc) work on UserIDs here?
    std::vector<GpgME::Key> fishies;
    std::vector<GpgME::Key> ickies;
    std::vector<GpgME::Key> rewookies;
    std::vector<GpgME::Key>::const_iterator it = keys.begin();
    const std::vector<GpgME::Key>::const_iterator end = keys.end();
    for (; it != end; ++it) {
        const GpgME::Key &key = *it;
        assert(ValidEncryptionKey(key));
        const GpgME::UserID uid = findBestMatchUID(key, address);
        if (uid.isRevoked()) {
            rewookies.push_back(key);
        }
        if (!uid.isRevoked() && uid.validity() == GpgME::UserID::Marginal) {
            fishies.push_back(key);
        }
        if (!uid.isRevoked() && uid.validity() < GpgME::UserID::Never) {
            ickies.push_back(key);
        }
    }

    if (fishies.empty() && ickies.empty() && rewookies.empty()) {
        return keys;
    }

    // if  some keys are not fully trusted, let the user confirm their use
    QString msg = address.isEmpty()
                  ? i18n("One or more of your configured OpenPGP encryption "
                         "keys or S/MIME certificates is not fully trusted "
                         "for encryption.")
                  : i18n("One or more of the OpenPGP encryption keys or S/MIME "
                         "certificates for recipient \"%1\" is not fully trusted "
                         "for encryption.", address);

    if (!fishies.empty()) {
        // certificates can't have marginal trust
        msg += i18n("\nThe following keys are only marginally trusted: \n");
        msg += keysAsStrings(fishies).join(QLatin1Char(','));
    }
    if (!ickies.empty()) {
        msg += i18n("\nThe following keys or certificates have unknown trust level: \n");
        msg += keysAsStrings(ickies).join(QLatin1Char(','));
    }
    if (!rewookies.empty()) {
        msg += i18n("\nThe following keys or certificates are <b>revoked</b>: \n");
        msg += keysAsStrings(rewookies).join(QLatin1Char(','));
    }

    if (KMessageBox::warningContinueCancel(nullptr, msg, i18n("Not Fully Trusted Encryption Keys"),
                                           KStandardGuiItem::cont(), KStandardGuiItem::cancel(),
                                           QStringLiteral("not fully trusted encryption key warning"))
        == KMessageBox::Continue) {
        return keys;
    } else {
        canceled = true;
    }
    return std::vector<GpgME::Key>();
}

namespace {
struct IsNotForFormat : public std::unary_function<GpgME::Key, bool> {
    IsNotForFormat(Kleo::CryptoMessageFormat f) : format(f)
    {
    }

    bool operator()(const GpgME::Key &key) const
    {
        return
            (isOpenPGP(format) && key.protocol() != GpgME::OpenPGP)
            || (isSMIME(format) && key.protocol() != GpgME::CMS);
    }

    const Kleo::CryptoMessageFormat format;
};

struct IsForFormat : std::unary_function<GpgME::Key, bool> {
    explicit IsForFormat(Kleo::CryptoMessageFormat f)
        : protocol(isOpenPGP(f) ? GpgME::OpenPGP
                   : isSMIME(f) ? GpgME::CMS
                   : GpgME::UnknownProtocol)
    {
    }

    bool operator()(const GpgME::Key &key) const
    {
        return key.protocol() == protocol;
    }

    const GpgME::Protocol protocol;
};
}

class Kleo::KeyResolver::SigningPreferenceCounter : public std::unary_function<Kleo::KeyResolver::Item, void>
{
public:
    SigningPreferenceCounter()
    {
    }

    void operator()(const Kleo::KeyResolver::Item &item);
#define make_int_accessor(x) unsigned int num ## x() const { return m ## x; }
    make_int_accessor(UnknownSigningPreference)
    make_int_accessor(NeverSign)
    make_int_accessor(AlwaysSign)
    make_int_accessor(AlwaysSignIfPossible)
    make_int_accessor(AlwaysAskForSigning)
    make_int_accessor(AskSigningWheneverPossible)
    make_int_accessor(Total)
#undef make_int_accessor
private:
    unsigned int mTotal = 0;
    unsigned int mUnknownSigningPreference = 0;
    unsigned int mNeverSign = 0;
    unsigned int mAlwaysSign = 0;
    unsigned int mAlwaysSignIfPossible = 0;
    unsigned int mAlwaysAskForSigning = 0;
    unsigned int mAskSigningWheneverPossible = 0;
};

void Kleo::KeyResolver::SigningPreferenceCounter::operator()(const Kleo::KeyResolver::Item &item)
{
    switch (item.signPref) {
#define CASE(x) case x: \
    ++m ## x; break
        CASE(UnknownSigningPreference);
        CASE(NeverSign);
        CASE(AlwaysSign);
        CASE(AlwaysSignIfPossible);
        CASE(AlwaysAskForSigning);
        CASE(AskSigningWheneverPossible);
#undef CASE
    }
    ++mTotal;
}

class Kleo::KeyResolver::EncryptionPreferenceCounter : public std::unary_function<Item, void>
{
    const Kleo::KeyResolver *_this;
public:
    EncryptionPreferenceCounter(const Kleo::KeyResolver *kr, EncryptionPreference defaultPreference)
        : _this(kr)
        , mDefaultPreference(defaultPreference)
    {
    }

    void operator()(Item &item);

    template<typename Container>
    void process(Container &c)
    {
        *this = std::for_each(c.begin(), c.end(), *this);
    }

#define make_int_accessor(x) unsigned int num ## x() const { return m ## x; }
    make_int_accessor(NoKey)
    make_int_accessor(NeverEncrypt)
    make_int_accessor(UnknownPreference)
    make_int_accessor(AlwaysEncrypt)
    make_int_accessor(AlwaysEncryptIfPossible)
    make_int_accessor(AlwaysAskForEncryption)
    make_int_accessor(AskWheneverPossible)
    make_int_accessor(Total)
#undef make_int_accessor
private:
    EncryptionPreference mDefaultPreference;
    unsigned int mTotal = 0;
    unsigned int mNoKey = 0;
    unsigned int mNeverEncrypt = 0;
    unsigned int mUnknownPreference = 0;
    unsigned int mAlwaysEncrypt = 0;
    unsigned int mAlwaysEncryptIfPossible = 0;
    unsigned int mAlwaysAskForEncryption = 0;
    unsigned int mAskWheneverPossible = 0;
};

void Kleo::KeyResolver::EncryptionPreferenceCounter::operator()(Item &item)
{
    if (_this) {
        if (item.needKeys) {
            item.keys = _this->getEncryptionKeys(item.address, true);
        }
        if (item.keys.empty()) {
            ++mNoKey;
            return;
        }
    }
    switch (!item.pref ? mDefaultPreference : item.pref) {
#define CASE(x) case Kleo::x: \
    ++m ## x; break
        CASE(NeverEncrypt);
        CASE(UnknownPreference);
        CASE(AlwaysEncrypt);
        CASE(AlwaysEncryptIfPossible);
        CASE(AlwaysAskForEncryption);
        CASE(AskWheneverPossible);
#undef CASE
    }
    ++mTotal;
}

namespace {
class FormatPreferenceCounterBase : public std::unary_function<Kleo::KeyResolver::Item, void>
{
public:
    FormatPreferenceCounterBase()
    {
    }

#define make_int_accessor(x) unsigned int num ## x() const { return m ## x; \
}
    make_int_accessor(Total)
    make_int_accessor(InlineOpenPGP)
    make_int_accessor(OpenPGPMIME)
    make_int_accessor(SMIME)
    make_int_accessor(SMIMEOpaque)
#undef make_int_accessor

    unsigned int numOf(Kleo::CryptoMessageFormat f) const
    {
        switch (f) {
#define CASE(x) case Kleo::x ## Format: \
    return m ## x
            CASE(InlineOpenPGP);
            CASE(OpenPGPMIME);
            CASE(SMIME);
            CASE(SMIMEOpaque);
#undef CASE
        default:
            return 0;
        }
    }

protected:
    unsigned int mTotal = 0;
    unsigned int mInlineOpenPGP = 0;
    unsigned int mOpenPGPMIME = 0;
    unsigned int mSMIME = 0;
    unsigned int mSMIMEOpaque = 0;
};

class EncryptionFormatPreferenceCounter : public FormatPreferenceCounterBase
{
public:
    EncryptionFormatPreferenceCounter() : FormatPreferenceCounterBase()
    {
    }

    void operator()(const Kleo::KeyResolver::Item &item);
};

class SigningFormatPreferenceCounter : public FormatPreferenceCounterBase
{
public:
    SigningFormatPreferenceCounter() : FormatPreferenceCounterBase()
    {
    }

    void operator()(const Kleo::KeyResolver::Item &item);
};

#define CASE(x) if (item.format & Kleo::x ## Format) {++m ## x;}
void EncryptionFormatPreferenceCounter::operator()(const Kleo::KeyResolver::Item &item)
{
    if (item.format & (Kleo::InlineOpenPGPFormat | Kleo::OpenPGPMIMEFormat)
        && std::find_if(item.keys.begin(), item.keys.end(),
                        ValidTrustedOpenPGPEncryptionKey) != item.keys.end()) {     // -= trusted?
        CASE(OpenPGPMIME);
        CASE(InlineOpenPGP);
    }
    if (item.format & (Kleo::SMIMEFormat | Kleo::SMIMEOpaqueFormat)
        && std::find_if(item.keys.begin(), item.keys.end(),
                        ValidTrustedSMIMEEncryptionKey) != item.keys.end()) {       // -= trusted?
        CASE(SMIME);
        CASE(SMIMEOpaque);
    }
    ++mTotal;
}

void SigningFormatPreferenceCounter::operator()(const Kleo::KeyResolver::Item &item)
{
    CASE(InlineOpenPGP);
    CASE(OpenPGPMIME);
    CASE(SMIME);
    CASE(SMIMEOpaque);
    ++mTotal;
}

#undef CASE
} // anon namespace

static QString canonicalAddress(const QString &_address)
{
    const QString address = KEmailAddress::extractEmailAddress(_address);
    if (!address.contains(QLatin1Char('@'))) {
        // local address
        //return address + '@' + KNetwork::KResolver::localHostName();
        return address + QLatin1String("@localdomain");
    } else {
        return address;
    }
}

struct FormatInfo {
    std::vector<Kleo::KeyResolver::SplitInfo> splitInfos;
    std::vector<GpgME::Key> signKeys;
};

struct Q_DECL_HIDDEN Kleo::KeyResolver::Private {
    std::set<QByteArray> alreadyWarnedFingerprints;

    std::vector<GpgME::Key> mOpenPGPSigningKeys; // signing
    std::vector<GpgME::Key> mSMIMESigningKeys; // signing

    std::vector<GpgME::Key> mOpenPGPEncryptToSelfKeys; // encryption to self
    std::vector<GpgME::Key> mSMIMEEncryptToSelfKeys; // encryption to self

    std::vector<Item> mPrimaryEncryptionKeys; // encryption to To/CC
    std::vector<Item> mSecondaryEncryptionKeys; // encryption to BCC

    std::map<CryptoMessageFormat, FormatInfo> mFormatInfoMap;

    // key=email address, value=crypto preferences for this contact (from kabc)
    typedef std::map<QString, ContactPreferences> ContactPreferencesMap;
    ContactPreferencesMap mContactPreferencesMap;
};

Kleo::KeyResolver::KeyResolver(bool encToSelf, bool showApproval, bool oppEncryption, unsigned int f, int encrWarnThresholdKey, int signWarnThresholdKey, int encrWarnThresholdRootCert, int signWarnThresholdRootCert, int encrWarnThresholdChainCert, int signWarnThresholdChainCert)
    : d(new Private)
    , mEncryptToSelf(encToSelf)
    , mShowApprovalDialog(showApproval)
    , mOpportunisticEncyption(oppEncryption)
    , mCryptoMessageFormats(f)
    , mEncryptKeyNearExpiryWarningThreshold(encrWarnThresholdKey)
    , mSigningKeyNearExpiryWarningThreshold(signWarnThresholdKey)
    , mEncryptRootCertNearExpiryWarningThreshold(encrWarnThresholdRootCert)
    , mSigningRootCertNearExpiryWarningThreshold(signWarnThresholdRootCert)
    , mEncryptChainCertNearExpiryWarningThreshold(encrWarnThresholdChainCert)
    , mSigningChainCertNearExpiryWarningThreshold(signWarnThresholdChainCert)
{
}

Kleo::KeyResolver::~KeyResolver()
{
    delete d;
}

Kleo::Result Kleo::KeyResolver::checkKeyNearExpiry(const GpgME::Key &key, const char *dontAskAgainName, bool mine, bool sign, bool ca, int recur_limit, const GpgME::Key &orig) const
{
    if (recur_limit <= 0) {
        qCDebug(MESSAGECOMPOSER_LOG) << "Key chain too long (>100 certs)";
        return Kleo::Ok;
    }
    const GpgME::Subkey subkey = key.subkey(0);
    if (d->alreadyWarnedFingerprints.count(subkey.fingerprint())) {
        return Kleo::Ok;    // already warned about this one (and so about it's issuers)
    }

    if (subkey.neverExpires()) {
        return Kleo::Ok;
    }
    static const double secsPerDay = 24 * 60 * 60;
    const double secsTillExpiry = ::difftime(subkey.expirationTime(), time(nullptr));
    if (secsTillExpiry <= 0) {
        const int daysSinceExpiry = 1 + int(-secsTillExpiry / secsPerDay);
        qCDebug(MESSAGECOMPOSER_LOG) << "Key 0x" << key.shortKeyID() << " expired less than "
                                     << daysSinceExpiry << " days ago";
        const QString msg
            = key.protocol() == GpgME::OpenPGP
              ? (mine ? sign
                 ? ki18np("<p>Your OpenPGP signing key</p><p align=center><b>%2</b> (KeyID 0x%3)</p>"
                          "<p>expired less than a day ago.</p>",
                          "<p>Your OpenPGP signing key</p><p align=center><b>%2</b> (KeyID 0x%3)</p>"
                          "<p>expired %1 days ago.</p>")
                 : ki18np("<p>Your OpenPGP encryption key</p><p align=center><b>%2</b> (KeyID 0x%3)</p>"
                          "<p>expired less than a day ago.</p>",
                          "<p>Your OpenPGP encryption key</p><p align=center><b>%2</b> (KeyID 0x%3)</p>"
                          "<p>expired %1 days ago.</p>")
                 : ki18np("<p>The OpenPGP key for</p><p align=center><b>%2</b> (KeyID 0x%3)</p>"
                          "<p>expired less than a day ago.</p>",
                          "<p>The OpenPGP key for</p><p align=center><b>%2</b> (KeyID 0x%3)</p>"
                          "<p>expired %1 days ago.</p>"))
              .subs(daysSinceExpiry)
              .subs(QString::fromUtf8(key.userID(0).id()))
              .subs(QString::fromLatin1(key.shortKeyID()))
              .toString()
              : (ca
                 ? (key.isRoot()
                    ? (mine ? sign
                       ? ki18np("<p>The root certificate</p><p align=center><b>%4</b></p>"
                                "<p>for your S/MIME signing certificate</p><p align=center><b>%2</b> (serial number %3)</p>"
                                "<p>expired less than a day ago.</p>",
                                "<p>The root certificate</p><p align=center><b>%4</b></p>"
                                "<p>for your S/MIME signing certificate</p><p align=center><b>%2</b> (serial number %3)</p>"
                                "<p>expired %1 days ago.</p>")
                       : ki18np("<p>The root certificate</p><p align=center><b>%4</b></p>"
                                "<p>for your S/MIME encryption certificate</p><p align=center><b>%2</b> (serial number %3)</p>"
                                "<p>expired less than a day ago.</p>",
                                "<p>The root certificate</p><p align=center><b>%4</b></p>"
                                "<p>for your S/MIME encryption certificate</p><p align=center><b>%2</b> (serial number %3)</p>"
                                "<p>expired %1 days ago.</p>")
                       : ki18np("<p>The root certificate</p><p align=center><b>%4</b></p>"
                                "<p>for S/MIME certificate</p><p align=center><b>%2</b> (serial number %3)</p>"
                                "<p>expired less than a day ago.</p>",
                                "<p>The root certificate</p><p align=center><b>%4</b></p>"
                                "<p>for S/MIME certificate</p><p align=center><b>%2</b> (serial number %3)</p>"
                                "<p>expired %1 days ago.</p>"))
                    : (mine ? sign
                       ? ki18np("<p>The intermediate CA certificate</p><p align=center><b>%4</b></p>"
                                "<p>for your S/MIME signing certificate</p><p align=center><b>%2</b> (serial number %3)</p>"
                                "<p>expired less than a day ago.</p>",
                                "<p>The intermediate CA certificate</p><p align=center><b>%4</b></p>"
                                "<p>for your S/MIME signing certificate</p><p align=center><b>%2</b> (serial number %3)</p>"
                                "<p>expired %1 days ago.</p>")
                       : ki18np("<p>The intermediate CA certificate</p><p align=center><b>%4</b></p>"
                                "<p>for your S/MIME encryption certificate</p><p align=center><b>%2</b> (serial number %3)</p>"
                                "<p>expired less than a day ago.</p>",
                                "<p>The intermediate CA certificate</p><p align=center><b>%4</b></p>"
                                "<p>for your S/MIME encryption certificate</p><p align=center><b>%2</b> (serial number %3)</p>"
                                "<p>expired %1 days ago.</p>")
                       : ki18np("<p>The intermediate CA certificate</p><p align=center><b>%4</b></p>"
                                "<p>for S/MIME certificate</p><p align=center><b>%2</b> (serial number %3)</p>"
                                "<p>expired less than a day ago.</p>",
                                "<p>The intermediate CA certificate</p><p align=center><b>%4</b></p>"
                                "<p>for S/MIME certificate</p><p align=center><b>%2</b> (serial number %3)</p>"
                                "<p>expired %1 days ago.</p>")))
                 .subs(daysSinceExpiry)
                 .subs(Kleo::DN(orig.userID(0).id()).prettyDN())
                 .subs(QString::fromLatin1(orig.issuerSerial()))
                 .subs(Kleo::DN(key.userID(0).id()).prettyDN())
                 .toString()
                 : (mine ? sign
                    ? ki18np("<p>Your S/MIME signing certificate</p><p align=center><b>%2</b> (serial number %3)</p>"
                             "<p>expired less than a day ago.</p>",
                             "<p>Your S/MIME signing certificate</p><p align=center><b>%2</b> (serial number %3)</p>"
                             "<p>expired %1 days ago.</p>")
                    : ki18np("<p>Your S/MIME encryption certificate</p><p align=center><b>%2</b> (serial number %3)</p>"
                             "<p>expired less than a day ago.</p>",
                             "<p>Your S/MIME encryption certificate</p><p align=center><b>%2</b> (serial number %3)</p>"
                             "<p>expired %1 days ago.</p>")
                    : ki18np("<p>The S/MIME certificate for</p><p align=center><b>%2</b> (serial number %3)</p>"
                             "<p>expired less than a day ago.</p>",
                             "<p>The S/MIME certificate for</p><p align=center><b>%2</b> (serial number %3)</p>"
                             "<p>expired %1 days ago.</p>"))
                 .subs(daysSinceExpiry)
                 .subs(Kleo::DN(key.userID(0).id()).prettyDN())
                 .subs(QString::fromLatin1(key.issuerSerial()))
                 .toString());
        d->alreadyWarnedFingerprints.insert(subkey.fingerprint());
        if (KMessageBox::warningContinueCancel(nullptr, msg,
                                               key.protocol() == GpgME::OpenPGP
                                               ? i18n("OpenPGP Key Expired")
                                               : i18n("S/MIME Certificate Expired"),
                                               KStandardGuiItem::cont(), KStandardGuiItem::cancel(), QLatin1String(dontAskAgainName)) == KMessageBox::Cancel) {
            return Kleo::Canceled;
        }
    } else {
        const int daysTillExpiry = 1 + int(secsTillExpiry / secsPerDay);
        qCDebug(MESSAGECOMPOSER_LOG) << "Key 0x" << key.shortKeyID() << "expires in less than"
                                     << daysTillExpiry << "days";
        const int threshold
            = ca
              ? (key.isRoot()
                 ? (sign
                    ? signingRootCertNearExpiryWarningThresholdInDays()
                    : encryptRootCertNearExpiryWarningThresholdInDays())
                 : (sign
                    ? signingChainCertNearExpiryWarningThresholdInDays()
                    : encryptChainCertNearExpiryWarningThresholdInDays()))
              : (sign
                 ? signingKeyNearExpiryWarningThresholdInDays()
                 : encryptKeyNearExpiryWarningThresholdInDays());
        if (threshold > -1 && daysTillExpiry <= threshold) {
            const QString msg
                = key.protocol() == GpgME::OpenPGP
                  ? (mine ? sign
                     ? ki18np("<p>Your OpenPGP signing key</p><p align=\"center\"><b>%2</b> (KeyID 0x%3)</p>"
                              "<p>expires in less than a day.</p>",
                              "<p>Your OpenPGP signing key</p><p align=\"center\"><b>%2</b> (KeyID 0x%3)</p>"
                              "<p>expires in less than %1 days.</p>")
                     : ki18np("<p>Your OpenPGP encryption key</p><p align=\"center\"><b>%2</b> (KeyID 0x%3)</p>"
                              "<p>expires in less than a day.</p>",
                              "<p>Your OpenPGP encryption key</p><p align=\"center\"><b>%2</b> (KeyID 0x%3)</p>"
                              "<p>expires in less than %1 days.</p>")
                     : ki18np("<p>The OpenPGP key for</p><p align=\"center\"><b>%2</b> (KeyID 0x%3)</p>"
                              "<p>expires in less than a day.</p>",
                              "<p>The OpenPGP key for</p><p align=\"center\"><b>%2</b> (KeyID 0x%3)</p>"
                              "<p>expires in less than %1 days.</p>"))
                  .subs(daysTillExpiry)
                  .subs(QString::fromUtf8(key.userID(0).id()))
                  .subs(QString::fromLatin1(key.shortKeyID()))
                  .toString()
                  : (ca
                     ? (key.isRoot()
                        ? (mine ? sign
                           ? ki18np("<p>The root certificate</p><p align=\"center\"><b>%4</b></p>"
                                    "<p>for your S/MIME signing certificate</p><p align=\"center\"><b>%2</b> (serial number %3)</p>"
                                    "<p>expires in less than a day.</p>",
                                    "<p>The root certificate</p><p align=\"center\"><b>%4</b></p>"
                                    "<p>for your S/MIME signing certificate</p><p align=\"center\"><b>%2</b> (serial number %3)</p>"
                                    "<p>expires in less than %1 days.</p>")
                           : ki18np("<p>The root certificate</p><p align=\"center\"><b>%4</b></p>"
                                    "<p>for your S/MIME encryption certificate</p><p align=\"center\"><b>%2</b> (serial number %3)</p>"
                                    "<p>expires in less than a day.</p>",
                                    "<p>The root certificate</p><p align=\"center\"><b>%4</b></p>"
                                    "<p>for your S/MIME encryption certificate</p><p align=\"center\"><b>%2</b> (serial number %3)</p>"
                                    "<p>expires in less than %1 days.</p>")
                           : ki18np("<p>The root certificate</p><p align=\"center\"><b>%4</b></p>"
                                    "<p>for S/MIME certificate</p><p align=\"center\"><b>%2</b> (serial number %3)</p>"
                                    "<p>expires in less than a day.</p>",
                                    "<p>The root certificate</p><p align=\"center\"><b>%4</b></p>"
                                    "<p>for S/MIME certificate</p><p align=\"center\"><b>%2</b> (serial number %3)</p>"
                                    "<p>expires in less than %1 days.</p>"))
                        : (mine ? sign
                           ? ki18np("<p>The intermediate CA certificate</p><p align=\"center\"><b>%4</b></p>"
                                    "<p>for your S/MIME signing certificate</p><p align=\"center\"><b>%2</b> (serial number %3)</p>"
                                    "<p>expires in less than a day.</p>",
                                    "<p>The intermediate CA certificate</p><p align=\"center\"><b>%4</b></p>"
                                    "<p>for your S/MIME signing certificate</p><p align=\"center\"><b>%2</b> (serial number %3)</p>"
                                    "<p>expires in less than %1 days.</p>")
                           : ki18np("<p>The intermediate CA certificate</p><p align=\"center\"><b>%4</b></p>"
                                    "<p>for your S/MIME encryption certificate</p><p align=\"center\"><b>%2</b> (serial number %3)</p>"
                                    "<p>expires in less than a day.</p>",
                                    "<p>The intermediate CA certificate</p><p align=\"center\"><b>%4</b></p>"
                                    "<p>for your S/MIME encryption certificate</p><p align=\"center\"><b>%2</b> (serial number %3)</p>"
                                    "<p>expires in less than %1 days.</p>")
                           : ki18np("<p>The intermediate CA certificate</p><p align=\"center\"><b>%4</b></p>"
                                    "<p>for S/MIME certificate</p><p align=\"center\"><b>%2</b> (serial number %3)</p>"
                                    "<p>expires in less than a day.</p>",
                                    "<p>The intermediate CA certificate</p><p align=\"center\"><b>%4</b></p>"
                                    "<p>for S/MIME certificate</p><p align=\"center\"><b>%2</b> (serial number %3)</p>"
                                    "<p>expires in less than %1 days.</p>")))
                     .subs(daysTillExpiry)
                     .subs(Kleo::DN(orig.userID(0).id()).prettyDN())
                     .subs(QString::fromLatin1(orig.issuerSerial()))
                     .subs(Kleo::DN(key.userID(0).id()).prettyDN())
                     .toString()
                     : (mine ? sign
                        ? ki18np("<p>Your S/MIME signing certificate</p><p align=\"center\"><b>%2</b> (serial number %3)</p>"
                                 "<p>expires in less than a day.</p>",
                                 "<p>Your S/MIME signing certificate</p><p align=\"center\"><b>%2</b> (serial number %3)</p>"
                                 "<p>expires in less than %1 days.</p>")
                        : ki18np("<p>Your S/MIME encryption certificate</p><p align=\"center\"><b>%2</b> (serial number %3)</p>"
                                 "<p>expires in less than a day.</p>",
                                 "<p>Your S/MIME encryption certificate</p><p align=\"center\"><b>%2</b> (serial number %3)</p>"
                                 "<p>expires in less than %1 days.</p>")
                        : ki18np("<p>The S/MIME certificate for</p><p align=\"center\"><b>%2</b> (serial number %3)</p>"
                                 "<p>expires in less than a day.</p>",
                                 "<p>The S/MIME certificate for</p><p align=\"center\"><b>%2</b> (serial number %3)</p>"
                                 "<p>expires in less than %1 days.</p>"))
                     .subs(daysTillExpiry)
                     .subs(Kleo::DN(key.userID(0).id()).prettyDN())
                     .subs(QString::fromLatin1(key.issuerSerial()))
                     .toString());
            d->alreadyWarnedFingerprints.insert(subkey.fingerprint());
            if (KMessageBox::warningContinueCancel(nullptr, msg,
                                                   key.protocol() == GpgME::OpenPGP
                                                   ? i18n("OpenPGP Key Expires Soon")
                                                   : i18n("S/MIME Certificate Expires Soon"),
                                                   KStandardGuiItem::cont(), KStandardGuiItem::cancel(),
                                                   QLatin1String(dontAskAgainName))
                == KMessageBox::Cancel) {
                return Kleo::Canceled;
            }
        }
    }
    if (key.isRoot()) {
        return Kleo::Ok;
    } else if (const char *chain_id = key.chainID()) {
        const std::vector<GpgME::Key> issuer = lookup(QStringList(QLatin1String(chain_id)), false);
        if (issuer.empty()) {
            return Kleo::Ok;
        } else {
            return checkKeyNearExpiry(issuer.front(), dontAskAgainName, mine, sign,
                                      true, recur_limit - 1, ca ? orig : key);
        }
    }
    return Kleo::Ok;
}

Kleo::Result Kleo::KeyResolver::setEncryptToSelfKeys(const QStringList &fingerprints)
{
    if (!encryptToSelf()) {
        return Kleo::Ok;
    }

    std::vector<GpgME::Key> keys = lookup(fingerprints);
    std::remove_copy_if(keys.begin(), keys.end(),
                        std::back_inserter(d->mOpenPGPEncryptToSelfKeys),
                        NotValidTrustedOpenPGPEncryptionKey);  // -= trusted?
    std::remove_copy_if(keys.begin(), keys.end(),
                        std::back_inserter(d->mSMIMEEncryptToSelfKeys),
                        NotValidTrustedSMIMEEncryptionKey);    // -= trusted?

    if (d->mOpenPGPEncryptToSelfKeys.size() + d->mSMIMEEncryptToSelfKeys.size()
        < keys.size()) {
        // too few keys remain...
        const QString msg = i18n("One or more of your configured OpenPGP encryption "
                                 "keys or S/MIME certificates is not usable for "
                                 "encryption. Please reconfigure your encryption keys "
                                 "and certificates for this identity in the identity "
                                 "configuration dialog.\n"
                                 "If you choose to continue, and the keys are needed "
                                 "later on, you will be prompted to specify the keys "
                                 "to use.");
        return KMessageBox::warningContinueCancel(nullptr, msg, i18n("Unusable Encryption Keys"),
                                                  KStandardGuiItem::cont(), KStandardGuiItem::cancel(),
                                                  QStringLiteral("unusable own encryption key warning"))
               == KMessageBox::Continue ? Kleo::Ok : Kleo::Canceled;
    }

    // check for near-expiry:
    std::vector<GpgME::Key>::const_iterator end(d->mOpenPGPEncryptToSelfKeys.end());

    for (std::vector<GpgME::Key>::const_iterator it = d->mOpenPGPEncryptToSelfKeys.begin(); it != end; ++it) {
        const Kleo::Result r = checkKeyNearExpiry(*it, "own encryption key expires soon warning",
                                                  true, false);
        if (r != Kleo::Ok) {
            return r;
        }
    }
    std::vector<GpgME::Key>::const_iterator end2(d->mSMIMEEncryptToSelfKeys.end());
    for (std::vector<GpgME::Key>::const_iterator it = d->mSMIMEEncryptToSelfKeys.begin(); it != end2; ++it) {
        const Kleo::Result r = checkKeyNearExpiry(*it, "own encryption key expires soon warning",
                                                  true, false);
        if (r != Kleo::Ok) {
            return r;
        }
    }

    return Kleo::Ok;
}

Kleo::Result Kleo::KeyResolver::setSigningKeys(const QStringList &fingerprints)
{
    std::vector<GpgME::Key> keys = lookup(fingerprints, true);   // secret keys
    std::remove_copy_if(keys.begin(), keys.end(),
                        std::back_inserter(d->mOpenPGPSigningKeys),
                        NotValidOpenPGPSigningKey);
    std::remove_copy_if(keys.begin(), keys.end(),
                        std::back_inserter(d->mSMIMESigningKeys),
                        NotValidSMIMESigningKey);

    if (d->mOpenPGPSigningKeys.size() + d->mSMIMESigningKeys.size() < keys.size()) {
        // too few keys remain...
        const QString msg = i18n("One or more of your configured OpenPGP signing keys "
                                 "or S/MIME signing certificates is not usable for "
                                 "signing. Please reconfigure your signing keys "
                                 "and certificates for this identity in the identity "
                                 "configuration dialog.\n"
                                 "If you choose to continue, and the keys are needed "
                                 "later on, you will be prompted to specify the keys "
                                 "to use.");
        return KMessageBox::warningContinueCancel(nullptr, msg, i18n("Unusable Signing Keys"),
                                                  KStandardGuiItem::cont(), KStandardGuiItem::cancel(),
                                                  QStringLiteral("unusable signing key warning"))
               == KMessageBox::Continue ? Kleo::Ok : Kleo::Canceled;
    }

    // check for near expiry:

    for (std::vector<GpgME::Key>::const_iterator it = d->mOpenPGPSigningKeys.begin(), total = d->mOpenPGPSigningKeys.end(); it != total; ++it) {
        const Kleo::Result r = checkKeyNearExpiry(*it, "signing key expires soon warning",
                                                  true, true);
        if (r != Kleo::Ok) {
            return r;
        }
    }

    for (std::vector<GpgME::Key>::const_iterator it = d->mSMIMESigningKeys.begin(), total = d->mSMIMESigningKeys.end(); it != total; ++it) {
        const Kleo::Result r = checkKeyNearExpiry(*it, "signing key expires soon warning",
                                                  true, true);
        if (r != Kleo::Ok) {
            return r;
        }
    }

    return Kleo::Ok;
}

void Kleo::KeyResolver::setPrimaryRecipients(const QStringList &addresses)
{
    d->mPrimaryEncryptionKeys = getEncryptionItems(addresses);
}

void Kleo::KeyResolver::setSecondaryRecipients(const QStringList &addresses)
{
    d->mSecondaryEncryptionKeys = getEncryptionItems(addresses);
}

std::vector<Kleo::KeyResolver::Item> Kleo::KeyResolver::getEncryptionItems(const QStringList &addresses)
{
    std::vector<Item> items;
    items.reserve(addresses.size());
    QStringList::const_iterator end(addresses.constEnd());
    for (QStringList::const_iterator it = addresses.constBegin(); it != end; ++it) {
        QString addr = canonicalAddress(*it).toLower();
        const ContactPreferences pref = lookupContactPreferences(addr);

        items.push_back(Item(*it,   /*getEncryptionKeys( *it, true ),*/
                             pref.encryptionPreference,
                             pref.signingPreference,
                             pref.cryptoMessageFormat));
    }
    return items;
}

static Kleo::Action action(bool doit, bool ask, bool donot, bool requested)
{
    if (requested && !donot) {
        return Kleo::DoIt;
    }
    if (doit && !ask && !donot) {
        return Kleo::DoIt;
    }
    if (!doit && ask && !donot) {
        return Kleo::Ask;
    }
    if (!doit && !ask && donot) {
        return requested ? Kleo::Conflict : Kleo::DontDoIt;
    }
    if (!doit && !ask && !donot) {
        return Kleo::DontDoIt;
    }
    return Kleo::Conflict;
}

Kleo::Action Kleo::KeyResolver::checkSigningPreferences(bool signingRequested) const
{
    if (signingRequested && d->mOpenPGPSigningKeys.empty() && d->mSMIMESigningKeys.empty()) {
        return Impossible;
    }

    SigningPreferenceCounter count;
    count = std::for_each(d->mPrimaryEncryptionKeys.begin(), d->mPrimaryEncryptionKeys.end(),
                          count);
    count = std::for_each(d->mSecondaryEncryptionKeys.begin(), d->mSecondaryEncryptionKeys.end(),
                          count);

    unsigned int sign = count.numAlwaysSign();
    unsigned int ask = count.numAlwaysAskForSigning();
    const unsigned int dontSign = count.numNeverSign();
    if (signingPossible()) {
        sign += count.numAlwaysSignIfPossible();
        ask += count.numAskSigningWheneverPossible();
    }

    return action(sign, ask, dontSign, signingRequested);
}

bool Kleo::KeyResolver::signingPossible() const
{
    return !d->mOpenPGPSigningKeys.empty() || !d->mSMIMESigningKeys.empty();
}

Kleo::Action Kleo::KeyResolver::checkEncryptionPreferences(bool encryptionRequested) const
{
    if (d->mPrimaryEncryptionKeys.empty() && d->mSecondaryEncryptionKeys.empty()) {
        return DontDoIt;
    }

    if (encryptionRequested && encryptToSelf()
        && d->mOpenPGPEncryptToSelfKeys.empty() && d->mSMIMEEncryptToSelfKeys.empty()) {
        return Impossible;
    }

    if (!encryptionRequested && !mOpportunisticEncyption) {
        // try to minimize crypto ops (including key lookups) by only
        // looking up keys when at least one of the encryption
        // preferences needs it:
        EncryptionPreferenceCounter count(nullptr, UnknownPreference);
        count.process(d->mPrimaryEncryptionKeys);
        count.process(d->mSecondaryEncryptionKeys);
        if (!count.numAlwaysEncrypt()
            && !count.numAlwaysAskForEncryption()     // this guy might not need a lookup, when declined, but it's too complex to implement that here
            && !count.numAlwaysEncryptIfPossible()
            && !count.numAskWheneverPossible()) {
            return DontDoIt;
        }
    }

    EncryptionPreferenceCounter count(this, mOpportunisticEncyption ? AskWheneverPossible : UnknownPreference);
    count = std::for_each(d->mPrimaryEncryptionKeys.begin(), d->mPrimaryEncryptionKeys.end(),
                          count);
    count = std::for_each(d->mSecondaryEncryptionKeys.begin(), d->mSecondaryEncryptionKeys.end(),
                          count);

    unsigned int encrypt = count.numAlwaysEncrypt();
    unsigned int ask = count.numAlwaysAskForEncryption();
    const unsigned int dontEncrypt = count.numNeverEncrypt() + count.numNoKey();
    if (encryptionPossible()) {
        encrypt += count.numAlwaysEncryptIfPossible();
        ask += count.numAskWheneverPossible();
    }

    const Action act = action(encrypt, ask, dontEncrypt, encryptionRequested);
    if (act != Ask
        || std::for_each(d->mPrimaryEncryptionKeys.begin(), d->mPrimaryEncryptionKeys.end(),
                         std::for_each(d->mSecondaryEncryptionKeys.begin(), d->mSecondaryEncryptionKeys.end(),
                                       EncryptionPreferenceCounter(this, UnknownPreference))).numAlwaysAskForEncryption()) {
        return act;
    } else {
        return AskOpportunistic;
    }
}

bool Kleo::KeyResolver::encryptionPossible() const
{
    return std::find_if(d->mPrimaryEncryptionKeys.begin(), d->mPrimaryEncryptionKeys.end(),
                        EmptyKeyList) == d->mPrimaryEncryptionKeys.end()
           && std::find_if(d->mSecondaryEncryptionKeys.begin(), d->mSecondaryEncryptionKeys.end(),
                           EmptyKeyList) == d->mSecondaryEncryptionKeys.end();
}

Kleo::Result Kleo::KeyResolver::resolveAllKeys(bool &signingRequested, bool &encryptionRequested)
{
    if (!encryptionRequested && !signingRequested) {
        // make a dummy entry with all recipients, but no signing or
        // encryption keys to avoid special-casing on the caller side:
        dump();
        d->mFormatInfoMap[OpenPGPMIMEFormat].splitInfos.push_back(SplitInfo(allRecipients()));
        dump();
        return Kleo::Ok;
    }
    Kleo::Result result = Kleo::Ok;
    if (encryptionRequested) {
        bool finalySendUnencrypted = false;
        result = resolveEncryptionKeys(signingRequested, finalySendUnencrypted);
        if (finalySendUnencrypted) {
            encryptionRequested = false;
        }
    }
    if (result != Kleo::Ok) {
        return result;
    }
    if (encryptionRequested) {
        result = resolveSigningKeysForEncryption();
    } else {
        result = resolveSigningKeysForSigningOnly();
        if (result == Kleo::Failure) {
            signingRequested = false;
            return Kleo::Ok;
        }
    }
    return result;
}

Kleo::Result Kleo::KeyResolver::resolveEncryptionKeys(bool signingRequested, bool &finalySendUnencrypted)
{
    //
    // 1. Get keys for all recipients:
    //
    qCDebug(MESSAGECOMPOSER_LOG) << "resolving enc keys";
    for (std::vector<Item>::iterator it = d->mPrimaryEncryptionKeys.begin(); it != d->mPrimaryEncryptionKeys.end(); ++it) {
        qCDebug(MESSAGECOMPOSER_LOG) << "checking primary:" << it->address;
        if (!it->needKeys) {
            continue;
        }
        it->keys = getEncryptionKeys(it->address, false);
        qCDebug(MESSAGECOMPOSER_LOG) << "got # keys:" << it->keys.size();
        if (it->keys.empty()) {
            return Kleo::Canceled;
        }
        QString addr = canonicalAddress(it->address).toLower();
        const ContactPreferences pref = lookupContactPreferences(addr);
        it->pref = pref.encryptionPreference;
        it->signPref = pref.signingPreference;
        it->format = pref.cryptoMessageFormat;
        qCDebug(MESSAGECOMPOSER_LOG) << "set key data:" << int(it->pref) << int(it->signPref) << int(it->format);
    }

    for (std::vector<Item>::iterator it = d->mSecondaryEncryptionKeys.begin(), total = d->mSecondaryEncryptionKeys.end(); it != total; ++it) {
        if (!it->needKeys) {
            continue;
        }
        it->keys = getEncryptionKeys(it->address, false);
        if (it->keys.empty()) {
            return Kleo::Canceled;
        }
        QString addr = canonicalAddress(it->address).toLower();
        const ContactPreferences pref = lookupContactPreferences(addr);
        it->pref = pref.encryptionPreference;
        it->signPref = pref.signingPreference;
        it->format = pref.cryptoMessageFormat;
    }

    // 1a: Present them to the user

    const Kleo::Result res = showKeyApprovalDialog(finalySendUnencrypted);
    if (res != Kleo::Ok) {
        return res;
    }

    //
    // 2. Check what the primary recipients need
    //

    // 2a. Try to find a common format for all primary recipients,
    //     else use as many formats as needed

    const EncryptionFormatPreferenceCounter primaryCount
        = std::for_each(d->mPrimaryEncryptionKeys.begin(), d->mPrimaryEncryptionKeys.end(),
                        EncryptionFormatPreferenceCounter());

    CryptoMessageFormat commonFormat = AutoFormat;
    for (unsigned int i = 0; i < numConcreteCryptoMessageFormats; ++i) {
        if (!(concreteCryptoMessageFormats[i] & mCryptoMessageFormats)) {
            continue;
        }
        if (signingRequested && signingKeysFor(concreteCryptoMessageFormats[i]).empty()) {
            continue;
        }
        if (encryptToSelf() && encryptToSelfKeysFor(concreteCryptoMessageFormats[i]).empty()) {
            continue;
        }
        if (primaryCount.numOf(concreteCryptoMessageFormats[i]) == primaryCount.numTotal()) {
            commonFormat = concreteCryptoMessageFormats[i];
            break;
        }
    }
    qCDebug(MESSAGECOMPOSER_LOG) << "got commonFormat for primary recipients:" << int(commonFormat);
    if (commonFormat != AutoFormat) {
        addKeys(d->mPrimaryEncryptionKeys, commonFormat);
    } else {
        addKeys(d->mPrimaryEncryptionKeys);
    }

    collapseAllSplitInfos(); // these can be encrypted together

    // 2b. Just try to find _something_ for each secondary recipient,
    //     with a preference to a common format (if that exists)

    const EncryptionFormatPreferenceCounter secondaryCount
        = std::for_each(d->mSecondaryEncryptionKeys.begin(), d->mSecondaryEncryptionKeys.end(),
                        EncryptionFormatPreferenceCounter());

    if (commonFormat != AutoFormat
        && secondaryCount.numOf(commonFormat) == secondaryCount.numTotal()) {
        addKeys(d->mSecondaryEncryptionKeys, commonFormat);
    } else {
        addKeys(d->mSecondaryEncryptionKeys);
    }

    // 3. Check for expiry:

    for (unsigned int i = 0; i < numConcreteCryptoMessageFormats; ++i) {
        const std::vector<SplitInfo> si_list = encryptionItems(concreteCryptoMessageFormats[i]);
        for (std::vector<SplitInfo>::const_iterator sit = si_list.begin(), total = si_list.end(); sit != total; ++sit) {
            for (std::vector<GpgME::Key>::const_iterator kit = sit->keys.begin(); kit != sit->keys.end(); ++kit) {
                const Kleo::Result r = checkKeyNearExpiry(*kit, "other encryption key near expiry warning",
                                                          false, false);
                if (r != Kleo::Ok) {
                    return r;
                }
            }
        }
    }

    // 4. Check that we have the right keys for encryptToSelf()

    if (!encryptToSelf()) {
        return Kleo::Ok;
    }

    // 4a. Check for OpenPGP keys

    qCDebug(MESSAGECOMPOSER_LOG) << "sizes of encryption items:" << encryptionItems(InlineOpenPGPFormat).size() << encryptionItems(OpenPGPMIMEFormat).size() << encryptionItems(SMIMEFormat).size()
                                 << encryptionItems(SMIMEOpaqueFormat).size();
    if (!encryptionItems(InlineOpenPGPFormat).empty()
        || !encryptionItems(OpenPGPMIMEFormat).empty()) {
        // need them
        if (d->mOpenPGPEncryptToSelfKeys.empty()) {
            const QString msg = i18n("Examination of recipient's encryption preferences "
                                     "yielded that the message should be encrypted using "
                                     "OpenPGP, at least for some recipients;\n"
                                     "however, you have not configured valid trusted "
                                     "OpenPGP encryption keys for this identity.\n"
                                     "You may continue without encrypting to yourself, "
                                     "but be aware that you will not be able to read your "
                                     "own messages if you do so.");
            if (KMessageBox::warningContinueCancel(nullptr, msg,
                                                   i18n("Unusable Encryption Keys"),
                                                   KStandardGuiItem::cont(), KStandardGuiItem::cancel(),
                                                   QStringLiteral("encrypt-to-self will fail warning"))
                == KMessageBox::Cancel) {
                return Kleo::Canceled;
            }
            // FIXME: Allow selection
        }
        addToAllSplitInfos(d->mOpenPGPEncryptToSelfKeys,
                           InlineOpenPGPFormat | OpenPGPMIMEFormat);
    }

    // 4b. Check for S/MIME certs:

    if (!encryptionItems(SMIMEFormat).empty()
        || !encryptionItems(SMIMEOpaqueFormat).empty()) {
        // need them
        if (d->mSMIMEEncryptToSelfKeys.empty()) {
            // don't have one
            const QString msg = i18n("Examination of recipient's encryption preferences "
                                     "yielded that the message should be encrypted using "
                                     "S/MIME, at least for some recipients;\n"
                                     "however, you have not configured valid "
                                     "S/MIME encryption certificates for this identity.\n"
                                     "You may continue without encrypting to yourself, "
                                     "but be aware that you will not be able to read your "
                                     "own messages if you do so.");
            if (KMessageBox::warningContinueCancel(nullptr, msg,
                                                   i18n("Unusable Encryption Keys"),
                                                   KStandardGuiItem::cont(), KStandardGuiItem::cancel(),
                                                   QStringLiteral("encrypt-to-self will fail warning"))
                == KMessageBox::Cancel) {
                return Kleo::Canceled;
            }
            // FIXME: Allow selection
        }
        addToAllSplitInfos(d->mSMIMEEncryptToSelfKeys,
                           SMIMEFormat | SMIMEOpaqueFormat);
    }

    // FIXME: Present another message if _both_ OpenPGP and S/MIME keys
    // are missing.

    return Kleo::Ok;
}

Kleo::Result Kleo::KeyResolver::resolveSigningKeysForEncryption()
{
    if ((!encryptionItems(InlineOpenPGPFormat).empty()
         || !encryptionItems(OpenPGPMIMEFormat).empty())
        && d->mOpenPGPSigningKeys.empty()) {
        const QString msg = i18n("Examination of recipient's signing preferences "
                                 "yielded that the message should be signed using "
                                 "OpenPGP, at least for some recipients;\n"
                                 "however, you have not configured valid "
                                 "OpenPGP signing certificates for this identity.");
        if (KMessageBox::warningContinueCancel(nullptr, msg,
                                               i18n("Unusable Signing Keys"),
                                               KGuiItem(i18n("Do Not OpenPGP-Sign")),
                                               KStandardGuiItem::cancel(),
                                               QStringLiteral("signing will fail warning"))
            == KMessageBox::Cancel) {
            return Kleo::Canceled;
        }
        // FIXME: Allow selection
    }
    if ((!encryptionItems(SMIMEFormat).empty()
         || !encryptionItems(SMIMEOpaqueFormat).empty())
        && d->mSMIMESigningKeys.empty()) {
        const QString msg = i18n("Examination of recipient's signing preferences "
                                 "yielded that the message should be signed using "
                                 "S/MIME, at least for some recipients;\n"
                                 "however, you have not configured valid "
                                 "S/MIME signing certificates for this identity.");
        if (KMessageBox::warningContinueCancel(nullptr, msg,
                                               i18n("Unusable Signing Keys"),
                                               KGuiItem(i18n("Do Not S/MIME-Sign")),
                                               KStandardGuiItem::cancel(),
                                               QStringLiteral("signing will fail warning"))
            == KMessageBox::Cancel) {
            return Kleo::Canceled;
        }
        // FIXME: Allow selection
    }

    // FIXME: Present another message if _both_ OpenPGP and S/MIME keys
    // are missing.

    for (std::map<CryptoMessageFormat, FormatInfo>::iterator it = d->mFormatInfoMap.begin(); it != d->mFormatInfoMap.end(); ++it) {
        if (!it->second.splitInfos.empty()) {
            dump();
            it->second.signKeys = signingKeysFor(it->first);
            dump();
        }
    }

    return Kleo::Ok;
}

Kleo::Result Kleo::KeyResolver::resolveSigningKeysForSigningOnly()
{
    //
    // we don't need to distinguish between primary and secondary
    // recipients here:
    //
    SigningFormatPreferenceCounter count;
    count = std::for_each(d->mPrimaryEncryptionKeys.begin(), d->mPrimaryEncryptionKeys.end(),
                          count);
    count = std::for_each(d->mSecondaryEncryptionKeys.begin(), d->mSecondaryEncryptionKeys.end(),
                          count);

    // try to find a common format that works for all (and that we have signing keys for):

    CryptoMessageFormat commonFormat = AutoFormat;

    for (unsigned int i = 0; i < numConcreteCryptoMessageFormats; ++i) {
        const auto res = concreteCryptoMessageFormats[i];
        if (!(mCryptoMessageFormats & res)) {
            continue;    // skip
        }
        if (signingKeysFor(res).empty()) {
            continue;    // skip
        }
        if (count.numOf(res) == count.numTotal()) {
            commonFormat = res;
            break;
        }
    }

    if (commonFormat != AutoFormat) {   // found
        dump();
        FormatInfo &fi = d->mFormatInfoMap[ commonFormat ];
        fi.signKeys = signingKeysFor(commonFormat);
        fi.splitInfos.resize(1);
        fi.splitInfos.front() = SplitInfo(allRecipients());
        dump();
        return Kleo::Ok;
    }

    const QString msg = i18n("Examination of recipient's signing preferences "
                             "showed no common type of signature matching your "
                             "available signing keys.\n"
                             "Send message without signing?");
    if (KMessageBox::warningContinueCancel(nullptr, msg, i18n("No signing possible"),
                                           KStandardGuiItem::cont())
        == KMessageBox::Continue) {
        d->mFormatInfoMap[OpenPGPMIMEFormat].splitInfos.push_back(SplitInfo(allRecipients()));
        return Kleo::Failure; // means "Ok, but without signing"
    }
    return Kleo::Canceled;
}

std::vector<GpgME::Key> Kleo::KeyResolver::signingKeysFor(CryptoMessageFormat f) const
{
    if (isOpenPGP(f)) {
        return d->mOpenPGPSigningKeys;
    }
    if (isSMIME(f)) {
        return d->mSMIMESigningKeys;
    }
    return std::vector<GpgME::Key>();
}

std::vector<GpgME::Key> Kleo::KeyResolver::encryptToSelfKeysFor(CryptoMessageFormat f) const
{
    if (isOpenPGP(f)) {
        return d->mOpenPGPEncryptToSelfKeys;
    }
    if (isSMIME(f)) {
        return d->mSMIMEEncryptToSelfKeys;
    }
    return std::vector<GpgME::Key>();
}

QStringList Kleo::KeyResolver::allRecipients() const
{
    QStringList result;
    std::transform(d->mPrimaryEncryptionKeys.begin(), d->mPrimaryEncryptionKeys.end(),
                   std::back_inserter(result), ItemDotAddress);
    std::transform(d->mSecondaryEncryptionKeys.begin(), d->mSecondaryEncryptionKeys.end(),
                   std::back_inserter(result), ItemDotAddress);
    return result;
}

void Kleo::KeyResolver::collapseAllSplitInfos()
{
    dump();
    for (unsigned int i = 0; i < numConcreteCryptoMessageFormats; ++i) {
        std::map<CryptoMessageFormat, FormatInfo>::iterator pos
            = d->mFormatInfoMap.find(concreteCryptoMessageFormats[i]);
        if (pos == d->mFormatInfoMap.end()) {
            continue;
        }
        std::vector<SplitInfo> &v = pos->second.splitInfos;
        if (v.size() < 2) {
            continue;
        }
        SplitInfo &si = v.front();
        for (std::vector<SplitInfo>::const_iterator it = v.begin() + 1; it != v.end(); ++it) {
            si.keys.insert(si.keys.end(), it->keys.begin(), it->keys.end());
            std::copy(it->recipients.begin(), it->recipients.end(), std::back_inserter(si.recipients));
        }
        v.resize(1);
    }
    dump();
}

void Kleo::KeyResolver::addToAllSplitInfos(const std::vector<GpgME::Key> &keys, unsigned int f)
{
    dump();
    if (!f || keys.empty()) {
        return;
    }
    for (unsigned int i = 0; i < numConcreteCryptoMessageFormats; ++i) {
        if (!(f & concreteCryptoMessageFormats[i])) {
            continue;
        }
        std::map<CryptoMessageFormat, FormatInfo>::iterator pos
            = d->mFormatInfoMap.find(concreteCryptoMessageFormats[i]);
        if (pos == d->mFormatInfoMap.end()) {
            continue;
        }
        std::vector<SplitInfo> &v = pos->second.splitInfos;
        for (std::vector<SplitInfo>::iterator it = v.begin(); it != v.end(); ++it) {
            it->keys.insert(it->keys.end(), keys.begin(), keys.end());
        }
    }
    dump();
}

void Kleo::KeyResolver::dump() const
{
#ifndef NDEBUG
    if (d->mFormatInfoMap.empty()) {
        qCDebug(MESSAGECOMPOSER_LOG) << "Keyresolver: Format info empty";
    }
    for (std::map<CryptoMessageFormat, FormatInfo>::const_iterator it = d->mFormatInfoMap.begin(); it != d->mFormatInfoMap.end(); ++it) {
        qCDebug(MESSAGECOMPOSER_LOG) << "Format info for " << Kleo::cryptoMessageFormatToString(it->first)
                                     << ":  Signing keys: ";
        for (std::vector<GpgME::Key>::const_iterator sit = it->second.signKeys.begin(); sit != it->second.signKeys.end(); ++sit) {
            qCDebug(MESSAGECOMPOSER_LOG) << "  " << sit->shortKeyID() << " ";
        }
        unsigned int i = 0;
        for (std::vector<SplitInfo>::const_iterator sit = it->second.splitInfos.begin(), sitEnd = it->second.splitInfos.end(); sit != sitEnd; ++sit, ++i) {
            qCDebug(MESSAGECOMPOSER_LOG) << "  SplitInfo #" << i << " encryption keys: ";
            for (std::vector<GpgME::Key>::const_iterator kit = sit->keys.begin(), sitEnd = sit->keys.end(); kit != sitEnd; ++kit) {
                qCDebug(MESSAGECOMPOSER_LOG) << "  " << kit->shortKeyID();
            }
            qCDebug(MESSAGECOMPOSER_LOG) << "  SplitInfo #" << i << " recipients: "
                                         << qPrintable(sit->recipients.join(QLatin1String(", ")));
        }
    }
#endif
}

Kleo::Result Kleo::KeyResolver::showKeyApprovalDialog(bool &finalySendUnencrypted)
{
    const bool showKeysForApproval = showApprovalDialog()
                                     || std::find_if(d->mPrimaryEncryptionKeys.begin(), d->mPrimaryEncryptionKeys.end(),
                                                     ApprovalNeeded) != d->mPrimaryEncryptionKeys.end()
                                     || std::find_if(d->mSecondaryEncryptionKeys.begin(), d->mSecondaryEncryptionKeys.end(),
                                                     ApprovalNeeded) != d->mSecondaryEncryptionKeys.end();

    if (!showKeysForApproval) {
        return Kleo::Ok;
    }

    std::vector<Kleo::KeyApprovalDialog::Item> items;
    items.reserve(d->mPrimaryEncryptionKeys.size()
                  +d->mSecondaryEncryptionKeys.size());
    std::copy(d->mPrimaryEncryptionKeys.begin(), d->mPrimaryEncryptionKeys.end(),
              std::back_inserter(items));
    std::copy(d->mSecondaryEncryptionKeys.begin(), d->mSecondaryEncryptionKeys.end(),
              std::back_inserter(items));

    std::vector<GpgME::Key> senderKeys;
    senderKeys.reserve(d->mOpenPGPEncryptToSelfKeys.size()
                       +d->mSMIMEEncryptToSelfKeys.size());
    std::copy(d->mOpenPGPEncryptToSelfKeys.begin(), d->mOpenPGPEncryptToSelfKeys.end(),
              std::back_inserter(senderKeys));
    std::copy(d->mSMIMEEncryptToSelfKeys.begin(), d->mSMIMEEncryptToSelfKeys.end(),
              std::back_inserter(senderKeys));

#ifndef QT_NO_CURSOR
    const KPIM::KCursorSaver idle(KPIM::KBusyPtr::idle());
#endif

    QPointer<Kleo::KeyApprovalDialog> dlg = new Kleo::KeyApprovalDialog(items, senderKeys);

    if (dlg->exec() == QDialog::Rejected) {
        delete dlg;
        return Kleo::Canceled;
    }

    items = dlg->items();
    senderKeys = dlg->senderKeys();
    const bool prefsChanged = dlg->preferencesChanged();
    delete dlg;

    if (prefsChanged) {
        for (uint i = 0, total = items.size(); i < total; ++i) {
            ContactPreferences pref = lookupContactPreferences(items[i].address);
            pref.encryptionPreference = items[i].pref;
            pref.pgpKeyFingerprints.clear();
            pref.smimeCertFingerprints.clear();
            const std::vector<GpgME::Key> &keys = items[i].keys;
            for (std::vector<GpgME::Key>::const_iterator it = keys.begin(), end = keys.end(); it != end; ++it) {
                if (it->protocol() == GpgME::OpenPGP) {
                    if (const char *fpr = it->primaryFingerprint()) {
                        pref.pgpKeyFingerprints.push_back(QLatin1String(fpr));
                    }
                } else if (it->protocol() == GpgME::CMS) {
                    if (const char *fpr = it->primaryFingerprint()) {
                        pref.smimeCertFingerprints.push_back(QLatin1String(fpr));
                    }
                }
            }
            saveContactPreference(items[i].address, pref);
        }
    }

    // show a warning if the user didn't select an encryption key for
    // herself:
    if (encryptToSelf() && senderKeys.empty()) {
        const QString msg = i18n("You did not select an encryption key for yourself "
                                 "(encrypt to self). You will not be able to decrypt "
                                 "your own message if you encrypt it.");
        if (KMessageBox::warningContinueCancel(nullptr, msg,
                                               i18n("Missing Key Warning"),
                                               KGuiItem(i18n("&Encrypt")))
            == KMessageBox::Cancel) {
            return Kleo::Canceled;
        } else {
            mEncryptToSelf = false;
        }
    }

    // count empty key ID lists
    const unsigned int emptyListCount
        = std::count_if(items.begin(), items.end(), EmptyKeyList);

    // show a warning if the user didn't select an encryption key for
    // some of the recipients
    if (items.size() == emptyListCount) {
        const QString msg = (d->mPrimaryEncryptionKeys.size()
                             +d->mSecondaryEncryptionKeys.size() == 1)
                            ? i18n("You did not select an encryption key for the "
                                   "recipient of this message; therefore, the message "
                                   "will not be encrypted.")
                            : i18n("You did not select an encryption key for any of the "
                                   "recipients of this message; therefore, the message "
                                   "will not be encrypted.");
        if (KMessageBox::warningContinueCancel(nullptr, msg,
                                               i18n("Missing Key Warning"),
                                               KGuiItem(i18n("Send &Unencrypted")))
            == KMessageBox::Cancel) {
            return Kleo::Canceled;
        }
        finalySendUnencrypted = true;
    } else if (emptyListCount > 0) {
        const QString msg = (emptyListCount == 1)
                            ? i18n("You did not select an encryption key for one of "
                                   "the recipients: this person will not be able to "
                                   "decrypt the message if you encrypt it.")
                            : i18n("You did not select encryption keys for some of "
                                   "the recipients: these persons will not be able to "
                                   "decrypt the message if you encrypt it.");
#ifndef QT_NO_CURSOR
        KPIM::KCursorSaver idle(KPIM::KBusyPtr::idle());
#endif
        if (KMessageBox::warningContinueCancel(nullptr, msg,
                                               i18n("Missing Key Warning"),
                                               KGuiItem(i18n("&Encrypt")))
            == KMessageBox::Cancel) {
            return Kleo::Canceled;
        }
    }

    std::transform(d->mPrimaryEncryptionKeys.begin(), d->mPrimaryEncryptionKeys.end(),
                   items.begin(),
                   d->mPrimaryEncryptionKeys.begin(),
                   CopyKeysAndEncryptionPreferences);
    std::transform(d->mSecondaryEncryptionKeys.begin(), d->mSecondaryEncryptionKeys.end(),
                   items.begin() + d->mPrimaryEncryptionKeys.size(),
                   d->mSecondaryEncryptionKeys.begin(),
                   CopyKeysAndEncryptionPreferences);

    d->mOpenPGPEncryptToSelfKeys.clear();
    d->mSMIMEEncryptToSelfKeys.clear();

    std::remove_copy_if(senderKeys.begin(), senderKeys.end(),
                        std::back_inserter(d->mOpenPGPEncryptToSelfKeys),
                        NotValidTrustedOpenPGPEncryptionKey);  // -= trusted (see above, too)?
    std::remove_copy_if(senderKeys.begin(), senderKeys.end(),
                        std::back_inserter(d->mSMIMEEncryptToSelfKeys),
                        NotValidTrustedSMIMEEncryptionKey);    // -= trusted (see above, too)?

    return Kleo::Ok;
}

std::vector<Kleo::KeyResolver::SplitInfo> Kleo::KeyResolver::encryptionItems(Kleo::CryptoMessageFormat f) const
{
    dump();
    std::map<CryptoMessageFormat, FormatInfo>::const_iterator it
        = d->mFormatInfoMap.find(f);
    return it != d->mFormatInfoMap.end() ? it->second.splitInfos : std::vector<SplitInfo>();
}

std::vector<GpgME::Key> Kleo::KeyResolver::signingKeys(CryptoMessageFormat f) const
{
    dump();
    std::map<CryptoMessageFormat, FormatInfo>::const_iterator it
        = d->mFormatInfoMap.find(f);
    return it != d->mFormatInfoMap.end() ? it->second.signKeys : std::vector<GpgME::Key>();
}

//
//
// Private helper methods below:
//
//

std::vector<GpgME::Key> Kleo::KeyResolver::selectKeys(
    const QString &person, const QString &msg, const std::vector<GpgME::Key> &selectedKeys) const
{
    const bool opgp = containsOpenPGP(mCryptoMessageFormats);
    const bool x509 = containsSMIME(mCryptoMessageFormats);

    QPointer<Kleo::KeySelectionDialog> dlg
        = new Kleo::KeySelectionDialog(
              i18n("Encryption Key Selection"),
              msg, KEmailAddress::extractEmailAddress(person), selectedKeys,
              Kleo::KeySelectionDialog::ValidEncryptionKeys
              & ~(opgp ? 0 : Kleo::KeySelectionDialog::OpenPGPKeys)
              & ~(x509 ? 0 : Kleo::KeySelectionDialog::SMIMEKeys),
              true, true); // multi-selection and "remember choice" box

    if (dlg->exec() != QDialog::Accepted) {
        delete dlg;
        return std::vector<GpgME::Key>();
    }

    std::vector<GpgME::Key> keys = dlg->selectedKeys();
    keys.erase(std::remove_if(keys.begin(), keys.end(),
                              NotValidEncryptionKey),
               keys.end());
    if (!keys.empty() && dlg->rememberSelection()) {
        setKeysForAddress(person, dlg->pgpKeyFingerprints(), dlg->smimeFingerprints());
    }

    delete dlg;
    return keys;
}

std::vector<GpgME::Key> Kleo::KeyResolver::getEncryptionKeys(const QString &person, bool quiet) const
{
    const QString address = canonicalAddress(person).toLower();

    // First look for this person's address in the address->key dictionary
    const QStringList fingerprints = keysForAddress(address);

    if (!fingerprints.empty()) {
        qCDebug(MESSAGECOMPOSER_LOG) << "Using encryption keys 0x"
                                     << fingerprints.join(QLatin1String(", 0x"))
                                     << "for" << person;
        std::vector<GpgME::Key> keys = lookup(fingerprints);
        if (!keys.empty()) {
            // Check if all of the keys are trusted and valid encryption keys
            if (std::find_if(keys.begin(), keys.end(),
                             NotValidTrustedEncryptionKey) != keys.end()) {   // -= trusted?
                // not ok, let the user select: this is not conditional on !quiet,
                // since it's a bug in the configuration and the user should be
                // notified about it as early as possible:
                keys = selectKeys(person,
                                  i18nc("if in your language something like "
                                        "'certificate(s)' is not possible please "
                                        "use the plural in the translation",
                                        "There is a problem with the "
                                        "encryption certificate(s) for \"%1\".\n\n"
                                        "Please re-select the certificate(s) which should "
                                        "be used for this recipient.", person),
                                  keys);
            }
            bool canceled = false;
            keys = trustedOrConfirmed(keys, address, canceled);
            if (canceled) {
                return std::vector<GpgME::Key>();
            }

            if (!keys.empty()) {
                return keys;
            }
            // keys.empty() is considered cancel by callers, so go on
        }
    }

    // Now search all public keys for matching keys
    std::vector<GpgME::Key> matchingKeys = lookup(QStringList(address));
    matchingKeys.erase(std::remove_if(matchingKeys.begin(), matchingKeys.end(),
                                      NotValidEncryptionKey), matchingKeys.end());

    // if called with quite == true (from EncryptionPreferenceCounter), we only want to
    // check if there are keys for this recipients, not (yet) their validity, so
    // don't show the untrusted encryption key warning in that case
    bool canceled = false;
    if (!quiet) {
        matchingKeys = trustedOrConfirmed(matchingKeys, address, canceled);
    }
    if (canceled) {
        return std::vector<GpgME::Key>();
    }
    if (quiet || matchingKeys.size() == 1) {
        return matchingKeys;
    }

    // no match until now, or more than one key matches; let the user
    // choose the key(s)
    // FIXME: let user get the key from keyserver
    return trustedOrConfirmed(selectKeys(person,
                                         matchingKeys.empty()
                                         ? i18nc("if in your language something like "
                                                 "'certificate(s)' is not possible please "
                                                 "use the plural in the translation",
                                                 "<qt>No valid and trusted encryption certificate was "
                                                 "found for \"%1\".<br/><br/>"
                                                 "Select the certificate(s) which should "
                                                 "be used for this recipient. If there is no suitable certificate in the list "
                                                 "you can also search for external certificates by clicking the button: "
                                                 "search for external certificates.</qt>",
                                                 person.toHtmlEscaped())
                                         : i18nc("if in your language something like "
                                                 "'certificate(s)' is not possible please "
                                                 "use the plural in the translation",
                                                 "More than one certificate matches \"%1\".\n\n"
                                                 "Select the certificate(s) which should "
                                                 "be used for this recipient.", person.toHtmlEscaped()),
                                         matchingKeys), address, canceled);
    // we can ignore 'canceled' here, since trustedOrConfirmed() returns
    // an empty vector when canceled == true, and we'd just do the same
}

std::vector<GpgME::Key> Kleo::KeyResolver::lookup(const QStringList &patterns, bool secret) const
{
    if (patterns.empty()) {
        return std::vector<GpgME::Key>();
    }
    qCDebug(MESSAGECOMPOSER_LOG) << "( \"" << patterns.join(QLatin1String("\", \"")) << "\"," << secret << ")";
    std::vector<GpgME::Key> result;
    if (mCryptoMessageFormats & (InlineOpenPGPFormat | OpenPGPMIMEFormat)) {
        if (const QGpgME::Protocol *p = QGpgME::openpgp()) {
            std::unique_ptr<QGpgME::KeyListJob> job(p->keyListJob(false, false, true));     // use validating keylisting
            if (job.get()) {
                std::vector<GpgME::Key> keys;
                job->exec(patterns, secret, keys);
                result.insert(result.end(), keys.begin(), keys.end());
            }
        }
    }
    if (mCryptoMessageFormats & (SMIMEFormat | SMIMEOpaqueFormat)) {
        if (const QGpgME::Protocol *p = QGpgME::smime()) {
            std::unique_ptr<QGpgME::KeyListJob> job(p->keyListJob(false, false, true));     // use validating keylisting
            if (job.get()) {
                std::vector<GpgME::Key> keys;
                job->exec(patterns, secret, keys);
                result.insert(result.end(), keys.begin(), keys.end());
            }
        }
    }
    qCDebug(MESSAGECOMPOSER_LOG) << " returned" << result.size() << "keys";
    return result;
}

void Kleo::KeyResolver::addKeys(const std::vector<Item> &items, CryptoMessageFormat f)
{
    dump();
    for (std::vector<Item>::const_iterator it = items.begin(); it != items.end(); ++it) {
        SplitInfo si(QStringList(it->address));
        std::remove_copy_if(it->keys.begin(), it->keys.end(),
                            std::back_inserter(si.keys), IsNotForFormat(f));
        dump();
        if (si.keys.empty()) {
            qCWarning(MESSAGECOMPOSER_LOG)
                << "Kleo::KeyResolver::addKeys(): Fix EncryptionFormatPreferenceCounter."
                << "It detected a common format, but the list of such keys for recipient \""
                << it->address << "\" is empty!";
        }
        d->mFormatInfoMap[ f ].splitInfos.push_back(si);
    }
    dump();
}

void Kleo::KeyResolver::addKeys(const std::vector<Item> &items)
{
    dump();
    for (std::vector<Item>::const_iterator it = items.begin(); it != items.end(); ++it) {
        SplitInfo si(QStringList(it->address));
        CryptoMessageFormat f = AutoFormat;
        for (unsigned int i = 0; i < numConcreteCryptoMessageFormats; ++i) {
            const CryptoMessageFormat fmt = concreteCryptoMessageFormats[i];
            if ((fmt & it->format)
                && kdtools::any(it->keys.begin(), it->keys.end(), IsForFormat(fmt))) {
                f = fmt;
                break;
            }
        }
        if (f == AutoFormat) {
            qCWarning(MESSAGECOMPOSER_LOG) << "Something went wrong. Didn't find a format for \""
                                           << it->address << "\"";
        } else {
            std::remove_copy_if(it->keys.begin(), it->keys.end(),
                                std::back_inserter(si.keys), IsNotForFormat(f));
        }
        d->mFormatInfoMap[ f ].splitInfos.push_back(si);
    }
    dump();
}

Kleo::KeyResolver::ContactPreferences Kleo::KeyResolver::lookupContactPreferences(const QString &address) const
{
#ifdef HAVE_A_FIX_FOR_LOCK
    const Private::ContactPreferencesMap::iterator it
        = d->mContactPreferencesMap.find(address);
    if (it != d->mContactPreferencesMap.end()) {
        return it->second;
    }

    Akonadi::ContactSearchJob *job = new Akonadi::ContactSearchJob();
    job->setLimit(1);
    job->setQuery(Akonadi::ContactSearchJob::Email, address);
    job->exec();

    const KContacts::Addressee::List res = job->contacts();
    ContactPreferences pref;
    if (!res.isEmpty()) {
        KContacts::Addressee addr = res.at(0);
        QString encryptPref = addr.custom(QStringLiteral("KADDRESSBOOK"), QStringLiteral("CRYPTOENCRYPTPREF"));
        pref.encryptionPreference = Kleo::stringToEncryptionPreference(encryptPref);
        QString signPref = addr.custom(QStringLiteral("KADDRESSBOOK"), QStringLiteral("CRYPTOSIGNPREF"));
        pref.signingPreference = Kleo::stringToSigningPreference(signPref);
        QString cryptoFormats = addr.custom(QStringLiteral("KADDRESSBOOK"), QStringLiteral("CRYPTOPROTOPREF"));
        pref.cryptoMessageFormat = Kleo::stringToCryptoMessageFormat(cryptoFormats);
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
        pref.pgpKeyFingerprints = addr.custom(QStringLiteral("KADDRESSBOOK"), QStringLiteral("OPENPGPFP")).split(QLatin1Char(','), QString::SkipEmptyParts);
#else
        pref.pgpKeyFingerprints = addr.custom(QStringLiteral("KADDRESSBOOK"), QStringLiteral("OPENPGPFP")).split(QLatin1Char(','), Qt::SkipEmptyParts);
#endif
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
        pref.smimeCertFingerprints = addr.custom(QStringLiteral("KADDRESSBOOK"), QStringLiteral("SMIMEFP")).split(QLatin1Char(','), QString::SkipEmptyParts);
#else
        pref.smimeCertFingerprints = addr.custom(QStringLiteral("KADDRESSBOOK"), QStringLiteral("SMIMEFP")).split(QLatin1Char(','), Qt::SkipEmptyParts);
#endif
    }
    // insert into map and grab resulting iterator
    d->mContactPreferencesMap.insert(std::make_pair(address, pref));
    return pref;
#else
    Q_UNUSED(address)
    return ContactPreferences();
#endif
}

void Kleo::KeyResolver::saveContactPreference(const QString &email, const ContactPreferences &pref) const
{
    d->mContactPreferencesMap.insert(std::make_pair(email, pref));
    MessageComposer::SaveContactPreferenceJob *saveContactPreferencesJob = new MessageComposer::SaveContactPreferenceJob(email, pref);
    saveContactPreferencesJob->start();
}

Kleo::KeyResolver::ContactPreferences::ContactPreferences()
    : encryptionPreference(UnknownPreference)
    , signingPreference(UnknownSigningPreference)
    , cryptoMessageFormat(AutoFormat)
{
}

QStringList Kleo::KeyResolver::keysForAddress(const QString &address) const
{
    if (address.isEmpty()) {
        return QStringList();
    }
    const QString addr = canonicalAddress(address).toLower();
    const ContactPreferences pref = lookupContactPreferences(addr);
    return pref.pgpKeyFingerprints + pref.smimeCertFingerprints;
}

void Kleo::KeyResolver::setKeysForAddress(const QString &address, const QStringList &pgpKeyFingerprints, const QStringList &smimeCertFingerprints) const
{
    if (address.isEmpty()) {
        return;
    }
    const QString addr = canonicalAddress(address).toLower();
    ContactPreferences pref = lookupContactPreferences(addr);
    pref.pgpKeyFingerprints = pgpKeyFingerprints;
    pref.smimeCertFingerprints = smimeCertFingerprints;
    saveContactPreference(addr, pref);
}

bool Kleo::KeyResolver::encryptToSelf() const
{
    return mEncryptToSelf;
}

bool Kleo::KeyResolver::showApprovalDialog() const
{
    return mShowApprovalDialog;
}

int Kleo::KeyResolver::encryptKeyNearExpiryWarningThresholdInDays() const
{
    return mEncryptKeyNearExpiryWarningThreshold;
}

int Kleo::KeyResolver::signingKeyNearExpiryWarningThresholdInDays() const
{
    return mSigningKeyNearExpiryWarningThreshold;
}

int Kleo::KeyResolver::encryptRootCertNearExpiryWarningThresholdInDays() const
{
    return mEncryptRootCertNearExpiryWarningThreshold;
}

int Kleo::KeyResolver::signingRootCertNearExpiryWarningThresholdInDays() const
{
    return mSigningRootCertNearExpiryWarningThreshold;
}

int Kleo::KeyResolver::encryptChainCertNearExpiryWarningThresholdInDays() const
{
    return mEncryptChainCertNearExpiryWarningThreshold;
}

int Kleo::KeyResolver::signingChainCertNearExpiryWarningThresholdInDays() const
{
    return mSigningChainCertNearExpiryWarningThreshold;
}
