Page Menu
Home
GnuPG
Search
Configure Global Search
Log In
Files
F34024039
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Size
14 KB
Subscribers
None
View Options
diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt
index e93df8ef..781bac59 100644
--- a/autotests/CMakeLists.txt
+++ b/autotests/CMakeLists.txt
@@ -1,96 +1,101 @@
# SPDX-License-Identifier: CC0-1.0
# SPDX-FileCopyrightText: none
include(ECMAddTests)
find_package(Qt6Test ${QT_REQUIRED_VERSION} CONFIG QUIET)
if(NOT TARGET Qt::Test)
message(STATUS "Qt6Test not found, autotests will not be built.")
return()
endif()
ecm_add_test(
flatkeylistmodeltest.cpp
abstractkeylistmodeltest.cpp
TEST_NAME flatkeylistmodeltest
LINK_LIBRARIES KPim6::Libkleo Qt::Test
)
ecm_add_test(
hierarchicalkeylistmodeltest.cpp
abstractkeylistmodeltest.cpp
abstractkeylistmodeltest.h
TEST_NAME hierarchicalkeylistmodeltest
LINK_LIBRARIES KPim6::Libkleo Qt::Test
)
ecm_add_test(
keyresolvercoretest.cpp
keyresolvercoretest.qrc
TEST_NAME keyresolvercoretest
LINK_LIBRARIES KPim6::Libkleo Qt::Test
)
set_property(TEST keyresolvercoretest PROPERTY
SKIP_REGULAR_EXPRESSION "SKIP.*::initTestCase()"
)
ecm_add_tests(
editdirectoryservicedialogtest.cpp
LINK_LIBRARIES KPim6::Libkleo KF6::WidgetsAddons Qt::Widgets Qt::Test
)
ecm_add_tests(
keyselectioncombotest.cpp
keyserverconfigtest.cpp
newkeyapprovaldialogtest.cpp
LINK_LIBRARIES KPim6::Libkleo Qt::Widgets Qt::Test
)
ecm_add_test(
expirycheckertest.cpp
expirycheckertest.qrc
testhelpers.h
TEST_NAME expirycheckertest
LINK_LIBRARIES
KPim6::Libkleo
Qt::Test
)
ecm_add_tests(
hextest.cpp
LINK_LIBRARIES KPim6::Libkleo Qt::Test
)
ecm_add_test(
classifytest.cpp
LINK_LIBRARIES
KPim6::Libkleo
Qt::Test
)
ecm_add_test(
compliancetest.cpp
LINK_LIBRARIES KPim6::Libkleo Qt::Test
)
ecm_add_tests(
formattingtest.cpp
LINK_LIBRARIES KPim6::Libkleo Qt::Test
)
ecm_add_tests(
keycachetest.cpp
LINK_LIBRARIES KPim6::Libkleo Qt::Test
)
ecm_add_test(
keyparameterstest.cpp
TEST_NAME keyparameterstest
LINK_LIBRARIES KPim6::Libkleo Gpgmepp Qt::Test
)
ecm_add_tests(
stringutilstest.cpp
LINK_LIBRARIES KPim6::Libkleo Qt::Test
)
+
+ecm_add_tests(
+ assuantest.cpp
+ LINK_LIBRARIES KPim6::Libkleo Qt::Test
+)
diff --git a/autotests/assuantest.cpp b/autotests/assuantest.cpp
new file mode 100644
index 00000000..3e746ba9
--- /dev/null
+++ b/autotests/assuantest.cpp
@@ -0,0 +1,50 @@
+/*
+ This file is part of libkleopatra's test suite.
+ SPDX-FileCopyrightText: 2025 g10 Code GmbH
+ SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#include <Libkleo/Assuan>
+
+#include <QTest>
+
+using namespace Qt::StringLiterals;
+
+class AssuanTest : public QObject
+{
+ Q_OBJECT
+private Q_SLOTS:
+ void test_escapeAttributeValue_data()
+ {
+ QTest::addColumn<QByteArray>("input");
+ QTest::addColumn<QByteArray>("expected");
+
+ QByteArray allCharsExceptControlCharsAndSpaceAndPlus{256 - 32 - 1 - 1, Qt::Uninitialized};
+ // initialize the start of allCharsExceptControlCharsAndSpaceAndPlus with the characters from '!' to '*' (the one before '+')
+ std::iota(allCharsExceptControlCharsAndSpaceAndPlus.begin(), allCharsExceptControlCharsAndSpaceAndPlus.begin() + ('+' - '!'), '!');
+ // initialize the rest of allCharsExceptControlCharsAndSpaceAndPlus with the characters from ',' (the one after '+') to '\xFF'
+ std::iota(allCharsExceptControlCharsAndSpaceAndPlus.begin() + ('+' - '!'), allCharsExceptControlCharsAndSpaceAndPlus.end(), ',');
+
+ QByteArray allControlChars{32, Qt::Uninitialized};
+ std::iota(allControlChars.begin(), allControlChars.end(), 0);
+ QTest::newRow("empty string") << ""_ba << ""_ba;
+ QTest::newRow("nothing to escape") << allCharsExceptControlCharsAndSpaceAndPlus << allCharsExceptControlCharsAndSpaceAndPlus;
+ QTest::newRow("control chars are percent-escaped")
+ << allControlChars << "%00%01%02%03%04%05%06%07%08%09%0A%0B%0C%0D%0E%0F%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F"_ba;
+ QTest::newRow("plus is percent-escaped") << "+"_ba << "%2B"_ba;
+ QTest::newRow("space is plus-escaped") << " "_ba << "+"_ba;
+ }
+
+ void test_escapeAttributeValue()
+ {
+ QFETCH(QByteArray, input);
+ QFETCH(QByteArray, expected);
+
+ QCOMPARE(Kleo::Assuan::escapeAttributeValue(input), expected);
+ }
+};
+
+QTEST_MAIN(AssuanTest)
+#include "assuantest.moc"
diff --git a/src/utils/assuan.cpp b/src/utils/assuan.cpp
index 82261bfb..b688269f 100644
--- a/src/utils/assuan.cpp
+++ b/src/utils/assuan.cpp
@@ -1,150 +1,177 @@
/*
utils/assuan.cpp
This file is part of libkleopatra
SPDX-FileCopyrightText: 2021, 2022 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-libkleo.h>
#include "assuan.h"
#include "gnupg.h"
#include <libkleo_debug.h>
#if __has_include(<QGpgME/Debug>)
#include <QGpgME/Debug>
#endif
#include <QThread>
#include <gpgme++/context.h>
#include <gpgme++/defaultassuantransaction.h>
#include <gpgme++/error.h>
using namespace GpgME;
using namespace Kleo;
using namespace Kleo::Assuan;
using namespace std::chrono_literals;
static const auto initialRetryDelay = 125ms;
static const auto maxRetryDelay = 1000ms;
static const auto maxConnectionAttempts = 10;
namespace
{
static QDebug operator<<(QDebug s, const std::vector<std::pair<std::string, std::string>> &v)
{
using pair = std::pair<std::string, std::string>;
s << '(';
for (const pair &p : v) {
s << "status(" << QString::fromStdString(p.first) << ") =" << QString::fromStdString(p.second) << '\n';
}
return s << ')';
}
}
bool Kleo::Assuan::agentIsRunning()
{
Error err;
const std::unique_ptr<Context> ctx = Context::createForEngine(AssuanEngine, &err);
if (err) {
qCWarning(LIBKLEO_LOG) << __func__ << ": Creating context for Assuan engine failed:" << err;
return false;
}
static const char *command = "GETINFO version";
err = ctx->assuanTransact(command);
if (!err) {
// all good
} else if (err.code() == GPG_ERR_ASS_CONNECT_FAILED) {
qCDebug(LIBKLEO_LOG) << __func__ << ": Connecting to the agent failed.";
} else {
qCWarning(LIBKLEO_LOG) << __func__ << ": Starting Assuan transaction for" << command << "failed:" << err;
}
return !err;
}
+QByteArray Kleo::Assuan::escapeAttributeValue(QByteArrayView value)
+{
+ static const char *HexChars = "0123456789ABCDEF";
+ const int numberToPercentEscape = std::ranges::count_if(value, [](unsigned char ch) {
+ return (ch < ' ') || (ch == '+');
+ });
+ QByteArray result{value.size() + 2 * numberToPercentEscape, Qt::Uninitialized};
+ auto resultIt = result.begin();
+ std::ranges::for_each(value, [&resultIt](unsigned char ch) {
+ if ((ch < ' ') || (ch == '+')) {
+ *resultIt = '%';
+ resultIt++;
+ *resultIt = HexChars[ch >> 4];
+ resultIt++;
+ *resultIt = HexChars[ch & 0x0F];
+ resultIt++;
+ } else if (ch == ' ') {
+ *resultIt = '+';
+ resultIt++;
+ } else {
+ *resultIt = ch;
+ resultIt++;
+ }
+ });
+ return result;
+}
+
std::unique_ptr<GpgME::AssuanTransaction> Kleo::Assuan::sendCommand(std::shared_ptr<GpgME::Context> &context,
const std::string &command,
std::unique_ptr<GpgME::AssuanTransaction> transaction,
GpgME::Error &err)
{
qCDebug(LIBKLEO_LOG) << __func__ << command;
int connectionAttempts = 1;
err = context->assuanTransact(command.c_str(), std::move(transaction));
auto retryDelay = initialRetryDelay;
while (err.code() == GPG_ERR_ASS_CONNECT_FAILED && connectionAttempts < maxConnectionAttempts) {
if (connectionAttempts == 1) {
Kleo::launchGpgAgent(Kleo::SkipCheckForRunningAgent);
}
// Esp. on Windows the agent processes may take their time so we try
// in increasing waits for them to start up
qCDebug(LIBKLEO_LOG) << "Connecting to the agent failed. Retrying in" << retryDelay.count() << "ms";
QThread::msleep(retryDelay.count());
retryDelay = std::min(retryDelay * 2, maxRetryDelay);
connectionAttempts++;
err = context->assuanTransact(command.c_str(), context->takeLastAssuanTransaction());
}
if (err.code()) {
qCDebug(LIBKLEO_LOG) << __func__ << command << "failed:" << err;
if (err.code() >= GPG_ERR_ASS_GENERAL && err.code() <= GPG_ERR_ASS_UNKNOWN_INQUIRE) {
qCDebug(LIBKLEO_LOG) << "Assuan problem, killing context";
context.reset();
}
return {};
}
return context->takeLastAssuanTransaction();
}
std::unique_ptr<DefaultAssuanTransaction> Kleo::Assuan::sendCommand(std::shared_ptr<Context> &context, const std::string &command, Error &err)
{
std::unique_ptr<AssuanTransaction> t = sendCommand(context, command, std::make_unique<DefaultAssuanTransaction>(), err);
return std::unique_ptr<DefaultAssuanTransaction>(dynamic_cast<DefaultAssuanTransaction *>(t.release()));
}
std::string Kleo::Assuan::sendDataCommand(std::shared_ptr<Context> context, const std::string &command, Error &err)
{
std::string data;
const std::unique_ptr<DefaultAssuanTransaction> t = sendCommand(context, command, err);
if (t.get()) {
data = t->data();
qCDebug(LIBKLEO_LOG) << __func__ << command << ": got" << QString::fromStdString(data);
} else {
qCDebug(LIBKLEO_LOG) << __func__ << command << ": t == NULL";
}
return data;
}
std::vector<std::pair<std::string, std::string>> Kleo::Assuan::sendStatusLinesCommand(std::shared_ptr<Context> context, const std::string &command, Error &err)
{
std::vector<std::pair<std::string, std::string>> statusLines;
const std::unique_ptr<DefaultAssuanTransaction> t = sendCommand(context, command, err);
if (t.get()) {
statusLines = t->statusLines();
qCDebug(LIBKLEO_LOG) << __func__ << command << ": got" << statusLines;
} else {
qCDebug(LIBKLEO_LOG) << __func__ << command << ": t == NULL";
}
return statusLines;
}
std::string Kleo::Assuan::sendStatusCommand(const std::shared_ptr<Context> &context, const std::string &command, Error &err)
{
const auto lines = sendStatusLinesCommand(context, command, err);
// The status is only the last attribute
// e.g. for SCD SERIALNO it would only be "SERIALNO" and for SCD GETATTR FOO
// it would only be FOO
const auto lastSpace = command.rfind(' ');
const auto needle = lastSpace == std::string::npos ? command : command.substr(lastSpace + 1);
for (const auto &pair : lines) {
if (pair.first == needle) {
return pair.second;
}
}
return {};
}
diff --git a/src/utils/assuan.h b/src/utils/assuan.h
index ebfe580c..f9fca620 100644
--- a/src/utils/assuan.h
+++ b/src/utils/assuan.h
@@ -1,73 +1,78 @@
/*
utils/assuan.h
This file is part of libkleopatra
SPDX-FileCopyrightText: 2021, 2022 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "kleo_export.h"
+#include <QByteArray>
+
#include <memory>
#include <string>
#include <vector>
namespace GpgME
{
class AssuanTransaction;
class Context;
class DefaultAssuanTransaction;
class Error;
}
namespace Kleo
{
/** The Assuan namespace collects functions for communicating with the GnuPG
* agent via the Assuan protocol. */
namespace Assuan
{
/** Checks if the GnuPG agent is running and accepts connections. */
KLEO_EXPORT bool agentIsRunning();
+/*! Escapes \a value for usage as value in a SETATTR call. */
+KLEO_EXPORT QByteArray escapeAttributeValue(QByteArrayView value);
+
/** Sends the Assuan @p command using the @p transaction and the @p assuanContext
* to the GnuPG agent and waits for the result. The returned transaction can be used
* to retrieve the result.
* If an error occurred, then @p err provides details. */
KLEO_EXPORT std::unique_ptr<GpgME::AssuanTransaction> sendCommand(std::shared_ptr<GpgME::Context> &assuanContext,
const std::string &command,
std::unique_ptr<GpgME::AssuanTransaction> transaction,
GpgME::Error &err);
/** Sends the Assuan @p command using a default Assuan transaction and the @p assuanContext
* to the GnuPG agent and waits for the result. The returned transaction can be used
* to retrieve the result.
* If an error occurred, then @p err provides details. */
KLEO_EXPORT std::unique_ptr<GpgME::DefaultAssuanTransaction>
sendCommand(std::shared_ptr<GpgME::Context> &assuanContext, const std::string &command, GpgME::Error &err);
/** Sends the Assuan @p command using a default Assuan transaction and the @p assuanContext
* to the GnuPG agent and waits for the result. Returns the data that was sent by
* GnuPG agent in response to the @p command.
* If an error occurred, then @p err provides details. */
KLEO_EXPORT std::string sendDataCommand(std::shared_ptr<GpgME::Context> assuanContext, const std::string &command, GpgME::Error &err);
/** Sends the Assuan @p command using a default Assuan transaction and the @p assuanContext
* to the GnuPG agent and waits for the result. Returns the status lines that were sent by
* GnuPG agent in response to the @p command.
* If an error occurred, then @p err provides details. */
KLEO_EXPORT std::vector<std::pair<std::string, std::string>>
sendStatusLinesCommand(std::shared_ptr<GpgME::Context> assuanContext, const std::string &command, GpgME::Error &err);
/** Sends the Assuan @p command using a default Assuan transaction and the @p assuanContext
* to the GnuPG agent and waits for the result. Returns the status that was sent by
* GnuPG agent in response to the @p command.
* If an error occurred, then @p err provides details. */
KLEO_EXPORT std::string sendStatusCommand(const std::shared_ptr<GpgME::Context> &assuanContext, const std::string &command, GpgME::Error &err);
}
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sat, Nov 29, 7:01 AM (21 h, 15 m)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
12/48/cd34102bf240d8542dffa4764514
Attached To
rLIBKLEO Libkleo
Event Timeline
Log In to Comment