diff options
| -rw-r--r-- | src/core/function/gpg/GpgAttributeHelper.cpp | 135 | ||||
| -rw-r--r-- | src/core/function/gpg/GpgAttributeHelper.h | 77 | ||||
| -rw-r--r-- | src/core/function/gpg/GpgCommandExecutor.cpp | 39 | ||||
| -rw-r--r-- | src/core/function/gpg/GpgCommandExecutor.h | 9 | ||||
| -rw-r--r-- | ui/KeyPairPhotosTab.ui | 139 |
5 files changed, 381 insertions, 18 deletions
diff --git a/src/core/function/gpg/GpgAttributeHelper.cpp b/src/core/function/gpg/GpgAttributeHelper.cpp new file mode 100644 index 00000000..237b1c45 --- /dev/null +++ b/src/core/function/gpg/GpgAttributeHelper.cpp @@ -0,0 +1,135 @@ +/** + * 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 "GpgAttributeHelper.h" + +namespace { +auto StripHeader(QVector<GpgFrontend::GpgAttrInfo>& infos) -> void { + for (auto& a : infos) { + if (a.blob.size() < 16) continue; + const auto* p = reinterpret_cast<const uchar*>(a.blob.constData()); + + int hdr_len = p[0] | (p[1] << 8); + if (hdr_len < 16 || a.blob.size() < hdr_len) continue; + uchar version = p[2]; + uchar enc = p[3]; // 0x01 == JPEG + a.payload = a.blob.mid(hdr_len); + + if (enc == 0x01 || + (a.payload.size() >= 3 && static_cast<uchar>(a.payload[0]) == 0xFF && + static_cast<uchar>(a.payload[1]) == 0xD8 && + static_cast<uchar>(a.payload[2]) == 0xFF)) { + a.ext = "jpg"; + } else { + a.ext = "bin"; + } + Q_UNUSED(version); + a.valid = true; + } +} +} // namespace + +namespace GpgFrontend { +GpgAttributeHelper::GpgAttributeHelper(int channel) + : GpgFrontend::SingletonFunctionObject<GpgAttributeHelper>(channel) {} + +GpgAttributeHelper::~GpgAttributeHelper() = default; + +auto GpgAttributeHelper::GetAttributes(const QString& key_id) + -> QContainer<GpgAttrInfo> { + auto [exit_code, out, err] = gce_.GpgExecuteSync({{}, + { + "--batch", + "--no-tty", + "--attribute-fd", + "2", + "--status-fd", + "1", + "--logger-fd", + "1", + "--list-keys", + key_id, + }, + {}}); + auto attr_bin_data = err; + auto attr_status_data = out; + + LOG_D() << "GPG attribute status data size: " << attr_status_data.size() + << " bytes."; + LOG_D() << "GPG attribute binary data size: " << attr_bin_data.size() + << " bytes."; + + QVector<GpgFrontend::GpgAttrInfo> v; + const QStringList lines = + QString::fromUtf8(attr_status_data).split('\n', Qt::SkipEmptyParts); + + for (const QString& line : lines) { + if (!line.startsWith("[GNUPG:] ATTRIBUTE ")) continue; + + QString rest = line.mid(QString("[GNUPG:] ATTRIBUTE ").size()); + const QStringList cols = rest.split(' ', Qt::SkipEmptyParts); + if (cols.size() < 8) continue; + + GpgFrontend::GpgAttrInfo a; + a.fpr = cols[0]; + a.octets = cols[1].toLongLong(); + a.type = cols[2].toInt(); + a.index = cols[3].toInt(); + a.count = cols[4].toInt(); + a.ts = cols[5].toLongLong(); + a.exp = cols[6].toLongLong(); + a.flags = cols[7].toInt(); + + v.push_back(a); + } + + bool is_valid = true; + qint64 off = 0; + for (auto& a : v) { + if (off + a.octets > attr_bin_data.size()) { + LOG_W() << "Attribute blob size exceeds available binary data size."; + is_valid = false; + break; + } + + LOG_D() << "Splitting attribute blob: FPR=" << a.fpr + << ", Octets=" << a.octets << ", Type=" << a.type + << ", Index=" << a.index << ", Count=" << a.count << ", TS=" << a.ts + << ", Exp=" << a.exp << ", Flags=" << a.flags; + a.blob = attr_bin_data.mid(off, a.octets); + off += a.octets; + } + + LOG_D() << "Total attribute binary data size: " << attr_bin_data.size() + << " bytes, processed size: " << off << " bytes."; + + if (is_valid) StripHeader(v); + return v; +} + +} // namespace GpgFrontend
\ No newline at end of file diff --git a/src/core/function/gpg/GpgAttributeHelper.h b/src/core/function/gpg/GpgAttributeHelper.h new file mode 100644 index 00000000..9d9a2e9f --- /dev/null +++ b/src/core/function/gpg/GpgAttributeHelper.h @@ -0,0 +1,77 @@ +/** + * 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 + * + */ + +#pragma once + +#include "core/function/gpg/GpgCommandExecutor.h" + +namespace GpgFrontend { +struct GpgAttrInfo { + QString fpr; + qint64 octets = 0; // contains size in bytes + int type = 0; // 1=Image (Photo ID) + int index = 0; // starts at 0 + int count = 0; // total number of attributes of this type + qint64 ts = 0; // timestamp + qint64 exp = 0; // expiration time + int flags = 0; // 0x01 primary uid, 0x02 revoked, 0x04 expired + QByteArray blob; + QString ext; + QByteArray payload; + bool valid = false; +}; + +class GF_CORE_EXPORT GpgAttributeHelper + : public SingletonFunctionObject<GpgAttributeHelper> { + public: + /** + * @brief Construct a new Gpg Attribute Helper object + * + * @param channel + */ + explicit GpgAttributeHelper(int channel); + + /** + * @brief Destroy the Gpg Attribute Helper object + * + */ + ~GpgAttributeHelper() override; + + /** + * @brief Get the Attributes object + * + * @param key_id + * @return QContainer<GpgAttrInfo> + */ + auto GetAttributes(const QString& key_id) -> QContainer<GpgAttrInfo>; + + private: + GpgCommandExecutor& gce_ = GpgCommandExecutor::GetInstance(GetChannel()); +}; + +} // namespace GpgFrontend
\ No newline at end of file diff --git a/src/core/function/gpg/GpgCommandExecutor.cpp b/src/core/function/gpg/GpgCommandExecutor.cpp index 72988a55..b2a54fb9 100644 --- a/src/core/function/gpg/GpgCommandExecutor.cpp +++ b/src/core/function/gpg/GpgCommandExecutor.cpp @@ -49,16 +49,18 @@ auto BuildTaskFromExecCtx(const GpgCommandExecutor::ExecuteContext &context) LOG_D() << "data object args count of cmd executor result callback:" << obj->GetObjectSize(); - if (!obj->Check<int, QByteArray, GpgCommandExecutorCallback>()) { - FLOG_W("data object checking failed"); + if (!obj->Check<int, QByteArray, QByteArray, + GpgCommandExecutorCallback>()) { + LOG_W() << "failed gpg command: " << cmd; return; } auto code = ExtractParams<int>(obj, 0); auto out = ExtractParams<QByteArray>(obj, 1); - auto cb = ExtractParams<GpgCommandExecutorCallback>(obj, 2); + auto err = ExtractParams<QByteArray>(obj, 2); + auto cb = ExtractParams<GpgCommandExecutorCallback>(obj, 3); - cb(code, out, {}); + cb(code, out, err); }; Thread::Task::TaskRunnable runner = @@ -87,7 +89,7 @@ auto BuildTaskFromExecCtx(const GpgCommandExecutor::ExecuteContext &context) pcs->moveToThread(QThread::currentThread()); // set process channel mode // this is to make sure we can get all output from stdout and stderr - pcs->setProcessChannelMode(QProcess::MergedChannels); + pcs->setProcessChannelMode(QProcess::SeparateChannels); pcs->setProgram(cmd); // set arguments @@ -118,6 +120,7 @@ auto BuildTaskFromExecCtx(const GpgCommandExecutor::ExecuteContext &context) pcs->waitForFinished(); auto out = pcs->readAllStandardOutput(); + auto err = pcs->readAllStandardError(); auto code = pcs->exitCode(); LOG_D() << "\n==== Process Execution Summary ====\n" @@ -126,12 +129,14 @@ auto BuildTaskFromExecCtx(const GpgCommandExecutor::ExecuteContext &context) << "Exit Code: " << code << "\n" << "---- Standard Output ----\n" << out << "\n" + << "---- Standard Error ----\n" + << err << "\n" << "==============================="; pcs->close(); pcs->deleteLater(); - data_object->Swap({code, out, callback}); + data_object->Swap({code, out, err, callback}); return 0; }; @@ -270,26 +275,30 @@ auto PrepareContext(const GpgContext &ctx_, const QString &path, return {true, ctx}; } -auto PrepareExecuteSyncContext(const GpgContext &ctx_, const QString &path, - const GpgCommandExecutor::ExecuteContext - &context) -> std::tuple<int, QString> { +auto PrepareExecuteSyncContext( + const GpgContext &ctx_, const QString &path, + const GpgCommandExecutor::ExecuteContext &context) + -> std::tuple<int, QByteArray, QByteArray> { auto ctx = context; int pcs_exit_code; - QString pcs_stdout; + QByteArray pcs_stdout; + QByteArray pcs_stderr; // proxy - ctx.cb_func = [&](int exit_code, const QString &out, const QString &) { + ctx.cb_func = [&](int exit_code, const QByteArray &out, + const QByteArray &err) { pcs_exit_code = exit_code; pcs_stdout = out; + pcs_stderr = err; }; auto [ret, ctx2] = PrepareContext(ctx_, path, ctx); if (ret) { GpgFrontend::GpgCommandExecutor::ExecuteSync(ctx2); - return {pcs_exit_code, pcs_stdout}; + return {pcs_exit_code, pcs_stdout, pcs_stderr}; } - return {-1, "invalid context"}; + return {-1, "invalid context", ""}; } void PrepareExecuteAsyncContext( @@ -300,7 +309,7 @@ void PrepareExecuteAsyncContext( } auto GpgCommandExecutor::GpgExecuteSync(const ExecuteContext &context) - -> std::tuple<int, QString> { + -> std::tuple<int, QByteArray, QByteArray> { return PrepareExecuteSyncContext(ctx_, Module::RetrieveRTValueTypedOrDefault<>( "core", "gpgme.ctx.app_path", QString{}), @@ -309,7 +318,7 @@ auto GpgCommandExecutor::GpgExecuteSync(const ExecuteContext &context) auto GpgCommandExecutor::GpgConfExecuteSync(const ExecuteContext &context) - -> std::tuple<int, QString> { + -> std::tuple<int, QByteArray, QByteArray> { return PrepareExecuteSyncContext( ctx_, Module::RetrieveRTValueTypedOrDefault<>("core", "gpgme.ctx.gpgconf_path", diff --git a/src/core/function/gpg/GpgCommandExecutor.h b/src/core/function/gpg/GpgCommandExecutor.h index e565f03b..ca0213fe 100644 --- a/src/core/function/gpg/GpgCommandExecutor.h +++ b/src/core/function/gpg/GpgCommandExecutor.h @@ -34,7 +34,8 @@ namespace GpgFrontend { -using GpgCommandExecutorCallback = std::function<void(int, QString, QString)>; +using GpgCommandExecutorCallback = + std::function<void(int, QByteArray, QByteArray)>; using GpgCommandExecutorInterator = std::function<void(QProcess *)>; /** @@ -117,13 +118,15 @@ class GF_CORE_EXPORT GpgCommandExecutor * @brief * */ - auto GpgExecuteSync(const ExecuteContext &) -> std::tuple<int, QString>; + auto GpgExecuteSync(const ExecuteContext &) + -> std::tuple<int, QByteArray, QByteArray>; /** * @brief * */ - auto GpgConfExecuteSync(const ExecuteContext &) -> std::tuple<int, QString>; + auto GpgConfExecuteSync(const ExecuteContext &) + -> std::tuple<int, QByteArray, QByteArray>; /** * @brief diff --git a/ui/KeyPairPhotosTab.ui b/ui/KeyPairPhotosTab.ui new file mode 100644 index 00000000..f5ef416d --- /dev/null +++ b/ui/KeyPairPhotosTab.ui @@ -0,0 +1,139 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>KeyPairPhotosTab</class> + <widget class="QWidget" name="KeyPairPhotosTab"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>576</width> + <height>662</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QGroupBox" name="listGroupBox"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="title"> + <string>Photo List</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <item> + <widget class="QTableWidget" name="tableWidget"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="editTriggers"> + <set>QAbstractItemView::EditTrigger::NoEditTriggers</set> + </property> + <property name="alternatingRowColors"> + <bool>true</bool> + </property> + <property name="selectionMode"> + <enum>QAbstractItemView::SelectionMode::SingleSelection</enum> + </property> + <property name="selectionBehavior"> + <enum>QAbstractItemView::SelectionBehavior::SelectRows</enum> + </property> + <attribute name="horizontalHeaderStretchLastSection"> + <bool>true</bool> + </attribute> + <attribute name="verticalHeaderStretchLastSection"> + <bool>false</bool> + </attribute> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="Line" name="line"> + <property name="orientation"> + <enum>Qt::Orientation::Horizontal</enum> + </property> + </widget> + </item> + <item> + <widget class="QGroupBox" name="viewerGroupBox"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="title"> + <string>Photo Viewer</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <property name="leftMargin"> + <number>5</number> + </property> + <property name="topMargin"> + <number>5</number> + </property> + <property name="rightMargin"> + <number>5</number> + </property> + <property name="bottomMargin"> + <number>5</number> + </property> + <item> + <widget class="QLabel" name="photoLabel"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="autoFillBackground"> + <bool>true</bool> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> |
