commit d96795022adbaaa91e18bffa3995a0359a72d24f
parent f2799b40989bd8bbf685d788810edcf8a2b2f7b0
Author: Florian Dold <florian@dold.me>
Date: Tue, 25 Nov 2025 18:16:19 +0100
wallet-core: fix handling of slashes in addExchange
There is now also a harness test for this.
Diffstat:
3 files changed, 48 insertions(+), 19 deletions(-)
diff --git a/packages/taler-harness/src/integrationtests/test-exchange-management.ts b/packages/taler-harness/src/integrationtests/test-exchange-management.ts
@@ -17,9 +17,10 @@
/**
* Imports.
*/
+import { j2s } from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
-import { GlobalTestState } from "../harness/harness.js";
import { createSimpleTestkudosEnvironmentV3 } from "../harness/environments.js";
+import { GlobalTestState } from "../harness/harness.js";
/**
* Test if the wallet handles outdated exchange versions correctly.
@@ -77,6 +78,35 @@ export async function runExchangeManagementTest(
);
t.assertDeepEqual(exchangesListResult4.exchanges.length, 1);
+
+ await walletClient.call(WalletApiOperation.DeleteExchange, {
+ exchangeBaseUrl: exchange.baseUrl,
+ });
+
+ const url = new URL(exchange.baseUrl);
+
+ {
+ await t.assertThrowsAsync(async () => {
+ await walletClient.call(WalletApiOperation.AddExchange, {
+ uri: `${url.hostname}:${url.port}`,
+ });
+ });
+ }
+
+ {
+ const err = await t.assertThrowsTalerErrorAsync(async () => {
+ const res = await walletClient.call(WalletApiOperation.AddExchange, {
+ uri: `${url.hostname}:${url.port}`,
+ allowCompletion: true,
+ });
+ });
+ console.log(j2s(err));
+ // Tries to complete to https://, but we use http in the local test.
+ t.assertDeepEqual(
+ (err.errorDetail as any).innerError.requestUrl,
+ "https://localhost:8081/keys",
+ );
+ }
}
runExchangeManagementTest.suites = ["wallet", "exchange"];
diff --git a/packages/taler-util/src/types-taler-wallet.ts b/packages/taler-util/src/types-taler-wallet.ts
@@ -2213,6 +2213,7 @@ export interface AddExchangeResponse {
export const codecForAddExchangeRequest = (): Codec<AddExchangeRequest> =>
buildCodecForObject<AddExchangeRequest>()
+ .property("allowCompletion", codecOptional(codecForBoolean()))
.property("exchangeBaseUrl", codecOptional(codecForCanonBaseUrl()))
.property("uri", codecOptional(codecForString()))
.property("forceUpdate", codecOptional(codecForBoolean()))
diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts
@@ -1047,20 +1047,28 @@ async function handleAddExchange(
);
exchangeBaseUrl = req.exchangeBaseUrl;
} else if (req.uri) {
- if (req.uri.startsWith("http")) {
- const canonUrl = canonicalizeBaseUrl(req.uri);
- if (req.uri != canonUrl) {
- throw Error("exchange base URL must be canonicalized");
- }
- exchangeBaseUrl = req.uri;
- } else if (req.uri.startsWith("taler")) {
+ if (req.uri.startsWith("taler")) {
const p = parseTalerUri(req.uri);
if (p?.type !== TalerUriAction.AddExchange) {
throw Error("invalid taler://add-exchange/ URI");
}
exchangeBaseUrl = p.exchangeBaseUrl;
+ } else if (req.allowCompletion) {
+ const completeRes = await handleCompleteExchangeBaseUrl(wex, {
+ url: req.uri,
+ });
+ if (completeRes.status != "ok") {
+ throw TalerError.fromUncheckedDetail(completeRes.error);
+ }
+ exchangeBaseUrl = completeRes.completion;
+ } else if (req.uri.startsWith("http")) {
+ const canonUrl = canonicalizeBaseUrl(req.uri);
+ if (req.uri != canonUrl) {
+ throw Error("exchange base URL must be canonicalized");
+ }
+ exchangeBaseUrl = req.uri;
} else {
- throw Error("AddExchangeRequest.uri must be http(s) or taler URI");
+ throw Error("AddExchangeRequest.uri must be http(s):// or taler://");
}
} else {
throw Error(
@@ -1068,16 +1076,6 @@ async function handleAddExchange(
);
}
- if (req.allowCompletion) {
- const completeRes = await handleCompleteExchangeBaseUrl(wex, {
- url: exchangeBaseUrl,
- });
- if (completeRes.status != "ok") {
- throw TalerError.fromUncheckedDetail(completeRes.error);
- }
- exchangeBaseUrl = completeRes.completion;
- }
-
await fetchFreshExchange(wex, exchangeBaseUrl, {});
// Exchange has been explicitly added upon user request.
// Thus, we mark it as "used".