diff --git a/src/translate.ts b/src/translate.ts index 3157fa7..d4b75b0 100644 --- a/src/translate.ts +++ b/src/translate.ts @@ -4,12 +4,21 @@ import type { Config, SUUID, UUID } from './types'; * Translate back to hex and into UUID format with dashes * Pad with zeros if necessary to accommodate unusual IDs */ -export const restoreUUID = (config:Config, shortId:SUUID):UUID => - config.hexFromAlphabet(shortId) - .padStart(32, '0') - .match(/(\w{8})(\w{4})(\w{4})(\w{4})(\w{12})/) +export const restoreUUID = (config:Config, shortId:SUUID):UUID => { + const hex = config.hexFromAlphabet(shortId).padStart(32, '0'); + + // An id that decodes to more than 128 bits cannot represent a UUID. Without + // this guard the unanchored match below would silently keep the first 32 hex + // characters and drop the overflow, returning a wrong UUID. + if (hex.length > 32) { + throw new Error(`The id "${shortId}" is out of range to represent a UUID.`); + } + + return hex + .match(/^(\w{8})(\w{4})(\w{4})(\w{4})(\w{12})$/) ?.slice(1) .join('-') as UUID; +}; export const shortenUUID = (config:Config, longId:UUID):SUUID => { const translated = config.hexToAlphabet(longId.toLowerCase().replace(/-/g, '')); diff --git a/test/index.js b/test/index.js index 2cd25f4..68caccc 100644 --- a/test/index.js +++ b/test/index.js @@ -139,6 +139,20 @@ test('Handle UUIDs with all "f"s', (t) => { t.equal(allFs, b36.toUUID(b36.fromUUID(allFs)), 'Supports all "f"s'); }); +test('should reject an id that is out of range for a UUID', (t) => { + t.plan(3); + + // 22 flickrBase58 characters can encode a value larger than 2^128-1, which + // cannot be a UUID; this id decodes to more than 32 hex characters. + const outOfRange = 'ZZZZZZZZZZZZZZZZZZZZZZ'; + + t.throws(() => b58.toUUID(outOfRange), /out of range/, 'toUUID throws instead of silently truncating'); + + const valid = b58.fromUUID(crypto.randomUUID()); + t.doesNotThrow(() => b58.toUUID(valid), 'a valid id does not throw'); + t.equal(b58.fromUUID(b58.toUUID(valid)), valid, 'a valid id still round-trips'); +}); + test('should handle UUID with uppercase letters', (t) => { t.plan(4);