From 32f0141031a208f64b9db85b5a706eefe8e5297f Mon Sep 17 00:00:00 2001 From: Petar Cirkovic Date: Sun, 24 May 2026 10:42:40 +0200 Subject: [PATCH] fix: decode multiline base64 input correctly Handle PEM-style wrapped strings and multiple independent base64 values on separate lines. --- components/utils/base-64.utils.test.ts | 20 +++++++++++++++ components/utils/base-64.utils.ts | 34 +++++++++++++++++++++++--- 2 files changed, 50 insertions(+), 4 deletions(-) diff --git a/components/utils/base-64.utils.test.ts b/components/utils/base-64.utils.test.ts index 052b3dc..293c4b0 100644 --- a/components/utils/base-64.utils.test.ts +++ b/components/utils/base-64.utils.test.ts @@ -16,6 +16,26 @@ describe("base-64.utils", () => { expect(fromBase64("aGVsbG8=")).toBe("hello"); }); + test("should decode base64 with line breaks and surrounding whitespace", () => { + expect(fromBase64("aGVs\nbG8=")).toBe("hello"); + expect(fromBase64("aGVs\r\nbG8=")).toBe("hello"); + expect(fromBase64(" aGVsbG8= ")).toBe("hello"); + }); + + test("should decode multiple base64 strings, one per line", () => { + expect(fromBase64("aGVsbG8=\nd29ybGQ=")).toBe("hello\nworld"); + }); + + test("should decode PEM-style wrapped base64 as a single message", () => { + const message = + "hello world this is a longer message for testing pem wrap"; + const wrapped = Buffer.from(message) + .toString("base64") + .match(/.{1,20}/g)! + .join("\n"); + expect(fromBase64(wrapped)).toBe(message); + }); + test("should throw an error for an invalid Base64 string", () => { expect(() => fromBase64("invalid_base64")).toThrow( "Invalid Base64 input" diff --git a/components/utils/base-64.utils.ts b/components/utils/base-64.utils.ts index 85a34ed..0fe990f 100644 --- a/components/utils/base-64.utils.ts +++ b/components/utils/base-64.utils.ts @@ -6,13 +6,39 @@ export function toBase64(value: string) { } } +function decodeBase64Chunk(value: string): string { + const decoded = Buffer.from(value, "base64").toString("utf-8"); + if (!isPrintableASCII(decoded)) { + throw new Error("Decoded string contains non-printable characters"); + } + return decoded; +} + export function fromBase64(value: string): string { try { - const decoded = Buffer.from(value, "base64").toString("utf-8"); - if (!isPrintableASCII(decoded)) { - throw new Error("Decoded string contains non-printable characters"); + const trimmed = value.trim(); + if (!trimmed) { + return ""; + } + + const lines = trimmed + .split(/\r?\n/) + .map((line) => line.trim()) + .filter(Boolean); + + if (lines.length > 1) { + const stripped = trimmed.replace(/\s/g, ""); + const singleDecoded = decodeBase64Chunk(stripped); + const firstLineDecoded = decodeBase64Chunk(lines[0]); + + // Multiple independent base64 strings (one per line) decode to the same + // output as the first line when joined; PEM-style wrapped input does not. + if (singleDecoded === firstLineDecoded) { + return lines.map((line) => decodeBase64Chunk(line)).join("\n"); + } } - return decoded; + + return decodeBase64Chunk(trimmed.replace(/\s/g, "")); } catch { throw new Error("Invalid Base64 input"); }