Page MenuHome GnuPG

No OneTemporary

diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 2a34829d..f6e2ccd5 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -1,418 +1,421 @@
# SPDX-License-Identifier: CC0-1.0
# SPDX-FileCopyrightText: none
# target_include_directories does not handle empty include paths
include_directories(
${Boost_INCLUDE_DIRS}
${GPGME_INCLUDES}
)
add_definitions(-DTRANSLATION_DOMAIN=\"libkleopatra\")
#add_definitions( -DQT_NO_CAST_FROM_ASCII )
#add_definitions( -DQT_NO_CAST_TO_ASCII )
kde_enable_exceptions()
add_definitions( -DGPGMEPP_ERR_SOURCE_DEFAULT=13 ) # 13 is GPG_ERR_SOURCE_KLEO, even if gpg-error's too old to know about
add_subdirectory( pics )
add_library(KPim${KF_MAJOR_VERSION}Libkleo)
add_library(KPim${KF_MAJOR_VERSION}::Libkleo ALIAS KPim${KF_MAJOR_VERSION}Libkleo)
########### next target ###############
target_sources(KPim${KF_MAJOR_VERSION}Libkleo PRIVATE
kleo/auditlogentry.cpp
kleo/auditlogentry.h
kleo/checksumdefinition.cpp
kleo/checksumdefinition.h
kleo/debug.cpp
kleo/debug.h
kleo/defaultkeyfilter.cpp
kleo/defaultkeyfilter.h
kleo/defaultkeygenerationjob.cpp
kleo/defaultkeygenerationjob.h
kleo/docaction.cpp kleo/docaction.h
kleo/dn.cpp
kleo/dn.h
kleo/enum.cpp
kleo/enum.h
kleo/expirychecker.cpp
kleo/expirychecker.h
kleo/expirycheckerconfig.cpp
kleo/expirycheckerconfig.h
kleo/expirycheckersettings.cpp
kleo/expirycheckersettings.h
kleo/kconfigbasedkeyfilter.cpp
kleo/kconfigbasedkeyfilter.h
kleo/keyfilter.h
kleo/keyfiltermanager.cpp
kleo/keyfiltermanager.h
kleo/keygroup.cpp
kleo/keygroup.h
kleo/keygroupconfig.cpp
kleo/keygroupconfig.h
kleo/keygroupimportexport.cpp
kleo/keygroupimportexport.h
kleo/keyresolver.cpp
kleo/keyresolver.h
kleo/keyresolvercore.cpp
kleo/keyresolvercore.h
kleo/keyserverconfig.cpp
kleo/keyserverconfig.h
kleo/kleoexception.cpp
kleo/kleoexception.h
kleo/oidmap.cpp
kleo/oidmap.h
kleo/predicates.h
kleo/stl_util.h
models/keycache.cpp
models/keycache.h
models/keycache_p.h
models/keylist.h
models/keylistmodel.cpp
models/keylistmodel.h
models/keylistmodelinterface.cpp
models/keylistmodelinterface.h
models/keylistsortfilterproxymodel.cpp
models/keylistsortfilterproxymodel.h
models/keyrearrangecolumnsproxymodel.cpp
models/keyrearrangecolumnsproxymodel.h
models/subkeylistmodel.cpp
models/subkeylistmodel.h
models/useridlistmodel.cpp
models/useridlistmodel.h
models/useridproxymodel.cpp
models/useridproxymodel.h
utils/algorithm.h
utils/assuan.cpp
utils/assuan.h
utils/chrono.h
utils/classify.cpp
utils/classify.h
utils/compat.cpp
utils/compat.h
utils/compliance.cpp
utils/compliance.h
utils/cryptoconfig.cpp
utils/cryptoconfig.h
utils/cryptoconfig_p.h
utils/filesystemwatcher.cpp
utils/filesystemwatcher.h
utils/formatting.cpp
utils/formatting.h
utils/gnupg-registry.c
utils/gnupg-registry.h
utils/gnupg.cpp
utils/gnupg.h
utils/hex.cpp
utils/hex.h
utils/keyhelpers.cpp
utils/keyhelpers.h
utils/keyusage.h
utils/qtstlhelpers.cpp
utils/qtstlhelpers.h
utils/scdaemon.cpp
utils/scdaemon.h
utils/stringutils.cpp
utils/stringutils.h
utils/systeminfo.cpp
utils/systeminfo.h
utils/test.cpp
utils/test.h
utils/uniquelock.cpp
utils/uniquelock.h
)
ecm_qt_declare_logging_category(KPim${KF_MAJOR_VERSION}Libkleo HEADER libkleo_debug.h IDENTIFIER LIBKLEO_LOG CATEGORY_NAME org.kde.pim.libkleo
DESCRIPTION "libkleo (kleo_core)"
EXPORT LIBKLEO
)
target_sources(KPim${KF_MAJOR_VERSION}Libkleo PRIVATE
ui/adjustingscrollarea.cpp
ui/adjustingscrollarea.h
ui/auditlogviewer.cpp
ui/auditlogviewer.h
ui/cryptoconfigentryreaderport.cpp
ui/cryptoconfigentryreaderport_p.h
ui/cryptoconfigmodule.cpp
ui/cryptoconfigmodule.h
ui/cryptoconfigmodule_p.h
ui/directoryserviceswidget.cpp
ui/directoryserviceswidget.h
ui/dnattributeorderconfigwidget.cpp
ui/dnattributeorderconfigwidget.h
ui/editdirectoryservicedialog.cpp
ui/editdirectoryservicedialog.h
ui/filenamerequester.cpp
ui/filenamerequester.h
ui/messagebox.cpp
ui/messagebox.h
ui/treeview.cpp
ui/treeview.h
ui/treewidget.cpp
ui/treewidget.h
ui/progressbar.cpp
ui/progressbar.h
ui/progressdialog.cpp
ui/progressdialog.h
ui/readerportselection.cpp
ui/readerportselection.h
)
ecm_qt_declare_logging_category(KPim${KF_MAJOR_VERSION}Libkleo HEADER kleo_ui_debug.h IDENTIFIER KLEO_UI_LOG CATEGORY_NAME org.kde.pim.kleo_ui
DESCRIPTION "libkleo (kleo_ui)"
OLD_CATEGORY_NAMES log_kleo_ui
EXPORT LIBKLEO
)
target_sources(KPim${KF_MAJOR_VERSION}Libkleo PRIVATE # make this a separate lib.
ui/keyapprovaldialog.cpp
ui/keyapprovaldialog.h
ui/keylistview.cpp
ui/keylistview.h
ui/keyrequester.cpp
ui/keyrequester.h
ui/keyselectioncombo.cpp
ui/keyselectioncombo.h
ui/keyselectiondialog.cpp
ui/keyselectiondialog.h
ui/newkeyapprovaldialog.cpp
ui/newkeyapprovaldialog.h
+ ui/useridselectioncombo.cpp
+ ui/useridselectioncombo.h
)
if(MINGW)
# we do not care about different signedness of passed pointer arguments
set_source_files_properties(utils/gnupg-registry.c PROPERTIES COMPILE_OPTIONS "-Wno-pointer-sign")
endif()
target_link_libraries(KPim${KF_MAJOR_VERSION}Libkleo PUBLIC Gpgmepp PRIVATE Qt::Widgets
KF${KF_MAJOR_VERSION}::I18n
KF${KF_MAJOR_VERSION}::Completion
KF${KF_MAJOR_VERSION}::ConfigCore
KF${KF_MAJOR_VERSION}::ConfigWidgets
KF${KF_MAJOR_VERSION}::CoreAddons
KF${KF_MAJOR_VERSION}::WidgetsAddons
KF${KF_MAJOR_VERSION}::ItemModels
KF${KF_MAJOR_VERSION}::Codecs
LibGpgError::LibGpgError)
if (QT_MAJOR_VERSION STREQUAL "6")
target_link_libraries(KPim${KF_MAJOR_VERSION}Libkleo PRIVATE Qt6::Core5Compat PUBLIC QGpgmeQt6)
else()
target_link_libraries(KPim${KF_MAJOR_VERSION}Libkleo PUBLIC QGpgme)
endif()
# Boost::headers may not be available for old versions of Boost
if (TARGET Boost::headers)
target_link_libraries(KPim${KF_MAJOR_VERSION}Libkleo PRIVATE Boost::headers)
endif()
if (KPim${KF_MAJOR_VERSION}TextEdit_FOUND)
add_definitions(-DHAVE_PIMTEXTEDIT)
target_link_libraries(KPim${KF_MAJOR_VERSION}Libkleo PRIVATE KPim${KF_MAJOR_VERSION}::PimTextEdit)
endif()
if (COMPILE_WITH_UNITY_CMAKE_SUPPORT)
set_target_properties(KPim${KF_MAJOR_VERSION}Libkleo PROPERTIES UNITY_BUILD ON)
endif()
ecm_generate_export_header(KPim${KF_MAJOR_VERSION}Libkleo
BASE_NAME kleo
VERSION ${PIM_VERSION}
DEPRECATED_BASE_VERSION 0
DEPRECATION_VERSIONS 5.23
)
if(WIN32)
target_link_libraries(KPim${KF_MAJOR_VERSION}Libkleo ${GPGME_VANILLA_LIBRARIES} )
endif()
set_target_properties(KPim${KF_MAJOR_VERSION}Libkleo PROPERTIES
VERSION ${LIBKLEO_VERSION}
SOVERSION ${LIBKLEO_SOVERSION}
EXPORT_NAME Libkleo
)
install(TARGETS
KPim${KF_MAJOR_VERSION}Libkleo
EXPORT KPim${KF_MAJOR_VERSION}LibkleoTargets ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}
)
target_include_directories(KPim${KF_MAJOR_VERSION}Libkleo INTERFACE "$<INSTALL_INTERFACE:${KDE_INSTALL_INCLUDEDIR}/KPim${KF_MAJOR_VERSION}/Libkleo>")
target_include_directories(KPim${KF_MAJOR_VERSION}Libkleo PUBLIC "$<BUILD_INTERFACE:${libkleo_SOURCE_DIR}/src;${libkleo_BINARY_DIR}/src>")
ecm_generate_headers(libkleo_CamelCase_HEADERS
HEADER_NAMES
AuditLogEntry
ChecksumDefinition
Debug
DefaultKeyFilter
DefaultKeyGenerationJob
DocAction
Dn
Enum
ExpiryChecker
ExpiryCheckerConfig
ExpiryCheckerSettings
KConfigBasedKeyFilter
KeyFilter
KeyFilterManager
KeyGroup
KeyGroupConfig
KeyGroupImportExport
KeyResolver
KeyResolverCore
KeyserverConfig
KleoException
OidMap
Predicates
Stl_Util
REQUIRED_HEADERS libkleo_HEADERS
PREFIX Libkleo
RELATIVE kleo
)
ecm_generate_headers(libkleo_CamelCase_models_HEADERS
HEADER_NAMES
KeyCache
KeyList
KeyListModel
KeyListModelInterface
KeyListSortFilterProxyModel
KeyRearrangeColumnsProxyModel
SubkeyListModel
UserIDListModel
UserIDProxyModel
REQUIRED_HEADERS libkleo_models_HEADERS
PREFIX Libkleo
RELATIVE models
)
ecm_generate_headers(libkleo_CamelCase_utils_HEADERS
HEADER_NAMES
Algorithm
Assuan
Chrono
Classify
Compat
Compliance
CryptoConfig
FileSystemWatcher
Formatting
GnuPG
Hex
KeyHelpers
KeyUsage
QtStlHelpers
SCDaemon
StringUtils
SystemInfo
Test
UniqueLock
REQUIRED_HEADERS libkleo_utils_HEADERS
PREFIX Libkleo
RELATIVE utils
)
ecm_generate_headers(libkleo_CamelCase_ui_HEADERS
HEADER_NAMES
AdjustingScrollArea
AuditLogViewer
CryptoConfigModule
DNAttributeOrderConfigWidget
DirectoryServicesWidget
EditDirectoryServiceDialog
FileNameRequester
KeyApprovalDialog
KeyListView
KeyRequester
KeySelectionCombo
KeySelectionDialog
MessageBox
TreeView
TreeWidget
NewKeyApprovalDialog
ProgressDialog
ReaderPortSelection
+ UserIDSelectionCombo
REQUIRED_HEADERS libkleo_ui_HEADERS
PREFIX Libkleo
RELATIVE ui
)
if (QT_MAJOR_VERSION STREQUAL "6")
ecm_generate_pri_file(BASE_NAME Libkleo
LIB_NAME KPim${KF_MAJOR_VERSION}Libkleo
DEPS "QGpgme" FILENAME_VAR PRI_FILENAME INCLUDE_INSTALL_DIR ${KDE_INSTALL_INCLUDEDIR}/KPim${KF_MAJOR_VERSION}/Libkleo
)
else()
ecm_generate_pri_file(BASE_NAME Libkleo
LIB_NAME KPim${KF_MAJOR_VERSION}Libkleo
DEPS "QGpgmeQt6" FILENAME_VAR PRI_FILENAME INCLUDE_INSTALL_DIR ${KDE_INSTALL_INCLUDEDIR}/KPim${KF_MAJOR_VERSION}/Libkleo
)
endif()
kconfig_add_kcfg_files(KPim${KF_MAJOR_VERSION}Libkleo
kcfg/expirycheckerconfigbase.kcfgc
kcfg/classifyconfig.kcfgc
)
install(FILES
${libkleo_CamelCase_HEADERS}
${libkleo_CamelCase_models_HEADERS}
${libkleo_CamelCase_ui_HEADERS}
${libkleo_CamelCase_utils_HEADERS}
DESTINATION ${KDE_INSTALL_INCLUDEDIR}/KPim${KF_MAJOR_VERSION}/Libkleo/Libkleo
COMPONENT Devel
)
install(FILES
${CMAKE_CURRENT_BINARY_DIR}/expirycheckerconfigbase.h
${CMAKE_CURRENT_BINARY_DIR}/kleo_export.h
${libkleo_HEADERS}
${libkleo_models_HEADERS}
${libkleo_ui_HEADERS}
${libkleo_utils_HEADERS}
${CMAKE_CURRENT_BINARY_DIR}/classifyconfig.h
DESTINATION ${KDE_INSTALL_INCLUDEDIR}/KPim${KF_MAJOR_VERSION}/Libkleo/libkleo
COMPONENT Devel
)
install(FILES
${PRI_FILENAME}
DESTINATION ${ECM_MKSPECS_INSTALL_DIR})
if ( WIN32 )
install ( FILES libkleopatrarc-win32.desktop DESTINATION ${KDE_INSTALL_CONFDIR} RENAME libkleopatrarc )
else ()
install ( FILES libkleopatrarc.desktop DESTINATION ${KDE_INSTALL_CONFDIR} RENAME libkleopatrarc )
endif ()
if (BUILD_QCH)
ecm_add_qch(
KPim${KF_MAJOR_VERSION}Libkleo_QCH
NAME KPim${KF_MAJOR_VERSION}Libkleo
BASE_NAME KPim${KF_MAJOR_VERSION}Libkleo
VERSION ${PIM_VERSION}
ORG_DOMAIN org.kde
SOURCES # using only public headers, to cover only public API
${libkleo_HEADERS}
${libkleo_models_HEADERS}
${libkleo_ui_HEADERS}
${libkleo_utils_HEADERS}
#MD_MAINPAGE "${CMAKE_SOURCE_DIR}/README.md"
#IMAGE_DIRS "${CMAKE_SOURCE_DIR}/docs/pics"
LINK_QCHS
Qt${QT_MAJOR_VERSION}Core_QCH
Qt${QT_MAJOR_VERSION}Gui_QCH
Qt${QT_MAJOR_VERSION}Widgets_QCH
INCLUDE_DIRS
${CMAKE_CURRENT_BINARY_DIR}
BLANK_MACROS
KLEO_EXPORT
TAGFILE_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR}
QCH_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR}
COMPONENT Devel
)
endif()
diff --git a/src/models/keylistsortfilterproxymodel.cpp b/src/models/keylistsortfilterproxymodel.cpp
index a76123d2..3d894dff 100644
--- a/src/models/keylistsortfilterproxymodel.cpp
+++ b/src/models/keylistsortfilterproxymodel.cpp
@@ -1,254 +1,267 @@
/* -*- 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;
+Q_DECLARE_METATYPE(GpgME::UserID)
+
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 (userID.isNull()) {
+ for (const auto &uid : key.userIDs()) {
+ const auto id = QString::fromUtf8(uid.id());
+ if (id.contains(rx)) {
+ match = true;
+ break;
+ }
+ }
+ } else {
+ const auto id = QString::fromUtf8(userID.id());
if (id.contains(rx)) {
match = true;
- break;
}
+ }
+ if (!match) {
// 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;
- 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;
}
}
- }
- if (!match) {
- return false;
+
+ 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;
}
diff --git a/src/ui/useridselectioncombo.cpp b/src/ui/useridselectioncombo.cpp
new file mode 100644
index 00000000..7d1b377f
--- /dev/null
+++ b/src/ui/useridselectioncombo.cpp
@@ -0,0 +1,742 @@
+/* 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 "useridselectioncombo.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;
+
+Q_DECLARE_METATYPE(GpgME::Key)
+Q_DECLARE_METATYPE(GpgME::UserID)
+
+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::UserID &userID)
+{
+ QString name;
+ QString email;
+
+ 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 = Kleo::DN(userID.parent().userID(0).id())[QStringLiteral("CN")];
+ }
+ }
+ 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 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 (rightUserId.isNull()) {
+ return true;
+ }
+ const auto leftNameAndEmail = formatUserID(leftUserId);
+ const auto rightNameAndEmail = formatUserID(rightUserId);
+ const int cmp = QString::localeAwareCompare(leftNameAndEmail, rightNameAndEmail);
+ if (cmp) {
+ return cmp < 0;
+ }
+
+ 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 : leftUserId.parent().subkeys()) {
+ if (s.isBad()) {
+ continue;
+ }
+ if (s.creationTime() > leftTime) {
+ leftTime = s.creationTime();
+ }
+ }
+ time_t rightTime = 0;
+ 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(leftUserId.parent().primaryFingerprint(), rightUserId.parent().primaryFingerprint()) < 0;
+ }
+
+protected:
+ QVariant data(const QModelIndex &index, int role) const override
+ {
+ if (!index.isValid()) {
+ return QVariant();
+ }
+
+ 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(userId);
+ if (Kleo::KeyCache::instance()->pgpOnly()) {
+ return i18nc("Name <email> (validity, created: date)",
+ "%1 (%2, created: %3)",
+ nameAndEmail,
+ Kleo::Formatting::complianceStringShort(userId),
+ Kleo::Formatting::creationDateString(userId.parent()));
+ } else {
+ return i18nc("Name <email> (validity, type, created: date)",
+ "%1 (%2, %3, created: %4)",
+ nameAndEmail,
+ 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(userId, Validity | Issuer | Subject | Fingerprint | ExpiryDates | UserIDs);
+ }
+ case Qt::DecorationRole: {
+ 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 UserIDSelectionComboPrivate
+{
+public:
+ UserIDSelectionComboPrivate(UserIDSelectionCombo *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 userID = idx.data(KeyList::UserIDRole).value<GpgME::UserID>();
+ if (userID.isNull()) {
+ // WTF?
+ continue;
+ }
+ if (QString::fromStdString(userID.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()
+ {
+ userIDBeforeModelChange = q->currentUserID();
+ customItemBeforeModelChange = q->currentData();
+ }
+
+ void restoreCurrentSelectionAfterModelChange()
+ {
+ if (!userIDBeforeModelChange.isNull()) {
+ q->setCurrentUserID(userIDBeforeModelChange);
+ } 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::UserID userIDBeforeModelChange;
+ QVariant customItemBeforeModelChange;
+ KeyUsage::Flags usageFlags;
+
+private:
+ UserIDSelectionCombo *const q;
+};
+
+}
+
+using namespace Kleo;
+
+UserIDSelectionCombo::UserIDSelectionCombo(QWidget *parent)
+ : UserIDSelectionCombo(true, KeyUsage::None, parent)
+{
+}
+
+UserIDSelectionCombo::UserIDSelectionCombo(bool secretOnly, QWidget *parent)
+ : UserIDSelectionCombo(secretOnly, KeyUsage::None, parent)
+{
+}
+
+UserIDSelectionCombo::UserIDSelectionCombo(KeyUsage::Flags usage, QWidget *parent)
+ : UserIDSelectionCombo{false, usage, parent}
+{
+}
+
+UserIDSelectionCombo::UserIDSelectionCombo(KeyUsage::Flag usage, QWidget *parent)
+ : UserIDSelectionCombo{false, usage, parent}
+{
+}
+
+UserIDSelectionCombo::UserIDSelectionCombo(bool secretOnly, KeyUsage::Flags usage, QWidget *parent)
+ : QComboBox(parent)
+ , d(new UserIDSelectionComboPrivate(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->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, &UserIDSelectionCombo::init);
+}
+
+UserIDSelectionCombo::~UserIDSelectionCombo() = default;
+
+void UserIDSelectionCombo::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, &UserIDSelectionCombo::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 UserIDSelectionCombo::setKeyFilter(const std::shared_ptr<const KeyFilter> &kf)
+{
+ d->sortFilterProxy->setKeyFilter(kf);
+ d->updateWithDefaultKey();
+}
+
+std::shared_ptr<const KeyFilter> UserIDSelectionCombo::keyFilter() const
+{
+ return d->sortFilterProxy->keyFilter();
+}
+
+void UserIDSelectionCombo::setIdFilter(const QString &id)
+{
+ d->sortFilterProxy->setFilterRegularExpression(id);
+ d->mPerfectMatchMbox = id;
+ d->updateWithDefaultKey();
+}
+
+QString UserIDSelectionCombo::idFilter() const
+{
+ return d->sortFilterProxy->filterRegularExpression().pattern();
+}
+
+GpgME::Key Kleo::UserIDSelectionCombo::currentKey() const
+{
+ return currentData(KeyList::KeyRole).value<GpgME::Key>();
+}
+
+void Kleo::UserIDSelectionCombo::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::UserIDSelectionCombo::setCurrentKey(const QString &fingerprint)
+{
+ const auto cur = currentKey();
+ if (!cur.isNull() && !fingerprint.isEmpty() && fingerprint == QLatin1String(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());
+}
+
+GpgME::UserID Kleo::UserIDSelectionCombo::currentUserID() const
+{
+ return currentData(KeyList::UserIDRole).value<GpgME::UserID>();
+}
+
+void Kleo::UserIDSelectionCombo::setCurrentUserID(const GpgME::UserID &userID)
+{
+ for (auto i = 0; i < count(); i++) {
+ const auto &other = itemData(i, KeyList::UserIDRole).value<GpgME::UserID>();
+ if (!qstrcmp(userID.id(), other.id()) && !qstrcmp(userID.parent().primaryFingerprint(), other.parent().primaryFingerprint())) {
+ setCurrentIndex(i);
+ setToolTip(currentData(Qt::ToolTipRole).toString());
+ return;
+ }
+ }
+ if (!d->selectPerfectIdMatch()) {
+ d->updateWithDefaultKey();
+ setToolTip(currentData(Qt::ToolTipRole).toString());
+ }
+}
+
+void UserIDSelectionCombo::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 UserIDSelectionCombo::appendCustomItem(const QIcon &icon, const QString &text, const QVariant &data, const QString &toolTip)
+{
+ d->proxyModel->appendItem(icon, text, data, toolTip);
+}
+
+void UserIDSelectionCombo::appendCustomItem(const QIcon &icon, const QString &text, const QVariant &data)
+{
+ appendCustomItem(icon, text, data, QString());
+}
+
+void UserIDSelectionCombo::prependCustomItem(const QIcon &icon, const QString &text, const QVariant &data, const QString &toolTip)
+{
+ d->proxyModel->prependItem(icon, text, data, toolTip);
+}
+
+void UserIDSelectionCombo::prependCustomItem(const QIcon &icon, const QString &text, const QVariant &data)
+{
+ prependCustomItem(icon, text, data, QString());
+}
+
+void UserIDSelectionCombo::removeCustomItem(const QVariant &data)
+{
+ d->proxyModel->removeCustomItem(data);
+}
+
+void Kleo::UserIDSelectionCombo::setDefaultKey(const QString &fingerprint, GpgME::Protocol proto)
+{
+ d->defaultKeys.insert(proto, fingerprint);
+ d->updateWithDefaultKey();
+}
+
+void Kleo::UserIDSelectionCombo::setDefaultKey(const QString &fingerprint)
+{
+ setDefaultKey(fingerprint, GpgME::UnknownProtocol);
+}
+
+QString Kleo::UserIDSelectionCombo::defaultKey(GpgME::Protocol proto) const
+{
+ return d->defaultKeys.value(proto);
+}
+
+QString Kleo::UserIDSelectionCombo::defaultKey() const
+{
+ return defaultKey(GpgME::UnknownProtocol);
+}
+#include "useridselectioncombo.moc"
+
+#include "moc_useridselectioncombo.cpp"
diff --git a/src/ui/useridselectioncombo.h b/src/ui/useridselectioncombo.h
new file mode 100644
index 00000000..59438a80
--- /dev/null
+++ b/src/ui/useridselectioncombo.h
@@ -0,0 +1,94 @@
+/* This file is part of Kleopatra, the KDE keymanager
+ SPDX-FileCopyrightText: 2016 Klarälvdalens Datakonsult AB
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#pragma once
+
+#include "kleo_export.h"
+
+#include <libkleo/enum.h>
+#include <libkleo/keyusage.h>
+
+#include <QComboBox>
+
+#include <gpgme++/global.h>
+
+#include <memory>
+
+namespace GpgME
+{
+class Key;
+}
+
+namespace Kleo
+{
+class KeyFilter;
+class UserIDSelectionComboPrivate;
+
+class KLEO_EXPORT UserIDSelectionCombo : public QComboBox
+{
+ Q_OBJECT
+
+public:
+ explicit UserIDSelectionCombo(QWidget *parent = nullptr);
+ explicit UserIDSelectionCombo(bool secretOnly, QWidget *parent = nullptr);
+ /**
+ * @param usage the desired usage of the certificate
+ *
+ * \a usage is used to mark certificates that cannot be used for the desired
+ * usage with an appropriate icon. This is useful in combination with a suitable
+ * key filter.
+ * For example, the key filter could filter out any certificates without
+ * encryption subkeys and the usage flags would mark certificates with expired
+ * encryption subkeys as unusable, so that the users see that there is a
+ * certificate, but that it cannot be used.
+ */
+ explicit UserIDSelectionCombo(KeyUsage::Flags usage, QWidget *parent = nullptr);
+ /* Overload to help the compiler choose the correct overload if a KeyUsage::Flag is passed as first argument.
+ * Without this overload the compiler tries to use the bool-overload instead of the KeyUsage::Flags-overload
+ * and throws an error. */
+ explicit UserIDSelectionCombo(KeyUsage::Flag usage, QWidget *parent = nullptr);
+ UserIDSelectionCombo(bool secretOnly, KeyUsage::Flags usage, QWidget *parent = nullptr);
+ ~UserIDSelectionCombo() override;
+
+ void setKeyFilter(const std::shared_ptr<const KeyFilter> &kf);
+ std::shared_ptr<const KeyFilter> keyFilter() const;
+
+ void setIdFilter(const QString &id);
+ QString idFilter() const;
+
+ void refreshKeys();
+
+ GpgME::Key currentKey() const;
+ void setCurrentKey(const GpgME::Key &key);
+ void setCurrentKey(const QString &fingerprint);
+
+ GpgME::UserID currentUserID() const;
+ void setCurrentUserID(const GpgME::UserID &userID);
+
+ void setDefaultKey(const QString &fingerprint);
+ void setDefaultKey(const QString &fingerprint, GpgME::Protocol proto);
+ QString defaultKey() const;
+ QString defaultKey(GpgME::Protocol proto) const;
+
+ void prependCustomItem(const QIcon &icon, const QString &text, const QVariant &data);
+ void appendCustomItem(const QIcon &icon, const QString &text, const QVariant &data);
+ void prependCustomItem(const QIcon &icon, const QString &text, const QVariant &data, const QString &toolTip);
+ void appendCustomItem(const QIcon &icon, const QString &text, const QVariant &data, const QString &toolTip);
+ void removeCustomItem(const QVariant &data);
+
+Q_SIGNALS:
+ void customItemSelected(const QVariant &data);
+ void currentKeyChanged(const GpgME::Key &key);
+ void keyListingFinished();
+
+protected:
+ virtual void init();
+
+private:
+ std::unique_ptr<UserIDSelectionComboPrivate> const d;
+};
+
+}
diff --git a/src/utils/formatting.cpp b/src/utils/formatting.cpp
index 9e558340..ee314f02 100644
--- a/src/utils/formatting.cpp
+++ b/src/utils/formatting.cpp
@@ -1,1485 +1,1527 @@
/* -*- 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 icon(key.userID(0));
+}
+
+QIcon Formatting::IconProvider::icon(const GpgME::UserID &userID) const
+{
+ if (usage.canEncrypt() && !Kleo::canBeUsedForEncryption(userID.parent())) {
return Formatting::errorIcon();
}
- if (usage.canSign() && !Kleo::canBeUsedForSigning(key)) {
+ if (usage.canSign() && !Kleo::canBeUsedForSigning(userID.parent())) {
return Formatting::errorIcon();
}
- if (key.isBad()) {
+ if (userID.parent().isBad() || userID.isBad()) {
return Formatting::errorIcon();
}
- const auto primaryUserId = key.userID(0);
- if (Kleo::isRevokedOrExpired(primaryUserId)) {
+ if (Kleo::isRevokedOrExpired(userID)) {
return Formatting::errorIcon();
}
- return iconForValidity(primaryUserId);
+ 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 QLatin1String("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(), QLatin1String(subkey.publicKeyAlgorithmAsString()));
} else {
return i18n("%1-bit %2", subkey.length(), QLatin1String(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(), QLatin1String(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(QLatin1String(", "));
}
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(QLatin1String(", "));
}
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 QLatin1String("<font color=\"red\">") + txt.toHtmlEscaped() + QLatin1String("</font>");
}
}
-QString Formatting::toolTip(const Key &key, int flags)
+static QString toolTipInternal(const GpgME::Key &key, const GpgME::UserID &userID, 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 (flags & Formatting::Validity) {
if (key.protocol() == GpgME::OpenPGP || (key.keyListMode() & Validate)) {
- if (key.isRevoked()) {
+ if (userID.isRevoked() || 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;
+ if (!userID.isNull()) {
+ if (userID.validity() >= UserID::Validity::Full) {
+ result = i18n("User-ID is certified.");
+ const auto compliance = Formatting::complianceStringForUserID(userID);
+ if (!compliance.isEmpty()) {
+ result += QStringLiteral("<br>") + compliance;
+ }
+ } else {
+ result = i18n("User-ID is not certified.");
}
} else {
- result = i18np("One User-ID is not certified.", "%1 User-IDs are not certified.", key.numUserIDs() - fullyTrusted);
+ 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 = Formatting::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) {
+ if (flags == Formatting::Validity) {
return result;
}
result += QLatin1String("<table border=\"0\">");
if (key.protocol() == GpgME::CMS) {
- if (flags & SerialNumber) {
+ if (flags & Formatting::SerialNumber) {
result += format_row(i18n("Serial number"), key.issuerSerial());
}
- if (flags & Issuer) {
+ if (flags & Formatting::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 & Formatting::UserIDs) {
+ if (userID.isNull()) {
+ const std::vector<UserID> uids = key.userIDs();
+ if (!uids.empty()) {
+ result += format_row(key.protocol() == GpgME::CMS ? i18n("Subject") : i18n("User-ID"), Formatting::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."), Formatting::prettyUserID(*it));
+ }
}
}
+ } else {
+ result += format_row(key.protocol() == GpgME::CMS ? i18n("Subject") : i18n("User-ID"), Formatting::prettyUserID(userID));
}
}
- if (flags & ExpiryDates) {
+ if (flags & Formatting::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) {
+
+ if (flags & Formatting::CertificateType) {
result += format_row(i18n("Type"), format_keytype(key));
}
- if (flags & CertificateUsage) {
+ if (flags & Formatting::CertificateUsage) {
result += format_row(i18n("Usage"), format_keyusage(key));
}
- if (flags & KeyID) {
+ if (flags & Formatting::KeyID) {
result += format_row(i18n("Key-ID"), QString::fromLatin1(key.shortKeyID()));
}
- if (flags & Fingerprint) {
+ if (flags & Formatting::Fingerprint) {
result += format_row(i18n("Fingerprint"), key.primaryFingerprint());
}
- if (flags & OwnerTrust) {
+ if (flags & Formatting::OwnerTrust) {
if (key.protocol() == GpgME::OpenPGP) {
- result += format_row(i18n("Certification trust"), ownerTrustShort(key));
+ result += format_row(i18n("Certification trust"), Formatting::ownerTrustShort(key));
} else if (key.isRoot()) {
- result += format_row(i18n("Trusted issuer?"), key.userID(0).validity() == UserID::Ultimate ? i18n("Yes") : i18n("No"));
+ result += format_row(i18n("Trusted issuer?"), (userID.isNull() ? key.userID(0) : userID).validity() == UserID::Ultimate ? i18n("Yes") : i18n("No"));
}
}
-
- if (flags & StorageLocation) {
+ if (flags & Formatting::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) {
+ if (flags & Formatting::Subkeys) {
for (const auto &sub : key.subkeys()) {
result += QLatin1String("<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) {
+ if (flags & Formatting::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) {
+ if (flags & Formatting::CertificateType) {
result += format_row(i18n("Type"), format_subkeytype(sub));
}
- if (flags & CertificateUsage) {
+ if (flags & Formatting::CertificateUsage) {
result += format_row(i18n("Usage"), format_subkeyusage(sub));
}
- if (flags & StorageLocation) {
+ if (flags & Formatting::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 += QLatin1String("</table>");
return result;
}
+QString Formatting::toolTip(const Key &key, int flags)
+{
+ return toolTipInternal(key, UserID(), flags);
+}
+
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(QLatin1String("<br>") + Formatting::summaryLine(*it).toHtmlEscaped());
}
}
if (keys.size() > numKeysForTooltip) {
result.push_back(QLatin1String("<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)
+{
+ return toolTipInternal(userID.parent(), userID, flags);
+}
+
//
// 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());
}
}
Q_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();
}
}
Q_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(QLatin1String(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, QLatin1String(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(QLatin1String(", "));
}
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 == QLatin1String("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

Mime Type
text/x-diff
Expires
Tue, Jan 20, 1:36 AM (1 d, 22 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
cf/47/4525aa9a58fc8672f8191cea0922

Event Timeline