Skip to content

Commit

Permalink
[Fix] Uint8Array.prototype.toBase64: properly check detachment; avo…
Browse files Browse the repository at this point in the history
…id invoking toString on a non-string `string` argument

Also:
 - add test coverage from tc39/test262#3994
  • Loading branch information
ljharb committed Feb 28, 2024
1 parent e8f558a commit 97ea97f
Show file tree
Hide file tree
Showing 3 changed files with 180 additions and 3 deletions.
8 changes: 6 additions & 2 deletions Uint8Array.prototype.toBase64/implementation.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
var $TypeError = require('es-errors/type');

var Get = require('es-abstract/2023/Get');
var ValidateUint8Array = require('../aos/ValidateUint8Array');
var GetOptionsObject = require('../aos/GetOptionsObject');
var GetUint8ArrayBytes = require('../aos/GetUint8ArrayBytes');
var ValidateUint8Array = require('../aos/ValidateUint8Array');

var alphabetFromIdentifier = require('../aos/helpers/alphabetFromIdentifier');

Expand All @@ -27,13 +28,16 @@ module.exports = function toBase64() {
}

if (typeof alphabet !== 'string') {
throw new $TypeError('`alphabet` is not a string: ' + alphabet); // step 6
throw new $TypeError('`alphabet` is not a string: ' + typeof alphabet); // step 6
}

if (alphabet !== 'base64' && alphabet !== 'base64url') {
throw new $TypeError('Invalid alphabet'); // step 7
}

// eslint-disable-next-line no-unused-vars
var toEncode = GetUint8ArrayBytes(O); // step 8

// if (alphabet === 'base64') { // step 8
// a. Let outAscii be the sequence of code points which results from encoding toEncode according to the base64 encoding specified in section 4 of RFC 4648.
// } else { // step 9
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
"@ljharb/eslint-config": "^21.1.0",
"aud": "^2.0.4",
"auto-changelog": "^2.4.0",
"available-typed-arrays": "^1.0.7",
"eslint": "=8.8.0",
"evalmd": "^0.0.19",
"for-each": "^0.3.3",
Expand Down
174 changes: 173 additions & 1 deletion test/Uint8Array.prototype.toBase64.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
'use strict';

var availableTypedArrays = require('available-typed-arrays')();
var callBind = require('call-bind');
var defineProperties = require('define-properties');
var DetachArrayBuffer = require('es-abstract/2023/DetachArrayBuffer');
var forEach = require('for-each');
var isCore = require('is-core-module');
var test = require('tape');
var callBind = require('call-bind');

/* globals postMessage: false */
var canDetach = typeof structuredClone === 'function' || typeof postMessage === 'function' || isCore('worker_threads');

var index = require('../Uint8Array.prototype.toBase64');
var impl = require('../Uint8Array.prototype.toBase64/implementation');
Expand Down Expand Up @@ -55,6 +62,171 @@ module.exports = {
'invalid alphabet throws'
);

st.test('test262: test/built-ins/Uint8Array/prototype/toBase64/alphabet.js', function (s2t) {
s2t.equal(method(new Uint8Array([199, 239, 242])), 'x+/y');

s2t.equal(method(new Uint8Array([199, 239, 242]), { alphabet: 'base64' }), 'x+/y');

s2t.equal(method(new Uint8Array([199, 239, 242]), { alphabet: 'base64url' }), 'x-_y');

s2t['throws'](
function () { method(new Uint8Array([199, 239, 242]), { alphabet: 'other' }); },
TypeError
);

s2t.end();
});

st.test('test262: test/built-ins/Uint8Array/prototype/toBase64/receiver-not-uint8array.js', {
skip: defineProperties.supportsDescriptors
}, function (s2t) {
var options = {};
s2t.intercept(options, 'alphabet', {
get: function () {
throw new EvalError('options.alphabet accessed despite incompatible receiver');
}
});

forEach(availableTypedArrays, function (taName) {
if (taName === 'Uint8Array') { return; }
var TA = global[taName];
var sample = new TA(2);
s2t['throws'](
function () { method(sample, options); },
TypeError,
'throws with ' + taName
);
});

s2t['throws'](
function () { method([], options); },
TypeError
);

s2t['throws'](
function () { method(options); },
TypeError
);

s2t.end();
});

st.test('test262: test/built-ins/Uint8Array/prototype/toBase64/detached-buffer.js', {
skip: !canDetach || !defineProperties.supportsDescriptors
}, function (s2t) {
var arr = new Uint8Array(2);
var receiverDetachingOptions = {};
var results = s2t.intercept(receiverDetachingOptions, 'alphabet', {
get: function () {
DetachArrayBuffer(arr.buffer);
return 'base64';
}
});

s2t['throws'](
function () { method(arr, receiverDetachingOptions); },
TypeError
);
s2t.deepEqual(results(), [
{
type: 'get',
success: true,
value: 'base64',
args: [],
receiver: receiverDetachingOptions
}
]);

var detached = new Uint8Array(2);
DetachArrayBuffer(detached.buffer);
var sideEffectingOptions = {};
var results2 = s2t.intercept(sideEffectingOptions, 'alphabet', {
get: function () {
return 'base64';
}
});

s2t['throws'](
function () { method(detached, sideEffectingOptions); },
TypeError
);
s2t.deepEqual(results2(), [
{
type: 'get',
success: true,
value: 'base64',
args: [],
receiver: sideEffectingOptions
}
]);

s2t.end();
});

st.test('test262: test/built-ins/Uint8Array/prototype/toBase64/option-coercion.js', function (s2t) {
var throwyToString = {};
var results = s2t.intercept(throwyToString, 'toString', {
value: function () {
throw new EvalError('toString called on alphabet value');
}
});
s2t['throws'](
function () { method(new Uint8Array(2), { alphabet: throwyToString }); },
TypeError
);
s2t.deepEqual(results(), []);

var base64UrlOptions = {};
var results2 = s2t.intercept(base64UrlOptions, 'alphabet', {
get: function () {
return 'base64url';
}
});
s2t.equal(method(new Uint8Array([199, 239, 242]), base64UrlOptions), 'x-_y');
s2t.deepEqual(results2(), [
{
type: 'get',
success: true,
value: 'base64url',
args: [],
receiver: base64UrlOptions
}
]);

// side-effects from the getter on the receiver are reflected in the result
var arr = new Uint8Array([0]);
var receiverMutatingOptions = {};
var results3 = s2t.intercept(receiverMutatingOptions, 'alphabet', {
get: function () {
arr[0] = 255;
return 'base64';
}
});
var result = method(arr, receiverMutatingOptions);
s2t.equal(result, '/w==');
s2t.equal(arr[0], 255);
s2t.deepEqual(results3(), [
{
type: 'get',
success: true,
value: 'base64',
args: [],
receiver: receiverMutatingOptions
}
]);

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==');
st.equal(method(new Uint8Array([102, 111])), 'Zm8=');
st.equal(method(new Uint8Array([102, 111, 111])), 'Zm9v');
st.equal(method(new Uint8Array([102, 111, 111, 98])), 'Zm9vYg==');
st.equal(method(new Uint8Array([102, 111, 111, 98, 97])), 'Zm9vYmE=');
st.equal(method(new Uint8Array([102, 111, 111, 98, 97, 114])), 'Zm9vYmFy');

st.end();
});
},
Expand Down

0 comments on commit 97ea97f

Please sign in to comment.