/**
* 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 "KeyServerImportDialog.h"
#include
#include "gpg/function/GpgKeyImportExporter.h"
#include "ui/SignalStation.h"
#include "ui/settings/GlobalSettingStation.h"
namespace GpgFrontend::UI {
KeyServerImportDialog::KeyServerImportDialog(bool automatic, QWidget* parent)
: QDialog(parent), m_automatic_(automatic) {
// Layout for messagebox
auto* messageLayout = new QHBoxLayout;
if (automatic) {
setWindowFlags(Qt::Window | Qt::WindowTitleHint | Qt::CustomizeWindowHint);
} else {
// Buttons
close_button_ = create_button(_("Close"), SLOT(close()));
import_button_ = create_button(_("Import ALL"), SLOT(slot_import()));
import_button_->setDisabled(true);
search_button_ = create_button(_("Search"), SLOT(slot_search()));
// Line edit for search string
search_label_ = new QLabel(QString(_("Search String")) + _(": "));
search_line_edit_ = new QLineEdit();
// combobox for keyserverlist
key_server_label_ = new QLabel(QString(_("Key Server")) + _(": "));
key_server_combo_box_ = create_comboBox();
// table containing the keys found
create_keys_table();
message_ = new QLabel;
message_->setFixedHeight(24);
icon_ = new QLabel;
icon_->setFixedHeight(24);
messageLayout->addWidget(icon_);
messageLayout->addWidget(message_);
messageLayout->addStretch();
}
// Network Waiting
waiting_bar_ = new QProgressBar();
waiting_bar_->setVisible(false);
waiting_bar_->setRange(0, 0);
waiting_bar_->setFixedWidth(200);
messageLayout->addWidget(waiting_bar_);
auto* mainLayout = new QGridLayout;
// 自动化调用界面布局
if (automatic) {
mainLayout->addLayout(messageLayout, 0, 0, 1, 3);
} else {
mainLayout->addWidget(search_label_, 1, 0);
mainLayout->addWidget(search_line_edit_, 1, 1);
mainLayout->addWidget(search_button_, 1, 2);
mainLayout->addWidget(key_server_label_, 2, 0);
mainLayout->addWidget(key_server_combo_box_, 2, 1);
mainLayout->addWidget(keys_table_, 3, 0, 1, 3);
mainLayout->addLayout(messageLayout, 4, 0, 1, 3);
// Layout for import and close button
auto* buttonsLayout = new QHBoxLayout;
buttonsLayout->addStretch();
buttonsLayout->addWidget(import_button_);
buttonsLayout->addWidget(close_button_);
mainLayout->addLayout(buttonsLayout, 6, 0, 1, 3);
}
this->setLayout(mainLayout);
if (automatic)
this->setWindowTitle(_("Update Keys from Keyserver"));
else
this->setWindowTitle(_("Import Keys from Keyserver"));
if (automatic) {
this->setFixedSize(240, 42);
} else {
auto pos = QPoint(150, 150);
LOG(INFO) << "parent" << parent;
if (parent) pos += parent->pos();
LOG(INFO) << "pos default" << pos.x() << pos.y();
auto size = QSize(800, 500);
try {
auto& settings = GlobalSettingStation::GetInstance().GetUISettings();
int x, y, width, height;
x = settings.lookup("window.import_from_keyserver.position.x");
y = settings.lookup("window.import_from_keyserver.position.y");
width = settings.lookup("window.import_from_keyserver.size.width");
height = settings.lookup("window.import_from_keyserver.size.height");
pos = QPoint(x, y);
size = QSize(width, height);
} catch (...) {
LOG(WARNING) << "cannot read pos or size from settings";
}
this->resize(size);
this->move(pos);
}
this->setModal(true);
connect(this, SIGNAL(SignalKeyImported()), SignalStation::GetInstance(),
SIGNAL(KeyDatabaseRefresh()));
// save window pos and size to configure file
connect(this, SIGNAL(finished(int)), this, SLOT(slot_save_window_state()));
}
QPushButton* KeyServerImportDialog::create_button(const QString& text,
const char* member) {
auto* button = new QPushButton(text);
connect(button, SIGNAL(clicked()), this, member);
return button;
}
QComboBox* KeyServerImportDialog::create_comboBox() {
auto* comboBox = new QComboBox;
comboBox->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
auto& settings = GlobalSettingStation::GetInstance().GetUISettings();
try {
auto& server_list = settings.lookup("keyserver.server_list");
const auto server_list_size = server_list.getLength();
for (int i = 0; i < server_list_size; i++) {
std::string server_url = server_list[i];
comboBox->addItem(server_url.c_str());
}
} catch (...) {
LOG(ERROR) << _("Setting Operation Error") << _("server_list");
}
// set default keyserver in combobox
try {
std::string default_server = settings.lookup("keyserver.default_server");
comboBox->setCurrentText(default_server.c_str());
} catch (...) {
LOG(ERROR) << _("Setting Operation Error") << _("default_server");
}
return comboBox;
}
void KeyServerImportDialog::create_keys_table() {
keys_table_ = new QTableWidget();
keys_table_->setColumnCount(4);
// always a whole row is marked
keys_table_->setSelectionBehavior(QAbstractItemView::SelectRows);
keys_table_->setEditTriggers(QAbstractItemView::NoEditTriggers);
// Make just one row selectable
keys_table_->setSelectionMode(QAbstractItemView::SingleSelection);
QStringList labels;
labels << _("UID") << _("Creation date") << _("KeyID") << _("Tag");
keys_table_->horizontalHeader()->setSectionResizeMode(
0, QHeaderView::ResizeToContents);
keys_table_->setHorizontalHeaderLabels(labels);
keys_table_->verticalHeader()->hide();
connect(keys_table_, SIGNAL(cellActivated(int, int)), this,
SLOT(slot_import()));
}
void KeyServerImportDialog::set_message(const QString& text, bool error) {
if (m_automatic_) return;
message_->setText(text);
if (error) {
icon_->setPixmap(
QPixmap(":error.png").scaled(QSize(24, 24), Qt::KeepAspectRatio));
} else {
icon_->setPixmap(
QPixmap(":info.png").scaled(QSize(24, 24), Qt::KeepAspectRatio));
}
}
void KeyServerImportDialog::slot_search() {
if (search_line_edit_->text().isEmpty()) {
set_message("" + QString(_("Text is empty.")) + "
", false);
return;
}
QUrl url_from_remote = key_server_combo_box_->currentText() +
"/pks/lookup?search=" + search_line_edit_->text() +
"&op=index&options=mr";
network_access_manager_ = new QNetworkAccessManager(this);
QNetworkReply* reply =
network_access_manager_->get(QNetworkRequest(url_from_remote));
connect(reply, SIGNAL(finished()), this, SLOT(slot_search_finished()));
set_loading(true);
this->search_button_->setDisabled(true);
this->key_server_combo_box_->setDisabled(true);
this->search_line_edit_->setReadOnly(true);
this->import_button_->setDisabled(true);
while (reply->isRunning()) {
QApplication::processEvents();
}
this->search_button_->setDisabled(false);
this->key_server_combo_box_->setDisabled(false);
this->search_line_edit_->setReadOnly(false);
this->import_button_->setDisabled(false);
set_loading(false);
}
void KeyServerImportDialog::slot_search_finished() {
LOG(INFO) << "KeyServerImportDialog::slot_search_finished Called";
auto* reply = qobject_cast(sender());
keys_table_->clearContents();
keys_table_->setRowCount(0);
QString first_line = QString(reply->readLine(1024));
auto error = reply->error();
if (error != QNetworkReply::NoError) {
qDebug() << "Error From Reply" << reply->errorString();
switch (error) {
case QNetworkReply::ContentNotFoundError:
set_message(_("Not Key Found"), true);
break;
case QNetworkReply::TimeoutError:
set_message(_("Timeout"), true);
break;
case QNetworkReply::HostNotFoundError:
set_message(_("Key Server Not Found"), true);
break;
default:
set_message(_("Connection Error"), true);
}
return;
}
if (first_line.contains("Error")) {
QString text = QString(reply->readLine(1024));
if (text.contains("Too many responses")) {
set_message(
"" + QString(_("Too many responses from keyserver!")) + "
",
true);
return;
} else if (text.contains("No keys found")) {
// if string looks like hex string, search again with 0x prepended
QRegExp rx("[0-9A-Fa-f]*");
QString query = search_line_edit_->text();
if (rx.exactMatch(query)) {
set_message(
"" +
QString(_("No keys found, input may be kexId, retrying search "
"with 0x.")) +
"
",
true);
search_line_edit_->setText(query.prepend("0x"));
this->slot_search();
return;
} else {
set_message(
"" + QString(_("No keys found containing the search string!")) +
"
",
true);
return;
}
} else if (text.contains("Insufficiently specific words")) {
set_message("" +
QString(_("Insufficiently specific search string!")) +
"
",
true);
return;
} else {
set_message(text, true);
return;
}
} else {
int row = 0;
bool strikeout = false;
while (reply->canReadLine()) {
auto line_buff = reply->readLine().trimmed();
QString decoded =
QString::fromUtf8(line_buff.constData(), line_buff.size());
QStringList line = decoded.split(":");
// TODO: have a look at two following pub lines
if (line[0] == "pub") {
strikeout = false;
QString flags = line[line.size() - 1];
keys_table_->setRowCount(row + 1);
// flags can be "d" for disabled, "r" for revoked
// or "e" for expired
if (flags.contains("r") or flags.contains("d") or flags.contains("e")) {
strikeout = true;
if (flags.contains("e")) {
keys_table_->setItem(row, 3,
new QTableWidgetItem(QString("expired")));
}
if (flags.contains("r")) {
keys_table_->setItem(row, 3,
new QTableWidgetItem(QString(_("revoked"))));
}
if (flags.contains("d")) {
keys_table_->setItem(row, 3,
new QTableWidgetItem(QString(_("disabled"))));
}
}
QStringList line2 = QString(reply->readLine()).split(":");
auto* uid = new QTableWidgetItem();
if (line2.size() > 1) {
uid->setText(line2[1]);
keys_table_->setItem(row, 0, uid);
}
auto* creation_date = new QTableWidgetItem(
QDateTime::fromTime_t(line[4].toInt()).toString("dd. MMM. yyyy"));
keys_table_->setItem(row, 1, creation_date);
auto* keyid = new QTableWidgetItem(line[1]);
keys_table_->setItem(row, 2, keyid);
if (strikeout) {
QFont strike = uid->font();
strike.setStrikeOut(true);
uid->setFont(strike);
creation_date->setFont(strike);
keyid->setFont(strike);
}
row++;
} else {
if (line[0] == "uid") {
QStringList l;
int height = keys_table_->rowHeight(row - 1);
keys_table_->setRowHeight(row - 1, height + 16);
QString tmp = keys_table_->item(row - 1, 0)->text();
tmp.append(QString("\n") + line[1]);
auto* tmp1 = new QTableWidgetItem(tmp);
keys_table_->setItem(row - 1, 0, tmp1);
if (strikeout) {
QFont strike = tmp1->font();
strike.setStrikeOut(true);
tmp1->setFont(strike);
}
}
}
set_message(
QString("") +
QString(_("%1 keys found. Double click a key to import it."))
.arg(row) +
"
",
false);
}
keys_table_->resizeColumnsToContents();
import_button_->setDisabled(keys_table_->size().isEmpty());
}
reply->deleteLater();
}
void KeyServerImportDialog::slot_import() {
LOG(INFO) << _("Current Row") << keys_table_->currentRow();
if (keys_table_->currentRow() > -1) {
QString keyid = keys_table_->item(keys_table_->currentRow(), 2)->text();
SlotImport(QStringList(keyid), key_server_combo_box_->currentText());
}
}
void KeyServerImportDialog::SlotImport(const KeyIdArgsListPtr& keys) {
std::string target_keyserver;
if (key_server_combo_box_ != nullptr) {
target_keyserver = key_server_combo_box_->currentText().toStdString();
}
if (target_keyserver.empty()) {
try {
auto& settings = GlobalSettingStation::GetInstance().GetUISettings();
target_keyserver = settings.lookup("keyserver.default_server").c_str();
LOG(INFO) << _("Set target Key Server to default Key Server")
<< target_keyserver;
} catch (...) {
LOG(ERROR) << _("Cannot read default_keyserver From Settings");
QMessageBox::critical(
nullptr, _("Default Keyserver Not Found"),
_("Cannot read default keyserver from your settings, "
"please set a default keyserver first"));
return;
}
}
auto key_ids = QStringList();
for (const auto& key_id : *keys)
key_ids.append(QString::fromStdString(key_id));
SlotImport(key_ids, QUrl(target_keyserver.c_str()));
}
void KeyServerImportDialog::SlotImport(const QStringList& keyIds,
const QUrl& keyserverUrl) {
for (const auto& keyId : keyIds) {
QUrl req_url(keyserverUrl.scheme() + "://" + keyserverUrl.host() +
"/pks/lookup?op=get&search=0x" + keyId + "&options=mr");
LOG(INFO) << "request url" << req_url.toString().toStdString();
auto manager = new QNetworkAccessManager(this);
QNetworkReply* reply = manager->get(QNetworkRequest(req_url));
connect(reply, &QNetworkReply::finished, this,
[&, keyId]() { this->slot_import_finished(keyId); });
LOG(INFO) << "loading start";
set_loading(true);
while (reply->isRunning()) QApplication::processEvents();
set_loading(false);
LOG(INFO) << "loading done";
}
}
void KeyServerImportDialog::slot_import_finished(const QString& keyid) {
LOG(INFO) << _("Called");
auto* reply = qobject_cast(sender());
QByteArray key = reply->readAll();
auto error = reply->error();
if (error != QNetworkReply::NoError) {
LOG(ERROR) << "Error From Reply" << reply->errorString().toStdString();
if (!m_automatic_) {
switch (error) {
case QNetworkReply::ContentNotFoundError:
set_message(_("Key Not Found"), true);
break;
case QNetworkReply::TimeoutError:
set_message(_("Timeout"), true);
break;
case QNetworkReply::HostNotFoundError:
set_message(_("Key Server Not Found"), true);
break;
default:
set_message(_("Connection Error"), true);
}
} else {
switch (error) {
case QNetworkReply::ContentNotFoundError:
QMessageBox::critical(
nullptr, _("Public key Not Found"),
QString(_("Public key fingerprint %1 not found in the Keyserver"))
.arg(keyid));
break;
case QNetworkReply::TimeoutError:
QMessageBox::critical(nullptr, _("Timeout"), "Connection timeout");
break;
case QNetworkReply::HostNotFoundError:
QMessageBox::critical(nullptr, _("Host Not Found"),
"cannot resolve the default Keyserver");
break;
default:
QMessageBox::critical(nullptr, _("Connection Error"),
_("General Connection Error"));
}
}
if (m_automatic_) {
setWindowFlags(Qt::Window | Qt::WindowTitleHint |
Qt::CustomizeWindowHint | Qt::WindowCloseButtonHint);
}
return;
}
reply->deleteLater();
this->import_keys(std::make_unique(key.constData(), key.length()));
if (!m_automatic_) {
set_message(QString("") + _("Key Imported") + "
", false);
}
}
void KeyServerImportDialog::import_keys(ByteArrayPtr in_data) {
GpgImportInformation result =
GpgKeyImportExporter::GetInstance().ImportKey(std::move(in_data));
emit SignalKeyImported();
QWidget* _parent = qobject_cast(parent());
if (m_automatic_) {
auto dialog = new KeyImportDetailDialog(result, true, _parent);
dialog->show();
this->accept();
} else {
auto dialog = new KeyImportDetailDialog(result, false, _parent);
dialog->exec();
}
}
void KeyServerImportDialog::set_loading(bool status) {
waiting_bar_->setVisible(status);
if (!m_automatic_) {
icon_->setVisible(!status);
message_->setVisible(!status);
}
}
KeyServerImportDialog::KeyServerImportDialog(QWidget* parent)
: QDialog(parent), m_automatic_(true) {
setWindowFlags(Qt::Window | Qt::WindowTitleHint | Qt::CustomizeWindowHint);
// Network Waiting
waiting_bar_ = new QProgressBar();
waiting_bar_->setVisible(false);
waiting_bar_->setRange(0, 0);
waiting_bar_->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
waiting_bar_->setTextVisible(false);
// Layout for messagebox
auto* layout = new QHBoxLayout();
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(0);
layout->addWidget(waiting_bar_);
key_server_combo_box_ = create_comboBox();
this->setLayout(layout);
this->setWindowTitle(_("Update Keys from Keyserver"));
this->setFixedSize(240, 42);
this->setModal(true);
}
void KeyServerImportDialog::slot_save_window_state() {
LOG(INFO) << _("Called");
if (m_automatic_) return;
auto& settings =
GpgFrontend::UI::GlobalSettingStation::GetInstance().GetUISettings();
if (!settings.exists("window") ||
settings.lookup("window").getType() != libconfig::Setting::TypeGroup)
settings.add("window", libconfig::Setting::TypeGroup);
auto& window = settings["window"];
if (!window.exists("import_from_keyserver") ||
window.lookup("import_from_keyserver").getType() !=
libconfig::Setting::TypeGroup)
window.add("import_from_keyserver", libconfig::Setting::TypeGroup);
auto& import_from_keyserver = window["import_from_keyserver"];
if (!import_from_keyserver.exists("position") ||
import_from_keyserver.lookup("position").getType() !=
libconfig::Setting::TypeGroup) {
auto& position =
import_from_keyserver.add("position", libconfig::Setting::TypeGroup);
position.add("x", libconfig::Setting::TypeInt) = pos().x();
position.add("y", libconfig::Setting::TypeInt) = pos().y();
} else {
import_from_keyserver["position"]["x"] = pos().x();
import_from_keyserver["position"]["y"] = pos().y();
}
if (!import_from_keyserver.exists("size") ||
import_from_keyserver.lookup("size").getType() !=
libconfig::Setting::TypeGroup) {
auto& size =
import_from_keyserver.add("size", libconfig::Setting::TypeGroup);
size.add("width", libconfig::Setting::TypeInt) = QWidget::width();
size.add("height", libconfig::Setting::TypeInt) = QWidget::height();
} else {
import_from_keyserver["size"]["width"] = QWidget::width();
import_from_keyserver["size"]["height"] = QWidget::height();
}
GlobalSettingStation::GetInstance().SyncSettings();
}
} // namespace GpgFrontend::UI