Skip to content

Commit

Permalink
buffer: simplify atob approach of disregarding whitespace
Browse files Browse the repository at this point in the history
* Use approach of counting non-ASCII whitespace characters instead of
  removing the characters via string replacement
* Additional atob validation tests have been added
  • Loading branch information
austinkelleher committed Apr 9, 2022
1 parent 2ad1a00 commit 1c109ce
Show file tree
Hide file tree
Showing 2 changed files with 29 additions and 25 deletions.
33 changes: 16 additions & 17 deletions lib/buffer.js
Original file line number Diff line number Diff line change
Expand Up @@ -1232,12 +1232,14 @@ function btoa(input) {
return buf.toString('base64');
}

// Refs: https://infra.spec.whatwg.org/#forgiving-base64-decode
const kForgivingBase64AllowedChars = [
const asciiWhitespaceCharacters = [
// ASCII whitespace
// Refs: https://infra.spec.whatwg.org/#ascii-whitespace
0x09, 0x0A, 0x0C, 0x0D, 0x20,
];

// Refs: https://infra.spec.whatwg.org/#forgiving-base64-decode
const kForgivingBase64AllowedChars = [
// Uppercase letters
...ArrayFrom({ length: 26 }, (_, i) => StringPrototypeCharCodeAt('A') + i),

Expand All @@ -1260,32 +1262,29 @@ function atob(input) {
throw new ERR_MISSING_ARGS('input');
}

if (input === undefined || input === false || typeof input === 'number') {
throw lazyDOMException(
'The string to be decoded is not correctly encoded.',
'ValidationError');
}
input = `${input}`;
let nonAsciiWhitespaceCharCount = 0;

// Remove all ASCII whitespace from data.
//
// See #1 - https://infra.spec.whatwg.org/#forgiving-base64
input = `${input}`.replace(/\s/g, '');
for (let n = 0; n < input.length; n++) {
const char = StringPrototypeCharCodeAt(input, n);

if (ArrayPrototypeIncludes(kForgivingBase64AllowedChars, char)) {
nonAsciiWhitespaceCharCount++;
} else if (!ArrayPrototypeIncludes(asciiWhitespaceCharacters, char)) {
throw lazyDOMException('Invalid character', 'InvalidCharacterError');
}
}

// If data's code point length divides by 4 leaving a remainder of 1, then
// return failure.
//
// See #3 - https://infra.spec.whatwg.org/#forgiving-base64
if (input.length % 4 === 1) {
if (nonAsciiWhitespaceCharCount % 4 === 1) {
throw lazyDOMException(
'The string to be decoded is not correctly encoded.',
'ValidationError');
}

for (let n = 0; n < input.length; n++) {
if (!ArrayPrototypeIncludes(kForgivingBase64AllowedChars,
StringPrototypeCharCodeAt(input, n)))
throw lazyDOMException('Invalid character', 'InvalidCharacterError');
}
return Buffer.from(input, 'base64').toString('latin1');
}

Expand Down
21 changes: 13 additions & 8 deletions test/parallel/test-btoa-atob.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,16 @@ throws(() => buffer.btoa(), /TypeError/);
strictEqual(atob(' '), '');
strictEqual(atob(' YW\tJ\njZA=\r= '), 'abcd');

throws(() => buffer.atob(undefined), /ValidationError/);
throws(() => buffer.atob(false), /ValidationError/);
throws(() => buffer.atob(1), /ValidationError/);
throws(() => buffer.atob(0), /ValidationError/);
throws(() => buffer.atob('a'), /ValidationError/);
throws(() => buffer.atob('a '), /ValidationError/);
throws(() => buffer.atob(' a'), /ValidationError/);
throws(() => buffer.atob('aaaaa'), /ValidationError/);
strictEqual(atob(null), '\x9Eée');
strictEqual(atob(NaN), '5£');
strictEqual(atob(Infinity), '"wâ\x9E+r');
strictEqual(atob(true), '¶»\x9E');
strictEqual(atob(1234), '×mø');
strictEqual(atob([]), '');
strictEqual(atob({ toString: () => '' }), '');
strictEqual(atob({ [Symbol.toPrimitive]: () => '' }), '');

throws(() => atob(), /ERR_MISSING_ARGS/);
throws(() => atob(Symbol()), /TypeError/);
[undefined, false, () => {}, 0, 1, 0n, 1n, -Infinity, [1], {}].forEach((value) =>
throws(() => atob(value), { constructor: DOMException }));

0 comments on commit 1c109ce

Please sign in to comment.