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"