Page Menu
Home
GnuPG
Search
Configure Global Search
Log In
Files
F34109917
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Size
124 KB
Subscribers
None
View Options
diff --git a/src/kleo/enum.cpp b/src/kleo/enum.cpp
index e7dee37f..0ca586db 100644
--- a/src/kleo/enum.cpp
+++ b/src/kleo/enum.cpp
@@ -1,310 +1,305 @@
/*
kleo/enum.cpp
This file is part of libkleopatra, the KDE keymanagement library
SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-libkleo.h>
#include "enum.h"
#include <libkleo/keycache.h>
#include <libkleo_debug.h>
#include <KLazyLocalizedString>
#include <KLocalizedString>
#include <QEventLoop>
#include <QString>
#include <gpgme++/key.h>
#include <gpgme++/tofuinfo.h>
#include <functional>
static const struct {
Kleo::CryptoMessageFormat format;
const KLazyLocalizedString displayName;
const char *configName;
} cryptoMessageFormats[] = {
// clang-format off
{Kleo::InlineOpenPGPFormat, kli18n("Inline OpenPGP (deprecated)"), "inline openpgp"},
{Kleo::OpenPGPMIMEFormat, kli18n("OpenPGP/MIME"), "openpgp/mime" },
{Kleo::SMIMEFormat, kli18n("S/MIME"), "s/mime" },
{Kleo::SMIMEOpaqueFormat, kli18n("S/MIME Opaque"), "s/mime opaque" },
{Kleo::AnySMIME, kli18n("Any S/MIME"), "any s/mime" },
{Kleo::AnyOpenPGP, kli18n("Any OpenPGP"), "any openpgp" },
// clang-format on
};
static const unsigned int numCryptoMessageFormats = sizeof cryptoMessageFormats / sizeof *cryptoMessageFormats;
const char *Kleo::cryptoMessageFormatToString(Kleo::CryptoMessageFormat f)
{
if (f == AutoFormat) {
return "auto";
}
for (unsigned int i = 0; i < numCryptoMessageFormats; ++i) {
if (f == cryptoMessageFormats[i].format) {
return cryptoMessageFormats[i].configName;
}
}
return nullptr;
}
QStringList Kleo::cryptoMessageFormatsToStringList(unsigned int f)
{
QStringList result;
for (unsigned int i = 0; i < numCryptoMessageFormats; ++i) {
if (f & cryptoMessageFormats[i].format) {
result.push_back(QLatin1String(cryptoMessageFormats[i].configName));
}
}
return result;
}
QString Kleo::cryptoMessageFormatToLabel(Kleo::CryptoMessageFormat f)
{
if (f == AutoFormat) {
return i18n("Any");
}
for (unsigned int i = 0; i < numCryptoMessageFormats; ++i) {
if (f == cryptoMessageFormats[i].format) {
return KLocalizedString(cryptoMessageFormats[i].displayName).toString();
}
}
return QString();
}
Kleo::CryptoMessageFormat Kleo::stringToCryptoMessageFormat(const QString &s)
{
const QString t = s.toLower();
for (unsigned int i = 0; i < numCryptoMessageFormats; ++i) {
if (t == QLatin1String(cryptoMessageFormats[i].configName)) {
return cryptoMessageFormats[i].format;
}
}
return AutoFormat;
}
unsigned int Kleo::stringListToCryptoMessageFormats(const QStringList &sl)
{
unsigned int result = 0;
for (QStringList::const_iterator it = sl.begin(); it != sl.end(); ++it) {
result |= stringToCryptoMessageFormat(*it);
}
return result;
}
// For the config values used below, see also kaddressbook/editors/cryptowidget.cpp
const char *Kleo::encryptionPreferenceToString(EncryptionPreference pref)
{
switch (pref) {
case UnknownPreference:
return nullptr;
case NeverEncrypt:
return "never";
case AlwaysEncrypt:
return "always";
case AlwaysEncryptIfPossible:
return "alwaysIfPossible";
case AlwaysAskForEncryption:
return "askAlways";
case AskWheneverPossible:
return "askWhenPossible";
}
return nullptr; // keep the compiler happy
}
Kleo::EncryptionPreference Kleo::stringToEncryptionPreference(const QString &str)
{
if (str == QLatin1String("never")) {
return NeverEncrypt;
}
if (str == QLatin1String("always")) {
return AlwaysEncrypt;
}
if (str == QLatin1String("alwaysIfPossible")) {
return AlwaysEncryptIfPossible;
}
if (str == QLatin1String("askAlways")) {
return AlwaysAskForEncryption;
}
if (str == QLatin1String("askWhenPossible")) {
return AskWheneverPossible;
}
return UnknownPreference;
}
QString Kleo::encryptionPreferenceToLabel(EncryptionPreference pref)
{
switch (pref) {
case NeverEncrypt:
return i18n("Never Encrypt");
case AlwaysEncrypt:
return i18n("Always Encrypt");
case AlwaysEncryptIfPossible:
return i18n("Always Encrypt If Possible");
case AlwaysAskForEncryption:
return i18n("Ask");
case AskWheneverPossible:
return i18n("Ask Whenever Possible");
default:
return xi18nc("no specific preference", "<placeholder>none</placeholder>");
}
}
const char *Kleo::signingPreferenceToString(SigningPreference pref)
{
switch (pref) {
case UnknownSigningPreference:
return nullptr;
case NeverSign:
return "never";
case AlwaysSign:
return "always";
case AlwaysSignIfPossible:
return "alwaysIfPossible";
case AlwaysAskForSigning:
return "askAlways";
case AskSigningWheneverPossible:
return "askWhenPossible";
}
return nullptr; // keep the compiler happy
}
Kleo::SigningPreference Kleo::stringToSigningPreference(const QString &str)
{
if (str == QLatin1String("never")) {
return NeverSign;
}
if (str == QLatin1String("always")) {
return AlwaysSign;
}
if (str == QLatin1String("alwaysIfPossible")) {
return AlwaysSignIfPossible;
}
if (str == QLatin1String("askAlways")) {
return AlwaysAskForSigning;
}
if (str == QLatin1String("askWhenPossible")) {
return AskSigningWheneverPossible;
}
return UnknownSigningPreference;
}
QString Kleo::signingPreferenceToLabel(SigningPreference pref)
{
switch (pref) {
case NeverSign:
return i18n("Never Sign");
case AlwaysSign:
return i18n("Always Sign");
case AlwaysSignIfPossible:
return i18n("Always Sign If Possible");
case AlwaysAskForSigning:
return i18n("Ask");
case AskSigningWheneverPossible:
return i18n("Ask Whenever Possible");
default:
return i18nc("no specific preference", "<none>");
}
}
Kleo::TrustLevel Kleo::trustLevel(const GpgME::Key &key)
{
TrustLevel maxTl = Level0;
for (int i = 0, c = key.numUserIDs(); i < c; ++i) {
const auto tl = trustLevel(key.userID(i));
maxTl = qMax(maxTl, tl);
if (maxTl == Level4) {
break;
}
}
return maxTl;
}
namespace
{
bool hasTrustedSignature(const GpgME::UserID &uid)
{
// lazily initialized cache
static std::shared_ptr<const Kleo::KeyCache> keyCache;
if (!keyCache) {
keyCache = Kleo::KeyCache::instance();
}
- if (!keyCache->initialized()) {
- QEventLoop el;
- QObject::connect(keyCache.get(), &Kleo::KeyCache::keyListingDone, &el, &QEventLoop::quit);
- el.exec();
- }
const auto signatures = uid.signatures();
std::vector<std::string> sigKeyIDs;
std::transform(signatures.cbegin(),
signatures.cend(),
std::back_inserter(sigKeyIDs),
std::bind(&GpgME::UserID::Signature::signerKeyID, std::placeholders::_1));
const auto keys = keyCache->findByKeyIDOrFingerprint(sigKeyIDs);
return std::any_of(keys.cbegin(), keys.cend(), [](const GpgME::Key &key) {
return key.ownerTrust() == GpgME::Key::Ultimate;
});
}
}
Kleo::TrustLevel Kleo::trustLevel(const GpgME::UserID &uid)
{
// Modelled after https://wiki.gnupg.org/EasyGpg2016/AutomatedEncryption,
// but modified to cover all cases, unlike the pseudocode in the document.
//
// TODO: Check whether the key comes from a trusted source (Cert/PKA/DANE/WKD)
switch (uid.validity()) {
case GpgME::UserID::Unknown:
case GpgME::UserID::Undefined:
case GpgME::UserID::Never:
// Not enough trust -> level 0
return Level0;
case GpgME::UserID::Marginal:
// Marginal trust without TOFU data means the key is still trusted
// through the Web of Trust -> level 2
if (uid.tofuInfo().isNull()) {
return Level2;
}
// Marginal trust with TOFU, level will depend on TOFU history
switch (uid.tofuInfo().validity()) {
case GpgME::TofuInfo::ValidityUnknown:
case GpgME::TofuInfo::Conflict:
case GpgME::TofuInfo::NoHistory:
// Marginal trust, but not enough history -> level 0
return Level0;
case GpgME::TofuInfo::LittleHistory:
// Marginal trust, but too little history -> level 1
return Level1;
case GpgME::TofuInfo::BasicHistory:
case GpgME::TofuInfo::LargeHistory:
// Marginal trust and enough history -> level 2
return Level2;
}
return Level2; // Not reached, but avoids fallthrough warnings
case GpgME::UserID::Full:
// Full trust, trust level depends whether the UserID is signed with
// at least one key with Ultimate ownertrust.
return hasTrustedSignature(uid) ? Level4 : Level3;
case GpgME::UserID::Ultimate:
// Ultimate trust -> leve 4
return Level4;
}
Q_UNREACHABLE();
}
diff --git a/src/models/keycache.cpp b/src/models/keycache.cpp
index f14a1d76..10a5b2ea 100644
--- a/src/models/keycache.cpp
+++ b/src/models/keycache.cpp
@@ -1,1768 +1,1765 @@
/* -*- mode: c++; c-basic-offset:4 -*-
models/keycache.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2007, 2008 Klarälvdalens Datakonsult AB
SPDX-FileCopyrightText: 2018 Intevation GmbH
SPDX-FileCopyrightText: 2020, 2021 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-libkleo.h>
#include "keycache.h"
#include "keycache_p.h"
#include <libkleo/algorithm.h>
#include <libkleo/compat.h>
#include <libkleo/debug.h>
#include <libkleo/dn.h>
#include <libkleo/enum.h>
#include <libkleo/filesystemwatcher.h>
#include <libkleo/keygroup.h>
#include <libkleo/keygroupconfig.h>
#include <libkleo/keyhelpers.h>
#include <libkleo/predicates.h>
#include <libkleo/qtstlhelpers.h>
#include <libkleo/stl_util.h>
#include <libkleo_debug.h>
#include <KSharedConfig>
#include <QGpgME/CryptoConfig>
#include <QGpgME/ListAllKeysJob>
#include <QGpgME/Protocol>
#include <QEventLoop>
#include <QPointer>
#include <QTimer>
#include <gpgme++/context.h>
#include <gpgme++/decryptionresult.h>
#include <gpgme++/error.h>
#include <gpgme++/key.h>
#include <gpgme++/keylistresult.h>
#include <gpgme++/verificationresult.h>
#include <gpg-error.h>
#include <algorithm>
#include <chrono>
#include <functional>
#include <iterator>
#include <utility>
using namespace std::chrono_literals;
using namespace Kleo;
using namespace GpgME;
using namespace KMime::Types;
static const unsigned int hours2ms = 1000 * 60 * 60;
//
//
// KeyCache
//
//
namespace
{
make_comparator_str(ByEMail, .first.c_str());
}
class Kleo::KeyCacheAutoRefreshSuspension
{
KeyCacheAutoRefreshSuspension()
{
qCDebug(LIBKLEO_LOG) << __func__;
auto cache = KeyCache::mutableInstance();
cache->enableFileSystemWatcher(false);
m_refreshInterval = cache->refreshInterval();
cache->setRefreshInterval(0);
cache->cancelKeyListing();
m_cache = cache;
}
public:
~KeyCacheAutoRefreshSuspension()
{
qCDebug(LIBKLEO_LOG) << __func__;
if (auto cache = m_cache.lock()) {
cache->enableFileSystemWatcher(true);
cache->setRefreshInterval(m_refreshInterval);
}
}
static std::shared_ptr<KeyCacheAutoRefreshSuspension> instance()
{
static std::weak_ptr<KeyCacheAutoRefreshSuspension> self;
if (auto s = self.lock()) {
return s;
} else {
s = std::shared_ptr<KeyCacheAutoRefreshSuspension>{new KeyCacheAutoRefreshSuspension{}};
self = s;
return s;
}
}
private:
std::weak_ptr<KeyCache> m_cache;
int m_refreshInterval = 0;
};
class KeyCache::Private
{
friend class ::Kleo::KeyCache;
KeyCache *const q;
public:
explicit Private(KeyCache *qq)
: q(qq)
, m_refreshInterval(1)
, m_initalized(false)
, m_pgpOnly(true)
, m_remarks_enabled(false)
{
connect(&m_autoKeyListingTimer, &QTimer::timeout, q, [this]() {
q->startKeyListing();
});
updateAutoKeyListingTimer();
}
~Private()
{
if (m_refreshJob) {
m_refreshJob->cancel();
}
}
template<template<template<typename U> class Op> class Comp>
std::vector<Key>::const_iterator find(const std::vector<Key> &keys, const char *key) const
{
ensureCachePopulated();
const auto it = std::lower_bound(keys.begin(), keys.end(), key, Comp<std::less>());
if (it == keys.end() || Comp<std::equal_to>()(*it, key)) {
return it;
} else {
return keys.end();
}
}
template<template<template<typename U> class Op> class Comp>
std::vector<Subkey>::const_iterator find(const std::vector<Subkey> &keys, const char *key) const
{
ensureCachePopulated();
const auto it = std::lower_bound(keys.begin(), keys.end(), key, Comp<std::less>());
if (it == keys.end() || Comp<std::equal_to>()(*it, key)) {
return it;
} else {
return keys.end();
}
}
std::vector<Key>::const_iterator find_fpr(const char *fpr) const
{
return find<_detail::ByFingerprint>(by.fpr, fpr);
}
std::pair<std::vector<std::pair<std::string, Key>>::const_iterator, std::vector<std::pair<std::string, Key>>::const_iterator>
find_email(const char *email) const
{
ensureCachePopulated();
return std::equal_range(by.email.begin(), by.email.end(), email, ByEMail<std::less>());
}
std::vector<Key> find_mailbox(const QString &email, bool sign) const;
std::vector<Subkey>::const_iterator find_keygrip(const char *keygrip) const
{
return find<_detail::ByKeyGrip>(by.keygrip, keygrip);
}
std::vector<Subkey>::const_iterator find_subkeyid(const char *subkeyid) const
{
return find<_detail::ByKeyID>(by.subkeyid, subkeyid);
}
std::vector<Key>::const_iterator find_keyid(const char *keyid) const
{
return find<_detail::ByKeyID>(by.keyid, keyid);
}
std::vector<Key>::const_iterator find_shortkeyid(const char *shortkeyid) const
{
return find<_detail::ByShortKeyID>(by.shortkeyid, shortkeyid);
}
std::pair<std::vector<Key>::const_iterator, std::vector<Key>::const_iterator> find_subjects(const char *chain_id) const
{
ensureCachePopulated();
return std::equal_range(by.chainid.begin(), by.chainid.end(), chain_id, _detail::ByChainID<std::less>());
}
void refreshJobDone(const KeyListResult &result);
void setRefreshInterval(int interval)
{
m_refreshInterval = interval;
updateAutoKeyListingTimer();
}
int refreshInterval() const
{
return m_refreshInterval;
}
void updateAutoKeyListingTimer()
{
setAutoKeyListingInterval(hours2ms * m_refreshInterval);
}
void setAutoKeyListingInterval(int ms)
{
m_autoKeyListingTimer.stop();
m_autoKeyListingTimer.setInterval(ms);
if (ms != 0) {
m_autoKeyListingTimer.start();
}
}
void ensureCachePopulated() const;
void readGroupsFromGpgConf()
{
// According to Werner Koch groups are more of a hack to solve
// a valid usecase (e.g. several keys defined for an internal mailing list)
// that won't make it in the proper keylist interface. And using gpgconf
// was the suggested way to support groups.
auto conf = QGpgME::cryptoConfig();
if (!conf) {
return;
}
auto entry = getCryptoConfigEntry(conf, "gpg", "group");
if (!entry) {
return;
}
// collect the key fingerprints for all groups read from the configuration
QMap<QString, QStringList> fingerprints;
const auto stringValueList = entry->stringValueList();
for (const QString &value : stringValueList) {
const QStringList split = value.split(QLatin1Char('='));
if (split.size() != 2) {
qCDebug(LIBKLEO_LOG) << "Ignoring invalid group config:" << value;
continue;
}
const QString groupName = split[0];
const QString fingerprint = split[1];
fingerprints[groupName].push_back(fingerprint);
}
// add all groups read from the configuration to the list of groups
for (auto it = fingerprints.cbegin(); it != fingerprints.cend(); ++it) {
const QString groupName = it.key();
const std::vector<Key> groupKeys = q->findByFingerprint(toStdStrings(it.value()));
KeyGroup g(groupName, groupName, groupKeys, KeyGroup::GnuPGConfig);
m_groups.push_back(g);
}
}
void readGroupsFromGroupsConfig()
{
Q_ASSERT(m_groupConfig);
if (!m_groupConfig) {
qCWarning(LIBKLEO_LOG) << __func__ << "group config not set";
return;
}
m_groups = m_groupConfig->readGroups();
}
KeyGroup writeGroupToGroupsConfig(const KeyGroup &group)
{
Q_ASSERT(m_groupConfig);
if (!m_groupConfig) {
qCWarning(LIBKLEO_LOG) << __func__ << "group config not set";
return {};
}
Q_ASSERT(!group.isNull());
Q_ASSERT(group.source() == KeyGroup::ApplicationConfig);
if (group.isNull() || group.source() != KeyGroup::ApplicationConfig) {
qCDebug(LIBKLEO_LOG) << __func__ << "group cannot be written to application configuration:" << group;
return group;
}
return m_groupConfig->writeGroup(group);
}
bool removeGroupFromGroupsConfig(const KeyGroup &group)
{
Q_ASSERT(m_groupConfig);
if (!m_groupConfig) {
qCWarning(LIBKLEO_LOG) << __func__ << "group config not set";
return false;
}
Q_ASSERT(!group.isNull());
Q_ASSERT(group.source() == KeyGroup::ApplicationConfig);
if (group.isNull() || group.source() != KeyGroup::ApplicationConfig) {
qCDebug(LIBKLEO_LOG) << __func__ << "group cannot be removed from application configuration:" << group;
return false;
}
return m_groupConfig->removeGroup(group);
}
void updateGroupCache()
{
// Update Group Keys
// this is a quick thing as it only involves reading the config
// so no need for a job.
m_groups.clear();
if (m_groupsEnabled) {
readGroupsFromGpgConf();
readGroupsFromGroupsConfig();
}
}
bool insert(const KeyGroup &group)
{
Q_ASSERT(!group.isNull());
Q_ASSERT(group.source() == KeyGroup::ApplicationConfig);
if (group.isNull() || group.source() != KeyGroup::ApplicationConfig) {
qCDebug(LIBKLEO_LOG) << "KeyCache::Private::insert - Invalid group:" << group;
return false;
}
const auto it = std::find_if(m_groups.cbegin(), m_groups.cend(), [group](const auto &g) {
return g.source() == group.source() && g.id() == group.id();
});
if (it != m_groups.cend()) {
qCDebug(LIBKLEO_LOG) << "KeyCache::Private::insert - Group already present in list of groups:" << group;
return false;
}
const KeyGroup savedGroup = writeGroupToGroupsConfig(group);
if (savedGroup.isNull()) {
qCDebug(LIBKLEO_LOG) << "KeyCache::Private::insert - Writing group" << group.id() << "to config file failed";
return false;
}
m_groups.push_back(savedGroup);
Q_EMIT q->groupAdded(savedGroup);
return true;
}
bool update(const KeyGroup &group)
{
Q_ASSERT(!group.isNull());
Q_ASSERT(group.source() == KeyGroup::ApplicationConfig);
if (group.isNull() || group.source() != KeyGroup::ApplicationConfig) {
qCDebug(LIBKLEO_LOG) << "KeyCache::Private::update - Invalid group:" << group;
return false;
}
const auto it = std::find_if(m_groups.cbegin(), m_groups.cend(), [group](const auto &g) {
return g.source() == group.source() && g.id() == group.id();
});
if (it == m_groups.cend()) {
qCDebug(LIBKLEO_LOG) << "KeyCache::Private::update - Group not found in list of groups:" << group;
return false;
}
const auto groupIndex = std::distance(m_groups.cbegin(), it);
const KeyGroup savedGroup = writeGroupToGroupsConfig(group);
if (savedGroup.isNull()) {
qCDebug(LIBKLEO_LOG) << "KeyCache::Private::update - Writing group" << group.id() << "to config file failed";
return false;
}
m_groups[groupIndex] = savedGroup;
Q_EMIT q->groupUpdated(savedGroup);
return true;
}
bool remove(const KeyGroup &group)
{
Q_ASSERT(!group.isNull());
Q_ASSERT(group.source() == KeyGroup::ApplicationConfig);
if (group.isNull() || group.source() != KeyGroup::ApplicationConfig) {
qCDebug(LIBKLEO_LOG) << "KeyCache::Private::remove - Invalid group:" << group;
return false;
}
const auto it = std::find_if(m_groups.cbegin(), m_groups.cend(), [group](const auto &g) {
return g.source() == group.source() && g.id() == group.id();
});
if (it == m_groups.cend()) {
qCDebug(LIBKLEO_LOG) << "KeyCache::Private::remove - Group not found in list of groups:" << group;
return false;
}
const bool success = removeGroupFromGroupsConfig(group);
if (!success) {
qCDebug(LIBKLEO_LOG) << "KeyCache::Private::remove - Removing group" << group.id() << "from config file failed";
return false;
}
m_groups.erase(it);
Q_EMIT q->groupRemoved(group);
return true;
}
private:
QPointer<RefreshKeysJob> m_refreshJob;
std::vector<std::shared_ptr<FileSystemWatcher>> m_fsWatchers;
QTimer m_autoKeyListingTimer;
int m_refreshInterval;
struct By {
std::vector<Key> fpr, keyid, shortkeyid, chainid;
std::vector<std::pair<std::string, Key>> email;
std::vector<Subkey> subkeyid, keygrip;
} by;
bool m_initalized;
bool m_pgpOnly;
bool m_remarks_enabled;
bool m_groupsEnabled = false;
std::shared_ptr<KeyGroupConfig> m_groupConfig;
std::vector<KeyGroup> m_groups;
};
std::shared_ptr<const KeyCache> KeyCache::instance()
{
return mutableInstance();
}
std::shared_ptr<KeyCache> KeyCache::mutableInstance()
{
static std::weak_ptr<KeyCache> self;
try {
return std::shared_ptr<KeyCache>(self);
} catch (const std::bad_weak_ptr &) {
const std::shared_ptr<KeyCache> s(new KeyCache);
self = s;
return s;
}
}
KeyCache::KeyCache()
: QObject()
, d(new Private(this))
{
}
KeyCache::~KeyCache()
{
}
void KeyCache::setGroupsEnabled(bool enabled)
{
d->m_groupsEnabled = enabled;
if (d->m_initalized) {
d->updateGroupCache();
}
}
void KeyCache::setGroupConfig(const std::shared_ptr<KeyGroupConfig> &groupConfig)
{
d->m_groupConfig = groupConfig;
}
void KeyCache::enableFileSystemWatcher(bool enable)
{
for (const auto &i : std::as_const(d->m_fsWatchers)) {
i->setEnabled(enable);
}
}
void KeyCache::setRefreshInterval(int hours)
{
d->setRefreshInterval(hours);
}
int KeyCache::refreshInterval() const
{
return d->refreshInterval();
}
std::shared_ptr<KeyCacheAutoRefreshSuspension> KeyCache::suspendAutoRefresh()
{
return KeyCacheAutoRefreshSuspension::instance();
}
void KeyCache::reload(GpgME::Protocol /*proto*/)
{
if (d->m_refreshJob) {
return;
}
d->updateAutoKeyListingTimer();
enableFileSystemWatcher(false);
d->m_refreshJob = new RefreshKeysJob(this);
connect(d->m_refreshJob.data(), &RefreshKeysJob::done, this, [this](const GpgME::KeyListResult &r) {
d->refreshJobDone(r);
});
connect(d->m_refreshJob.data(), &RefreshKeysJob::canceled, this, [this]() {
d->m_refreshJob.clear();
});
d->m_refreshJob->start();
}
void KeyCache::cancelKeyListing()
{
if (!d->m_refreshJob) {
return;
}
d->m_refreshJob->cancel();
}
void KeyCache::addFileSystemWatcher(const std::shared_ptr<FileSystemWatcher> &watcher)
{
if (!watcher) {
return;
}
d->m_fsWatchers.push_back(watcher);
connect(watcher.get(), &FileSystemWatcher::directoryChanged, this, [this]() {
startKeyListing();
});
connect(watcher.get(), &FileSystemWatcher::fileChanged, this, [this]() {
startKeyListing();
});
watcher->setEnabled(d->m_refreshJob.isNull());
}
void KeyCache::enableRemarks(bool value)
{
if (!d->m_remarks_enabled && value) {
d->m_remarks_enabled = value;
if (d->m_initalized && !d->m_refreshJob) {
qCDebug(LIBKLEO_LOG) << "Reloading keycache with remarks enabled";
reload();
}
} else {
d->m_remarks_enabled = value;
}
}
bool KeyCache::remarksEnabled() const
{
return d->m_remarks_enabled;
}
void KeyCache::Private::refreshJobDone(const KeyListResult &result)
{
m_refreshJob.clear();
q->enableFileSystemWatcher(true);
if (!m_initalized && q->remarksEnabled()) {
// trigger another key listing to read signatures and signature notations
QMetaObject::invokeMethod(
q,
[this]() {
qCDebug(LIBKLEO_LOG) << "Reloading keycache with remarks enabled";
q->reload();
},
Qt::QueuedConnection);
}
m_initalized = true;
updateGroupCache();
Q_EMIT q->keyListingDone(result);
}
const Key &KeyCache::findByFingerprint(const char *fpr) const
{
const std::vector<Key>::const_iterator it = d->find_fpr(fpr);
if (it == d->by.fpr.end()) {
static const Key null;
return null;
} else {
return *it;
}
}
const Key &KeyCache::findByFingerprint(const std::string &fpr) const
{
return findByFingerprint(fpr.c_str());
}
std::vector<GpgME::Key> KeyCache::findByFingerprint(const std::vector<std::string> &fprs) const
{
std::vector<Key> keys;
keys.reserve(fprs.size());
for (const auto &fpr : fprs) {
const Key key = findByFingerprint(fpr.c_str());
if (key.isNull()) {
qCDebug(LIBKLEO_LOG) << __func__ << "Ignoring unknown key with fingerprint:" << fpr.c_str();
continue;
}
keys.push_back(key);
}
return keys;
}
std::vector<Key> KeyCache::findByEMailAddress(const char *email) const
{
const auto pair = d->find_email(email);
std::vector<Key> result;
result.reserve(std::distance(pair.first, pair.second));
std::transform(pair.first, pair.second, std::back_inserter(result), [](const std::pair<std::string, Key> &pair) {
return pair.second;
});
return result;
}
std::vector<Key> KeyCache::findByEMailAddress(const std::string &email) const
{
return findByEMailAddress(email.c_str());
}
const Key &KeyCache::findByShortKeyID(const char *id) const
{
const std::vector<Key>::const_iterator it = d->find_shortkeyid(id);
if (it != d->by.shortkeyid.end()) {
return *it;
}
static const Key null;
return null;
}
const Key &KeyCache::findByShortKeyID(const std::string &id) const
{
return findByShortKeyID(id.c_str());
}
const Key &KeyCache::findByKeyIDOrFingerprint(const char *id) const
{
{
// try by.fpr first:
const std::vector<Key>::const_iterator it = d->find_fpr(id);
if (it != d->by.fpr.end()) {
return *it;
}
}
{
// try by.keyid next:
const std::vector<Key>::const_iterator it = d->find_keyid(id);
if (it != d->by.keyid.end()) {
return *it;
}
}
static const Key null;
return null;
}
const Key &KeyCache::findByKeyIDOrFingerprint(const std::string &id) const
{
return findByKeyIDOrFingerprint(id.c_str());
}
std::vector<Key> KeyCache::findByKeyIDOrFingerprint(const std::vector<std::string> &ids) const
{
std::vector<std::string> keyids;
std::remove_copy_if(ids.begin(), ids.end(), std::back_inserter(keyids), [](const std::string &str) {
return !str.c_str() || !*str.c_str();
});
// this is just case-insensitive string search:
std::sort(keyids.begin(), keyids.end(), _detail::ByFingerprint<std::less>());
std::vector<Key> result;
result.reserve(keyids.size()); // dups shouldn't happen
d->ensureCachePopulated();
kdtools::set_intersection(d->by.fpr.begin(),
d->by.fpr.end(),
keyids.begin(),
keyids.end(),
std::back_inserter(result),
_detail::ByFingerprint<std::less>());
if (result.size() < keyids.size()) {
// note that By{Fingerprint,KeyID,ShortKeyID} define the same
// order for _strings_
kdtools::set_intersection(d->by.keyid.begin(),
d->by.keyid.end(),
keyids.begin(),
keyids.end(),
std::back_inserter(result),
_detail::ByKeyID<std::less>());
}
// duplicates shouldn't happen, but make sure nonetheless:
std::sort(result.begin(), result.end(), _detail::ByFingerprint<std::less>());
result.erase(std::unique(result.begin(), result.end(), _detail::ByFingerprint<std::equal_to>()), result.end());
// we skip looking into short key ids here, as it's highly
// unlikely they're used for this purpose. We might need to revise
// this decision, but only after testing.
return result;
}
const Subkey &KeyCache::findSubkeyByKeyGrip(const char *grip, Protocol protocol) const
{
static const Subkey null;
d->ensureCachePopulated();
const auto range = std::equal_range(d->by.keygrip.begin(), d->by.keygrip.end(), grip, _detail::ByKeyGrip<std::less>());
if (range.first == range.second) {
return null;
} else if (protocol == UnknownProtocol) {
return *range.first;
} else {
for (auto it = range.first; it != range.second; ++it) {
if (it->parent().protocol() == protocol) {
return *it;
}
}
}
return null;
}
const Subkey &KeyCache::findSubkeyByKeyGrip(const std::string &grip, Protocol protocol) const
{
return findSubkeyByKeyGrip(grip.c_str(), protocol);
}
std::vector<Subkey> KeyCache::findSubkeysByKeyID(const std::vector<std::string> &ids) const
{
std::vector<std::string> sorted;
sorted.reserve(ids.size());
std::remove_copy_if(ids.begin(), ids.end(), std::back_inserter(sorted), [](const std::string &str) {
return !str.c_str() || !*str.c_str();
});
std::sort(sorted.begin(), sorted.end(), _detail::ByKeyID<std::less>());
std::vector<Subkey> result;
d->ensureCachePopulated();
kdtools::set_intersection(d->by.subkeyid.begin(),
d->by.subkeyid.end(),
sorted.begin(),
sorted.end(),
std::back_inserter(result),
_detail::ByKeyID<std::less>());
return result;
}
std::vector<Key> KeyCache::findRecipients(const DecryptionResult &res) const
{
std::vector<std::string> keyids;
const auto recipients = res.recipients();
for (const DecryptionResult::Recipient &r : recipients) {
if (const char *kid = r.keyID()) {
keyids.push_back(kid);
}
}
const std::vector<Subkey> subkeys = findSubkeysByKeyID(keyids);
std::vector<Key> result;
result.reserve(subkeys.size());
std::transform(subkeys.begin(), subkeys.end(), std::back_inserter(result), std::mem_fn(&Subkey::parent));
std::sort(result.begin(), result.end(), _detail::ByFingerprint<std::less>());
result.erase(std::unique(result.begin(), result.end(), _detail::ByFingerprint<std::equal_to>()), result.end());
return result;
}
std::vector<Key> KeyCache::findSigners(const VerificationResult &res) const
{
std::vector<std::string> fprs;
const auto signatures = res.signatures();
for (const Signature &s : signatures) {
if (const char *fpr = s.fingerprint()) {
fprs.push_back(fpr);
}
}
return findByKeyIDOrFingerprint(fprs);
}
std::vector<Key> KeyCache::findSigningKeysByMailbox(const QString &mb) const
{
return d->find_mailbox(mb, true);
}
std::vector<Key> KeyCache::findEncryptionKeysByMailbox(const QString &mb) const
{
return d->find_mailbox(mb, false);
}
namespace
{
#define DO(op, meth, meth2) \
if (op key.meth()) { \
} else { \
qDebug("rejecting for signing: %s: %s", #meth2, key.primaryFingerprint()); \
return false; \
}
#define ACCEPT(meth) DO(!!, meth, !meth)
#define REJECT(meth) DO(!, meth, meth)
struct ready_for_signing {
bool operator()(const Key &key) const
{
ACCEPT(hasSecret);
#if GPGMEPP_KEY_HAS_HASCERTIFY_SIGN_ENCRYPT_AUTHENTICATE
ACCEPT(hasSign);
#else
ACCEPT(canSign);
#endif
REJECT(isRevoked);
REJECT(isExpired);
REJECT(isDisabled);
REJECT(isInvalid);
return true;
#undef DO
}
};
#define DO(op, meth, meth2) \
if (op key.meth()) { \
} else { \
qDebug("rejecting for encrypting: %s: %s", #meth2, key.primaryFingerprint()); \
return false; \
}
struct ready_for_encryption {
bool operator()(const Key &key) const
{
#if 1
#if GPGMEPP_KEY_HAS_HASCERTIFY_SIGN_ENCRYPT_AUTHENTICATE
ACCEPT(hasEncrypt);
#else
ACCEPT(canEncrypt);
#endif
REJECT(isRevoked);
REJECT(isExpired);
REJECT(isDisabled);
REJECT(isInvalid);
return true;
#else
return key.hasEncrypt() && !key.isRevoked() && !key.isExpired() && !key.isDisabled() && !key.isInvalid();
#endif
}
#undef DO
#undef ACCEPT
#undef REJECT
};
}
std::vector<Key> KeyCache::Private::find_mailbox(const QString &email, bool sign) const
{
if (email.isEmpty()) {
return std::vector<Key>();
}
const auto pair = find_email(email.toUtf8().constData());
std::vector<Key> result;
result.reserve(std::distance(pair.first, pair.second));
if (sign) {
kdtools::copy_2nd_if(pair.first, pair.second, std::back_inserter(result), ready_for_signing());
} else {
kdtools::copy_2nd_if(pair.first, pair.second, std::back_inserter(result), ready_for_encryption());
}
return result;
}
std::vector<Key> KeyCache::findSubjects(const GpgME::Key &key, Options options) const
{
return findSubjects(std::vector<Key>(1, key), options);
}
std::vector<Key> KeyCache::findSubjects(const std::vector<Key> &keys, Options options) const
{
return findSubjects(keys.begin(), keys.end(), options);
}
std::vector<Key> KeyCache::findSubjects(std::vector<Key>::const_iterator first, std::vector<Key>::const_iterator last, Options options) const
{
if (first == last) {
return std::vector<Key>();
}
std::vector<Key> result;
while (first != last) {
const auto pair = d->find_subjects(first->primaryFingerprint());
result.insert(result.end(), pair.first, pair.second);
++first;
}
std::sort(result.begin(), result.end(), _detail::ByFingerprint<std::less>());
result.erase(std::unique(result.begin(), result.end(), _detail::ByFingerprint<std::equal_to>()), result.end());
if (options & RecursiveSearch) {
const std::vector<Key> furtherSubjects = findSubjects(result, options);
std::vector<Key> combined;
combined.reserve(result.size() + furtherSubjects.size());
std::merge(result.begin(),
result.end(),
furtherSubjects.begin(),
furtherSubjects.end(),
std::back_inserter(combined),
_detail::ByFingerprint<std::less>());
combined.erase(std::unique(combined.begin(), combined.end(), _detail::ByFingerprint<std::equal_to>()), combined.end());
result.swap(combined);
}
return result;
}
std::vector<Key> KeyCache::findIssuers(const Key &key, Options options) const
{
std::vector<Key> result;
if (key.isNull()) {
return result;
}
if (options & IncludeSubject) {
result.push_back(key);
}
if (key.isRoot()) {
return result;
}
const Key &issuer = findByFingerprint(key.chainID());
if (issuer.isNull()) {
return result;
}
result.push_back(issuer);
if (!(options & RecursiveSearch)) {
return result;
}
while (true) {
const Key &issuer = findByFingerprint(result.back().chainID());
if (issuer.isNull()) {
break;
}
const bool chainAlreadyContainsIssuer = Kleo::contains_if(result, [issuer](const auto &key) {
return _detail::ByFingerprint<std::equal_to>()(issuer, key);
});
// we also add the issuer if the chain already contains it, so that
// the user can spot the recursion
result.push_back(issuer);
if (issuer.isRoot() || chainAlreadyContainsIssuer) {
break;
}
}
return result;
}
static std::string email(const UserID &uid)
{
// Prefer the gnupg normalized one
const std::string addr = uid.addrSpec();
if (!addr.empty()) {
return addr;
}
const std::string email = uid.email();
if (email.empty()) {
return DN(uid.id())[QStringLiteral("EMAIL")].trimmed().toUtf8().constData();
}
if (email[0] == '<' && email[email.size() - 1] == '>') {
return email.substr(1, email.size() - 2);
} else {
return email;
}
}
static std::vector<std::string> emails(const Key &key)
{
std::vector<std::string> emails;
const auto userIDs = key.userIDs();
for (const UserID &uid : userIDs) {
const std::string e = email(uid);
if (!e.empty()) {
emails.push_back(e);
}
}
std::sort(emails.begin(), emails.end(), ByEMail<std::less>());
emails.erase(std::unique(emails.begin(), emails.end(), ByEMail<std::equal_to>()), emails.end());
return emails;
}
void KeyCache::remove(const Key &key)
{
if (key.isNull()) {
return;
}
const char *fpr = key.primaryFingerprint();
if (!fpr) {
return;
}
- Q_EMIT aboutToRemove(key);
-
{
const auto range = std::equal_range(d->by.fpr.begin(), d->by.fpr.end(), fpr, _detail::ByFingerprint<std::less>());
d->by.fpr.erase(range.first, range.second);
}
if (const char *keyid = key.keyID()) {
const auto range = std::equal_range(d->by.keyid.begin(), d->by.keyid.end(), keyid, _detail::ByKeyID<std::less>());
const auto it = std::remove_if(range.first, range.second, [fpr](const GpgME::Key &key) {
return _detail::ByFingerprint<std::equal_to>()(fpr, key);
});
d->by.keyid.erase(it, range.second);
}
if (const char *shortkeyid = key.shortKeyID()) {
const auto range = std::equal_range(d->by.shortkeyid.begin(), d->by.shortkeyid.end(), shortkeyid, _detail::ByShortKeyID<std::less>());
const auto it = std::remove_if(range.first, range.second, [fpr](const GpgME::Key &key) {
return _detail::ByFingerprint<std::equal_to>()(fpr, key);
});
d->by.shortkeyid.erase(it, range.second);
}
if (const char *chainid = key.chainID()) {
const auto range = std::equal_range(d->by.chainid.begin(), d->by.chainid.end(), chainid, _detail::ByChainID<std::less>());
const auto range2 = std::equal_range(range.first, range.second, fpr, _detail::ByFingerprint<std::less>());
d->by.chainid.erase(range2.first, range2.second);
}
const auto emailsKey{emails(key)};
for (const std::string &email : emailsKey) {
const auto range = std::equal_range(d->by.email.begin(), d->by.email.end(), email, ByEMail<std::less>());
const auto it = std::remove_if(range.first, range.second, [fpr](const std::pair<std::string, Key> &pair) {
return qstricmp(fpr, pair.second.primaryFingerprint()) == 0;
});
d->by.email.erase(it, range.second);
}
const auto keySubKeys{key.subkeys()};
for (const Subkey &subkey : keySubKeys) {
if (const char *keyid = subkey.keyID()) {
const auto range = std::equal_range(d->by.subkeyid.begin(), d->by.subkeyid.end(), keyid, _detail::ByKeyID<std::less>());
const auto it = std::remove_if(range.first, range.second, [fpr](const Subkey &subkey) {
return !qstricmp(fpr, subkey.parent().primaryFingerprint());
});
d->by.subkeyid.erase(it, range.second);
}
if (const char *keygrip = subkey.keyGrip()) {
const auto range = std::equal_range(d->by.keygrip.begin(), d->by.keygrip.end(), keygrip, _detail::ByKeyGrip<std::less>());
const auto it = std::remove_if(range.first, range.second, [fpr](const Subkey &subkey) {
return !qstricmp(fpr, subkey.parent().primaryFingerprint());
});
d->by.keygrip.erase(it, range.second);
}
}
}
void KeyCache::remove(const std::vector<Key> &keys)
{
for (const Key &key : keys) {
remove(key);
}
}
const std::vector<GpgME::Key> &KeyCache::keys() const
{
d->ensureCachePopulated();
return d->by.fpr;
}
std::vector<Key> KeyCache::secretKeys() const
{
std::vector<Key> keys = this->keys();
keys.erase(std::remove_if(keys.begin(),
keys.end(),
[](const Key &key) {
return !key.hasSecret();
}),
keys.end());
return keys;
}
KeyGroup KeyCache::group(const QString &id) const
{
KeyGroup result{};
const auto it = std::find_if(std::cbegin(d->m_groups), std::cend(d->m_groups), [id](const auto &g) {
return g.id() == id;
});
if (it != std::cend(d->m_groups)) {
result = *it;
}
return result;
}
std::vector<KeyGroup> KeyCache::groups() const
{
d->ensureCachePopulated();
return d->m_groups;
}
std::vector<KeyGroup> KeyCache::configurableGroups() const
{
std::vector<KeyGroup> groups;
groups.reserve(d->m_groups.size());
std::copy_if(d->m_groups.cbegin(), d->m_groups.cend(), std::back_inserter(groups), [](const KeyGroup &group) {
return group.source() == KeyGroup::ApplicationConfig;
});
return groups;
}
namespace
{
bool compareById(const KeyGroup &lhs, const KeyGroup &rhs)
{
return lhs.id() < rhs.id();
}
std::vector<KeyGroup> sortedById(std::vector<KeyGroup> groups)
{
std::sort(groups.begin(), groups.end(), &compareById);
return groups;
}
}
void KeyCache::saveConfigurableGroups(const std::vector<KeyGroup> &groups)
{
const std::vector<KeyGroup> oldGroups = sortedById(configurableGroups());
const std::vector<KeyGroup> newGroups = sortedById(groups);
{
std::vector<KeyGroup> removedGroups;
std::set_difference(oldGroups.begin(), oldGroups.end(), newGroups.begin(), newGroups.end(), std::back_inserter(removedGroups), &compareById);
for (const auto &group : std::as_const(removedGroups)) {
qCDebug(LIBKLEO_LOG) << "Removing group" << group;
d->remove(group);
}
}
{
std::vector<KeyGroup> updatedGroups;
std::set_intersection(newGroups.begin(), newGroups.end(), oldGroups.begin(), oldGroups.end(), std::back_inserter(updatedGroups), &compareById);
for (const auto &group : std::as_const(updatedGroups)) {
qCDebug(LIBKLEO_LOG) << "Updating group" << group;
d->update(group);
}
}
{
std::vector<KeyGroup> addedGroups;
std::set_difference(newGroups.begin(), newGroups.end(), oldGroups.begin(), oldGroups.end(), std::back_inserter(addedGroups), &compareById);
for (const auto &group : std::as_const(addedGroups)) {
qCDebug(LIBKLEO_LOG) << "Adding group" << group;
d->insert(group);
}
}
Q_EMIT keysMayHaveChanged();
}
bool KeyCache::insert(const KeyGroup &group)
{
if (!d->insert(group)) {
return false;
}
Q_EMIT keysMayHaveChanged();
return true;
}
bool KeyCache::update(const KeyGroup &group)
{
if (!d->update(group)) {
return false;
}
Q_EMIT keysMayHaveChanged();
return true;
}
bool KeyCache::remove(const KeyGroup &group)
{
if (!d->remove(group)) {
return false;
}
Q_EMIT keysMayHaveChanged();
return true;
}
void KeyCache::refresh(const std::vector<Key> &keys)
{
// make this better...
clear();
insert(keys);
}
void KeyCache::insert(const Key &key)
{
insert(std::vector<Key>(1, key));
}
namespace
{
template<template<template<typename T> class Op> class T1, template<template<typename T> class Op> class T2>
struct lexicographically {
using result_type = bool;
template<typename U, typename V>
bool operator()(const U &lhs, const V &rhs) const
{
return T1<std::less>()(lhs, rhs) //
|| (T1<std::equal_to>()(lhs, rhs) && T2<std::less>()(lhs, rhs));
}
};
}
void KeyCache::insert(const std::vector<Key> &keys)
{
// 1. remove those with empty fingerprints:
std::vector<Key> sorted;
sorted.reserve(keys.size());
std::remove_copy_if(keys.begin(), keys.end(), std::back_inserter(sorted), [](const Key &key) {
auto fp = key.primaryFingerprint();
return !fp || !*fp;
});
Q_FOREACH (const Key &key, sorted) {
remove(key); // this is sub-optimal, but makes implementation from here on much easier
}
// 2. sort by fingerprint:
std::sort(sorted.begin(), sorted.end(), _detail::ByFingerprint<std::less>());
// 2a. insert into fpr index:
std::vector<Key> by_fpr;
by_fpr.reserve(sorted.size() + d->by.fpr.size());
std::merge(sorted.begin(), sorted.end(), d->by.fpr.begin(), d->by.fpr.end(), std::back_inserter(by_fpr), _detail::ByFingerprint<std::less>());
// 3. build email index:
std::vector<std::pair<std::string, Key>> pairs;
pairs.reserve(sorted.size());
for (const Key &key : std::as_const(sorted)) {
const std::vector<std::string> emails = ::emails(key);
for (const std::string &e : emails) {
pairs.push_back(std::make_pair(e, key));
}
}
std::sort(pairs.begin(), pairs.end(), ByEMail<std::less>());
// 3a. insert into email index:
std::vector<std::pair<std::string, Key>> by_email;
by_email.reserve(pairs.size() + d->by.email.size());
std::merge(pairs.begin(), pairs.end(), d->by.email.begin(), d->by.email.end(), std::back_inserter(by_email), ByEMail<std::less>());
// 3.5: stable-sort by chain-id (effectively lexicographically<ByChainID,ByFingerprint>)
std::stable_sort(sorted.begin(), sorted.end(), _detail::ByChainID<std::less>());
// 3.5a: insert into chain-id index:
std::vector<Key> nonroot;
nonroot.reserve(sorted.size());
std::vector<Key> by_chainid;
by_chainid.reserve(sorted.size() + d->by.chainid.size());
std::copy_if(sorted.cbegin(), sorted.cend(), std::back_inserter(nonroot), [](const Key &key) {
return !key.isRoot();
});
std::merge(nonroot.cbegin(),
nonroot.cend(),
d->by.chainid.cbegin(),
d->by.chainid.cend(),
std::back_inserter(by_chainid),
lexicographically<_detail::ByChainID, _detail::ByFingerprint>());
// 4. sort by key id:
std::sort(sorted.begin(), sorted.end(), _detail::ByKeyID<std::less>());
// 4a. insert into keyid index:
std::vector<Key> by_keyid;
by_keyid.reserve(sorted.size() + d->by.keyid.size());
std::merge(sorted.begin(), sorted.end(), d->by.keyid.begin(), d->by.keyid.end(), std::back_inserter(by_keyid), _detail::ByKeyID<std::less>());
// 5. sort by short key id:
std::sort(sorted.begin(), sorted.end(), _detail::ByShortKeyID<std::less>());
// 5a. insert into short keyid index:
std::vector<Key> by_shortkeyid;
by_shortkeyid.reserve(sorted.size() + d->by.shortkeyid.size());
std::merge(sorted.begin(),
sorted.end(),
d->by.shortkeyid.begin(),
d->by.shortkeyid.end(),
std::back_inserter(by_shortkeyid),
_detail::ByShortKeyID<std::less>());
// 6. build subkey ID index:
std::vector<Subkey> subkeys;
subkeys.reserve(sorted.size());
for (const Key &key : std::as_const(sorted)) {
const auto keySubkeys{key.subkeys()};
for (const Subkey &subkey : keySubkeys) {
subkeys.push_back(subkey);
}
}
// 6a sort by key id:
std::sort(subkeys.begin(), subkeys.end(), _detail::ByKeyID<std::less>());
// 6b. insert into subkey ID index:
std::vector<Subkey> by_subkeyid;
by_subkeyid.reserve(subkeys.size() + d->by.subkeyid.size());
std::merge(subkeys.begin(), subkeys.end(), d->by.subkeyid.begin(), d->by.subkeyid.end(), std::back_inserter(by_subkeyid), _detail::ByKeyID<std::less>());
// 6c. sort by key grip
std::sort(subkeys.begin(), subkeys.end(), _detail::ByKeyGrip<std::less>());
// 6d. insert into subkey keygrip index:
std::vector<Subkey> by_keygrip;
by_keygrip.reserve(subkeys.size() + d->by.keygrip.size());
std::merge(subkeys.begin(), subkeys.end(), d->by.keygrip.begin(), d->by.keygrip.end(), std::back_inserter(by_keygrip), _detail::ByKeyGrip<std::less>());
// now commit (well, we already removed keys...)
by_fpr.swap(d->by.fpr);
by_keyid.swap(d->by.keyid);
by_shortkeyid.swap(d->by.shortkeyid);
by_email.swap(d->by.email);
by_subkeyid.swap(d->by.subkeyid);
by_keygrip.swap(d->by.keygrip);
by_chainid.swap(d->by.chainid);
for (const Key &key : std::as_const(sorted)) {
d->m_pgpOnly &= key.protocol() == GpgME::OpenPGP;
- Q_EMIT added(key);
}
Q_EMIT keysMayHaveChanged();
}
void KeyCache::clear()
{
d->by = Private::By();
}
//
//
// RefreshKeysJob
//
//
class KeyCache::RefreshKeysJob::Private
{
RefreshKeysJob *const q;
public:
Private(KeyCache *cache, RefreshKeysJob *qq);
void doStart();
Error startKeyListing(GpgME::Protocol protocol);
void listAllKeysJobDone(const KeyListResult &res, const std::vector<Key> &nextKeys)
{
std::vector<Key> keys;
keys.reserve(m_keys.size() + nextKeys.size());
if (m_keys.empty()) {
keys = nextKeys;
} else {
std::merge(m_keys.begin(), m_keys.end(), nextKeys.begin(), nextKeys.end(), std::back_inserter(keys), _detail::ByFingerprint<std::less>());
}
m_keys.swap(keys);
jobDone(res);
}
void emitDone(const KeyListResult &result);
void updateKeyCache();
QPointer<KeyCache> m_cache;
QList<QGpgME::ListAllKeysJob *> m_jobsPending;
std::vector<Key> m_keys;
KeyListResult m_mergedResult;
bool m_canceled;
private:
void jobDone(const KeyListResult &res);
};
KeyCache::RefreshKeysJob::Private::Private(KeyCache *cache, RefreshKeysJob *qq)
: q(qq)
, m_cache(cache)
, m_canceled(false)
{
Q_ASSERT(m_cache);
}
void KeyCache::RefreshKeysJob::Private::jobDone(const KeyListResult &result)
{
if (m_canceled) {
q->deleteLater();
return;
}
QObject *const sender = q->sender();
if (sender) {
sender->disconnect(q);
}
Q_ASSERT(m_jobsPending.size() > 0);
m_jobsPending.removeOne(qobject_cast<QGpgME::ListAllKeysJob *>(sender));
m_mergedResult.mergeWith(result);
if (m_jobsPending.size() > 0) {
return;
}
updateKeyCache();
emitDone(m_mergedResult);
}
void KeyCache::RefreshKeysJob::Private::emitDone(const KeyListResult &res)
{
q->deleteLater();
Q_EMIT q->done(res);
}
KeyCache::RefreshKeysJob::RefreshKeysJob(KeyCache *cache, QObject *parent)
: QObject(parent)
, d(new Private(cache, this))
{
}
KeyCache::RefreshKeysJob::~RefreshKeysJob()
{
delete d;
}
void KeyCache::RefreshKeysJob::start()
{
QTimer::singleShot(0, this, [this]() {
d->doStart();
});
}
void KeyCache::RefreshKeysJob::cancel()
{
d->m_canceled = true;
std::for_each(d->m_jobsPending.begin(), d->m_jobsPending.end(), std::mem_fn(&QGpgME::ListAllKeysJob::slotCancel));
Q_EMIT canceled();
}
void KeyCache::RefreshKeysJob::Private::doStart()
{
if (m_canceled) {
q->deleteLater();
return;
}
Q_ASSERT(m_jobsPending.size() == 0);
m_mergedResult.mergeWith(KeyListResult(startKeyListing(GpgME::OpenPGP)));
m_mergedResult.mergeWith(KeyListResult(startKeyListing(GpgME::CMS)));
if (m_jobsPending.size() != 0) {
return;
}
const bool hasError = m_mergedResult.error() || m_mergedResult.error().isCanceled();
emitDone(hasError ? m_mergedResult : KeyListResult(Error(GPG_ERR_UNSUPPORTED_OPERATION)));
}
void KeyCache::RefreshKeysJob::Private::updateKeyCache()
{
if (!m_cache || m_canceled) {
q->deleteLater();
return;
}
std::vector<Key> cachedKeys = m_cache->initialized() ? m_cache->keys() : std::vector<Key>();
std::sort(cachedKeys.begin(), cachedKeys.end(), _detail::ByFingerprint<std::less>());
std::vector<Key> keysToRemove;
std::set_difference(cachedKeys.begin(),
cachedKeys.end(),
m_keys.begin(),
m_keys.end(),
std::back_inserter(keysToRemove),
_detail::ByFingerprint<std::less>());
m_cache->remove(keysToRemove);
m_cache->refresh(m_keys);
}
Error KeyCache::RefreshKeysJob::Private::startKeyListing(GpgME::Protocol proto)
{
const auto *const protocol = (proto == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime();
if (!protocol) {
return Error();
}
QGpgME::ListAllKeysJob *const job = protocol->listAllKeysJob(/*includeSigs*/ false, /*validate*/ true);
if (!job) {
return Error();
}
if (!m_cache->initialized()) {
// avoid delays during the initial key listing
job->setOptions(QGpgME::ListAllKeysJob::DisableAutomaticTrustDatabaseCheck);
}
#if 0
aheinecke: 2017.01.12:
For unknown reasons the new style connect fails at runtime
over library borders into QGpgME from the GpgME repo
when cross compiled for Windows and default arguments
are used in the Signal.
This was tested with gcc 4.9 (Mingw 3.0.2) and we could not
find an explanation for this. So until this is fixed or we understand
the problem we need to use the old style connect for QGpgME signals.
The new style connect of the canceled signal right below
works fine.
connect(job, &QGpgME::ListAllKeysJob::result,
q, [this](const GpgME::KeyListResult &res, const std::vector<GpgME::Key> &keys) {
listAllKeysJobDone(res, keys);
});
#endif
connect(job, SIGNAL(result(GpgME::KeyListResult, std::vector<GpgME::Key>)), q, SLOT(listAllKeysJobDone(GpgME::KeyListResult, std::vector<GpgME::Key>)));
connect(q, &RefreshKeysJob::canceled, job, &QGpgME::Job::slotCancel);
// Only do this for initialized keycaches to avoid huge waits for
// signature notations during initial keylisting.
if (proto == GpgME::OpenPGP && m_cache->remarksEnabled() && m_cache->initialized()) {
auto ctx = QGpgME::Job::context(job);
if (ctx) {
ctx->addKeyListMode(KeyListMode::Signatures | KeyListMode::SignatureNotations);
}
}
const Error error = job->start(true);
if (!error && !error.isCanceled()) {
m_jobsPending.push_back(job);
}
return error;
}
bool KeyCache::initialized() const
{
return d->m_initalized;
}
void KeyCache::Private::ensureCachePopulated() const
{
if (!m_initalized) {
q->startKeyListing();
QEventLoop loop;
loop.connect(q, &KeyCache::keyListingDone, &loop, &QEventLoop::quit);
qCDebug(LIBKLEO_LOG) << "Waiting for keycache.";
loop.exec();
qCDebug(LIBKLEO_LOG) << "Keycache available.";
}
}
bool KeyCache::pgpOnly() const
{
return d->m_pgpOnly;
}
static bool keyIsOk(const Key &k)
{
return !k.isExpired() && !k.isRevoked() && !k.isInvalid() && !k.isDisabled();
}
static bool uidIsOk(const UserID &uid)
{
return keyIsOk(uid.parent()) && !uid.isRevoked() && !uid.isInvalid();
}
static bool subkeyIsOk(const Subkey &s)
{
return !s.isRevoked() && !s.isInvalid() && !s.isDisabled();
}
namespace
{
time_t creationTimeOfNewestSuitableSubKey(const Key &key, KeyCache::KeyUsage usage)
{
time_t creationTime = 0;
for (const Subkey &s : key.subkeys()) {
if (!subkeyIsOk(s)) {
continue;
}
if (usage == KeyCache::KeyUsage::Sign && !s.canSign()) {
continue;
}
if (usage == KeyCache::KeyUsage::Encrypt && !s.canEncrypt()) {
continue;
}
if (s.creationTime() > creationTime) {
creationTime = s.creationTime();
}
}
return creationTime;
}
struct BestMatch {
Key key;
UserID uid;
time_t creationTime = 0;
};
}
GpgME::Key KeyCache::findBestByMailBox(const char *addr, GpgME::Protocol proto, KeyUsage usage) const
{
d->ensureCachePopulated();
if (!addr) {
return {};
}
// support lookup of email addresses enclosed in angle brackets
QByteArray address(addr);
if (address.size() > 1 && address[0] == '<' && address[address.size() - 1] == '>') {
address = address.mid(1, address.size() - 2);
}
address = address.toLower();
BestMatch best;
for (const Key &k : findByEMailAddress(address.constData())) {
if (proto != Protocol::UnknownProtocol && k.protocol() != proto) {
continue;
}
if (usage == KeyUsage::Encrypt && !keyHasEncrypt(k)) {
continue;
}
if (usage == KeyUsage::Sign && (!keyHasSign(k) || !k.hasSecret())) {
continue;
}
const time_t creationTime = creationTimeOfNewestSuitableSubKey(k, usage);
if (creationTime == 0) {
// key does not have a suitable (and usable) subkey
continue;
}
for (const UserID &u : k.userIDs()) {
if (QByteArray::fromStdString(u.addrSpec()).toLower() != address) {
// user ID does not match the given email address
continue;
}
if (best.uid.isNull()) {
// we have found our first candidate
best = {k, u, creationTime};
} else if (!uidIsOk(best.uid) && uidIsOk(u)) {
// validity of the new key is better
best = {k, u, creationTime};
} else if (!k.isExpired() && best.uid.validity() < u.validity()) {
// validity of the new key is better
best = {k, u, creationTime};
} else if (best.key.isExpired() && !k.isExpired()) {
// validity of the new key is better
best = {k, u, creationTime};
} else if (best.uid.validity() == u.validity() && uidIsOk(u) && best.creationTime < creationTime) {
// both keys/user IDs have same validity, but the new key is newer
best = {k, u, creationTime};
}
}
}
return best.key;
}
namespace
{
template<typename T>
bool allKeysAllowUsage(const T &keys, KeyCache::KeyUsage usage)
{
switch (usage) {
case KeyCache::KeyUsage::AnyUsage:
return true;
case KeyCache::KeyUsage::Sign:
return std::all_of(std::begin(keys),
std::end(keys),
#if GPGMEPP_KEY_HAS_HASCERTIFY_SIGN_ENCRYPT_AUTHENTICATE
std::mem_fn(&Key::hasSign)
#else
Kleo::keyHasSign
#endif
);
case KeyCache::KeyUsage::Encrypt:
return std::all_of(std::begin(keys),
std::end(keys),
#if GPGMEPP_KEY_HAS_HASCERTIFY_SIGN_ENCRYPT_AUTHENTICATE
std::mem_fn(&Key::hasEncrypt)
#else
Kleo::keyHasEncrypt
#endif
);
case KeyCache::KeyUsage::Certify:
return std::all_of(std::begin(keys),
std::end(keys),
#if GPGMEPP_KEY_HAS_HASCERTIFY_SIGN_ENCRYPT_AUTHENTICATE
std::mem_fn(&Key::hasCertify)
#else
Kleo::keyHasCertify
#endif
);
case KeyCache::KeyUsage::Authenticate:
return std::all_of(std::begin(keys),
std::end(keys),
#if GPGMEPP_KEY_HAS_HASCERTIFY_SIGN_ENCRYPT_AUTHENTICATE
std::mem_fn(&Key::hasAuthenticate)
#else
Kleo::keyHasAuthenticate
#endif
);
}
qCDebug(LIBKLEO_LOG) << __func__ << "called with invalid usage" << int(usage);
return false;
}
}
KeyGroup KeyCache::findGroup(const QString &name, Protocol protocol, KeyUsage usage) const
{
d->ensureCachePopulated();
Q_ASSERT(usage == KeyUsage::Sign || usage == KeyUsage::Encrypt);
for (const auto &group : std::as_const(d->m_groups)) {
if (group.name() == name) {
const KeyGroup::Keys &keys = group.keys();
if (allKeysAllowUsage(keys, usage) && (protocol == UnknownProtocol || allKeysHaveProtocol(keys, protocol))) {
return group;
}
}
}
return {};
}
std::vector<Key> KeyCache::getGroupKeys(const QString &groupName) const
{
std::vector<Key> result;
for (const KeyGroup &g : std::as_const(d->m_groups)) {
if (g.name() == groupName) {
const KeyGroup::Keys &keys = g.keys();
std::copy(keys.cbegin(), keys.cend(), std::back_inserter(result));
}
}
_detail::sort_by_fpr(result);
_detail::remove_duplicates_by_fpr(result);
return result;
}
void KeyCache::setKeys(const std::vector<GpgME::Key> &keys)
{
// disable regular key listing and cancel running key listing
setRefreshInterval(0);
cancelKeyListing();
clear();
insert(keys);
d->m_initalized = true;
Q_EMIT keyListingDone(KeyListResult());
}
void KeyCache::setGroups(const std::vector<KeyGroup> &groups)
{
Q_ASSERT(d->m_initalized && "Call setKeys() before setting groups");
d->m_groups = groups;
Q_EMIT keysMayHaveChanged();
}
#include "moc_keycache.cpp"
#include "moc_keycache_p.cpp"
diff --git a/src/models/keycache.h b/src/models/keycache.h
index 5e61ae48..d8f8bf3c 100644
--- a/src/models/keycache.h
+++ b/src/models/keycache.h
@@ -1,220 +1,217 @@
/* -*- mode: c++; c-basic-offset:4 -*-
models/keycache.h
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "kleo_export.h"
#include <QObject>
#include <gpgme++/global.h>
#include <memory>
#include <string>
#include <vector>
namespace GpgME
{
class Key;
class DecryptionResult;
class VerificationResult;
class KeyListResult;
class Subkey;
}
namespace KMime
{
namespace Types
{
class Mailbox;
}
}
namespace Kleo
{
class FileSystemWatcher;
class KeyGroup;
class KeyGroupConfig;
class KeyCacheAutoRefreshSuspension;
class KLEO_EXPORT KeyCache : public QObject
{
Q_OBJECT
protected:
explicit KeyCache();
public:
enum class KeyUsage {
AnyUsage,
Sign,
Encrypt,
Certify,
Authenticate,
};
static std::shared_ptr<const KeyCache> instance();
static std::shared_ptr<KeyCache> mutableInstance();
~KeyCache() override;
void setGroupsEnabled(bool enabled);
void setGroupConfig(const std::shared_ptr<KeyGroupConfig> &groupConfig);
void insert(const GpgME::Key &key);
void insert(const std::vector<GpgME::Key> &keys);
bool insert(const KeyGroup &group);
void refresh(const std::vector<GpgME::Key> &keys);
bool update(const KeyGroup &group);
void remove(const GpgME::Key &key);
void remove(const std::vector<GpgME::Key> &keys);
bool remove(const KeyGroup &group);
void addFileSystemWatcher(const std::shared_ptr<FileSystemWatcher> &watcher);
void enableFileSystemWatcher(bool enable);
void setRefreshInterval(int hours);
int refreshInterval() const;
std::shared_ptr<KeyCacheAutoRefreshSuspension> suspendAutoRefresh();
void enableRemarks(bool enable);
bool remarksEnabled() const;
const std::vector<GpgME::Key> &keys() const;
std::vector<GpgME::Key> secretKeys() const;
KeyGroup group(const QString &id) const;
std::vector<KeyGroup> groups() const;
std::vector<KeyGroup> configurableGroups() const;
void saveConfigurableGroups(const std::vector<KeyGroup> &groups);
const GpgME::Key &findByFingerprint(const char *fpr) const;
const GpgME::Key &findByFingerprint(const std::string &fpr) const;
std::vector<GpgME::Key> findByFingerprint(const std::vector<std::string> &fprs) const;
std::vector<GpgME::Key> findByEMailAddress(const char *email) const;
std::vector<GpgME::Key> findByEMailAddress(const std::string &email) const;
/** Look through the cache and search for the best key for a mailbox.
*
* The best key is the key with a UID for the provided mailbox that
* has the highest validity and a subkey that is capable for the given
* usage.
* If more then one key have a UID with the same validity
* the most recently created key is taken.
*
* @returns the "best" key for the mailbox. */
GpgME::Key findBestByMailBox(const char *addr, GpgME::Protocol proto, KeyUsage usage) const;
/**
* Looks for a group named @a name which contains keys with protocol @a protocol
* that are suitable for the usage @a usage.
*
* If @a protocol is GpgME::OpenPGP or GpgME::CMS, then only groups consisting of keys
* matching this protocol are considered. Use @a protocol GpgME::UnknownProtocol to consider
* any groups regardless of the protocol including mixed-protocol groups.
*
* If @a usage is not KeyUsage::AnyUsage, then only groups consisting of keys supporting this usage
* are considered.
* The validity of keys and the presence of a private key (necessary for signing, certification, and
* authentication) is not taken into account.
*
* The first group that fulfills all conditions is returned.
*
* @returns a matching group or a null group if no matching group is found.
*/
KeyGroup findGroup(const QString &name, GpgME::Protocol protocol, KeyUsage usage) const;
const GpgME::Key &findByShortKeyID(const char *id) const;
const GpgME::Key &findByShortKeyID(const std::string &id) const;
const GpgME::Key &findByKeyIDOrFingerprint(const char *id) const;
const GpgME::Key &findByKeyIDOrFingerprint(const std::string &id) const;
std::vector<GpgME::Key> findByKeyIDOrFingerprint(const std::vector<std::string> &ids) const;
const GpgME::Subkey &findSubkeyByKeyGrip(const char *grip, GpgME::Protocol protocol = GpgME::UnknownProtocol) const;
const GpgME::Subkey &findSubkeyByKeyGrip(const std::string &grip, GpgME::Protocol protocol = GpgME::UnknownProtocol) const;
std::vector<GpgME::Subkey> findSubkeysByKeyID(const std::vector<std::string> &ids) const;
std::vector<GpgME::Key> findRecipients(const GpgME::DecryptionResult &result) const;
std::vector<GpgME::Key> findSigners(const GpgME::VerificationResult &result) const;
std::vector<GpgME::Key> findSigningKeysByMailbox(const QString &mb) const;
std::vector<GpgME::Key> findEncryptionKeysByMailbox(const QString &mb) const;
/** Check for group keys.
*
* @returns A list of keys configured for groupName. Empty if no group cached.*/
std::vector<GpgME::Key> getGroupKeys(const QString &groupName) const;
enum Option {
// clang-format off
NoOption = 0,
RecursiveSearch = 1,
IncludeSubject = 2,
// clang-format on
};
Q_DECLARE_FLAGS(Options, Option)
std::vector<GpgME::Key> findSubjects(const GpgME::Key &key, Options option = RecursiveSearch) const;
std::vector<GpgME::Key> findSubjects(const std::vector<GpgME::Key> &keys, Options options = RecursiveSearch) const;
std::vector<GpgME::Key>
findSubjects(std::vector<GpgME::Key>::const_iterator first, std::vector<GpgME::Key>::const_iterator last, Options options = RecursiveSearch) const;
std::vector<GpgME::Key> findIssuers(const GpgME::Key &key, Options options = RecursiveSearch) const;
/** Check if at least one keylisting was finished. */
bool initialized() const;
/** Check if all keys have OpenPGP Protocol. */
bool pgpOnly() const;
/** Set the keys the cache shall contain. Marks cache as initialized. Use for tests only. */
void setKeys(const std::vector<GpgME::Key> &keys);
void setGroups(const std::vector<KeyGroup> &groups);
public Q_SLOTS:
void clear();
void startKeyListing(GpgME::Protocol proto = GpgME::UnknownProtocol)
{
reload(proto);
}
void reload(GpgME::Protocol proto = GpgME::UnknownProtocol);
void cancelKeyListing();
Q_SIGNALS:
- // void changed( const GpgME::Key & key );
- void aboutToRemove(const GpgME::Key &key);
- void added(const GpgME::Key &key);
void keyListingDone(const GpgME::KeyListResult &result);
void keysMayHaveChanged();
void groupAdded(const Kleo::KeyGroup &group);
void groupUpdated(const Kleo::KeyGroup &group);
void groupRemoved(const Kleo::KeyGroup &group);
private:
class RefreshKeysJob;
class Private;
QScopedPointer<Private> const d;
};
}
Q_DECLARE_OPERATORS_FOR_FLAGS(Kleo::KeyCache::Options)
diff --git a/src/models/keylistmodel.cpp b/src/models/keylistmodel.cpp
index 9d344efd..96365252 100644
--- a/src/models/keylistmodel.cpp
+++ b/src/models/keylistmodel.cpp
@@ -1,1602 +1,1619 @@
/* -*- mode: c++; c-basic-offset:4 -*-
models/keylistmodel.cpp
This file is part of libkleopatra, the KDE keymanagement library
SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB
SPDX-FileCopyrightText: 2021 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-libkleo.h>
#include "keylistmodel.h"
#include "keycache.h"
#include <libkleo/algorithm.h>
#include <libkleo/formatting.h>
#include <libkleo/keyfilter.h>
#include <libkleo/keyfiltermanager.h>
#include <libkleo/predicates.h>
#include <libkleo/systeminfo.h>
#include <KLocalizedString>
#ifdef KLEO_MODEL_TEST
#include <QAbstractItemModelTester>
#endif
#include <QColor>
#include <QDate>
#include <QFont>
#include <QHash>
#include <QIcon>
#include <gpgme++/key.h>
#ifndef Q_MOC_RUN // QTBUG-22829
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/topological_sort.hpp>
#endif
#include <algorithm>
#include <iterator>
#include <map>
#include <set>
using namespace GpgME;
using namespace Kleo;
using namespace Kleo::KeyList;
Q_DECLARE_METATYPE(GpgME::Key)
Q_DECLARE_METATYPE(KeyGroup)
class AbstractKeyListModel::Private
{
AbstractKeyListModel *const q;
public:
explicit Private(AbstractKeyListModel *qq);
void updateFromKeyCache();
QString getEMail(const Key &key) const;
public:
int m_toolTipOptions = Formatting::Validity;
mutable QHash<const char *, QString> prettyEMailCache;
mutable QHash<const char *, QVariant> remarksCache;
bool m_useKeyCache = false;
bool m_modelResetInProgress = false;
KeyList::Options m_keyListOptions = AllKeys;
std::vector<GpgME::Key> m_remarkKeys;
};
AbstractKeyListModel::Private::Private(Kleo::AbstractKeyListModel *qq)
: q(qq)
{
}
void AbstractKeyListModel::Private::updateFromKeyCache()
{
if (m_useKeyCache) {
+ const bool inReset = q->modelResetInProgress();
+ if (!inReset) {
+ q->beginResetModel();
+ }
q->setKeys(m_keyListOptions == SecretKeysOnly ? KeyCache::instance()->secretKeys() : KeyCache::instance()->keys());
if (m_keyListOptions == IncludeGroups) {
q->setGroups(KeyCache::instance()->groups());
}
+ if (!inReset) {
+ q->endResetModel();
+ }
}
}
QString AbstractKeyListModel::Private::getEMail(const Key &key) const
{
QString email;
if (const auto fpr = key.primaryFingerprint()) {
const auto it = prettyEMailCache.constFind(fpr);
if (it != prettyEMailCache.constEnd()) {
email = *it;
} else {
email = Formatting::prettyEMail(key);
prettyEMailCache[fpr] = email;
}
}
return email;
}
AbstractKeyListModel::AbstractKeyListModel(QObject *p)
: QAbstractItemModel(p)
, KeyListModelInterface()
, d(new Private(this))
{
connect(this, &QAbstractItemModel::modelAboutToBeReset, this, [this]() {
d->m_modelResetInProgress = true;
});
connect(this, &QAbstractItemModel::modelReset, this, [this]() {
d->m_modelResetInProgress = false;
});
}
AbstractKeyListModel::~AbstractKeyListModel()
{
}
void AbstractKeyListModel::setToolTipOptions(int opts)
{
d->m_toolTipOptions = opts;
}
int AbstractKeyListModel::toolTipOptions() const
{
return d->m_toolTipOptions;
}
void AbstractKeyListModel::setRemarkKeys(const std::vector<GpgME::Key> &keys)
{
d->m_remarkKeys = keys;
}
std::vector<GpgME::Key> AbstractKeyListModel::remarkKeys() const
{
return d->m_remarkKeys;
}
Key AbstractKeyListModel::key(const QModelIndex &idx) const
{
Key key = Key::null;
if (idx.isValid()) {
key = doMapToKey(idx);
}
return key;
}
std::vector<Key> AbstractKeyListModel::keys(const QList<QModelIndex> &indexes) const
{
std::vector<Key> result;
result.reserve(indexes.size());
std::transform(indexes.begin(), //
indexes.end(),
std::back_inserter(result),
[this](const QModelIndex &idx) {
return this->key(idx);
});
result.erase(std::remove_if(result.begin(), result.end(), std::mem_fn(&GpgME::Key::isNull)), result.end());
_detail::remove_duplicates_by_fpr(result);
return result;
}
KeyGroup AbstractKeyListModel::group(const QModelIndex &idx) const
{
if (idx.isValid()) {
return doMapToGroup(idx);
} else {
return KeyGroup();
}
}
QModelIndex AbstractKeyListModel::index(const Key &key) const
{
return index(key, 0);
}
QModelIndex AbstractKeyListModel::index(const Key &key, int col) const
{
if (key.isNull() || col < 0 || col >= NumColumns) {
return {};
} else {
return doMapFromKey(key, col);
}
}
QList<QModelIndex> AbstractKeyListModel::indexes(const std::vector<Key> &keys) const
{
QList<QModelIndex> result;
result.reserve(keys.size());
std::transform(keys.begin(), //
keys.end(),
std::back_inserter(result),
[this](const Key &key) {
return this->index(key);
});
return result;
}
QModelIndex AbstractKeyListModel::index(const KeyGroup &group) const
{
return index(group, 0);
}
QModelIndex AbstractKeyListModel::index(const KeyGroup &group, int col) const
{
if (group.isNull() || col < 0 || col >= NumColumns) {
return {};
} else {
return doMapFromGroup(group, col);
}
}
void AbstractKeyListModel::setKeys(const std::vector<Key> &keys)
{
- beginResetModel();
+ const bool inReset = modelResetInProgress();
+ if (!inReset) {
+ beginResetModel();
+ }
clear(Keys);
addKeys(keys);
- endResetModel();
+ if (!inReset) {
+ endResetModel();
+ }
}
QModelIndex AbstractKeyListModel::addKey(const Key &key)
{
const std::vector<Key> vec(1, key);
const QList<QModelIndex> l = doAddKeys(vec);
return l.empty() ? QModelIndex() : l.front();
}
void AbstractKeyListModel::removeKey(const Key &key)
{
if (key.isNull()) {
return;
}
doRemoveKey(key);
d->prettyEMailCache.remove(key.primaryFingerprint());
d->remarksCache.remove(key.primaryFingerprint());
}
QList<QModelIndex> AbstractKeyListModel::addKeys(const std::vector<Key> &keys)
{
std::vector<Key> sorted;
sorted.reserve(keys.size());
std::remove_copy_if(keys.begin(), keys.end(), std::back_inserter(sorted), std::mem_fn(&Key::isNull));
std::sort(sorted.begin(), sorted.end(), _detail::ByFingerprint<std::less>());
return doAddKeys(sorted);
}
void AbstractKeyListModel::setGroups(const std::vector<KeyGroup> &groups)
{
- beginResetModel();
+ const bool inReset = modelResetInProgress();
+ if (!inReset) {
+ beginResetModel();
+ }
clear(Groups);
doSetGroups(groups);
- endResetModel();
+ if (!inReset) {
+ endResetModel();
+ }
}
QModelIndex AbstractKeyListModel::addGroup(const KeyGroup &group)
{
if (group.isNull()) {
return QModelIndex();
}
return doAddGroup(group);
}
bool AbstractKeyListModel::removeGroup(const KeyGroup &group)
{
if (group.isNull()) {
return false;
}
return doRemoveGroup(group);
}
void AbstractKeyListModel::clear(ItemTypes types)
{
const bool inReset = modelResetInProgress();
if (!inReset) {
beginResetModel();
}
doClear(types);
if (types & Keys) {
d->prettyEMailCache.clear();
d->remarksCache.clear();
}
if (!inReset) {
endResetModel();
}
}
int AbstractKeyListModel::columnCount(const QModelIndex &) const
{
return NumColumns;
}
QVariant AbstractKeyListModel::headerData(int section, Qt::Orientation o, int role) const
{
if (o == Qt::Horizontal) {
if (role == Qt::DisplayRole || role == Qt::EditRole || role == Qt::ToolTipRole) {
switch (section) {
case PrettyName:
return i18n("Name");
case PrettyEMail:
return i18n("E-Mail");
case Validity:
return i18n("User-IDs");
case ValidFrom:
return i18n("Valid From");
case ValidUntil:
return i18n("Valid Until");
case TechnicalDetails:
return i18n("Protocol");
case ShortKeyID:
return i18n("Key-ID");
case KeyID:
return i18n("Key-ID");
case Fingerprint:
return i18n("Fingerprint");
case Issuer:
return i18n("Issuer");
case SerialNumber:
return i18n("Serial Number");
case Origin:
return i18n("Origin");
case LastUpdate:
return i18n("Last Update");
case OwnerTrust:
return i18n("Certification Trust");
case Remarks:
return i18n("Tags");
case NumColumns:;
}
}
}
return QVariant();
}
static QVariant returnIfValid(const QColor &t)
{
if (t.isValid()) {
return t;
} else {
return QVariant();
}
}
static QVariant returnIfValid(const QIcon &t)
{
if (!t.isNull()) {
return t;
} else {
return QVariant();
}
}
QVariant AbstractKeyListModel::data(const QModelIndex &index, int role) const
{
const Key key = this->key(index);
if (!key.isNull()) {
return data(key, index.column(), role);
}
const KeyGroup group = this->group(index);
if (!group.isNull()) {
return data(group, index.column(), role);
}
return QVariant();
}
QVariant AbstractKeyListModel::data(const Key &key, int column, int role) const
{
if (role == Qt::DisplayRole || role == Qt::EditRole || role == Qt::AccessibleTextRole) {
switch (column) {
case PrettyName: {
const auto name = Formatting::prettyName(key);
if (role == Qt::AccessibleTextRole) {
return name.isEmpty() ? i18nc("text for screen readers for an empty name", "no name") : name;
}
return name;
}
case PrettyEMail: {
const auto email = d->getEMail(key);
if (role == Qt::AccessibleTextRole) {
return email.isEmpty() ? i18nc("text for screen readers for an empty email address", "no email") : email;
}
return email;
}
case Validity:
return Formatting::complianceStringShort(key);
case ValidFrom:
if (role == Qt::EditRole) {
return Formatting::creationDate(key);
} else if (role == Qt::AccessibleTextRole) {
return Formatting::accessibleCreationDate(key);
} else {
return Formatting::creationDateString(key);
}
case ValidUntil:
if (role == Qt::EditRole) {
return Formatting::expirationDate(key);
} else if (role == Qt::AccessibleTextRole) {
return Formatting::accessibleExpirationDate(key);
} else {
return Formatting::expirationDateString(key);
}
case TechnicalDetails:
return Formatting::type(key);
case ShortKeyID:
if (role == Qt::AccessibleTextRole) {
return Formatting::accessibleHexID(key.shortKeyID());
} else {
return Formatting::prettyID(key.shortKeyID());
}
case KeyID:
if (role == Qt::AccessibleTextRole) {
return Formatting::accessibleHexID(key.keyID());
} else {
return Formatting::prettyID(key.keyID());
}
case Summary:
return Formatting::summaryLine(key);
case Fingerprint:
if (role == Qt::AccessibleTextRole) {
return Formatting::accessibleHexID(key.primaryFingerprint());
} else {
return Formatting::prettyID(key.primaryFingerprint());
}
case Issuer:
return QString::fromUtf8(key.issuerName());
case Origin:
return Formatting::origin(key.origin());
case LastUpdate:
if (role == Qt::AccessibleTextRole) {
return Formatting::accessibleDate(key.lastUpdate());
} else {
return Formatting::dateString(key.lastUpdate());
}
case SerialNumber:
return QString::fromUtf8(key.issuerSerial());
case OwnerTrust:
return Formatting::ownerTrustShort(key.ownerTrust());
case Remarks: {
const char *const fpr = key.primaryFingerprint();
if (fpr && key.protocol() == GpgME::OpenPGP && key.numUserIDs() && d->m_remarkKeys.size()) {
if (!(key.keyListMode() & GpgME::SignatureNotations)) {
return i18n("Loading...");
}
const QHash<const char *, QVariant>::const_iterator it = d->remarksCache.constFind(fpr);
if (it != d->remarksCache.constEnd()) {
return *it;
} else {
GpgME::Error err;
const auto remarks = key.userID(0).remarks(d->m_remarkKeys, err);
if (remarks.size() == 1) {
const auto remark = QString::fromStdString(remarks[0]);
return d->remarksCache[fpr] = remark;
} else {
QStringList remarkList;
remarkList.reserve(remarks.size());
for (const auto &rem : remarks) {
remarkList << QString::fromStdString(rem);
}
const auto remark = remarkList.join(QStringLiteral("; "));
return d->remarksCache[fpr] = remark;
}
}
} else {
return QVariant();
}
}
return QVariant();
case NumColumns:
break;
}
} else if (role == Qt::ToolTipRole) {
return Formatting::toolTip(key, toolTipOptions());
} else if (role == Qt::FontRole) {
return KeyFilterManager::instance()->font(key,
(column == ShortKeyID || column == KeyID || column == Fingerprint) ? QFont(QStringLiteral("monospace"))
: QFont());
} else if (role == Qt::DecorationRole) {
return column == Icon ? returnIfValid(KeyFilterManager::instance()->icon(key)) : QVariant();
} else if (role == Qt::BackgroundRole) {
if (!SystemInfo::isHighContrastModeActive()) {
return returnIfValid(KeyFilterManager::instance()->bgColor(key));
}
} else if (role == Qt::ForegroundRole) {
if (!SystemInfo::isHighContrastModeActive()) {
return returnIfValid(KeyFilterManager::instance()->fgColor(key));
}
} else if (role == FingerprintRole) {
return QString::fromLatin1(key.primaryFingerprint());
} else if (role == KeyRole) {
return QVariant::fromValue(key);
}
return QVariant();
}
QVariant AbstractKeyListModel::data(const KeyGroup &group, int column, int role) const
{
if (role == Qt::DisplayRole || role == Qt::EditRole || role == Qt::AccessibleTextRole) {
switch (column) {
case PrettyName:
return group.name();
case Validity:
return Formatting::complianceStringShort(group);
case TechnicalDetails:
return Formatting::type(group);
case Summary:
return Formatting::summaryLine(group); // used for filtering
case PrettyEMail:
case ValidFrom:
case ValidUntil:
case ShortKeyID:
case KeyID:
case Fingerprint:
case Issuer:
case Origin:
case LastUpdate:
case SerialNumber:
case OwnerTrust:
case Remarks:
if (role == Qt::AccessibleTextRole) {
return i18nc("text for screen readers", "not applicable");
}
break;
case NumColumns:
break;
}
} else if (role == Qt::ToolTipRole) {
return Formatting::toolTip(group, toolTipOptions());
} else if (role == Qt::FontRole) {
return QFont();
} else if (role == Qt::DecorationRole) {
return column == Icon ? QIcon::fromTheme(QStringLiteral("group")) : QVariant();
} else if (role == Qt::BackgroundRole) {
} else if (role == Qt::ForegroundRole) {
} else if (role == GroupRole) {
return QVariant::fromValue(group);
}
return QVariant();
}
bool AbstractKeyListModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
Q_UNUSED(role)
Q_ASSERT(value.canConvert<KeyGroup>());
if (value.canConvert<KeyGroup>()) {
const KeyGroup group = value.value<KeyGroup>();
return doSetGroupData(index, group);
}
return false;
}
bool AbstractKeyListModel::modelResetInProgress()
{
return d->m_modelResetInProgress;
}
namespace
{
template<typename Base>
class TableModelMixin : public Base
{
public:
explicit TableModelMixin(QObject *p = nullptr)
: Base(p)
{
}
~TableModelMixin() override
{
}
using Base::index;
QModelIndex index(int row, int column, const QModelIndex &pidx = QModelIndex()) const override
{
return this->hasIndex(row, column, pidx) ? this->createIndex(row, column, nullptr) : QModelIndex();
}
private:
QModelIndex parent(const QModelIndex &) const override
{
return QModelIndex();
}
bool hasChildren(const QModelIndex &pidx) const override
{
return (pidx.model() == this || !pidx.isValid()) && this->rowCount(pidx) > 0 && this->columnCount(pidx) > 0;
}
};
class FlatKeyListModel
#ifndef Q_MOC_RUN
: public TableModelMixin<AbstractKeyListModel>
#else
: public AbstractKeyListModel
#endif
{
Q_OBJECT
public:
explicit FlatKeyListModel(QObject *parent = nullptr);
~FlatKeyListModel() override;
int rowCount(const QModelIndex &pidx) const override
{
return pidx.isValid() ? 0 : mKeysByFingerprint.size() + mGroups.size();
}
private:
Key doMapToKey(const QModelIndex &index) const override;
QModelIndex doMapFromKey(const Key &key, int col) const override;
QList<QModelIndex> doAddKeys(const std::vector<Key> &keys) override;
void doRemoveKey(const Key &key) override;
KeyGroup doMapToGroup(const QModelIndex &index) const override;
QModelIndex doMapFromGroup(const KeyGroup &group, int column) const override;
void doSetGroups(const std::vector<KeyGroup> &groups) override;
QModelIndex doAddGroup(const KeyGroup &group) override;
bool doSetGroupData(const QModelIndex &index, const KeyGroup &group) override;
bool doRemoveGroup(const KeyGroup &group) override;
void doClear(ItemTypes types) override
{
if (types & Keys) {
mKeysByFingerprint.clear();
}
if (types & Groups) {
mGroups.clear();
}
}
int firstGroupRow() const
{
return mKeysByFingerprint.size();
}
int lastGroupRow() const
{
return mKeysByFingerprint.size() + mGroups.size() - 1;
}
int groupIndex(const QModelIndex &index) const
{
if (!index.isValid() || index.row() < firstGroupRow() || index.row() > lastGroupRow() || index.column() >= NumColumns) {
return -1;
}
return index.row() - firstGroupRow();
}
private:
std::vector<Key> mKeysByFingerprint;
std::vector<KeyGroup> mGroups;
};
class HierarchicalKeyListModel : public AbstractKeyListModel
{
Q_OBJECT
public:
explicit HierarchicalKeyListModel(QObject *parent = nullptr);
~HierarchicalKeyListModel() override;
int rowCount(const QModelIndex &pidx) const override;
using AbstractKeyListModel::index;
QModelIndex index(int row, int col, const QModelIndex &pidx) const override;
QModelIndex parent(const QModelIndex &idx) const override;
bool hasChildren(const QModelIndex &pidx) const override
{
return rowCount(pidx) > 0;
}
private:
Key doMapToKey(const QModelIndex &index) const override;
QModelIndex doMapFromKey(const Key &key, int col) const override;
QList<QModelIndex> doAddKeys(const std::vector<Key> &keys) override;
void doRemoveKey(const Key &key) override;
KeyGroup doMapToGroup(const QModelIndex &index) const override;
QModelIndex doMapFromGroup(const KeyGroup &group, int column) const override;
void doSetGroups(const std::vector<KeyGroup> &groups) override;
QModelIndex doAddGroup(const KeyGroup &group) override;
bool doSetGroupData(const QModelIndex &index, const KeyGroup &group) override;
bool doRemoveGroup(const KeyGroup &group) override;
void doClear(ItemTypes types) override;
int firstGroupRow() const
{
return mTopLevels.size();
}
int lastGroupRow() const
{
return mTopLevels.size() + mGroups.size() - 1;
}
int groupIndex(const QModelIndex &index) const
{
if (!index.isValid() || index.row() < firstGroupRow() || index.row() > lastGroupRow() || index.column() >= NumColumns) {
return -1;
}
return index.row() - firstGroupRow();
}
private:
void addTopLevelKey(const Key &key);
void addKeyWithParent(const char *issuer_fpr, const Key &key);
void addKeyWithoutParent(const char *issuer_fpr, const Key &key);
private:
typedef std::map<std::string, std::vector<Key>> Map;
std::vector<Key> mKeysByFingerprint; // all keys
Map mKeysByExistingParent, mKeysByNonExistingParent; // parent->child map
std::vector<Key> mTopLevels; // all roots + parent-less
std::vector<KeyGroup> mGroups;
};
class Issuers
{
Issuers()
{
}
public:
static Issuers *instance()
{
static auto self = std::unique_ptr<Issuers>{new Issuers{}};
return self.get();
}
const char *cleanChainID(const Key &key) const
{
const char *chainID = "";
if (!key.isRoot()) {
const char *const chid = key.chainID();
if (chid && mKeysWithMaskedIssuer.find(key) == std::end(mKeysWithMaskedIssuer)) {
chainID = chid;
}
}
return chainID;
}
void maskIssuerOfKey(const Key &key)
{
mKeysWithMaskedIssuer.insert(key);
}
void clear()
{
mKeysWithMaskedIssuer.clear();
}
private:
std::set<Key, _detail::ByFingerprint<std::less>> mKeysWithMaskedIssuer;
};
static const char *cleanChainID(const Key &key)
{
return Issuers::instance()->cleanChainID(key);
}
}
FlatKeyListModel::FlatKeyListModel(QObject *p)
: TableModelMixin<AbstractKeyListModel>(p)
{
}
FlatKeyListModel::~FlatKeyListModel()
{
}
Key FlatKeyListModel::doMapToKey(const QModelIndex &idx) const
{
Q_ASSERT(idx.isValid());
if (static_cast<unsigned>(idx.row()) < mKeysByFingerprint.size() && idx.column() < NumColumns) {
return mKeysByFingerprint[idx.row()];
} else {
return Key::null;
}
}
QModelIndex FlatKeyListModel::doMapFromKey(const Key &key, int col) const
{
Q_ASSERT(!key.isNull());
const std::vector<Key>::const_iterator it =
std::lower_bound(mKeysByFingerprint.begin(), mKeysByFingerprint.end(), key, _detail::ByFingerprint<std::less>());
if (it == mKeysByFingerprint.end() || !_detail::ByFingerprint<std::equal_to>()(*it, key)) {
return {};
} else {
return createIndex(it - mKeysByFingerprint.begin(), col);
}
}
QList<QModelIndex> FlatKeyListModel::doAddKeys(const std::vector<Key> &keys)
{
Q_ASSERT(std::is_sorted(keys.begin(), keys.end(), _detail::ByFingerprint<std::less>()));
if (keys.empty()) {
return QList<QModelIndex>();
}
for (auto it = keys.begin(), end = keys.end(); it != end; ++it) {
// find an insertion point:
const std::vector<Key>::iterator pos = std::upper_bound(mKeysByFingerprint.begin(), mKeysByFingerprint.end(), *it, _detail::ByFingerprint<std::less>());
const unsigned int idx = std::distance(mKeysByFingerprint.begin(), pos);
if (idx > 0 && qstrcmp(mKeysByFingerprint[idx - 1].primaryFingerprint(), it->primaryFingerprint()) == 0) {
// key existed before - replace with new one:
mKeysByFingerprint[idx - 1] = *it;
if (!modelResetInProgress()) {
Q_EMIT dataChanged(createIndex(idx - 1, 0), createIndex(idx - 1, NumColumns - 1));
}
} else {
// new key - insert:
if (!modelResetInProgress()) {
beginInsertRows(QModelIndex(), idx, idx);
}
mKeysByFingerprint.insert(pos, *it);
if (!modelResetInProgress()) {
endInsertRows();
}
}
}
return indexes(keys);
}
void FlatKeyListModel::doRemoveKey(const Key &key)
{
const std::vector<Key>::iterator it = Kleo::binary_find(mKeysByFingerprint.begin(), mKeysByFingerprint.end(), key, _detail::ByFingerprint<std::less>());
if (it == mKeysByFingerprint.end()) {
return;
}
const unsigned int row = std::distance(mKeysByFingerprint.begin(), it);
if (!modelResetInProgress()) {
beginRemoveRows(QModelIndex(), row, row);
}
mKeysByFingerprint.erase(it);
if (!modelResetInProgress()) {
endRemoveRows();
}
}
KeyGroup FlatKeyListModel::doMapToGroup(const QModelIndex &idx) const
{
Q_ASSERT(idx.isValid());
if (static_cast<unsigned>(idx.row()) >= mKeysByFingerprint.size() && static_cast<unsigned>(idx.row()) < mKeysByFingerprint.size() + mGroups.size()
&& idx.column() < NumColumns) {
return mGroups[idx.row() - mKeysByFingerprint.size()];
} else {
return KeyGroup();
}
}
QModelIndex FlatKeyListModel::doMapFromGroup(const KeyGroup &group, int column) const
{
Q_ASSERT(!group.isNull());
const auto it = std::find_if(mGroups.cbegin(), mGroups.cend(), [group](const KeyGroup &g) {
return g.source() == group.source() && g.id() == group.id();
});
if (it == mGroups.cend()) {
return QModelIndex();
} else {
return createIndex(it - mGroups.cbegin() + mKeysByFingerprint.size(), column);
}
}
void FlatKeyListModel::doSetGroups(const std::vector<KeyGroup> &groups)
{
Q_ASSERT(mGroups.empty()); // ensure that groups have been cleared
const int first = mKeysByFingerprint.size();
const int last = first + groups.size() - 1;
if (!modelResetInProgress()) {
beginInsertRows(QModelIndex(), first, last);
}
mGroups = groups;
if (!modelResetInProgress()) {
endInsertRows();
}
}
QModelIndex FlatKeyListModel::doAddGroup(const KeyGroup &group)
{
const int newRow = lastGroupRow() + 1;
if (!modelResetInProgress()) {
beginInsertRows(QModelIndex(), newRow, newRow);
}
mGroups.push_back(group);
if (!modelResetInProgress()) {
endInsertRows();
}
return createIndex(newRow, 0);
}
bool FlatKeyListModel::doSetGroupData(const QModelIndex &index, const KeyGroup &group)
{
if (group.isNull()) {
return false;
}
const int groupIndex = this->groupIndex(index);
if (groupIndex == -1) {
return false;
}
mGroups[groupIndex] = group;
if (!modelResetInProgress()) {
Q_EMIT dataChanged(createIndex(index.row(), 0), createIndex(index.row(), NumColumns - 1));
}
return true;
}
bool FlatKeyListModel::doRemoveGroup(const KeyGroup &group)
{
const QModelIndex modelIndex = doMapFromGroup(group, 0);
if (!modelIndex.isValid()) {
return false;
}
const int groupIndex = this->groupIndex(modelIndex);
Q_ASSERT(groupIndex != -1);
if (groupIndex == -1) {
return false;
}
if (!modelResetInProgress()) {
beginRemoveRows(QModelIndex(), modelIndex.row(), modelIndex.row());
}
mGroups.erase(mGroups.begin() + groupIndex);
if (!modelResetInProgress()) {
endRemoveRows();
}
return true;
}
HierarchicalKeyListModel::HierarchicalKeyListModel(QObject *p)
: AbstractKeyListModel(p)
, mKeysByFingerprint()
, mKeysByExistingParent()
, mKeysByNonExistingParent()
, mTopLevels()
{
}
HierarchicalKeyListModel::~HierarchicalKeyListModel()
{
}
int HierarchicalKeyListModel::rowCount(const QModelIndex &pidx) const
{
// toplevel item:
if (!pidx.isValid()) {
return mTopLevels.size() + mGroups.size();
}
if (pidx.column() != 0) {
return 0;
}
// non-toplevel item - find the number of subjects for this issuer:
const Key issuer = this->key(pidx);
const char *const fpr = issuer.primaryFingerprint();
if (!fpr || !*fpr) {
return 0;
}
const Map::const_iterator it = mKeysByExistingParent.find(fpr);
if (it == mKeysByExistingParent.end()) {
return 0;
}
return it->second.size();
}
QModelIndex HierarchicalKeyListModel::index(int row, int col, const QModelIndex &pidx) const
{
if (row < 0 || col < 0 || col >= NumColumns) {
return {};
}
// toplevel item:
if (!pidx.isValid()) {
if (static_cast<unsigned>(row) < mTopLevels.size()) {
return index(mTopLevels[row], col);
} else if (static_cast<unsigned>(row) < mTopLevels.size() + mGroups.size()) {
return index(mGroups[row - mTopLevels.size()], col);
} else {
return QModelIndex();
}
}
// non-toplevel item - find the row'th subject of this key:
const Key issuer = this->key(pidx);
const char *const fpr = issuer.primaryFingerprint();
if (!fpr || !*fpr) {
return QModelIndex();
}
const Map::const_iterator it = mKeysByExistingParent.find(fpr);
if (it == mKeysByExistingParent.end() || static_cast<unsigned>(row) >= it->second.size()) {
return QModelIndex();
}
return index(it->second[row], col);
}
QModelIndex HierarchicalKeyListModel::parent(const QModelIndex &idx) const
{
const Key key = this->key(idx);
if (key.isNull() || key.isRoot()) {
return {};
}
const std::vector<Key>::const_iterator it =
Kleo::binary_find(mKeysByFingerprint.begin(), mKeysByFingerprint.end(), cleanChainID(key), _detail::ByFingerprint<std::less>());
return it != mKeysByFingerprint.end() ? index(*it) : QModelIndex();
}
Key HierarchicalKeyListModel::doMapToKey(const QModelIndex &idx) const
{
Key key = Key::null;
if (idx.isValid()) {
const char *const issuer_fpr = static_cast<const char *>(idx.internalPointer());
if (!issuer_fpr || !*issuer_fpr) {
// top-level:
if (static_cast<unsigned>(idx.row()) < mTopLevels.size()) {
key = mTopLevels[idx.row()];
}
} else {
// non-toplevel:
const Map::const_iterator it = mKeysByExistingParent.find(issuer_fpr);
if (it != mKeysByExistingParent.end() && static_cast<unsigned>(idx.row()) < it->second.size()) {
key = it->second[idx.row()];
}
}
}
return key;
}
QModelIndex HierarchicalKeyListModel::doMapFromKey(const Key &key, int col) const
{
if (key.isNull()) {
return {};
}
const char *issuer_fpr = cleanChainID(key);
// we need to look in the toplevels list,...
const std::vector<Key> *v = &mTopLevels;
if (issuer_fpr && *issuer_fpr) {
const std::map<std::string, std::vector<Key>>::const_iterator it = mKeysByExistingParent.find(issuer_fpr);
// ...unless we find an existing parent:
if (it != mKeysByExistingParent.end()) {
v = &it->second;
} else {
issuer_fpr = nullptr; // force internalPointer to zero for toplevels
}
}
const std::vector<Key>::const_iterator it = std::lower_bound(v->begin(), v->end(), key, _detail::ByFingerprint<std::less>());
if (it == v->end() || !_detail::ByFingerprint<std::equal_to>()(*it, key)) {
return QModelIndex();
}
const unsigned int row = std::distance(v->begin(), it);
return createIndex(row, col, const_cast<char * /* thanks, Trolls :/ */>(issuer_fpr));
}
void HierarchicalKeyListModel::addKeyWithParent(const char *issuer_fpr, const Key &key)
{
Q_ASSERT(issuer_fpr);
Q_ASSERT(*issuer_fpr);
Q_ASSERT(!key.isNull());
std::vector<Key> &subjects = mKeysByExistingParent[issuer_fpr];
// find insertion point:
const std::vector<Key>::iterator it = std::lower_bound(subjects.begin(), subjects.end(), key, _detail::ByFingerprint<std::less>());
const int row = std::distance(subjects.begin(), it);
if (it != subjects.end() && qstricmp(it->primaryFingerprint(), key.primaryFingerprint()) == 0) {
// exists -> replace
*it = key;
if (!modelResetInProgress()) {
Q_EMIT dataChanged(createIndex(row, 0, const_cast<char *>(issuer_fpr)), createIndex(row, NumColumns - 1, const_cast<char *>(issuer_fpr)));
}
} else {
// doesn't exist -> insert
const std::vector<Key>::const_iterator pos =
Kleo::binary_find(mKeysByFingerprint.begin(), mKeysByFingerprint.end(), issuer_fpr, _detail::ByFingerprint<std::less>());
Q_ASSERT(pos != mKeysByFingerprint.end());
if (!modelResetInProgress()) {
beginInsertRows(index(*pos), row, row);
}
subjects.insert(it, key);
if (!modelResetInProgress()) {
endInsertRows();
}
}
}
void HierarchicalKeyListModel::addKeyWithoutParent(const char *issuer_fpr, const Key &key)
{
Q_ASSERT(issuer_fpr);
Q_ASSERT(*issuer_fpr);
Q_ASSERT(!key.isNull());
std::vector<Key> &subjects = mKeysByNonExistingParent[issuer_fpr];
// find insertion point:
const std::vector<Key>::iterator it = std::lower_bound(subjects.begin(), subjects.end(), key, _detail::ByFingerprint<std::less>());
if (it != subjects.end() && qstricmp(it->primaryFingerprint(), key.primaryFingerprint()) == 0) {
// exists -> replace
*it = key;
} else {
// doesn't exist -> insert
subjects.insert(it, key);
}
addTopLevelKey(key);
}
void HierarchicalKeyListModel::addTopLevelKey(const Key &key)
{
// find insertion point:
const std::vector<Key>::iterator it = std::lower_bound(mTopLevels.begin(), mTopLevels.end(), key, _detail::ByFingerprint<std::less>());
const int row = std::distance(mTopLevels.begin(), it);
if (it != mTopLevels.end() && qstricmp(it->primaryFingerprint(), key.primaryFingerprint()) == 0) {
// exists -> replace
*it = key;
if (!modelResetInProgress()) {
Q_EMIT dataChanged(createIndex(row, 0), createIndex(row, NumColumns - 1));
}
} else {
// doesn't exist -> insert
if (!modelResetInProgress()) {
beginInsertRows(QModelIndex(), row, row);
}
mTopLevels.insert(it, key);
if (!modelResetInProgress()) {
endInsertRows();
}
}
}
namespace
{
// based on https://www.boost.org/doc/libs/1_77_0/libs/graph/doc/file_dependency_example.html#sec:cycles
struct cycle_detector : public boost::dfs_visitor<> {
cycle_detector(bool &has_cycle)
: _has_cycle{has_cycle}
{
}
template<class Edge, class Graph>
void back_edge(Edge, Graph &)
{
_has_cycle = true;
}
private:
bool &_has_cycle;
};
static bool graph_has_cycle(const boost::adjacency_list<> &graph)
{
bool cycle_found = false;
cycle_detector vis{cycle_found};
boost::depth_first_search(graph, visitor(vis));
return cycle_found;
}
static void find_keys_causing_cycles_and_mask_their_issuers(const std::vector<Key> &keys)
{
boost::adjacency_list<> graph{keys.size()};
for (unsigned int i = 0, end = keys.size(); i != end; ++i) {
const auto &key = keys[i];
const char *const issuer_fpr = cleanChainID(key);
if (!issuer_fpr || !*issuer_fpr) {
continue;
}
const std::vector<Key>::const_iterator it = Kleo::binary_find(keys.begin(), keys.end(), issuer_fpr, _detail::ByFingerprint<std::less>());
if (it == keys.end()) {
continue;
}
const auto j = std::distance(keys.begin(), it);
const auto edge = boost::add_edge(i, j, graph).first;
if (graph_has_cycle(graph)) {
Issuers::instance()->maskIssuerOfKey(key);
boost::remove_edge(edge, graph);
}
}
}
static auto build_key_graph(const std::vector<Key> &keys)
{
boost::adjacency_list<> graph(keys.size());
// add edges from children to parents:
for (unsigned int i = 0, end = keys.size(); i != end; ++i) {
const char *const issuer_fpr = cleanChainID(keys[i]);
if (!issuer_fpr || !*issuer_fpr) {
continue;
}
const std::vector<Key>::const_iterator it = Kleo::binary_find(keys.begin(), keys.end(), issuer_fpr, _detail::ByFingerprint<std::less>());
if (it == keys.end()) {
continue;
}
const auto j = std::distance(keys.begin(), it);
add_edge(i, j, graph);
}
return graph;
}
// sorts 'keys' such that parent always come before their children:
static std::vector<Key> topological_sort(const std::vector<Key> &keys)
{
const auto graph = build_key_graph(keys);
std::vector<int> order;
order.reserve(keys.size());
topological_sort(graph, std::back_inserter(order));
Q_ASSERT(order.size() == keys.size());
std::vector<Key> result;
result.reserve(keys.size());
for (int i : std::as_const(order)) {
result.push_back(keys[i]);
}
return result;
}
}
QList<QModelIndex> HierarchicalKeyListModel::doAddKeys(const std::vector<Key> &keys)
{
Q_ASSERT(std::is_sorted(keys.begin(), keys.end(), _detail::ByFingerprint<std::less>()));
if (keys.empty()) {
return QList<QModelIndex>();
}
const std::vector<Key> oldKeys = mKeysByFingerprint;
std::vector<Key> merged;
merged.reserve(keys.size() + mKeysByFingerprint.size());
std::set_union(keys.begin(),
keys.end(),
mKeysByFingerprint.begin(),
mKeysByFingerprint.end(),
std::back_inserter(merged),
_detail::ByFingerprint<std::less>());
mKeysByFingerprint = merged;
if (graph_has_cycle(build_key_graph(mKeysByFingerprint))) {
find_keys_causing_cycles_and_mask_their_issuers(mKeysByFingerprint);
}
std::set<Key, _detail::ByFingerprint<std::less>> changedParents;
const auto topologicalSortedList = topological_sort(keys);
for (const Key &key : topologicalSortedList) {
// check to see whether this key is a parent for a previously parent-less group:
const char *const fpr = key.primaryFingerprint();
if (!fpr || !*fpr) {
continue;
}
const bool keyAlreadyExisted = std::binary_search(oldKeys.begin(), oldKeys.end(), key, _detail::ByFingerprint<std::less>());
const Map::iterator it = mKeysByNonExistingParent.find(fpr);
const std::vector<Key> children = it != mKeysByNonExistingParent.end() ? it->second : std::vector<Key>();
if (it != mKeysByNonExistingParent.end()) {
mKeysByNonExistingParent.erase(it);
}
// Step 1: For new keys, remove children from toplevel:
if (!keyAlreadyExisted) {
auto last = mTopLevels.begin();
auto lastFP = mKeysByFingerprint.begin();
for (const Key &k : children) {
last = Kleo::binary_find(last, mTopLevels.end(), k, _detail::ByFingerprint<std::less>());
Q_ASSERT(last != mTopLevels.end());
const int row = std::distance(mTopLevels.begin(), last);
lastFP = Kleo::binary_find(lastFP, mKeysByFingerprint.end(), k, _detail::ByFingerprint<std::less>());
Q_ASSERT(lastFP != mKeysByFingerprint.end());
Q_EMIT rowAboutToBeMoved(QModelIndex(), row);
if (!modelResetInProgress()) {
beginRemoveRows(QModelIndex(), row, row);
}
last = mTopLevels.erase(last);
lastFP = mKeysByFingerprint.erase(lastFP);
if (!modelResetInProgress()) {
endRemoveRows();
}
}
}
// Step 2: add/update key
const char *const issuer_fpr = cleanChainID(key);
if (!issuer_fpr || !*issuer_fpr) {
// root or something...
addTopLevelKey(key);
} else if (std::binary_search(mKeysByFingerprint.begin(), mKeysByFingerprint.end(), issuer_fpr, _detail::ByFingerprint<std::less>())) {
// parent exists...
addKeyWithParent(issuer_fpr, key);
} else {
// parent doesn't exist yet...
addKeyWithoutParent(issuer_fpr, key);
}
const QModelIndex key_idx = index(key);
QModelIndex key_parent = key_idx.parent();
while (key_parent.isValid()) {
changedParents.insert(doMapToKey(key_parent));
key_parent = key_parent.parent();
}
// Step 3: Add children to new parent ( == key )
if (!keyAlreadyExisted && !children.empty()) {
addKeys(children);
const QModelIndex new_parent = index(key);
// Q_EMIT the rowMoved() signals in reversed direction, so the
// implementation can use a stack for mapping.
for (int i = children.size() - 1; i >= 0; --i) {
Q_EMIT rowMoved(new_parent, i);
}
}
}
// Q_EMIT dataChanged for all parents with new children. This triggers KeyListSortFilterProxyModel to
// show a parent node if it just got children matching the proxy's filter
if (!modelResetInProgress()) {
for (const Key &i : std::as_const(changedParents)) {
const QModelIndex idx = index(i);
if (idx.isValid()) {
Q_EMIT dataChanged(idx.sibling(idx.row(), 0), idx.sibling(idx.row(), NumColumns - 1));
}
}
}
return indexes(keys);
}
void HierarchicalKeyListModel::doRemoveKey(const Key &key)
{
const QModelIndex idx = index(key);
if (!idx.isValid()) {
return;
}
const char *const fpr = key.primaryFingerprint();
if (mKeysByExistingParent.find(fpr) != mKeysByExistingParent.end()) {
// handle non-leave nodes:
std::vector<Key> keys = mKeysByFingerprint;
const std::vector<Key>::iterator it = Kleo::binary_find(keys.begin(), keys.end(), key, _detail::ByFingerprint<std::less>());
if (it == keys.end()) {
return;
}
keys.erase(it);
// FIXME for simplicity, we just clear the model and re-add all keys minus the removed one. This is suboptimal,
// but acceptable given that deletion of non-leave nodes is rather rare.
clear(Keys);
addKeys(keys);
return;
}
// handle leave nodes:
const std::vector<Key>::iterator it = Kleo::binary_find(mKeysByFingerprint.begin(), mKeysByFingerprint.end(), key, _detail::ByFingerprint<std::less>());
Q_ASSERT(it != mKeysByFingerprint.end());
Q_ASSERT(mKeysByNonExistingParent.find(fpr) == mKeysByNonExistingParent.end());
Q_ASSERT(mKeysByExistingParent.find(fpr) == mKeysByExistingParent.end());
if (!modelResetInProgress()) {
beginRemoveRows(parent(idx), idx.row(), idx.row());
}
mKeysByFingerprint.erase(it);
const char *const issuer_fpr = cleanChainID(key);
const std::vector<Key>::iterator tlIt = Kleo::binary_find(mTopLevels.begin(), mTopLevels.end(), key, _detail::ByFingerprint<std::less>());
if (tlIt != mTopLevels.end()) {
mTopLevels.erase(tlIt);
}
if (issuer_fpr && *issuer_fpr) {
const Map::iterator nexIt = mKeysByNonExistingParent.find(issuer_fpr);
if (nexIt != mKeysByNonExistingParent.end()) {
const std::vector<Key>::iterator eit = Kleo::binary_find(nexIt->second.begin(), nexIt->second.end(), key, _detail::ByFingerprint<std::less>());
if (eit != nexIt->second.end()) {
nexIt->second.erase(eit);
}
if (nexIt->second.empty()) {
mKeysByNonExistingParent.erase(nexIt);
}
}
const Map::iterator exIt = mKeysByExistingParent.find(issuer_fpr);
if (exIt != mKeysByExistingParent.end()) {
const std::vector<Key>::iterator eit = Kleo::binary_find(exIt->second.begin(), exIt->second.end(), key, _detail::ByFingerprint<std::less>());
if (eit != exIt->second.end()) {
exIt->second.erase(eit);
}
if (exIt->second.empty()) {
mKeysByExistingParent.erase(exIt);
}
}
}
if (!modelResetInProgress()) {
endRemoveRows();
}
}
KeyGroup HierarchicalKeyListModel::doMapToGroup(const QModelIndex &idx) const
{
Q_ASSERT(idx.isValid());
if (idx.parent().isValid()) {
// groups are always top-level
return KeyGroup();
}
if (static_cast<unsigned>(idx.row()) >= mTopLevels.size() && static_cast<unsigned>(idx.row()) < mTopLevels.size() + mGroups.size()
&& idx.column() < NumColumns) {
return mGroups[idx.row() - mTopLevels.size()];
} else {
return KeyGroup();
}
}
QModelIndex HierarchicalKeyListModel::doMapFromGroup(const KeyGroup &group, int column) const
{
Q_ASSERT(!group.isNull());
const auto it = std::find_if(mGroups.cbegin(), mGroups.cend(), [group](const KeyGroup &g) {
return g.source() == group.source() && g.id() == group.id();
});
if (it == mGroups.cend()) {
return QModelIndex();
} else {
return createIndex(it - mGroups.cbegin() + mTopLevels.size(), column);
}
}
void HierarchicalKeyListModel::doSetGroups(const std::vector<KeyGroup> &groups)
{
Q_ASSERT(mGroups.empty()); // ensure that groups have been cleared
const int first = mTopLevels.size();
const int last = first + groups.size() - 1;
if (!modelResetInProgress()) {
beginInsertRows(QModelIndex(), first, last);
}
mGroups = groups;
if (!modelResetInProgress()) {
endInsertRows();
}
}
QModelIndex HierarchicalKeyListModel::doAddGroup(const KeyGroup &group)
{
const int newRow = lastGroupRow() + 1;
if (!modelResetInProgress()) {
beginInsertRows(QModelIndex(), newRow, newRow);
}
mGroups.push_back(group);
if (!modelResetInProgress()) {
endInsertRows();
}
return createIndex(newRow, 0);
}
bool HierarchicalKeyListModel::doSetGroupData(const QModelIndex &index, const KeyGroup &group)
{
if (group.isNull()) {
return false;
}
const int groupIndex = this->groupIndex(index);
if (groupIndex == -1) {
return false;
}
mGroups[groupIndex] = group;
if (!modelResetInProgress()) {
Q_EMIT dataChanged(createIndex(index.row(), 0), createIndex(index.row(), NumColumns - 1));
}
return true;
}
bool HierarchicalKeyListModel::doRemoveGroup(const KeyGroup &group)
{
const QModelIndex modelIndex = doMapFromGroup(group, 0);
if (!modelIndex.isValid()) {
return false;
}
const int groupIndex = this->groupIndex(modelIndex);
Q_ASSERT(groupIndex != -1);
if (groupIndex == -1) {
return false;
}
if (!modelResetInProgress()) {
beginRemoveRows(QModelIndex(), modelIndex.row(), modelIndex.row());
}
mGroups.erase(mGroups.begin() + groupIndex);
if (!modelResetInProgress()) {
endRemoveRows();
}
return true;
}
void HierarchicalKeyListModel::doClear(ItemTypes types)
{
if (types & Keys) {
mTopLevels.clear();
mKeysByFingerprint.clear();
mKeysByExistingParent.clear();
mKeysByNonExistingParent.clear();
Issuers::instance()->clear();
}
if (types & Groups) {
mGroups.clear();
}
}
void AbstractKeyListModel::useKeyCache(bool value, KeyList::Options options)
{
d->m_keyListOptions = options;
d->m_useKeyCache = value;
if (!d->m_useKeyCache) {
clear(All);
} else {
d->updateFromKeyCache();
}
connect(KeyCache::instance().get(), &KeyCache::keysMayHaveChanged, this, [this] {
d->updateFromKeyCache();
});
}
// static
AbstractKeyListModel *AbstractKeyListModel::createFlatKeyListModel(QObject *p)
{
AbstractKeyListModel *const m = new FlatKeyListModel(p);
#ifdef KLEO_MODEL_TEST
new QAbstractItemModelTester(m, p);
#endif
return m;
}
// static
AbstractKeyListModel *AbstractKeyListModel::createHierarchicalKeyListModel(QObject *p)
{
AbstractKeyListModel *const m = new HierarchicalKeyListModel(p);
#ifdef KLEO_MODEL_TEST
new QAbstractItemModelTester(m, p);
#endif
return m;
}
#include "keylistmodel.moc"
/*!
\fn AbstractKeyListModel::rowAboutToBeMoved( const QModelIndex & old_parent, int old_row )
Emitted before the removal of a row from that model. It will later
be added to the model again, in response to which rowMoved() will be
emitted. If multiple rows are moved in one go, multiple
rowAboutToBeMoved() signals are emitted before the corresponding
number of rowMoved() signals is emitted - in reverse order.
This works around the absence of move semantics in
QAbstractItemModel. Clients can maintain a stack to perform the
QModelIndex-mapping themselves, or, e.g., to preserve the selection
status of the row:
\code
std::vector<bool> mMovingRowWasSelected; // transient, used when rows are moved
// ...
void slotRowAboutToBeMoved( const QModelIndex & p, int row ) {
mMovingRowWasSelected.push_back( selectionModel()->isSelected( model()->index( row, 0, p ) ) );
}
void slotRowMoved( const QModelIndex & p, int row ) {
const bool wasSelected = mMovingRowWasSelected.back();
mMovingRowWasSelected.pop_back();
if ( wasSelected )
selectionModel()->select( model()->index( row, 0, p ), Select|Rows );
}
\endcode
A similar mechanism could be used to preserve the current item during moves.
*/
/*!
\fn AbstractKeyListModel::rowMoved( const QModelIndex & new_parent, int new_parent )
See rowAboutToBeMoved()
*/
#include "moc_keylistmodel.cpp"
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Fri, Dec 5, 5:32 AM (1 d, 22 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
3d/08/c4f07128679e4c31cbbf7a8331a6
Attached To
rLIBKLEO Libkleo
Event Timeline
Log In to Comment