merchant

Merchant backend to process payments, run by merchants
Log | Files | Refs | Submodules | README | LICENSE

commit 4f6b3258eb3ebca62080507c1af70c4d41725d1e
parent f20bc8bd00efaa270be937432afccf9fae1403ff
Author: Christian Grothoff <christian@grothoff.org>
Date:   Wed,  5 Aug 2026 22:17:10 +0200

fix payto URI mismatch where REGEXP_REPLACE failed to properly strip receiver-name as intended

Diffstat:
Msrc/backenddb/insert_transfer.sql | 11+++++++----
Msrc/backenddb/test_merchantdb.c | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 59 insertions(+), 4 deletions(-)

diff --git a/src/backenddb/insert_transfer.sql b/src/backenddb/insert_transfer.sql @@ -35,13 +35,16 @@ BEGIN out_conflict=FALSE; +-- NOTE: compare the payto URIs with any query parameters (such as +-- 'receiver-name') stripped. SPLIT_PART is used instead of REGEXP_REPLACE +-- because the previous regular expression ('\\?.*') matched an *optional* +-- backslash followed by anything, i.e. it matched at offset 0 and reduced +-- every URI to the empty string, making this condition always true. SELECT account_serial INTO my_account_serial FROM merchant_accounts - WHERE REGEXP_REPLACE(payto_uri, - '\\?.*','') - =REGEXP_REPLACE(in_credited_account_payto, - '\\?.*',''); + WHERE SPLIT_PART(payto_uri,'?',1) + =SPLIT_PART(in_credited_account_payto,'?',1); IF NOT FOUND THEN out_no_account=TRUE; diff --git a/src/backenddb/test_merchantdb.c b/src/backenddb/test_merchantdb.c @@ -4431,6 +4431,54 @@ test_insert_transfer (const struct InstanceData *instance, /** + * Tests that inserting a transfer crediting a payto URI which belongs to + * no account of the instance is reported via the @e no_account flag, + * instead of being silently attributed to an arbitrary account. + * + * Regression test: the account lookup in insert_transfer.sql compared + * REGEXP_REPLACE(payto_uri,'\\?.*',''), a pattern which matches an + * *optional* backslash followed by anything. It therefore matched at + * offset 0, reduced every URI to the empty string, and made the + * comparison unconditionally true. + * + * @param instance the instance to use. + * @param transfer the transfer to attempt to insert. + * @return 0 on success, 1 otherwise. + */ +static int +test_insert_transfer_unknown_account ( + const struct InstanceData *instance, + const struct TransferData *transfer) +{ + bool no_account = false; + bool conflict = false; + struct TALER_FullPayto unknown = { + .full_payto = (char *) "payto://iban/DE/NO-SUCH-ACCOUNT" + "?receiver-name=Not+My+Account" + }; + + TEST_SET_INSTANCE (instance->instance.id, + GNUNET_DB_STATUS_SUCCESS_ONE_RESULT); + TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == + TALER_MERCHANTDB_insert_transfer ( + pg, + instance->instance.id, + transfer->exchange_url, + &transfer->wtid, + &transfer->data.total_amount, + unknown, + transfer->confirmed, + &no_account, + &conflict), + "Insert transfer for unknown account failed\n"); + TEST_COND_RET_ON_FAIL (no_account, + "Insert transfer accepted a payto URI belonging to " + "no account of the instance\n"); + return 0; +} + + +/** * Tests linking a deposit to a transfer. * * @param instance the instance that the deposit and transfer are for. @@ -4681,6 +4729,10 @@ run_test_transfers (struct TestTransfers_Closure *cls) TEST_RET_ON_FAIL (test_mark_contract_paid (&cls->instance, &cls->order, GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); + /* A payto URI that matches no account must be rejected, not silently + attributed to some arbitrary account of the instance. */ + TEST_RET_ON_FAIL (test_insert_transfer_unknown_account (&cls->instance, + &cls->transfers[0])); /* Insert the transfer */ TEST_RET_ON_FAIL (test_insert_transfer (&cls->instance, &cls->account,