taler-typescript-core

Wallet core logic and WebUIs for various components
Log | Files | Refs | Submodules | README | LICENSE

commit dc9ac3bdaf8525ae458ac38905e34b159b1628a1
parent e3b2f049795121bcd4e99cf07f221f0207cdac21
Author: Florian Dold <dold@taler.net>
Date:   Tue, 21 Jul 2026 11:07:07 +0200

util: test taler URI components containing control characters

Diffstat:
Mpackages/taler-util/src/taleruri-roundtrip.test.ts | 38++++++++++++++++++++++++++++++++++++--
1 file changed, 36 insertions(+), 2 deletions(-)

diff --git a/packages/taler-util/src/taleruri-roundtrip.test.ts b/packages/taler-util/src/taleruri-roundtrip.test.ts @@ -30,7 +30,11 @@ test("pay URI: a component with structural characters round-trips", (t) => { for (const orderId of ["a/b", "a?c=evil", "a#b", "a&b", "plain-01", "a b"]) { const s = TalerUris.stringify(payUri(orderId)); const r = TalerUris.parse(s); - assert.strictEqual(r.tag, "ok", `parse failed for orderId ${orderId}: ${s}`); + assert.strictEqual( + r.tag, + "ok", + `parse failed for orderId ${orderId}: ${s}`, + ); const v = Result.unpack(r) as any; assert.strictEqual(v.orderId, orderId, `orderId not preserved: ${s}`); // the merchant host must survive intact @@ -62,6 +66,36 @@ test("withdrawal-transfer-result URI round-trips", (t) => { }); test("withdrawal-transfer-result URI requires a ref", (t) => { - const r = TalerUris.parse("taler://withdrawal-transfer-result?status=success"); + const r = TalerUris.parse( + "taler://withdrawal-transfer-result?status=success", + ); assert.strictEqual(r.tag, "error"); }); + +test("pay URI: control characters cannot erase the merchant host", (t) => { + // The WHATWG URL parser strips tab, LF and CR before parsing, so a + // component of ".." followed by one of them is not caught by the dot-segment + // guard and is resolved away together with the preceding path segment. + for (const orderId of ["..\t", "..\n", "..\r", "a\tb", "x\ny"]) { + const s = TalerUris.stringify(payUri(orderId)); + const r = TalerUris.parse(s); + assert.strictEqual(r.tag, "ok", `parse failed: ${JSON.stringify(s)}`); + const v = Result.unpack(r) as any; + assert.strictEqual(v.merchantBaseUrl, "https://m.example.com/", s); + assert.strictEqual(v.orderId, orderId, `orderId not preserved: ${s}`); + } +}); + +test("pay URI: an instance sub-path survives a hostile order id", (t) => { + const s = TalerUris.stringify({ + type: TalerUriAction.Pay, + merchantBaseUrl: "https://shop.example.com/instances/alice/" as any, + orderId: "..\t", + sessionId: "sess", + }); + const v = Result.unpack(TalerUris.parse(s)) as any; + assert.strictEqual( + v.merchantBaseUrl, + "https://shop.example.com/instances/alice/", + ); +});