Skip to content
This repository was archived by the owner on Mar 13, 2025. It is now read-only.

Commit 2865304

Browse files
committed
Remove ASCII whitespace from atob input, closes #104
1 parent 8049440 commit 2865304

File tree

3 files changed

+67
-14
lines changed

3 files changed

+67
-14
lines changed

packages/core/src/plugins/core.ts

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ import {
4343
ScheduledEvent,
4444
TextDecoder,
4545
WorkerGlobalScope,
46+
atob,
47+
btoa,
4648
createCompatFetch,
4749
crypto,
4850
inputGatedSetInterval,
@@ -67,15 +69,6 @@ function proxyStringFormDataFiles<
6769
});
6870
}
6971

70-
// Approximations of atob and btoa for Jest, which doesn't include these in the
71-
// global scope with jest-environment-node :(
72-
function atobBuffer(s: string): string {
73-
return Buffer.from(s, "base64").toString("binary");
74-
}
75-
function btoaBuffer(s: string): string {
76-
return Buffer.from(s, "binary").toString("base64");
77-
}
78-
7972
// Approximation of structuredClone for Node < 17.0.0
8073
function structuredCloneBuffer<T>(value: T): T {
8174
return deserialize(serialize(value));
@@ -338,10 +331,8 @@ export class CorePlugin extends Plugin<CoreOptions> implements CoreOptions {
338331
clearInterval,
339332
queueMicrotask,
340333

341-
// Fix for Jest :(, jest-environment-node doesn't include atob/btoa in the
342-
// global scope
343-
atob: globalThis.atob ?? atobBuffer,
344-
btoa: globalThis.btoa ?? btoaBuffer,
334+
atob,
335+
btoa,
345336

346337
crypto,
347338
CryptoKey: crypto.CryptoKey,

packages/core/src/standards/encoding.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { TextDecoder as BaseTextDecoder } from "util";
2+
import { DOMException } from "@miniflare/core";
23

34
export class TextDecoder extends BaseTextDecoder {
45
constructor(
@@ -17,3 +18,39 @@ export class TextDecoder extends BaseTextDecoder {
1718
super(encoding, options);
1819
}
1920
}
21+
22+
// Implementations of base64 functions adapted from Node.js:
23+
// https://github.com/nodejs/node/blob/1086468aa3d328d2eac00bf66058906553ecd209/lib/buffer.js#L1213-L1239
24+
//
25+
// Our `atob` removes all ASCII whitespace (https://infra.spec.whatwg.org/#ascii-whitespace)
26+
// prior to decoding, as required by the spec (https://infra.spec.whatwg.org/#forgiving-base64-decode),
27+
// and as implemented by Cloudflare Workers
28+
//
29+
// Note, Jest doesn't include btoa or atob in the global scope with
30+
// jest-environment-node :(, so we'd have to implement it ourselves anyways.
31+
// Doing this also allows us to use our own DOMException type.
32+
33+
export function btoa(input: string): string {
34+
input = `${input}`;
35+
for (let n = 0; n < input.length; n++) {
36+
if (input[n].charCodeAt(0) > 0xff) {
37+
throw new DOMException("Invalid character", "InvalidCharacterError");
38+
}
39+
}
40+
return Buffer.from(input, "latin1").toString("base64");
41+
}
42+
43+
const BASE_64_DIGITS =
44+
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
45+
46+
export function atob(input: string): string {
47+
// Make sure input is a string, removing ASCII whitespace:
48+
// https://infra.spec.whatwg.org/#ascii-whitespace
49+
input = `${input}`.replace(/[\t\n\f\r ]+/g, "");
50+
for (let n = 0; n < input.length; n++) {
51+
if (!BASE_64_DIGITS.includes(input[n])) {
52+
throw new DOMException("Invalid character", "InvalidCharacterError");
53+
}
54+
}
55+
return Buffer.from(input, "base64").toString("latin1");
56+
}

packages/core/test/standards/encoding.spec.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { TextEncoder } from "util";
2-
import { TextDecoder } from "@miniflare/core";
2+
import { DOMException, TextDecoder, atob, btoa } from "@miniflare/core";
33
import test, { ThrowsExpectation } from "ava";
44

55
const encoder = new TextEncoder();
@@ -23,3 +23,28 @@ test("TextDecoder: only supports utf8 encoding", (t) => {
2323
// Check non-utf8 encoding not supported by Node nor Workers
2424
t.throws(() => new TextDecoder("not-an-encoding"), utf8Expectations);
2525
});
26+
27+
test("btoa: base64 encodes data", (t) => {
28+
t.is(btoa("test"), "dGVzdA==");
29+
});
30+
test("btoa: throws on invalid character", (t) => {
31+
t.throws(() => btoa("✅"), {
32+
instanceOf: DOMException,
33+
name: "InvalidCharacterError",
34+
message: "Invalid character",
35+
});
36+
});
37+
38+
test("atob: base64 encodes data", (t) => {
39+
t.is(atob("dGVzdA=="), "test");
40+
});
41+
test("atob: removes ASCII whitespace from input", (t) => {
42+
t.is(atob(" dG\fV\tz\r\nd A== "), "test");
43+
});
44+
test("atob: throws on invalid character", (t) => {
45+
t.throws(() => atob("base64!"), {
46+
instanceOf: DOMException,
47+
name: "InvalidCharacterError",
48+
message: "Invalid character",
49+
});
50+
});

0 commit comments

Comments
 (0)