Skip to content

Commit

Permalink
add Uint8Array.prototype.setFromBase64 and lastChunkHandling option
Browse files Browse the repository at this point in the history
  • Loading branch information
zloirock committed Jul 31, 2024
1 parent 8b10ea8 commit 4ce7d0c
Show file tree
Hide file tree
Showing 18 changed files with 259 additions and 84 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@
- Methods:
- `Uint8Array.fromBase64`
- `Uint8Array.fromHex`
- `Uint8Array.prototype.setFromBase64`
- `Uint8Array.prototype.setFromHex`
- `Uint8Array.prototype.toBase64`
- `Uint8Array.prototype.toHex`
- Added `Uint8Array.prototype.setFromHex` method
- Added `Uint8Array.prototype.{ setFromBase64, setFromHex }` methods
- Added `Uint8Array.fromBase64` and `Uint8Array.prototype.setFromBase64` `lastChunkHandling` option, [proposal-arraybuffer-base64/33](https://github.com/tc39/proposal-arraybuffer-base64/pull/33)
- Added `Uint8Array.prototype.toBase64` `omitPadding` option, [proposal-arraybuffer-base64/60](https://github.com/tc39/proposal-arraybuffer-base64/pull/60)
- Added throwing a `TypeError` on arrays backed by detached buffers
- Unconditional forced replacement changed to feature detection
Expand Down
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2433,9 +2433,10 @@ console.log(view.getFloat16(0)); // => 1.3369140625
Modules [`esnext.uint8-array.from-base64`](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/esnext.uint8-array.from-base64.js), [`esnext.uint8-array.from-hex`](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/esnext.uint8-array.from-hex.js), [`esnext.uint8-array.set-from-hex`](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/esnext.uint8-array.set-from-hex.js), [`esnext.uint8-array.to-base64`](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/esnext.uint8-array.to-base64.js), [`esnext.uint8-array.to-hex`](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/esnext.uint8-array.to-hex.js).
```js
class Uint8Array {
static fromBase64(string, options?: { alphabet?: 'base64' | 'base64url', strict?: boolean }): Uint8Array;
static fromHex(string): Uint8Array;
setFromHex(string): { read: uint, written: uint };
static fromBase64(string: string, options?: { alphabet?: 'base64' | 'base64url', lastChunkHandling?: 'loose' | 'strict' | 'stop-before-partial' }): Uint8Array;
static fromHex(string: string): Uint8Array;
setFromBase64(string: string, options?: { alphabet?: 'base64' | 'base64url', lastChunkHandling?: 'loose' | 'strict' | 'stop-before-partial' }): { read: uint, written: uint };
setFromHex(string: string): { read: uint, written: uint };
toBase64(options?: { alphabet?: 'base64' | 'base64url', omitPadding?: boolean }): string;
toHex(): string;
}
Expand All @@ -2446,6 +2447,7 @@ class Uint8Array {
core-js/proposals/array-buffer-base64
core-js(-pure)/actual|full/typed-array/from-base64
core-js(-pure)/actual|full/typed-array/from-hex
core-js(-pure)/actual|full/typed-array/set-from-base64
core-js(-pure)/actual|full/typed-array/set-from-hex
core-js(-pure)/actual|full/typed-array/to-base64
core-js(-pure)/actual|full/typed-array/to-hex
Expand Down
2 changes: 2 additions & 0 deletions packages/core-js-compat/src/data.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2512,6 +2512,8 @@ export const data = {
},
'esnext.uint8-array.from-hex': {
},
'esnext.uint8-array.set-from-base64': {
},
'esnext.uint8-array.set-from-hex': {
},
'esnext.uint8-array.to-base64': {
Expand Down
1 change: 1 addition & 0 deletions packages/core-js-compat/src/modules-by-versions.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ export default {
'web.url.parse',
],
3.38: [
'esnext.uint8-array.set-from-base64',
'esnext.uint8-array.set-from-hex',
],
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// empty
1 change: 1 addition & 0 deletions packages/core-js/actual/typed-array/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
var parent = require('../../stable/typed-array');
require('../../modules/esnext.uint8-array.from-base64');
require('../../modules/esnext.uint8-array.from-hex');
require('../../modules/esnext.uint8-array.set-from-base64');
require('../../modules/esnext.uint8-array.set-from-hex');
require('../../modules/esnext.uint8-array.to-base64');
require('../../modules/esnext.uint8-array.to-hex');
Expand Down
1 change: 1 addition & 0 deletions packages/core-js/actual/typed-array/methods.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
var parent = require('../../stable/typed-array/methods');
require('../../modules/esnext.uint8-array.from-base64');
require('../../modules/esnext.uint8-array.from-hex');
require('../../modules/esnext.uint8-array.set-from-base64');
require('../../modules/esnext.uint8-array.set-from-hex');
require('../../modules/esnext.uint8-array.to-base64');
require('../../modules/esnext.uint8-array.to-hex');
Expand Down
2 changes: 2 additions & 0 deletions packages/core-js/actual/typed-array/set-from-base64.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
'use strict';
require('../../modules/esnext.uint8-array.set-from-base64');
4 changes: 4 additions & 0 deletions packages/core-js/full/typed-array/set-from-base64.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
'use strict';
var parent = require('../../actual/typed-array/set-from-base64');

module.exports = parent;
157 changes: 157 additions & 0 deletions packages/core-js/internals/uint8-from-base64.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
'use strict';
var globalThis = require('../internals/global-this');
var uncurryThis = require('../internals/function-uncurry-this');
var anObjectOrUndefined = require('../internals/an-object-or-undefined');
var aString = require('../internals/a-string');
var hasOwn = require('../internals/has-own-property');
var base64Map = require('../internals/base64-map');
var getAlphabetOption = require('../internals/get-alphabet-option');
var notDetached = require('../internals/array-buffer-not-detached');

var base64Alphabet = base64Map.c2i;
var base64UrlAlphabet = base64Map.c2iUrl;

var SyntaxError = globalThis.SyntaxError;
var TypeError = globalThis.TypeError;
var ASCII_WHITESPACE = /[\t\n\f\r ]/;
var exec = uncurryThis(ASCII_WHITESPACE.exec);
var at = uncurryThis(''.charAt);

var skipAsciiWhitespace = function (string, index) {
var length = string.length;
for (;index < length; index++) {
if (!exec(ASCII_WHITESPACE, at(string, index))) break;
} return index;
};

var decodeBase64Chunk = function (chunk, alphabet, throwOnExtraBits) {
var chunkLength = chunk.length;

if (chunkLength < 4) {
chunk += chunkLength === 2 ? 'AA' : 'A';
}

var triplet = (alphabet[at(chunk, 0)] << 18)
+ (alphabet[at(chunk, 1)] << 12)
+ (alphabet[at(chunk, 2)] << 6)
+ alphabet[at(chunk, 3)];

var chunkBytes = [
(triplet >> 16) & 255,
(triplet >> 8) & 255,
triplet & 255
];

if (chunkLength === 2) {
if (throwOnExtraBits && chunkBytes[1] !== 0) {
throw new SyntaxError('Extra bits');
}
return [chunkBytes[0]];
}

if (chunkLength === 3) {
if (throwOnExtraBits && chunkBytes[2] !== 0) {
throw new SyntaxError('Extra bits');
}
return [chunkBytes[0], chunkBytes[1]];
}

return chunkBytes;
};

var writeBytes = function (bytes, elements, written) {
var elementsLength = elements.length;
for (var index = 0; index < elementsLength; index++) {
bytes[written + index] = elements[index];
}
return written + elementsLength;
};

/* eslint-disable max-statements, max-depth -- TODO */
module.exports = function (string, options, into, maxLength) {
aString(string);
anObjectOrUndefined(options);
var alphabet = getAlphabetOption(options) === 'base64' ? base64Alphabet : base64UrlAlphabet;
var lastChunkHandling = options ? options.lastChunkHandling : undefined;

if (lastChunkHandling === undefined) lastChunkHandling = 'loose';

if (lastChunkHandling !== 'loose' && lastChunkHandling !== 'strict' && lastChunkHandling !== 'stop-before-partial') {
throw new TypeError('Incorrect `lastChunkHandling` option');
}

if (into) notDetached(into.buffer);

var bytes = into || [];
var written = 0;
var read = 0;
var chunk = '';
var index = 0;

if (maxLength) while (true) {
index = skipAsciiWhitespace(string, index);
if (index === string.length) {
if (chunk.length > 0) {
if (lastChunkHandling === 'stop-before-partial') {
break;
}
if (lastChunkHandling === 'loose') {
if (chunk.length === 1) {
throw new SyntaxError('Malformed padding: exactly one additional character');
}
written = writeBytes(bytes, decodeBase64Chunk(chunk, alphabet, false), written);
} else {
throw new SyntaxError('Missing padding');
}
}
read = string.length;
break;
}
var chr = at(string, index);
++index;
if (chr === '=') {
if (chunk.length < 2) {
throw new SyntaxError('Padding is too early');
}
index = skipAsciiWhitespace(string, index);
if (chunk.length === 2) {
if (index === string.length) {
if (lastChunkHandling === 'stop-before-partial') {
break;
}
throw new SyntaxError('Malformed padding: only one =');
}
if (at(string, index) === '=') {
++index;
index = skipAsciiWhitespace(string, index);
}
}
if (index < string.length) {
throw new SyntaxError('Unexpected character after padding');
}
written = writeBytes(bytes, decodeBase64Chunk(chunk, alphabet, lastChunkHandling === 'strict'), written);
read = string.length;
break;
}
if (!hasOwn(alphabet, chr)) {
throw new SyntaxError('Unexpected character');
}
var remainingBytes = maxLength - written;
if (remainingBytes === 1 && chunk.length === 2 || remainingBytes === 2 && chunk.length === 3) {
// special case: we can fit exactly the number of bytes currently represented by chunk, so we were just checking for `=`
break;
}

chunk += chr;
if (chunk.length === 4) {
written = writeBytes(bytes, decodeBase64Chunk(chunk, alphabet, false), written);
chunk = '';
read = index;
if (written === maxLength) {
break;
}
}
}

return { bytes: bytes, read: read, written: written };
};
6 changes: 3 additions & 3 deletions packages/core-js/internals/uint8-from-hex.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ module.exports = function (string, into) {
var maxLength = into ? min(into.length, stringLength / 2) : stringLength / 2;
var bytes = into || new Uint8Array(maxLength);
var read = 0;
var index = 0;
while (index < maxLength) {
var written = 0;
while (written < maxLength) {
var hexits = stringSlice(string, read, read += 2);
if (exec(NOT_HEX, hexits)) throw new SyntaxError('String should only contain hex characters');
bytes[index++] = parseInt(hexits, 16);
bytes[written++] = parseInt(hexits, 16);
}
return { bytes: bytes, read: read };
};
65 changes: 3 additions & 62 deletions packages/core-js/modules/esnext.uint8-array.from-base64.js
Original file line number Diff line number Diff line change
@@ -1,75 +1,16 @@
'use strict';
var $ = require('../internals/export');
var globalThis = require('../internals/global-this');
var uncurryThis = require('../internals/function-uncurry-this');
var anObjectOrUndefined = require('../internals/an-object-or-undefined');
var aString = require('../internals/a-string');
var hasOwn = require('../internals/has-own-property');
var arrayFromConstructorAndList = require('../internals/array-from-constructor-and-list');
var base64Map = require('../internals/base64-map');
var getAlphabetOption = require('../internals/get-alphabet-option');

var base64Alphabet = base64Map.c2i;
var base64UrlAlphabet = base64Map.c2iUrl;
var $fromBase64 = require('../internals/uint8-from-base64');

var Uint8Array = globalThis.Uint8Array;
var SyntaxError = globalThis.SyntaxError;
var charAt = uncurryThis(''.charAt);
var replace = uncurryThis(''.replace);
var stringSlice = uncurryThis(''.slice);
var push = uncurryThis([].push);
var SPACES = /[\t\n\f\r ]/g;
var EXTRA_BITS = 'Extra bits';

// `Uint8Array.fromBase64` method
// https://github.com/tc39/proposal-arraybuffer-base64
if (Uint8Array) $({ target: 'Uint8Array', stat: true }, {
fromBase64: function fromBase64(string /* , options */) {
aString(string);
var options = arguments.length > 1 ? anObjectOrUndefined(arguments[1]) : undefined;
var alphabet = getAlphabetOption(options) === 'base64' ? base64Alphabet : base64UrlAlphabet;
var strict = options ? !!options.strict : false;

var input = strict ? string : replace(string, SPACES, '');

if (input.length % 4 === 0) {
if (stringSlice(input, -2) === '==') input = stringSlice(input, 0, -2);
else if (stringSlice(input, -1) === '=') input = stringSlice(input, 0, -1);
} else if (strict) throw new SyntaxError('Input is not correctly padded');

var lastChunkSize = input.length % 4;

switch (lastChunkSize) {
case 1: throw new SyntaxError('Bad input length');
case 2: input += 'AA'; break;
case 3: input += 'A';
}

var bytes = [];
var i = 0;
var inputLength = input.length;

var at = function (shift) {
var chr = charAt(input, i + shift);
if (!hasOwn(alphabet, chr)) throw new SyntaxError('Bad char in input: "' + chr + '"');
return alphabet[chr] << (18 - 6 * shift);
};

for (; i < inputLength; i += 4) {
var triplet = at(0) + at(1) + at(2) + at(3);
push(bytes, (triplet >> 16) & 255, (triplet >> 8) & 255, triplet & 255);
}

var byteLength = bytes.length;

if (lastChunkSize === 2) {
if (strict && bytes[byteLength - 2] !== 0) throw new SyntaxError(EXTRA_BITS);
byteLength -= 2;
} else if (lastChunkSize === 3) {
if (strict && bytes[byteLength - 1] !== 0) throw new SyntaxError(EXTRA_BITS);
byteLength--;
}

return arrayFromConstructorAndList(Uint8Array, bytes, byteLength);
var result = $fromBase64(string, arguments.length > 1 ? arguments[1] : undefined, null, 0x1FFFFFFFFFFFFF);
return arrayFromConstructorAndList(Uint8Array, result.bytes);
}
});
19 changes: 19 additions & 0 deletions packages/core-js/modules/esnext.uint8-array.set-from-base64.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
'use strict';
var $ = require('../internals/export');
var globalThis = require('../internals/global-this');
var $fromBase64 = require('../internals/uint8-from-base64');
var anUint8Array = require('../internals/an-uint8-array');

var Uint8Array = globalThis.Uint8Array;

// `Uint8Array.prototype.setFromBase64` method
// https://github.com/tc39/proposal-arraybuffer-base64
if (Uint8Array) $({ target: 'Uint8Array', proto: true }, {
setFromBase64: function setFromBase64(string /* , options */) {
anUint8Array(this);

var result = $fromBase64(string, arguments.length > 1 ? arguments[1] : undefined, this, this.length);

return { read: result.read, written: result.written };
}
});
1 change: 1 addition & 0 deletions packages/core-js/proposals/array-buffer-base64.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// https://github.com/tc39/proposal-arraybuffer-base64
require('../modules/esnext.uint8-array.from-base64');
require('../modules/esnext.uint8-array.from-hex');
require('../modules/esnext.uint8-array.set-from-base64');
require('../modules/esnext.uint8-array.set-from-hex');
require('../modules/esnext.uint8-array.to-base64');
require('../modules/esnext.uint8-array.to-hex');
3 changes: 3 additions & 0 deletions tests/compat/tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -1860,6 +1860,9 @@ GLOBAL.tests = {
'esnext.uint8-array.from-hex': function () {
return Uint8Array.fromHex;
},
'esnext.uint8-array.set-from-base64': function () {
return Uint8Array.prototype.setFromBase64;
},
'esnext.uint8-array.set-from-hex': function () {
return Uint8Array.prototype.setFromHex;
},
Expand Down
1 change: 1 addition & 0 deletions tests/entries/unit.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1074,6 +1074,7 @@ for (const NS of ['es', 'stable', 'actual', 'full', 'features']) {
for (const NS of ['actual', 'full', 'features']) {
load(NS, 'typed-array/from-base64');
load(NS, 'typed-array/from-hex');
load(NS, 'typed-array/set-from-base64');
load(NS, 'typed-array/set-from-hex');
load(NS, 'typed-array/to-base64');
load(NS, 'typed-array/to-hex');
Expand Down
Loading

0 comments on commit 4ce7d0c

Please sign in to comment.