diff --git a/CHANGELOG.md b/CHANGELOG.md index 05b6a7f06993..9dd75cf8f2c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ ## Changelog ##### Unreleased +- Added [Base64 utility methods](https://developer.mozilla.org/en-US/docs/Glossary/Base64): + - `atob` + - `btoa` - Added the proper validation of arguments to some methods from web standards - Added Rhino 1.7.14 compat data - Added Deno 1.19 compat data mapping diff --git a/README.md b/README.md index 9a62c34ba8b5..49903c727bf7 100644 --- a/README.md +++ b/README.md @@ -159,6 +159,7 @@ queueMicrotask(() => console.log('called as microtask')); - [`Reflect` metadata](#reflect-metadata) - [Web standards](#web-standards) - [`structuredClone`](#structuredclone) + - [Base64 utility methods](#base64-utility-methods) - [`setTimeout` and `setInterval`](#settimeout-and-setinterval) - [`setImmediate`](#setimmediate) - [`queueMicrotask`](#queuemicrotask) @@ -2998,10 +2999,9 @@ function structuredClone(value: Serializable, { transfer?: Sequence DataCloneError on non-serializable types * `ArrayBuffer` instances and many platform types cannot be transferred in most engines since we have no way to polyfill this behavior, however `.transfer` option works for some platform types. I recommend avoiding this option. * Some specific platform types can't be cloned in old engines. Mainly it's very specific types or very old engines, but here are some exceptions. For example, we have no sync way to clone `ImageBitmap` in Safari 14.0- or Firefox 83-, so it's recommended to look to the [polyfill source](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/web.structured-clone.js) if you wanna clone something specific. +#### Base64 utility methods[⬆](#index) +[Specification](https://html.spec.whatwg.org/multipage/webappapis.html#atob), [MDN](https://developer.mozilla.org/en-US/docs/Glossary/Base64). Modules [`web.atob`](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/web.atob.js), [`web.btoa`](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/web.btoa.js). +```js +function atob(data: string): string; +function btoa(data: string): string; +``` +[*CommonJS entry points:*](#commonjs-api) +```js +core-js(-pure)/stable|actual|features/atob +core-js(-pure)/stable|actual|features/btoa +``` +[*Examples*](is.gd/4Nxmzn): +```js +btoa('hi, core-js'); // => 'aGksIGNvcmUtanM=' +atob('aGksIGNvcmUtanM='); // => 'hi, core-js' +``` + #### `setTimeout` and `setInterval`[⬆](#index) Module [`web.timers`](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/web.timers.js). Additional arguments fix for IE9-. ```js @@ -3306,7 +3323,7 @@ console.log(iterator.next().value); // 2 console.log(iterator.next().value); // 3 console.log(iterator.next().value); // undefined -getIterator({}); // TypeError: [object Object] is not iterable! +getIterator({}); // TypeError: [object Object] is not iterable! let method = getIteratorMethod(list); console.log(typeof method); // 'function' diff --git a/packages/core-js-compat/src/data.mjs b/packages/core-js-compat/src/data.mjs index e2e0600854c6..322322024df6 100644 --- a/packages/core-js-compat/src/data.mjs +++ b/packages/core-js-compat/src/data.mjs @@ -1818,6 +1818,26 @@ export const data = { }, 'esnext.weak-set.of': { }, + 'web.atob': { + chrome: '34', + deno: '1.0', + edge: '13', + firefox: '27', + // https://github.com/nodejs/node/issues/41450 + // node: '16.0', + opera: '10.5', + safari: '10.1', + }, + 'web.btoa': { + chrome: '4', + deno: '1.0', + firefox: '1', + ie: '10', + // https://github.com/nodejs/node/issues/41450 + // node: '16.0', + opera: '10.5', + safari: '3.0', + }, 'web.dom-collections.for-each': { chrome: '58', deno: '1.0', diff --git a/packages/core-js-compat/src/modules-by-versions.mjs b/packages/core-js-compat/src/modules-by-versions.mjs index 885387d11ec3..9d27f0fab9d2 100644 --- a/packages/core-js-compat/src/modules-by-versions.mjs +++ b/packages/core-js-compat/src/modules-by-versions.mjs @@ -135,4 +135,8 @@ export default { 'web.dom-exception.to-string-tag', 'web.structured-clone', ], + 3.21: [ + 'web.atob', + 'web.btoa', + ], }; diff --git a/packages/core-js/actual/atob.js b/packages/core-js/actual/atob.js new file mode 100644 index 000000000000..a79a3f1b16f7 --- /dev/null +++ b/packages/core-js/actual/atob.js @@ -0,0 +1,3 @@ +var parent = require('../stable/atob'); + +module.exports = parent; diff --git a/packages/core-js/actual/btoa.js b/packages/core-js/actual/btoa.js new file mode 100644 index 000000000000..18bc9581bab3 --- /dev/null +++ b/packages/core-js/actual/btoa.js @@ -0,0 +1,3 @@ +var parent = require('../stable/btoa'); + +module.exports = parent; diff --git a/packages/core-js/features/atob.js b/packages/core-js/features/atob.js new file mode 100644 index 000000000000..72664939b603 --- /dev/null +++ b/packages/core-js/features/atob.js @@ -0,0 +1,3 @@ +var parent = require('../actual/atob'); + +module.exports = parent; diff --git a/packages/core-js/features/btoa.js b/packages/core-js/features/btoa.js new file mode 100644 index 000000000000..7148387d2a9c --- /dev/null +++ b/packages/core-js/features/btoa.js @@ -0,0 +1,3 @@ +var parent = require('../actual/btoa'); + +module.exports = parent; diff --git a/packages/core-js/internals/base64-map.js b/packages/core-js/internals/base64-map.js new file mode 100644 index 000000000000..c1b586fe7c4a --- /dev/null +++ b/packages/core-js/internals/base64-map.js @@ -0,0 +1,9 @@ +var itoc = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; +var ctoi = {}; + +for (var index = 0; index < 66; index++) ctoi[itoc.charAt(index)] = index; + +module.exports = { + itoc: itoc, + ctoi: ctoi +}; diff --git a/packages/core-js/modules/web.atob.js b/packages/core-js/modules/web.atob.js new file mode 100644 index 000000000000..1183ebaba16c --- /dev/null +++ b/packages/core-js/modules/web.atob.js @@ -0,0 +1,52 @@ +var $ = require('../internals/export'); +var getBuiltIn = require('../internals/get-built-in'); +var uncurryThis = require('../internals/function-uncurry-this'); +var fails = require('../internals/fails'); +var toString = require('../internals/to-string'); +var hasOwn = require('../internals/has-own-property'); +var validateArgumentsLength = require('../internals/validate-arguments-length'); +var ctoi = require('../internals/base64-map').ctoi; + +var disallowed = /[^\d+/a-z]/i; +var whitespaces = /[\t\n\f\r ]+/g; +var finalEq = /[=]+$/; + +var $atob = getBuiltIn('atob'); +var fromCharCode = String.fromCharCode; +var charAt = uncurryThis(''.charAt); +var replace = uncurryThis(''.replace); +var exec = uncurryThis(disallowed.exec); + +var NO_SPACES_IGNORE = fails(function () { + return atob(' ') !== ''; +}); + +var NO_ARG_RECEIVING_CHECK = !NO_SPACES_IGNORE && !fails(function () { + $atob(); +}); + +// `atob` method +// https://html.spec.whatwg.org/multipage/webappapis.html#dom-atob +$({ global: true, enumerable: true, forced: NO_SPACES_IGNORE || NO_ARG_RECEIVING_CHECK }, { + atob: function atob(data) { + validateArgumentsLength(arguments.length, 1); + if (NO_ARG_RECEIVING_CHECK) return $atob(data); + var string = replace(toString(data), whitespaces, ''); + var output = ''; + var position = 0; + var bc = 0; + var chr, bs; + if (string.length % 4 == 0) { + string = replace(string, finalEq, ''); + } + if (string.length % 4 == 1 || exec(disallowed, string)) { + throw new (getBuiltIn('DOMException'))('The string is not correctly encoded', 'InvalidCharacterError'); + } + while (chr = charAt(string, position++)) { + if (hasOwn(ctoi, chr)) { + bs = bc % 4 ? bs * 64 + ctoi[chr] : ctoi[chr]; + if (bc++ % 4) output += fromCharCode(255 & bs >> (-2 * bc & 6)); + } + } return output; + } +}); diff --git a/packages/core-js/modules/web.btoa.js b/packages/core-js/modules/web.btoa.js new file mode 100644 index 000000000000..e092fe305d71 --- /dev/null +++ b/packages/core-js/modules/web.btoa.js @@ -0,0 +1,37 @@ +var $ = require('../internals/export'); +var getBuiltIn = require('../internals/get-built-in'); +var uncurryThis = require('../internals/function-uncurry-this'); +var fails = require('../internals/fails'); +var toString = require('../internals/to-string'); +var validateArgumentsLength = require('../internals/validate-arguments-length'); +var itoc = require('../internals/base64-map').itoc; + +var $btoa = getBuiltIn('btoa'); +var charAt = uncurryThis(''.charAt); +var charCodeAt = uncurryThis(''.charCodeAt); + +var NO_ARG_RECEIVING_CHECK = !!$btoa && !fails(function () { + $btoa(); +}); + +// `btoa` method +// https://html.spec.whatwg.org/multipage/webappapis.html#dom-btoa +$({ global: true, enumerable: true, forced: NO_ARG_RECEIVING_CHECK }, { + btoa: function btoa(data) { + validateArgumentsLength(arguments.length, 1); + if (NO_ARG_RECEIVING_CHECK) return $btoa(data); + var string = toString(data); + var output = ''; + var position = 0; + var map = itoc; + var block, charCode; + while (charAt(string, position) || (map = '=', position % 1)) { + charCode = charCodeAt(string, position += 3 / 4); + if (charCode > 0xFF) { + throw new (getBuiltIn('DOMException'))('The string contains characters outside of the Latin1 range', 'InvalidCharacterError'); + } + block = block << 8 | charCode; + output += charAt(map, 63 & block >> 8 - position % 1 * 8); + } return output; + } +}); diff --git a/packages/core-js/stable/atob.js b/packages/core-js/stable/atob.js new file mode 100644 index 000000000000..cf41fbdda263 --- /dev/null +++ b/packages/core-js/stable/atob.js @@ -0,0 +1,9 @@ +require('../modules/es.error.to-string'); +require('../modules/es.object.to-string'); +require('../modules/web.atob'); +require('../modules/web.dom-exception.constructor'); +require('../modules/web.dom-exception.stack'); +require('../modules/web.dom-exception.to-string-tag'); +var path = require('../internals/path'); + +module.exports = path.atob; diff --git a/packages/core-js/stable/btoa.js b/packages/core-js/stable/btoa.js new file mode 100644 index 000000000000..f2b39e7095e6 --- /dev/null +++ b/packages/core-js/stable/btoa.js @@ -0,0 +1,9 @@ +require('../modules/es.error.to-string'); +require('../modules/es.object.to-string'); +require('../modules/web.btoa'); +require('../modules/web.dom-exception.constructor'); +require('../modules/web.dom-exception.stack'); +require('../modules/web.dom-exception.to-string-tag'); +var path = require('../internals/path'); + +module.exports = path.btoa; diff --git a/packages/core-js/web/index.js b/packages/core-js/web/index.js index 69bf1f429251..92031d808df3 100644 --- a/packages/core-js/web/index.js +++ b/packages/core-js/web/index.js @@ -1,3 +1,5 @@ +require('../modules/web.atob'); +require('../modules/web.btoa'); require('../modules/web.dom-collections.for-each'); require('../modules/web.dom-collections.iterator'); require('../modules/web.dom-exception.constructor'); diff --git a/tests/commonjs.mjs b/tests/commonjs.mjs index 21e38b2f794e..60a4206a790b 100644 --- a/tests/commonjs.mjs +++ b/tests/commonjs.mjs @@ -545,6 +545,8 @@ for (PATH of ['core-js-pure', 'core-js']) { } for (const NS of ['stable', 'actual', 'features']) { + ok(load(NS, 'atob')('Zg==') === 'f'); + ok(load(NS, 'btoa')('f') === 'Zg=='); ok(typeof load(NS, 'dom-exception/constructor') == 'function'); ok(load(NS, 'dom-exception/to-string-tag') === 'DOMException'); ok(typeof load(NS, 'dom-exception') == 'function'); diff --git a/tests/compat/tests.js b/tests/compat/tests.js index f424819a6c46..0bb334586d28 100644 --- a/tests/compat/tests.js +++ b/tests/compat/tests.js @@ -1618,6 +1618,20 @@ GLOBAL.tests = { 'esnext.weak-set.of': function () { return WeakSet.of; }, + 'web.atob': function () { + try { + atob(); + } catch (error) { + return atob(' ') === ''; + } + }, + 'web.btoa': function () { + try { + btoa(); + } catch (error) { + return typeof btoa == 'function'; + } + }, 'web.dom-collections.for-each': function () { return (!GLOBAL.NodeList || (NodeList.prototype.forEach && NodeList.prototype.forEach === [].forEach)) && (!GLOBAL.DOMTokenList || (DOMTokenList.prototype.forEach && DOMTokenList.prototype.forEach === [].forEach)); diff --git a/tests/pure/web.atob.js b/tests/pure/web.atob.js new file mode 100644 index 000000000000..40cfd3c9126f --- /dev/null +++ b/tests/pure/web.atob.js @@ -0,0 +1,33 @@ +// based on https://github.com/davidchambers/Base64.js/blob/master/test/base64.js +import atob from 'core-js-pure/stable/atob'; + +QUnit.test('atob', assert => { + assert.isFunction(atob); + assert.arity(atob, 1); + + assert.same(atob(''), ''); + assert.same(atob('Zg=='), 'f'); + assert.same(atob('Zm8='), 'fo'); + assert.same(atob('Zm9v'), 'foo'); + assert.same(atob('cXV1eA=='), 'quux'); + assert.same(atob('ISIjJCU='), '!"#$%'); + assert.same(atob('JicoKSor'), "&'()*+"); + assert.same(atob('LC0uLzAxMg=='), ',-./012'); + assert.same(atob('MzQ1Njc4OTo='), '3456789:'); + assert.same(atob('Ozw9Pj9AQUJD'), ';<=>?@ABC'); + assert.same(atob('REVGR0hJSktMTQ=='), 'DEFGHIJKLM'); + assert.same(atob('Tk9QUVJTVFVWV1g='), 'NOPQRSTUVWX'); + assert.same(atob('WVpbXF1eX2BhYmM='), 'YZ[\\]^_`abc'); + assert.same(atob('ZGVmZ2hpamtsbW5vcA=='), 'defghijklmnop'); + assert.same(atob('cXJzdHV2d3h5ent8fX4='), 'qrstuvwxyz{|}~'); + assert.same(atob(' '), ''); + + assert.same(atob(42), atob('42')); + assert.same(atob(null), atob('null')); + + assert.throws(() => atob(), TypeError, 'no args'); + assert.throws(() => atob('a'), 'invalid #1'); + assert.throws(() => atob('a '), 'invalid #2'); + assert.throws(() => atob('aaaaa'), 'invalid #3'); + assert.throws(() => atob('[object Object]'), 'invalid #4'); +}); diff --git a/tests/pure/web.btoa.js b/tests/pure/web.btoa.js new file mode 100644 index 000000000000..5913ef876560 --- /dev/null +++ b/tests/pure/web.btoa.js @@ -0,0 +1,31 @@ +// based on https://github.com/davidchambers/Base64.js/blob/master/test/base64.js +import btoa from 'core-js-pure/stable/btoa'; + +QUnit.test('btoa', assert => { + assert.isFunction(btoa); + assert.arity(btoa, 1); + + assert.same(btoa(''), ''); + assert.same(btoa('f'), 'Zg=='); + assert.same(btoa('fo'), 'Zm8='); + assert.same(btoa('foo'), 'Zm9v'); + assert.same(btoa('quux'), 'cXV1eA=='); + assert.same(btoa('!"#$%'), 'ISIjJCU='); + assert.same(btoa("&'()*+"), 'JicoKSor'); + assert.same(btoa(',-./012'), 'LC0uLzAxMg=='); + assert.same(btoa('3456789:'), 'MzQ1Njc4OTo='); + assert.same(btoa(';<=>?@ABC'), 'Ozw9Pj9AQUJD'); + assert.same(btoa('DEFGHIJKLM'), 'REVGR0hJSktMTQ=='); + assert.same(btoa('NOPQRSTUVWX'), 'Tk9QUVJTVFVWV1g='); + assert.same(btoa('YZ[\\]^_`abc'), 'WVpbXF1eX2BhYmM='); + assert.same(btoa('defghijklmnop'), 'ZGVmZ2hpamtsbW5vcA=='); + assert.same(btoa('qrstuvwxyz{|}~'), 'cXJzdHV2d3h5ent8fX4='); + assert.same(btoa('qrstuvwxyz{|}~'), 'cXJzdHV2d3h5ent8fX4='); + + assert.same(btoa(42), btoa('42')); + assert.same(btoa(null), btoa('null')); + assert.same(btoa({ x: 1 }), btoa('[object Object]')); + + assert.throws(() => btoa(), TypeError, 'no args'); + assert.throws(() => btoa('✈'), 'non-ASCII'); +}); diff --git a/tests/tests/web.atob.js b/tests/tests/web.atob.js new file mode 100644 index 000000000000..c67bc91a594f --- /dev/null +++ b/tests/tests/web.atob.js @@ -0,0 +1,35 @@ +// based on https://github.com/davidchambers/Base64.js/blob/master/test/base64.js +import { NODE } from '../helpers/constants'; + +QUnit.test('atob', assert => { + assert.isFunction(atob); + assert.arity(atob, 1); + assert.name(atob, 'atob'); + if (!NODE) assert.looksNative(atob); + + assert.same(atob(''), ''); + assert.same(atob('Zg=='), 'f'); + assert.same(atob('Zm8='), 'fo'); + assert.same(atob('Zm9v'), 'foo'); + assert.same(atob('cXV1eA=='), 'quux'); + assert.same(atob('ISIjJCU='), '!"#$%'); + assert.same(atob('JicoKSor'), "&'()*+"); + assert.same(atob('LC0uLzAxMg=='), ',-./012'); + assert.same(atob('MzQ1Njc4OTo='), '3456789:'); + assert.same(atob('Ozw9Pj9AQUJD'), ';<=>?@ABC'); + assert.same(atob('REVGR0hJSktMTQ=='), 'DEFGHIJKLM'); + assert.same(atob('Tk9QUVJTVFVWV1g='), 'NOPQRSTUVWX'); + assert.same(atob('WVpbXF1eX2BhYmM='), 'YZ[\\]^_`abc'); + assert.same(atob('ZGVmZ2hpamtsbW5vcA=='), 'defghijklmnop'); + assert.same(atob('cXJzdHV2d3h5ent8fX4='), 'qrstuvwxyz{|}~'); + assert.same(atob(' '), ''); + + assert.same(atob(42), atob('42')); + assert.same(atob(null), atob('null')); + + assert.throws(() => atob(), TypeError, 'no args'); + assert.throws(() => atob('a'), 'invalid #1'); + assert.throws(() => atob('a '), 'invalid #2'); + assert.throws(() => atob('aaaaa'), 'invalid #3'); + assert.throws(() => atob('[object Object]'), 'invalid #4'); +}); diff --git a/tests/tests/web.btoa.js b/tests/tests/web.btoa.js new file mode 100644 index 000000000000..02cbe5cc7f0d --- /dev/null +++ b/tests/tests/web.btoa.js @@ -0,0 +1,33 @@ +// based on https://github.com/davidchambers/Base64.js/blob/master/test/base64.js +import { NODE } from '../helpers/constants'; + +QUnit.test('btoa', assert => { + assert.isFunction(btoa); + assert.arity(btoa, 1); + assert.name(btoa, 'btoa'); + if (!NODE) assert.looksNative(btoa); + + assert.same(btoa(''), ''); + assert.same(btoa('f'), 'Zg=='); + assert.same(btoa('fo'), 'Zm8='); + assert.same(btoa('foo'), 'Zm9v'); + assert.same(btoa('quux'), 'cXV1eA=='); + assert.same(btoa('!"#$%'), 'ISIjJCU='); + assert.same(btoa("&'()*+"), 'JicoKSor'); + assert.same(btoa(',-./012'), 'LC0uLzAxMg=='); + assert.same(btoa('3456789:'), 'MzQ1Njc4OTo='); + assert.same(btoa(';<=>?@ABC'), 'Ozw9Pj9AQUJD'); + assert.same(btoa('DEFGHIJKLM'), 'REVGR0hJSktMTQ=='); + assert.same(btoa('NOPQRSTUVWX'), 'Tk9QUVJTVFVWV1g='); + assert.same(btoa('YZ[\\]^_`abc'), 'WVpbXF1eX2BhYmM='); + assert.same(btoa('defghijklmnop'), 'ZGVmZ2hpamtsbW5vcA=='); + assert.same(btoa('qrstuvwxyz{|}~'), 'cXJzdHV2d3h5ent8fX4='); + assert.same(btoa('qrstuvwxyz{|}~'), 'cXJzdHV2d3h5ent8fX4='); + + assert.same(btoa(42), btoa('42')); + assert.same(btoa(null), btoa('null')); + assert.same(btoa({ x: 1 }), btoa('[object Object]')); + + assert.throws(() => btoa(), TypeError, 'no args'); + assert.throws(() => btoa('✈'), 'non-ASCII'); +});