commit 29118e7c544af1a90465050e1f25da2a1451edd1
parent 73127e67bf2bf796981c49571ef37877b094cc46
Author: Florian Dold <dold@taler.net>
Date: Tue, 21 Jul 2026 01:55:23 +0200
util: test x-taler-bank normalization against the C reference
Diffstat:
1 file changed, 72 insertions(+), 0 deletions(-)
diff --git a/packages/taler-util/src/payto-xtalerbank.test.ts b/packages/taler-util/src/payto-xtalerbank.test.ts
@@ -0,0 +1,72 @@
+/*
+ This file is part of GNU Taler
+ (C) 2026 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+import { test } from "node:test";
+import assert from "node:assert";
+import { Paytos } from "./payto.js";
+import { Result } from "./result.js";
+
+/**
+ * Reference implementation of normalize_payto_x_taler_bank from the C exchange
+ * (exchange/src/util/payto.c): lowercase every character up to and including
+ * the host (before the 4th slash), preserve the rest, and do not resolve dot
+ * segments. H_PAYTO is compared across implementations, so the TypeScript
+ * normalization must match this byte for byte.
+ */
+function cNormalizeXTalerBank(fullPayto: string): string {
+ let slashes = 0;
+ let out = "";
+ for (const c of fullPayto) {
+ if (c === "/") slashes++;
+ out += slashes < 4 ? c.toLowerCase() : c;
+ }
+ return out;
+}
+
+const cases = [
+ "payto://x-taler-bank/bank.example.com/alice",
+ "payto://x-taler-bank/Bank.Example.COM/alice",
+ "payto://x-taler-bank/bank.com/SUB/acct",
+ "payto://x-taler-bank/Bank.COM/SUB/Acct",
+ "payto://x-taler-bank/bank.com/../../evil/acct",
+ "payto://x-taler-bank/localhost/exchange",
+];
+
+test("x-taler-bank normalization matches the C reference", (t) => {
+ for (const input of cases) {
+ const r = Paytos.fromString(input);
+ assert.strictEqual(r.tag, "ok", `parse failed: ${input}`);
+ const got = Paytos.toNormalizedString(Result.unpack(r));
+ assert.strictEqual(got, cNormalizeXTalerBank(input), `for ${input}`);
+ }
+});
+
+test("x-taler-bank: host case and dot segments do not change H_PAYTO independence", (t) => {
+ // Two accounts differing only in the host case are the same account.
+ const a = Paytos.fromString("payto://x-taler-bank/Bank.COM/acct");
+ const b = Paytos.fromString("payto://x-taler-bank/bank.com/acct");
+ assert.strictEqual(
+ Paytos.toNormalizedString(Result.unpack(a)),
+ Paytos.toNormalizedString(Result.unpack(b)),
+ );
+ // Two accounts differing in the sub-path case are NOT the same.
+ const c = Paytos.fromString("payto://x-taler-bank/bank.com/SUB/acct");
+ const d = Paytos.fromString("payto://x-taler-bank/bank.com/sub/acct");
+ assert.notStrictEqual(
+ Paytos.toNormalizedString(Result.unpack(c)),
+ Paytos.toNormalizedString(Result.unpack(d)),
+ );
+});