Skip to content

Commit ccd3ce5

Browse files
authored
Merge pull request #161 from jamdotdev/petar/decode-multiline-base64
fix: decode multiline base64 input correctly
2 parents a50bd23 + 32f0141 commit ccd3ce5

2 files changed

Lines changed: 50 additions & 4 deletions

File tree

components/utils/base-64.utils.test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,26 @@ describe("base-64.utils", () => {
1616
expect(fromBase64("aGVsbG8=")).toBe("hello");
1717
});
1818

19+
test("should decode base64 with line breaks and surrounding whitespace", () => {
20+
expect(fromBase64("aGVs\nbG8=")).toBe("hello");
21+
expect(fromBase64("aGVs\r\nbG8=")).toBe("hello");
22+
expect(fromBase64(" aGVsbG8= ")).toBe("hello");
23+
});
24+
25+
test("should decode multiple base64 strings, one per line", () => {
26+
expect(fromBase64("aGVsbG8=\nd29ybGQ=")).toBe("hello\nworld");
27+
});
28+
29+
test("should decode PEM-style wrapped base64 as a single message", () => {
30+
const message =
31+
"hello world this is a longer message for testing pem wrap";
32+
const wrapped = Buffer.from(message)
33+
.toString("base64")
34+
.match(/.{1,20}/g)!
35+
.join("\n");
36+
expect(fromBase64(wrapped)).toBe(message);
37+
});
38+
1939
test("should throw an error for an invalid Base64 string", () => {
2040
expect(() => fromBase64("invalid_base64")).toThrow(
2141
"Invalid Base64 input"

components/utils/base-64.utils.ts

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,39 @@ export function toBase64(value: string) {
66
}
77
}
88

9+
function decodeBase64Chunk(value: string): string {
10+
const decoded = Buffer.from(value, "base64").toString("utf-8");
11+
if (!isPrintableASCII(decoded)) {
12+
throw new Error("Decoded string contains non-printable characters");
13+
}
14+
return decoded;
15+
}
16+
917
export function fromBase64(value: string): string {
1018
try {
11-
const decoded = Buffer.from(value, "base64").toString("utf-8");
12-
if (!isPrintableASCII(decoded)) {
13-
throw new Error("Decoded string contains non-printable characters");
19+
const trimmed = value.trim();
20+
if (!trimmed) {
21+
return "";
22+
}
23+
24+
const lines = trimmed
25+
.split(/\r?\n/)
26+
.map((line) => line.trim())
27+
.filter(Boolean);
28+
29+
if (lines.length > 1) {
30+
const stripped = trimmed.replace(/\s/g, "");
31+
const singleDecoded = decodeBase64Chunk(stripped);
32+
const firstLineDecoded = decodeBase64Chunk(lines[0]);
33+
34+
// Multiple independent base64 strings (one per line) decode to the same
35+
// output as the first line when joined; PEM-style wrapped input does not.
36+
if (singleDecoded === firstLineDecoded) {
37+
return lines.map((line) => decodeBase64Chunk(line)).join("\n");
38+
}
1439
}
15-
return decoded;
40+
41+
return decodeBase64Chunk(trimmed.replace(/\s/g, ""));
1642
} catch {
1743
throw new Error("Invalid Base64 input");
1844
}

0 commit comments

Comments
 (0)