aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsaturneric <[email protected]>2026-09-22 21:16:52 +0200
committersaturneric <[email protected]>2026-09-22 21:16:52 +0200
commit86a75d6b49e671de6558273ee97026c819923816 (patch)
tree936f40e33f40760a8b8ff5d59d0f779394b0372c
parentrefactor(gpg-info): migrate to module logging macros (diff)
downloadmodules-86a75d6b49e671de6558273ee97026c819923816.tar.gz
modules-86a75d6b49e671de6558273ee97026c819923816.zip
refactor(email): migrate to context-based SDK
- pass SDK contexts through crypto, storage, UI, and buffer APIs - replace legacy ownership helpers with RAII-style buffer handling - preserve secure credential storage and wiping semantics - update analysis, key, recipient, and import integrations - migrate tests to fake host API tables and real SDK wrappers - add storage capability to the email module manifest
-rw-r--r--src/m_email/EMailAccountStore.cpp3
-rw-r--r--src/m_email/EMailBasicGpgOpera.cpp68
-rw-r--r--src/m_email/EMailCredentialStore.cpp43
-rw-r--r--src/m_email/EMailModule.cpp100
-rw-r--r--src/m_email/EMailPageView.cpp79
-rw-r--r--src/m_email/EMailSecret.cpp22
-rw-r--r--src/m_email/EMailSecret.h17
-rw-r--r--src/m_email/EMailSecurityView.cpp32
-rw-r--r--src/m_email/EMailStructureView.cpp6
-rw-r--r--src/m_email/EMailViewStyle.h29
-rw-r--r--src/m_email/module.json5
-rw-r--r--src/m_email/test/CMakeLists.txt15
-rw-r--r--src/m_email/test/EMailCryptoSdkStubs.cpp270
-rw-r--r--src/m_email/test/EMailSdkStubs.cpp295
-rw-r--r--src/m_email/test/EMailSecretTest.cpp28
15 files changed, 682 insertions, 330 deletions
diff --git a/src/m_email/EMailAccountStore.cpp b/src/m_email/EMailAccountStore.cpp
index 0198205..7216bac 100644
--- a/src/m_email/EMailAccountStore.cpp
+++ b/src/m_email/EMailAccountStore.cpp
@@ -50,7 +50,8 @@ constexpr auto kSchemaVersion = 1;
Q_GLOBAL_STATIC(QMutex, settings_mutex)
auto GlobalSettings() -> QSettings* {
- return qobject_cast<QSettings*>(static_cast<QObject*>(GFUIGlobalSettings()));
+ return qobject_cast<QSettings*>(
+ static_cast<QObject*>(GFStorageSettingsRoot(GFModuleSdkContext())));
}
} // namespace
diff --git a/src/m_email/EMailBasicGpgOpera.cpp b/src/m_email/EMailBasicGpgOpera.cpp
index e0154a2..f1ceace 100644
--- a/src/m_email/EMailBasicGpgOpera.cpp
+++ b/src/m_email/EMailBasicGpgOpera.cpp
@@ -111,11 +111,11 @@ auto EncryptPlainText(int channel, const QStringList& keys,
// including the early return below, which is where the struct-based API
// leaked because a FAILED call still allocated a result to explain
// itself.
- auto in = GFBuf::Copy(body_data);
+ auto in = GFBuf::Copy(GFModuleSdkContext(), body_data);
auto key_ids = KeyIdArgs(keys);
- GFGpgResult r;
- if (GFGpgEncrypt(channel, key_ids.Data(), key_ids.Size(), in.View(), 1,
- r.Out()) != GF_GPG_OK) {
+ GFGpgResult r(GFModuleSdkContext());
+ if (GFGpgEncrypt(GFModuleSdkContext(), channel, key_ids.Data(),
+ key_ids.Size(), in.View(), 1, r.Out()) != GF_GPG_OK) {
eml_data = SdkFailureText("Encryption", r.ErrorString()).toUtf8();
return kFAILED;
}
@@ -286,11 +286,11 @@ auto EncryptEMLData(int channel, const QStringList& keys,
// The SDK sets *ps = nullptr and returns non-zero when it cannot
// allocate the result, so both must be checked before the first
// dereference below -- not after it, as this used to.
- auto in = GFBuf::Copy(plain_raw_data);
+ auto in = GFBuf::Copy(GFModuleSdkContext(), plain_raw_data);
auto key_ids = KeyIdArgs(keys);
- GFGpgResult r;
- if (GFGpgEncrypt(channel, key_ids.Data(), key_ids.Size(), in.View(), 1,
- r.Out()) != GF_GPG_OK) {
+ GFGpgResult r(GFModuleSdkContext());
+ if (GFGpgEncrypt(GFModuleSdkContext(), channel, key_ids.Data(),
+ key_ids.Size(), in.View(), 1, r.Out()) != GF_GPG_OK) {
eml_data = SdkFailureText("Encryption", r.ErrorString()).toUtf8();
return kFAILED;
}
@@ -542,7 +542,7 @@ auto SignPlainText(int channel, const QString& key,
vmime::word(std::string{"OpenPGP_signature.asc"}));
auto public_key =
- UDUP(GFGpgPublicKey(channel, (key).toUtf8().constData(), 1));
+ UDUP(gf::sdk::PublicKey(GFModuleSdkContext(), channel, (key), 1 != 0));
if (public_key.isEmpty()) {
eml_data = "Get Public Key of Sign Key Failed";
return kFAILED;
@@ -593,11 +593,11 @@ auto SignPlainText(int channel, const QString& key,
// The SDK sets *ps = nullptr and returns non-zero when it cannot
// allocate the result, so both must be checked before the first
// dereference below -- not after it, as this used to.
- auto in = GFBuf::Copy(container_raw_data);
+ auto in = GFBuf::Copy(GFModuleSdkContext(), container_raw_data);
auto key_ids = KeyIdArgs({key});
- GFGpgResult r;
- if (GFGpgSign(channel, key_ids.Data(), key_ids.Size(), in.View(), 1, 1,
- r.Out()) != GF_GPG_OK) {
+ GFGpgResult r(GFModuleSdkContext());
+ if (GFGpgSign(GFModuleSdkContext(), channel, key_ids.Data(), key_ids.Size(),
+ in.View(), 1, 1, r.Out()) != GF_GPG_OK) {
eml_data = SdkFailureText("Sign", r.ErrorString()).toUtf8();
return kFAILED;
}
@@ -777,7 +777,7 @@ auto SignEMLData(int channel, const QString& key,
vmime::word(std::string{"OpenPGP_signature.asc"}));
auto public_key =
- UDUP(GFGpgPublicKey(channel, (key).toUtf8().constData(), 1));
+ UDUP(gf::sdk::PublicKey(GFModuleSdkContext(), channel, (key), 1 != 0));
if (public_key.isEmpty()) {
eml_data = "Get Public Key of Sign Key Failed";
return kFAILED;
@@ -827,11 +827,11 @@ auto SignEMLData(int channel, const QString& key,
// The SDK sets *ps = nullptr and returns non-zero when it cannot
// allocate the result, so both must be checked before the first
// dereference below -- not after it, as this used to.
- auto in = GFBuf::Copy(container_raw_data);
+ auto in = GFBuf::Copy(GFModuleSdkContext(), container_raw_data);
auto key_ids = KeyIdArgs({key});
- GFGpgResult r;
- if (GFGpgSign(channel, key_ids.Data(), key_ids.Size(), in.View(), 1, 1,
- r.Out()) != GF_GPG_OK) {
+ GFGpgResult r(GFModuleSdkContext());
+ if (GFGpgSign(GFModuleSdkContext(), channel, key_ids.Data(), key_ids.Size(),
+ in.View(), 1, 1, r.Out()) != GF_GPG_OK) {
eml_data = SdkFailureText("Sign", r.ErrorString()).toUtf8();
return kFAILED;
}
@@ -1141,10 +1141,11 @@ auto VerifyOneRegion(int channel, const QByteArray& raw,
// It EXPLAINS a failure below; it never rescues one.
verdict.signed_bytes_non_canonical = HasBareLineFeeds(signed_bytes);
- auto in = GFBuf::Copy(signed_bytes);
- auto sig = GFBuf::Copy(signature_bytes);
- GFGpgResult r;
- if (GFGpgVerify(channel, in.View(), sig.View(), r.Out()) != GF_GPG_OK) {
+ auto in = GFBuf::Copy(GFModuleSdkContext(), signed_bytes);
+ auto sig = GFBuf::Copy(GFModuleSdkContext(), signature_bytes);
+ GFGpgResult r(GFModuleSdkContext());
+ if (GFGpgVerify(GFModuleSdkContext(), channel, in.View(), sig.View(),
+ r.Out()) != GF_GPG_OK) {
// One region failing is not the walk failing: the others are still worth
// verifying. Nothing to reclaim by hand -- r releases itself.
verdict.exec = EMailVerifyExec::kENGINE_ERROR;
@@ -1162,16 +1163,14 @@ auto VerifyOneRegion(int channel, const QByteArray& raw,
// The structured form AND the report text, from the one call: a per-
// signature view cannot be rebuilt by re-reading prose, and the capsule is
// consumed here, so everything anyone needs has to come out of it now.
- const char* analyse = nullptr;
- const char* cards = nullptr;
- const char* info_json = nullptr;
report.region_id = region.region_id;
- report.status = GFAnalyseVerifyResultInfoByCapsule(
- channel, err, (capsule_id).toUtf8().constData(), &analyse, &cards,
- &info_json);
- report.detail = UnStrDup(analyse);
- report.cards = UnStrDup(cards);
- report.info_json = UnStrDup(info_json).toUtf8();
+
+ const auto analysis = gf::sdk::AnalyseResult(
+ GFModuleSdkContext(), GF_GPG_ANALYSE_VERIFY, channel, err, capsule_id);
+ report.status = analysis.status;
+ report.detail = analysis.report;
+ report.cards = analysis.cards;
+ report.info_json = analysis.info_json.toUtf8();
auto region_results =
ParseSignatureResults(report.info_json, region.region_id);
@@ -1465,9 +1464,10 @@ auto DecryptEMLData(int channel, const QByteArray& data,
// The SDK sets *ps = nullptr and returns non-zero when it cannot
// allocate the result, so both must be checked before the first
// dereference below -- not after it, as this used to.
- auto in = GFBuf::Copy(part_encr_body_content);
- GFGpgResult r;
- if (GFGpgDecrypt(channel, in.View(), r.Out()) != GF_GPG_OK) {
+ auto in = GFBuf::Copy(GFModuleSdkContext(), part_encr_body_content);
+ GFGpgResult r(GFModuleSdkContext());
+ if (GFGpgDecrypt(GFModuleSdkContext(), channel, in.View(), r.Out()) !=
+ GF_GPG_OK) {
eml_data = SdkFailureText("Decrypt", r.ErrorString()).toUtf8();
return kFAILED;
}
diff --git a/src/m_email/EMailCredentialStore.cpp b/src/m_email/EMailCredentialStore.cpp
index 6208dae..6aefd51 100644
--- a/src/m_email/EMailCredentialStore.cpp
+++ b/src/m_email/EMailCredentialStore.cpp
@@ -29,7 +29,6 @@
#include "EMailCredentialStore.h"
#include "GFModule.h"
-#include "GFSDKBasic.h"
namespace {
@@ -44,7 +43,7 @@ auto CredentialKey(const QString& account_id) -> QString {
namespace EMailCredentialStore {
auto CurrentProtection() -> ProtectionLevel {
- switch (GFAppKeyProtectionLevel()) {
+ switch (GFAppKeyProtectionLevel(GFModuleSdkContext())) {
case 0:
return ProtectionLevel::kNONE;
case 1:
@@ -88,14 +87,24 @@ auto Save(const QString& account_id, const EMailSecret& password) -> bool {
// Both arguments are handed over owned, from the allocators the SDK will
// release them through: the key ordinary, the secret secure. Passing a
// QByteArray's internal pointer here would have the SDK free memory Qt owns.
- auto* secret = password.ToSecureCString();
- if (secret == nullptr) {
+ // Deliberately the raw buffer API rather than the QString convenience: a
+ // QString cannot be erased, and routing a password through one would add a
+ // copy this module could never wipe. The buffer is wiped on release.
+ //
+ // The secure tier BORROWS now, like every other argument in this SDK. It
+ // used to take ownership of both arguments and free them through two
+ // different allocators, a rule nothing in the signature hinted at.
+ auto* ctx = GFModuleSdkContext();
+ auto* buffer = GFBufferNewFromBytes(ctx, password.Data(), password.Size());
+ if (buffer == nullptr) {
LOG_ERROR("could not allocate a secure buffer for a mail credential");
return false;
}
- const auto result = GFSecDurableCacheSave(
- (CredentialKey(account_id)).toUtf8().constData(), secret);
+ const auto result = GFStorageCacheSet(
+ ctx, GF_STORE_SECURE_DURABLE,
+ CredentialKey(account_id).toUtf8().constData(), buffer, 0);
+ GFBufferRelease(ctx, buffer);
if (result != 0) LOG_ERROR("failed to store mail credential");
return result == 0;
@@ -104,15 +113,18 @@ auto Save(const QString& account_id, const EMailSecret& password) -> bool {
auto Load(const QString& account_id) -> EMailSecretPtr {
if (account_id.isEmpty()) return std::make_shared<EMailSecret>();
- auto* raw =
- GFSecDurableCacheGet((CredentialKey(account_id)).toUtf8().constData());
- if (raw == nullptr) return std::make_shared<EMailSecret>();
+ // Raw buffer again, for the same reason: the secret must not pass through
+ // a QString on its way into storage that can erase it.
+ auto* ctx = GFModuleSdkContext();
+ GFBufferRef raw = nullptr;
+ if (GFStorageCacheGet(ctx, GF_STORE_SECURE_DURABLE,
+ CredentialKey(account_id).toUtf8().constData(),
+ &raw) != 0) {
+ return std::make_shared<EMailSecret>();
+ }
- // AdoptCString copies the bytes out, wipes the SDK's buffer and frees it to
- // the secure allocator it came from, so the secret exists in exactly one
- // place from here on -- and that place can actually be erased, which a
- // QString could not be. See EMailSecret.
- return EMailSecret::AdoptCString(raw);
+ // AdoptBuffer copies the bytes out and releases the buffer, which wipes it.
+ return EMailSecret::AdoptBuffer(ctx, raw);
}
auto Has(const QString& account_id) -> bool {
@@ -121,7 +133,8 @@ auto Has(const QString& account_id) -> bool {
void Remove(const QString& account_id) {
if (account_id.isEmpty()) return;
- GFSecDurableCacheRemove((CredentialKey(account_id)).toUtf8().constData());
+ gf::sdk::RemoveCache(GFModuleSdkContext(), GF_STORE_SECURE_DURABLE,
+ (CredentialKey(account_id)).toUtf8().constData());
}
} // namespace EMailCredentialStore
diff --git a/src/m_email/EMailModule.cpp b/src/m_email/EMailModule.cpp
index a1b8049..309fe8e 100644
--- a/src/m_email/EMailModule.cpp
+++ b/src/m_email/EMailModule.cpp
@@ -28,7 +28,6 @@
#include "EMailModule.h"
-#include <GFSDKBasic.h>
#include <GFSDKBuildInfo.h>
#include <GFSDKLog.h>
@@ -168,16 +167,19 @@ auto BuildAttachmentCard(const EMailMetaData& m) -> QJsonObject {
auto AddressOfKey(int channel, const QString& key_id) -> QString {
if (key_id.isEmpty()) return {};
- GFGpgKeyUID* uid = nullptr;
- if (GFGpgKeyPrimaryUID(channel, (key_id).toUtf8().constData(), &uid) != 0 ||
- uid == nullptr) {
+ // Three out-parameters, and only the two that are wanted. The struct this
+ // replaced handed back all three as char* members to be freed one by one,
+ // which is why the comment field used to be fetched purely to release it.
+ auto* ctx = GFModuleSdkContext();
+ GFBufferRef name_buf = nullptr;
+ GFBufferRef email_buf = nullptr;
+ if (GFGpgKeyPrimaryUid(ctx, channel, key_id.toUtf8().constData(), &name_buf,
+ &email_buf, nullptr) != 0) {
return {};
}
- const auto name = UDUP(uid->name);
- const auto email = UDUP(uid->email);
- UDUP(uid->comment); // free the unused field
- GFFreeMemory(uid);
+ const auto name = gf::sdk::TakeString(ctx, name_buf);
+ const auto email = gf::sdk::TakeString(ctx, email_buf);
return email.isEmpty() ? name : QString("%1 <%2>").arg(name, email);
}
@@ -402,12 +404,12 @@ auto OnActivate() -> GFResult {
// The message view of an e-mail tab. The host still owns the page and its
// document -- this only supplies the widget shown on top of it, with the raw
// MIME still one click away.
- GFUIRegisterTabPageView(
- "EMAIL", [](void*) -> void* { return new EMailPageView(nullptr); },
- nullptr);
+ gf::sdk::RegisterTabPageView(
+ GFModuleSdkContext(), "EMAIL",
+ [](void*) -> void* { return new EMailPageView(nullptr); }, nullptr);
// register file extension handler
- GFUIRegisterFileExtensionHandleEvent("eml", "EMAIL");
+ GFUIRegisterFileExtension(GFModuleSdkContext(), "eml", "EMAIL");
// These cross thread boundaries as queued signal arguments, so Qt has to
// know how to copy them before the first connection is made.
qRegisterMetaType<MailAccountConfig>("MailAccountConfig");
@@ -436,10 +438,10 @@ auto OnActivate() -> GFResult {
QStringList{GC_TR("mail"), GC_TR("email"), GC_TR("imap"),
GC_TR("smtp"), GC_TR("account"), GC_TR("send")}
.join('\n');
- GFUIRegisterSettingsPage(kMailSettingsPageId, "features",
- GC_TR("Mail Accounts"),
- (keywords).toUtf8().constData(),
- EMailAccountSettingsPageFactory, nullptr);
+ gf::sdk::RegisterSettingsPage(GFModuleSdkContext(), kMailSettingsPageId,
+ "features", GC_TR("Mail Accounts"),
+ keywords.toUtf8().constData(),
+ EMailAccountSettingsPageFactory, nullptr);
return GFResult::Ok();
}
@@ -447,8 +449,8 @@ auto OnActivate() -> GFResult {
auto OnDeactivate() -> GFResult {
// A factory pointing into an unloaded shared object would crash the next
// time an e-mail tab is opened.
- GFUIUnregisterTabPageView("EMAIL");
- GFUIUnregisterSettingsPage(kMailSettingsPageId);
+ GFUIUnregisterTabPageView(GFModuleSdkContext(), "EMAIL");
+ GFUIUnregisterSettingsPage(GFModuleSdkContext(), kMailSettingsPageId);
return GFResult::Ok();
}
@@ -522,7 +524,7 @@ auto ConfirmExport(QWidget* parent, const EMailExportCheck& check) -> bool {
// Where a Save dialog should open. Falls back to the home directory only if
// the host cannot answer, which it always can in practice.
auto default_save_dir() -> QString {
- auto path = UnStrDup(GFUIDefaultUserFilePath());
+ const auto path = gf::sdk::UserFilePath(GFModuleSdkContext());
return path.isEmpty() ? QDir::homePath() : path;
}
@@ -1030,12 +1032,12 @@ auto DoDecryptEMLData(int channel, const QByteArray& data, const GFEvent& event,
// The Info variant, because the recipient cross-check needs the structured
// recipients rather than the rendered report. The capsule is consumed by
// whichever analyse call touches it, so everything has to come from this one.
- result_status = GFAnalyseDecryptResultInfoByCapsule(
- channel, err, (capsule_id).toUtf8().constData(), &tmp, &cards_tmp,
- &info_tmp);
- result_detail = UnStrDup(tmp);
- result_cards = UnStrDup(cards_tmp);
- decrypt_info_json = UnStrDup(info_tmp).toUtf8();
+ const auto analysis = gf::sdk::AnalyseResult(
+ GFModuleSdkContext(), GF_GPG_ANALYSE_DECRYPT, channel, err, capsule_id);
+ result_status = analysis.status;
+ result_detail = analysis.report;
+ result_cards = analysis.cards;
+ decrypt_info_json = analysis.info_json.toUtf8();
if (ret == kGPG_FAILED) {
// decrypt failed. The analysis cards built above travel with it: a failure
@@ -1161,12 +1163,12 @@ auto DoSignEMLData(int channel, const QString& sign_key,
// The Info variant, not the plain one: the structured description
// and details are what let a FAILURE explain itself, and without
// them the board can only fall back to "<operation> failed."
- result_status = GFAnalyseSignResultInfoByCapsule(
- channel, err, (capsule_id).toUtf8().constData(), &tmp, &cards_tmp,
- &info_tmp);
- result_detail = UnStrDup(tmp);
- result_cards = UnStrDup(cards_tmp);
- info_json = UnStrDup(info_tmp).toUtf8();
+ const auto analysis = gf::sdk::AnalyseResult(
+ GFModuleSdkContext(), GF_GPG_ANALYSE_SIGN, channel, err, capsule_id);
+ result_status = analysis.status;
+ result_detail = analysis.report;
+ result_cards = analysis.cards;
+ info_json = analysis.info_json.toUtf8();
if (ret == kGPG_FAILED) {
// decrypt failed. The analysis cards built above travel with it: a failure
@@ -1217,12 +1219,12 @@ auto DoSignPlainText(int channel, const QString& sign_key,
// The Info variant, not the plain one: the structured description
// and details are what let a FAILURE explain itself, and without
// them the board can only fall back to "<operation> failed."
- result_status = GFAnalyseSignResultInfoByCapsule(
- channel, err, (capsule_id).toUtf8().constData(), &tmp, &cards_tmp,
- &info_tmp);
- result_detail = UnStrDup(tmp);
- result_cards = UnStrDup(cards_tmp);
- info_json = UnStrDup(info_tmp).toUtf8();
+ const auto analysis = gf::sdk::AnalyseResult(
+ GFModuleSdkContext(), GF_GPG_ANALYSE_SIGN, channel, err, capsule_id);
+ result_status = analysis.status;
+ result_detail = analysis.report;
+ result_cards = analysis.cards;
+ info_json = analysis.info_json.toUtf8();
if (ret == kGPG_FAILED) {
// decrypt failed. The analysis cards built above travel with it: a failure
@@ -1339,12 +1341,12 @@ auto DoEncryptEMLData(int channel, const QStringList& encrypt_keys,
// The Info variant, not the plain one: the structured description
// and details are what let a FAILURE explain itself, and without
// them the board can only fall back to "<operation> failed."
- result_status = GFAnalyseEncryptResultInfoByCapsule(
- channel, err, (capsule_id).toUtf8().constData(), &tmp, &cards_tmp,
- &info_tmp);
- result_detail = UnStrDup(tmp);
- result_cards = UnStrDup(cards_tmp);
- info_json = UnStrDup(info_tmp).toUtf8();
+ const auto analysis = gf::sdk::AnalyseResult(
+ GFModuleSdkContext(), GF_GPG_ANALYSE_ENCRYPT, channel, err, capsule_id);
+ result_status = analysis.status;
+ result_detail = analysis.report;
+ result_cards = analysis.cards;
+ info_json = analysis.info_json.toUtf8();
if (ret == kGPG_FAILED) {
// encrypt failed. The analysis cards built above travel with it: a failure
@@ -1399,12 +1401,12 @@ auto DoEncryptPlainText(int channel, const QStringList& encrypt_keys,
// The Info variant, not the plain one: the structured description
// and details are what let a FAILURE explain itself, and without
// them the board can only fall back to "<operation> failed."
- result_status = GFAnalyseEncryptResultInfoByCapsule(
- channel, err, (capsule_id).toUtf8().constData(), &tmp, &cards_tmp,
- &info_tmp);
- result_detail = UnStrDup(tmp);
- result_cards = UnStrDup(cards_tmp);
- info_json = UnStrDup(info_tmp).toUtf8();
+ const auto analysis = gf::sdk::AnalyseResult(
+ GFModuleSdkContext(), GF_GPG_ANALYSE_ENCRYPT, channel, err, capsule_id);
+ result_status = analysis.status;
+ result_detail = analysis.report;
+ result_cards = analysis.cards;
+ info_json = analysis.info_json.toUtf8();
if (ret == kGPG_FAILED) {
// encrypt failed. The analysis cards built above travel with it: a failure
diff --git a/src/m_email/EMailPageView.cpp b/src/m_email/EMailPageView.cpp
index 59b9e14..9330763 100644
--- a/src/m_email/EMailPageView.cpp
+++ b/src/m_email/EMailPageView.cpp
@@ -457,7 +457,7 @@ void EMailPageView::apply_colors() {
EMailSetLabelColor(attachment_heading_, MutedColor(this));
}
if (unsigned_notice_ != nullptr) {
- EMailSetLabelColor(unsigned_notice_, ThemeColor(this, &GFUIWarningColor));
+ EMailSetLabelColor(unsigned_notice_, ThemeColor(this, GF_UI_COLOR_WARNING));
}
if (attachment_list_ != nullptr) {
@@ -1936,11 +1936,12 @@ void EMailPageView::refresh_locked_capability(const QStringList& named) {
// for mail encrypted to someone else who happened to share an address.
const auto* ciphertext = FindEncryptedCiphertextPart(tree_root_);
if (ciphertext != nullptr) {
- auto in = GFBuf::Copy(ciphertext->data);
+ auto in = GFBuf::Copy(GFModuleSdkContext(), ciphertext->data);
GFGpgRecipientListRef list = nullptr;
- if (GFGpgSniffRecipients(GFGpgCurrentGpgContextChannel(), in.View(),
- &list) == 0) {
- const auto count = GFGpgRecipientListCount(list);
+ if (GFGpgSniffRecipients(GFModuleSdkContext(),
+ GFGpgCurrentChannel(GFModuleSdkContext()),
+ in.View(), &list) == 0) {
+ const auto count = GFGpgRecipientCount(GFModuleSdkContext(), list);
QList<EMailEncRecipient> recipients;
recipients.reserve(static_cast<qsizetype>(count));
for (size_t i = 0; i < count; ++i) {
@@ -1948,16 +1949,20 @@ void EMailPageView::refresh_locked_capability(const QStringList& named) {
// pointer that dies with the list. There is no per-element free to
// get wrong, and no count to hand back on release.
recipients.append(EMailEncRecipient{
- QString::fromUtf8(GFGpgRecipientKeyId(list, i)),
- QString::fromUtf8(GFGpgRecipientPubAlgo(list, i)),
- QString::fromUtf8(GFGpgRecipientFingerprint(list, i)),
- QString::fromUtf8(GFGpgRecipientUid(list, i)),
- GFGpgRecipientKeyFound(list, i) != 0,
- GFGpgRecipientHasSecret(list, i) != 0,
- GFGpgRecipientHidden(list, i) != 0,
+ QString::fromUtf8(
+ GFGpgRecipientAt(GFModuleSdkContext(), list, i)->key_id),
+ QString::fromUtf8(
+ GFGpgRecipientAt(GFModuleSdkContext(), list, i)->pub_algo),
+ QString::fromUtf8(
+ GFGpgRecipientAt(GFModuleSdkContext(), list, i)->fingerprint),
+ QString::fromUtf8(
+ GFGpgRecipientAt(GFModuleSdkContext(), list, i)->uid),
+ GFGpgRecipientAt(GFModuleSdkContext(), list, i)->key_found != 0,
+ GFGpgRecipientAt(GFModuleSdkContext(), list, i)->has_secret != 0,
+ GFGpgRecipientAt(GFModuleSdkContext(), list, i)->hidden != 0,
});
}
- GFGpgRecipientListRelease(list);
+ GFGpgRecipientRelease(GFModuleSdkContext(), list);
if (!recipients.isEmpty()) {
show_decrypt_capability(DescribeDecryptCapability(recipients));
@@ -1991,7 +1996,7 @@ void EMailPageView::show_decrypt_capability(
case EMailDecryptVerdict::kCANNOT_OPEN:
EMailSetLabelColor(locked_capability_,
- ThemeColor(this, &GFUIWarningColor));
+ ThemeColor(this, GF_UI_COLOR_WARNING));
locked_capability_->setText(
tr("This message is encrypted to keys you do not hold the private "
"half of, so it cannot be opened on this computer."));
@@ -2018,19 +2023,21 @@ void EMailPageView::refresh_locked_capability_by_address(
// so it is reached only when the message itself could not be read, and every
// wording below is hedged accordingly.
GFStringListRef addresses = nullptr;
- if (GFGpgListAddresses(GFGpgCurrentGpgContextChannel(), 1, &addresses) != 0) {
+ if (GFGpgListAddresses(GFModuleSdkContext(),
+ GFGpgCurrentChannel(GFModuleSdkContext()), 1,
+ &addresses) != 0) {
locked_capability_->setVisible(false);
return;
}
QStringList mine;
- const auto count = GFStringListCount(addresses);
+ const auto count = GFStringListCount(GFModuleSdkContext(), addresses);
for (size_t i = 0; i < count; ++i) {
- const auto address =
- AddressOfUid(QString::fromUtf8(GFStringListAt(addresses, i)));
+ const auto address = AddressOfUid(
+ QString::fromUtf8(GFStringListAt(GFModuleSdkContext(), addresses, i)));
if (!address.isEmpty()) mine.append(address.toLower());
}
- GFStringListRelease(addresses);
+ GFStringListRelease(GFModuleSdkContext(), addresses);
// Nothing knowable either way: no recipient is named, or the keyring holds
// no private key at all. Saying something in that case would be inventing
@@ -2054,7 +2061,8 @@ void EMailPageView::refresh_locked_capability_by_address(
locked_capability_->setVisible(true);
if (matched.isEmpty()) {
- EMailSetLabelColor(locked_capability_, ThemeColor(this, &GFUIWarningColor));
+ EMailSetLabelColor(locked_capability_,
+ ThemeColor(this, GF_UI_COLOR_WARNING));
locked_capability_->setText(
tr("You do not hold a private key for any of these addresses. Unless "
"the message was also encrypted to a key that is not named here, "
@@ -2254,8 +2262,8 @@ void EMailPageView::refresh_security() {
security_view_->SetMessage(
security_state_, regions_, cached_verification_.signatures,
recipient_rows_, addresses, message_.from,
- GFGpgCurrentGpgContextChannel(), findings, cached_verification_.state,
- !message_key_parts().isEmpty());
+ GFGpgCurrentChannel(GFModuleSdkContext()), findings,
+ cached_verification_.state, !message_key_parts().isEmpty());
}
/**
@@ -2362,20 +2370,22 @@ void EMailPageView::refresh_send_state() {
void EMailPageView::install_address_hints() {
const auto addresses = [](bool secret_only) {
GFStringListRef raw = nullptr;
- if (GFGpgListAddresses(GFGpgCurrentGpgContextChannel(), secret_only ? 1 : 0,
- &raw) != 0) {
+ if (GFGpgListAddresses(GFModuleSdkContext(),
+ GFGpgCurrentChannel(GFModuleSdkContext()),
+ secret_only ? 1 : 0, &raw) != 0) {
return QStringList{};
}
- const auto count = GFStringListCount(raw);
+ const auto count = GFStringListCount(GFModuleSdkContext(), raw);
QStringList out;
out.reserve(static_cast<qsizetype>(count));
// Copied rather than taken: the accessor returns a BORROWED pointer that
// dies with the list, which goes back in a single release below.
for (size_t i = 0; i < count; ++i) {
- out.append(QString::fromUtf8(GFStringListAt(raw, i)));
+ out.append(
+ QString::fromUtf8(GFStringListAt(GFModuleSdkContext(), raw, i)));
}
- GFStringListRelease(raw);
+ GFStringListRelease(GFModuleSdkContext(), raw);
return out;
};
@@ -3133,7 +3143,7 @@ void EMailPageView::slot_selection_changed() {
}
void EMailPageView::slot_add_attachment() {
- auto default_dir = UnStrDup(GFUIDefaultUserFilePath());
+ auto default_dir = gf::sdk::UserFilePath(GFModuleSdkContext());
if (default_dir.isEmpty()) default_dir = QDir::homePath();
attach_paths(
@@ -3324,9 +3334,9 @@ void EMailPageView::import_message_keys() {
// them carries them for the same reason, and asking twice about one act the
// user already asked for would be a dialog per part.
for (const auto* part : keys) {
- GFGpgImportKeys(GFGpgCurrentGpgContextChannel(), this,
- part->data.constData(),
- static_cast<int>(part->data.size()));
+ gf::sdk::ImportKeys(GFModuleSdkContext(),
+ GFGpgCurrentChannel(GFModuleSdkContext()), this,
+ part->data);
}
// The keyring is what the Security tab was reporting against, so it has to
@@ -3340,8 +3350,9 @@ void EMailPageView::import_attachment_key(const EMailAttachment& att) {
// The host owns the import, including whatever it wants to say about what
// came of it: this module has no business inventing a second report of the
// same operation.
- GFGpgImportKeys(GFGpgCurrentGpgContextChannel(), this, att.data.constData(),
- static_cast<int>(att.data.size()));
+ gf::sdk::ImportKeys(GFModuleSdkContext(),
+ GFGpgCurrentChannel(GFModuleSdkContext()), this,
+ att.data);
}
void EMailPageView::open_attachment(QTreeWidgetItem* item) {
@@ -3506,7 +3517,7 @@ void EMailPageView::slot_save_all_attachments() {
void EMailPageView::save_attachments(const QList<EMailAttachment>& chosen) {
if (chosen.isEmpty()) return;
- auto default_dir = UnStrDup(GFUIDefaultUserFilePath());
+ auto default_dir = gf::sdk::UserFilePath(GFModuleSdkContext());
if (default_dir.isEmpty()) default_dir = QDir::homePath();
if (chosen.size() == 1) {
diff --git a/src/m_email/EMailSecret.cpp b/src/m_email/EMailSecret.cpp
index a8f6ea3..7068edd 100644
--- a/src/m_email/EMailSecret.cpp
+++ b/src/m_email/EMailSecret.cpp
@@ -56,17 +56,20 @@ void EMailSecret::Wipe() {
bytes_.shrink_to_fit();
}
-auto EMailSecret::AdoptCString(char* source) -> std::shared_ptr<EMailSecret> {
+auto EMailSecret::AdoptBuffer(GFSDKContext* ctx, GFBufferRef source)
+ -> std::shared_ptr<EMailSecret> {
auto secret = std::make_shared<EMailSecret>();
if (source == nullptr) return secret;
- const auto length = std::strlen(source);
- secret->bytes_.assign(source, source + length);
+ const auto* data = static_cast<const char*>(GFBufferData(ctx, source));
+ const auto size = GFBufferSize(ctx, source);
+ if (data != nullptr && size != 0) {
+ secret->bytes_.assign(data, data + size);
+ }
- // The SDK's buffer is wiped and returned to the secure allocator it came
- // from, so the secret exists in exactly one place from here on.
- SecureZero(source, length);
- GFSecFreeMemory(static_cast<void*>(source));
+ // Release wipes: the secret exists in exactly one place from here on, and
+ // that place is a vector this class can erase.
+ GFBufferRelease(ctx, source);
return secret;
}
@@ -85,8 +88,9 @@ auto EMailSecret::CopyFrom(const QString& text)
}
auto EMailSecret::ToSecureCString() const -> char* {
- auto* buffer = static_cast<char*>(
- GFSecAllocateMemory(static_cast<uint32_t>(bytes_.size() + 1)));
+ auto* buffer =
+ static_cast<char*>(GFMemAlloc(GFModuleSdkContext(), GF_ARENA_SECURE,
+ static_cast<uint32_t>(bytes_.size() + 1)));
if (buffer == nullptr) return nullptr;
if (!bytes_.empty()) std::memcpy(buffer, bytes_.data(), bytes_.size());
diff --git a/src/m_email/EMailSecret.h b/src/m_email/EMailSecret.h
index ad40da3..9ee3ed1 100644
--- a/src/m_email/EMailSecret.h
+++ b/src/m_email/EMailSecret.h
@@ -28,6 +28,8 @@
#pragma once
+#include <GFSDKBuffer.h>
+
#include <QMetaType>
#include <memory>
#include <string>
@@ -59,9 +61,13 @@ class EMailSecret {
EMailSecret() = default;
~EMailSecret() { Wipe(); }
- /// Takes the bytes of a NUL-terminated C string, then wipes the source.
- /// For the SDK's secure buffers, which arrive as char*.
- static auto AdoptCString(char* source) -> std::shared_ptr<EMailSecret>;
+ /// Takes the bytes out of an SDK buffer, then wipes and releases it.
+ ///
+ /// A buffer rather than a char*: the SDK hands secrets back as GFBufferRef
+ /// now, and a buffer wipes itself on release, so the secret never sits in
+ /// an ordinary allocation on the way here.
+ static auto AdoptBuffer(GFSDKContext* ctx, GFBufferRef source)
+ -> std::shared_ptr<EMailSecret>;
/// Copies from a QString, for text the user typed into a field. The QString
/// itself cannot be erased -- see the class note -- so the shorter its life
@@ -105,6 +111,11 @@ class EMailSecret {
/// allocated. Never free it yourself once it has been handed over.
[[nodiscard]] auto ToSecureCString() const -> char*;
+ /// The raw bytes, borrowed, for handing straight into a wipeable SDK
+ /// buffer. Deliberately not a QString or QByteArray: both are implicitly
+ /// shared and neither can be erased afterwards.
+ [[nodiscard]] auto Data() const -> const char* { return bytes_.data(); }
+
/// Overwrites the buffer and releases it. Safe to call more than once.
void Wipe();
diff --git a/src/m_email/EMailSecurityView.cpp b/src/m_email/EMailSecurityView.cpp
index 2f32af5..8d3eb0f 100644
--- a/src/m_email/EMailSecurityView.cpp
+++ b/src/m_email/EMailSecurityView.cpp
@@ -533,8 +533,10 @@ void EMailSecurityView::add_key_section(const QStringList& addresses,
// The address stays ours: every SDK argument is borrowed, so the
// QByteArray below is safe to let die at the end of the statement.
GFGpgKeyBriefListRef briefs = nullptr;
- GFGpgFindKeys(channel, email.toUtf8().constData(), &briefs);
- const auto count = static_cast<int>(GFGpgKeyBriefListCount(briefs));
+ GFGpgFindKeys(GFModuleSdkContext(), channel, email.toUtf8().constData(),
+ &briefs);
+ const auto count =
+ static_cast<int>(GFGpgKeyBriefCount(GFModuleSdkContext(), briefs));
auto* item = new QTreeWidgetItem(group);
item->setText(kColItem, email);
@@ -545,7 +547,7 @@ void EMailSecurityView::add_key_section(const QStringList& addresses,
// Recorded on the row so the menu knows this is an address with nothing
// behind it, which is the one case where importing is worth offering.
item->setData(kColItem, kRoleMissingKey, true);
- GFGpgKeyBriefListRelease(briefs);
+ GFGpgKeyBriefRelease(GFModuleSdkContext(), briefs);
continue;
}
@@ -556,12 +558,14 @@ void EMailSecurityView::add_key_section(const QStringList& addresses,
// Borrowed accessors throughout: nothing here is separately releasable,
// so the whole list goes back in one call at the end.
auto* key_item = new QTreeWidgetItem(item);
- key_item->setText(kColItem,
- QString::fromUtf8(GFGpgKeyBriefUid(briefs, i)));
+ key_item->setText(
+ kColItem, QString::fromUtf8(
+ GFGpgKeyBriefAt(GFModuleSdkContext(), briefs, i)->uid));
// Usability. Says whether the key can be used at all -- and nothing
// whatsoever about whose key it is.
- const auto usability = GFGpgKeyBriefUsability(briefs, i);
+ const auto usability =
+ GFGpgKeyBriefAt(GFModuleSdkContext(), briefs, i)->usability;
key_item->setText(kColValue,
tr("key is %1").arg(DescribeUsability(usability)));
EMailSetCellTone(
@@ -570,8 +574,10 @@ void EMailSecurityView::add_key_section(const QStringList& addresses,
auto* fpr = new QTreeWidgetItem(key_item);
fpr->setText(kColItem, tr("Fingerprint"));
- fpr->setText(kColValue,
- QString::fromUtf8(GFGpgKeyBriefFingerprint(briefs, i)));
+ fpr->setText(
+ kColValue,
+ QString::fromUtf8(
+ GFGpgKeyBriefAt(GFModuleSdkContext(), briefs, i)->fingerprint));
EMailSetCellTone(fpr, kColItem, EMailTone::kMUTED, this);
// Identity binding, kept as its own row. A usable key carrying this
@@ -579,11 +585,13 @@ void EMailSecurityView::add_key_section(const QStringList& addresses,
// single combined verdict would bury.
auto* identity = new QTreeWidgetItem(key_item);
identity->setText(kColItem, tr("Identity"));
- if (GFGpgKeyBriefMatchedUidRevoked(briefs, i) != 0) {
+ if (GFGpgKeyBriefAt(GFModuleSdkContext(), briefs, i)
+ ->matched_uid_revoked != 0) {
identity->setText(
kColValue, tr("this address is on a REVOKED user ID of the key"));
EMailSetCellTone(identity, kColValue, EMailTone::kWARN, this);
- } else if (GFGpgKeyBriefMatchedUidIsPrimary(briefs, i) != 0) {
+ } else if (GFGpgKeyBriefAt(GFModuleSdkContext(), briefs, i)
+ ->matched_uid_is_primary != 0) {
identity->setText(kColValue, tr("this address is the key's primary "
"user ID"));
EMailSetCellTone(identity, kColValue, EMailTone::kGOOD, this);
@@ -593,7 +601,7 @@ void EMailSecurityView::add_key_section(const QStringList& addresses,
EMailSetCellTone(identity, kColValue, EMailTone::kMUTED, this);
}
- if (GFGpgKeyBriefCanEncrypt(briefs, i) == 0) {
+ if (GFGpgKeyBriefAt(GFModuleSdkContext(), briefs, i)->can_encrypt == 0) {
auto* note = new QTreeWidgetItem(key_item);
note->setText(kColItem, tr("Note"));
note->setText(kColValue, tr("this key cannot be used for encryption"));
@@ -601,7 +609,7 @@ void EMailSecurityView::add_key_section(const QStringList& addresses,
}
}
- GFGpgKeyBriefListRelease(briefs);
+ GFGpgKeyBriefRelease(GFModuleSdkContext(), briefs);
}
}
diff --git a/src/m_email/EMailStructureView.cpp b/src/m_email/EMailStructureView.cpp
index b04b1d7..d470556 100644
--- a/src/m_email/EMailStructureView.cpp
+++ b/src/m_email/EMailStructureView.cpp
@@ -190,9 +190,9 @@ void EMailStructureView::show_part_menu(const QPoint& pos) {
save_part(*part);
} else if (import != nullptr && chosen == import) {
// The host owns the import and whatever it reports about the outcome.
- GFGpgImportKeys(GFGpgCurrentGpgContextChannel(), this,
- part->data.constData(),
- static_cast<int>(part->data.size()));
+ gf::sdk::ImportKeys(GFModuleSdkContext(),
+ GFGpgCurrentChannel(GFModuleSdkContext()), this,
+ part->data);
} else if (chosen == copy_type) {
clipboard->setText(part->content_type);
} else if (chosen == copy_name) {
diff --git a/src/m_email/EMailViewStyle.h b/src/m_email/EMailViewStyle.h
index e52dc14..3968b98 100644
--- a/src/m_email/EMailViewStyle.h
+++ b/src/m_email/EMailViewStyle.h
@@ -41,6 +41,7 @@
#include <QTreeWidgetItem>
#include <QVBoxLayout>
#include <QWidget>
+#include <array>
#include <functional>
#include "GFModule.h"
@@ -58,34 +59,39 @@
* de-emphasised rather than red. Red belongs to irreversible acts and to
* secrets travelling in the clear, not to "this part is unsigned".
*/
-inline auto EMailThemeColor(QWidget* w, uint32_t (*getter)(void*)) -> QColor {
- const auto rgba = getter(w);
+/// One colour, by role. Five host entry points became one taking a role, so
+/// the function pointer this used to take is now an int.
+inline auto EMailThemeColor(QWidget* w, int role) -> QColor {
+ const auto rgba = GFUIThemeColor(GFModuleSdkContext(), role, w);
return rgba == 0 ? w->palette().color(QPalette::WindowText)
: QColor::fromRgba(rgba);
}
inline auto EMailMutedColor(QWidget* w) -> QColor {
- return EMailThemeColor(w, &GFUIMutedTextColor);
+ return EMailThemeColor(w, GF_UI_COLOR_MUTED_TEXT);
}
inline auto EMailWarningColor(QWidget* w) -> QColor {
- return EMailThemeColor(w, &GFUIWarningColor);
+ return EMailThemeColor(w, GF_UI_COLOR_WARNING);
}
inline auto EMailAccentColor(QWidget* w, bool positive) -> QColor {
- const auto rgba = GFUIAccentColor(w, positive ? 1 : 0);
- return rgba == 0 ? w->palette().color(QPalette::WindowText)
- : QColor::fromRgba(rgba);
+ return EMailThemeColor(
+ w, positive ? GF_UI_COLOR_ACCENT_POSITIVE : GF_UI_COLOR_ACCENT_NEGATIVE);
}
/// A size written the way the rest of the application writes it.
+///
+/// Pure: it formats into a local buffer and needs no context at all.
inline auto EMailHumanSize(qint64 bytes) -> QString {
- return UnStrDup(GFUIHumanSize(bytes));
+ std::array<char, 64> text{};
+ const auto written = GFUIHumanSize(bytes, text.data(), text.size());
+ return written < 0 ? QString() : QString::fromUtf8(text.data(), written);
}
/// The application's own border colour, for the one-pixel rules around cards.
inline auto EMailBorderColor(QWidget* w) -> QColor {
- return EMailThemeColor(w, &GFUIBorderColor);
+ return EMailThemeColor(w, GF_UI_COLOR_BORDER);
}
/// @p a mixed with @p b, @p t of the way towards @p b.
@@ -333,7 +339,8 @@ inline auto EMailTintedBanner(QWidget* parent, const QColor& tint,
* nothing here is worth refusing to build a widget over.
*/
inline auto EMailViewSettings() -> QSettings* {
- return qobject_cast<QSettings*>(static_cast<QObject*>(GFUIGlobalSettings()));
+ return qobject_cast<QSettings*>(
+ static_cast<QObject*>(GFStorageSettingsRoot(GFModuleSdkContext())));
}
/**
@@ -370,7 +377,7 @@ inline auto EMailToneColor(QWidget* owner, EMailTone tone) -> QColor {
case EMailTone::kWARN:
return EMailWarningColor(owner);
case EMailTone::kDANGER:
- return EMailThemeColor(owner, &GFUIDangerColor);
+ return EMailThemeColor(owner, GF_UI_COLOR_DANGER);
case EMailTone::kDEFAULT:
break;
}
diff --git a/src/m_email/module.json b/src/m_email/module.json
index 92f0104..f27d188 100644
--- a/src/m_email/module.json
+++ b/src/m_email/module.json
@@ -7,8 +7,9 @@
"author": "Saturneric",
"capabilities": [
"gpg",
- "ui",
- "network"
+ "network",
+ "storage",
+ "ui"
],
"events": [
"EDIT_TAB_TYPE_EMAIL_OP_DECRYPT",
diff --git a/src/m_email/test/CMakeLists.txt b/src/m_email/test/CMakeLists.txt
index 3186cb0..d38182b 100644
--- a/src/m_email/test/CMakeLists.txt
+++ b/src/m_email/test/CMakeLists.txt
@@ -116,14 +116,17 @@ target_include_directories(gf_mod_email_net_test PRIVATE
"${CMAKE_BINARY_DIR}/src"
"${CMAKE_BINARY_DIR}/src/sdk")
+# The REAL module-side SDK, against a fake host table the harness supplies.
+# That is the point of the split: the SDK is stateless and takes a context, so
+# a test can hand it one without a host anywhere in the process.
target_link_libraries(gf_mod_email_net_test PRIVATE
GTest::gtest GTest::gtest_main Qt::Core Qt::Network vmime-static
- OpenSSL::SSL OpenSSL::Crypto)
+ OpenSSL::SSL OpenSSL::Crypto gf_sdk)
-# The stubs stand in for the SDK, so the real export macros must expand to
-# nothing rather than to a visibility attribute on a symbol we define here.
+# Only the module export macro needs neutering now: nothing here defines an
+# SDK symbol any more.
target_compile_definitions(gf_mod_email_net_test PRIVATE
- VMIME_STATIC "GF_SDK_EXPORT=" "GF_MODULE_EXPORT=")
+ VMIME_STATIC "GF_MODULE_EXPORT=")
set_target_properties(gf_mod_email_net_test PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${GPGFRONTEND_MODULE_TEST_OUTPUT_DIR}")
@@ -164,10 +167,10 @@ target_include_directories(gf_mod_email_crypto_test PRIVATE
"${CMAKE_BINARY_DIR}/src/sdk")
target_link_libraries(gf_mod_email_crypto_test PRIVATE
- GTest::gtest GTest::gtest_main Qt::Core Qt::Network vmime-static)
+ GTest::gtest GTest::gtest_main Qt::Core Qt::Network vmime-static gf_sdk)
target_compile_definitions(gf_mod_email_crypto_test PRIVATE
- VMIME_STATIC "GF_SDK_EXPORT=" "GF_MODULE_EXPORT=")
+ VMIME_STATIC "GF_MODULE_EXPORT=")
set_target_properties(gf_mod_email_crypto_test PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${GPGFRONTEND_MODULE_TEST_OUTPUT_DIR}")
diff --git a/src/m_email/test/EMailCryptoSdkStubs.cpp b/src/m_email/test/EMailCryptoSdkStubs.cpp
index 0e322ef..2553b7f 100644
--- a/src/m_email/test/EMailCryptoSdkStubs.cpp
+++ b/src/m_email/test/EMailCryptoSdkStubs.cpp
@@ -41,7 +41,10 @@
#include "EMailCryptoRecorder.h"
//
+#include <GFSDKBuildInfo.h>
+#include <GFSDKContext.h>
#include <GFSDKGpg.h>
+#include <GFSDKHostApi.h>
#include <QByteArray>
#include <cstdint>
@@ -79,15 +82,15 @@ auto Get() -> Recording& { return g_recording; }
using crypto_recorder::g_recording;
-extern "C" {
+namespace {
-void* GFAllocateMemory(unsigned int n) {
+void* MemAllocNormal(unsigned int n) {
void* p = std::malloc(n);
NormalArena().insert(p);
return p;
}
-void GFFreeMemory(void* p) {
+void MemFreeNormal(void* p) {
if (p == nullptr) return;
if (NormalArena().erase(p) == 0) {
std::printf("[FAIL] GFFreeMemory on a pointer it does not own\n");
@@ -97,13 +100,13 @@ void GFFreeMemory(void* p) {
std::free(p);
}
-void* GFSecAllocateMemory(unsigned int n) {
+void* MemAllocSecure(unsigned int n) {
void* p = std::malloc(n);
SecureArena().insert(p);
return p;
}
-void GFSecFreeMemory(void* p) {
+void MemFreeSecure(void* p) {
if (p == nullptr) return;
if (SecureArena().erase(p) == 0) {
std::printf("[FAIL] GFSecFreeMemory on a pointer it does not own\n");
@@ -114,42 +117,29 @@ void GFSecFreeMemory(void* p) {
}
// Deliberately identical to the shipping implementation, truncation and all.
-char* GFModuleStrDup(const char* s) {
+char* StrDupNormal(const char* s) {
if (s == nullptr) return nullptr;
auto n = std::strlen(s) + 1;
- char* d = static_cast<char*>(GFAllocateMemory(n));
+ char* d = static_cast<char*>(MemAllocNormal(n));
std::memcpy(d, s, n);
return d;
}
-char* GFModuleSecStrDup(const char* s) {
+char* StrDupSecure(const char* s) {
if (s == nullptr) return nullptr;
auto n = std::strlen(s) + 1;
- char* d = static_cast<char*>(GFSecAllocateMemory(n));
+ char* d = static_cast<char*>(MemAllocSecure(n));
std::memcpy(d, s, n);
return d;
}
-void GFModuleLogTrace(const char*) {}
-void GFModuleLogDebug(const char*) {}
-void GFModuleLogInfo(const char*) {}
-void GFModuleLogWarn(const char*) {}
-void GFModuleLogError(const char*) {}
-void GFModuleLogAt(const char*, int, const char*, int, const char*,
- const char*) {}
-int GFModuleLogEnabled(const char*, int) { return 1; }
-
-char* GFAppActiveLocale() { return GFModuleStrDup("en_US"); }
+void LogWrite(GFHostContextRef, int, const char*, int, const char*,
+ const char*) {}
+auto LogEnabled(GFHostContextRef, int) -> int { return 1; }
-char* GFGpgPublicKey(int, const char* key_id, int) {
- // Borrowed, not consumed: the real entry point stopped freeing its
- // arguments, and a stub that still frees would report a bogus allocator
- // violation for every correct call.
- (void)key_id;
- return GFModuleStrDup(
- "-----BEGIN PGP PUBLIC KEY BLOCK-----\nstub\n"
- "-----END PGP PUBLIC KEY BLOCK-----\n");
-}
+auto AppText(GFHostContextRef) -> const char* { return "test"; }
+auto AppLocale(GFHostContextRef) -> char* { return StrDupNormal("en_US"); }
+auto AppZero(GFHostContextRef) -> int { return 0; }
// --- the four crypto entry points -------------------------------------------
//
@@ -161,7 +151,7 @@ namespace {
// Mirrors GFBytesDup on the host side: byte-exact, NUL-terminated one past the
// end, with the true length reported separately.
auto BytesOut(const QByteArray& b, size_t* size) -> char* {
- auto* d = static_cast<char*>(GFAllocateMemory(b.size() + 1));
+ auto* d = static_cast<char*>(MemAllocNormal(b.size() + 1));
std::memcpy(d, b.constData(), b.size());
d[b.size()] = '\0';
if (size != nullptr) *size = static_cast<size_t>(b.size());
@@ -169,12 +159,18 @@ auto BytesOut(const QByteArray& b, size_t* size) -> char* {
}
} // namespace
-int GFAnalyseVerifyResultInfoByCapsule(int, uint32_t, const char* capsule_id,
- const char** analyse, const char** cards,
- const char** info_json) {
+auto GpgAnalyse(GFHostContextRef, int, int, uint32_t, const char* capsule_id,
+ uint32_t, GFGpgAnalysis* out) -> int {
(void)capsule_id; // borrowed, like every SDK argument
- if (analyse != nullptr) *analyse = GFModuleStrDup("");
- if (cards != nullptr) *cards = GFModuleStrDup("[]");
+ if (out == nullptr) return -1;
+ out->struct_size = sizeof(GFGpgAnalysis);
+
+ // Nine entry points became one filling a struct, so the fake fills it.
+ const char** analyse = const_cast<const char**>(&out->report);
+ const char** cards = const_cast<const char**>(&out->cards);
+ const char** info_json = const_cast<const char**>(&out->info_json);
+ if (analyse != nullptr) *analyse = StrDupNormal("");
+ if (cards != nullptr) *cards = StrDupNormal("[]");
// One good signature, so ParseSignatureResults has something to walk --
// unless the test queued an answer for this call. Regions are analysed in
@@ -183,9 +179,9 @@ int GFAnalyseVerifyResultInfoByCapsule(int, uint32_t, const char* capsule_id,
auto& recording = crypto_recorder::Get();
if (!recording.verify_info_json.isEmpty()) {
const auto queued = recording.verify_info_json.takeFirst();
- *info_json = GFModuleStrDup(queued.constData());
+ *info_json = StrDupNormal(queued.constData());
} else {
- *info_json = GFModuleStrDup(
+ *info_json = StrDupNormal(
R"({"signatures":[{"status":"good","fingerprint":"DEADBEEF",)"
R"("hash_algo":"SHA256","uid":"Test <[email protected]>"}]})");
}
@@ -193,7 +189,7 @@ int GFAnalyseVerifyResultInfoByCapsule(int, uint32_t, const char* capsule_id,
return 0;
}
-} // extern "C"
+} // namespace
/* ===================================================================== *
* Handle-based SDK: buffers and results
@@ -282,7 +278,7 @@ auto KeyIdCount(const char* const* key_ids, size_t n) -> int {
extern "C" {
-GFBufferRef GFBufferNewFromBytes(const void* data, size_t size) {
+auto BufNew(GFHostContextRef, const void* data, size_t size) -> GFBufferRef {
if (data == nullptr && size != 0) return nullptr;
auto* b = new StubBuffer{
QByteArray(static_cast<const char*>(data), static_cast<int>(size))};
@@ -290,22 +286,34 @@ GFBufferRef GFBufferNewFromBytes(const void* data, size_t size) {
return reinterpret_cast<GFBufferRef>(b);
}
-const void* GFBufferData(GFBufferView buf) {
+auto GpgPublicKey(GFHostContextRef ctx, int, const char* key_id, int)
+ -> GFBufferRef {
+ // Borrowed, not consumed: the real entry point stopped freeing its
+ // arguments, and a stub that still frees would report a bogus allocator
+ // violation for every correct call.
+ (void)key_id;
+ static constexpr char kBlock[] =
+ "-----BEGIN PGP PUBLIC KEY BLOCK-----\nstub\n"
+ "-----END PGP PUBLIC KEY BLOCK-----\n";
+ return BufNew(ctx, kBlock, sizeof(kBlock) - 1);
+}
+
+auto BufData(GFHostContextRef, GFBufferView buf) -> const void* {
auto* impl = LiveBuf(buf);
return impl == nullptr ? nullptr : impl->bytes.constData();
}
-size_t GFBufferSize(GFBufferView buf) {
+auto BufSize(GFHostContextRef, GFBufferView buf) -> size_t {
auto* impl = LiveBuf(buf);
return impl == nullptr ? 0 : static_cast<size_t>(impl->bytes.size());
}
-void GFBufferZeroize(GFBufferRef buf) {
+void BufZeroize(GFHostContextRef, GFBufferRef buf) {
auto* impl = LiveBuf(buf);
if (impl != nullptr) impl->bytes.fill('\0');
}
-void GFBufferRelease(GFBufferRef buf) {
+void BufRelease(GFHostContextRef, GFBufferRef buf) {
if (buf == nullptr) return;
if (LiveBuffers().erase(buf) == 0) {
std::printf("[FAIL] GFBufferRelease on a handle it does not own\n");
@@ -315,10 +323,11 @@ void GFBufferRelease(GFBufferRef buf) {
delete reinterpret_cast<StubBuffer*>(buf);
}
-size_t GFBufferOutstandingCount(const char*) { return LiveBuffers().size(); }
+auto BufOutstanding(GFHostContextRef) -> size_t { return LiveBuffers().size(); }
-int GFGpgSign(int, const char* const* key_ids, size_t key_ids_size,
- GFBufferView in, int sign_mode, int, GFGpgResultRef* out) {
+auto GpgSign(GFHostContextRef, int, const char* const* key_ids,
+ size_t key_ids_size, GFBufferView in, int sign_mode, int,
+ GFGpgResultRef* out) -> int {
if (out == nullptr) return -1;
g_recording.sign.append({BufferBytes(in), sign_mode});
(void)KeyIdCount(key_ids, key_ids_size);
@@ -331,8 +340,8 @@ int GFGpgSign(int, const char* const* key_ids, size_t key_ids_size,
return GF_GPG_OP_FAILED;
}
- r->data = GFBufferNewFromBytes(g_recording.sign_output.constData(),
- g_recording.sign_output.size());
+ r->data = BufNew(nullptr, g_recording.sign_output.constData(),
+ g_recording.sign_output.size());
r->hash_algo = "SHA256";
r->capsule_id = "stub-capsule";
r->error_string = "Success";
@@ -340,8 +349,9 @@ int GFGpgSign(int, const char* const* key_ids, size_t key_ids_size,
return GF_GPG_OK;
}
-int GFGpgEncrypt(int, const char* const* key_ids, size_t key_ids_size,
- GFBufferView in, int, GFGpgResultRef* out) {
+auto GpgEncrypt(GFHostContextRef, int, const char* const* key_ids,
+ size_t key_ids_size, GFBufferView in, int, GFGpgResultRef* out)
+ -> int {
if (out == nullptr) return -1;
g_recording.encrypt.append({BufferBytes(in)});
(void)KeyIdCount(key_ids, key_ids_size);
@@ -354,15 +364,16 @@ int GFGpgEncrypt(int, const char* const* key_ids, size_t key_ids_size,
return GF_GPG_OP_FAILED;
}
- r->data = GFBufferNewFromBytes(g_recording.encrypt_output.constData(),
- g_recording.encrypt_output.size());
+ r->data = BufNew(nullptr, g_recording.encrypt_output.constData(),
+ g_recording.encrypt_output.size());
r->capsule_id = "stub-capsule";
r->error_string = "Success";
*out = reinterpret_cast<GFGpgResultRef>(r);
return GF_GPG_OK;
}
-int GFGpgDecrypt(int, GFBufferView in, GFGpgResultRef* out) {
+auto GpgDecrypt(GFHostContextRef, int, GFBufferView in, GFGpgResultRef* out)
+ -> int {
if (out == nullptr) return -1;
g_recording.decrypt.append({BufferBytes(in)});
@@ -374,16 +385,16 @@ int GFGpgDecrypt(int, GFBufferView in, GFGpgResultRef* out) {
return GF_GPG_OP_FAILED;
}
- r->data = GFBufferNewFromBytes(g_recording.decrypt_output.constData(),
- g_recording.decrypt_output.size());
+ r->data = BufNew(nullptr, g_recording.decrypt_output.constData(),
+ g_recording.decrypt_output.size());
r->capsule_id = "stub-capsule";
r->error_string = "Success";
*out = reinterpret_cast<GFGpgResultRef>(r);
return GF_GPG_OK;
}
-int GFGpgVerify(int, GFBufferView in, GFBufferView signature,
- GFGpgResultRef* out) {
+auto GpgVerify(GFHostContextRef, int, GFBufferView in, GFBufferView signature,
+ GFGpgResultRef* out) -> int {
if (out == nullptr) return -1;
g_recording.verify.append({BufferBytes(in), BufferBytes(signature)});
@@ -401,37 +412,37 @@ int GFGpgVerify(int, GFBufferView in, GFBufferView signature,
return GF_GPG_OK;
}
-int GFGpgResultStatusOf(GFGpgResultRef r) {
+auto GpgResultStatus(GFHostContextRef, GFGpgResultRef r) -> int {
auto* impl = LiveRes(r);
return impl == nullptr ? GF_GPG_BAD_REQUEST : impl->status;
}
-uint32_t GFGpgResultError(GFGpgResultRef r) {
+auto GpgResultError(GFHostContextRef, GFGpgResultRef r) -> uint32_t {
auto* impl = LiveRes(r);
return impl == nullptr ? 0 : impl->error;
}
-GFBufferView GFGpgResultData(GFGpgResultRef r) {
+auto GpgResultData(GFHostContextRef, GFGpgResultRef r) -> GFBufferView {
auto* impl = LiveRes(r);
return impl == nullptr ? nullptr : impl->data;
}
-const char* GFGpgResultCapsuleId(GFGpgResultRef r) {
+auto ResultCapsuleId(GFGpgResultRef r) -> const char* {
auto* impl = LiveRes(r);
return impl == nullptr ? "" : impl->capsule_id.constData();
}
-const char* GFGpgResultErrorString(GFGpgResultRef r) {
+auto ResultErrorString(GFGpgResultRef r) -> const char* {
auto* impl = LiveRes(r);
return impl == nullptr ? "" : impl->error_string.constData();
}
-const char* GFGpgResultHashAlgo(GFGpgResultRef r) {
+auto ResultHashAlgo(GFGpgResultRef r) -> const char* {
auto* impl = LiveRes(r);
return impl == nullptr ? "" : impl->hash_algo.constData();
}
-GFBufferRef GFGpgResultTakeData(GFGpgResultRef r) {
+auto GpgResultTakeData(GFHostContextRef, GFGpgResultRef r) -> GFBufferRef {
auto* impl = LiveRes(r);
if (impl == nullptr) return nullptr;
auto* taken = impl->data;
@@ -439,7 +450,7 @@ GFBufferRef GFGpgResultTakeData(GFGpgResultRef r) {
return taken;
}
-void GFGpgResultRelease(GFGpgResultRef r) {
+void GpgResultRelease(GFHostContextRef, GFGpgResultRef r) {
if (r == nullptr) return;
if (LiveResults().erase(r) == 0) {
std::printf("[FAIL] GFGpgResultRelease on a handle it does not own\n");
@@ -447,15 +458,134 @@ void GFGpgResultRelease(GFGpgResultRef r) {
return;
}
auto* impl = reinterpret_cast<StubResult*>(r);
- GFBufferRelease(impl->data);
+ BufRelease(nullptr, impl->data);
delete impl;
}
-size_t GFGpgResultOutstandingCount(const char*) { return LiveResults().size(); }
+auto GpgResultOutstanding(GFHostContextRef) -> size_t {
+ return LiveResults().size();
+}
+
+/* --- the tables ----------------------------------------------------------- */
-} // extern "C"
+auto MemAlloc(GFHostContextRef, int arena, uint32_t n) -> void* {
+ return arena == GF_ARENA_SECURE ? MemAllocSecure(n) : MemAllocNormal(n);
+}
+auto MemRealloc(GFHostContextRef, int, void* p, uint32_t n) -> void* {
+ return std::realloc(p, n);
+}
+void MemFree(GFHostContextRef, int arena, void* p) {
+ if (arena == GF_ARENA_SECURE) {
+ MemFreeSecure(p);
+ } else {
+ MemFreeNormal(p);
+ }
+}
+auto MemStrDup(GFHostContextRef, int arena, const char* s) -> char* {
+ return arena == GF_ARENA_SECURE ? StrDupSecure(s) : StrDupNormal(s);
+}
+
+/// Three named getters became one taking a field, so the fake follows.
+auto GpgResultText(GFHostContextRef, GFGpgResultRef r, int field) -> const
+ char* {
+ switch (field) {
+ case GF_GPG_RESULT_TEXT_ERROR_STRING:
+ return ResultErrorString(r);
+ case GF_GPG_RESULT_TEXT_HASH_ALGO:
+ return ResultHashAlgo(r);
+ default:
+ return ResultCapsuleId(r);
+ }
+}
+
+const GFHostBufferApi kBuffer = {
+ sizeof(GFHostBufferApi),
+ &BufNew,
+ &BufData,
+ &BufSize,
+ &BufZeroize,
+ &BufRelease,
+ &BufOutstanding,
+ &MemAlloc,
+ &MemRealloc,
+ &MemFree,
+ &MemStrDup,
+};
+
+const GFHostLogApi kLog = {sizeof(GFHostLogApi), &LogWrite, &LogEnabled};
+
+const GFHostAppApi kApp = {
+ sizeof(GFHostAppApi), &AppText, &AppText, &AppText, &AppText,
+ &AppLocale, &AppZero, &AppZero,
+};
+
+auto NotImplementedInt(GFHostContextRef, ...) -> int { return -1; }
+
+const GFHostGpgApi kGpg = {
+ sizeof(GFHostGpgApi),
+ nullptr, // current_channel: these tests always name the channel
+ &GpgSign,
+ &GpgEncrypt,
+ &GpgDecrypt,
+ &GpgVerify,
+ &GpgResultStatus,
+ &GpgResultError,
+ &GpgResultData,
+ &GpgResultText,
+ &GpgResultTakeData,
+ &GpgResultRelease,
+ &GpgResultOutstanding,
+ &GpgAnalyse,
+ &GpgPublicKey,
+ nullptr, // key_primary_uid
+ nullptr, // export_key
+ nullptr, // import_keys
+ nullptr, // find_keys
+ nullptr, // key_brief_count
+ nullptr, // key_brief_at
+ nullptr, // key_brief_release
+ nullptr, // sniff_recipients
+ nullptr, // recipient_count
+ nullptr, // recipient_at
+ nullptr, // recipient_release
+ nullptr, // list_addresses
+};
+
+struct FakeHostContext {
+ int marker = 0;
+};
+
+auto TheTable() -> GFHostApi& {
+ static FakeHostContext host_context;
+ static GFHostApi host = [&]() {
+ GFHostApi t{};
+ t.struct_size = sizeof(GFHostApi);
+ t.abi_version = GF_SDK_ABI_VERSION;
+ t.module_id = "harness";
+ t.context = reinterpret_cast<GFHostContextRef>(&host_context);
+ t.buffer = &kBuffer;
+ t.log = &kLog;
+ t.app = &kApp;
+ t.gpg = &kGpg;
+ return t;
+ }();
+ return host;
+}
+
+} // namespace
+
+/// The module's SDK context, which the runtime would normally own.
+auto GFModuleSdkContext() -> GFSDKContext* {
+ static GFSDKContext context = []() {
+ GFSDKContext c{};
+ c.struct_size = sizeof(GFSDKContext);
+ c.abi_version = GF_SDK_ABI_VERSION;
+ c.host = &TheTable();
+ c.module_id = "harness";
+ return c;
+ }();
+ return &context;
+}
-// C++ linkage, deliberately outside the extern "C" block above: GFModule.h
-// declares it as an ordinary C++ function, and the log macros call it on every
-// line. Defined here with C linkage it compiles cleanly and fails to link.
+/// Identity, for the log macros. C++ linkage, as GFModule.h declares it.
const char* GFGetModuleID() { return "harness"; }
diff --git a/src/m_email/test/EMailSdkStubs.cpp b/src/m_email/test/EMailSdkStubs.cpp
index 4ecee84..5b4e3ef 100644
--- a/src/m_email/test/EMailSdkStubs.cpp
+++ b/src/m_email/test/EMailSdkStubs.cpp
@@ -1,9 +1,21 @@
-// Stand-ins for the SDK symbols these translation units reference.
+// A fake host, for the translation units that reach the SDK.
+//
+// What changed, and why it is a better harness: this file used to define the
+// SDK's exported C symbols by hand. The SDK is now module-side and stateless,
+// so the thing to fake is the HOST TABLE it is handed. Every GFSDK* call these
+// tests make therefore runs the real wrapper code and lands here, which is
+// what lets a test assert that the wrappers behave -- something a hand-written
+// stub of the wrapper itself could never show.
//
// The allocator stubs are not merely placeholders: they enforce the same
-// ownership contract the real SDK does, so that handing a function memory from
-// the wrong arena is reported here instead of aborting the process with
+// arena contract the real host does, so handing a function memory from the
+// wrong arena is reported here instead of aborting the process with
// munmap_chunk() at runtime.
+
+#include <GFSDKBuildInfo.h>
+#include <GFSDKContext.h>
+#include <GFSDKHostApi.h>
+
#include <QSettings>
#include <QString>
#include <cstdio>
@@ -12,6 +24,7 @@
#include <set>
namespace {
+
std::set<void*>& NormalArena() {
static std::set<void*> a;
return a;
@@ -20,6 +33,7 @@ std::set<void*>& SecureArena() {
static std::set<void*> a;
return a;
}
+
} // namespace
int g_allocator_violations = 0;
@@ -28,84 +42,155 @@ namespace harness {
bool LastSaveUsedSecureValue = false;
}
-extern "C" {
+namespace {
+
+/* --- buffers and memory --------------------------------------------------- */
-void* GFAllocateMemory(unsigned int n) {
- void* p = std::malloc(n);
- NormalArena().insert(p);
- return p;
+struct FakeBuffer {
+ size_t size;
+ char* data;
+};
+
+auto BufNew(GFHostContextRef, const void* data, size_t size) -> GFBufferRef {
+ auto* b = new FakeBuffer{size, static_cast<char*>(std::malloc(size + 1))};
+ if (size != 0 && data != nullptr) std::memcpy(b->data, data, size);
+ b->data[size] = '\0';
+ return reinterpret_cast<GFBufferRef>(b);
}
-void GFFreeMemory(void* p) {
- if (p == nullptr) return;
- if (NormalArena().erase(p) == 0) {
- std::printf("[FAIL] GFFreeMemory on a pointer it does not own\n");
- ++g_allocator_violations;
- return;
- }
- std::free(p);
+auto BufData(GFHostContextRef, GFBufferView buf) -> const void* {
+ return buf == nullptr ? nullptr
+ : reinterpret_cast<const FakeBuffer*>(buf)->data;
}
-void* GFSecAllocateMemory(unsigned int n) {
- void* p = std::malloc(n);
- SecureArena().insert(p);
+auto BufSize(GFHostContextRef, GFBufferView buf) -> size_t {
+ return buf == nullptr ? 0 : reinterpret_cast<const FakeBuffer*>(buf)->size;
+}
+
+void BufZeroize(GFHostContextRef, GFBufferRef buf) {
+ if (buf == nullptr) return;
+ auto* b = reinterpret_cast<FakeBuffer*>(buf);
+ std::memset(b->data, 0, b->size);
+}
+
+void BufRelease(GFHostContextRef, GFBufferRef buf) {
+ if (buf == nullptr) return;
+ auto* b = reinterpret_cast<FakeBuffer*>(buf);
+ // Release wipes, as the real one does: these tests carry passwords.
+ std::memset(b->data, 0, b->size);
+ std::free(b->data);
+ delete b;
+}
+
+auto BufOutstanding(GFHostContextRef) -> size_t { return 0; }
+
+auto MemAlloc(GFHostContextRef, int arena, uint32_t n) -> void* {
+ auto* p = std::malloc(n);
+ if (p == nullptr) return nullptr;
+ (arena == GF_ARENA_SECURE ? SecureArena() : NormalArena()).insert(p);
return p;
}
-void GFSecFreeMemory(void* p) {
+auto MemRealloc(GFHostContextRef, int, void* p, uint32_t n) -> void* {
+ return std::realloc(p, n);
+}
+
+void MemFree(GFHostContextRef, int arena, void* p) {
if (p == nullptr) return;
- if (SecureArena().erase(p) == 0) {
- // Exactly the real crash: a pointer that never came from the secure
- // allocator handed to the secure deallocator.
+ auto& mine = arena == GF_ARENA_SECURE ? SecureArena() : NormalArena();
+ if (mine.erase(p) == 0) {
std::printf(
- "[FAIL] GFSecFreeMemory on a pointer it does not own "
- "(this is the munmap_chunk abort)\n");
+ "[FAIL] free from the wrong arena (this is the munmap_chunk abort)\n");
++g_allocator_violations;
return;
}
std::free(p);
}
-char* GFModuleStrDup(const char* s) {
+auto MemStrDup(GFHostContextRef ctx, int arena, const char* s) -> char* {
if (s == nullptr) return nullptr;
- auto n = std::strlen(s) + 1;
- char* d = static_cast<char*>(GFAllocateMemory(n));
- std::memcpy(d, s, n);
- return d;
+ const auto n = std::strlen(s);
+ auto* copy = static_cast<char*>(MemAlloc(ctx, arena, n + 1));
+ if (copy == nullptr) return nullptr;
+ std::memcpy(copy, s, n + 1);
+ return copy;
}
-char* GFModuleSecStrDup(const char* s) {
- if (s == nullptr) return nullptr;
- auto n = std::strlen(s) + 1;
- char* d = static_cast<char*>(GFSecAllocateMemory(n));
- std::memcpy(d, s, n);
- return d;
+const GFHostBufferApi kBuffer = {
+ sizeof(GFHostBufferApi),
+ &BufNew,
+ &BufData,
+ &BufSize,
+ &BufZeroize,
+ &BufRelease,
+ &BufOutstanding,
+ &MemAlloc,
+ &MemRealloc,
+ &MemFree,
+ &MemStrDup,
+};
+
+/* --- log ------------------------------------------------------------------ */
+
+void LogWrite(GFHostContextRef, int, const char*, int, const char*,
+ const char*) {}
+auto LogEnabled(GFHostContextRef, int) -> int { return 1; }
+
+const GFHostLogApi kLog = {sizeof(GFHostLogApi), &LogWrite, &LogEnabled};
+
+/* --- app ------------------------------------------------------------------ */
+
+auto AppText(GFHostContextRef) -> const char* { return "test"; }
+auto AppLocale(GFHostContextRef ctx) -> char* {
+ return MemStrDup(ctx, GF_ARENA_NORMAL, "en_US");
}
+auto AppZero(GFHostContextRef) -> int { return 0; }
+auto AppKeyProtection(GFHostContextRef) -> int { return 1; }
-// Mirrors the real implementation's ownership behaviour: the key goes back to
-// the ordinary allocator, the secret to the secure one.
-int GFSecDurableCacheSave(const char* key, const char* value) {
- if (key == nullptr || value == nullptr) return -1;
- harness::LastSaveUsedSecureValue =
- SecureArena().count(const_cast<char*>(value)) != 0;
- GFFreeMemory(const_cast<char*>(key));
- GFSecFreeMemory(const_cast<char*>(value));
- return 0;
+const GFHostAppApi kApp = {
+ sizeof(GFHostAppApi), &AppText, &AppText, &AppText, &AppText,
+ &AppLocale, &AppZero, &AppKeyProtection,
+};
+
+/* --- ui ------------------------------------------------------------------- */
+
+auto ThemeColor(GFHostContextRef, int role, void*) -> uint32_t {
+ switch (role) {
+ case GF_UI_COLOR_WARNING:
+ return 0xFFFF8800;
+ case GF_UI_COLOR_DANGER:
+ return 0xFFFF0000;
+ case GF_UI_COLOR_ACCENT_POSITIVE:
+ case GF_UI_COLOR_ACCENT_NEGATIVE:
+ return 0xFF0066CC;
+ default:
+ return 0xFF808080;
+ }
}
-char* GFSecDurableCacheGet(const char* key) {
- GFFreeMemory(const_cast<char*>(key));
+auto UiNull(GFHostContextRef, const char*) -> void* { return nullptr; }
+auto UiCreate(GFHostContextRef, QObjectFactory, void*) -> void* {
return nullptr;
}
-
-int GFSecDurableCacheRemove(const char* key) {
- GFFreeMemory(const_cast<char*>(key));
+auto UiShow(GFHostContextRef, void*, void*) -> int { return 0; }
+auto UiPath(GFHostContextRef ctx) -> GFBufferRef {
+ return BufNew(ctx, "/tmp", 4);
+}
+auto UiRegSettings(GFHostContextRef, const GFUISettingsPageSpec*) -> int {
return 0;
}
+auto UiUnreg(GFHostContextRef, const char*) -> int { return 0; }
+auto UiRegTab(GFHostContextRef, const GFUITabViewSpec*) -> int { return 0; }
+auto UiRegExt(GFHostContextRef, const char*, const char*) -> int { return 0; }
-int GFAppKeyProtectionLevel() { return 1; }
+const GFHostUiApi kUi = {
+ sizeof(GFHostUiApi), &UiCreate, &UiNull, &UiShow, &ThemeColor, &UiPath,
+ &UiRegSettings, &UiUnreg, &UiRegTab, &UiUnreg, &UiRegExt,
+};
-void* GFUIGlobalSettings() {
+/* --- storage -------------------------------------------------------------- */
+
+auto SettingsRoot(GFHostContextRef) -> void* {
static QSettings* settings = nullptr;
if (settings == nullptr) {
settings = new QSettings("/tmp/gf-focus-harness.ini", QSettings::IniFormat);
@@ -113,25 +198,95 @@ void* GFUIGlobalSettings() {
return settings;
}
-void GFModuleLogTrace(const char*) {}
-void GFModuleLogDebug(const char*) {}
-void GFModuleLogInfo(const char*) {}
-void GFModuleLogWarn(const char*) {}
-void GFModuleLogError(const char*) {}
-void GFModuleLogAt(const char*, int, const char*, int, const char*,
- const char*) {}
-int GFModuleLogEnabled(const char*, int) { return 1; }
+auto CacheGet(GFHostContextRef, int, const char*, GFBufferRef* out) -> int {
+ if (out != nullptr) *out = nullptr;
+ return -1; // nothing stored: absence is reportable now
+}
+
+auto CacheSet(GFHostContextRef ctx, int store, const char*, GFBufferView value,
+ int64_t) -> int {
+ // The secret arrives as a buffer, which is the whole point: it never passes
+ // through an allocation the module cannot wipe. Recorded so the credential
+ // test can still assert the secure tier was the one used.
+ harness::LastSaveUsedSecureValue =
+ store == GF_STORE_SECURE_DURABLE && BufSize(ctx, value) != 0;
+ return 0;
+}
+
+auto CacheRemove(GFHostContextRef, int, const char*) -> int { return 0; }
+auto StateGetText(GFHostContextRef, const char*, const char*, GFBufferRef* out)
+ -> int {
+ if (out != nullptr) *out = nullptr;
+ return -1;
+}
+auto StateSetText(GFHostContextRef, const char*, const char*, GFBufferView)
+ -> int {
+ return 0;
+}
+auto StateGetBool(GFHostContextRef, const char*, const char*, int*) -> int {
+ return -1;
+}
+auto StateSetBool(GFHostContextRef, const char*, const char*, int) -> int {
+ return 0;
+}
+auto StateChildren(GFHostContextRef, const char*, const char*,
+ GFStringListRef* out) -> int {
+ if (out != nullptr) *out = nullptr;
+ return -1;
+}
+
+const GFHostStorageApi kStorage = {
+ sizeof(GFHostStorageApi),
+ &SettingsRoot,
+ &CacheGet,
+ &CacheSet,
+ &CacheRemove,
+ &StateGetText,
+ &StateSetText,
+ &StateGetBool,
+ &StateSetBool,
+ &StateChildren,
+};
+
+/* --- the table ------------------------------------------------------------ */
+
+struct FakeHostContext {
+ int marker = 0;
+};
+
+auto TheTable() -> GFHostApi& {
+ static FakeHostContext host_context;
+ static GFHostApi host = [&]() {
+ GFHostApi t{};
+ t.struct_size = sizeof(GFHostApi);
+ t.abi_version = GF_SDK_ABI_VERSION;
+ t.module_id = "harness";
+ t.context = reinterpret_cast<GFHostContextRef>(&host_context);
+ t.buffer = &kBuffer;
+ t.log = &kLog;
+ t.app = &kApp;
+ t.ui = &kUi;
+ t.storage = &kStorage;
+ return t;
+ }();
+ return host;
+}
+
+} // namespace
-char* GFAppActiveLocale() { return GFModuleStrDup("en_US"); }
-unsigned int GFUIMutedTextColor(void*) { return 0xFF808080; }
-unsigned int GFUIBorderColor(void*) { return 0xFF808080; }
-unsigned int GFUIWarningColor(void*) { return 0xFFFF8800; }
-unsigned int GFUIDangerColor(void*) { return 0xFFFF0000; }
-unsigned int GFUIAccentColor(void*, int) { return 0xFF0066CC; }
-char* GFUIHumanSize(long long) { return GFModuleStrDup("0 B"); }
+/// The module's SDK context, which the runtime would normally own. These
+/// tests link no runtime, so the harness provides it.
+auto GFModuleSdkContext() -> GFSDKContext* {
+ static GFSDKContext context = []() {
+ GFSDKContext c{};
+ c.struct_size = sizeof(GFSDKContext);
+ c.abi_version = GF_SDK_ABI_VERSION;
+ c.host = &TheTable();
+ c.module_id = "harness";
+ return c;
+ }();
+ return &context;
}
-// C++ linkage, deliberately outside the extern "C" block above: GFModule.h
-// declares it as an ordinary C++ function, and the log macros call it on every
-// line. Defined here with C linkage it compiles cleanly and fails to link.
+/// Identity, for the log macros. C++ linkage, as GFModule.h declares it.
const char* GFGetModuleID() { return "harness"; }
diff --git a/src/m_email/test/EMailSecretTest.cpp b/src/m_email/test/EMailSecretTest.cpp
index eb1455f..b36a99b 100644
--- a/src/m_email/test/EMailSecretTest.cpp
+++ b/src/m_email/test/EMailSecretTest.cpp
@@ -39,7 +39,8 @@
// this class exists is recorded rather than asserted. The rest hold
// EMailSecret to the behaviour that replaces it.
-#include <GFSDKBasic.h>
+#include <GFSDKBuffer.h>
+#include <GFSDKContext.h>
#include <gtest/gtest.h>
#include <QString>
@@ -47,6 +48,9 @@
#include "EMailSecret.h"
+/// Supplied by the harness (EMailSdkStubs.cpp).
+auto GFModuleSdkContext() -> GFSDKContext*;
+
namespace {
TEST(EMailSecretTest, AQStringWipeDoesNotReachASharedBuffer) {
@@ -131,23 +135,25 @@ TEST(EMailSecretTest, TheLastHolderReleasingErasesTheBytes) {
<< "releasing one holder erased bytes the other still needed";
}
-TEST(EMailSecretTest, AdoptingACStringWipesTheSource) {
- // The SDK hands back a secure char*. Copying it out has to leave nothing
- // behind in the buffer it came from.
+TEST(EMailSecretTest, AdoptingABufferTakesTheBytesAndReleasesIt) {
+ // The SDK hands a secret back as a buffer now, not a secure char*. A buffer
+ // wipes itself on release, so the bytes exist in one place afterwards and
+ // that place is the vector this class can erase.
const char* text = "from-the-sdk";
const auto length = std::strlen(text);
- auto* raw = static_cast<char*>(GFSecAllocateMemory(length + 1));
- std::memcpy(raw, text, length + 1);
+ auto* ctx = GFModuleSdkContext();
+ auto* raw = GFBufferNewFromBytes(ctx, text, length);
+ ASSERT_NE(raw, nullptr);
- auto secret = EMailSecret::AdoptCString(raw);
+ auto secret = EMailSecret::AdoptBuffer(ctx, raw);
EXPECT_EQ(secret->StdStringCopy(), "from-the-sdk");
EXPECT_EQ(secret->Size(), length);
}
TEST(EMailSecretTest, AdoptingNullIsEmptyRatherThanACrash) {
- auto secret = EMailSecret::AdoptCString(nullptr);
+ auto secret = EMailSecret::AdoptBuffer(GFModuleSdkContext(), nullptr);
ASSERT_NE(secret, nullptr);
EXPECT_TRUE(secret->IsEmpty());
}
@@ -180,7 +186,7 @@ TEST(EMailSecretTest, ASecureCStringCarriesTheSecretVerbatim) {
// NUL-terminated one past the secret, so a C API can read it as a string.
EXPECT_EQ(buffer[secret->Size()], '\0');
- GFSecFreeMemory(buffer);
+ GFMemFree(GFModuleSdkContext(), GF_ARENA_SECURE, buffer);
}
TEST(EMailSecretTest, AnEmptySecretStillProducesAUsableString) {
@@ -190,7 +196,7 @@ TEST(EMailSecretTest, AnEmptySecretStillProducesAUsableString) {
ASSERT_NE(buffer, nullptr);
EXPECT_STREQ(buffer, "");
- GFSecFreeMemory(buffer);
+ GFMemFree(GFModuleSdkContext(), GF_ARENA_SECURE, buffer);
}
TEST(EMailSecretTest, NonAsciiSurvivesTheRoundTrip) {
@@ -202,7 +208,7 @@ TEST(EMailSecretTest, NonAsciiSurvivesTheRoundTrip) {
ASSERT_NE(buffer, nullptr);
EXPECT_EQ(QString::fromUtf8(buffer), typed);
- GFSecFreeMemory(buffer);
+ GFMemFree(GFModuleSdkContext(), GF_ARENA_SECURE, buffer);
}
} // namespace