aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/core/function/gpg/GpgAttributeHelper.cpp135
-rw-r--r--src/core/function/gpg/GpgAttributeHelper.h77
-rw-r--r--src/core/function/gpg/GpgCommandExecutor.cpp39
-rw-r--r--src/core/function/gpg/GpgCommandExecutor.h9
-rw-r--r--ui/KeyPairPhotosTab.ui139
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>