diff --git a/lib/buffer.js b/lib/buffer.js index 2b7536643ba95d..69f9560c19b9e3 100644 --- a/lib/buffer.js +++ b/lib/buffer.js @@ -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), @@ -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'); } diff --git a/test/parallel/test-btoa-atob.js b/test/parallel/test-btoa-atob.js index 3a9786f7a62111..988f8a0f74fc0b 100644 --- a/test/parallel/test-btoa-atob.js +++ b/test/parallel/test-btoa-atob.js @@ -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 }));