diff --git a/Uint8Array.prototype.setFromBase64/implementation.js b/Uint8Array.prototype.setFromBase64/implementation.js index 760658e..d7522cd 100644 --- a/Uint8Array.prototype.setFromBase64/implementation.js +++ b/Uint8Array.prototype.setFromBase64/implementation.js @@ -64,21 +64,21 @@ module.exports = function setFromBase64(string) { var result = FromBase64(string, alphabet, lastChunkHandling, byteLength); // step 14 - if (result['[[Error]]']) { // step 15 - throw result['[[Error]]']; // step 15.a - } - - var bytes = result['[[Bytes]]']; // step 16 + var bytes = result['[[Bytes]]']; // step 15 - var written = bytes.length; // step 17 + var written = bytes.length; // step 16 - // 18. NOTE: FromBase64 does not invoke any user code, so the ArrayBuffer backing into cannot have been detached or shrunk. + // 17. NOTE: FromBase64 does not invoke any user code, so the ArrayBuffer backing into cannot have been detached or shrunk. if (written > byteLength) { throw new $TypeError('Assertion failed: written is not <= byteLength'); // step 19 } - SetUint8ArrayBytes(into, bytes); // step 20 + SetUint8ArrayBytes(into, bytes); // step 19 + + if (result['[[Error]]']) { // step 20 + throw result['[[Error]]']; // step 20.a + } var offset = typedArrayByteOffset(into); // step 21 diff --git a/Uint8Array.prototype.setFromHex/implementation.js b/Uint8Array.prototype.setFromHex/implementation.js index df76531..659c673 100644 --- a/Uint8Array.prototype.setFromHex/implementation.js +++ b/Uint8Array.prototype.setFromHex/implementation.js @@ -36,21 +36,21 @@ module.exports = function setFromHex(string) { var result = FromHex(string, byteLength); // step 7 - if (result['[[Error]]']) { // step 8 - throw result['[[Error]]']; // step 8.a - } - - var bytes = result['[[Bytes]]']; // step 9 + var bytes = result['[[Bytes]]']; // step 8 - var written = bytes.length; // step 10 + var written = bytes.length; // step 9 - // 11. NOTE: FromHex does not invoke any user code, so the ArrayBuffer backing into cannot have been detached or shrunk. + // 10. NOTE: FromHex does not invoke any user code, so the ArrayBuffer backing into cannot have been detached or shrunk. if (written > byteLength) { - throw new $TypeError('Assertion failed: written is not <= byteLength'); // step 12 + throw new $TypeError('Assertion failed: written is not <= byteLength'); // step 11 } - SetUint8ArrayBytes(into, bytes); // step 13 + SetUint8ArrayBytes(into, bytes); // step 12 + + if (result['[[Error]]']) { // step 13 + throw result['[[Error]]']; // step 13.a + } // var resultObject = {}; // step 14 // OrdinaryObjectCreate(%Object.prototype%) // CreateDataPropertyOrThrow(resultObject, 'read', result['[[Read]]']); // step 15 diff --git a/aos/FromHex.js b/aos/FromHex.js index 3ed45bd..31d1f84 100644 --- a/aos/FromHex.js +++ b/aos/FromHex.js @@ -31,26 +31,38 @@ module.exports = function FromHex(string) { var length = string.length; // step 2 - if (modulo(length, 2) !== 0) { - throw new $SyntaxError('string should be an even number of characters'); // step 3 - } + var bytes = []; // step 3 - var bytes = []; // step 4 + var read = 0; // step 4 - var index = 0; // step 5 + if (modulo(length, 2) !== 0) { // step 5 + return { + '[[Read]]': read, + '[[Bytes]]': bytes, + '[[Error]]': new $SyntaxError('string should be an even number of characters') + }; + } - while (index < length && bytes.length < maxLength) { // step 6 - var hexits = substring(string, index, index + 2); // step 6.a + while (read < length && bytes.length < maxLength) { // step 6 + var hexits = substring(string, read, read + 2); // step 6.a - if (!isHexDigit(hexits)) { - throw new $SyntaxError('string should only contain hex characters'); // step 6.b + if (!isHexDigit(hexits)) { // step 6.b + return { + '[[Read]]': read, + '[[Bytes]]': bytes, + '[[Error]]': new $SyntaxError('string should only contain hex characters') + }; } - index += 2; // step 6.c + read += 2; // step 6.c var byte = $parseInt(hexits, 16); // step 6.d $push(bytes, byte); // step 6.e } - return { '[[Read]]': index, '[[Bytes]]': bytes }; // step 7 + return { + '[[Read]]': read, + '[[Bytes]]': bytes, + '[[Error]]': null + }; // step 7 }; diff --git a/test/Uint8Array.prototype.setFromBase64.js b/test/Uint8Array.prototype.setFromBase64.js index bb253c0..7d502ab 100644 --- a/test/Uint8Array.prototype.setFromBase64.js +++ b/test/Uint8Array.prototype.setFromBase64.js @@ -574,6 +574,46 @@ module.exports = { s2t.end(); }); + st.test('test262: test/built-ins/Uint8Array/prototype/setFromBase64/writes-up-to-error', function (s2t) { + var target = new Uint8Array([255, 255, 255, 255, 255]); + s2t['throws']( + function () { method(target, 'MjYyZm.9v'); }, + SyntaxError, + 'illegal character in second chunk' + ); + s2t.deepEqual( + target, + new Uint8Array([50, 54, 50, 255, 255]), + 'decoding from MjYyZm.9v should only write the valid chunks' + ); + + var target2 = new Uint8Array([255, 255, 255, 255, 255]); + s2t['throws']( + function () { method(target2, 'MjYyZg', { lastChunkHandling: 'strict' }); }, + SyntaxError, + 'padding omitted with lastChunkHandling: strict' + ); + s2t.deepEqual( + target2, + new Uint8Array([50, 54, 50, 255, 255]), + 'decoding from MjYyZg should only write the valid chunks' + ); + + var target3 = new Uint8Array([255, 255, 255, 255, 255]); + s2t['throws']( + function () { method(target3, 'MjYyZg==='); }, + SyntaxError, + 'extra characters after padding' + ); + s2t.deepEqual( + target3, + new Uint8Array([50, 54, 50, 255, 255]), + 'decoding from MjYyZg=== should not write the last chunk because it has extra padding' + ); + + s2t.end(); + }); + var illegal = [ 'Zm.9v', 'Zm9v^', diff --git a/test/Uint8Array.prototype.setFromHex.js b/test/Uint8Array.prototype.setFromHex.js index f1d3b2d..0a1fb5a 100644 --- a/test/Uint8Array.prototype.setFromHex.js +++ b/test/Uint8Array.prototype.setFromHex.js @@ -181,6 +181,26 @@ module.exports = { s2t.end(); }); + st.test('test262: test/built-ins/Uint8Array/prototype/setFromHex/writes-up-to-error', function (s2t) { + ['aaa ', 'aaag'].forEach(function (value) { + var target = new Uint8Array([255, 255, 255, 255, 255]); + s2t['throws']( + function () { method(target, value); }, + SyntaxError + ); + s2t.deepEqual(target, new Uint8Array([170, 255, 255, 255, 255]), 'decoding from ' + value); + }); + + var target = new Uint8Array([255, 255, 255, 255, 255]); + s2t['throws']( + function () { method(target, 'aaa'); }, + SyntaxError + ); + s2t.deepEqual(target, new Uint8Array([255, 255, 255, 255, 255]), 'when length is odd no data is written'); + + s2t.end(); + }); + st.end(); }); }, diff --git a/test/Uint8Array.prototype.toBase64.js b/test/Uint8Array.prototype.toBase64.js index 2294765..c1380b2 100644 --- a/test/Uint8Array.prototype.toBase64.js +++ b/test/Uint8Array.prototype.toBase64.js @@ -225,6 +225,25 @@ module.exports = { s2t.end(); }); + st.test('test262: test/built-ins/Uint8Array/prototype/toBase64/omit-padding', function (s2t) { + s2t.equal(method(new Uint8Array([199, 239])), 'x+8='); + s2t.equal(method(new Uint8Array([199, 239]), { omitPadding: false }), 'x+8='); + s2t.equal(method(new Uint8Array([199, 239]), { omitPadding: true }), 'x+8'); + s2t.equal(method(new Uint8Array([255]), { omitPadding: true }), '/w'); + + // works with base64url alphabet + s2t.equal(method(new Uint8Array([199, 239]), { alphabet: 'base64url' }), 'x-8='); + s2t.equal(method(new Uint8Array([199, 239]), { alphabet: 'base64url', omitPadding: false }), 'x-8='); + s2t.equal(method(new Uint8Array([199, 239]), { alphabet: 'base64url', omitPadding: true }), 'x-8'); + s2t.equal(method(new Uint8Array([255]), { alphabet: 'base64url', omitPadding: true }), '_w'); + + // performs ToBoolean on the argument + s2t.equal(method(new Uint8Array([255]), { omitPadding: 0 }), '/w=='); + s2t.equal(method(new Uint8Array([255]), { omitPadding: 1 }), '/w'); + + s2t.end(); + }); + // standard test vectors from https://datatracker.ietf.org/doc/html/rfc4648#section-10 st.equal(method(new Uint8Array([])), ''); st.equal(method(new Uint8Array([102])), 'Zg==');