File indexing completed on 2024-06-23 05:14:06
0001 /* dialogs/updatenotification.cpp 0002 0003 This file is part of Kleopatra, the KDE keymanager 0004 SPDX-FileCopyrightText: 2017 Bundesamt für Sicherheit in der Informationstechnik 0005 SPDX-FileContributor: Intevation GmbH 0006 0007 SPDX-License-Identifier: GPL-2.0-or-later 0008 */ 0009 #include "updatenotification.h" 0010 0011 #include <view/htmllabel.h> 0012 0013 #include <Libkleo/Compat> 0014 #include <Libkleo/Formatting> 0015 #include <Libkleo/GnuPG> 0016 0017 #include "kleopatra_debug.h" 0018 0019 #include <QCheckBox> 0020 #include <QDateTime> 0021 #include <QDesktopServices> 0022 #include <QDialogButtonBox> 0023 #include <QGridLayout> 0024 #include <QIcon> 0025 #include <QLabel> 0026 #include <QProcess> 0027 #include <QProgressDialog> 0028 #include <QPushButton> 0029 #include <QRegularExpression> 0030 #include <QUrl> 0031 0032 #include <KAboutData> 0033 #include <KConfigGroup> 0034 #include <KIconLoader> 0035 #include <KLocalizedString> 0036 #include <KMessageBox> 0037 #include <KSharedConfig> 0038 0039 #include <QGpgME/CryptoConfig> 0040 #include <QGpgME/Protocol> 0041 0042 #include <gpgme++/error.h> 0043 #include <gpgme++/gpgmefw.h> 0044 #include <gpgme++/swdbresult.h> 0045 0046 using namespace Kleo; 0047 0048 namespace 0049 { 0050 static void gpgconf_set_update_check(bool value) 0051 { 0052 auto conf = QGpgME::cryptoConfig(); 0053 auto entry = getCryptoConfigEntry(conf, "dirmngr", "allow-version-check"); 0054 if (!entry) { 0055 qCDebug(KLEOPATRA_LOG) << "allow-version-check entry not found"; 0056 return; 0057 } 0058 if (entry->boolValue() != value) { 0059 entry->setBoolValue(value); 0060 conf->sync(true); 0061 } 0062 } 0063 } // namespace 0064 0065 void UpdateNotification::forceUpdateCheck(QWidget *parent) 0066 { 0067 auto proc = new QProcess; 0068 0069 proc->setProgram(gnupgInstallPath() + QStringLiteral("/gpg-connect-agent.exe")); 0070 proc->setArguments({ 0071 QStringLiteral("--dirmngr"), 0072 QStringLiteral("loadswdb --force"), 0073 QStringLiteral("/bye"), 0074 }); 0075 0076 auto progress = new QProgressDialog(i18n("Searching for updates..."), i18n("Cancel"), 0, 0, parent); 0077 progress->setMinimumDuration(0); 0078 progress->show(); 0079 0080 connect(progress, &QProgressDialog::canceled, proc, [proc]() { 0081 proc->kill(); 0082 qCDebug(KLEOPATRA_LOG) << "Update force canceled. Output:" << QString::fromLocal8Bit(proc->readAllStandardOutput()) 0083 << "stderr:" << QString::fromLocal8Bit(proc->readAllStandardError()); 0084 }); 0085 0086 connect(proc, &QProcess::finished, progress, [parent, progress, proc](int exitCode, QProcess::ExitStatus exitStatus) { 0087 qCDebug(KLEOPATRA_LOG) << "Update force exited with status:" << exitStatus << "code:" << exitCode; 0088 delete progress; 0089 proc->deleteLater(); 0090 UpdateNotification::checkUpdate(parent, exitStatus == QProcess::NormalExit); 0091 }); 0092 0093 qCDebug(KLEOPATRA_LOG) << "Starting:" << proc->program() << "args" << proc->arguments(); 0094 0095 proc->start(); 0096 } 0097 #ifdef Q_OS_WIN 0098 /* Extract the actual version number (conforming to the semanticversioning spec) 0099 * from the Version strings which might be used for Gpg4win / GnuPG VS-Desktop 0100 * which are optionally prefixed with some text followed by a dash 0101 * e.g. "Gpg4win-3.1.15-beta15"; see https://dev.gnupg.org/T5663 */ 0102 static const QByteArray extractVersionNumber(const QString &versionString) 0103 { 0104 static const QRegularExpression catchSemVerRegExp{QLatin1StringView{R"(-([0-9]+(?:\.[0-9]+)*(?:-[.0-9A-Za-z-]+)?(?:\+[.0-9a-zA-Z-]+)?)$)"}}; 0105 0106 const auto match = catchSemVerRegExp.match(versionString); 0107 const auto current = match.hasMatch() ? match.captured(1) : versionString; 0108 return current.toUtf8(); 0109 } 0110 #endif 0111 0112 void UpdateNotification::checkUpdate(QWidget *parent, bool force) 0113 { 0114 #ifdef Q_OS_WIN 0115 KConfigGroup updatecfg(KSharedConfig::openConfig(), QStringLiteral("UpdateNotification")); 0116 0117 if (updatecfg.readEntry("NeverShow", false) && !force) { 0118 return; 0119 } 0120 0121 // Gpg defaults to no update check. For Gpg4win we want this 0122 // enabled if the user does not explicitly disable update 0123 // checks neverShow would be true in that case or 0124 // we would have set AllowVersionCheck once and the user 0125 // explicitly removed that. 0126 if (force || updatecfg.readEntry("AllowVersionCheckSetOnce", false)) { 0127 gpgconf_set_update_check(true); 0128 updatecfg.writeEntry("AllowVersionCheckSetOnce", true); 0129 } 0130 0131 GpgME::Error err; 0132 const auto lastshown = updatecfg.readEntry("LastShown", QDateTime()); 0133 0134 if (!force && lastshown.isValid() && lastshown.addSecs(20 * 60 * 60) > QDateTime::currentDateTime()) { 0135 qDebug() << QDateTime::currentDateTime().addSecs(20 * 60 * 60); 0136 return; 0137 } 0138 0139 const auto current = extractVersionNumber(KAboutData::applicationData().version()); 0140 0141 const auto results = GpgME::SwdbResult::query("gpg4win", current.constData(), &err); 0142 if (err) { 0143 qCDebug(KLEOPATRA_LOG) << "update check failed: " << Formatting::errorAsString(err); 0144 return; 0145 } 0146 0147 if (results.size() != 1) { 0148 /* Should not happen */ 0149 qCDebug(KLEOPATRA_LOG) << "more then one result"; 0150 return; 0151 } 0152 0153 const auto result = results[0]; 0154 0155 if (result.update()) { 0156 const QString newVersion = QStringLiteral("%1.%2.%3").arg(result.version().major).arg(result.version().minor).arg(result.version().patch); 0157 qCDebug(KLEOPATRA_LOG) << "Have update to version:" << newVersion; 0158 UpdateNotification notifier(parent, newVersion); 0159 notifier.exec(); 0160 updatecfg.writeEntry("LastShown", QDateTime::currentDateTime()); 0161 updatecfg.sync(); 0162 } else { 0163 qCDebug(KLEOPATRA_LOG) << "No update for:" << current; 0164 if (force) { 0165 KMessageBox::information(parent, i18nc("@info", "No update found in the available version database."), i18nc("@title", "Up to date")); 0166 } 0167 } 0168 #else 0169 Q_UNUSED(parent) 0170 Q_UNUSED(force) 0171 #endif 0172 } 0173 0174 UpdateNotification::UpdateNotification(QWidget *parent, const QString &version) 0175 : QDialog(parent) 0176 { 0177 resize(400, 200); 0178 auto lay = new QGridLayout(this); 0179 auto logo = new QLabel; 0180 logo->setMaximumWidth(110); 0181 0182 setAttribute(Qt::WA_QuitOnClose, false); 0183 0184 KIconLoader *const il = KIconLoader::global(); 0185 const QString iconPath = il->iconPath(QStringLiteral("gpg4win"), KIconLoader::User); 0186 logo->setPixmap(QIcon(iconPath).pixmap(100, 100)); 0187 0188 auto label = new HtmlLabel; 0189 const QString boldVersion = QStringLiteral("<b>%1</b>").arg(version); 0190 label->setHtml(i18nc("%1 is the version number", "Version %1 is available.", boldVersion) + QStringLiteral("<br><br>") 0191 + i18nc("Link to NEWS style changelog", "See the <a href=\"https://www.gpg4win.org/change-history.html\">new features</a>.")); 0192 label->setOpenExternalLinks(true); 0193 label->setTextInteractionFlags(Qt::TextBrowserInteraction); 0194 label->setWordWrap(true); 0195 setWindowTitle(i18nc("@title:window", "Update Available")); 0196 setWindowIcon(QIcon(QLatin1StringView("gpg4win"))); 0197 0198 lay->addWidget(logo, 0, 0); 0199 lay->addWidget(label, 0, 1); 0200 const auto chk = new QCheckBox(i18n("Show this notification for future updates.")); 0201 lay->addWidget(chk, 1, 0, 1, -1); 0202 0203 KConfigGroup updatecfg(KSharedConfig::openConfig(), QStringLiteral("UpdateNotification")); 0204 chk->setChecked(!updatecfg.readEntry("NeverShow", false)); 0205 0206 const auto bb = new QDialogButtonBox(); 0207 const auto b = bb->addButton(i18n("&Get update"), QDialogButtonBox::AcceptRole); 0208 b->setDefault(true); 0209 b->setIcon(QIcon::fromTheme(QStringLiteral("arrow-down"))); 0210 bb->addButton(QDialogButtonBox::Cancel); 0211 lay->addWidget(bb, 2, 0, 1, -1); 0212 connect(bb, &QDialogButtonBox::accepted, this, [this, chk]() { 0213 QDesktopServices::openUrl(QUrl(QStringLiteral("https://www.gpg4win.org/download.html"))); 0214 KConfigGroup updatecfg(KSharedConfig::openConfig(), QStringLiteral("UpdateNotification")); 0215 updatecfg.writeEntry("NeverShow", !chk->isChecked()); 0216 gpgconf_set_update_check(chk->isChecked()); 0217 QDialog::accept(); 0218 }); 0219 connect(bb, &QDialogButtonBox::rejected, this, [this, chk]() { 0220 KConfigGroup updatecfg(KSharedConfig::openConfig(), QStringLiteral("UpdateNotification")); 0221 updatecfg.writeEntry("NeverShow", !chk->isChecked()); 0222 gpgconf_set_update_check(chk->isChecked()); 0223 QDialog::reject(); 0224 }); 0225 }