diff options
Diffstat (limited to 'src/m_pgp_inspect/PGPInspectDialog.cpp')
| -rw-r--r-- | src/m_pgp_inspect/PGPInspectDialog.cpp | 237 |
1 files changed, 237 insertions, 0 deletions
diff --git a/src/m_pgp_inspect/PGPInspectDialog.cpp b/src/m_pgp_inspect/PGPInspectDialog.cpp new file mode 100644 index 0000000..e8c58cf --- /dev/null +++ b/src/m_pgp_inspect/PGPInspectDialog.cpp @@ -0,0 +1,237 @@ +/** + * Copyright (C) 2021-2024 Saturneric <[email protected]> + * + * 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 <https://www.gnu.org/licenses/>. + * + * 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 <[email protected]> starting on May 12, 2021. + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#include "PGPInspectDialog.h" + +#include <GFModule.h> +#include <GFSDKPgp.h> +#include <GFSDKUI.h> + +#include <QHeaderView> +#include <QLabel> +#include <QLineEdit> +#include <QTreeWidget> +#include <QVBoxLayout> + +namespace { + +/// Which column holds what. Packet rows use the first only; a field row puts +/// its label there and its value beside it. +constexpr int kLabelColumn = 0; +constexpr int kValueColumn = 1; + +auto HumanSize(qint64 bytes) -> QString { return UDUP(GFUIHumanSize(bytes)); } + +} // namespace + +auto PGPInspectCurrentTabBytes() -> QByteArray { + auto* content = GFUITakeCurrentEditorContent(); + if (content == nullptr) return {}; + + const auto* data = static_cast<const char*>(GFBufferData(content)); + const auto size = static_cast<qsizetype>(GFBufferSize(content)); + auto bytes = QByteArray(data == nullptr ? "" : data, size); + GFBufferRelease(content); + return bytes; +} + +auto PGPInspectBytes(const QByteArray& data) -> PGPInspectDocument { + PGPInspectDocument document; + if (data.isEmpty()) return document; + + auto* buffer = + GFBufferNewFromBytes(data.constData(), static_cast<size_t>(data.size())); + if (buffer == nullptr) return document; + + char* json = nullptr; + const auto status = GFPgpInspectData(buffer, &json); + GFBufferRelease(buffer); + + if (status != 0 || json == nullptr) return document; + return ParsePGPInspectDocument(UDUP(json).toUtf8()); +} + +PGPInspectDialog::PGPInspectDialog(const PGPInspectDocument& document, + qint64 size, QWidget* parent) + : QDialog(parent), document_(document), size_(size) { + setWindowTitle(QCoreApplication::translate("GTrC", "OpenPGP Structure")); + setAttribute(Qt::WA_DeleteOnClose); + resize(820, 620); + create_widgets(); + render_document(); +} + +void PGPInspectDialog::create_widgets() { + search_ = new QLineEdit(this); + search_->setClearButtonEnabled(true); + search_->setPlaceholderText( + QCoreApplication::translate("GTrC", "Search packets, fields and values")); + connect(search_, &QLineEdit::textChanged, this, + &PGPInspectDialog::slot_filter_changed); + + tree_ = new QTreeWidget(this); + tree_->setColumnCount(2); + tree_->setHeaderLabels({QCoreApplication::translate("GTrC", "Structure"), + QCoreApplication::translate("GTrC", "Value")}); + tree_->header()->setSectionResizeMode(kLabelColumn, + QHeaderView::ResizeToContents); + tree_->header()->setSectionResizeMode(kValueColumn, QHeaderView::Stretch); + tree_->setUniformRowHeights(true); + + summary_label_ = new QLabel(this); + summary_label_->setWordWrap(true); + + auto* layout = new QVBoxLayout(); + layout->addWidget(search_); + layout->addWidget(tree_); + layout->addWidget(summary_label_); + setLayout(layout); +} + +void PGPInspectDialog::render_document() { + tree_->clear(); + + if (!document_.valid) { + summary_label_->setText( + document_.parse_error.isEmpty() + ? QCoreApplication::translate( + "GTrC", "The current tab holds no OpenPGP data.") + : document_.parse_error); + return; + } + + int index = 1; + for (const auto& block : document_.blocks) { + auto* item = new QTreeWidgetItem(tree_); + item->setText(kLabelColumn, PGPInspectBlockSummary(block, index++)); + + // The block's own facts are rows like any other, so the search box reaches + // the armor headers and the CRC verdict too. + QVector<PGPInspectField> facts; + if (block.has_armor) { + facts.push_back({QCoreApplication::translate("GTrC", "Armor Type"), + block.armor.block_type}); + facts.push_back( + {QCoreApplication::translate("GTrC", "CRC24"), block.armor.crc24}); + facts += block.armor.headers; + } + if (block.has_cleartext) { + facts.push_back({QCoreApplication::translate("GTrC", "Signed Text"), + HumanSize(block.cleartext_text_size)}); + facts += block.cleartext_headers; + } + if (!block.error.isEmpty()) { + facts.push_back( + {QCoreApplication::translate("GTrC", "Problem"), block.error}); + } + add_fields(item, facts); + + if (block.has_armor && block.armor.crc24 == QLatin1String("mismatch")) { + item->setForeground(kLabelColumn, QBrush(QColor(GFUIDangerColor(this)))); + } else if (!block.error.isEmpty()) { + item->setForeground(kLabelColumn, QBrush(QColor(GFUIWarningColor(this)))); + } + + add_packets(item, block.packets); + } + + tree_->expandAll(); + + // The count goes through a local: a function call inside a translate() + // %n argument makes lupdate silently skip EVERY string in the file. + const int packet_count = document_.PacketCount(); + auto summary = QCoreApplication::translate("GTrC", "%1, %2, %n packet(s)", + nullptr, packet_count) + .arg(document_.format, HumanSize(size_)); + if (!document_.errors.isEmpty()) { + summary += + QLatin1String(" - ") + document_.errors.join(QLatin1String("; ")); + } + summary_label_->setText(summary); +} + +void PGPInspectDialog::add_packets(QTreeWidgetItem* parent, + const QVector<PGPInspectPacket>& packets) { + int index = 1; + for (const auto& packet : packets) { + auto* item = new QTreeWidgetItem(parent); + item->setText(kLabelColumn, PGPInspectPacketSummary(packet, index++)); + + if (packet.Malformed()) { + item->setForeground(kLabelColumn, QBrush(QColor(GFUIWarningColor(this)))); + } + + auto fields = packet.fields; + if (packet.Malformed()) { + fields.push_back( + {QCoreApplication::translate("GTrC", "Problem"), packet.error}); + } + add_fields(item, fields); + + add_packets(item, packet.children); + } +} + +void PGPInspectDialog::add_fields(QTreeWidgetItem* parent, + const QVector<PGPInspectField>& fields) { + for (const auto& field : fields) { + auto* row = new QTreeWidgetItem(parent); + row->setText(kLabelColumn, field.label); + row->setText(kValueColumn, field.value); + row->setForeground(kLabelColumn, QBrush(QColor(GFUIMutedTextColor(this)))); + } +} + +void PGPInspectDialog::slot_filter_changed(const QString& needle) { + for (int i = 0; i < tree_->topLevelItemCount(); ++i) { + PGPInspectFilterItem(tree_->topLevelItem(i), needle, false); + } + if (!needle.isEmpty()) tree_->expandAll(); +} + +auto PGPInspectFilterItem(QTreeWidgetItem* item, const QString& needle, + bool ancestor_matched) -> bool { + if (item == nullptr) return false; + + const auto matched = needle.isEmpty() || + item->text(0).contains(needle, Qt::CaseInsensitive) || + item->text(1).contains(needle, Qt::CaseInsensitive); + + // A matching packet keeps its whole subtree: the reader searched for the + // packet, so hiding the fields that describe it would answer the wrong + // question. + auto descendant_matched = false; + for (int i = 0; i < item->childCount(); ++i) { + descendant_matched |= PGPInspectFilterItem(item->child(i), needle, + ancestor_matched || matched); + } + + const auto visible = matched || descendant_matched || ancestor_matched; + item->setHidden(!visible); + return visible; +} |
