From 9de9d6cb74aed652a43d0a97a1abd9743629e899 Mon Sep 17 00:00:00 2001 From: Rohit <71192000+rohitsux@users.noreply.github.com> Date: Mon, 31 Aug 2026 20:05:31 +0530 Subject: [PATCH] fix(ts-sdk): decode base64 data URLs with media-type parameters toImageBytes()'s regex ^data:[^;]+;base64, requires a ;-free media type, so valid RFC 2397 data URLs with a media-type parameter (e.g. data:image/svg+xml;charset=utf-8;base64,...) or an omitted media type (data:;base64,...) don't match and fall through, handing the entire data URL to the base64 decoder. That decoder then throws InvalidCharacterError under atob (browser) or silently corrupts bytes under Buffer.from (Node). Fix matches up to the ;base64, marker ([^,]*) instead. Base64 payloads never contain a comma, so the payload is still captured correctly. No change in behavior for existing inputs. Added two regression tests covering a media type with a parameter and an omitted media type. --- packages/sie_ts_sdk/src/images.ts | 8 ++++++-- packages/sie_ts_sdk/tests/images.test.ts | 19 +++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/packages/sie_ts_sdk/src/images.ts b/packages/sie_ts_sdk/src/images.ts index 1e94817f7..9f9a2f6e7 100644 --- a/packages/sie_ts_sdk/src/images.ts +++ b/packages/sie_ts_sdk/src/images.ts @@ -79,8 +79,12 @@ export async function toImageBytes(input: ImageInput): Promise { // Base64 string or data URL if (typeof input === "string") { - // Check if it's a data URL - const dataUrlMatch = input.match(/^data:[^;]+;base64,(.+)$/); + // Check if it's a base64 data URL. Per RFC 2397 the media type may carry + // parameters (e.g. ";charset=utf-8") or be omitted entirely, so match + // everything up to the ";base64," marker rather than a single ";"-free + // segment — otherwise such URLs fall through and the whole data URL is + // handed to the base64 decoder (corrupting the bytes or throwing). + const dataUrlMatch = input.match(/^data:[^,]*;base64,(.+)$/); if (dataUrlMatch?.[1]) { return base64ToBytes(dataUrlMatch[1]); } diff --git a/packages/sie_ts_sdk/tests/images.test.ts b/packages/sie_ts_sdk/tests/images.test.ts index 769fe3796..d560b2472 100644 --- a/packages/sie_ts_sdk/tests/images.test.ts +++ b/packages/sie_ts_sdk/tests/images.test.ts @@ -51,6 +51,25 @@ describe("toImageBytes", () => { expect(new TextDecoder().decode(result)).toBe("test"); }); + it("decodes a data URL whose media type carries a parameter", async () => { + // Valid per RFC 2397: the media type may be followed by ";param=value" + // (e.g. charset) before ";base64,". "Hello" base64-encoded. + const dataUrl = "data:image/svg+xml;charset=utf-8;base64,SGVsbG8="; + const result = await toImageBytes(dataUrl); + + expect(result).toBeInstanceOf(Uint8Array); + expect(new TextDecoder().decode(result)).toBe("Hello"); + }); + + it("decodes a data URL with an omitted media type", async () => { + // RFC 2397 permits an empty media type (defaults to text/plain). + const dataUrl = "data:;base64,SGVsbG8="; + const result = await toImageBytes(dataUrl); + + expect(result).toBeInstanceOf(Uint8Array); + expect(new TextDecoder().decode(result)).toBe("Hello"); + }); + it("throws for unsupported input type", async () => { await expect(toImageBytes(123 as unknown as Uint8Array)).rejects.toThrow( "Unsupported image input type",