taler-typescript-core

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

commit bbce9ca9179b4677c7599caa88591d341f1e2df4
parent 31a2564086f1ad6e6d898c9c817fbdc147377cdc
Author: Florian Dold <dold@taler.net>
Date:   Tue, 21 Jul 2026 00:40:41 +0200

util: check protocol version compatibility correctly

compare() returns an object even for incompatible versions, so the truthiness
test in carefullyParseConfig accepted any parseable version.  Add isCompatible
and use it.  parseVersion also accepted hex, fractional and negative
components that the C reference rejects.

Diffstat:
Mpackages/taler-util/src/libtool-version.ts | 39++++++++++++++++++++++++---------------
Mpackages/taler-util/src/operation.ts | 4++--
2 files changed, 26 insertions(+), 17 deletions(-)

diff --git a/packages/taler-util/src/libtool-version.ts b/packages/taler-util/src/libtool-version.ts @@ -65,6 +65,16 @@ export namespace LibtoolVersion { return { compatible, currentCmp }; } + /** + * Check whether two libtool-style version strings are compatible. + * + * Prefer this over compare(), which returns an object even for incompatible + * versions, so testing its result for truthiness detects nothing. + */ + export function isCompatible(me: string, other: string): boolean { + return compare(me, other)?.compatible ?? false; + } + export function parseVersionOrThrow(v: string): Version { const res = parseVersion(v); if (!res) { @@ -74,23 +84,22 @@ export namespace LibtoolVersion { } export function parseVersion(v: string): Version | undefined { - const [currentStr, revisionStr, ageStr, ...rest] = v.split(":"); - if (rest.length !== 0) { + // The C reference uses sscanf("%u:%u:%u%c") and requires exactly three + // conversions. Number.parseInt accepts a sign, a radix prefix, a + // fractional part and trailing garbage, so match the whole string first. + const m = /^([0-9]+):([0-9]+):([0-9]+)$/.exec(v); + if (!m) { return undefined; } - const current = Number.parseInt(currentStr); - const revision = Number.parseInt(revisionStr); - const age = Number.parseInt(ageStr); - - if (Number.isNaN(current)) { - return undefined; - } - - if (Number.isNaN(revision)) { - return undefined; - } - - if (Number.isNaN(age)) { + const current = Number.parseInt(m[1], 10); + const revision = Number.parseInt(m[2], 10); + const age = Number.parseInt(m[3], 10); + + if ( + !Number.isSafeInteger(current) || + !Number.isSafeInteger(revision) || + !Number.isSafeInteger(age) + ) { return undefined; } diff --git a/packages/taler-util/src/operation.ts b/packages/taler-util/src/operation.ts @@ -159,11 +159,11 @@ export async function carefullyParseConfig<T>( code: TalerErrorCode.GENERIC_UNEXPECTED_REQUEST_ERROR, requestUrl: httpResponse.requestUrl, httpStatusCode: httpResponse.status, - detail: `Unexpected server component name (got ${minBody.name}, expected ${expectedName}})`, + detail: `Unexpected server component name (got ${minBody.name}, expected ${expectedName})`, }); } - if (!LibtoolVersion.compare(clientVersion, minBody.version)) { + if (!LibtoolVersion.isCompatible(clientVersion, minBody.version)) { throw TalerError.fromUncheckedDetail({ code: TalerErrorCode.GENERIC_CLIENT_UNSUPPORTED_PROTOCOL_VERSION, requestUrl: httpResponse.requestUrl,