diff --git a/src/conf/cryptooperationsconfigwidget.cpp b/src/conf/cryptooperationsconfigwidget.cpp index d8505264b..9dbaca182 100644 --- a/src/conf/cryptooperationsconfigwidget.cpp +++ b/src/conf/cryptooperationsconfigwidget.cpp @@ -1,244 +1,244 @@ /* cryptooperationsconfigwidget.cpp This file is part of kleopatra, the KDE key manager SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2016 Bundesamt für Sicherheit in der Informationstechnik SPDX-FileContributor: Intevation GmbH SPDX-FileCopyrightText: 2022 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de> SPDX-License-Identifier: GPL-2.0-or-later */ #include <config-kleopatra.h> #include "cryptooperationsconfigwidget.h" #include "kleopatra_debug.h" #include "fileoperationspreferences.h" #include "settings.h" #include <Libkleo/ChecksumDefinition> #include <Libkleo/KeyFilterManager> #include <libkleo/classifyconfig.h> #include <KConfig> #include <KConfigGroup> #include <KLocalizedString> #include <KMessageBox> #include <KSharedConfig> #include <QCheckBox> #include <QComboBox> #include <QDir> #include <QGroupBox> #include <QHBoxLayout> #include <QLabel> #include <QPushButton> #include <QRegularExpression> #include <QVBoxLayout> #include <memory> using namespace Kleo; using namespace Kleo::Config; CryptoOperationsConfigWidget::CryptoOperationsConfigWidget(QWidget *p, Qt::WindowFlags f) : QWidget{p, f} { setupGui(); } void CryptoOperationsConfigWidget::setupGui() { auto baseLay = new QVBoxLayout(this); mPGPFileExtCB = new QCheckBox(i18n(R"(Create OpenPGP encrypted files with ".pgp" file extensions instead of ".gpg")")); mASCIIArmorCB = new QCheckBox(i18n("Create signed or encrypted files as text files.")); mASCIIArmorCB->setToolTip(i18nc("@info", "Set this option to encode encrypted or signed files as base64 encoded text. " "So that they can be opened with an editor or sent in a mail body. " "This will increase file size by one third.")); mTreatP7mEmailCB = new QCheckBox(i18nc("@option:check", "Treat .p7m files without extensions as mails.")); mAutoDecryptVerifyCB = new QCheckBox(i18n("Automatically start operation based on input detection for decrypt/verify.")); mAutoExtractArchivesCB = new QCheckBox(i18n("Automatically extract file archives after decryption")); mTmpDirCB = new QCheckBox(i18n("Create temporary decrypted files in the folder of the encrypted file.")); mTmpDirCB->setToolTip(i18nc("@info", "Set this option to avoid using the users temporary directory.")); mSymmetricOnlyCB = new QCheckBox(i18n("Use symmetric encryption only.")); mSymmetricOnlyCB->setToolTip(i18nc("@info", "Set this option to disable public key encryption.")); mPublicKeyOnlyCB = new QCheckBox(i18n("Use public-key encryption only.")); mPublicKeyOnlyCB->setToolTip(i18nc("@info", "Set this option to disable password-based encryption.")); connect(mSymmetricOnlyCB, &QCheckBox::toggled, this, [this]() { if (mSymmetricOnlyCB->isChecked()) { mPublicKeyOnlyCB->setChecked(false); } }); connect(mPublicKeyOnlyCB, &QCheckBox::toggled, this, [this]() { if (mPublicKeyOnlyCB->isChecked()) { mSymmetricOnlyCB->setChecked(false); } }); baseLay->addWidget(mPGPFileExtCB); baseLay->addWidget(mTreatP7mEmailCB); baseLay->addWidget(mAutoDecryptVerifyCB); baseLay->addWidget(mAutoExtractArchivesCB); baseLay->addWidget(mASCIIArmorCB); baseLay->addWidget(mTmpDirCB); baseLay->addWidget(mSymmetricOnlyCB); baseLay->addWidget(mPublicKeyOnlyCB); auto comboLay = new QGridLayout; mChecksumDefinitionCB.createWidgets(this); mChecksumDefinitionCB.label()->setText(i18n("Checksum program to use when creating checksum files:")); comboLay->addWidget(mChecksumDefinitionCB.label(), 0, 0); comboLay->addWidget(mChecksumDefinitionCB.widget(), 0, 1); mArchiveDefinitionCB.createWidgets(this); mArchiveDefinitionCB.label()->setText(i18n("Archive command to use when archiving files:")); comboLay->addWidget(mArchiveDefinitionCB.label(), 1, 0); comboLay->addWidget(mArchiveDefinitionCB.widget(), 1, 1); baseLay->addLayout(comboLay); baseLay->addStretch(1); - for (auto cb : findChildren<QCheckBox *>()) { - connect(cb, &QCheckBox::toggled, this, &CryptoOperationsConfigWidget::changed); + for (const auto checkboxes = findChildren<QCheckBox *>(); auto checkbox : checkboxes) { + connect(checkbox, &QCheckBox::toggled, this, &CryptoOperationsConfigWidget::changed); } - for (auto combo : findChildren<QComboBox *>()) { - connect(combo, &QComboBox::currentIndexChanged, this, &CryptoOperationsConfigWidget::changed); + for (const auto comboboxes = findChildren<QComboBox *>(); auto combobox : comboboxes) { + connect(combobox, &QComboBox::currentIndexChanged, this, &CryptoOperationsConfigWidget::changed); } } CryptoOperationsConfigWidget::~CryptoOperationsConfigWidget() { } void CryptoOperationsConfigWidget::defaults() { FileOperationsPreferences filePrefs; filePrefs.setUsePGPFileExt(filePrefs.findItem(QStringLiteral("UsePGPFileExt"))->getDefault().toBool()); filePrefs.setAutoDecryptVerify(filePrefs.findItem(QStringLiteral("AutoDecryptVerify"))->getDefault().toBool()); filePrefs.setAutoExtractArchives(filePrefs.findItem(QStringLiteral("AutoExtractArchives"))->getDefault().toBool()); filePrefs.setAddASCIIArmor(filePrefs.findItem(QStringLiteral("AddASCIIArmor"))->getDefault().toBool()); filePrefs.setDontUseTmpDir(filePrefs.findItem(QStringLiteral("DontUseTmpDir"))->getDefault().toBool()); filePrefs.setSymmetricEncryptionOnly(filePrefs.findItem(QStringLiteral("SymmetricEncryptionOnly"))->getDefault().toBool()); filePrefs.setPublicKeyEncryptionOnly(filePrefs.findItem(QStringLiteral("PublicKeyEncryptionOnly"))->getDefault().toBool()); filePrefs.setArchiveCommand(filePrefs.findItem(QStringLiteral("ArchiveCommand"))->getDefault().toString()); ClassifyConfig classifyConfig; classifyConfig.setP7mWithoutExtensionAreEmail(classifyConfig.defaultP7mWithoutExtensionAreEmailValue()); Settings settings; settings.setChecksumDefinitionId(settings.findItem(QStringLiteral("ChecksumDefinitionId"))->getDefault().toString()); load(filePrefs, settings, classifyConfig); } void CryptoOperationsConfigWidget::load(const Kleo::FileOperationsPreferences &filePrefs, const Kleo::Settings &settings, const Kleo::ClassifyConfig &classifyConfig) { mPGPFileExtCB->setChecked(filePrefs.usePGPFileExt()); mPGPFileExtCB->setEnabled(!filePrefs.isImmutable(QStringLiteral("UsePGPFileExt"))); mAutoDecryptVerifyCB->setChecked(filePrefs.autoDecryptVerify()); mAutoDecryptVerifyCB->setEnabled(!filePrefs.isImmutable(QStringLiteral("AutoDecryptVerify"))); mAutoExtractArchivesCB->setChecked(filePrefs.autoExtractArchives()); mAutoExtractArchivesCB->setEnabled(!filePrefs.isImmutable(QStringLiteral("AutoExtractArchives"))); mASCIIArmorCB->setChecked(filePrefs.addASCIIArmor()); mASCIIArmorCB->setEnabled(!filePrefs.isImmutable(QStringLiteral("AddASCIIArmor"))); mTmpDirCB->setChecked(filePrefs.dontUseTmpDir()); mTmpDirCB->setEnabled(!filePrefs.isImmutable(QStringLiteral("DontUseTmpDir"))); mSymmetricOnlyCB->setChecked(filePrefs.symmetricEncryptionOnly()); mSymmetricOnlyCB->setEnabled(!filePrefs.isImmutable(QStringLiteral("SymmetricEncryptionOnly"))); mPublicKeyOnlyCB->setChecked(filePrefs.publicKeyEncryptionOnly()); mPublicKeyOnlyCB->setEnabled(!filePrefs.isPublicKeyEncryptionOnlyImmutable()); mTreatP7mEmailCB->setChecked(classifyConfig.p7mWithoutExtensionAreEmail()); mTreatP7mEmailCB->setEnabled(!classifyConfig.isP7mWithoutExtensionAreEmailImmutable()); const auto defaultChecksumDefinitionId = settings.checksumDefinitionId(); { const auto index = mChecksumDefinitionCB.widget()->findData(defaultChecksumDefinitionId); if (index >= 0) { mChecksumDefinitionCB.widget()->setCurrentIndex(index); } else { qCWarning(KLEOPATRA_LOG) << "No checksum definition found with id" << defaultChecksumDefinitionId; } } mChecksumDefinitionCB.setEnabled(!settings.isImmutable(QStringLiteral("ChecksumDefinitionId"))); const auto ad_default_id = filePrefs.archiveCommand(); { const auto index = mArchiveDefinitionCB.widget()->findData(ad_default_id); if (index >= 0) { mArchiveDefinitionCB.widget()->setCurrentIndex(index); } else { qCWarning(KLEOPATRA_LOG) << "No archive definition found with id" << ad_default_id; } } mArchiveDefinitionCB.setEnabled(!filePrefs.isImmutable(QStringLiteral("ArchiveCommand"))); } void CryptoOperationsConfigWidget::load() { mChecksumDefinitionCB.widget()->clear(); const auto cds = ChecksumDefinition::getChecksumDefinitions(); for (const std::shared_ptr<ChecksumDefinition> &cd : cds) { mChecksumDefinitionCB.widget()->addItem(cd->label(), QVariant{cd->id()}); } // This is a weird hack but because we are a KCM we can't link // against ArchiveDefinition which pulls in loads of other classes. // So we do the parsing which archive definitions exist here ourself. mArchiveDefinitionCB.widget()->clear(); if (KSharedConfigPtr config = KSharedConfig::openConfig(QStringLiteral("libkleopatrarc"))) { const QStringList groups = config->groupList().filter(QRegularExpression(QStringLiteral("^Archive Definition #"))); for (const QString &group : groups) { const KConfigGroup cGroup(config, group); const QString id = cGroup.readEntryUntranslated(QStringLiteral("id")); const QString name = cGroup.readEntry("Name"); mArchiveDefinitionCB.widget()->addItem(name, QVariant(id)); } } load(FileOperationsPreferences{}, Settings{}, ClassifyConfig{}); } void CryptoOperationsConfigWidget::save() { FileOperationsPreferences filePrefs; filePrefs.setUsePGPFileExt(mPGPFileExtCB->isChecked()); filePrefs.setAutoDecryptVerify(mAutoDecryptVerifyCB->isChecked()); filePrefs.setAutoExtractArchives(mAutoExtractArchivesCB->isChecked()); filePrefs.setAddASCIIArmor(mASCIIArmorCB->isChecked()); filePrefs.setDontUseTmpDir(mTmpDirCB->isChecked()); filePrefs.setSymmetricEncryptionOnly(mSymmetricOnlyCB->isChecked()); filePrefs.setPublicKeyEncryptionOnly(mPublicKeyOnlyCB->isChecked()); Settings settings; const int idx = mChecksumDefinitionCB.widget()->currentIndex(); if (idx >= 0) { const auto id = mChecksumDefinitionCB.widget()->itemData(idx).toString(); settings.setChecksumDefinitionId(id); } settings.save(); const int aidx = mArchiveDefinitionCB.widget()->currentIndex(); if (aidx >= 0) { const QString id = mArchiveDefinitionCB.widget()->itemData(aidx).toString(); filePrefs.setArchiveCommand(id); } filePrefs.save(); ClassifyConfig classifyConfig; classifyConfig.setP7mWithoutExtensionAreEmail(mTreatP7mEmailCB->isChecked()); classifyConfig.save(); } #include "moc_cryptooperationsconfigwidget.cpp" diff --git a/src/crypto/gui/signencryptwidget.cpp b/src/crypto/gui/signencryptwidget.cpp index c03f7c819..b66ff939e 100644 --- a/src/crypto/gui/signencryptwidget.cpp +++ b/src/crypto/gui/signencryptwidget.cpp @@ -1,935 +1,935 @@ /* crypto/gui/signencryptwidget.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2016 Bundesamt für Sicherheit in der Informationstechnik SPDX-FileContributor: Intevation GmbH SPDX-License-Identifier: GPL-2.0-or-later */ #include "signencryptwidget.h" #include "kleopatra_debug.h" #include "certificatelineedit.h" #include "fileoperationspreferences.h" #include "kleopatraapplication.h" #include "settings.h" #include "unknownrecipientwidget.h" #include "dialogs/certificateselectiondialog.h" #include "utils/gui-helper.h" #include <QCheckBox> #include <QGroupBox> #include <QHBoxLayout> #include <QLabel> #include <QScrollArea> #include <QScrollBar> #include <QVBoxLayout> #include <Libkleo/Algorithm> #include <Libkleo/Compliance> #include <Libkleo/DefaultKeyFilter> #include <Libkleo/ExpiryChecker> #include <Libkleo/ExpiryCheckerConfig> #include <Libkleo/ExpiryCheckerSettings> #include <Libkleo/Formatting> #include <Libkleo/KeyCache> #include <Libkleo/KeyHelpers> #include <Libkleo/KeyListModel> #include <Libkleo/KeyListSortFilterProxyModel> #include <Libkleo/UserIDSelectionCombo> #include <Libkleo/GnuPG> #include <KConfigGroup> #include <KLocalizedString> #include <KMessageBox> #include <KMessageWidget> #include <KSharedConfig> using namespace Kleo; using namespace Kleo::Dialogs; using namespace GpgME; namespace { class SignCertificateFilter : public DefaultKeyFilter { public: SignCertificateFilter(GpgME::Protocol proto) : DefaultKeyFilter() { setIsBad(DefaultKeyFilter::NotSet); setHasSecret(DefaultKeyFilter::Set); setCanSign(DefaultKeyFilter::Set); setValidIfSMIME(DefaultKeyFilter::Set); if (proto == GpgME::OpenPGP) { setIsOpenPGP(DefaultKeyFilter::Set); } else if (proto == GpgME::CMS) { setIsOpenPGP(DefaultKeyFilter::NotSet); } } }; class EncryptCertificateFilter : public DefaultKeyFilter { public: EncryptCertificateFilter(GpgME::Protocol proto) : DefaultKeyFilter() { setIsBad(DefaultKeyFilter::NotSet); setCanEncrypt(DefaultKeyFilter::Set); setValidIfSMIME(DefaultKeyFilter::Set); if (proto == GpgME::OpenPGP) { setIsOpenPGP(DefaultKeyFilter::Set); } else if (proto == GpgME::CMS) { setIsOpenPGP(DefaultKeyFilter::NotSet); } } }; class EncryptSelfCertificateFilter : public EncryptCertificateFilter { public: EncryptSelfCertificateFilter(GpgME::Protocol proto) : EncryptCertificateFilter(proto) { setRevoked(DefaultKeyFilter::NotSet); setExpired(DefaultKeyFilter::NotSet); setCanEncrypt(DefaultKeyFilter::Set); setHasSecret(DefaultKeyFilter::Set); setValidIfSMIME(DefaultKeyFilter::Set); } }; } class SignEncryptWidget::Private { SignEncryptWidget *const q; public: struct RecipientWidgets { CertificateLineEdit *edit; KMessageWidget *expiryMessage; }; explicit Private(SignEncryptWidget *qq, bool sigEncExclusive) : q{qq} , mModel{AbstractKeyListModel::createFlatKeyListModel(qq)} , mIsExclusive{sigEncExclusive} { } CertificateLineEdit *addRecipientWidget(); /* Inserts a new recipient widget after widget @p after or at the end * if @p after is null. */ CertificateLineEdit *insertRecipientWidget(CertificateLineEdit *after); void recpRemovalRequested(const RecipientWidgets &recipient); void onProtocolChanged(); void updateCheckBoxes(); ExpiryChecker *expiryChecker(); void updateExpiryMessages(KMessageWidget *w, const GpgME::UserID &userID, ExpiryChecker::CheckFlags flags); void updateAllExpiryMessages(); public: UserIDSelectionCombo *mSigSelect = nullptr; KMessageWidget *mSignKeyExpiryMessage = nullptr; UserIDSelectionCombo *mSelfSelect = nullptr; KMessageWidget *mEncryptToSelfKeyExpiryMessage = nullptr; std::vector<RecipientWidgets> mRecpWidgets; QList<UnknownRecipientWidget *> mUnknownWidgets; QList<GpgME::Key> mAddedKeys; QList<KeyGroup> mAddedGroups; QVBoxLayout *mRecpLayout = nullptr; Operations mOp; AbstractKeyListModel *mModel = nullptr; QCheckBox *mSymmetric = nullptr; QCheckBox *mSigChk = nullptr; QLabel *mEncOtherLabel = nullptr; QCheckBox *mEncSelfChk = nullptr; GpgME::Protocol mCurrentProto = GpgME::UnknownProtocol; const bool mIsExclusive; std::unique_ptr<ExpiryChecker> mExpiryChecker; }; SignEncryptWidget::SignEncryptWidget(QWidget *parent, bool sigEncExclusive) : QWidget{parent} , d{new Private{this, sigEncExclusive}} { auto lay = new QVBoxLayout(this); lay->setContentsMargins(0, 0, 0, 0); d->mModel->useKeyCache(true, KeyList::IncludeGroups); const bool haveSecretKeys = !KeyCache::instance()->secretKeys().empty(); const bool havePublicKeys = !KeyCache::instance()->keys().empty(); const bool symmetricOnly = FileOperationsPreferences().symmetricEncryptionOnly(); const bool publicKeyOnly = FileOperationsPreferences().publicKeyEncryptionOnly(); /* The signature selection */ { auto sigGrp = new QGroupBox{i18nc("@title:group", "Prove authenticity (sign)"), this}; d->mSigChk = new QCheckBox{i18n("Sign as:"), this}; d->mSigChk->setEnabled(haveSecretKeys); d->mSigChk->setChecked(haveSecretKeys); d->mSigSelect = new UserIDSelectionCombo{KeyUsage::Sign, this}; d->mSigSelect->setEnabled(d->mSigChk->isChecked()); d->mSignKeyExpiryMessage = new KMessageWidget{this}; d->mSignKeyExpiryMessage->setVisible(false); connect(d->mSigSelect, &UserIDSelectionCombo::certificateSelectionRequested, this, [this]() { ownCertificateSelectionRequested(CertificateSelectionDialog::SignOnly, d->mSigSelect); }); connect(d->mSigSelect, &UserIDSelectionCombo::customItemSelected, this, [this]() { updateOp(); d->updateExpiryMessages(d->mSignKeyExpiryMessage, signUserId(), ExpiryChecker::OwnSigningKey); }); auto groupLayout = new QGridLayout{sigGrp}; groupLayout->setColumnStretch(1, 1); groupLayout->addWidget(d->mSigChk, 0, 0); groupLayout->addWidget(d->mSigSelect, 0, 1); groupLayout->addWidget(d->mSignKeyExpiryMessage, 1, 1); lay->addWidget(sigGrp); connect(d->mSigChk, &QCheckBox::toggled, this, [this](bool checked) { d->mSigSelect->setEnabled(checked); updateOp(); d->updateExpiryMessages(d->mSignKeyExpiryMessage, signUserId(), ExpiryChecker::OwnSigningKey); }); connect(d->mSigSelect, &UserIDSelectionCombo::currentKeyChanged, this, [this]() { updateOp(); d->updateExpiryMessages(d->mSignKeyExpiryMessage, signUserId(), ExpiryChecker::OwnSigningKey); }); } // Recipient selection { auto encBox = new QGroupBox{i18nc("@title:group", "Encrypt"), this}; auto encBoxLay = new QVBoxLayout{encBox}; auto recipientGrid = new QGridLayout; int row = 0; // Own key d->mEncSelfChk = new QCheckBox{i18n("Encrypt for me:"), this}; d->mEncSelfChk->setEnabled(haveSecretKeys && !symmetricOnly); d->mEncSelfChk->setChecked(haveSecretKeys && !symmetricOnly); d->mSelfSelect = new UserIDSelectionCombo{KeyUsage::Encrypt, this}; d->mSelfSelect->setEnabled(d->mEncSelfChk->isChecked()); connect(d->mSelfSelect, &UserIDSelectionCombo::certificateSelectionRequested, this, [this]() { ownCertificateSelectionRequested(CertificateSelectionDialog::EncryptOnly, d->mSelfSelect); }); connect(d->mSelfSelect, &UserIDSelectionCombo::customItemSelected, this, [this]() { updateOp(); d->updateExpiryMessages(d->mEncryptToSelfKeyExpiryMessage, selfUserId(), ExpiryChecker::OwnEncryptionKey); }); d->mEncryptToSelfKeyExpiryMessage = new KMessageWidget{this}; d->mEncryptToSelfKeyExpiryMessage->setVisible(false); recipientGrid->addWidget(d->mEncSelfChk, row, 0); recipientGrid->addWidget(d->mSelfSelect, row, 1); row++; recipientGrid->addWidget(d->mEncryptToSelfKeyExpiryMessage, row, 1); // Checkbox for other keys row++; d->mEncOtherLabel = new QLabel(i18nc("@label", "Encrypt for others:"), this); d->mEncOtherLabel->setEnabled(havePublicKeys && !symmetricOnly); recipientGrid->addWidget(d->mEncOtherLabel, row, 0, Qt::AlignTop | Qt::AlignRight); d->mRecpLayout = new QVBoxLayout; recipientGrid->addLayout(d->mRecpLayout, row, 1); recipientGrid->setRowStretch(row + 1, 1); // Scroll area for other keys auto recipientWidget = new QWidget; auto recipientScroll = new QScrollArea; recipientWidget->setLayout(recipientGrid); recipientScroll->setWidget(recipientWidget); recipientScroll->setWidgetResizable(true); recipientScroll->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContentsOnFirstShow); recipientScroll->setFrameStyle(QFrame::NoFrame); recipientScroll->setFocusPolicy(Qt::NoFocus); recipientGrid->setContentsMargins(0, 0, 0, 0); encBoxLay->addWidget(recipientScroll, 1); auto bar = recipientScroll->verticalScrollBar(); connect(bar, &QScrollBar::rangeChanged, this, [bar](int, int max) { bar->setValue(max); }); d->addRecipientWidget(); // Checkbox for password d->mSymmetric = new QCheckBox(i18n("Encrypt with password. Anyone you share the password with can read the data.")); d->mSymmetric->setToolTip(i18nc("Tooltip information for symmetric encryption", "Additionally to the keys of the recipients you can encrypt your data with a password. " "Anyone who has the password can read the data without any secret key. " "Using a password is <b>less secure</b> then public key cryptography. Even if you pick a very strong password.")); d->mSymmetric->setChecked((symmetricOnly || !havePublicKeys) && !publicKeyOnly); d->mSymmetric->setEnabled(!publicKeyOnly); encBoxLay->addWidget(d->mSymmetric); // Connect it connect(d->mEncSelfChk, &QCheckBox::toggled, this, [this](bool checked) { d->mSelfSelect->setEnabled(checked); updateOp(); d->updateExpiryMessages(d->mEncryptToSelfKeyExpiryMessage, selfUserId(), ExpiryChecker::OwnEncryptionKey); }); connect(d->mSelfSelect, &UserIDSelectionCombo::currentKeyChanged, this, [this]() { updateOp(); d->updateExpiryMessages(d->mEncryptToSelfKeyExpiryMessage, selfUserId(), ExpiryChecker::OwnEncryptionKey); }); connect(d->mSymmetric, &QCheckBox::toggled, this, &SignEncryptWidget::updateOp); if (d->mIsExclusive) { connect(d->mEncSelfChk, &QCheckBox::toggled, this, [this](bool value) { if (d->mCurrentProto != GpgME::CMS) { return; } if (value) { d->mSigChk->setChecked(false); } }); connect(d->mSigChk, &QCheckBox::toggled, this, [this](bool value) { if (d->mCurrentProto != GpgME::CMS) { return; } if (value) { d->mEncSelfChk->setChecked(false); // Copying the vector makes sure that all items are actually deleted. for (const auto &widget : std::vector(d->mRecpWidgets)) { d->recpRemovalRequested(widget); } d->addRecipientWidget(); } }); } // Ensure that the d->mSigChk is aligned together with the encryption check boxes. d->mSigChk->setMinimumWidth(qMax(d->mEncOtherLabel->width(), d->mEncSelfChk->width())); lay->addWidget(encBox); } connect(KeyCache::instance().get(), &Kleo::KeyCache::keysMayHaveChanged, this, [this]() { d->updateCheckBoxes(); d->updateAllExpiryMessages(); }); connect(KleopatraApplication::instance(), &KleopatraApplication::configurationChanged, this, [this]() { d->updateCheckBoxes(); d->mExpiryChecker.reset(); d->updateAllExpiryMessages(); }); loadKeys(); d->onProtocolChanged(); updateOp(); } SignEncryptWidget::~SignEncryptWidget() = default; void SignEncryptWidget::setSignAsText(const QString &text) { d->mSigChk->setText(text); } void SignEncryptWidget::setEncryptForMeText(const QString &text) { d->mEncSelfChk->setText(text); } void SignEncryptWidget::setEncryptWithPasswordText(const QString &text) { d->mSymmetric->setText(text); } CertificateLineEdit *SignEncryptWidget::Private::addRecipientWidget() { return insertRecipientWidget(nullptr); } CertificateLineEdit *SignEncryptWidget::Private::insertRecipientWidget(CertificateLineEdit *after) { Q_ASSERT(!after || mRecpLayout->indexOf(after) != -1); const auto index = after ? mRecpLayout->indexOf(after) + 2 : mRecpLayout->count(); const RecipientWidgets recipient{new CertificateLineEdit{mModel, KeyUsage::Encrypt, new EncryptCertificateFilter{mCurrentProto}, q}, new KMessageWidget{q}}; recipient.edit->setAccessibleNameOfLineEdit(i18nc("text for screen readers", "recipient key")); recipient.edit->setEnabled(!KeyCache::instance()->keys().empty() && !FileOperationsPreferences().symmetricEncryptionOnly()); recipient.expiryMessage->setVisible(false); if (static_cast<unsigned>(index / 2) < mRecpWidgets.size()) { mRecpWidgets.insert(mRecpWidgets.begin() + index / 2, recipient); } else { mRecpWidgets.push_back(recipient); } if (mRecpLayout->count() > 0) { auto prevWidget = after ? after : mRecpLayout->itemAt(mRecpLayout->count() - 1)->widget(); Kleo::forceSetTabOrder(prevWidget, recipient.edit); Kleo::forceSetTabOrder(recipient.edit, recipient.expiryMessage); } mRecpLayout->insertWidget(index, recipient.edit); mRecpLayout->insertWidget(index + 1, recipient.expiryMessage); connect(recipient.edit, &CertificateLineEdit::keyChanged, q, &SignEncryptWidget::recipientsChanged); connect(recipient.edit, &CertificateLineEdit::editingStarted, q, &SignEncryptWidget::recipientsChanged); connect(recipient.edit, &CertificateLineEdit::cleared, q, &SignEncryptWidget::recipientsChanged); connect(recipient.edit, &CertificateLineEdit::certificateSelectionRequested, q, [this, recipient]() { q->certificateSelectionRequested(recipient.edit); }); if (mIsExclusive) { connect(recipient.edit, &CertificateLineEdit::keyChanged, q, [this]() { if (mCurrentProto != GpgME::CMS) { return; } mSigChk->setChecked(false); }); } return recipient.edit; } void SignEncryptWidget::addRecipient(const Key &key) { CertificateLineEdit *certSel = d->addRecipientWidget(); if (!key.isNull()) { certSel->setKey(key); d->mAddedKeys << key; } } void SignEncryptWidget::addRecipient(const KeyGroup &group) { CertificateLineEdit *certSel = d->addRecipientWidget(); if (!group.isNull()) { certSel->setGroup(group); d->mAddedGroups << group; } } void SignEncryptWidget::ownCertificateSelectionRequested(CertificateSelectionDialog::Options options, UserIDSelectionCombo *combo) { CertificateSelectionDialog dialog{this}; dialog.setOptions(CertificateSelectionDialog::Options( // CertificateSelectionDialog::SingleSelection | // CertificateSelectionDialog::SecretKeys | // CertificateSelectionDialog::optionsFromProtocol(d->mCurrentProto)) | // options); if (dialog.exec()) { auto userId = dialog.selectedUserIDs()[0]; auto index = combo->findUserId(userId); if (index == -1) { combo->appendCustomItem(QIcon::fromTheme(QStringLiteral("emblem-error")), Formatting::summaryLine(userId), QVariant::fromValue(userId)); index = combo->combo()->count() - 1; } combo->combo()->setCurrentIndex(index); } recipientsChanged(); } void SignEncryptWidget::certificateSelectionRequested(CertificateLineEdit *certificateLineEdit) { CertificateSelectionDialog dlg{this}; dlg.setOptions(CertificateSelectionDialog::Options( // CertificateSelectionDialog::MultiSelection | // CertificateSelectionDialog::EncryptOnly | // CertificateSelectionDialog::optionsFromProtocol(d->mCurrentProto) | // CertificateSelectionDialog::IncludeGroups)); if (!certificateLineEdit->key().isNull()) { const auto key = certificateLineEdit->key(); const auto name = QString::fromUtf8(key.userID(0).name()); const auto email = QString::fromUtf8(key.userID(0).email()); dlg.setStringFilter(!name.isEmpty() ? name : email); } else if (!certificateLineEdit->group().isNull()) { dlg.setStringFilter(certificateLineEdit->group().name()); } else if (!certificateLineEdit->userID().isNull()) { const auto userID = certificateLineEdit->userID(); const auto name = QString::fromUtf8(userID.name()); const auto email = QString::fromUtf8(userID.email()); dlg.setStringFilter(!name.isEmpty() ? name : email); } else { dlg.setStringFilter(certificateLineEdit->text()); } if (dlg.exec()) { const std::vector<UserID> userIds = dlg.selectedUserIDs(); const std::vector<KeyGroup> groups = dlg.selectedGroups(); if (userIds.size() == 0 && groups.size() == 0) { return; } CertificateLineEdit *certWidget = nullptr; for (const auto &userId : userIds) { if (!certWidget) { certWidget = certificateLineEdit; } else { certWidget = d->insertRecipientWidget(certWidget); } certWidget->setUserID(userId); } for (const KeyGroup &group : groups) { if (!certWidget) { certWidget = certificateLineEdit; } else { certWidget = d->insertRecipientWidget(certWidget); } certWidget->setGroup(group); } } recipientsChanged(); } void SignEncryptWidget::clearAddedRecipients() { for (auto w : std::as_const(d->mUnknownWidgets)) { d->mRecpLayout->removeWidget(w); delete w; } for (auto &key : std::as_const(d->mAddedKeys)) { removeRecipient(key); } for (auto &group : std::as_const(d->mAddedGroups)) { removeRecipient(group); } } void SignEncryptWidget::addUnknownRecipient(const char *keyID) { auto unknownWidget = new UnknownRecipientWidget(keyID); d->mUnknownWidgets << unknownWidget; if (d->mRecpLayout->count() > 0) { auto lastWidget = d->mRecpLayout->itemAt(d->mRecpLayout->count() - 1)->widget(); setTabOrder(lastWidget, unknownWidget); } d->mRecpLayout->addWidget(unknownWidget); connect(KeyCache::instance().get(), &Kleo::KeyCache::keysMayHaveChanged, this, [this]() { // Check if any unknown recipient can now be found. - for (auto w : d->mUnknownWidgets) { + for (auto w : std::as_const(d->mUnknownWidgets)) { auto key = KeyCache::instance()->findByKeyIDOrFingerprint(w->keyID().toLatin1().constData()); if (key.isNull()) { std::vector<std::string> subids; subids.push_back(std::string(w->keyID().toLatin1().constData())); for (const auto &subkey : KeyCache::instance()->findSubkeysByKeyID(subids)) { key = subkey.parent(); } } if (key.isNull()) { continue; } // Key is now available replace by line edit. qCDebug(KLEOPATRA_LOG) << "Removing widget for keyid: " << w->keyID(); d->mRecpLayout->removeWidget(w); d->mUnknownWidgets.removeAll(w); delete w; addRecipient(key); } }); } void SignEncryptWidget::recipientsChanged() { const bool hasEmptyRecpWidget = std::any_of(std::cbegin(d->mRecpWidgets), std::cend(d->mRecpWidgets), [](auto w) { return w.edit->isEmpty(); }); if (!hasEmptyRecpWidget) { d->addRecipientWidget(); } updateOp(); for (const auto &recipient : std::as_const(d->mRecpWidgets)) { if (!recipient.edit->isEditingInProgress() || recipient.edit->isEmpty()) { d->updateExpiryMessages(recipient.expiryMessage, recipient.edit->userID(), ExpiryChecker::EncryptionKey); } } } UserID SignEncryptWidget::signUserId() const { if (d->mSigSelect->isEnabled()) { return d->mSigSelect->currentUserID(); } return UserID(); } UserID SignEncryptWidget::selfUserId() const { if (d->mSelfSelect->isEnabled()) { return d->mSelfSelect->currentUserID(); } return UserID(); } std::vector<Key> SignEncryptWidget::recipients() const { std::vector<Key> ret; for (const auto &recipient : std::as_const(d->mRecpWidgets)) { const auto *const w = recipient.edit; if (!w->isEnabled()) { // If one is disabled, all are disabled. break; } const Key k = w->key(); const KeyGroup g = w->group(); const UserID u = w->userID(); if (!k.isNull()) { ret.push_back(k); } else if (!g.isNull()) { const auto keys = g.keys(); std::copy(keys.begin(), keys.end(), std::back_inserter(ret)); } else if (!u.isNull()) { ret.push_back(u.parent()); } } const Key k = selfUserId().parent(); if (!k.isNull()) { ret.push_back(k); } return ret; } bool SignEncryptWidget::isDeVsAndValid() const { if (!signUserId().isNull() && !DeVSCompliance::userIDIsCompliant(signUserId())) { return false; } if (!selfUserId().isNull() && !DeVSCompliance::userIDIsCompliant(selfUserId())) { return false; } for (const auto &key : recipients()) { if (!DeVSCompliance::keyIsCompliant(key)) { return false; } } return true; } static QString expiryMessage(const ExpiryChecker::Result &result) { if (result.expiration.certificate.isNull()) { return {}; } switch (result.expiration.status) { case ExpiryChecker::Expired: return i18nc("@info", "This certificate is expired."); case ExpiryChecker::ExpiresSoon: { if (result.expiration.duration.count() == 0) { return i18nc("@info", "This certificate expires today."); } else { return i18ncp("@info", "This certificate expires tomorrow.", "This certificate expires in %1 days.", result.expiration.duration.count()); } } case ExpiryChecker::NoSuitableSubkey: if (result.checkFlags & ExpiryChecker::EncryptionKey) { return i18nc("@info", "This certificate cannot be used for encryption."); } else { return i18nc("@info", "This certificate cannot be used for signing."); } case ExpiryChecker::InvalidKey: case ExpiryChecker::InvalidCheckFlags: break; // wrong usage of ExpiryChecker; can be ignored case ExpiryChecker::NotNearExpiry:; } return {}; } void SignEncryptWidget::updateOp() { const std::vector<Key> recp = recipients(); Operations op = NoOperation; if (!signUserId().isNull()) { op |= Sign; } if (!recp.empty() || encryptSymmetric()) { op |= Encrypt; } d->mOp = op; Q_EMIT operationChanged(d->mOp); Q_EMIT keysChanged(); } SignEncryptWidget::Operations SignEncryptWidget::currentOp() const { return d->mOp; } namespace { bool recipientWidgetHasFocus(QWidget *w) { // check if w (or its focus proxy) or a child widget of w has focus return w->hasFocus() || w->isAncestorOf(qApp->focusWidget()); } } void SignEncryptWidget::Private::recpRemovalRequested(const RecipientWidgets &recipient) { if (!recipient.edit) { return; } if (recipientWidgetHasFocus(recipient.edit) || recipientWidgetHasFocus(recipient.expiryMessage)) { const int index = mRecpLayout->indexOf(recipient.edit); const auto focusWidget = (index < mRecpLayout->count() - 2) ? // mRecpLayout->itemAt(index + 2)->widget() : mRecpLayout->itemAt(mRecpLayout->count() - 3)->widget(); focusWidget->setFocus(); } mRecpLayout->removeWidget(recipient.expiryMessage); mRecpLayout->removeWidget(recipient.edit); const auto it = std::find_if(std::begin(mRecpWidgets), std::end(mRecpWidgets), [recipient](const auto &r) { return r.edit == recipient.edit; }); mRecpWidgets.erase(it); recipient.expiryMessage->deleteLater(); recipient.edit->deleteLater(); } void SignEncryptWidget::removeRecipient(const GpgME::Key &key) { for (const auto &recipient : std::as_const(d->mRecpWidgets)) { const auto editKey = recipient.edit->key(); if (key.isNull() && editKey.isNull()) { d->recpRemovalRequested(recipient); return; } if (editKey.primaryFingerprint() && key.primaryFingerprint() && !strcmp(editKey.primaryFingerprint(), key.primaryFingerprint())) { d->recpRemovalRequested(recipient); return; } } } void SignEncryptWidget::removeRecipient(const KeyGroup &group) { for (const auto &recipient : std::as_const(d->mRecpWidgets)) { const auto editGroup = recipient.edit->group(); if (group.isNull() && editGroup.isNull()) { d->recpRemovalRequested(recipient); return; } if (editGroup.name() == group.name()) { d->recpRemovalRequested(recipient); return; } } } bool SignEncryptWidget::encryptSymmetric() const { return d->mSymmetric->isChecked(); } void SignEncryptWidget::loadKeys() { KConfigGroup keys(KSharedConfig::openConfig(), QStringLiteral("SignEncryptKeys")); auto cache = KeyCache::instance(); d->mSigSelect->setDefaultKey(keys.readEntry("SigningKey", QString())); d->mSelfSelect->setDefaultKey(keys.readEntry("EncryptKey", QString())); } void SignEncryptWidget::saveOwnKeys() const { KConfigGroup keys(KSharedConfig::openConfig(), QStringLiteral("SignEncryptKeys")); auto sigKey = d->mSigSelect->currentKey(); auto encKey = d->mSelfSelect->currentKey(); if (!sigKey.isNull()) { keys.writeEntry("SigningKey", sigKey.primaryFingerprint()); } if (!encKey.isNull()) { keys.writeEntry("EncryptKey", encKey.primaryFingerprint()); } } void SignEncryptWidget::setSigningChecked(bool value) { d->mSigChk->setChecked(value && !KeyCache::instance()->secretKeys().empty()); } void SignEncryptWidget::setEncryptionChecked(bool checked) { if (checked) { const bool haveSecretKeys = !KeyCache::instance()->secretKeys().empty(); const bool havePublicKeys = !KeyCache::instance()->keys().empty(); const bool symmetricOnly = FileOperationsPreferences().symmetricEncryptionOnly(); const bool publicKeyOnly = FileOperationsPreferences().publicKeyEncryptionOnly(); d->mEncSelfChk->setChecked(haveSecretKeys && !symmetricOnly); d->mSymmetric->setChecked((symmetricOnly || !havePublicKeys) && !publicKeyOnly); } else { d->mEncSelfChk->setChecked(false); d->mSymmetric->setChecked(false); } } void SignEncryptWidget::setProtocol(GpgME::Protocol proto) { if (d->mCurrentProto == proto) { return; } d->mCurrentProto = proto; d->onProtocolChanged(); } void Kleo::SignEncryptWidget::Private::onProtocolChanged() { auto encryptForOthers = q->recipients().size() > 0; mSigSelect->setKeyFilter(std::shared_ptr<KeyFilter>(new SignCertificateFilter(mCurrentProto))); mSelfSelect->setKeyFilter(std::shared_ptr<KeyFilter>(new EncryptSelfCertificateFilter(mCurrentProto))); const auto encFilter = std::shared_ptr<KeyFilter>(new EncryptCertificateFilter(mCurrentProto)); for (const auto &widget : std::vector(mRecpWidgets)) { recpRemovalRequested(widget); } addRecipientWidget(); for (const auto &recipient : std::as_const(mRecpWidgets)) { recipient.edit->setKeyFilter(encFilter); } if (mIsExclusive) { mSymmetric->setDisabled(mCurrentProto == GpgME::CMS); if (mSymmetric->isChecked() && mCurrentProto == GpgME::CMS) { mSymmetric->setChecked(false); } if (mSigChk->isChecked() && mCurrentProto == GpgME::CMS && (mEncSelfChk->isChecked() || encryptForOthers)) { mSigChk->setChecked(false); } } } static bool recipientIsOkay(const CertificateLineEdit *edit) { if (!edit->isEnabled() || edit->isEmpty()) { return true; } if (!edit->hasAcceptableInput()) { return false; } if (const auto userID = edit->userID(); !userID.isNull()) { return Kleo::canBeUsedForEncryption(userID.parent()) && !userID.isBad(); } if (const auto key = edit->key(); !key.isNull()) { return Kleo::canBeUsedForEncryption(key); } if (const auto group = edit->group(); !group.isNull()) { return std::ranges::all_of(group.keys(), Kleo::canBeUsedForEncryption); } // we should never reach this point return false; } bool SignEncryptWidget::isComplete() const { if (currentOp() == NoOperation) { return false; } if ((currentOp() & SignEncryptWidget::Sign) && !Kleo::canBeUsedForSigning(signUserId().parent())) { return false; } if (currentOp() & SignEncryptWidget::Encrypt) { if (!selfUserId().isNull() && !Kleo::canBeUsedForEncryption(selfUserId().parent())) { return false; } const bool allOtherRecipientsAreOkay = std::ranges::all_of(d->mRecpWidgets, [](const auto &r) { return recipientIsOkay(r.edit); }); if (!allOtherRecipientsAreOkay) { return false; } } return true; } bool SignEncryptWidget::validate() { CertificateLineEdit *firstUnresolvedRecipient = nullptr; QStringList unresolvedRecipients; for (const auto &recipient : std::as_const(d->mRecpWidgets)) { if (recipient.edit->isEnabled() && !recipient.edit->hasAcceptableInput()) { if (!firstUnresolvedRecipient) { firstUnresolvedRecipient = recipient.edit; } unresolvedRecipients.push_back(recipient.edit->text().toHtmlEscaped()); } } if (!unresolvedRecipients.isEmpty()) { KMessageBox::errorList(this, i18n("Could not find a key for the following recipients:"), unresolvedRecipients, i18nc("@title:window", "Failed to find some keys")); } if (firstUnresolvedRecipient) { firstUnresolvedRecipient->setFocus(); } return unresolvedRecipients.isEmpty(); } void SignEncryptWidget::Private::updateCheckBoxes() { const bool haveSecretKeys = !KeyCache::instance()->secretKeys().empty(); const bool symmetricOnly = FileOperationsPreferences().symmetricEncryptionOnly(); mSigChk->setEnabled(haveSecretKeys); mEncSelfChk->setEnabled(haveSecretKeys && !symmetricOnly); if (symmetricOnly) { mEncSelfChk->setChecked(false); mSymmetric->setChecked(true); } } ExpiryChecker *Kleo::SignEncryptWidget::Private::expiryChecker() { if (!mExpiryChecker) { mExpiryChecker.reset(new ExpiryChecker{ExpiryCheckerConfig{}.settings()}); } return mExpiryChecker.get(); } void SignEncryptWidget::Private::updateExpiryMessages(KMessageWidget *messageWidget, const GpgME::UserID &userID, ExpiryChecker::CheckFlags flags) { messageWidget->setCloseButtonVisible(false); if (userID.isNull()) { messageWidget->setVisible(false); } else if (userID.parent().isExpired()) { messageWidget->setText(i18nc("@info", "This certificate is expired.")); messageWidget->setVisible(true); } else if (userID.isRevoked() || userID.parent().isRevoked()) { messageWidget->setText(i18nc("@info", "This certificate is revoked.")); messageWidget->setVisible(true); } else if (Settings{}.showExpiryNotifications()) { const auto result = expiryChecker()->checkKey(userID.parent(), flags); const auto message = expiryMessage(result); messageWidget->setText(message); messageWidget->setVisible(!message.isEmpty()); } else { messageWidget->setVisible(false); } } void SignEncryptWidget::Private::updateAllExpiryMessages() { updateExpiryMessages(mSignKeyExpiryMessage, q->signUserId(), ExpiryChecker::OwnSigningKey); updateExpiryMessages(mEncryptToSelfKeyExpiryMessage, q->selfUserId(), ExpiryChecker::OwnEncryptionKey); for (const auto &recipient : std::as_const(mRecpWidgets)) { if (recipient.edit->isEnabled()) { updateExpiryMessages(recipient.expiryMessage, recipient.edit->userID(), ExpiryChecker::EncryptionKey); } } } #include "moc_signencryptwidget.cpp" diff --git a/src/crypto/signencryptfilescontroller.cpp b/src/crypto/signencryptfilescontroller.cpp index d7881f147..fc7abfb1f 100644 --- a/src/crypto/signencryptfilescontroller.cpp +++ b/src/crypto/signencryptfilescontroller.cpp @@ -1,814 +1,814 @@ /* -*- mode: c++; c-basic-offset:4 -*- crypto/signencryptfilescontroller.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2017 Bundesamt für Sicherheit in der Informationstechnik SPDX-FileContributor: Intevation GmbH SPDX-License-Identifier: GPL-2.0-or-later */ #include <config-kleopatra.h> #include "signencryptfilescontroller.h" #include "signencrypttask.h" #include "crypto/gui/signencryptfileswizard.h" #include "crypto/taskcollection.h" #include "fileoperationspreferences.h" #include "utils/archivedefinition.h" #include "utils/input.h" #include "utils/kleo_assert.h" #include "utils/output.h" #include "utils/path-helper.h" #include <Libkleo/Classify> #include <Libkleo/KleoException> #include "kleopatra_debug.h" #include <KLocalizedString> #include <QGpgME/SignEncryptArchiveJob> #include <QDir> #include <QFileInfo> #include <QPointer> #include <QTimer> using namespace Kleo; using namespace Kleo::Crypto; using namespace GpgME; class SignEncryptFilesController::Private { friend class ::Kleo::Crypto::SignEncryptFilesController; SignEncryptFilesController *const q; public: explicit Private(SignEncryptFilesController *qq); ~Private(); private: void slotWizardOperationPrepared(); void slotWizardCanceled(); private: void ensureWizardCreated(); void ensureWizardVisible(); void updateWizardMode(); void cancelAllTasks(); void reportError(int err, const QString &details) { q->setLastError(err, details); q->emitDoneOrError(); } void schedule(); std::shared_ptr<SignEncryptTask> takeRunnable(GpgME::Protocol proto); static void assertValidOperation(unsigned int); static QString titleForOperation(unsigned int op); private: std::vector<std::shared_ptr<SignEncryptTask>> runnable, completed; std::shared_ptr<SignEncryptTask> cms, openpgp; QPointer<SignEncryptFilesWizard> wizard; QStringList files; unsigned int operation; Protocol protocol; }; SignEncryptFilesController::Private::Private(SignEncryptFilesController *qq) : q(qq) , runnable() , cms() , openpgp() , wizard() , files() , operation(SignAllowed | EncryptAllowed | ArchiveAllowed) , protocol(UnknownProtocol) { } SignEncryptFilesController::Private::~Private() { qCDebug(KLEOPATRA_LOG) << q << __func__; } QString SignEncryptFilesController::Private::titleForOperation(unsigned int op) { const bool signDisallowed = (op & SignMask) == SignDisallowed; const bool encryptDisallowed = (op & EncryptMask) == EncryptDisallowed; const bool archiveSelected = (op & ArchiveMask) == ArchiveForced; kleo_assert(!signDisallowed || !encryptDisallowed); if (!signDisallowed && encryptDisallowed) { if (archiveSelected) { return i18n("Archive and Sign Files"); } else { return i18n("Sign Files"); } } if (signDisallowed && !encryptDisallowed) { if (archiveSelected) { return i18n("Archive and Encrypt Files"); } else { return i18n("Encrypt Files"); } } if (archiveSelected) { return i18n("Archive and Sign/Encrypt Files"); } else { return i18n("Sign/Encrypt Files"); } } SignEncryptFilesController::SignEncryptFilesController(QObject *p) : Controller(p) , d(new Private(this)) { } SignEncryptFilesController::SignEncryptFilesController(const std::shared_ptr<const ExecutionContext> &ctx, QObject *p) : Controller(ctx, p) , d(new Private(this)) { } SignEncryptFilesController::~SignEncryptFilesController() { qCDebug(KLEOPATRA_LOG) << this << __func__; if (d->wizard && !d->wizard->isVisible()) { delete d->wizard; } // d->wizard->close(); ### ? } void SignEncryptFilesController::setProtocol(Protocol proto) { kleo_assert(d->protocol == UnknownProtocol || d->protocol == proto); d->protocol = proto; d->ensureWizardCreated(); } Protocol SignEncryptFilesController::protocol() const { return d->protocol; } // static void SignEncryptFilesController::Private::assertValidOperation(unsigned int op) { kleo_assert((op & SignMask) == SignDisallowed || // (op & SignMask) == SignAllowed || // (op & SignMask) == SignSelected); kleo_assert((op & EncryptMask) == EncryptDisallowed || // (op & EncryptMask) == EncryptAllowed || // (op & EncryptMask) == EncryptSelected); kleo_assert((op & ArchiveMask) == ArchiveDisallowed || // (op & ArchiveMask) == ArchiveAllowed || // (op & ArchiveMask) == ArchiveForced); kleo_assert((op & ~(SignMask | EncryptMask | ArchiveMask)) == 0); } void SignEncryptFilesController::setOperationMode(unsigned int mode) { Private::assertValidOperation(mode); d->operation = mode; d->updateWizardMode(); } void SignEncryptFilesController::Private::updateWizardMode() { if (!wizard) { return; } wizard->setWindowTitle(titleForOperation(operation)); const unsigned int signOp = (operation & SignMask); const unsigned int encrOp = (operation & EncryptMask); const unsigned int archOp = (operation & ArchiveMask); if (signOp == SignDisallowed) { wizard->setSigningUserMutable(false); wizard->setSigningPreset(false); } else { wizard->setSigningUserMutable(true); wizard->setSigningPreset(signOp == SignSelected); } if (encrOp == EncryptDisallowed) { wizard->setEncryptionPreset(false); wizard->setEncryptionUserMutable(false); } else { wizard->setEncryptionUserMutable(true); wizard->setEncryptionPreset(encrOp == EncryptSelected); } wizard->setArchiveForced(archOp == ArchiveForced); wizard->setArchiveMutable(archOp == ArchiveAllowed); } unsigned int SignEncryptFilesController::operationMode() const { return d->operation; } static QString extension(bool pgp, bool sign, bool encrypt, bool ascii, bool detached) { unsigned int cls = pgp ? Class::OpenPGP : Class::CMS; if (encrypt) { cls |= Class::CipherText; } else if (sign) { cls |= detached ? Class::DetachedSignature : Class::OpaqueSignature; } cls |= ascii ? Class::Ascii : Class::Binary; const bool usePGPFileExt = FileOperationsPreferences().usePGPFileExt(); const auto ext = outputFileExtension(cls, usePGPFileExt); if (!ext.isEmpty()) { return ext; } else { return QStringLiteral("out"); } } static std::shared_ptr<ArchiveDefinition> getDefaultAd() { const std::vector<std::shared_ptr<ArchiveDefinition>> ads = ArchiveDefinition::getArchiveDefinitions(); Q_ASSERT(!ads.empty()); std::shared_ptr<ArchiveDefinition> ad = ads.front(); const FileOperationsPreferences prefs; const QString archiveCmd = prefs.archiveCommand(); auto it = std::find_if(ads.cbegin(), ads.cend(), [&archiveCmd](const std::shared_ptr<ArchiveDefinition> &toCheck) { return toCheck->id() == archiveCmd; }); if (it != ads.cend()) { ad = *it; } return ad; } static QMap<int, QString> buildOutputNames(const QStringList &files, const bool archive) { QMap<int, QString> nameMap; // Build the default names for the wizard. QString baseNameCms; QString baseNamePgp; const QFileInfo firstFile(files.first()); if (archive) { QString baseName = files.size() > 1 ? i18nc("base name of an archive file, e.g. archive.zip or archive.tar.gz", "archive") : firstFile.baseName(); baseName = QDir(heuristicBaseDirectory(files)).absoluteFilePath(baseName); const auto ad = getDefaultAd(); baseNamePgp = baseName + QLatin1Char('.') + ad->extensions(GpgME::OpenPGP).first() + QLatin1Char('.'); baseNameCms = baseName + QLatin1Char('.') + ad->extensions(GpgME::CMS).first() + QLatin1Char('.'); } else { baseNameCms = baseNamePgp = files.first() + QLatin1Char('.'); } const FileOperationsPreferences prefs; const bool ascii = prefs.addASCIIArmor(); nameMap.insert(SignEncryptFilesWizard::SignatureCMS, baseNameCms + extension(false, true, false, ascii, true)); nameMap.insert(SignEncryptFilesWizard::EncryptedCMS, baseNameCms + extension(false, false, true, ascii, false)); nameMap.insert(SignEncryptFilesWizard::CombinedPGP, baseNamePgp + extension(true, true, true, ascii, false)); nameMap.insert(SignEncryptFilesWizard::EncryptedPGP, baseNamePgp + extension(true, false, true, ascii, false)); nameMap.insert(SignEncryptFilesWizard::SignaturePGP, baseNamePgp + extension(true, true, false, ascii, true)); nameMap.insert(SignEncryptFilesWizard::Directory, heuristicBaseDirectory(files)); return nameMap; } static QMap<int, QString> buildOutputNamesForDir(const QString &file, const QMap<int, QString> &orig) { QMap<int, QString> ret; const QString dir = orig.value(SignEncryptFilesWizard::Directory); if (dir.isEmpty()) { return orig; } // Build the default names for the wizard. const QFileInfo fi(file); const QString baseName = dir + QLatin1Char('/') + fi.fileName() + QLatin1Char('.'); const FileOperationsPreferences prefs; const bool ascii = prefs.addASCIIArmor(); ret.insert(SignEncryptFilesWizard::SignatureCMS, baseName + extension(false, true, false, ascii, true)); ret.insert(SignEncryptFilesWizard::EncryptedCMS, baseName + extension(false, false, true, ascii, false)); ret.insert(SignEncryptFilesWizard::CombinedPGP, baseName + extension(true, true, true, ascii, false)); ret.insert(SignEncryptFilesWizard::EncryptedPGP, baseName + extension(true, false, true, ascii, false)); ret.insert(SignEncryptFilesWizard::SignaturePGP, baseName + extension(true, true, false, ascii, true)); return ret; } // strips all trailing slashes from the filename, but keeps filename "/" static QString stripTrailingSlashes(const QString &fileName) { if (fileName.size() < 2 || !fileName.endsWith(QLatin1Char('/'))) { return fileName; } auto tmp = QStringView{fileName}.chopped(1); while (tmp.size() > 1 && tmp.endsWith(QLatin1Char('/'))) { tmp.chop(1); } return tmp.toString(); } static QStringList stripTrailingSlashesForAll(const QStringList &fileNames) { QStringList result; result.reserve(fileNames.size()); std::transform(fileNames.begin(), fileNames.end(), std::back_inserter(result), &stripTrailingSlashes); return result; } void SignEncryptFilesController::setFiles(const QStringList &files) { kleo_assert(!files.empty()); d->files = stripTrailingSlashesForAll(files); bool archive = false; if (d->files.size() > 1) { setOperationMode((operationMode() & ~ArchiveMask) | ArchiveAllowed); archive = true; } - for (const auto &file : d->files) { + for (const auto &file : std::as_const(d->files)) { if (QFileInfo(file).isDir()) { setOperationMode((operationMode() & ~ArchiveMask) | ArchiveForced); archive = true; break; } } d->ensureWizardCreated(); d->wizard->setSingleFile(!archive); d->wizard->setOutputNames(buildOutputNames(d->files, archive)); } void SignEncryptFilesController::Private::slotWizardCanceled() { qCDebug(KLEOPATRA_LOG) << q << __func__; q->cancel(); reportError(gpg_error(GPG_ERR_CANCELED), i18n("User cancel")); } void SignEncryptFilesController::start() { d->ensureWizardVisible(); } static std::shared_ptr<SignEncryptTask> createSignEncryptTaskForFileInfo(const QFileInfo &fi, bool ascii, const std::vector<Key> &recipients, const std::vector<Key> &signers, const QString &outputName, bool symmetric) { const std::shared_ptr<SignEncryptTask> task(new SignEncryptTask); Q_ASSERT(!signers.empty() || !recipients.empty() || symmetric); task->setAsciiArmor(ascii); if (!signers.empty()) { task->setSign(true); task->setSigners(signers); task->setDetachedSignature(true); } else { task->setSign(false); } if (!recipients.empty()) { task->setEncrypt(true); task->setRecipients(recipients); task->setDetachedSignature(false); } else { task->setEncrypt(false); } task->setEncryptSymmetric(symmetric); const QString input = fi.absoluteFilePath(); task->setInputFileName(input); #if QGPGME_FILE_JOBS_SUPPORT_DIRECT_FILE_IO if (task->protocol() != GpgME::OpenPGP) { task->setInput(Input::createFromFile(input)); } #else task->setInput(Input::createFromFile(input)); #endif task->setOutputFileName(outputName); return task; } static bool archiveJobsCanBeUsed([[maybe_unused]] GpgME::Protocol protocol) { return (protocol == GpgME::OpenPGP) && QGpgME::SignEncryptArchiveJob::isSupported(); } static std::shared_ptr<SignEncryptTask> createArchiveSignEncryptTaskForFiles(const QStringList &files, const std::shared_ptr<ArchiveDefinition> &ad, bool pgp, bool ascii, const std::vector<Key> &recipients, const std::vector<Key> &signers, const QString &outputName, bool symmetric) { const std::shared_ptr<SignEncryptTask> task(new SignEncryptTask); task->setCreateArchive(true); task->setEncryptSymmetric(symmetric); Q_ASSERT(!signers.empty() || !recipients.empty() || symmetric); task->setAsciiArmor(ascii); if (!signers.empty()) { task->setSign(true); task->setSigners(signers); task->setDetachedSignature(false); } else { task->setSign(false); } if (!recipients.empty()) { task->setEncrypt(true); task->setRecipients(recipients); } else { task->setEncrypt(false); } const Protocol proto = pgp ? OpenPGP : CMS; task->setInputFileNames(files); if (!archiveJobsCanBeUsed(proto)) { // use legacy archive creation kleo_assert(ad); task->setInput(ad->createInputFromPackCommand(proto, files)); } task->setOutputFileName(outputName); return task; } static std::vector<std::shared_ptr<SignEncryptTask>> createSignEncryptTasksForFileInfo(const QFileInfo &fi, bool ascii, const std::vector<Key> &pgpRecipients, const std::vector<Key> &pgpSigners, const std::vector<Key> &cmsRecipients, const std::vector<Key> &cmsSigners, const QMap<int, QString> &outputNames, bool symmetric) { std::vector<std::shared_ptr<SignEncryptTask>> result; const bool pgp = !pgpSigners.empty() || !pgpRecipients.empty(); const bool cms = !cmsSigners.empty() || !cmsRecipients.empty(); result.reserve(pgp + cms); if (pgp || symmetric) { // Symmetric encryption is only supported for PGP int outKind = 0; if ((!pgpRecipients.empty() || symmetric) && !pgpSigners.empty()) { outKind = SignEncryptFilesWizard::CombinedPGP; } else if (!pgpRecipients.empty() || symmetric) { outKind = SignEncryptFilesWizard::EncryptedPGP; } else { outKind = SignEncryptFilesWizard::SignaturePGP; } result.push_back(createSignEncryptTaskForFileInfo(fi, ascii, pgpRecipients, pgpSigners, outputNames[outKind], symmetric)); } if (cms) { // There is no combined sign / encrypt in gpgsm so we create one sign task // and one encrypt task. Which leaves us with the age old dilemma, encrypt // then sign, or sign then encrypt. Ugly. if (!cmsSigners.empty()) { result.push_back( createSignEncryptTaskForFileInfo(fi, ascii, std::vector<Key>(), cmsSigners, outputNames[SignEncryptFilesWizard::SignatureCMS], false)); } if (!cmsRecipients.empty()) { result.push_back( createSignEncryptTaskForFileInfo(fi, ascii, cmsRecipients, std::vector<Key>(), outputNames[SignEncryptFilesWizard::EncryptedCMS], false)); } } return result; } static std::vector<std::shared_ptr<SignEncryptTask>> createArchiveSignEncryptTasksForFiles(const QStringList &files, const std::shared_ptr<ArchiveDefinition> &ad, bool ascii, const std::vector<Key> &pgpRecipients, const std::vector<Key> &pgpSigners, const std::vector<Key> &cmsRecipients, const std::vector<Key> &cmsSigners, const QMap<int, QString> &outputNames, bool symmetric) { std::vector<std::shared_ptr<SignEncryptTask>> result; const bool pgp = !pgpSigners.empty() || !pgpRecipients.empty(); const bool cms = !cmsSigners.empty() || !cmsRecipients.empty(); result.reserve(pgp + cms); if (pgp || symmetric) { int outKind = 0; if ((!pgpRecipients.empty() || symmetric) && !pgpSigners.empty()) { outKind = SignEncryptFilesWizard::CombinedPGP; } else if (!pgpRecipients.empty() || symmetric) { outKind = SignEncryptFilesWizard::EncryptedPGP; } else { outKind = SignEncryptFilesWizard::SignaturePGP; } result.push_back(createArchiveSignEncryptTaskForFiles(files, ad, true, ascii, pgpRecipients, pgpSigners, outputNames[outKind], symmetric)); } if (cms) { if (!cmsSigners.empty()) { result.push_back(createArchiveSignEncryptTaskForFiles(files, ad, false, ascii, std::vector<Key>(), cmsSigners, outputNames[SignEncryptFilesWizard::SignatureCMS], false)); } if (!cmsRecipients.empty()) { result.push_back(createArchiveSignEncryptTaskForFiles(files, ad, false, ascii, cmsRecipients, std::vector<Key>(), outputNames[SignEncryptFilesWizard::EncryptedCMS], false)); } } return result; } namespace { static bool isDetachedOpenPGPSignature(const QString &fileName) { const auto classification = Kleo::classify(fileName); return Kleo::isOpenPGP(classification) && Kleo::isDetachedSignature(classification); } static auto resolveFileNameConflicts(const std::vector<std::shared_ptr<SignEncryptTask>> &tasks, QWidget *parent) { std::vector<std::shared_ptr<SignEncryptTask>> resolvedTasks; OverwritePolicy overwritePolicy{parent, tasks.size() > 1 ? OverwritePolicy::MultipleFiles : OverwritePolicy::Options{}}; for (auto &task : tasks) { // by default, do not overwrite existing files task->setOverwritePolicy(std::make_shared<OverwritePolicy>(OverwritePolicy::Skip)); const auto outputFileName = task->outputFileName(); if (QFile::exists(outputFileName)) { const auto isDetachedOpenPGPSigningTask = (task->protocol() == GpgME::OpenPGP) && task->detachedSignatureEnabled(); const auto extraOptions = (isDetachedOpenPGPSigningTask && isDetachedOpenPGPSignature(outputFileName) // ? OverwritePolicy::AllowAppend // : OverwritePolicy::Options{}); const auto policyAndFileName = overwritePolicy.obtainOverwritePermission(outputFileName, extraOptions); if (policyAndFileName.policy == OverwritePolicy::Cancel) { resolvedTasks.clear(); break; } switch (policyAndFileName.policy) { case OverwritePolicy::Skip: // do not add task to the final task list continue; case OverwritePolicy::Rename: { task->setOutputFileName(policyAndFileName.fileName); break; } case OverwritePolicy::Append: { task->setOverwritePolicy(std::make_shared<OverwritePolicy>(OverwritePolicy::Append)); break; } case OverwritePolicy::Overwrite: { task->setOverwritePolicy(std::make_shared<OverwritePolicy>(OverwritePolicy::Overwrite)); break; } case OverwritePolicy::Cancel: // already handled outside of switch break; case OverwritePolicy::None: case OverwritePolicy::Ask: qCDebug(KLEOPATRA_LOG) << "Unexpected OverwritePolicy result" << policyAndFileName.policy << "for" << outputFileName; }; } resolvedTasks.push_back(task); } return resolvedTasks; } } void SignEncryptFilesController::Private::slotWizardOperationPrepared() { try { kleo_assert(wizard); kleo_assert(!files.empty()); const bool archive = ((wizard->outputNames().value(SignEncryptFilesWizard::Directory).isNull() && files.size() > 1) // || ((operation & ArchiveMask) == ArchiveForced)); const std::vector<Key> recipients = wizard->resolvedRecipients(); const std::vector<Key> signers = wizard->resolvedSigners(); const FileOperationsPreferences prefs; const bool ascii = prefs.addASCIIArmor(); std::vector<Key> pgpRecipients, cmsRecipients, pgpSigners, cmsSigners; for (const Key &k : recipients) { if (k.protocol() == GpgME::OpenPGP) { pgpRecipients.push_back(k); } else { cmsRecipients.push_back(k); } } for (const Key &k : signers) { if (k.protocol() == GpgME::OpenPGP) { pgpSigners.push_back(k); } else { cmsSigners.push_back(k); } } std::vector<std::shared_ptr<SignEncryptTask>> tasks; if (!archive) { tasks.reserve(files.size()); } if (archive) { tasks = createArchiveSignEncryptTasksForFiles(files, getDefaultAd(), ascii, pgpRecipients, pgpSigners, cmsRecipients, cmsSigners, wizard->outputNames(), wizard->encryptSymmetric()); } else { for (const QString &file : std::as_const(files)) { const std::vector<std::shared_ptr<SignEncryptTask>> created = createSignEncryptTasksForFileInfo(QFileInfo(file), ascii, pgpRecipients, pgpSigners, cmsRecipients, cmsSigners, buildOutputNamesForDir(file, wizard->outputNames()), wizard->encryptSymmetric()); tasks.insert(tasks.end(), created.begin(), created.end()); } } tasks = resolveFileNameConflicts(tasks, wizard); if (tasks.empty()) { q->cancel(); return; } kleo_assert(runnable.empty()); runnable.swap(tasks); for (const auto &task : std::as_const(runnable)) { q->connectTask(task); } std::shared_ptr<TaskCollection> coll(new TaskCollection); std::vector<std::shared_ptr<Task>> tmp; std::copy(runnable.begin(), runnable.end(), std::back_inserter(tmp)); coll->setTasks(tmp); wizard->setTaskCollection(coll); QTimer::singleShot(0, q, SLOT(schedule())); } catch (const Kleo::Exception &e) { reportError(e.error().encodedError(), e.message()); } catch (const std::exception &e) { reportError( gpg_error(GPG_ERR_UNEXPECTED), i18n("Caught unexpected exception in SignEncryptFilesController::Private::slotWizardOperationPrepared: %1", QString::fromLocal8Bit(e.what()))); } catch (...) { reportError(gpg_error(GPG_ERR_UNEXPECTED), i18n("Caught unknown exception in SignEncryptFilesController::Private::slotWizardOperationPrepared")); } } void SignEncryptFilesController::Private::schedule() { if (!cms) if (const std::shared_ptr<SignEncryptTask> t = takeRunnable(CMS)) { t->start(); cms = t; } if (!openpgp) if (const std::shared_ptr<SignEncryptTask> t = takeRunnable(OpenPGP)) { t->start(); openpgp = t; } if (!cms && !openpgp) { kleo_assert(runnable.empty()); q->emitDoneOrError(); } } std::shared_ptr<SignEncryptTask> SignEncryptFilesController::Private::takeRunnable(GpgME::Protocol proto) { const auto it = std::find_if(runnable.begin(), runnable.end(), [proto](const std::shared_ptr<Task> &task) { return task->protocol() == proto; }); if (it == runnable.end()) { return std::shared_ptr<SignEncryptTask>(); } const std::shared_ptr<SignEncryptTask> result = *it; runnable.erase(it); return result; } void SignEncryptFilesController::doTaskDone(const Task *task, const std::shared_ptr<const Task::Result> &result) { Q_UNUSED(result) Q_ASSERT(task); // We could just delete the tasks here, but we can't use // Qt::QueuedConnection here (we need sender()) and other slots // might not yet have executed. Therefore, we push completed tasks // into a burial container if (task == d->cms.get()) { d->completed.push_back(d->cms); d->cms.reset(); } else if (task == d->openpgp.get()) { d->completed.push_back(d->openpgp); d->openpgp.reset(); } QTimer::singleShot(0, this, SLOT(schedule())); } void SignEncryptFilesController::cancel() { qCDebug(KLEOPATRA_LOG) << this << __func__; try { if (d->wizard) { d->wizard->close(); } d->cancelAllTasks(); } catch (const std::exception &e) { qCDebug(KLEOPATRA_LOG) << "Caught exception: " << e.what(); } } void SignEncryptFilesController::Private::cancelAllTasks() { // we just kill all runnable tasks - this will not result in // signal emissions. runnable.clear(); // a cancel() will result in a call to if (cms) { cms->cancel(); } if (openpgp) { openpgp->cancel(); } } void SignEncryptFilesController::Private::ensureWizardCreated() { if (wizard) { return; } std::unique_ptr<SignEncryptFilesWizard> w(new SignEncryptFilesWizard); w->setAttribute(Qt::WA_DeleteOnClose); connect(w.get(), SIGNAL(operationPrepared()), q, SLOT(slotWizardOperationPrepared()), Qt::QueuedConnection); connect(w.get(), SIGNAL(rejected()), q, SLOT(slotWizardCanceled()), Qt::QueuedConnection); wizard = w.release(); updateWizardMode(); } void SignEncryptFilesController::Private::ensureWizardVisible() { ensureWizardCreated(); q->bringToForeground(wizard); } #include "moc_signencryptfilescontroller.cpp" diff --git a/src/kwatchgnupg/kwatchgnupgmainwin.cpp b/src/kwatchgnupg/kwatchgnupgmainwin.cpp index 23cd9e67d..3dfdb14d3 100644 --- a/src/kwatchgnupg/kwatchgnupgmainwin.cpp +++ b/src/kwatchgnupg/kwatchgnupgmainwin.cpp @@ -1,267 +1,266 @@ /* kwatchgnupgmainwin.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2001, 2002, 2004 Klar �vdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include <config-kleopatra.h> #include "kwatchgnupgmainwin.h" #include "kwatchgnupg.h" #include "kwatchgnupgconfig.h" #include "tray.h" #include <QGpgME/CryptoConfig> #include <QGpgME/Protocol> #include <QTextEdit> #include <KActionCollection> #include <KConfig> #include <KConfigGroup> #include <KEditToolBar> #include <KLocalizedString> #include <KMessageBox> #include <KProcess> #include <KShortcutsDialog> #include <KStandardAction> #include <QAction> #include <QApplication> #include <QIcon> #include <KSharedConfig> #include <QDateTime> #include <QEventLoop> #include <QFileDialog> #include <QTextStream> KWatchGnuPGMainWindow::KWatchGnuPGMainWindow(QWidget *parent) : KXmlGuiWindow(parent, Qt::Window) , mConfig(nullptr) { createActions(); createGUI(); mCentralWidget = new QTextEdit(this); mCentralWidget->setReadOnly(true); setCentralWidget(mCentralWidget); mWatcher = new KProcess; - connect(mWatcher, &KProcess::finished, this, &KWatchGnuPGMainWindow::slotWatcherExited); - + connect(mWatcher, &QProcess::finished, this, &KWatchGnuPGMainWindow::slotWatcherExited); connect(mWatcher, &QProcess::readyReadStandardOutput, this, &KWatchGnuPGMainWindow::slotReadStdout); slotReadConfig(); mSysTray = new KWatchGnuPGTray(this); QAction *act = mSysTray->action(QStringLiteral("quit")); if (act) { connect(act, &QAction::triggered, this, &KWatchGnuPGMainWindow::slotQuit); } setAutoSaveSettings(); } KWatchGnuPGMainWindow::~KWatchGnuPGMainWindow() { delete mWatcher; } void KWatchGnuPGMainWindow::slotClear() { mCentralWidget->clear(); mCentralWidget->append(i18n("[%1] Log cleared", QDateTime::currentDateTime().toString(Qt::ISODate))); } void KWatchGnuPGMainWindow::createActions() { QAction *action = actionCollection()->addAction(QStringLiteral("clear_log")); action->setIcon(QIcon::fromTheme(QStringLiteral("edit-clear-history"))); action->setText(i18n("C&lear History")); connect(action, &QAction::triggered, this, &KWatchGnuPGMainWindow::slotClear); actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL | Qt::Key_L)); (void)KStandardAction::saveAs(this, &KWatchGnuPGMainWindow::slotSaveAs, actionCollection()); (void)KStandardAction::close(this, &KWatchGnuPGMainWindow::close, actionCollection()); (void)KStandardAction::quit(this, &KWatchGnuPGMainWindow::slotQuit, actionCollection()); (void)KStandardAction::preferences(this, &KWatchGnuPGMainWindow::slotConfigure, actionCollection()); (void)KStandardAction::keyBindings(this, &KWatchGnuPGMainWindow::configureShortcuts, actionCollection()); (void)KStandardAction::configureToolbars(this, &KWatchGnuPGMainWindow::slotConfigureToolbars, actionCollection()); } void KWatchGnuPGMainWindow::configureShortcuts() { KShortcutsDialog::showDialog(actionCollection(), KShortcutsEditor::LetterShortcutsAllowed, this); } void KWatchGnuPGMainWindow::slotConfigureToolbars() { KEditToolBar dlg(factory()); dlg.exec(); } void KWatchGnuPGMainWindow::startWatcher() { disconnect(mWatcher, &QProcess::finished, this, &KWatchGnuPGMainWindow::slotWatcherExited); if (mWatcher->state() == QProcess::Running) { mWatcher->kill(); while (mWatcher->state() == QProcess::Running) { qApp->processEvents(QEventLoop::ExcludeUserInputEvents); } mCentralWidget->append(i18n("[%1] Log stopped", QDateTime::currentDateTime().toString(Qt::ISODate))); mCentralWidget->ensureCursorVisible(); } mWatcher->clearProgram(); { const KConfigGroup config(KSharedConfig::openConfig(), QStringLiteral("WatchGnuPG")); *mWatcher << config.readEntry("Executable", WATCHGNUPGBINARY); *mWatcher << QStringLiteral("--force"); *mWatcher << config.readEntry("Socket", WATCHGNUPGSOCKET); } mWatcher->setOutputChannelMode(KProcess::OnlyStdoutChannel); mWatcher->start(); const bool ok = mWatcher->waitForStarted(); if (!ok) { KMessageBox::error(this, i18n("The watchgnupg logging process could not be started.\nPlease install watchgnupg somewhere in your $PATH.\nThis log window is " "unable to display any useful information.")); } else { mCentralWidget->append(i18n("[%1] Log started", QDateTime::currentDateTime().toString(Qt::ISODate))); mCentralWidget->ensureCursorVisible(); } connect(mWatcher, &QProcess::finished, this, &KWatchGnuPGMainWindow::slotWatcherExited); } void KWatchGnuPGMainWindow::setGnuPGConfig() { QStringList logclients; // Get config object QGpgME::CryptoConfig *const cconfig = QGpgME::cryptoConfig(); if (!cconfig) { return; } KConfigGroup config(KSharedConfig::openConfig(), QStringLiteral("WatchGnuPG")); const QStringList comps = cconfig->componentList(); for (QStringList::const_iterator it = comps.constBegin(); it != comps.constEnd(); ++it) { const QGpgME::CryptoConfigComponent *const comp = cconfig->component(*it); Q_ASSERT(comp); { QGpgME::CryptoConfigEntry *const entry = cconfig->entry(comp->name(), QStringLiteral("log-file")); if (entry) { entry->setStringValue(QLatin1StringView("socket://") + config.readEntry("Socket", WATCHGNUPGSOCKET)); logclients << QStringLiteral("%1 (%2)").arg(*it, comp->description()); } } { QGpgME::CryptoConfigEntry *const entry = cconfig->entry(comp->name(), QStringLiteral("debug-level")); if (entry) { entry->setStringValue(config.readEntry("LogLevel", "basic")); } } } cconfig->sync(true); if (logclients.isEmpty()) { KMessageBox::error(nullptr, i18n("There are no components available that support logging.")); } } void KWatchGnuPGMainWindow::slotWatcherExited(int, QProcess::ExitStatus) { if (KMessageBox::questionTwoActions(this, i18n("The watchgnupg logging process died.\nDo you want to try to restart it?"), QString(), KGuiItem(i18nc("@action:button", "Try Restart")), KGuiItem(i18nc("@action:button", "Do Not Try"))) == KMessageBox::ButtonCode::PrimaryAction) { mCentralWidget->append(i18n("====== Restarting logging process =====")); mCentralWidget->ensureCursorVisible(); startWatcher(); } else { KMessageBox::error(this, i18n("The watchgnupg logging process is not running.\nThis log window is unable to display any useful information.")); } } void KWatchGnuPGMainWindow::slotReadStdout() { if (!mWatcher) { return; } while (mWatcher->canReadLine()) { QString str = QString::fromUtf8(mWatcher->readLine()); if (str.endsWith(QLatin1Char('\n'))) { str.chop(1); } if (str.endsWith(QLatin1Char('\r'))) { str.chop(1); } mCentralWidget->append(str); mCentralWidget->ensureCursorVisible(); if (!isVisible()) { // Change tray icon to show something happened // PENDING(steffen) mSysTray->setAttention(true); } } } void KWatchGnuPGMainWindow::show() { mSysTray->setAttention(false); KMainWindow::show(); } void KWatchGnuPGMainWindow::slotSaveAs() { const QString filename = QFileDialog::getSaveFileName(this, i18n("Save Log to File")); if (filename.isEmpty()) { return; } QFile file(filename); if (file.open(QIODevice::WriteOnly)) { QTextStream(&file) << mCentralWidget->document()->toPlainText(); } else KMessageBox::information(this, i18n("Could not save file %1: %2", filename, file.errorString())); } void KWatchGnuPGMainWindow::slotQuit() { disconnect(mWatcher, &QProcess::finished, this, &KWatchGnuPGMainWindow::slotWatcherExited); mWatcher->kill(); qApp->quit(); } void KWatchGnuPGMainWindow::slotConfigure() { if (!mConfig) { mConfig = new KWatchGnuPGConfig(this); mConfig->setObjectName(QLatin1StringView("config dialog")); connect(mConfig, &KWatchGnuPGConfig::reconfigure, this, &KWatchGnuPGMainWindow::slotReadConfig); } mConfig->loadConfig(); mConfig->exec(); } void KWatchGnuPGMainWindow::slotReadConfig() { const KConfigGroup config(KSharedConfig::openConfig(), QStringLiteral("LogWindow")); const int maxLogLen = config.readEntry("MaxLogLen", 10000); mCentralWidget->document()->setMaximumBlockCount(maxLogLen < 1 ? -1 : maxLogLen); setGnuPGConfig(); startWatcher(); } bool KWatchGnuPGMainWindow::queryClose() { if (!qApp->isSavingSession()) { hide(); return false; } return KMainWindow::queryClose(); } #include "moc_kwatchgnupgmainwin.cpp"