Page Menu
Home
GnuPG
Search
Configure Global Search
Log In
Files
F34109804
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Size
97 KB
Subscribers
None
View Options
diff --git a/src/models/keylistsortfilterproxymodel.cpp b/src/models/keylistsortfilterproxymodel.cpp
index d8cc8367..2a4ebf6a 100644
--- a/src/models/keylistsortfilterproxymodel.cpp
+++ b/src/models/keylistsortfilterproxymodel.cpp
@@ -1,256 +1,265 @@
/* -*- mode: c++; c-basic-offset:4 -*-
models/keylistsortfilterproxymodel.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-libkleo.h>
#include "keylistsortfilterproxymodel.h"
#include "keylist.h"
#include "keylistmodel.h"
#include <libkleo/keyfilter.h>
#include <libkleo/keygroup.h>
#include <libkleo/stl_util.h>
#include <libkleo_debug.h>
#include <gpgme++/key.h>
using namespace Kleo;
using namespace GpgME;
AbstractKeyListSortFilterProxyModel::AbstractKeyListSortFilterProxyModel(QObject *p)
: QSortFilterProxyModel(p)
, KeyListModelInterface()
{
init();
}
AbstractKeyListSortFilterProxyModel::AbstractKeyListSortFilterProxyModel(const AbstractKeyListSortFilterProxyModel &other)
: QSortFilterProxyModel()
, KeyListModelInterface()
{
Q_UNUSED(other)
init();
}
void AbstractKeyListSortFilterProxyModel::init()
{
setDynamicSortFilter(true);
setSortRole(Qt::EditRole); // EditRole can be expected to be in a less formatted way, better for sorting
setFilterRole(Qt::DisplayRole);
setFilterCaseSensitivity(Qt::CaseInsensitive);
}
AbstractKeyListSortFilterProxyModel::~AbstractKeyListSortFilterProxyModel()
{
}
Key AbstractKeyListSortFilterProxyModel::key(const QModelIndex &idx) const
{
const KeyListModelInterface *const klmi = dynamic_cast<KeyListModelInterface *>(sourceModel());
if (!klmi) {
static Key null;
return null;
}
return klmi->key(mapToSource(idx));
}
std::vector<Key> AbstractKeyListSortFilterProxyModel::keys(const QList<QModelIndex> &indexes) const
{
const KeyListModelInterface *const klmi = dynamic_cast<KeyListModelInterface *>(sourceModel());
if (!klmi) {
return std::vector<Key>();
}
QList<QModelIndex> mapped;
mapped.reserve(indexes.size());
std::transform(indexes.begin(), //
indexes.end(),
std::back_inserter(mapped),
[this](const QModelIndex &idx) {
return mapToSource(idx);
});
return klmi->keys(mapped);
}
KeyGroup AbstractKeyListSortFilterProxyModel::group(const QModelIndex &idx) const
{
if (const KeyListModelInterface *const klmi = dynamic_cast<KeyListModelInterface *>(sourceModel())) {
return klmi->group(mapToSource(idx));
}
return KeyGroup();
}
QModelIndex AbstractKeyListSortFilterProxyModel::index(const Key &key) const
{
if (const KeyListModelInterface *const klmi = dynamic_cast<KeyListModelInterface *>(sourceModel())) {
return mapFromSource(klmi->index(key));
}
return {};
}
QList<QModelIndex> AbstractKeyListSortFilterProxyModel::indexes(const std::vector<Key> &keys) const
{
if (const KeyListModelInterface *const klmi = dynamic_cast<KeyListModelInterface *>(sourceModel())) {
const QList<QModelIndex> source = klmi->indexes(keys);
QList<QModelIndex> mapped;
mapped.reserve(source.size());
std::transform(source.begin(), //
source.end(),
std::back_inserter(mapped),
[this](const QModelIndex &idx) {
return mapFromSource(idx);
});
return mapped;
}
return QList<QModelIndex>();
}
QModelIndex AbstractKeyListSortFilterProxyModel::index(const Kleo::KeyGroup &group) const
{
if (const KeyListModelInterface *const klmi = dynamic_cast<KeyListModelInterface *>(sourceModel())) {
return mapFromSource(klmi->index(group));
}
return {};
}
class KeyListSortFilterProxyModel::Private
{
friend class ::Kleo::KeyListSortFilterProxyModel;
public:
explicit Private()
: keyFilter()
{
}
~Private()
{
}
private:
std::shared_ptr<const KeyFilter> keyFilter;
};
KeyListSortFilterProxyModel::KeyListSortFilterProxyModel(QObject *p)
: AbstractKeyListSortFilterProxyModel(p)
, d(new Private)
{
}
KeyListSortFilterProxyModel::KeyListSortFilterProxyModel(const KeyListSortFilterProxyModel &other)
: AbstractKeyListSortFilterProxyModel(other)
, d(new Private(*other.d))
{
}
KeyListSortFilterProxyModel::~KeyListSortFilterProxyModel()
{
}
KeyListSortFilterProxyModel *KeyListSortFilterProxyModel::clone() const
{
return new KeyListSortFilterProxyModel(*this);
}
std::shared_ptr<const KeyFilter> KeyListSortFilterProxyModel::keyFilter() const
{
return d->keyFilter;
}
void KeyListSortFilterProxyModel::setKeyFilter(const std::shared_ptr<const KeyFilter> &kf)
{
if (kf == d->keyFilter) {
return;
}
d->keyFilter = kf;
invalidate();
}
bool KeyListSortFilterProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
{
//
// 0. Keep parents of matching children:
//
const QModelIndex index = sourceModel()->index(source_row, 0, source_parent);
for (int i = 0, end = sourceModel()->rowCount(index); i != end; ++i) {
if (filterAcceptsRow(i, index)) {
return true;
}
}
//
// 1. Check filterRegExp
//
const int role = filterRole();
const int col = filterKeyColumn();
const QRegularExpression rx = filterRegularExpression();
const QModelIndex nameIndex = sourceModel()->index(source_row, KeyList::PrettyName, source_parent);
const KeyListModelInterface *const klm = dynamic_cast<KeyListModelInterface *>(sourceModel());
Q_ASSERT(klm);
const Key key = klm->key(nameIndex);
+ const auto userID = nameIndex.data(KeyList::UserIDRole).value<UserID>();
const KeyGroup group = klm->group(nameIndex);
Q_ASSERT(!key.isNull() || !group.isNull());
if (col) {
const QModelIndex colIdx = sourceModel()->index(source_row, col, source_parent);
const QString content = colIdx.data(role).toString();
if (!content.contains(rx)) {
return false;
}
} else if (!key.isNull()) {
// By default match against the full uid data (name / email / comment / dn)
bool match = false;
- for (const auto &uid : key.userIDs()) {
- const auto id = QString::fromUtf8(uid.id());
- if (id.contains(rx)) {
- match = true;
- break;
- }
- // Also match against remarks (search tags)
- const auto alm = dynamic_cast<AbstractKeyListModel *>(sourceModel());
- if (alm) {
- const auto remarks = alm->data(alm->index(key, KeyList::Remarks));
- if (!remarks.isNull() && remarks.toString().contains(rx)) {
+
+ if (userID.isNull()) {
+ for (const auto &uid : key.userIDs()) {
+ const auto id = QString::fromUtf8(uid.id());
+ if (id.contains(rx)) {
match = true;
break;
}
}
- // Also match against fingerprints
- for (const auto &subkey : key.subkeys()) {
- const auto fpr = QString::fromLatin1(subkey.fingerprint());
- if (fpr.contains(rx)) {
- match = true;
- break;
- }
+ } else {
+ const auto id = QString::fromUtf8(userID.id());
+ if (id.contains(rx)) {
+ match = true;
}
}
+ // Also match against remarks (search tags)
+ const auto alm = dynamic_cast<AbstractKeyListModel *>(sourceModel());
+ if (alm) {
+ const auto remarks = alm->data(alm->index(key, KeyList::Remarks));
+ if (!remarks.isNull() && remarks.toString().contains(rx)) {
+ match = true;
+ }
+ }
+ // Also match against fingerprints
+ for (const auto &subkey : key.subkeys()) {
+ const auto fpr = QString::fromLatin1(subkey.fingerprint());
+ if (fpr.contains(rx)) {
+ match = true;
+ break;
+ }
+ }
+
if (!match) {
return false;
}
} else if (!group.isNull()) {
if (!group.name().contains(rx)) {
return false;
}
} else {
return false;
}
//
// 2. For keys check that key filters match (if any are defined)
//
if (d->keyFilter && !key.isNull()) { // avoid artifacts when no filters are defined
return d->keyFilter->matches(key, KeyFilter::Filtering);
}
// 3. match by default:
return true;
}
#include "moc_keylistsortfilterproxymodel.cpp"
diff --git a/src/ui/keyselectioncombo.cpp b/src/ui/keyselectioncombo.cpp
index 02d87a1a..304db1d7 100644
--- a/src/ui/keyselectioncombo.cpp
+++ b/src/ui/keyselectioncombo.cpp
@@ -1,725 +1,723 @@
/* This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2016 Klarälvdalens Datakonsult AB
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-libkleo.h>
#include "keyselectioncombo.h"
#include "progressbar.h"
#include <libkleo/defaultkeyfilter.h>
#include <libkleo/dn.h>
#include <libkleo/formatting.h>
#include <libkleo/keycache.h>
#include <libkleo/keylist.h>
#include <libkleo/keylistmodel.h>
#include <libkleo/keylistsortfilterproxymodel.h>
+#include <libkleo/useridproxymodel.h>
#include <kleo_ui_debug.h>
#include <KLocalizedString>
#include <QList>
#include <QSortFilterProxyModel>
#include <QTimer>
#include <gpgme++/key.h>
using namespace Kleo;
#if !UNITY_BUILD
Q_DECLARE_METATYPE(GpgME::Key)
#endif
namespace
{
class SortFilterProxyModel : public KeyListSortFilterProxyModel
{
Q_OBJECT
public:
using KeyListSortFilterProxyModel::KeyListSortFilterProxyModel;
void setAlwaysAcceptedKey(const QString &fingerprint)
{
if (fingerprint == mFingerprint) {
return;
}
mFingerprint = fingerprint;
invalidate();
}
protected:
bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override
{
if (!mFingerprint.isEmpty()) {
const QModelIndex index = sourceModel()->index(source_row, 0, source_parent);
const auto fingerprint = sourceModel()->data(index, KeyList::FingerprintRole).toString();
if (fingerprint == mFingerprint) {
return true;
}
}
return KeyListSortFilterProxyModel::filterAcceptsRow(source_row, source_parent);
}
private:
QString mFingerprint;
};
-static QString formatUserID(const GpgME::Key &key)
+static QString formatUserID(const GpgME::UserID &userID)
{
- const auto userID = key.userID(0);
QString name;
QString email;
- if (key.protocol() == GpgME::OpenPGP) {
+ if (userID.parent().protocol() == GpgME::OpenPGP) {
name = QString::fromUtf8(userID.name());
email = QString::fromUtf8(userID.email());
} else {
const Kleo::DN dn(userID.id());
name = dn[QStringLiteral("CN")];
email = dn[QStringLiteral("EMAIL")];
+ if (name.isEmpty()) {
+ name = Formatting::prettyName(userID.parent());
+ }
}
return email.isEmpty() ? name : name.isEmpty() ? email : i18nc("Name <email>", "%1 <%2>", name, email);
}
class SortAndFormatCertificatesProxyModel : public QSortFilterProxyModel
{
Q_OBJECT
public:
SortAndFormatCertificatesProxyModel(KeyUsage::Flags usageFlags, QObject *parent = nullptr)
: QSortFilterProxyModel{parent}
, mIconProvider{usageFlags}
{
}
private:
bool lessThan(const QModelIndex &left, const QModelIndex &right) const override
{
- const auto leftKey = sourceModel()->data(left, KeyList::KeyRole).value<GpgME::Key>();
- const auto rightKey = sourceModel()->data(right, KeyList::KeyRole).value<GpgME::Key>();
- if (leftKey.isNull()) {
- return false;
- }
- if (rightKey.isNull()) {
- return true;
- }
- // As we display UID(0) this is ok. We probably need a get Best UID at some point.
- const auto lUid = leftKey.userID(0);
- const auto rUid = rightKey.userID(0);
- if (lUid.isNull()) {
+ const auto leftUserId = sourceModel()->data(left, KeyList::UserIDRole).value<GpgME::UserID>();
+ const auto rightUserId = sourceModel()->data(right, KeyList::UserIDRole).value<GpgME::UserID>();
+ if (leftUserId.isNull()) {
return false;
}
- if (rUid.isNull()) {
+ if (rightUserId.isNull()) {
return true;
}
- const auto leftNameAndEmail = formatUserID(leftKey);
- const auto rightNameAndEmail = formatUserID(rightKey);
+ const auto leftNameAndEmail = formatUserID(leftUserId);
+ const auto rightNameAndEmail = formatUserID(rightUserId);
const int cmp = QString::localeAwareCompare(leftNameAndEmail, rightNameAndEmail);
if (cmp) {
return cmp < 0;
}
- if (lUid.validity() != rUid.validity()) {
- return lUid.validity() > rUid.validity();
+ if (leftUserId.validity() != rightUserId.validity()) {
+ return leftUserId.validity() > rightUserId.validity();
}
/* Both have the same validity, check which one is newer. */
time_t leftTime = 0;
- for (const GpgME::Subkey &s : leftKey.subkeys()) {
+ for (const GpgME::Subkey &s : leftUserId.parent().subkeys()) {
if (s.isBad()) {
continue;
}
if (s.creationTime() > leftTime) {
leftTime = s.creationTime();
}
}
time_t rightTime = 0;
- for (const GpgME::Subkey &s : rightKey.subkeys()) {
+ for (const GpgME::Subkey &s : rightUserId.parent().subkeys()) {
if (s.isBad()) {
continue;
}
if (s.creationTime() > rightTime) {
rightTime = s.creationTime();
}
}
if (rightTime != leftTime) {
return leftTime > rightTime;
}
// as final resort we compare the fingerprints
- return strcmp(leftKey.primaryFingerprint(), rightKey.primaryFingerprint()) < 0;
+ return strcmp(leftUserId.parent().primaryFingerprint(), rightUserId.parent().primaryFingerprint()) < 0;
}
protected:
QVariant data(const QModelIndex &index, int role) const override
{
if (!index.isValid()) {
return QVariant();
}
- const auto key = QSortFilterProxyModel::data(index, KeyList::KeyRole).value<GpgME::Key>();
- Q_ASSERT(!key.isNull());
- if (key.isNull()) {
+ const auto userId = QSortFilterProxyModel::data(index, KeyList::UserIDRole).value<GpgME::UserID>();
+ Q_ASSERT(!userId.isNull());
+ if (userId.isNull()) {
return QVariant();
}
switch (role) {
case Qt::DisplayRole:
case Qt::AccessibleTextRole: {
- const auto nameAndEmail = formatUserID(key);
+ const auto nameAndEmail = formatUserID(userId);
if (Kleo::KeyCache::instance()->pgpOnly()) {
return i18nc("Name <email> (validity, created: date)",
"%1 (%2, created: %3)",
nameAndEmail,
- Kleo::Formatting::complianceStringShort(key),
- Kleo::Formatting::creationDateString(key));
+ Kleo::Formatting::complianceStringShort(userId),
+ Kleo::Formatting::creationDateString(userId.parent())); // TODO show user id creation time instead (?)
} else {
return i18nc("Name <email> (validity, type, created: date)",
"%1 (%2, %3, created: %4)",
nameAndEmail,
- Kleo::Formatting::complianceStringShort(key),
- Formatting::displayName(key.protocol()),
- Kleo::Formatting::creationDateString(key));
+ Kleo::Formatting::complianceStringShort(userId),
+ Formatting::displayName(userId.parent().protocol()),
+ Kleo::Formatting::creationDateString(userId.parent()));
}
}
case Qt::ToolTipRole: {
using namespace Kleo::Formatting;
- return Kleo::Formatting::toolTip(key, Validity | Issuer | Subject | Fingerprint | ExpiryDates | UserIDs);
+ return Kleo::Formatting::toolTip(userId.parent(), Validity | Issuer | Subject | Fingerprint | ExpiryDates | UserIDs); // TODO
}
case Qt::DecorationRole: {
- return mIconProvider.icon(key);
+ return mIconProvider.icon(userId.parent());
}
default:
return QSortFilterProxyModel::data(index, role);
}
}
private:
Formatting::IconProvider mIconProvider;
};
class CustomItemsProxyModel : public QSortFilterProxyModel
{
Q_OBJECT
private:
struct CustomItem {
QIcon icon;
QString text;
QVariant data;
QString toolTip;
};
public:
CustomItemsProxyModel(QObject *parent = nullptr)
: QSortFilterProxyModel(parent)
{
}
~CustomItemsProxyModel() override
{
qDeleteAll(mFrontItems);
qDeleteAll(mBackItems);
}
bool isCustomItem(const int row) const
{
return row < mFrontItems.count() || row >= mFrontItems.count() + QSortFilterProxyModel::rowCount();
}
void prependItem(const QIcon &icon, const QString &text, const QVariant &data, const QString &toolTip)
{
beginInsertRows(QModelIndex(), 0, 0);
mFrontItems.push_front(new CustomItem{icon, text, data, toolTip});
endInsertRows();
}
void appendItem(const QIcon &icon, const QString &text, const QVariant &data, const QString &toolTip)
{
beginInsertRows(QModelIndex(), rowCount(), rowCount());
mBackItems.push_back(new CustomItem{icon, text, data, toolTip});
endInsertRows();
}
void removeCustomItem(const QVariant &data)
{
for (int i = 0; i < mFrontItems.count(); ++i) {
if (mFrontItems[i]->data == data) {
beginRemoveRows(QModelIndex(), i, i);
delete mFrontItems.takeAt(i);
endRemoveRows();
return;
}
}
for (int i = 0; i < mBackItems.count(); ++i) {
if (mBackItems[i]->data == data) {
const int index = mFrontItems.count() + QSortFilterProxyModel::rowCount() + i;
beginRemoveRows(QModelIndex(), index, index);
delete mBackItems.takeAt(i);
endRemoveRows();
return;
}
}
}
int rowCount(const QModelIndex &parent = QModelIndex()) const override
{
return mFrontItems.count() + QSortFilterProxyModel::rowCount(parent) + mBackItems.count();
}
int columnCount(const QModelIndex &parent = QModelIndex()) const override
{
Q_UNUSED(parent)
// pretend that there is only one column to workaround a bug in
// QAccessibleTable which provides the accessibility interface for the
// pop-up of the combo box
return 1;
}
QModelIndex mapToSource(const QModelIndex &index) const override
{
if (!index.isValid()) {
return {};
}
if (!isCustomItem(index.row())) {
const int sourceRow = index.row() - mFrontItems.count();
return QSortFilterProxyModel::mapToSource(createIndex(sourceRow, index.column(), index.internalPointer()));
}
return {};
}
QModelIndex mapFromSource(const QModelIndex &source_index) const override
{
const QModelIndex idx = QSortFilterProxyModel::mapFromSource(source_index);
return createIndex(mFrontItems.count() + idx.row(), idx.column(), idx.internalPointer());
}
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override
{
if (row < 0 || row >= rowCount()) {
return {};
}
if (row < mFrontItems.count()) {
return createIndex(row, column, mFrontItems[row]);
} else if (row >= mFrontItems.count() + QSortFilterProxyModel::rowCount()) {
return createIndex(row, column, mBackItems[row - mFrontItems.count() - QSortFilterProxyModel::rowCount()]);
} else {
const QModelIndex mi = QSortFilterProxyModel::index(row - mFrontItems.count(), column, parent);
return createIndex(row, column, mi.internalPointer());
}
}
Qt::ItemFlags flags(const QModelIndex &index) const override
{
Q_UNUSED(index)
return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemNeverHasChildren;
}
QModelIndex parent(const QModelIndex &) const override
{
// Flat list
return {};
}
QVariant data(const QModelIndex &index, int role) const override
{
if (!index.isValid()) {
return QVariant();
}
if (isCustomItem(index.row())) {
Q_ASSERT(!mFrontItems.isEmpty() || !mBackItems.isEmpty());
auto ci = static_cast<CustomItem *>(index.internalPointer());
switch (role) {
case Qt::DisplayRole:
return ci->text;
case Qt::DecorationRole:
return ci->icon;
case Qt::UserRole:
return ci->data;
case Qt::ToolTipRole:
return ci->toolTip;
default:
return QVariant();
}
}
return QSortFilterProxyModel::data(index, role);
}
private:
QList<CustomItem *> mFrontItems;
QList<CustomItem *> mBackItems;
};
} // anonymous namespace
namespace Kleo
{
class KeySelectionComboPrivate
{
public:
KeySelectionComboPrivate(KeySelectionCombo *parent, bool secretOnly_, KeyUsage::Flags usage)
: wasEnabled(true)
, secretOnly{secretOnly_}
, usageFlags{usage}
, q{parent}
{
}
/* Selects the first key with a UID addrSpec that matches
* the mPerfectMatchMbox variable.
*
* The idea here is that if there are keys like:
*
* tom-store@abc.com
* susi-store@abc.com
* store@abc.com
*
* And the user wants to send a mail to "store@abc.com"
* the filter should still show tom and susi (because they
* both are part of store) but the key for "store" should
* be preselected.
*
* Returns true if one was selected. False otherwise. */
bool selectPerfectIdMatch() const
{
if (mPerfectMatchMbox.isEmpty()) {
return false;
}
for (int i = 0; i < proxyModel->rowCount(); ++i) {
const auto idx = proxyModel->index(i, 0, QModelIndex());
const auto key = proxyModel->data(idx, KeyList::KeyRole).value<GpgME::Key>();
if (key.isNull()) {
// WTF?
continue;
}
for (const auto &uid : key.userIDs()) {
if (QString::fromStdString(uid.addrSpec()) == mPerfectMatchMbox) {
q->setCurrentIndex(i);
return true;
}
}
}
return false;
}
/* Updates the current key with the default key if the key matches
* the current key filter. */
void updateWithDefaultKey()
{
GpgME::Protocol filterProto = GpgME::UnknownProtocol;
const auto filter = dynamic_cast<const DefaultKeyFilter *>(sortFilterProxy->keyFilter().get());
if (filter && filter->isOpenPGP() == DefaultKeyFilter::Set) {
filterProto = GpgME::OpenPGP;
} else if (filter && filter->isOpenPGP() == DefaultKeyFilter::NotSet) {
filterProto = GpgME::CMS;
}
QString defaultKey = defaultKeys.value(filterProto);
if (defaultKey.isEmpty()) {
// Fallback to unknown protocol
defaultKey = defaultKeys.value(GpgME::UnknownProtocol);
}
// make sure that the default key is not filtered out unless it has the wrong protocol
if (filterProto == GpgME::UnknownProtocol) {
sortFilterProxy->setAlwaysAcceptedKey(defaultKey);
} else {
const auto key = KeyCache::instance()->findByFingerprint(defaultKey.toLatin1().constData());
if (!key.isNull() && key.protocol() == filterProto) {
sortFilterProxy->setAlwaysAcceptedKey(defaultKey);
} else {
sortFilterProxy->setAlwaysAcceptedKey({});
}
}
q->setCurrentKey(defaultKey);
}
void storeCurrentSelectionBeforeModelChange()
{
keyBeforeModelChange = q->currentKey();
customItemBeforeModelChange = q->currentData();
}
void restoreCurrentSelectionAfterModelChange()
{
if (!keyBeforeModelChange.isNull()) {
q->setCurrentKey(keyBeforeModelChange);
} else if (customItemBeforeModelChange.isValid()) {
const auto index = q->findData(customItemBeforeModelChange);
if (index != -1) {
q->setCurrentIndex(index);
} else {
updateWithDefaultKey();
}
}
}
Kleo::AbstractKeyListModel *model = nullptr;
+ UserIDProxyModel *userIdProxy = nullptr;
SortFilterProxyModel *sortFilterProxy = nullptr;
SortAndFormatCertificatesProxyModel *sortAndFormatProxy = nullptr;
CustomItemsProxyModel *proxyModel = nullptr;
std::shared_ptr<Kleo::KeyCache> cache;
QMap<GpgME::Protocol, QString> defaultKeys;
bool wasEnabled = false;
bool useWasEnabled = false;
bool secretOnly = false;
bool initialKeyListingDone = false;
QString mPerfectMatchMbox;
GpgME::Key keyBeforeModelChange;
QVariant customItemBeforeModelChange;
KeyUsage::Flags usageFlags;
private:
KeySelectionCombo *const q;
};
}
using namespace Kleo;
KeySelectionCombo::KeySelectionCombo(QWidget *parent)
: KeySelectionCombo(true, KeyUsage::None, parent)
{
}
KeySelectionCombo::KeySelectionCombo(bool secretOnly, QWidget *parent)
: KeySelectionCombo(secretOnly, KeyUsage::None, parent)
{
}
KeySelectionCombo::KeySelectionCombo(KeyUsage::Flags usage, QWidget *parent)
: KeySelectionCombo{false, usage, parent}
{
}
KeySelectionCombo::KeySelectionCombo(KeyUsage::Flag usage, QWidget *parent)
: KeySelectionCombo{false, usage, parent}
{
}
KeySelectionCombo::KeySelectionCombo(bool secretOnly, KeyUsage::Flags usage, QWidget *parent)
: QComboBox(parent)
, d(new KeySelectionComboPrivate(this, secretOnly, usage))
{
// set a non-empty string as accessible description to prevent screen readers
// from reading the tool tip which isn't meant for screen readers
setAccessibleDescription(QStringLiteral(" "));
d->model = Kleo::AbstractKeyListModel::createFlatKeyListModel(this);
+ d->userIdProxy = new UserIDProxyModel(this);
+ d->userIdProxy->setSourceModel(d->model);
+
d->sortFilterProxy = new SortFilterProxyModel(this);
- d->sortFilterProxy->setSourceModel(d->model);
+ d->sortFilterProxy->setSourceModel(d->userIdProxy);
d->sortAndFormatProxy = new SortAndFormatCertificatesProxyModel{usage, this};
d->sortAndFormatProxy->setSourceModel(d->sortFilterProxy);
// initialize dynamic sorting
d->sortAndFormatProxy->sort(0);
d->proxyModel = new CustomItemsProxyModel{this};
d->proxyModel->setSourceModel(d->sortAndFormatProxy);
setModel(d->proxyModel);
connect(this, &QComboBox::currentIndexChanged, this, [this](int row) {
if (row >= 0 && row < d->proxyModel->rowCount()) {
if (d->proxyModel->isCustomItem(row)) {
Q_EMIT customItemSelected(currentData(Qt::UserRole));
} else {
Q_EMIT currentKeyChanged(currentKey());
}
}
});
d->cache = Kleo::KeyCache::mutableInstance();
connect(model(), &QAbstractItemModel::rowsAboutToBeInserted, this, [this]() {
d->storeCurrentSelectionBeforeModelChange();
});
connect(model(), &QAbstractItemModel::rowsInserted, this, [this]() {
d->restoreCurrentSelectionAfterModelChange();
});
connect(model(), &QAbstractItemModel::rowsAboutToBeRemoved, this, [this]() {
d->storeCurrentSelectionBeforeModelChange();
});
connect(model(), &QAbstractItemModel::rowsRemoved, this, [this]() {
d->restoreCurrentSelectionAfterModelChange();
});
connect(model(), &QAbstractItemModel::modelAboutToBeReset, this, [this]() {
d->storeCurrentSelectionBeforeModelChange();
});
connect(model(), &QAbstractItemModel::modelReset, this, [this]() {
d->restoreCurrentSelectionAfterModelChange();
});
QTimer::singleShot(0, this, &KeySelectionCombo::init);
}
KeySelectionCombo::~KeySelectionCombo() = default;
void KeySelectionCombo::init()
{
connect(d->cache.get(), &Kleo::KeyCache::keyListingDone, this, [this]() {
// Set useKeyCache ensures that the cache is populated
// so this can be a blocking call if the cache is not initialized
if (!d->initialKeyListingDone) {
d->model->useKeyCache(true, d->secretOnly ? KeyList::SecretKeysOnly : KeyList::AllKeys);
}
d->proxyModel->removeCustomItem(QStringLiteral("-libkleo-loading-keys"));
// We use the useWasEnabled state variable to decide if we should
// change the enable / disable state based on the keylist done signal.
// If we triggered the refresh useWasEnabled is true and we want to
// enable / disable again after our refresh, as the refresh disabled it.
//
// But if a keyListingDone signal comes from just a generic refresh
// triggered by someone else we don't want to change the enable / disable
// state.
if (d->useWasEnabled) {
setEnabled(d->wasEnabled);
d->useWasEnabled = false;
}
Q_EMIT keyListingFinished();
});
connect(this, &KeySelectionCombo::keyListingFinished, this, [this]() {
if (!d->initialKeyListingDone) {
d->updateWithDefaultKey();
d->initialKeyListingDone = true;
}
});
if (!d->cache->initialized()) {
refreshKeys();
} else {
d->model->useKeyCache(true, d->secretOnly ? KeyList::SecretKeysOnly : KeyList::AllKeys);
Q_EMIT keyListingFinished();
}
connect(this, &QComboBox::currentIndexChanged, this, [this]() {
setToolTip(currentData(Qt::ToolTipRole).toString());
});
}
void KeySelectionCombo::setKeyFilter(const std::shared_ptr<const KeyFilter> &kf)
{
d->sortFilterProxy->setKeyFilter(kf);
d->updateWithDefaultKey();
}
std::shared_ptr<const KeyFilter> KeySelectionCombo::keyFilter() const
{
return d->sortFilterProxy->keyFilter();
}
void KeySelectionCombo::setIdFilter(const QString &id)
{
d->sortFilterProxy->setFilterRegularExpression(id);
d->mPerfectMatchMbox = id;
d->updateWithDefaultKey();
}
QString KeySelectionCombo::idFilter() const
{
return d->sortFilterProxy->filterRegularExpression().pattern();
}
GpgME::Key Kleo::KeySelectionCombo::currentKey() const
{
return currentData(KeyList::KeyRole).value<GpgME::Key>();
}
void Kleo::KeySelectionCombo::setCurrentKey(const GpgME::Key &key)
{
const int idx = findData(QString::fromLatin1(key.primaryFingerprint()), KeyList::FingerprintRole, Qt::MatchExactly);
if (idx > -1) {
setCurrentIndex(idx);
} else if (!d->selectPerfectIdMatch()) {
d->updateWithDefaultKey();
}
setToolTip(currentData(Qt::ToolTipRole).toString());
}
void Kleo::KeySelectionCombo::setCurrentKey(const QString &fingerprint)
{
const auto cur = currentKey();
if (!cur.isNull() && !fingerprint.isEmpty() && fingerprint == QLatin1StringView(cur.primaryFingerprint())) {
// already set; still emit a changed signal because the current key may
// have become the item at the current index by changes in the underlying model
Q_EMIT currentKeyChanged(cur);
return;
}
const int idx = findData(fingerprint, KeyList::FingerprintRole, Qt::MatchExactly);
if (idx > -1) {
setCurrentIndex(idx);
} else if (!d->selectPerfectIdMatch()) {
setCurrentIndex(0);
}
setToolTip(currentData(Qt::ToolTipRole).toString());
}
void KeySelectionCombo::refreshKeys()
{
d->wasEnabled = isEnabled();
d->useWasEnabled = true;
setEnabled(false);
const bool wasBlocked = blockSignals(true);
prependCustomItem(QIcon(), i18n("Loading keys ..."), QStringLiteral("-libkleo-loading-keys"));
setCurrentIndex(0);
blockSignals(wasBlocked);
d->cache->startKeyListing();
}
void KeySelectionCombo::appendCustomItem(const QIcon &icon, const QString &text, const QVariant &data, const QString &toolTip)
{
d->proxyModel->appendItem(icon, text, data, toolTip);
}
void KeySelectionCombo::appendCustomItem(const QIcon &icon, const QString &text, const QVariant &data)
{
appendCustomItem(icon, text, data, QString());
}
void KeySelectionCombo::prependCustomItem(const QIcon &icon, const QString &text, const QVariant &data, const QString &toolTip)
{
d->proxyModel->prependItem(icon, text, data, toolTip);
}
void KeySelectionCombo::prependCustomItem(const QIcon &icon, const QString &text, const QVariant &data)
{
prependCustomItem(icon, text, data, QString());
}
void KeySelectionCombo::removeCustomItem(const QVariant &data)
{
d->proxyModel->removeCustomItem(data);
}
void Kleo::KeySelectionCombo::setDefaultKey(const QString &fingerprint, GpgME::Protocol proto)
{
d->defaultKeys.insert(proto, fingerprint);
d->updateWithDefaultKey();
}
void Kleo::KeySelectionCombo::setDefaultKey(const QString &fingerprint)
{
setDefaultKey(fingerprint, GpgME::UnknownProtocol);
}
QString Kleo::KeySelectionCombo::defaultKey(GpgME::Protocol proto) const
{
return d->defaultKeys.value(proto);
}
QString Kleo::KeySelectionCombo::defaultKey() const
{
return defaultKey(GpgME::UnknownProtocol);
}
#include "keyselectioncombo.moc"
#include "moc_keyselectioncombo.cpp"
diff --git a/src/utils/formatting.cpp b/src/utils/formatting.cpp
index 6397cbaf..9c568b56 100644
--- a/src/utils/formatting.cpp
+++ b/src/utils/formatting.cpp
@@ -1,1486 +1,1636 @@
/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil; -*-
utils/formatting.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB
SPDX-FileCopyrightText: 2021, 2022 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 "formatting.h"
#include "algorithm.h"
#include "compat.h"
#include "compliance.h"
#include "cryptoconfig.h"
#include "gnupg.h"
#include "keyhelpers.h"
#include <libkleo/dn.h>
#include <libkleo/keycache.h>
#include <libkleo/keygroup.h>
#include <libkleo_debug.h>
#include <KEmailAddress>
#include <KLocalizedString>
#include <QGpgME/CryptoConfig>
#include <QGpgME/Protocol>
#include <QDateTime>
#include <QIcon>
#include <QLocale>
#include <QRegularExpression>
#include <QString>
#include <QTextDocument> // for Qt::escape
#include <gpgme++/importresult.h>
#include <gpgme++/key.h>
#include <gpg-error.h>
using namespace GpgME;
using namespace Kleo;
namespace
{
QIcon iconForValidityAndCompliance(UserID::Validity validity, bool isCompliant)
{
switch (validity) {
case UserID::Ultimate:
case UserID::Full:
case UserID::Marginal:
return isCompliant ? Formatting::successIcon() : Formatting::infoIcon();
case UserID::Never:
return Formatting::errorIcon();
case UserID::Undefined:
case UserID::Unknown:
default:
return Formatting::infoIcon();
}
}
QIcon iconForValidity(const UserID &userId)
{
const bool keyIsCompliant = !DeVSCompliance::isActive() || //
(DeVSCompliance::isCompliant() && DeVSCompliance::keyIsCompliant(userId.parent()));
return iconForValidityAndCompliance(userId.validity(), keyIsCompliant);
}
}
QIcon Formatting::IconProvider::icon(const GpgME::Key &key) const
{
if (usage.canEncrypt() && !Kleo::canBeUsedForEncryption(key)) {
return Formatting::errorIcon();
}
if (usage.canSign() && !Kleo::canBeUsedForSigning(key)) {
return Formatting::errorIcon();
}
if (key.isBad()) {
return Formatting::errorIcon();
}
const auto primaryUserId = key.userID(0);
if (Kleo::isRevokedOrExpired(primaryUserId)) {
return Formatting::errorIcon();
}
return iconForValidity(primaryUserId);
}
+QIcon Formatting::IconProvider::icon(const GpgME::UserID &userID) const
+{
+ if (usage.canEncrypt() && !Kleo::canBeUsedForEncryption(userID.parent())) {
+ return Formatting::errorIcon();
+ }
+ if (usage.canSign() && !Kleo::canBeUsedForSigning(userID.parent())) {
+ return Formatting::errorIcon();
+ }
+ if (userID.parent().isBad() || userID.isBad()) {
+ return Formatting::errorIcon();
+ }
+ if (Kleo::isRevokedOrExpired(userID)) {
+ return Formatting::errorIcon();
+ }
+ return iconForValidity(userID);
+}
+
QIcon Formatting::IconProvider::icon(const KeyGroup &group) const
{
if (usage.canEncrypt() && !Kleo::all_of(group.keys(), Kleo::canBeUsedForEncryption)) {
return Formatting::errorIcon();
}
if (usage.canSign() && !Kleo::all_of(group.keys(), Kleo::canBeUsedForSigning)) {
return Formatting::errorIcon();
}
return validityIcon(group);
}
QIcon Formatting::successIcon()
{
return QIcon::fromTheme(QStringLiteral("emblem-success"));
}
QIcon Formatting::infoIcon()
{
return QIcon::fromTheme(QStringLiteral("emblem-information"));
}
QIcon Formatting::questionIcon()
{
return QIcon::fromTheme(QStringLiteral("emblem-question"));
}
QIcon Formatting::unavailableIcon()
{
return QIcon::fromTheme(QStringLiteral("emblem-unavailable"));
}
QIcon Formatting::warningIcon()
{
return QIcon::fromTheme(QStringLiteral("emblem-warning"));
}
QIcon Formatting::errorIcon()
{
return QIcon::fromTheme(QStringLiteral("emblem-error"));
}
//
// Name
//
QString Formatting::prettyName(int proto, const char *id, const char *name_, const char *comment_)
{
if (proto == GpgME::OpenPGP) {
const QString name = QString::fromUtf8(name_);
if (name.isEmpty()) {
return QString();
}
const QString comment = QString::fromUtf8(comment_);
if (comment.isEmpty()) {
return name;
}
return QStringLiteral("%1 (%2)").arg(name, comment);
}
if (proto == GpgME::CMS) {
const DN subject(id);
const QString cn = subject[QStringLiteral("CN")].trimmed();
if (cn.isEmpty()) {
return subject.prettyDN();
}
return cn;
}
return QString();
}
QString Formatting::prettyNameAndEMail(int proto, const char *id, const char *name_, const char *email_, const char *comment_)
{
return prettyNameAndEMail(proto, QString::fromUtf8(id), QString::fromUtf8(name_), prettyEMail(email_, id), QString::fromUtf8(comment_));
}
QString Formatting::prettyNameAndEMail(int proto, const QString &id, const QString &name, const QString &email, const QString &comment)
{
if (proto == GpgME::OpenPGP) {
if (name.isEmpty()) {
if (email.isEmpty()) {
return QString();
} else if (comment.isEmpty()) {
return QStringLiteral("<%1>").arg(email);
} else {
return QStringLiteral("(%2) <%1>").arg(email, comment);
}
}
if (email.isEmpty()) {
if (comment.isEmpty()) {
return name;
} else {
return QStringLiteral("%1 (%2)").arg(name, comment);
}
}
if (comment.isEmpty()) {
return QStringLiteral("%1 <%2>").arg(name, email);
} else {
return QStringLiteral("%1 (%3) <%2>").arg(name, email, comment);
}
}
if (proto == GpgME::CMS) {
const DN subject(id);
const QString cn = subject[QStringLiteral("CN")].trimmed();
if (cn.isEmpty()) {
return subject.prettyDN();
}
return cn;
}
return QString();
}
QString Formatting::prettyUserID(const UserID &uid)
{
if (uid.parent().protocol() == GpgME::OpenPGP) {
return prettyNameAndEMail(uid);
}
const QByteArray id = QByteArray(uid.id()).trimmed();
if (id.startsWith('<')) {
return prettyEMail(uid.email(), uid.id());
}
if (id.startsWith('(')) {
// ### parse uri/dns:
return QString::fromUtf8(uid.id());
} else {
return DN(uid.id()).prettyDN();
}
}
QString Formatting::prettyKeyID(const char *id)
{
if (!id) {
return QString();
}
return QLatin1StringView("0x") + QString::fromLatin1(id).toUpper();
}
QString Formatting::prettyNameAndEMail(const UserID &uid)
{
return prettyNameAndEMail(uid.parent().protocol(), uid.id(), uid.name(), uid.email(), uid.comment());
}
QString Formatting::prettyNameAndEMail(const Key &key)
{
return prettyNameAndEMail(key.userID(0));
}
QString Formatting::prettyName(const Key &key)
{
return prettyName(key.userID(0));
}
QString Formatting::prettyName(const UserID &uid)
{
return prettyName(uid.parent().protocol(), uid.id(), uid.name(), uid.comment());
}
QString Formatting::prettyName(const UserID::Signature &sig)
{
return prettyName(GpgME::OpenPGP, sig.signerUserID(), sig.signerName(), sig.signerComment());
}
//
// EMail
//
QString Formatting::prettyEMail(const Key &key)
{
for (unsigned int i = 0, end = key.numUserIDs(); i < end; ++i) {
const QString email = prettyEMail(key.userID(i));
if (!email.isEmpty()) {
return email;
}
}
return QString();
}
QString Formatting::prettyEMail(const UserID &uid)
{
return prettyEMail(uid.email(), uid.id());
}
QString Formatting::prettyEMail(const UserID::Signature &sig)
{
return prettyEMail(sig.signerEmail(), sig.signerUserID());
}
QString Formatting::prettyEMail(const char *email_, const char *id)
{
QString email;
QString name;
QString comment;
if (email_ && KEmailAddress::splitAddress(QString::fromUtf8(email_), name, email, comment) == KEmailAddress::AddressOk) {
return email;
} else {
return DN(id)[QStringLiteral("EMAIL")].trimmed();
}
}
//
// Tooltip
//
namespace
{
static QString protect_whitespace(QString s)
{
static const QLatin1Char SP(' ');
static const QLatin1Char NBSP('\xA0');
return s.replace(SP, NBSP);
}
template<typename T_arg>
QString format_row(const QString &field, const T_arg &arg)
{
return QStringLiteral("<tr><th>%1:</th><td>%2</td></tr>").arg(protect_whitespace(field), arg);
}
QString format_row(const QString &field, const QString &arg)
{
return QStringLiteral("<tr><th>%1:</th><td>%2</td></tr>").arg(protect_whitespace(field), arg.toHtmlEscaped());
}
QString format_row(const QString &field, const char *arg)
{
return format_row(field, QString::fromUtf8(arg));
}
QString format_keytype(const Key &key)
{
const Subkey subkey = key.subkey(0);
if (key.hasSecret()) {
return i18n("%1-bit %2 (secret key available)", subkey.length(), QLatin1StringView(subkey.publicKeyAlgorithmAsString()));
} else {
return i18n("%1-bit %2", subkey.length(), QLatin1StringView(subkey.publicKeyAlgorithmAsString()));
}
}
QString format_subkeytype(const Subkey &subkey)
{
const auto algo = subkey.publicKeyAlgorithm();
if (algo == Subkey::AlgoECC || algo == Subkey::AlgoECDSA || algo == Subkey::AlgoECDH || algo == Subkey::AlgoEDDSA) {
return QString::fromStdString(subkey.algoName());
}
return i18n("%1-bit %2", subkey.length(), QLatin1StringView(subkey.publicKeyAlgorithmAsString()));
}
QString format_keyusage(const Key &key)
{
QStringList capabilities;
if (Kleo::keyHasSign(key)) {
if (key.isQualified()) {
capabilities.push_back(i18n("Signing (Qualified)"));
} else {
capabilities.push_back(i18n("Signing"));
}
}
if (Kleo::keyHasEncrypt(key)) {
capabilities.push_back(i18n("Encryption"));
}
if (Kleo::keyHasCertify(key)) {
capabilities.push_back(i18n("Certifying User-IDs"));
}
if (Kleo::keyHasAuthenticate(key)) {
capabilities.push_back(i18n("SSH Authentication"));
}
return capabilities.join(QLatin1StringView(", "));
}
QString format_subkeyusage(const Subkey &subkey)
{
QStringList capabilities;
if (subkey.canSign()) {
if (subkey.isQualified()) {
capabilities.push_back(i18n("Signing (Qualified)"));
} else {
capabilities.push_back(i18n("Signing"));
}
}
if (subkey.canEncrypt()) {
capabilities.push_back(i18n("Encryption"));
}
if (subkey.canCertify()) {
capabilities.push_back(i18n("Certifying User-IDs"));
}
if (subkey.canAuthenticate()) {
capabilities.push_back(i18n("SSH Authentication"));
}
return capabilities.join(QLatin1StringView(", "));
}
static QString time_t2string(time_t t)
{
const QDateTime dt = QDateTime::fromSecsSinceEpoch(quint32(t));
return QLocale().toString(dt, QLocale::ShortFormat);
}
static QString make_red(const QString &txt)
{
return QLatin1StringView("<font color=\"red\">") + txt.toHtmlEscaped() + QLatin1String("</font>");
}
}
QString Formatting::toolTip(const Key &key, int flags)
{
if (flags == 0 || (key.protocol() != GpgME::CMS && key.protocol() != GpgME::OpenPGP)) {
return QString();
}
const Subkey subkey = key.subkey(0);
QString result;
if (flags & Validity) {
if (key.protocol() == GpgME::OpenPGP || (key.keyListMode() & Validate)) {
if (key.isRevoked()) {
result = make_red(i18n("Revoked"));
} else if (key.isExpired()) {
result = make_red(i18n("Expired"));
} else if (key.isDisabled()) {
result = i18n("Disabled");
} else if (key.keyListMode() & GpgME::Validate) {
unsigned int fullyTrusted = 0;
for (const auto &uid : key.userIDs()) {
if (uid.validity() >= UserID::Validity::Full) {
fullyTrusted++;
}
}
if (fullyTrusted == key.numUserIDs()) {
result = i18n("All User-IDs are certified.");
const auto compliance = complianceStringForKey(key);
if (!compliance.isEmpty()) {
result += QStringLiteral("<br>") + compliance;
}
} else {
result = i18np("One User-ID is not certified.", "%1 User-IDs are not certified.", key.numUserIDs() - fullyTrusted);
}
} else {
result = i18n("The validity cannot be checked at the moment.");
}
} else {
result = i18n("The validity cannot be checked at the moment.");
}
}
if (flags == Validity) {
return result;
}
result += QLatin1StringView("<table border=\"0\">");
if (key.protocol() == GpgME::CMS) {
if (flags & SerialNumber) {
result += format_row(i18n("Serial number"), key.issuerSerial());
}
if (flags & Issuer) {
result += format_row(i18n("Issuer"), key.issuerName());
}
}
if (flags & UserIDs) {
const std::vector<UserID> uids = key.userIDs();
if (!uids.empty()) {
result += format_row(key.protocol() == GpgME::CMS ? i18n("Subject") : i18n("User-ID"), prettyUserID(uids.front()));
}
if (uids.size() > 1) {
for (auto it = uids.begin() + 1, end = uids.end(); it != end; ++it) {
if (!it->isRevoked() && !it->isInvalid()) {
result += format_row(i18n("a.k.a."), prettyUserID(*it));
}
}
}
}
if (flags & ExpiryDates) {
result += format_row(i18n("Valid from"), time_t2string(subkey.creationTime()));
if (!subkey.neverExpires()) {
result += format_row(i18n("Valid until"), time_t2string(subkey.expirationTime()));
}
}
if (flags & CertificateType) {
result += format_row(i18n("Type"), format_keytype(key));
}
if (flags & CertificateUsage) {
result += format_row(i18n("Usage"), format_keyusage(key));
}
if (flags & KeyID) {
result += format_row(i18n("Key-ID"), QString::fromLatin1(key.shortKeyID()));
}
if (flags & Fingerprint) {
result += format_row(i18n("Fingerprint"), key.primaryFingerprint());
}
if (flags & OwnerTrust) {
if (key.protocol() == GpgME::OpenPGP) {
result += format_row(i18n("Certification trust"), ownerTrustShort(key));
} else if (key.isRoot()) {
result += format_row(i18n("Trusted issuer?"), key.userID(0).validity() == UserID::Ultimate ? i18n("Yes") : i18n("No"));
}
}
if (flags & StorageLocation) {
if (const char *card = subkey.cardSerialNumber()) {
result += format_row(i18n("Stored"), i18nc("stored...", "on SmartCard with serial no. %1", QString::fromUtf8(card)));
} else {
result += format_row(i18n("Stored"), i18nc("stored...", "on this computer"));
}
}
if (flags & Subkeys) {
for (const auto &sub : key.subkeys()) {
result += QLatin1StringView("<hr/>");
result += format_row(i18n("Subkey"), sub.fingerprint());
if (sub.isRevoked()) {
result += format_row(i18n("Status"), i18n("Revoked"));
} else if (sub.isExpired()) {
result += format_row(i18n("Status"), i18n("Expired"));
}
if (flags & ExpiryDates) {
result += format_row(i18n("Valid from"), time_t2string(sub.creationTime()));
if (!sub.neverExpires()) {
result += format_row(i18n("Valid until"), time_t2string(sub.expirationTime()));
}
}
if (flags & CertificateType) {
result += format_row(i18n("Type"), format_subkeytype(sub));
}
if (flags & CertificateUsage) {
result += format_row(i18n("Usage"), format_subkeyusage(sub));
}
if (flags & StorageLocation) {
if (const char *card = sub.cardSerialNumber()) {
result += format_row(i18n("Stored"), i18nc("stored...", "on SmartCard with serial no. %1", QString::fromUtf8(card)));
} else {
result += format_row(i18n("Stored"), i18nc("stored...", "on this computer"));
}
}
}
}
result += QLatin1StringView("</table>");
return result;
}
namespace
{
template<typename Container>
QString getValidityStatement(const Container &keys)
{
const bool allKeysAreOpenPGP = std::all_of(keys.cbegin(), keys.cend(), [](const Key &key) {
return key.protocol() == GpgME::OpenPGP;
});
const bool allKeysAreValidated = std::all_of(keys.cbegin(), keys.cend(), [](const Key &key) {
return key.keyListMode() & Validate;
});
if (allKeysAreOpenPGP || allKeysAreValidated) {
const bool someKeysAreBad = std::any_of(keys.cbegin(), keys.cend(), std::mem_fn(&Key::isBad));
if (someKeysAreBad) {
return i18n("Some keys are revoked, expired, disabled, or invalid.");
} else {
const bool allKeysAreFullyValid = std::all_of(keys.cbegin(), keys.cend(), &Kleo::allUserIDsHaveFullValidity);
if (allKeysAreFullyValid) {
return i18n("All keys are certified.");
} else {
return i18n("Some keys are not certified.");
}
}
}
return i18n("The validity of the keys cannot be checked at the moment.");
}
}
QString Formatting::toolTip(const KeyGroup &group, int flags)
{
static const unsigned int maxNumKeysForTooltip = 20;
if (group.isNull()) {
return QString();
}
const KeyGroup::Keys &keys = group.keys();
if (keys.size() == 0) {
return i18nc("@info:tooltip", "This group does not contain any keys.");
}
const QString validity = (flags & Validity) ? getValidityStatement(keys) : QString();
if (flags == Validity) {
return validity;
}
// list either up to maxNumKeysForTooltip keys or (maxNumKeysForTooltip-1) keys followed by "and n more keys"
const unsigned int numKeysForTooltip = keys.size() > maxNumKeysForTooltip ? maxNumKeysForTooltip - 1 : keys.size();
QStringList result;
result.reserve(3 + 2 + numKeysForTooltip + 2);
if (!validity.isEmpty()) {
result.push_back(QStringLiteral("<p>"));
result.push_back(validity.toHtmlEscaped());
result.push_back(QStringLiteral("</p>"));
}
result.push_back(QStringLiteral("<p>"));
result.push_back(i18n("Keys:"));
{
auto it = keys.cbegin();
for (unsigned int i = 0; i < numKeysForTooltip; ++i, ++it) {
result.push_back(QLatin1StringView("<br>") + Formatting::summaryLine(*it).toHtmlEscaped());
}
}
if (keys.size() > numKeysForTooltip) {
result.push_back(QLatin1StringView("<br>")
+ i18ncp("this follows a list of keys", "and 1 more key", "and %1 more keys", keys.size() - numKeysForTooltip));
}
result.push_back(QStringLiteral("</p>"));
return result.join(QLatin1Char('\n'));
}
+QString Formatting::toolTip(const UserID &userID, int flags)
+{
+ if (flags == 0 || (userID.parent().protocol() != GpgME::CMS && userID.parent().protocol() != GpgME::OpenPGP)) {
+ return QString();
+ }
+
+ const Subkey subkey = userID.parent().subkey(0);
+
+ QString result;
+ if (flags & Validity) {
+ if (userID.parent().protocol() == GpgME::OpenPGP || (userID.parent().keyListMode() & Validate)) {
+ if (userID.isRevoked() || userID.parent().isRevoked()) {
+ result = make_red(i18n("Revoked"));
+ } else if (userID.parent().isExpired()) {
+ result = make_red(i18n("Expired"));
+ } else if (userID.parent().isDisabled()) {
+ result = i18n("Disabled");
+ } else if (userID.parent().keyListMode() & GpgME::Validate) {
+ if (userID.validity() >= UserID::Validity::Full) {
+ result = i18n("User-ID is certified.");
+ const auto compliance = complianceStringForUserID(userID);
+ if (!compliance.isEmpty()) {
+ result += QStringLiteral("<br>") + compliance;
+ }
+ } else {
+ result = i18n("User-ID is not certified.");
+ }
+ } else {
+ result = i18n("The validity cannot be checked at the moment.");
+ }
+ } else {
+ result = i18n("The validity cannot be checked at the moment.");
+ }
+ }
+ if (flags == Validity) {
+ return result;
+ }
+
+ result += QLatin1StringView("<table border=\"0\">");
+ if (userID.parent().protocol() == GpgME::CMS) {
+ if (flags & SerialNumber) {
+ result += format_row(i18n("Serial number"), userID.parent().issuerSerial());
+ }
+ if (flags & Issuer) {
+ result += format_row(i18n("Issuer"), userID.parent().issuerName());
+ }
+ }
+ if (flags & UserIDs) {
+ result += format_row(userID.parent().protocol() == GpgME::CMS ? i18n("Subject") : i18n("User-ID"), prettyUserID(userID));
+ }
+ if (flags & ExpiryDates) {
+ result += format_row(i18n("Valid from"), time_t2string(subkey.creationTime()));
+
+ if (!subkey.neverExpires()) {
+ result += format_row(i18n("Valid until"), time_t2string(subkey.expirationTime()));
+ }
+ }
+ if (flags & CertificateType) {
+ result += format_row(i18n("Type"), format_keytype(userID.parent()));
+ }
+ if (flags & CertificateUsage) {
+ result += format_row(i18n("Usage"), format_keyusage(userID.parent()));
+ }
+ if (flags & KeyID) {
+ result += format_row(i18n("Key-ID"), QString::fromLatin1(userID.parent().shortKeyID()));
+ }
+ if (flags & Fingerprint) {
+ result += format_row(i18n("Fingerprint"), userID.parent().primaryFingerprint());
+ }
+ if (flags & OwnerTrust) {
+ if (userID.parent().protocol() == GpgME::OpenPGP) {
+ result += format_row(i18n("Certification trust"), ownerTrustShort(userID.parent()));
+ } else if (userID.parent().isRoot()) {
+ result += format_row(i18n("Trusted issuer?"), userID.validity() == UserID::Ultimate ? i18n("Yes") : i18n("No"));
+ }
+ }
+
+ if (flags & StorageLocation) {
+ if (const char *card = subkey.cardSerialNumber()) {
+ result += format_row(i18n("Stored"), i18nc("stored...", "on SmartCard with serial no. %1", QString::fromUtf8(card)));
+ } else {
+ result += format_row(i18n("Stored"), i18nc("stored...", "on this computer"));
+ }
+ }
+ if (flags & Subkeys) {
+ for (const auto &sub : userID.parent().subkeys()) {
+ result += QLatin1StringView("<hr/>");
+ result += format_row(i18n("Subkey"), sub.fingerprint());
+ if (sub.isRevoked()) {
+ result += format_row(i18n("Status"), i18n("Revoked"));
+ } else if (sub.isExpired()) {
+ result += format_row(i18n("Status"), i18n("Expired"));
+ }
+ if (flags & ExpiryDates) {
+ result += format_row(i18n("Valid from"), time_t2string(sub.creationTime()));
+
+ if (!sub.neverExpires()) {
+ result += format_row(i18n("Valid until"), time_t2string(sub.expirationTime()));
+ }
+ }
+
+ if (flags & CertificateType) {
+ result += format_row(i18n("Type"), format_subkeytype(sub));
+ }
+ if (flags & CertificateUsage) {
+ result += format_row(i18n("Usage"), format_subkeyusage(sub));
+ }
+ if (flags & StorageLocation) {
+ if (const char *card = sub.cardSerialNumber()) {
+ result += format_row(i18n("Stored"), i18nc("stored...", "on SmartCard with serial no. %1", QString::fromUtf8(card)));
+ } else {
+ result += format_row(i18n("Stored"), i18nc("stored...", "on this computer"));
+ }
+ }
+ }
+ }
+ result += QLatin1StringView("</table>");
+
+ return result;
+}
+
//
// Creation and Expiration
//
namespace
{
static QDate time_t2date(time_t t)
{
if (!t) {
return {};
}
const QDateTime dt = QDateTime::fromSecsSinceEpoch(quint32(t));
return dt.date();
}
static QString accessible_date_format()
{
return i18nc(
"date format suitable for screen readers; "
"d: day as a number without a leading zero, "
"MMMM: localized month name, "
"yyyy: year as a four digit number",
"MMMM d, yyyy");
}
template<typename T>
QString expiration_date_string(const T &tee, const QString &noExpiration)
{
return tee.neverExpires() ? noExpiration : Formatting::dateString(time_t2date(tee.expirationTime()));
}
template<typename T>
QDate creation_date(const T &tee)
{
return time_t2date(tee.creationTime());
}
template<typename T>
QDate expiration_date(const T &tee)
{
return time_t2date(tee.expirationTime());
}
}
QString Formatting::dateString(time_t t)
{
return dateString(time_t2date(t));
}
QString Formatting::dateString(const QDate &date)
{
return QLocale().toString(date, QLocale::ShortFormat);
}
QString Formatting::accessibleDate(time_t t)
{
return accessibleDate(time_t2date(t));
}
QString Formatting::accessibleDate(const QDate &date)
{
return QLocale().toString(date, accessible_date_format());
}
QString Formatting::expirationDateString(const Key &key, const QString &noExpiration)
{
// if key is remote but has a non-zero expiration date (e.g. a key looked up via WKD),
// then we assume that the date is valid; if the date is zero for a remote key, then
// we don't know if it's unknown or unlimited
return isRemoteKey(key) && (key.subkey(0).expirationTime() == 0) //
? i18nc("@info the expiration date of the key is unknown", "unknown")
: expiration_date_string(key.subkey(0), noExpiration);
}
QString Formatting::expirationDateString(const Subkey &subkey, const QString &noExpiration)
{
return expiration_date_string(subkey, noExpiration);
}
QString Formatting::expirationDateString(const UserID::Signature &sig, const QString &noExpiration)
{
return expiration_date_string(sig, noExpiration);
}
QDate Formatting::expirationDate(const Key &key)
{
return expiration_date(key.subkey(0));
}
QDate Formatting::expirationDate(const Subkey &subkey)
{
return expiration_date(subkey);
}
QDate Formatting::expirationDate(const UserID::Signature &sig)
{
return expiration_date(sig);
}
QString Formatting::accessibleExpirationDate(const Key &key, const QString &noExpiration)
{
// if key is remote but has a non-zero expiration date (e.g. a key looked up via WKD),
// then we assume that the date is valid; if the date is zero for a remote key, then
// we don't know if it's unknown or unlimited
return isRemoteKey(key) && (key.subkey(0).expirationTime() == 0) //
? i18nc("@info the expiration date of the key is unknown", "unknown")
: accessibleExpirationDate(key.subkey(0), noExpiration);
}
QString Formatting::accessibleExpirationDate(const Subkey &subkey, const QString &noExpiration)
{
if (subkey.neverExpires()) {
return noExpiration.isEmpty() ? i18n("unlimited") : noExpiration;
} else {
return accessibleDate(expirationDate(subkey));
}
}
QString Formatting::accessibleExpirationDate(const UserID::Signature &sig, const QString &noExpiration)
{
if (sig.neverExpires()) {
return noExpiration.isEmpty() ? i18n("unlimited") : noExpiration;
} else {
return accessibleDate(expirationDate(sig));
}
}
QString Formatting::creationDateString(const Key &key)
{
return dateString(creation_date(key.subkey(0)));
}
QString Formatting::creationDateString(const Subkey &subkey)
{
return dateString(creation_date(subkey));
}
QString Formatting::creationDateString(const UserID::Signature &sig)
{
return dateString(creation_date(sig));
}
QDate Formatting::creationDate(const Key &key)
{
return creation_date(key.subkey(0));
}
QDate Formatting::creationDate(const Subkey &subkey)
{
return creation_date(subkey);
}
QDate Formatting::creationDate(const UserID::Signature &sig)
{
return creation_date(sig);
}
QString Formatting::accessibleCreationDate(const Key &key)
{
return accessibleDate(creationDate(key));
}
QString Formatting::accessibleCreationDate(const Subkey &subkey)
{
return accessibleDate(creationDate(subkey));
}
//
// Types
//
QString Formatting::displayName(GpgME::Protocol p)
{
if (p == GpgME::CMS) {
return i18nc("X.509/CMS encryption standard", "S/MIME");
}
if (p == GpgME::OpenPGP) {
return i18n("OpenPGP");
}
return i18nc("Unknown encryption protocol", "Unknown");
}
QString Formatting::type(const Key &key)
{
return displayName(key.protocol());
}
QString Formatting::type(const Subkey &subkey)
{
return QString::fromUtf8(subkey.publicKeyAlgorithmAsString());
}
QString Formatting::type(const KeyGroup &group)
{
Q_UNUSED(group)
return i18nc("a group of keys/certificates", "Group");
}
//
// Status / Validity
//
QString Formatting::ownerTrustShort(const Key &key)
{
return ownerTrustShort(key.ownerTrust());
}
QString Formatting::ownerTrustShort(Key::OwnerTrust trust)
{
switch (trust) {
case Key::Unknown:
return i18nc("unknown trust level", "unknown");
case Key::Never:
return i18n("untrusted");
case Key::Marginal:
return i18nc("marginal trust", "marginal");
case Key::Full:
return i18nc("full trust", "full");
case Key::Ultimate:
return i18nc("ultimate trust", "ultimate");
case Key::Undefined:
return i18nc("undefined trust", "undefined");
default:
Q_ASSERT(!"unexpected owner trust value");
break;
}
return QString();
}
QString Formatting::validityShort(const Subkey &subkey)
{
if (subkey.isRevoked()) {
return i18n("revoked");
}
if (subkey.isExpired()) {
return i18n("expired");
}
if (subkey.isDisabled()) {
return i18n("disabled");
}
if (subkey.isInvalid()) {
return i18n("invalid");
}
return i18nc("as in good/valid signature", "good");
}
QString Formatting::validityShort(const UserID &uid)
{
if (uid.isRevoked()) {
return i18n("revoked");
}
if (uid.isInvalid()) {
return i18n("invalid");
}
switch (uid.validity()) {
case UserID::Unknown:
return i18nc("unknown trust level", "unknown");
case UserID::Undefined:
return i18nc("undefined trust", "undefined");
case UserID::Never:
return i18n("untrusted");
case UserID::Marginal:
return i18nc("marginal trust", "marginal");
case UserID::Full:
return i18nc("full trust", "full");
case UserID::Ultimate:
return i18nc("ultimate trust", "ultimate");
}
return QString();
}
QString Formatting::validityShort(const UserID::Signature &sig)
{
switch (sig.status()) {
case UserID::Signature::NoError:
if (!sig.isInvalid()) {
/* See RFC 4880 Section 5.2.1 */
switch (sig.certClass()) {
case 0x10: /* Generic */
case 0x11: /* Persona */
case 0x12: /* Casual */
case 0x13: /* Positive */
return i18n("valid");
case 0x30:
return i18n("revoked");
default:
return i18n("class %1", sig.certClass());
}
}
[[fallthrough]];
// fall through:
case UserID::Signature::GeneralError:
return i18n("invalid");
case UserID::Signature::SigExpired:
return i18n("expired");
case UserID::Signature::KeyExpired:
return i18n("certificate expired");
case UserID::Signature::BadSignature:
return i18nc("fake/invalid signature", "bad");
case UserID::Signature::NoPublicKey: {
/* GnuPG returns the same error for no public key as for expired
* or revoked certificates. */
const auto key = KeyCache::instance()->findByKeyIDOrFingerprint(sig.signerKeyID());
if (key.isNull()) {
return i18n("no public key");
} else if (key.isExpired()) {
return i18n("key expired");
} else if (key.isRevoked()) {
return i18n("key revoked");
} else if (key.isDisabled()) {
return i18n("key disabled");
}
/* can't happen */
return QStringLiteral("unknown");
}
}
return QString();
}
QIcon Formatting::validityIcon(const UserID::Signature &sig)
{
switch (sig.status()) {
case UserID::Signature::NoError:
if (!sig.isInvalid()) {
/* See RFC 4880 Section 5.2.1 */
switch (sig.certClass()) {
case 0x10: /* Generic */
case 0x11: /* Persona */
case 0x12: /* Casual */
case 0x13: /* Positive */
return Formatting::successIcon();
case 0x30:
return Formatting::errorIcon();
default:
return QIcon();
}
}
[[fallthrough]];
// fall through:
case UserID::Signature::BadSignature:
case UserID::Signature::GeneralError:
return Formatting::errorIcon();
case UserID::Signature::SigExpired:
case UserID::Signature::KeyExpired:
return Formatting::infoIcon();
case UserID::Signature::NoPublicKey:
return Formatting::questionIcon();
}
return QIcon();
}
QString Formatting::formatKeyLink(const Key &key)
{
if (key.isNull()) {
return QString();
}
return QStringLiteral("<a href=\"key:%1\">%2</a>").arg(QLatin1StringView(key.primaryFingerprint()), Formatting::prettyName(key));
}
QString Formatting::formatForComboBox(const GpgME::Key &key)
{
const QString name = prettyName(key);
QString mail = prettyEMail(key);
if (!mail.isEmpty()) {
mail = QLatin1Char('<') + mail + QLatin1Char('>');
}
return i18nc("name, email, key id", "%1 %2 (%3)", name, mail, QLatin1StringView(key.shortKeyID())).simplified();
}
QString Formatting::nameAndEmailForSummaryLine(const UserID &id)
{
Q_ASSERT(!id.isNull());
const QString email = Formatting::prettyEMail(id);
const QString name = Formatting::prettyName(id);
if (name.isEmpty()) {
return email;
} else if (email.isEmpty()) {
return name;
} else {
return QStringLiteral("%1 <%2>").arg(name, email);
}
}
QString Formatting::nameAndEmailForSummaryLine(const Key &key)
{
Q_ASSERT(!key.isNull());
const QString email = Formatting::prettyEMail(key);
const QString name = Formatting::prettyName(key);
if (name.isEmpty()) {
return email;
} else if (email.isEmpty()) {
return name;
} else {
return QStringLiteral("%1 <%2>").arg(name, email);
}
}
const char *Formatting::summaryToString(const Signature::Summary summary)
{
if (summary & Signature::Red) {
return "RED";
}
if (summary & Signature::Green) {
return "GREEN";
}
return "YELLOW";
}
QString Formatting::signatureToString(const Signature &sig, const Key &key)
{
if (sig.isNull()) {
return QString();
}
const bool red = (sig.summary() & Signature::Red);
const bool valid = (sig.summary() & Signature::Valid);
if (red) {
if (key.isNull()) {
if (const char *fpr = sig.fingerprint()) {
return i18n("Bad signature by unknown certificate %1: %2", QString::fromLatin1(fpr), Formatting::errorAsString(sig.status()));
} else {
return i18n("Bad signature by an unknown certificate: %1", Formatting::errorAsString(sig.status()));
}
} else {
return i18n("Bad signature by %1: %2", nameAndEmailForSummaryLine(key), Formatting::errorAsString(sig.status()));
}
} else if (valid) {
if (key.isNull()) {
if (const char *fpr = sig.fingerprint()) {
return i18n("Good signature by unknown certificate %1.", QString::fromLatin1(fpr));
} else {
return i18n("Good signature by an unknown certificate.");
}
} else {
return i18n("Good signature by %1.", nameAndEmailForSummaryLine(key));
}
} else if (key.isNull()) {
if (const char *fpr = sig.fingerprint()) {
return i18n("Invalid signature by unknown certificate %1: %2", QString::fromLatin1(fpr), Formatting::errorAsString(sig.status()));
} else {
return i18n("Invalid signature by an unknown certificate: %1", Formatting::errorAsString(sig.status()));
}
} else {
return i18n("Invalid signature by %1: %2", nameAndEmailForSummaryLine(key), Formatting::errorAsString(sig.status()));
}
}
//
// ImportResult
//
QString Formatting::importMetaData(const Import &import, const QStringList &ids)
{
const QString result = importMetaData(import);
if (result.isEmpty()) {
return QString();
} else {
return result + QLatin1Char('\n') + i18n("This certificate was imported from the following sources:") + QLatin1Char('\n') + ids.join(QLatin1Char('\n'));
}
}
QString Formatting::importMetaData(const Import &import)
{
if (import.isNull()) {
return QString();
}
if (import.error().isCanceled()) {
return i18n("The import of this certificate was canceled.");
}
if (import.error()) {
return i18n("An error occurred importing this certificate: %1", Formatting::errorAsString(import.error()));
}
const unsigned int status = import.status();
if (status & Import::NewKey) {
return (status & Import::ContainedSecretKey) ? i18n("This certificate was new to your keystore. The secret key is available.")
: i18n("This certificate is new to your keystore.");
}
QStringList results;
if (status & Import::NewUserIDs) {
results.push_back(i18n("New user-ids were added to this certificate by the import."));
}
if (status & Import::NewSignatures) {
results.push_back(i18n("New signatures were added to this certificate by the import."));
}
if (status & Import::NewSubkeys) {
results.push_back(i18n("New subkeys were added to this certificate by the import."));
}
return results.empty() ? i18n("The import contained no new data for this certificate. It is unchanged.") : results.join(QLatin1Char('\n'));
}
//
// Overview in CertificateDetailsDialog
//
QString Formatting::formatOverview(const Key &key)
{
return toolTip(key, AllOptions);
}
QString Formatting::usageString(const Subkey &sub)
{
QStringList usageStrings;
if (sub.canCertify()) {
usageStrings << i18n("Certify");
}
if (sub.canSign()) {
usageStrings << i18n("Sign");
}
if (sub.canEncrypt()) {
usageStrings << i18n("Encrypt");
}
if (sub.canAuthenticate()) {
usageStrings << i18n("Authenticate");
}
if (sub.canRenc()) {
usageStrings << i18nc("Means 'Additional Decryption Subkey'; Don't try translating that, though.", "ADSK");
}
return usageStrings.join(QLatin1StringView(", "));
}
QString Formatting::summaryLine(const UserID &id)
{
return i18nc("name <email> (validity, protocol, creation date)",
"%1 (%2, %3, created: %4)",
nameAndEmailForSummaryLine(id),
Formatting::complianceStringShort(id),
displayName(id.parent().protocol()),
Formatting::creationDateString(id.parent()));
}
QString Formatting::summaryLine(const Key &key)
{
return nameAndEmailForSummaryLine(key) + QLatin1Char(' ')
+ i18nc("(validity, protocol, creation date)",
"(%1, %2, created: %3)",
Formatting::complianceStringShort(key),
displayName(key.protocol()),
Formatting::creationDateString(key));
}
QString Formatting::summaryLine(const KeyGroup &group)
{
switch (group.source()) {
case KeyGroup::ApplicationConfig:
case KeyGroup::GnuPGConfig:
return i18ncp("name of group of keys (n key(s), validity)",
"%2 (1 key, %3)",
"%2 (%1 keys, %3)",
group.keys().size(),
group.name(),
Formatting::complianceStringShort(group));
case KeyGroup::Tags:
return i18ncp("name of group of keys (n key(s), validity, tag)",
"%2 (1 key, %3, tag)",
"%2 (%1 keys, %3, tag)",
group.keys().size(),
group.name(),
Formatting::complianceStringShort(group));
default:
return i18ncp("name of group of keys (n key(s), validity, group ...)",
"%2 (1 key, %3, unknown origin)",
"%2 (%1 keys, %3, unknown origin)",
group.keys().size(),
group.name(),
Formatting::complianceStringShort(group));
}
}
// Icon for certificate selection indication
QIcon Formatting::iconForUid(const UserID &uid)
{
if (Kleo::isRevokedOrExpired(uid)) {
return Formatting::errorIcon();
}
return iconForValidity(uid);
}
QString Formatting::validity(const UserID &uid)
{
switch (uid.validity()) {
case UserID::Ultimate:
return i18n("The certificate is marked as your own.");
case UserID::Full:
return i18n("The certificate belongs to this recipient.");
case UserID::Marginal:
return i18n("The trust model indicates marginally that the certificate belongs to this recipient.");
case UserID::Never:
return i18n("This certificate should not be used.");
case UserID::Undefined:
case UserID::Unknown:
default:
return i18n("There is no indication that this certificate belongs to this recipient.");
}
}
QString Formatting::validity(const KeyGroup &group)
{
if (group.isNull()) {
return QString();
}
const KeyGroup::Keys &keys = group.keys();
if (keys.size() == 0) {
return i18n("This group does not contain any keys.");
}
return getValidityStatement(keys);
}
namespace
{
template<typename Container>
UserID::Validity minimalValidity(const Container &keys)
{
const int minValidity = std::accumulate(keys.cbegin(), keys.cend(), UserID::Ultimate + 1, [](int validity, const Key &key) {
return std::min<int>(validity, minimalValidityOfNotRevokedUserIDs(key));
});
return minValidity <= UserID::Ultimate ? static_cast<UserID::Validity>(minValidity) : UserID::Unknown;
}
template<typename Container>
bool allKeysAreCompliant(const Container &keys)
{
if (!DeVSCompliance::isActive()) {
return true;
}
if (!DeVSCompliance::isCompliant()) {
return false;
}
return Kleo::all_of(keys, DeVSCompliance::keyIsCompliant);
}
}
QIcon Formatting::validityIcon(const KeyGroup &group)
{
if (Kleo::any_of(group.keys(), std::mem_fn(&Key::isBad))) {
return Formatting::errorIcon();
}
return iconForValidityAndCompliance(minimalValidity(group.keys()), allKeysAreCompliant(group.keys()));
}
bool Formatting::uidsHaveFullValidity(const Key &key)
{
return allUserIDsHaveFullValidity(key);
}
QString Formatting::complianceMode()
{
const auto complianceValue = getCryptoConfigStringValue("gpg", "compliance");
return complianceValue == QLatin1StringView("gnupg") ? QString() : complianceValue;
}
bool Formatting::isKeyDeVs(const GpgME::Key &key)
{
return DeVSCompliance::allSubkeysAreCompliant(key);
}
QString Formatting::complianceStringForKey(const GpgME::Key &key)
{
// There will likely be more in the future for other institutions
// for now we only have DE-VS
if (DeVSCompliance::isCompliant()) {
return isRemoteKey(key) //
? i18nc("@info the compliance of the key with certain requirements is unknown", "unknown")
: DeVSCompliance::name(DeVSCompliance::keyIsCompliant(key));
}
return QString();
}
+QString Formatting::complianceStringForUserID(const GpgME::UserID &userID)
+{
+ // There will likely be more in the future for other institutions
+ // for now we only have DE-VS
+ if (DeVSCompliance::isCompliant()) {
+ return isRemoteKey(userID.parent()) //
+ ? i18nc("@info the compliance of the key with certain requirements is unknown", "unknown")
+ : DeVSCompliance::name(DeVSCompliance::userIDIsCompliant(userID));
+ }
+ return QString();
+}
+
QString Formatting::complianceStringShort(const GpgME::UserID &id)
{
if (DeVSCompliance::isCompliant() && DeVSCompliance::userIDIsCompliant(id)) {
return QStringLiteral("★ ") + DeVSCompliance::name(true);
}
const bool keyValidityChecked = (id.parent().keyListMode() & GpgME::Validate);
if (keyValidityChecked && id.validity() >= UserID::Full) {
return i18nc("As in 'this user ID is valid.'", "certified");
}
if (id.parent().isExpired() || isExpired(id)) {
return i18n("expired");
}
if (id.parent().isRevoked() || id.isRevoked()) {
return i18n("revoked");
}
if (id.parent().isDisabled()) {
return i18n("disabled");
}
if (id.parent().isInvalid() || id.isInvalid()) {
return i18n("invalid");
}
if (keyValidityChecked) {
return i18nc("As in 'this user ID is not certified'", "not certified");
}
return i18nc("The validity of this user ID has not been/could not be checked", "not checked");
}
QString Formatting::complianceStringShort(const GpgME::Key &key)
{
if (DeVSCompliance::isCompliant() && DeVSCompliance::keyIsCompliant(key)) {
return QStringLiteral("★ ") + DeVSCompliance::name(true);
}
const bool keyValidityChecked = (key.keyListMode() & GpgME::Validate);
if (keyValidityChecked && Kleo::allUserIDsHaveFullValidity(key)) {
return i18nc("As in all user IDs are valid.", "certified");
}
if (key.isExpired()) {
return i18n("expired");
}
if (key.isRevoked()) {
return i18n("revoked");
}
if (key.isDisabled()) {
return i18n("disabled");
}
if (key.isInvalid()) {
return i18n("invalid");
}
if (keyValidityChecked) {
return i18nc("As in not all user IDs are valid.", "not certified");
}
return i18nc("The validity of the user IDs has not been/could not be checked", "not checked");
}
QString Formatting::complianceStringShort(const KeyGroup &group)
{
const KeyGroup::Keys &keys = group.keys();
const bool allKeysFullyValid = std::all_of(keys.cbegin(), keys.cend(), &Kleo::allUserIDsHaveFullValidity);
if (allKeysFullyValid) {
return i18nc("As in all keys are valid.", "all certified");
}
return i18nc("As in not all keys are valid.", "not all certified");
}
QString Formatting::prettyID(const char *id)
{
if (!id) {
return QString();
}
QString ret = QString::fromLatin1(id).toUpper().replace(QRegularExpression(QStringLiteral("(....)")), QStringLiteral("\\1 ")).trimmed();
// For the standard 10 group fingerprint let us use a double space in the
// middle to increase readability
if (ret.size() == 49) {
ret.insert(24, QLatin1Char(' '));
}
return ret;
}
QString Formatting::accessibleHexID(const char *id)
{
static const QRegularExpression groupOfFourRegExp{QStringLiteral("(?:(.)(.)(.)(.))")};
QString ret;
ret = QString::fromLatin1(id);
if (!ret.isEmpty() && (ret.size() % 4 == 0)) {
ret = ret.replace(groupOfFourRegExp, QStringLiteral("\\1 \\2 \\3 \\4, ")).chopped(2);
}
return ret;
}
QString Formatting::origin(int o)
{
switch (o) {
case Key::OriginKS:
return i18n("Keyserver");
case Key::OriginDane:
return QStringLiteral("DANE");
case Key::OriginWKD:
return QStringLiteral("WKD");
case Key::OriginURL:
return QStringLiteral("URL");
case Key::OriginFile:
return i18n("File import");
case Key::OriginSelf:
return i18n("Generated");
case Key::OriginOther:
case Key::OriginUnknown:
default:
return i18n("Unknown");
}
}
QString Formatting::deVsString(bool compliant)
{
return DeVSCompliance::name(compliant);
}
namespace
{
QString formatTrustScope(const char *trustScope)
{
static const QRegularExpression escapedNonAlphaNum{QStringLiteral(R"(\\([^0-9A-Za-z]))")};
const auto scopeRegExp = QString::fromUtf8(trustScope);
if (scopeRegExp.startsWith(u"<[^>]+[@.]") && scopeRegExp.endsWith(u">$")) {
// looks like a trust scope regular expression created by gpg
auto domain = scopeRegExp.mid(10, scopeRegExp.size() - 10 - 2);
domain.replace(escapedNonAlphaNum, QStringLiteral(R"(\1)"));
return domain;
}
return scopeRegExp;
}
}
QString Formatting::trustSignatureDomain(const GpgME::UserID::Signature &sig)
{
return formatTrustScope(sig.trustScope());
}
QString Formatting::trustSignature(const GpgME::UserID::Signature &sig)
{
switch (sig.trustValue()) {
case TrustSignatureTrust::Partial:
return i18nc("Certifies this key as partially trusted introducer for 'domain name'.",
"Certifies this key as partially trusted introducer for '%1'.",
trustSignatureDomain(sig));
case TrustSignatureTrust::Complete:
return i18nc("Certifies this key as fully trusted introducer for 'domain name'.",
"Certifies this key as fully trusted introducer for '%1'.",
trustSignatureDomain(sig));
default:
return {};
}
}
QString Formatting::errorAsString(const GpgME::Error &error)
{
#ifdef Q_OS_WIN
// On Windows, we set GpgME resp. libgpg-error to return (translated) error messages as UTF-8
const char *s = error.asString();
qCDebug(LIBKLEO_LOG) << __func__ << "gettext_use_utf8(-1) returns" << gettext_use_utf8(-1);
qCDebug(LIBKLEO_LOG) << __func__ << "error:" << s;
qCDebug(LIBKLEO_LOG) << __func__ << "error (percent-encoded):" << QByteArray{s}.toPercentEncoding();
return QString::fromUtf8(s);
#else
return QString::fromLocal8Bit(error.asString());
#endif
}
QString Formatting::prettyAlgorithmName(const std::string &algorithm)
{
static const std::map<std::string, QString> displayNames = {
{"brainpoolP256r1", i18nc("@info", "ECC (Brainpool P-256)")},
{"brainpoolP384r1", i18nc("@info", "ECC (Brainpool P-384)")},
{"brainpoolP512r1", i18nc("@info", "ECC (Brainpool P-512)")},
{"curve25519", i18nc("@info", "ECC (Curve25519)")},
{"curve448", i18nc("@info", "ECC (Curve448)")},
{"ed25519", i18nc("@info", "ECC (Ed25519)")},
{"ed448", i18nc("@info", "ECC (Ed448)")},
{"cv25519", i18nc("@info", "ECC (Cv25519)")},
{"cv448", i18nc("@info", "ECC (Cv448)")},
{"nistp256", i18nc("@info", "ECC (NIST P-256)")},
{"nistp384", i18nc("@info", "ECC (NIST P-384)")},
{"nistp521", i18nc("@info", "ECC (NIST P-521)")},
{"rsa2048", i18nc("@info", "RSA 2048")},
{"rsa3072", i18nc("@info", "RSA 3072")},
{"rsa4096", i18nc("@info", "RSA 4096")},
{"dsa1024", i18nc("@info", "DSA 1024")},
{"dsa2048", i18nc("@info", "DSA 2048")},
{"elg1024", i18nc("@info", "Elgamal 1024")},
{"elg2048", i18nc("@info", "Elgamal 2048")},
{"elg3072", i18nc("@info", "Elgamal 3072")},
{"elg4096", i18nc("@info", "Elgamal 4096")},
};
const auto it = displayNames.find(algorithm);
return (it != displayNames.end()) ? it->second : i18nc("@info", "Unknown algorithm");
}
diff --git a/src/utils/formatting.h b/src/utils/formatting.h
index e1299c28..29e2aab7 100644
--- a/src/utils/formatting.h
+++ b/src/utils/formatting.h
@@ -1,232 +1,235 @@
/* -*- mode: c++; c-basic-offset:4 -*-
utils/formatting.h
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB
SPDX-FileCopyrightText: 2021, 2022 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "keyusage.h"
#include "kleo_export.h"
#include <QStringList>
#include <gpgme++/key.h>
class QString;
class QDate;
class QIcon;
namespace GpgME
{
class Error;
class Import;
}
namespace Kleo
{
class KeyGroup;
namespace Formatting
{
class KLEO_EXPORT IconProvider
{
public:
inline explicit IconProvider(KeyUsage::Flags requiredUsages)
: usage{requiredUsages}
{
}
QIcon icon(const GpgME::Key &key) const;
QIcon icon(const KeyGroup &group) const;
+ QIcon icon(const GpgME::UserID &userID) const;
private:
KeyUsage usage;
};
KLEO_EXPORT QIcon successIcon();
KLEO_EXPORT QIcon infoIcon();
KLEO_EXPORT QIcon questionIcon();
KLEO_EXPORT QIcon unavailableIcon();
KLEO_EXPORT QIcon warningIcon();
KLEO_EXPORT QIcon errorIcon();
KLEO_EXPORT QString prettyNameAndEMail(int proto, const char *id, const char *name, const char *email, const char *comment = nullptr);
KLEO_EXPORT QString prettyNameAndEMail(int proto, const QString &id, const QString &name, const QString &email, const QString &comment = {});
KLEO_EXPORT QString prettyNameAndEMail(const GpgME::Key &key);
KLEO_EXPORT QString prettyNameAndEMail(const GpgME::UserID &key);
KLEO_EXPORT QString prettyUserID(const GpgME::UserID &uid);
KLEO_EXPORT QString prettyKeyID(const char *id);
KLEO_EXPORT QString prettyName(int proto, const char *id, const char *name, const char *comment = nullptr);
KLEO_EXPORT QString prettyName(const GpgME::Key &key);
KLEO_EXPORT QString prettyName(const GpgME::UserID &uid);
KLEO_EXPORT QString prettyName(const GpgME::UserID::Signature &sig);
KLEO_EXPORT QString prettyEMail(const char *email, const char *id);
KLEO_EXPORT QString prettyEMail(const GpgME::Key &key);
KLEO_EXPORT QString prettyEMail(const GpgME::UserID &uid);
KLEO_EXPORT QString prettyEMail(const GpgME::UserID::Signature &sig);
/* Formats a fingerprint or keyid into groups of four */
KLEO_EXPORT QString prettyID(const char *id);
KLEO_EXPORT QString accessibleHexID(const char *id);
// clang-format off
enum ToolTipOption {
KeyID = 0x001,
Validity = 0x002,
StorageLocation = 0x004,
SerialNumber = 0x008,
Issuer = 0x010,
Subject = 0x020,
ExpiryDates = 0x040,
CertificateType = 0x080,
CertificateUsage = 0x100,
Fingerprint = 0x200,
UserIDs = 0x400,
OwnerTrust = 0x800,
Subkeys = 0x1000,
AllOptions = 0xffff
};
// clang-format on
KLEO_EXPORT QString toolTip(const GpgME::Key &key, int opts);
KLEO_EXPORT QString toolTip(const Kleo::KeyGroup &group, int opts);
+KLEO_EXPORT QString toolTip(const GpgME::UserID &userID, int opts);
/// Returns expiration date of @p key as string, or @p noExpiration if the key doesn't expire.
KLEO_EXPORT QString expirationDateString(const GpgME::Key &key, const QString &noExpiration = {});
/// Returns expiration date of @p subkey as string, or @p noExpiration if the subkey doesn't expire.
KLEO_EXPORT QString expirationDateString(const GpgME::Subkey &subkey, const QString &noExpiration = {});
/// Returns expiration date of @p sig as string, or @p noExpiration if the signature doesn't expire.
KLEO_EXPORT QString expirationDateString(const GpgME::UserID::Signature &sig, const QString &noExpiration = {});
KLEO_EXPORT QDate expirationDate(const GpgME::Key &key);
KLEO_EXPORT QDate expirationDate(const GpgME::Subkey &subkey);
KLEO_EXPORT QDate expirationDate(const GpgME::UserID::Signature &sig);
/**
* Returns expiration date of @p key as string suitable for screen readers.
* If the key doesn't expire, then it returns @p noExpiration if @p noExpiration is not empty. Otherwise,
* returns the localization of "unlimited".
*/
KLEO_EXPORT QString accessibleExpirationDate(const GpgME::Key &key, const QString &noExpiration = {});
/**
* Returns expiration date of @p subkey as string suitable for screen readers.
* If the subkey doesn't expire, then it returns @p noExpiration if @p noExpiration is not empty. Otherwise,
* returns the localization of "unlimited".
*/
KLEO_EXPORT QString accessibleExpirationDate(const GpgME::Subkey &subkey, const QString &noExpiration = {});
/**
* Returns expiration date of @p sig as string suitable for screen readers.
* If the signature doesn't expire, then it returns @p noExpiration if @p noExpiration is not empty. Otherwise,
* returns the localization of "unlimited".
*/
KLEO_EXPORT QString accessibleExpirationDate(const GpgME::UserID::Signature &sig, const QString &noExpiration = {});
KLEO_EXPORT QString creationDateString(const GpgME::Key &key);
KLEO_EXPORT QString creationDateString(const GpgME::Subkey &subkey);
KLEO_EXPORT QString creationDateString(const GpgME::UserID::Signature &sig);
KLEO_EXPORT QDate creationDate(const GpgME::Key &key);
KLEO_EXPORT QDate creationDate(const GpgME::Subkey &subkey);
KLEO_EXPORT QDate creationDate(const GpgME::UserID::Signature &sig);
KLEO_EXPORT QString accessibleCreationDate(const GpgME::Key &key);
KLEO_EXPORT QString accessibleCreationDate(const GpgME::Subkey &subkey);
/* Convert a GPGME style time or a QDate to a localized string */
KLEO_EXPORT QString dateString(time_t t);
KLEO_EXPORT QString dateString(const QDate &date);
KLEO_EXPORT QString accessibleDate(time_t t);
KLEO_EXPORT QString accessibleDate(const QDate &date);
KLEO_EXPORT QString displayName(GpgME::Protocol prot);
KLEO_EXPORT QString type(const GpgME::Key &key);
KLEO_EXPORT QString type(const GpgME::Subkey &subkey);
KLEO_EXPORT QString type(const Kleo::KeyGroup &group);
KLEO_EXPORT QString ownerTrustShort(const GpgME::Key &key);
KLEO_EXPORT QString ownerTrustShort(GpgME::Key::OwnerTrust trust);
KLEO_EXPORT QString validityShort(const GpgME::Subkey &subkey);
KLEO_EXPORT QString validityShort(const GpgME::UserID &uid);
KLEO_EXPORT QString validityShort(const GpgME::UserID::Signature &sig);
KLEO_EXPORT QIcon validityIcon(const GpgME::UserID::Signature &sig);
/* A sentence about the validity of the UserID */
KLEO_EXPORT QString validity(const GpgME::UserID &uid);
KLEO_EXPORT QString validity(const Kleo::KeyGroup &group);
KLEO_EXPORT QIcon validityIcon(const Kleo::KeyGroup &group);
KLEO_EXPORT QString formatForComboBox(const GpgME::Key &key);
KLEO_EXPORT QString formatKeyLink(const GpgME::Key &key);
KLEO_EXPORT QString signatureToString(const GpgME::Signature &sig, const GpgME::Key &key);
KLEO_EXPORT const char *summaryToString(const GpgME::Signature::Summary summary);
KLEO_EXPORT QString importMetaData(const GpgME::Import &import);
KLEO_EXPORT QString importMetaData(const GpgME::Import &import, const QStringList &sources);
KLEO_EXPORT QString formatOverview(const GpgME::Key &key);
KLEO_EXPORT QString usageString(const GpgME::Subkey &subkey);
KLEO_EXPORT QString summaryLine(const GpgME::UserID &id);
KLEO_EXPORT QString summaryLine(const GpgME::Key &key);
KLEO_EXPORT QString summaryLine(const KeyGroup &group);
KLEO_EXPORT QString nameAndEmailForSummaryLine(const GpgME::Key &key);
KLEO_EXPORT QString nameAndEmailForSummaryLine(const GpgME::UserID &id);
KLEO_EXPORT QIcon iconForUid(const GpgME::UserID &uid);
/* Is the key valid i.e. are all uids fully trusted? */
KLEO_EXPORT bool uidsHaveFullValidity(const GpgME::Key &key);
/* The compliance mode of the gnupg system. Empty if compliance
* mode is not set.
* Use Kleo::gnupgComplianceMode() instead.
*/
KLEO_DEPRECATED_EXPORT QString complianceMode();
/* Is the given key in compliance with CO_DE_VS? */
KLEO_EXPORT bool isKeyDeVs(const GpgME::Key &key);
/**
* Use Kleo::DeVSCompliance::name(bool) instead.
*/
KLEO_DEPRECATED_EXPORT QString deVsString(bool compliant = true);
/* A sentence if the key confirms to the current compliance mode */
KLEO_EXPORT QString complianceStringForKey(const GpgME::Key &key);
+KLEO_EXPORT QString complianceStringForUserID(const GpgME::UserID &userID);
/* A single word for use in keylists to describe the validity of the
* given key, including any conformance statements relevant to the
* current conformance mode. */
KLEO_EXPORT QString complianceStringShort(const GpgME::Key &key);
KLEO_EXPORT QString complianceStringShort(const GpgME::UserID &id);
KLEO_EXPORT QString complianceStringShort(const Kleo::KeyGroup &group);
/* The origin of the key mapped to a localized string */
KLEO_EXPORT QString origin(int o);
/* Human-readable trust signature scope (for trust signature regexp created by GnuPG) */
KLEO_EXPORT QString trustSignatureDomain(const GpgME::UserID::Signature &sig);
/* Summary of trust signature properties */
KLEO_EXPORT QString trustSignature(const GpgME::UserID::Signature &sig);
/**
* Returns the value of Error::asString() for the error \p error as Unicode string.
*/
KLEO_EXPORT QString errorAsString(const GpgME::Error &error);
/**
* Returns a name suitable for being displayed for the GPG algorithm name @p algorithm.
*/
KLEO_EXPORT QString prettyAlgorithmName(const std::string &algorithm);
}
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Fri, Dec 5, 5:00 AM (1 h, 9 m)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
f2/4d/b169d2d51372991b950038841444
Attached To
rLIBKLEO Libkleo
Event Timeline
Log In to Comment