/** * Copyright (C) 2021 Saturneric * * This file is part of GpgFrontend. * * GpgFrontend is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * GpgFrontend is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GpgFrontend. If not, see . * * The initial version of the source code is inherited from * the gpg4usb project, which is under GPL-3.0-or-later. * * All the source code of GpgFrontend was modified and released by * Saturneric starting on May 12, 2021. * * SPDX-License-Identifier: GPL-3.0-or-later * */ #include "UserInterfaceUtils.h" #include #include #include #include #include "core/GpgConstants.h" #include "core/function/CoreSignalStation.h" #include "core/function/gpg/GpgKeyGetter.h" #include "core/model/GpgImportInformation.h" #include "core/module/ModuleManager.h" #include "core/thread/Task.h" #include "core/thread/TaskRunnerGetter.h" #include "core/typedef/GpgTypedef.h" #include "core/utils/GpgUtils.h" #include "core/utils/IOUtils.h" #include "ui/UISignalStation.h" #include "ui/dialog/WaitingDialog.h" #include "ui/dialog/gnupg/GnuPGControllerDialog.h" #include "ui/struct/CacheObject.h" #include "ui/struct/SettingsObject.h" #include "ui/struct/settings/KeyServerSO.h" #include "ui/widgets/TextEdit.h" namespace GpgFrontend::UI { std::unique_ptr GpgFrontend::UI::CommonUtils::instance_ = nullptr; void show_verify_details(QWidget *parent, InfoBoardWidget *info_board, GpgError error, const GpgVerifyResult &verify_result) { // take out result info_board->ResetOptionActionsMenu(); info_board->AddOptionalAction(QObject::tr("Show Verify Details"), [=]() { VerifyDetailsDialog(parent, error, verify_result); }); } void import_unknown_key_from_keyserver( QWidget *parent, const GpgVerifyResultAnalyse &verify_res) { QMessageBox::StandardButton reply; reply = QMessageBox::question( parent, QObject::tr("Public key not found locally"), QObject::tr( "There is no target public key content in local for GpgFrontend to " "gather enough information about this Signature. Do you want to " "import the public key from Keyserver now?"), QMessageBox::Yes | QMessageBox::No); if (reply == QMessageBox::Yes) { auto dialog = KeyServerImportDialog(parent); auto key_ids = std::make_unique(); auto *signature = verify_res.GetSignatures(); while (signature != nullptr) { GF_UI_LOG_DEBUG("signature fpr: {}", signature->fpr); key_ids->push_back(signature->fpr); signature = signature->next; } dialog.show(); dialog.SlotImport(key_ids); } } void refresh_info_board(InfoBoardWidget *info_board, int status, const QString &report_text) { if (status < 0) { info_board->SlotRefresh(report_text, INFO_ERROR_CRITICAL); } else if (status > 0) { info_board->SlotRefresh(report_text, INFO_ERROR_OK); } else { info_board->SlotRefresh(report_text, INFO_ERROR_WARN); } } void process_result_analyse(TextEdit *edit, InfoBoardWidget *info_board, const GpgResultAnalyse &result_analyse) { info_board->AssociateTabWidget(edit->tab_widget_); refresh_info_board(info_board, result_analyse.GetStatus(), result_analyse.GetResultReport()); } void process_result_analyse(TextEdit *edit, InfoBoardWidget *info_board, const GpgResultAnalyse &result_analyse_a, const GpgResultAnalyse &result_analyse_b) { info_board->AssociateTabWidget(edit->tab_widget_); refresh_info_board( info_board, std::min(result_analyse_a.GetStatus(), result_analyse_b.GetStatus()), result_analyse_a.GetResultReport() + result_analyse_b.GetResultReport()); } void process_operation(QWidget *parent, const QString &waiting_title, const Thread::Task::TaskRunnable func, const Thread::Task::TaskCallback callback, DataObjectPtr data_object) { auto *dialog = new WaitingDialog(waiting_title, parent); auto *process_task = new Thread::Task(std::move(func), waiting_title, data_object, std::move(callback)); QApplication::connect(process_task, &Thread::Task::SignalTaskEnd, dialog, &QDialog::close); QApplication::connect(process_task, &Thread::Task::SignalTaskEnd, dialog, &QDialog::deleteLater); // a looper to wait for the operation QEventLoop looper; QApplication::connect(process_task, &Thread::Task::SignalTaskEnd, &looper, &QEventLoop::quit); // post process task to task runner Thread::TaskRunnerGetter::GetInstance() .GetTaskRunner(Thread::TaskRunnerGetter::kTaskRunnerType_GPG) ->PostTask(process_task); // block until task finished // this is to keep reference vaild until task finished looper.exec(); } auto CommonUtils::GetInstance() -> CommonUtils * { if (instance_ == nullptr) { instance_ = std::make_unique(); } return instance_.get(); } CommonUtils::CommonUtils() : QWidget(nullptr) { connect(CoreSignalStation::GetInstance(), &CoreSignalStation::SignalBadGnupgEnv, this, &CommonUtils::SignalBadGnupgEnv); connect(this, &CommonUtils::SignalKeyStatusUpdated, UISignalStation::GetInstance(), &UISignalStation::SignalKeyDatabaseRefresh); connect(this, &CommonUtils::SignalKeyDatabaseRefreshDone, UISignalStation::GetInstance(), &UISignalStation::SignalKeyDatabaseRefreshDone); // directly connect to SignalKeyStatusUpdated // to avoid the delay of signal emitting // when the key database is refreshed connect(UISignalStation::GetInstance(), &UISignalStation::SignalKeyDatabaseRefresh, this, &CommonUtils::slot_update_key_status); connect(this, &CommonUtils::SignalRestartApplication, UISignalStation::GetInstance(), &UISignalStation::SignalRestartApplication); connect(UISignalStation::GetInstance(), &UISignalStation::SignalRestartApplication, this, &CommonUtils::SlotRestartApplication); connect(this, &CommonUtils::SignalBadGnupgEnv, this, [=](const QString &reason) { QMessageBox msg_box; msg_box.setText(tr("GnuPG Context Loading Failed")); msg_box.setInformativeText( tr("Gnupg(gpg) is not installed correctly, please follow " "this " "notes in FAQ to install Gnupg and then open " "GpgFrontend.
" "Or, you can open GnuPG Controller to set a " "custom GnuPG which GpgFrontend should use. Then, " "GpgFrontend will restart.

