/** * 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 #include "MainWindow.h" #include "core/function/ArchiveFileOperator.h" #include "core/function/GlobalSettingStation.h" #include "core/function/gpg/GpgFileOpera.h" #include "core/function/gpg/GpgKeyGetter.h" #include "core/function/result_analyse/GpgDecryptResultAnalyse.h" #include "core/function/result_analyse/GpgEncryptResultAnalyse.h" #include "core/function/result_analyse/GpgSignResultAnalyse.h" #include "core/function/result_analyse/GpgVerifyResultAnalyse.h" #include "core/thread/Task.h" #include "ui/UserInterfaceUtils.h" #include "ui/dialog/SignersPicker.h" namespace GpgFrontend::UI { bool path_pre_check(QWidget* parent, const QString& path) { QFileInfo file_info(path); QFileInfo path_info(file_info.absolutePath()); if (!path_info.exists()) { QMessageBox::critical(parent, _("Error"), QString(_("The path %1 does not exist.")).arg(path)); return false; } if (!file_info.isReadable()) { QMessageBox::critical(parent, _("Error"), _("No permission to read this file.")); return false; } if (!path_info.isWritable()) { QMessageBox::critical(parent, _("Error"), _("No permission to create file.")); return false; } return true; } /** * @brief convert directory into tarball * * @param parent parent widget * @param path the directory to be converted * @return */ bool process_tarball_into_directory(QWidget* parent, std::filesystem::path& path) { SPDLOG_DEBUG("converting directory into tarball: {}", path.u8string()); auto selected_dir_path = std::filesystem::path(path); if (selected_dir_path.extension() != ".tar") { QMessageBox::critical(parent, _("Error"), _("The file is not a tarball.")); return false; } try { auto base_path = selected_dir_path.parent_path(); auto target_path = selected_dir_path; target_path.replace_extension(".tar"); SPDLOG_DEBUG("base path: {} target archive path: {]", base_path.u8string(), target_path.u8string()); bool if_error = false; process_operation(parent, _("Extracting Tarball"), [&](Thread::DataObjectPtr) -> int { try { GpgFrontend::ArchiveFileOperator::ExtractArchive( target_path, base_path); } catch (const std::runtime_error& e) { if_error = true; } return 0; }); if (if_error || !exists(target_path)) { throw std::runtime_error("Decompress Failed"); } path = target_path.u8string().c_str(); } catch (...) { SPDLOG_ERROR("decompress error"); return false; } return true; } /** * @brief convert tarball into directory * * @param parent parent widget * @param path the tarball to be converted */ bool process_directory_into_tarball(QWidget* parent, QString& path) { #ifdef WINDOWS std::filesystem::path selected_dir_path = path.toStdU16String(); #else std::filesystem::path selected_dir_path = path.toStdString(); #endif try { auto base_path = selected_dir_path.parent_path(); auto target_path = selected_dir_path; selected_dir_path.replace_extension(""); SPDLOG_DEBUG("base path: {} target archive path: {} selected_dir_path: {}", base_path.u8string(), target_path.u8string(), selected_dir_path.u8string()); bool if_error = false; process_operation(parent, _("Making Tarball"), [&](Thread::DataObjectPtr) -> int { try { GpgFrontend::ArchiveFileOperator::CreateArchive( base_path, target_path, 0, {selected_dir_path}); } catch (const std::runtime_error& e) { if_error = true; } return 0; }); if (if_error || !exists(target_path)) { throw std::runtime_error("Compress Failed"); } path = target_path.u8string().c_str(); } catch (...) { SPDLOG_ERROR("compress error"); return false; } return true; } void MainWindow::SlotFileEncrypt() { auto fileTreeView = edit_->SlotCurPageFileTreeView(); auto path = fileTreeView->GetSelected(); if (!path_pre_check(this, path)) { SPDLOG_ERROR("path pre check failed"); return; } // check selected keys auto key_ids = m_key_list_->GetChecked(); GpgEncrResult result = nullptr; GpgError error; bool if_error = false; bool non_ascii_when_export = GlobalSettingStation::GetInstance().LookupSettings( "general.non_ascii_when_export", true); // get file info QFileInfo file_info(path); if (file_info.isDir()) { path = path + (file_info.isDir() ? ".tar" : ""); } auto _channel = GPGFRONTEND_DEFAULT_CHANNEL; auto _extension = ".asc"; if (non_ascii_when_export || file_info.isDir()) { _channel = GPGFRONTEND_NON_ASCII_CHANNEL; _extension = ".gpg"; } auto out_path = path + _extension; if (QFile::exists(out_path)) { #ifdef WINDOWS std::filesystem::path _out_path = out_path.toStdU16String(); #else std::filesystem::path _out_path = out_path.toStdString(); #endif auto out_file_name = boost::format(_("The target file %1% already exists, " "do you need to overwrite it?")) % _out_path.filename(); auto ret = QMessageBox::warning(this, _("Warning"), out_file_name.str().c_str(), QMessageBox::Ok | QMessageBox::Cancel); if (ret == QMessageBox::Cancel) return; } if (file_info.isDir()) { // stop if the process making tarball failed if (!process_directory_into_tarball(this, path)) { QMessageBox::critical(this, _("Error"), _("Unable to convert the folder into tarball.")); return; } } if (key_ids->empty()) { // Symmetric Encrypt auto ret = QMessageBox::information( this, _("Symmetric Encryption"), _("No Key Selected. Do you want to encrypt with a " "symmetric cipher using a passphrase?"), QMessageBox::Ok | QMessageBox::Cancel); if (ret == QMessageBox::Cancel) return; process_operation( this, _("Symmetrically Encrypting"), [&](Thread::DataObjectPtr) -> int { try { error = GpgFrontend::GpgFileOpera::EncryptFileSymmetric( path.toStdString(), out_path.toStdString(), result, _channel); } catch (const std::runtime_error& e) { if_error = true; } return 0; }); } else { auto p_keys = GpgKeyGetter::GetInstance().GetKeys(key_ids); // check key abilities for (const auto& key : *p_keys) { bool key_can_encrypt = key.IsHasActualEncryptionCapability(); if (!key_can_encrypt) { QMessageBox::critical( nullptr, _("Invalid KeyPair"), QString(_("The selected keypair cannot be used for encryption.")) + "

" + _("For example the Following Key:") + "
" + QString::fromStdString(key.GetUIDs()->front().GetUID())); return; } } process_operation(this, _("Encrypting"), [&](Thread::DataObjectPtr) -> int { try { error = GpgFileOpera::EncryptFile(std::move(p_keys), path.toStdString(), out_path.toStdString(), result, _channel); } catch (const std::runtime_error& e) { if_error = true; } return 0; }); } // remove xxx.tar and only left xxx.tar.gpg if (file_info.isDir()) { auto selected_dir_path = std::filesystem::path(path.toStdString()); auto target_path = selected_dir_path.replace_extension(".tar"); if (exists(target_path)) { std::filesystem::remove(target_path); } } if (!if_error) { auto resultAnalyse = GpgEncryptResultAnalyse(error, std::move(result)); resultAnalyse.Analyse(); process_result_analyse(edit_, info_board_, resultAnalyse); fileTreeView->update(); } else { QMessageBox::critical(this, _("Error"), _("An error occurred during operation.")); return; } } void MainWindow::SlotFileDecrypt() { auto fileTreeView = edit_->SlotCurPageFileTreeView(); auto path = fileTreeView->GetSelected(); if (!path_pre_check(this, path)) return; #ifdef WINDOWS std::filesystem::path out_path = path.toStdU16String(); #else std::filesystem::path out_path = path.toStdString(); #endif if (out_path.extension() == ".asc" || out_path.extension() == ".gpg") { out_path = out_path.parent_path() / out_path.stem(); } else { out_path += ".out"; } if (exists(out_path)) { auto ret = QMessageBox::warning( this, _("Warning"), _("The target file already exists, do you need to overwrite it?"), QMessageBox::Ok | QMessageBox::Cancel); if (ret == QMessageBox::Cancel) return; } GpgDecrResult result = nullptr; gpgme_error_t error; bool if_error = false; process_operation(this, _("Decrypting"), [&](Thread::DataObjectPtr) -> int { try { error = GpgFileOpera::DecryptFile(path.toStdString(), out_path.u8string(), result); } catch (const std::runtime_error& e) { if_error = true; } return 0; }); if (!if_error) { auto resultAnalyse = GpgDecryptResultAnalyse(error, std::move(result)); resultAnalyse.Analyse(); process_result_analyse(edit_, info_board_, resultAnalyse); fileTreeView->update(); } else { QMessageBox::critical(this, _("Error"), _("An error occurred during operation.")); return; } // extract the tarball if (out_path.extension() == ".tar" && exists(out_path)) { bool ret = QMessageBox::information( this, _("Decrypting"), _("Do you want to extract and delete the decrypted tarball?"), QMessageBox::Ok | QMessageBox::Cancel); if (ret) { if (process_tarball_into_directory(this, out_path)) { QMessageBox::information(this, _("Decrypting"), _("Extracting tarball succeeded.")); // remove tarball std::filesystem::remove(out_path); } else { QMessageBox::critical(this, _("Decrypting"), _("Extracting tarball failed.")); } } } } void MainWindow::SlotFileSign() { auto fileTreeView = edit_->SlotCurPageFileTreeView(); auto path = fileTreeView->GetSelected(); if (!path_pre_check(this, path)) return; auto key_ids = m_key_list_->GetChecked(); auto keys = GpgKeyGetter::GetInstance().GetKeys(key_ids); if (keys->empty()) { QMessageBox::critical( this, _("No Key Checked"), _("Please check the key in the key toolbox on the right.")); return; } for (const auto& key : *keys) { if (!key.IsHasActualSigningCapability()) { QMessageBox::information( this, _("Invalid Operation"), QString(_("The selected key contains a key that does not actually " "have a sign usage.")) + "

" + _("for example the Following Key:") + "
" + QString::fromStdString(key.GetUIDs()->front().GetUID())); return; } } bool non_ascii_when_export = GlobalSettingStation::GetInstance().LookupSettings( "general.non_ascii_when_export", true); auto _channel = GPGFRONTEND_DEFAULT_CHANNEL; auto _extension = ".asc"; if (non_ascii_when_export) { _channel = GPGFRONTEND_NON_ASCII_CHANNEL; _extension = ".sig"; } #ifdef WINDOWS std::filesystem::path in_path = path.toStdU16String(); #else std::filesystem::path in_path = path.toStdString(); #endif auto sig_file_path = in_path; sig_file_path += _extension; if (exists(sig_file_path)) { auto ret = QMessageBox::warning( this, _("Warning"), QString(_("The signature file \"%1\" exists, " "do you need to overwrite it?")) .arg(sig_file_path.filename().u8string().c_str()), QMessageBox::Ok | QMessageBox::Cancel); if (ret == QMessageBox::Cancel) return; } GpgSignResult result = nullptr; gpgme_error_t error; bool if_error = false; process_operation(this, _("Signing"), [&](Thread::DataObjectPtr) -> int { try { error = GpgFileOpera::SignFile(std::move(keys), in_path.u8string(), sig_file_path.u8string(), result, _channel); } catch (const std::runtime_error& e) { if_error = true; } return 0; }); if (!if_error) { auto resultAnalyse = GpgSignResultAnalyse(error, std::move(result)); resultAnalyse.Analyse(); process_result_analyse(edit_, info_board_, resultAnalyse); fileTreeView->update(); } else { QMessageBox::critical(this, _("Error"), _("An error occurred during operation.")); return; } fileTreeView->update(); } void MainWindow::SlotFileVerify() { auto fileTreeView = edit_->SlotCurPageFileTreeView(); auto path = fileTreeView->GetSelected(); #ifdef WINDOWS std::filesystem::path in_path = path.toStdU16String(); #else std::filesystem::path in_path = path.toStdString(); #endif std::filesystem::path sign_file_path = in_path, data_file_path; bool non_ascii_when_export = GlobalSettingStation::GetInstance().LookupSettings( "general.non_ascii_when_export", true); auto _channel = GPGFRONTEND_DEFAULT_CHANNEL; if (non_ascii_when_export) { _channel = GPGFRONTEND_NON_ASCII_CHANNEL; } if (in_path.extension() == ".gpg") { swap(data_file_path, sign_file_path); } else if (in_path.extension() == ".sig" || in_path.extension() == ".asc") { data_file_path = sign_file_path.parent_path() / sign_file_path.stem(); } SPDLOG_DEBUG("sign_file_path: {} {}", sign_file_path.u8string(), sign_file_path.extension().u8string()); if (in_path.extension() != ".gpg") { bool ok; QString text = QInputDialog::getText( this, _("Origin file to verify"), _("Filepath"), QLineEdit::Normal, data_file_path.u8string().c_str(), &ok); if (ok && !text.isEmpty()) { data_file_path = text.toStdString(); } else { return; } } if (!is_regular_file(data_file_path) || (!sign_file_path.empty() && !is_regular_file(sign_file_path))) { QMessageBox::critical( this, _("Error"), _("Please select the appropriate origin file or signature file. " "Ensure that both are in this directory.")); return; } SPDLOG_DEBUG("data path: {}", data_file_path.u8string()); SPDLOG_DEBUG("sign path: {}", sign_file_path.u8string()); GpgVerifyResult result = nullptr; gpgme_error_t error; bool if_error = false; process_operation(this, _("Verifying"), [&](Thread::DataObjectPtr) -> int { try { error = GpgFileOpera::VerifyFile(data_file_path.u8string(), sign_file_path.u8string(), result, _channel); } catch (const std::runtime_error& e) { if_error = true; } return 0; }); if (!if_error) { auto result_analyse = GpgVerifyResultAnalyse(error, result); result_analyse.Analyse(); process_result_analyse(edit_, info_board_, result_analyse); if (result_analyse.GetStatus() == -2) import_unknown_key_from_keyserver(this, result_analyse); if (result_analyse.GetStatus() >= 0) show_verify_details(this, info_board_, error, result); fileTreeView->update(); } else { QMessageBox::critical(this, _("Error"), _("An error occurred during operation.")); return; } } void MainWindow::SlotFileEncryptSign() { auto fileTreeView = edit_->SlotCurPageFileTreeView(); auto path = fileTreeView->GetSelected(); if (!path_pre_check(this, path)) return; // check selected keys auto key_ids = m_key_list_->GetChecked(); auto p_keys = GpgKeyGetter::GetInstance().GetKeys(key_ids); if (p_keys->empty()) { QMessageBox::critical( this, _("No Key Checked"), _("Please check the key in the key toolbox on the right.")); return; } // check key abilities for (const auto& key : *p_keys) { bool key_can_encrypt = key.IsHasActualEncryptionCapability(); if (!key_can_encrypt) { QMessageBox::critical( nullptr, _("Invalid KeyPair"), QString(_("The selected keypair cannot be used for encryption.")) + "

" + _("For example the Following Key:") + "
" + QString::fromStdString(key.GetUIDs()->front().GetUID())); return; } } bool non_ascii_when_export = GlobalSettingStation::GetInstance().LookupSettings( "general.non_ascii_when_export", true); // get file info QFileInfo file_info(path); if (file_info.isDir()) { path = path + (file_info.isDir() ? ".tar" : ""); } auto _channel = GPGFRONTEND_DEFAULT_CHANNEL; auto _extension = ".asc"; if (non_ascii_when_export || file_info.isDir()) { _channel = GPGFRONTEND_NON_ASCII_CHANNEL; _extension = ".gpg"; } auto out_path = path + _extension; if (QFile::exists(out_path)) { auto ret = QMessageBox::warning( this, _("Warning"), _("The target file already exists, do you need to overwrite it?"), QMessageBox::Ok | QMessageBox::Cancel); if (ret == QMessageBox::Cancel) return; } auto signersPicker = new SignersPicker(this); QEventLoop loop; connect(signersPicker, &SignersPicker::finished, &loop, &QEventLoop::quit); loop.exec(); // return when canceled if (!signersPicker->GetStatus()) return; auto signer_key_ids = signersPicker->GetCheckedSigners(); auto p_signer_keys = GpgKeyGetter::GetInstance().GetKeys(signer_key_ids); // convert directory into tarball if (file_info.isDir()) { // stop if the process making tarball failed if (!process_directory_into_tarball(this, path)) { QMessageBox::critical(this, _("Error"), _("Unable to convert the folder into tarball.")); return; } } GpgEncrResult encr_result = nullptr; GpgSignResult sign_result = nullptr; gpgme_error_t error; bool if_error = false; process_operation( this, _("Encrypting and Signing"), [&](Thread::DataObjectPtr) -> int { try { error = GpgFileOpera::EncryptSignFile( std::move(p_keys), std::move(p_signer_keys), path.toStdString(), out_path.toStdString(), encr_result, sign_result, _channel); } catch (const std::runtime_error& e) { if_error = true; } return 0; }); if (!if_error) { auto encrypt_result = GpgEncryptResultAnalyse(error, std::move(encr_result)); auto sign_res = GpgSignResultAnalyse(error, std::move(sign_result)); encrypt_result.Analyse(); sign_res.Analyse(); process_result_analyse(edit_, info_board_, encrypt_result, sign_res); fileTreeView->update(); } else { QMessageBox::critical(this, _("Error"), _("An error occurred during operation.")); return; } // remove xxx.tar and only left xxx.tar.gpg if (file_info.isDir()) { auto selected_dir_path = std::filesystem::path(path.toStdString()); auto target_path = selected_dir_path.replace_extension(".tar"); if (exists(target_path)) { std::filesystem::remove(target_path); } } } void MainWindow::SlotFileDecryptVerify() { auto fileTreeView = edit_->SlotCurPageFileTreeView(); auto path = fileTreeView->GetSelected(); if (!path_pre_check(this, path)) return; #ifdef WINDOWS std::filesystem::path in_path = path.toStdU16String(); #else std::filesystem::path in_path = path.toStdString(); #endif std::filesystem::path out_path = in_path; if (in_path.extension() == ".asc" || in_path.extension() == ".gpg") { out_path = in_path.parent_path() / out_path.stem(); } else { out_path += ".out"; } SPDLOG_DEBUG("out path: {}", out_path.u8string()); if (QFile::exists(out_path.u8string().c_str())) { auto ret = QMessageBox::warning(this, _("Warning"), QString(_("The output file %1 already exists, do " "you need to overwrite it?")) .arg(out_path.filename().u8string().c_str()), QMessageBox::Ok | QMessageBox::Cancel); if (ret == QMessageBox::Cancel) return; } GpgDecrResult d_result = nullptr; GpgVerifyResult v_result = nullptr; gpgme_error_t error; bool if_error = false; process_operation( this, _("Decrypting and Verifying"), [&](Thread::DataObjectPtr) -> int { try { error = GpgFileOpera::DecryptVerifyFile( path.toStdString(), out_path.u8string(), d_result, v_result); } catch (const std::runtime_error& e) { if_error = true; } return 0; }); if (!if_error) { auto decrypt_res = GpgDecryptResultAnalyse(error, std::move(d_result)); auto verify_res = GpgVerifyResultAnalyse(error, v_result); decrypt_res.Analyse(); verify_res.Analyse(); process_result_analyse(edit_, info_board_, decrypt_res, verify_res); if (verify_res.GetStatus() == -2) import_unknown_key_from_keyserver(this, verify_res); if (verify_res.GetStatus() >= 0) show_verify_details(this, info_board_, error, v_result); fileTreeView->update(); } else { QMessageBox::critical(this, _("Error"), _("An error occurred during operation.")); return; } // extract the tarball if (out_path.extension() == ".tar" && exists(out_path)) { bool ret = QMessageBox::information( this, _("Decrypting"), _("Do you want to extract and delete the decrypted tarball?"), QMessageBox::Ok | QMessageBox::Cancel); if (ret) { if (process_tarball_into_directory(this, out_path)) { QMessageBox::information(this, _("Decrypting"), _("Extracting tarball succeeded.")); // remove tarball std::filesystem::remove(out_path); } else { QMessageBox::critical(this, _("Decrypting"), _("Extracting tarball failed.")); } } } } } // namespace GpgFrontend::UI