Skip to content

Commit

Permalink
[WIP] Add atob / btoa (#1036)
Browse files Browse the repository at this point in the history
  • Loading branch information
zloirock authored Jan 30, 2022
1 parent 8cf13a8 commit 4faa860
Show file tree
Hide file tree
Showing 20 changed files with 325 additions and 3 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
23 changes: 20 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -2998,10 +2999,9 @@ function structuredClone(value: Serializable, { transfer?: Sequence<Transferable
```
[*CommonJS entry points:*](#commonjs-api)
```js
core-js/web/structured-clone
core-js(-pure)/stable|actual|features/structured-clone
```
[*Examples*](t.ly/dBEC):
[*Examples*](is.gd/RhK7TW):
```js
const structured = [{ a: 42 }];
const sclone = structuredClone(structured);
Expand Down Expand Up @@ -3036,6 +3036,23 @@ structuredClone(new WeakMap()); // => 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
Expand Down Expand Up @@ -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'
Expand Down
20 changes: 20 additions & 0 deletions packages/core-js-compat/src/data.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
4 changes: 4 additions & 0 deletions packages/core-js-compat/src/modules-by-versions.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -135,4 +135,8 @@ export default {
'web.dom-exception.to-string-tag',
'web.structured-clone',
],
3.21: [
'web.atob',
'web.btoa',
],
};
3 changes: 3 additions & 0 deletions packages/core-js/actual/atob.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
var parent = require('../stable/atob');

module.exports = parent;
3 changes: 3 additions & 0 deletions packages/core-js/actual/btoa.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
var parent = require('../stable/btoa');

module.exports = parent;
3 changes: 3 additions & 0 deletions packages/core-js/features/atob.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
var parent = require('../actual/atob');

module.exports = parent;
3 changes: 3 additions & 0 deletions packages/core-js/features/btoa.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
var parent = require('../actual/btoa');

module.exports = parent;
9 changes: 9 additions & 0 deletions packages/core-js/internals/base64-map.js
Original file line number Diff line number Diff line change
@@ -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
};
52 changes: 52 additions & 0 deletions packages/core-js/modules/web.atob.js
Original file line number Diff line number Diff line change
@@ -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;
}
});
37 changes: 37 additions & 0 deletions packages/core-js/modules/web.btoa.js
Original file line number Diff line number Diff line change
@@ -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;
}
});
9 changes: 9 additions & 0 deletions packages/core-js/stable/atob.js
Original file line number Diff line number Diff line change
@@ -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;
9 changes: 9 additions & 0 deletions packages/core-js/stable/btoa.js
Original file line number Diff line number Diff line change
@@ -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;
2 changes: 2 additions & 0 deletions packages/core-js/web/index.js
Original file line number Diff line number Diff line change
@@ -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');
Expand Down
2 changes: 2 additions & 0 deletions tests/commonjs.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down
14 changes: 14 additions & 0 deletions tests/compat/tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
33 changes: 33 additions & 0 deletions tests/pure/web.atob.js
Original file line number Diff line number Diff line change
@@ -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');
});
31 changes: 31 additions & 0 deletions tests/pure/web.btoa.js
Original file line number Diff line number Diff line change
@@ -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');
});
35 changes: 35 additions & 0 deletions tests/tests/web.atob.js
Original file line number Diff line number Diff line change
@@ -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');
});
Loading

0 comments on commit 4faa860

Please sign in to comment.