" "Breif Reason: %1") .arg(reason)); msg_box.setStandardButtons(QMessageBox::Open | QMessageBox::Cancel); msg_box.setDefaultButton(QMessageBox::Save); int ret = msg_box.exec(); switch (ret) { case QMessageBox::Open: (new GnuPGControllerDialog(this))->exec(); // restart application when loop start application_need_to_restart_at_once_ = true; // restart application, core and ui emit SignalRestartApplication(kDeepRestartCode); break; case QMessageBox::Cancel: // close application emit SignalRestartApplication(0); break; default: // close application emit SignalRestartApplication(0); break; } }); } void CommonUtils::WaitForOpera(QWidget *parent, const QString &waiting_dialog_title, const OperaWaitingCb &opera) { QEventLoop looper; QPointer const dialog = new WaitingDialog(waiting_dialog_title, parent); connect(dialog, &QDialog::finished, &looper, &QEventLoop::quit); connect(dialog, &QDialog::finished, dialog, &QDialog::deleteLater); dialog->show(); QTimer::singleShot(64, parent, [=]() { opera([dialog]() { if (dialog != nullptr) { GF_UI_LOG_DEBUG("called operating waiting cb, dialog: {}", static_cast(dialog)); dialog->close(); dialog->accept(); } }); }); looper.exec(); } void CommonUtils::RaiseMessageBox(QWidget *parent, GpgError err) { GpgErrorDesc desc = DescribeGpgErrCode(err); GpgErrorCode err_code = CheckGpgError2ErrCode(err); if (err_code == GPG_ERR_NO_ERROR) { QMessageBox::information(parent, tr("Success"), tr("Gpg Operation succeed.")); } else { RaiseFailureMessageBox(parent, err); } } void CommonUtils::RaiseFailureMessageBox(QWidget *parent, GpgError err) { GpgErrorDesc desc = DescribeGpgErrCode(err); GpgErrorCode err_code = CheckGpgError2ErrCode(err); QMessageBox::critical(parent, tr("Failure"), tr("Gpg Operation failed.\n\nError code: %1\nSource: " " %2\nDescription: %3") .arg(err_code) .arg(desc.first) .arg(desc.second)); } void CommonUtils::SlotImportKeys(QWidget *parent, const QString &in_buffer) { auto info = GpgKeyImportExporter::GetInstance().ImportKey(GFBuffer(in_buffer)); emit SignalKeyStatusUpdated(); (new KeyImportDetailDialog(info, parent))->exec(); } void CommonUtils::SlotImportKeyFromFile(QWidget *parent) { auto file_name = QFileDialog::getOpenFileName(this, tr("Open Key"), QString(), tr("Key Files")) + " (*.asc *.txt);;" + tr("Keyring files") + " (*.gpg);;All Files (*)"; if (!file_name.isNull()) { QByteArray key_buffer; if (!ReadFile(file_name, key_buffer)) { QMessageBox::critical(nullptr, tr("File Open Failed"), tr("Failed to open file: ") + file_name); return; } SlotImportKeys(parent, key_buffer); } } void CommonUtils::SlotImportKeyFromKeyServer(QWidget *parent) { auto *dialog = new KeyServerImportDialog(parent); dialog->show(); } void CommonUtils::SlotImportKeyFromClipboard(QWidget *parent) { QClipboard *cb = QApplication::clipboard(); SlotImportKeys(parent, cb->text(QClipboard::Clipboard)); } void CommonUtils::SlotExecuteCommand( const QString &cmd, const QStringList &arguments, const std::function &interact_func) { QEventLoop looper; auto *cmd_process = new QProcess(&looper); cmd_process->setProcessChannelMode(QProcess::MergedChannels); connect(cmd_process, qOverload(&QProcess::finished), &looper, &QEventLoop::quit); connect(cmd_process, &QProcess::errorOccurred, &looper, &QEventLoop::quit); connect(cmd_process, &QProcess::started, []() -> void { GF_UI_LOG_DEBUG("process started"); }); connect(cmd_process, &QProcess::readyReadStandardOutput, [interact_func, cmd_process]() { interact_func(cmd_process); }); connect(cmd_process, &QProcess::errorOccurred, this, [=]() -> void { GF_UI_LOG_ERROR("error in process"); }); connect(cmd_process, qOverload(&QProcess::finished), this, [=](int, QProcess::ExitStatus status) { if (status == QProcess::NormalExit) GF_UI_LOG_DEBUG("succeed in executing command: {}", cmd); else GF_UI_LOG_WARN("error in executing command: {}", cmd); }); cmd_process->setProgram(cmd); cmd_process->setArguments(arguments); cmd_process->start(); looper.exec(); } void CommonUtils::SlotExecuteGpgCommand( const QStringList &arguments, const std::function &interact_func) { QEventLoop looper; auto dialog = new WaitingDialog(tr("Processing"), nullptr); dialog->show(); auto *gpg_process = new QProcess(&looper); gpg_process->setProcessChannelMode(QProcess::MergedChannels); connect(gpg_process, qOverload(&QProcess::finished), &looper, &QEventLoop::quit); connect(gpg_process, qOverload(&QProcess::finished), dialog, &WaitingDialog::deleteLater); connect(gpg_process, &QProcess::errorOccurred, &looper, &QEventLoop::quit); connect(gpg_process, &QProcess::started, []() -> void { GF_UI_LOG_DEBUG("gpg process started"); }); connect(gpg_process, &QProcess::readyReadStandardOutput, [interact_func, gpg_process]() { interact_func(gpg_process); }); connect(gpg_process, &QProcess::errorOccurred, this, [=]() -> void { GF_UI_LOG_ERROR("Error in Process"); dialog->close(); QMessageBox::critical(nullptr, tr("Failure"), tr("Failed to execute command.")); }); connect(gpg_process, qOverload(&QProcess::finished), this, [=](int, QProcess::ExitStatus status) { dialog->close(); if (status == QProcess::NormalExit) QMessageBox::information(nullptr, tr("Success"), tr("Succeed in executing command.")); else QMessageBox::information(nullptr, tr("Warning"), tr("Finished executing command.")); }); const auto app_path = Module::RetrieveRTValueTypedOrDefault<>( "core", "gpgme.ctx.app_path", QString{}); GF_UI_LOG_DEBUG("got gnupg app path from rt: {}", app_path); gpg_process->setProgram(app_path); gpg_process->setArguments(arguments); gpg_process->start(); looper.exec(); dialog->close(); dialog->deleteLater(); } void CommonUtils::SlotImportKeyFromKeyServer( const KeyIdArgsList &key_ids, const ImportCallbackFunctiopn &callback) { auto target_keyserver = KeyServerSO(SettingsObject("key_server")).GetTargetServer(); if (target_keyserver.isEmpty()) { QMessageBox::critical( nullptr, tr("Default Keyserver Not Found"), tr("Cannot read default keyserver from your settings, " "please set a default keyserver first")); return; } GF_UI_LOG_DEBUG("set target key server to default Key Server: {}", target_keyserver); auto *thread = QThread::create([target_keyserver, key_ids, callback]() { QUrl target_keyserver_url(target_keyserver); auto network_manager = std::make_unique(); // LOOP decltype(key_ids.size()) current_index = 1; decltype(key_ids.size()) all_index = key_ids.size(); for (const auto &key_id : key_ids) { // New Req Url QUrl req_url(target_keyserver_url.scheme() + "://" + target_keyserver_url.host() + "/pks/lookup?op=get&search=0x" + key_id + "&options=mr"); GF_UI_LOG_DEBUG("request url: {}", req_url.toString().toStdString()); // Waiting for reply QNetworkReply *reply = network_manager->get(QNetworkRequest(req_url)); QEventLoop loop; connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit); loop.exec(); // Detect status QString status; auto error = reply->error(); if (error != QNetworkReply::NoError) { switch (error) { case QNetworkReply::ContentNotFoundError: status = tr("Key Not Found"); break; case QNetworkReply::TimeoutError: status = tr("Timeout"); break; case QNetworkReply::HostNotFoundError: status = tr("Key Server Not Found"); break; default: status = tr("Connection Error"); } } reply->deleteLater(); // Try importing auto result = GpgKeyImportExporter::GetInstance().ImportKey( GFBuffer(reply->readAll())); if (result->imported == 1) { status = tr("The key has been updated"); } else { status = tr("No need to update the key"); } callback(key_id, status, current_index, all_index); current_index++; } }); connect(thread, &QThread::finished, thread, &QThread::deleteLater); thread->start(); } void CommonUtils::slot_update_key_status() { auto *refresh_task = new Thread::Task( [](DataObjectPtr) -> int { // flush key cache for all GpgKeyGetter Intances. for (const auto &channel_id : GpgKeyGetter::GetAllChannelId()) { GpgKeyGetter::GetInstance(channel_id).FlushKeyCache(); } return 0; }, "update_key_database_task"); connect(refresh_task, &Thread::Task::SignalTaskEnd, this, &CommonUtils::SignalKeyDatabaseRefreshDone); // post the task to the default task runner Thread::TaskRunnerGetter::GetInstance().GetTaskRunner()->PostTask( refresh_task); } void CommonUtils::SlotRestartApplication(int code) { GF_UI_LOG_DEBUG("application need restart, code: {}", code); if (code == 0) { std::exit(0); } else { QCoreApplication::exit(code); } } bool CommonUtils::isApplicationNeedRestart() { return application_need_to_restart_at_once_; } auto CommonUtils::KeyExistsinFavouriteList(const GpgKey &key) -> bool { // load cache auto json_data = CacheObject("favourite_key_pair"); if (!json_data.isArray()) json_data.setArray(QJsonArray()); auto key_array = json_data.array(); return std::find(key_array.begin(), key_array.end(), key.GetFingerprint()) != key_array.end(); } void CommonUtils::AddKey2Favourtie(const GpgKey &key) { auto json_data = CacheObject("favourite_key_pair"); QJsonArray key_array; if (json_data.isArray()) key_array = json_data.array(); key_array.push_back(key.GetFingerprint()); json_data.setArray(key_array); } void CommonUtils::RemoveKeyFromFavourite(const GpgKey &key) { auto json_data = CacheObject("favourite_key_pair"); QJsonArray key_array; if (json_data.isArray()) key_array = json_data.array(); QString fingerprint = key.GetFingerprint(); QJsonArray new_key_array; for (auto &&item : key_array) { if (item.isString() && item.toString() != fingerprint) { new_key_array.append(item); } } json_data.setArray(new_key_array); } } // namespace GpgFrontend::UI