File indexing completed on 2025-01-05 04:55:49

0001 /*  -*- c++ -*-
0002     keyselectiondialog.cpp
0003 
0004     This file is part of libkleopatra, the KDE keymanagement library
0005     SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB
0006 
0007     Based on kpgpui.cpp
0008     SPDX-FileCopyrightText: 2001, 2002 the KPGP authors
0009     See file libkdenetwork/AUTHORS.kpgp for details
0010 
0011     SPDX-License-Identifier: GPL-2.0-or-later
0012 */
0013 
0014 #include <config-libkleo.h>
0015 
0016 #include "keyselectiondialog.h"
0017 
0018 #include "keylistview.h"
0019 #include "progressdialog.h"
0020 
0021 #include <libkleo/compat.h>
0022 #include <libkleo/compliance.h>
0023 #include <libkleo/dn.h>
0024 #include <libkleo/formatting.h>
0025 
0026 #include <kleo_ui_debug.h>
0027 
0028 #include <KConfig>
0029 #include <KConfigGroup>
0030 #include <KLocalizedString>
0031 #include <KMessageBox>
0032 #include <KSharedConfig>
0033 
0034 #include <QGpgME/KeyListJob>
0035 
0036 #include <QApplication>
0037 #include <QCheckBox>
0038 #include <QDialogButtonBox>
0039 #include <QFrame>
0040 #include <QHBoxLayout>
0041 #include <QLabel>
0042 #include <QLineEdit>
0043 #include <QMenu>
0044 #include <QProcess>
0045 #include <QPushButton>
0046 #include <QRegularExpression>
0047 #include <QScrollBar>
0048 #include <QTimer>
0049 #include <QVBoxLayout>
0050 
0051 #include <gpgme++/key.h>
0052 #include <gpgme++/keylistresult.h>
0053 
0054 #include <algorithm>
0055 #include <iterator>
0056 #include <string.h>
0057 
0058 using namespace Kleo;
0059 
0060 static bool checkKeyUsage(const GpgME::Key &key, unsigned int keyUsage, QString *statusString = nullptr)
0061 {
0062     auto setStatusString = [statusString](const QString &status) {
0063         if (statusString) {
0064             *statusString = status;
0065         }
0066     };
0067 
0068     if (keyUsage & KeySelectionDialog::ValidKeys) {
0069         if (key.isInvalid()) {
0070             if (key.keyListMode() & GpgME::Validate) {
0071                 qCDebug(KLEO_UI_LOG) << "key is invalid";
0072                 setStatusString(i18n("The key is not valid."));
0073                 return false;
0074             } else {
0075                 qCDebug(KLEO_UI_LOG) << "key is invalid - ignoring";
0076             }
0077         }
0078         if (key.isExpired()) {
0079             qCDebug(KLEO_UI_LOG) << "key is expired";
0080             setStatusString(i18n("The key is expired."));
0081             return false;
0082         } else if (key.isRevoked()) {
0083             qCDebug(KLEO_UI_LOG) << "key is revoked";
0084             setStatusString(i18n("The key is revoked."));
0085             return false;
0086         } else if (key.isDisabled()) {
0087             qCDebug(KLEO_UI_LOG) << "key is disabled";
0088             setStatusString(i18n("The key is disabled."));
0089             return false;
0090         }
0091     }
0092 
0093     if (keyUsage & KeySelectionDialog::EncryptionKeys && !Kleo::keyHasEncrypt(key)) {
0094         qCDebug(KLEO_UI_LOG) << "key can't encrypt";
0095         setStatusString(i18n("The key is not designated for encryption."));
0096         return false;
0097     }
0098     if (keyUsage & KeySelectionDialog::SigningKeys && !Kleo::keyHasSign(key)) {
0099         qCDebug(KLEO_UI_LOG) << "key can't sign";
0100         setStatusString(i18n("The key is not designated for signing."));
0101         return false;
0102     }
0103     if (keyUsage & KeySelectionDialog::CertificationKeys && !Kleo::keyHasCertify(key)) {
0104         qCDebug(KLEO_UI_LOG) << "key can't certify";
0105         setStatusString(i18n("The key is not designated for certifying."));
0106         return false;
0107     }
0108     if (keyUsage & KeySelectionDialog::AuthenticationKeys && !Kleo::keyHasAuthenticate(key)) {
0109         qCDebug(KLEO_UI_LOG) << "key can't authenticate";
0110         setStatusString(i18n("The key is not designated for authentication."));
0111         return false;
0112     }
0113 
0114     if (keyUsage & KeySelectionDialog::SecretKeys && !(keyUsage & KeySelectionDialog::PublicKeys) && !key.hasSecret()) {
0115         qCDebug(KLEO_UI_LOG) << "key isn't secret";
0116         setStatusString(i18n("The key is not secret."));
0117         return false;
0118     }
0119 
0120     if (keyUsage & KeySelectionDialog::TrustedKeys && key.protocol() == GpgME::OpenPGP &&
0121         // only check this for secret keys for now.
0122         // Seems validity isn't checked for secret keylistings...
0123         !key.hasSecret()) {
0124         std::vector<GpgME::UserID> uids = key.userIDs();
0125         for (std::vector<GpgME::UserID>::const_iterator it = uids.begin(); it != uids.end(); ++it) {
0126             if (!it->isRevoked() && it->validity() >= GpgME::UserID::Marginal) {
0127                 setStatusString(i18n("The key can be used."));
0128                 return true;
0129             }
0130         }
0131         qCDebug(KLEO_UI_LOG) << "key has no UIDs with validity >= Marginal";
0132         setStatusString(i18n("The key is not trusted enough."));
0133         return false;
0134     }
0135     // X.509 keys are always trusted, else they won't be the keybox.
0136     // PENDING(marc) check that this ^ is correct
0137 
0138     setStatusString(i18n("The key can be used."));
0139     return true;
0140 }
0141 
0142 static bool checkKeyUsage(const std::vector<GpgME::Key> &keys, unsigned int keyUsage)
0143 {
0144     for (auto it = keys.begin(); it != keys.end(); ++it) {
0145         if (!checkKeyUsage(*it, keyUsage)) {
0146             return false;
0147         }
0148     }
0149     return true;
0150 }
0151 
0152 namespace
0153 {
0154 
0155 class ColumnStrategy : public KeyListView::ColumnStrategy
0156 {
0157 public:
0158     ColumnStrategy(unsigned int keyUsage);
0159 
0160     QString title(int col) const override;
0161     int width(int col, const QFontMetrics &fm) const override;
0162 
0163     QString text(const GpgME::Key &key, int col) const override;
0164     QString accessibleText(const GpgME::Key &key, int column) const override;
0165     QString toolTip(const GpgME::Key &key, int col) const override;
0166     QIcon icon(const GpgME::Key &key, int col) const override;
0167 
0168 private:
0169     const QIcon mKeyGoodPix, mKeyBadPix, mKeyUnknownPix, mKeyValidPix;
0170     const unsigned int mKeyUsage;
0171 };
0172 
0173 static QString iconPath(const QString &name)
0174 {
0175     return QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("libkleopatra/pics/") + name + QStringLiteral(".png"));
0176 }
0177 
0178 ColumnStrategy::ColumnStrategy(unsigned int keyUsage)
0179     : KeyListView::ColumnStrategy()
0180     , mKeyGoodPix(iconPath(QStringLiteral("key_ok")))
0181     , mKeyBadPix(iconPath(QStringLiteral("key_bad")))
0182     , mKeyUnknownPix(iconPath(QStringLiteral("key_unknown")))
0183     , mKeyValidPix(iconPath(QStringLiteral("key")))
0184     , mKeyUsage(keyUsage)
0185 {
0186     if (keyUsage == 0) {
0187         qCWarning(KLEO_UI_LOG) << "KeySelectionDialog: keyUsage == 0. You want to use AllKeys instead.";
0188     }
0189 }
0190 
0191 QString ColumnStrategy::title(int col) const
0192 {
0193     switch (col) {
0194     case 0:
0195         return i18n("Key ID");
0196     case 1:
0197         return i18n("User ID");
0198     default:
0199         return QString();
0200     }
0201 }
0202 
0203 int ColumnStrategy::width(int col, const QFontMetrics &fm) const
0204 {
0205     if (col == 0) {
0206         static const char hexchars[] = "0123456789ABCDEF";
0207         int maxWidth = 0;
0208         for (unsigned int i = 0; i < 16; ++i) {
0209             maxWidth = qMax(fm.boundingRect(QLatin1Char(hexchars[i])).width(), maxWidth);
0210         }
0211         return 8 * maxWidth + 2 * 16 /* KIconLoader::SizeSmall */;
0212     }
0213     return KeyListView::ColumnStrategy::width(col, fm);
0214 }
0215 
0216 QString ColumnStrategy::text(const GpgME::Key &key, int col) const
0217 {
0218     switch (col) {
0219     case 0: {
0220         if (key.shortKeyID()) {
0221             return Formatting::prettyID(key.shortKeyID());
0222         } else {
0223             return xi18n("<placeholder>unknown</placeholder>");
0224         }
0225     }
0226     case 1: {
0227         const char *uid = key.userID(0).id();
0228         if (key.protocol() == GpgME::OpenPGP) {
0229             return uid && *uid ? QString::fromUtf8(uid) : QString();
0230         } else { // CMS
0231             return DN(uid).prettyDN();
0232         }
0233     }
0234     default:
0235         return QString();
0236     }
0237 }
0238 
0239 QString ColumnStrategy::accessibleText(const GpgME::Key &key, int col) const
0240 {
0241     switch (col) {
0242     case 0: {
0243         if (key.shortKeyID()) {
0244             return Formatting::accessibleHexID(key.shortKeyID());
0245         }
0246         [[fallthrough]];
0247     }
0248     default:
0249         return {};
0250     }
0251 }
0252 
0253 QString ColumnStrategy::toolTip(const GpgME::Key &key, int) const
0254 {
0255     const char *uid = key.userID(0).id();
0256     const char *fpr = key.primaryFingerprint();
0257     const char *issuer = key.issuerName();
0258     const GpgME::Subkey subkey = key.subkey(0);
0259     const QString expiry = Formatting::expirationDateString(subkey);
0260     const QString creation = Formatting::creationDateString(subkey);
0261     QString keyStatusString;
0262     if (!checkKeyUsage(key, mKeyUsage, &keyStatusString)) {
0263         // Show the status in bold if there is a problem
0264         keyStatusString = QLatin1StringView("<b>") % keyStatusString % QLatin1String("</b>");
0265     }
0266 
0267     QString html = QStringLiteral("<qt><p style=\"style='white-space:pre'\">");
0268     if (key.protocol() == GpgME::OpenPGP) {
0269         html += i18n("OpenPGP key for <b>%1</b>", uid ? QString::fromUtf8(uid) : i18n("unknown"));
0270     } else {
0271         html += i18n("S/MIME key for <b>%1</b>", uid ? DN(uid).prettyDN() : i18n("unknown"));
0272     }
0273     html += QStringLiteral("</p><table>");
0274 
0275     const auto addRow = [&html](const QString &name, const QString &value) {
0276         html += QStringLiteral("<tr><td align=\"right\"><b>%1: </b></td><td>%2</td></tr>").arg(name, value);
0277     };
0278     addRow(i18n("Valid from"), creation);
0279     addRow(i18n("Valid until"), expiry);
0280     addRow(i18nc("Key fingerprint", "Fingerprint"), fpr ? QString::fromLatin1(fpr) : i18n("unknown"));
0281     if (key.protocol() != GpgME::OpenPGP) {
0282         addRow(i18nc("Key issuer", "Issuer"), issuer ? DN(issuer).prettyDN() : i18n("unknown"));
0283     }
0284     addRow(i18nc("Key status", "Status"), keyStatusString);
0285     if (DeVSCompliance::isActive()) {
0286         addRow(i18nc("Compliance of key", "Compliance"), DeVSCompliance::name(key.isDeVs()));
0287     }
0288     html += QStringLiteral("</table></qt>");
0289 
0290     return html;
0291 }
0292 
0293 QIcon ColumnStrategy::icon(const GpgME::Key &key, int col) const
0294 {
0295     if (col != 0) {
0296         return QIcon();
0297     }
0298     // this key did not undergo a validating keylisting yet:
0299     if (!(key.keyListMode() & GpgME::Validate)) {
0300         return mKeyUnknownPix;
0301     }
0302 
0303     if (!checkKeyUsage(key, mKeyUsage)) {
0304         return mKeyBadPix;
0305     }
0306 
0307     if (key.protocol() == GpgME::CMS) {
0308         return mKeyGoodPix;
0309     }
0310 
0311     switch (key.userID(0).validity()) {
0312     default:
0313     case GpgME::UserID::Unknown:
0314     case GpgME::UserID::Undefined:
0315         return mKeyUnknownPix;
0316     case GpgME::UserID::Never:
0317         return mKeyValidPix;
0318     case GpgME::UserID::Marginal:
0319     case GpgME::UserID::Full:
0320     case GpgME::UserID::Ultimate: {
0321         if (DeVSCompliance::isActive() && !key.isDeVs()) {
0322             return mKeyValidPix;
0323         }
0324         return mKeyGoodPix;
0325     }
0326     }
0327 }
0328 
0329 }
0330 
0331 static const int sCheckSelectionDelay = 250;
0332 
0333 KeySelectionDialog::KeySelectionDialog(QWidget *parent, Options options)
0334     : QDialog(parent)
0335     , mOpenPGPBackend(QGpgME::openpgp())
0336     , mSMIMEBackend(QGpgME::smime())
0337     , mKeyUsage(AllKeys)
0338 {
0339     qCDebug(KLEO_UI_LOG) << "mTruncated:" << mTruncated << "mSavedOffsetY:" << mSavedOffsetY;
0340     setUpUI(options, QString());
0341 }
0342 
0343 KeySelectionDialog::KeySelectionDialog(const QString &title,
0344                                        const QString &text,
0345                                        const std::vector<GpgME::Key> &selectedKeys,
0346                                        unsigned int keyUsage,
0347                                        bool extendedSelection,
0348                                        bool rememberChoice,
0349                                        QWidget *parent,
0350                                        bool modal)
0351     : QDialog(parent)
0352     , mSelectedKeys(selectedKeys)
0353     , mKeyUsage(keyUsage)
0354 {
0355     setWindowTitle(title);
0356     setModal(modal);
0357     init(rememberChoice, extendedSelection, text, QString());
0358 }
0359 
0360 KeySelectionDialog::KeySelectionDialog(const QString &title,
0361                                        const QString &text,
0362                                        const QString &initialQuery,
0363                                        const std::vector<GpgME::Key> &selectedKeys,
0364                                        unsigned int keyUsage,
0365                                        bool extendedSelection,
0366                                        bool rememberChoice,
0367                                        QWidget *parent,
0368                                        bool modal)
0369     : QDialog(parent)
0370     , mSelectedKeys(selectedKeys)
0371     , mKeyUsage(keyUsage)
0372     , mSearchText(initialQuery)
0373     , mInitialQuery(initialQuery)
0374 {
0375     setWindowTitle(title);
0376     setModal(modal);
0377     init(rememberChoice, extendedSelection, text, initialQuery);
0378 }
0379 
0380 KeySelectionDialog::KeySelectionDialog(const QString &title,
0381                                        const QString &text,
0382                                        const QString &initialQuery,
0383                                        unsigned int keyUsage,
0384                                        bool extendedSelection,
0385                                        bool rememberChoice,
0386                                        QWidget *parent,
0387                                        bool modal)
0388     : QDialog(parent)
0389     , mKeyUsage(keyUsage)
0390     , mSearchText(initialQuery)
0391     , mInitialQuery(initialQuery)
0392 {
0393     setWindowTitle(title);
0394     setModal(modal);
0395     init(rememberChoice, extendedSelection, text, initialQuery);
0396 }
0397 
0398 void KeySelectionDialog::setUpUI(Options options, const QString &initialQuery)
0399 {
0400     auto mainLayout = new QVBoxLayout(this);
0401     QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
0402     mOkButton = buttonBox->button(QDialogButtonBox::Ok);
0403     mOkButton->setDefault(true);
0404     mOkButton->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_Return));
0405 
0406     mCheckSelectionTimer = new QTimer(this);
0407     mStartSearchTimer = new QTimer(this);
0408 
0409     QFrame *page = new QFrame(this);
0410     mainLayout->addWidget(page);
0411     mainLayout->addWidget(buttonBox);
0412 
0413     mTopLayout = new QVBoxLayout(page);
0414     mTopLayout->setContentsMargins(0, 0, 0, 0);
0415 
0416     mTextLabel = new QLabel(page);
0417     mTextLabel->setWordWrap(true);
0418 
0419     // Setting the size policy is necessary as a workaround for https://issues.kolab.org/issue4429
0420     // and http://bugreports.qt.nokia.com/browse/QTBUG-8740
0421     mTextLabel->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
0422     connect(mTextLabel, &QLabel::linkActivated, this, &KeySelectionDialog::slotStartCertificateManager);
0423     mTopLayout->addWidget(mTextLabel);
0424     mTextLabel->hide();
0425 
0426     QPushButton *const searchExternalPB = new QPushButton(i18n("Search for &External Certificates"), page);
0427     mTopLayout->addWidget(searchExternalPB, 0, Qt::AlignLeft);
0428     connect(searchExternalPB, &QAbstractButton::clicked, this, &KeySelectionDialog::slotStartSearchForExternalCertificates);
0429     if (initialQuery.isEmpty()) {
0430         searchExternalPB->hide();
0431     }
0432 
0433     auto hlay = new QHBoxLayout();
0434     mTopLayout->addLayout(hlay);
0435 
0436     auto le = new QLineEdit(page);
0437     le->setClearButtonEnabled(true);
0438     le->setText(initialQuery);
0439 
0440     QLabel *lbSearchFor = new QLabel(i18n("&Search for:"), page);
0441     lbSearchFor->setBuddy(le);
0442 
0443     hlay->addWidget(lbSearchFor);
0444     hlay->addWidget(le, 1);
0445     le->setFocus();
0446 
0447     connect(le, &QLineEdit::textChanged, this, [this](const QString &s) {
0448         slotSearch(s);
0449     });
0450     connect(mStartSearchTimer, &QTimer::timeout, this, &KeySelectionDialog::slotFilter);
0451 
0452     mKeyListView = new KeyListView(new ColumnStrategy(mKeyUsage), nullptr, page);
0453     mKeyListView->setObjectName(QLatin1StringView("mKeyListView"));
0454     mKeyListView->header()->stretchLastSection();
0455     mKeyListView->setRootIsDecorated(true);
0456     mKeyListView->setSortingEnabled(true);
0457     mKeyListView->header()->setSortIndicatorShown(true);
0458     mKeyListView->header()->setSortIndicator(1, Qt::AscendingOrder); // sort by User ID
0459     if (options & ExtendedSelection) {
0460         mKeyListView->setSelectionMode(QAbstractItemView::ExtendedSelection);
0461     }
0462     mTopLayout->addWidget(mKeyListView, 10);
0463 
0464     if (options & RememberChoice) {
0465         mRememberCB = new QCheckBox(i18n("&Remember choice"), page);
0466         mTopLayout->addWidget(mRememberCB);
0467         mRememberCB->setWhatsThis(
0468             i18n("<qt><p>If you check this box your choice will "
0469                  "be stored and you will not be asked again."
0470                  "</p></qt>"));
0471     }
0472 
0473     connect(mCheckSelectionTimer, &QTimer::timeout, this, [this]() {
0474         slotCheckSelection();
0475     });
0476     connectSignals();
0477 
0478     connect(mKeyListView, &KeyListView::doubleClicked, this, &KeySelectionDialog::slotTryOk);
0479     connect(mKeyListView, &KeyListView::contextMenu, this, &KeySelectionDialog::slotRMB);
0480 
0481     if (options & RereadKeys) {
0482         QPushButton *button = new QPushButton(i18n("&Reread Keys"));
0483         buttonBox->addButton(button, QDialogButtonBox::ActionRole);
0484         connect(button, &QPushButton::clicked, this, &KeySelectionDialog::slotRereadKeys);
0485     }
0486     if (options & ExternalCertificateManager) {
0487         QPushButton *button = new QPushButton(i18n("&Start Certificate Manager"));
0488         buttonBox->addButton(button, QDialogButtonBox::ActionRole);
0489         connect(button, &QPushButton::clicked, this, [this]() {
0490             slotStartCertificateManager();
0491         });
0492     }
0493     connect(mOkButton, &QPushButton::clicked, this, &KeySelectionDialog::slotOk);
0494     connect(buttonBox->button(QDialogButtonBox::Cancel), &QPushButton::clicked, this, &KeySelectionDialog::slotCancel);
0495 
0496     mTopLayout->activate();
0497 
0498     if (qApp) {
0499         QSize dialogSize(sizeHint());
0500         KConfigGroup dialogConfig(KSharedConfig::openStateConfig(), QStringLiteral("Key Selection Dialog"));
0501         dialogSize = dialogConfig.readEntry("Dialog size", dialogSize);
0502         const QByteArray headerState = dialogConfig.readEntry("header", QByteArray());
0503         if (!headerState.isEmpty()) {
0504             mKeyListView->header()->restoreState(headerState);
0505         }
0506         resize(dialogSize);
0507     }
0508 }
0509 
0510 void KeySelectionDialog::init(bool rememberChoice, bool extendedSelection, const QString &text, const QString &initialQuery)
0511 {
0512     Options options = {RereadKeys, ExternalCertificateManager};
0513     options.setFlag(ExtendedSelection, extendedSelection);
0514     options.setFlag(RememberChoice, rememberChoice);
0515 
0516     setUpUI(options, initialQuery);
0517     setText(text);
0518 
0519     if (mKeyUsage & OpenPGPKeys) {
0520         mOpenPGPBackend = QGpgME::openpgp();
0521     }
0522     if (mKeyUsage & SMIMEKeys) {
0523         mSMIMEBackend = QGpgME::smime();
0524     }
0525 
0526     slotRereadKeys();
0527 }
0528 
0529 KeySelectionDialog::~KeySelectionDialog()
0530 {
0531     disconnectSignals();
0532     KConfigGroup dialogConfig(KSharedConfig::openStateConfig(), QStringLiteral("Key Selection Dialog"));
0533     dialogConfig.writeEntry("Dialog size", size());
0534     dialogConfig.writeEntry("header", mKeyListView->header()->saveState());
0535     dialogConfig.sync();
0536 }
0537 
0538 void KeySelectionDialog::setText(const QString &text)
0539 {
0540     mTextLabel->setText(text);
0541     mTextLabel->setVisible(!text.isEmpty());
0542 }
0543 
0544 void KeySelectionDialog::setKeys(const std::vector<GpgME::Key> &keys)
0545 {
0546     for (const GpgME::Key &key : keys) {
0547         mKeyListView->slotAddKey(key);
0548     }
0549 }
0550 
0551 void KeySelectionDialog::connectSignals()
0552 {
0553     if (mKeyListView->isMultiSelection()) {
0554         connect(mKeyListView, &QTreeWidget::itemSelectionChanged, this, &KeySelectionDialog::slotSelectionChanged);
0555     } else {
0556         connect(mKeyListView,
0557                 qOverload<KeyListViewItem *>(&KeyListView::selectionChanged),
0558                 this,
0559                 qOverload<KeyListViewItem *>(&KeySelectionDialog::slotCheckSelection));
0560     }
0561 }
0562 
0563 void KeySelectionDialog::disconnectSignals()
0564 {
0565     if (mKeyListView->isMultiSelection()) {
0566         disconnect(mKeyListView, &QTreeWidget::itemSelectionChanged, this, &KeySelectionDialog::slotSelectionChanged);
0567     } else {
0568         disconnect(mKeyListView,
0569                    qOverload<KeyListViewItem *>(&KeyListView::selectionChanged),
0570                    this,
0571                    qOverload<KeyListViewItem *>(&KeySelectionDialog::slotCheckSelection));
0572     }
0573 }
0574 
0575 const GpgME::Key &KeySelectionDialog::selectedKey() const
0576 {
0577     static const GpgME::Key null = GpgME::Key::null;
0578     if (mKeyListView->isMultiSelection() || !mKeyListView->selectedItem()) {
0579         return null;
0580     }
0581     return mKeyListView->selectedItem()->key();
0582 }
0583 
0584 QString KeySelectionDialog::fingerprint() const
0585 {
0586     return QLatin1StringView(selectedKey().primaryFingerprint());
0587 }
0588 
0589 QStringList KeySelectionDialog::fingerprints() const
0590 {
0591     QStringList result;
0592     for (auto it = mSelectedKeys.begin(); it != mSelectedKeys.end(); ++it) {
0593         if (const char *fpr = it->primaryFingerprint()) {
0594             result.push_back(QLatin1StringView(fpr));
0595         }
0596     }
0597     return result;
0598 }
0599 
0600 QStringList KeySelectionDialog::pgpKeyFingerprints() const
0601 {
0602     QStringList result;
0603     for (auto it = mSelectedKeys.begin(); it != mSelectedKeys.end(); ++it) {
0604         if (it->protocol() == GpgME::OpenPGP) {
0605             if (const char *fpr = it->primaryFingerprint()) {
0606                 result.push_back(QLatin1StringView(fpr));
0607             }
0608         }
0609     }
0610     return result;
0611 }
0612 
0613 QStringList KeySelectionDialog::smimeFingerprints() const
0614 {
0615     QStringList result;
0616     for (auto it = mSelectedKeys.begin(); it != mSelectedKeys.end(); ++it) {
0617         if (it->protocol() == GpgME::CMS) {
0618             if (const char *fpr = it->primaryFingerprint()) {
0619                 result.push_back(QLatin1StringView(fpr));
0620             }
0621         }
0622     }
0623     return result;
0624 }
0625 
0626 void KeySelectionDialog::slotRereadKeys()
0627 {
0628     mKeyListView->clear();
0629     mListJobCount = 0;
0630     mTruncated = 0;
0631     mSavedOffsetY = mKeyListView->verticalScrollBar()->value();
0632 
0633     disconnectSignals();
0634     mKeyListView->setEnabled(false);
0635 
0636     // FIXME: save current selection
0637     if (mOpenPGPBackend) {
0638         startKeyListJobForBackend(mOpenPGPBackend, std::vector<GpgME::Key>(), false /*non-validating*/);
0639     }
0640     if (mSMIMEBackend) {
0641         startKeyListJobForBackend(mSMIMEBackend, std::vector<GpgME::Key>(), false /*non-validating*/);
0642     }
0643 
0644     if (mListJobCount == 0) {
0645         mKeyListView->setEnabled(true);
0646         KMessageBox::information(this,
0647                                  i18n("No backends found for listing keys. "
0648                                       "Check your installation."),
0649                                  i18n("Key Listing Failed"));
0650         connectSignals();
0651     }
0652 }
0653 
0654 void KeySelectionDialog::slotStartCertificateManager(const QString &query)
0655 {
0656     QStringList args;
0657 
0658     if (!query.isEmpty()) {
0659         args << QStringLiteral("--search") << query;
0660     }
0661     const QString exec = QStandardPaths::findExecutable(QStringLiteral("kleopatra"));
0662     if (exec.isEmpty()) {
0663         qCWarning(KLEO_UI_LOG) << "Could not find kleopatra executable in PATH";
0664         KMessageBox::error(this,
0665                            i18n("Could not start certificate manager; "
0666                                 "please check your installation."),
0667                            i18n("Certificate Manager Error"));
0668     } else {
0669         QProcess::startDetached(QStringLiteral("kleopatra"), args);
0670         qCDebug(KLEO_UI_LOG) << "\nslotStartCertManager(): certificate manager started.";
0671     }
0672 }
0673 
0674 #ifndef __KLEO_UI_SHOW_KEY_LIST_ERROR_H__
0675 #define __KLEO_UI_SHOW_KEY_LIST_ERROR_H__
0676 static void showKeyListError(QWidget *parent, const GpgME::Error &err)
0677 {
0678     Q_ASSERT(err);
0679     const QString msg = i18n(
0680         "<qt><p>An error occurred while fetching "
0681         "the keys from the backend:</p>"
0682         "<p><b>%1</b></p></qt>",
0683         Formatting::errorAsString(err));
0684 
0685     KMessageBox::error(parent, msg, i18n("Key Listing Failed"));
0686 }
0687 #endif // __KLEO_UI_SHOW_KEY_LIST_ERROR_H__
0688 
0689 namespace
0690 {
0691 struct ExtractFingerprint {
0692     QString operator()(const GpgME::Key &key)
0693     {
0694         return QLatin1StringView(key.primaryFingerprint());
0695     }
0696 };
0697 }
0698 
0699 void KeySelectionDialog::startKeyListJobForBackend(const QGpgME::Protocol *backend, const std::vector<GpgME::Key> &keys, bool validate)
0700 {
0701     Q_ASSERT(backend);
0702     QGpgME::KeyListJob *job = backend->keyListJob(false, false, validate); // local, w/o sigs, validation as given
0703     if (!job) {
0704         return;
0705     }
0706 
0707     connect(job, &QGpgME::KeyListJob::result, this, &KeySelectionDialog::slotKeyListResult);
0708     if (validate) {
0709         connect(job, &QGpgME::KeyListJob::nextKey, mKeyListView, &KeyListView::slotRefreshKey);
0710     } else {
0711         connect(job, &QGpgME::KeyListJob::nextKey, mKeyListView, &KeyListView::slotAddKey);
0712     }
0713 
0714     QStringList fprs;
0715     std::transform(keys.begin(), keys.end(), std::back_inserter(fprs), ExtractFingerprint());
0716     const GpgME::Error err = job->start(fprs, mKeyUsage & SecretKeys && !(mKeyUsage & PublicKeys));
0717 
0718     if (err) {
0719         return showKeyListError(this, err);
0720     }
0721 
0722 #ifndef LIBKLEO_NO_PROGRESSDIALOG
0723     // FIXME: create a MultiProgressDialog:
0724     (void)new ProgressDialog(job, validate ? i18n("Checking selected keys...") : i18n("Fetching keys..."), this);
0725 #endif
0726     ++mListJobCount;
0727 }
0728 
0729 static void selectKeys(KeyListView *klv, const std::vector<GpgME::Key> &selectedKeys)
0730 {
0731     klv->clearSelection();
0732     if (selectedKeys.empty()) {
0733         return;
0734     }
0735     for (auto it = selectedKeys.begin(); it != selectedKeys.end(); ++it) {
0736         if (KeyListViewItem *item = klv->itemByFingerprint(it->primaryFingerprint())) {
0737             item->setSelected(true);
0738         }
0739     }
0740 }
0741 
0742 void KeySelectionDialog::slotKeyListResult(const GpgME::KeyListResult &res)
0743 {
0744     if (res.error()) {
0745         showKeyListError(this, res.error());
0746     } else if (res.isTruncated()) {
0747         ++mTruncated;
0748     }
0749 
0750     if (--mListJobCount > 0) {
0751         return; // not yet finished...
0752     }
0753 
0754     if (mTruncated > 0) {
0755         KMessageBox::information(this,
0756                                  i18np("<qt>One backend returned truncated output.<p>"
0757                                        "Not all available keys are shown</p></qt>",
0758                                        "<qt>%1 backends returned truncated output.<p>"
0759                                        "Not all available keys are shown</p></qt>",
0760                                        mTruncated),
0761                                  i18n("Key List Result"));
0762     }
0763 
0764     mKeyListView->flushKeys();
0765 
0766     mKeyListView->setEnabled(true);
0767     mListJobCount = mTruncated = 0;
0768     mKeysToCheck.clear();
0769 
0770     selectKeys(mKeyListView, mSelectedKeys);
0771 
0772     slotFilter();
0773 
0774     connectSignals();
0775 
0776     slotSelectionChanged();
0777 
0778     // restore the saved position of the contents
0779     mKeyListView->verticalScrollBar()->setValue(mSavedOffsetY);
0780     mSavedOffsetY = 0;
0781 }
0782 
0783 void KeySelectionDialog::slotSelectionChanged()
0784 {
0785     qCDebug(KLEO_UI_LOG) << "KeySelectionDialog::slotSelectionChanged()";
0786 
0787     // (re)start the check selection timer. Checking the selection is delayed
0788     // because else drag-selection doesn't work very good (checking key trust
0789     // is slow).
0790     mCheckSelectionTimer->start(sCheckSelectionDelay);
0791 }
0792 
0793 namespace
0794 {
0795 struct AlreadyChecked {
0796     bool operator()(const GpgME::Key &key) const
0797     {
0798         return key.keyListMode() & GpgME::Validate;
0799     }
0800 };
0801 }
0802 
0803 void KeySelectionDialog::slotCheckSelection(KeyListViewItem *item)
0804 {
0805     qCDebug(KLEO_UI_LOG) << "KeySelectionDialog::slotCheckSelection()";
0806 
0807     mCheckSelectionTimer->stop();
0808 
0809     mSelectedKeys.clear();
0810 
0811     if (!mKeyListView->isMultiSelection()) {
0812         if (item) {
0813             mSelectedKeys.push_back(item->key());
0814         }
0815     }
0816 
0817     for (KeyListViewItem *it = mKeyListView->firstChild(); it; it = it->nextSibling()) {
0818         if (it->isSelected()) {
0819             mSelectedKeys.push_back(it->key());
0820         }
0821     }
0822 
0823     mKeysToCheck.clear();
0824     std::remove_copy_if(mSelectedKeys.begin(), mSelectedKeys.end(), std::back_inserter(mKeysToCheck), AlreadyChecked());
0825     if (mKeysToCheck.empty()) {
0826         mOkButton->setEnabled(!mSelectedKeys.empty() && checkKeyUsage(mSelectedKeys, mKeyUsage));
0827         return;
0828     }
0829 
0830     // performed all fast checks - now for validating key listing:
0831     startValidatingKeyListing();
0832 }
0833 
0834 void KeySelectionDialog::startValidatingKeyListing()
0835 {
0836     if (mKeysToCheck.empty()) {
0837         return;
0838     }
0839 
0840     mListJobCount = 0;
0841     mTruncated = 0;
0842     mSavedOffsetY = mKeyListView->verticalScrollBar()->value();
0843 
0844     disconnectSignals();
0845     mKeyListView->setEnabled(false);
0846 
0847     std::vector<GpgME::Key> smime;
0848     std::vector<GpgME::Key> openpgp;
0849     for (std::vector<GpgME::Key>::const_iterator it = mKeysToCheck.begin(); it != mKeysToCheck.end(); ++it) {
0850         if (it->protocol() == GpgME::OpenPGP) {
0851             openpgp.push_back(*it);
0852         } else {
0853             smime.push_back(*it);
0854         }
0855     }
0856 
0857     if (!openpgp.empty()) {
0858         Q_ASSERT(mOpenPGPBackend);
0859         startKeyListJobForBackend(mOpenPGPBackend, openpgp, true /*validate*/);
0860     }
0861     if (!smime.empty()) {
0862         Q_ASSERT(mSMIMEBackend);
0863         startKeyListJobForBackend(mSMIMEBackend, smime, true /*validate*/);
0864     }
0865 
0866     Q_ASSERT(mListJobCount > 0);
0867 }
0868 
0869 bool KeySelectionDialog::rememberSelection() const
0870 {
0871     return mRememberCB && mRememberCB->isChecked();
0872 }
0873 
0874 void KeySelectionDialog::slotRMB(KeyListViewItem *item, const QPoint &p)
0875 {
0876     if (!item) {
0877         return;
0878     }
0879 
0880     mCurrentContextMenuItem = item;
0881 
0882     QMenu menu;
0883     menu.addAction(i18n("Recheck Key"), this, &KeySelectionDialog::slotRecheckKey);
0884     menu.exec(p);
0885 }
0886 
0887 void KeySelectionDialog::slotRecheckKey()
0888 {
0889     if (!mCurrentContextMenuItem || mCurrentContextMenuItem->key().isNull()) {
0890         return;
0891     }
0892 
0893     mKeysToCheck.clear();
0894     mKeysToCheck.push_back(mCurrentContextMenuItem->key());
0895 }
0896 
0897 void KeySelectionDialog::slotTryOk()
0898 {
0899     if (!mSelectedKeys.empty() && checkKeyUsage(mSelectedKeys, mKeyUsage)) {
0900         slotOk();
0901     }
0902 }
0903 
0904 void KeySelectionDialog::slotOk()
0905 {
0906     if (mCheckSelectionTimer->isActive()) {
0907         slotCheckSelection();
0908     }
0909 #if 0 // Laurent I don't understand why we returns here.
0910     // button could be disabled again after checking the selected key1
0911     if (!mSelectedKeys.empty() && checkKeyUsage(mSelectedKeys, mKeyUsage)) {
0912         return;
0913     }
0914 #endif
0915     mStartSearchTimer->stop();
0916     accept();
0917 }
0918 
0919 void KeySelectionDialog::slotCancel()
0920 {
0921     mCheckSelectionTimer->stop();
0922     mStartSearchTimer->stop();
0923     reject();
0924 }
0925 
0926 void KeySelectionDialog::slotSearch(const QString &text)
0927 {
0928     mSearchText = text.trimmed().toUpper();
0929     slotSearch();
0930 }
0931 
0932 void KeySelectionDialog::slotSearch()
0933 {
0934     mStartSearchTimer->setSingleShot(true);
0935     mStartSearchTimer->start(sCheckSelectionDelay);
0936 }
0937 
0938 void KeySelectionDialog::slotFilter()
0939 {
0940     if (mSearchText.isEmpty()) {
0941         showAllItems();
0942         return;
0943     }
0944 
0945     // OK, so we need to filter:
0946     QRegularExpression keyIdRegExp(QRegularExpression::anchoredPattern(QLatin1StringView("(?:0x)?[A-F0-9]{1,8}")), QRegularExpression::CaseInsensitiveOption);
0947     if (keyIdRegExp.match(mSearchText).hasMatch()) {
0948         if (mSearchText.startsWith(QLatin1StringView("0X")))
0949         // search for keyID only:
0950         {
0951             filterByKeyID(mSearchText.mid(2));
0952         } else
0953         // search for UID and keyID:
0954         {
0955             filterByKeyIDOrUID(mSearchText);
0956         }
0957     } else {
0958         // search in UID:
0959         filterByUID(mSearchText);
0960     }
0961 }
0962 
0963 void KeySelectionDialog::filterByKeyID(const QString &keyID)
0964 {
0965     Q_ASSERT(keyID.length() <= 8);
0966     Q_ASSERT(!keyID.isEmpty()); // regexp in slotFilter should prevent these
0967     if (keyID.isEmpty()) {
0968         showAllItems();
0969     } else {
0970         for (KeyListViewItem *item = mKeyListView->firstChild(); item; item = item->nextSibling()) {
0971             item->setHidden(!item->text(0).toUpper().startsWith(keyID));
0972         }
0973     }
0974 }
0975 
0976 static bool anyUIDMatches(const KeyListViewItem *item, const QRegularExpression &rx)
0977 {
0978     if (!item) {
0979         return false;
0980     }
0981 
0982     const std::vector<GpgME::UserID> uids = item->key().userIDs();
0983     for (auto it = uids.begin(); it != uids.end(); ++it) {
0984         if (it->id() && rx.match(QString::fromUtf8(it->id())).hasMatch()) {
0985             return true;
0986         }
0987     }
0988     return false;
0989 }
0990 
0991 void KeySelectionDialog::filterByKeyIDOrUID(const QString &str)
0992 {
0993     Q_ASSERT(!str.isEmpty());
0994 
0995     // match beginnings of words:
0996     QRegularExpression rx(QLatin1StringView("\\b") + QRegularExpression::escape(str), QRegularExpression::CaseInsensitiveOption);
0997 
0998     for (KeyListViewItem *item = mKeyListView->firstChild(); item; item = item->nextSibling()) {
0999         item->setHidden(!item->text(0).toUpper().startsWith(str) && !anyUIDMatches(item, rx));
1000     }
1001 }
1002 
1003 void KeySelectionDialog::filterByUID(const QString &str)
1004 {
1005     Q_ASSERT(!str.isEmpty());
1006 
1007     // match beginnings of words:
1008     QRegularExpression rx(QLatin1StringView("\\b") + QRegularExpression::escape(str), QRegularExpression::CaseInsensitiveOption);
1009 
1010     for (KeyListViewItem *item = mKeyListView->firstChild(); item; item = item->nextSibling()) {
1011         item->setHidden(!anyUIDMatches(item, rx));
1012     }
1013 }
1014 
1015 void KeySelectionDialog::showAllItems()
1016 {
1017     for (KeyListViewItem *item = mKeyListView->firstChild(); item; item = item->nextSibling()) {
1018         item->setHidden(false);
1019     }
1020 }
1021 
1022 #include "moc_keyselectiondialog.cpp"