Skip to content

Commit aae0940

Browse files
committed
[Fix] Uint8Array.fromBase64: properly throw in some cases with strict lastChunkHandling
Also: - add test coverage from tc39/test262#3994 - `fromBase64`: avoid invoking toString on a non-string `string` argument
1 parent 100b0d1 commit aae0940

3 files changed

Lines changed: 233 additions & 2 deletions

File tree

Uint8Array.fromBase64/implementation.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ module.exports = function fromBase64(string) {
1616
}
1717

1818
if (typeof string !== 'string') {
19-
throw new $TypeError('`string` is not a string: ' + string); // step 1
19+
throw new $TypeError('`string` is not a string: ' + typeof string); // step 1
2020
}
2121

2222
var opts = GetOptionsObject(arguments.length > 1 ? arguments[1] : void undefined); // step 2

aos/FromBase64.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ module.exports = function FromBase64(string, alphabet, lastChunkHandling) {
108108
throw new $SyntaxError('unexpected character after padding'); // step 10.e.iv.1
109109
}
110110

111-
var throwOnExtraBits = lastChunkHandling === 'string'; // step 10.e.v - vi
111+
var throwOnExtraBits = lastChunkHandling === 'strict'; // step 10.e.v - vi
112112

113113
bytes = safeArrayConcat(bytes, DecodeBase64Chunk(chunk, throwOnExtraBits)); // step 10.e.vii
114114

test/Uint8Array.fromBase64.js

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
'use strict';
22

33
var defineProperties = require('define-properties');
4+
var forEach = require('for-each');
5+
var getProto = require('es-abstract/helpers/getProto');
6+
var inspect = require('object-inspect');
47
var test = require('tape');
58

69
var index = require('../Uint8Array.fromBase64');
@@ -78,6 +81,234 @@ module.exports = {
7881
'base64url string with base64 alphabet throws'
7982
);
8083

84+
st.test('test262: test/built-ins/Uint8Array/fromBase64/alphabet.js', function (s2t) {
85+
s2t.deepEqual(method('x+/y'), new Uint8Array([199, 239, 242]));
86+
s2t.deepEqual(method('x+/y', { alphabet: 'base64' }), new Uint8Array([199, 239, 242]));
87+
s2t['throws'](
88+
function () {
89+
method('x+/y', { alphabet: 'base64url' });
90+
},
91+
SyntaxError
92+
);
93+
94+
s2t.deepEqual(method('x-_y', { alphabet: 'base64url' }), new Uint8Array([199, 239, 242]));
95+
s2t['throws'](
96+
function () { method('x-_y'); },
97+
SyntaxError
98+
);
99+
s2t['throws'](
100+
function () { method('x-_y', { alphabet: 'base64' }); },
101+
SyntaxError
102+
);
103+
104+
s2t.end();
105+
});
106+
107+
var illegal = [
108+
'Zm.9v',
109+
'Zm9v^',
110+
'Zg==&',
111+
'Z\u2212==', // U+2212 'Minus Sign'
112+
'Z\uFF0B==', // U+FF0B 'Fullwidth Plus Sign'
113+
'Zg\u00A0==', // nbsp
114+
'Zg\u2009==', // thin space
115+
'Zg\u2028==' // line separator
116+
];
117+
forEach(illegal, function (value) {
118+
st['throws'](
119+
function () { method(value); },
120+
SyntaxError,
121+
inspect(value) + ' throws SyntaxError'
122+
);
123+
});
124+
125+
st.test('test262: test/built-ins/Uint8Array/fromBase64/last-chunk-handling.js', function (s2t) {
126+
// padding
127+
s2t.deepEqual(method('ZXhhZg=='), new Uint8Array([101, 120, 97, 102]));
128+
s2t.deepEqual(method('ZXhhZg==', { lastChunkHandling: 'loose' }), new Uint8Array([101, 120, 97, 102]));
129+
s2t.deepEqual(method('ZXhhZg==', { lastChunkHandling: 'stop-before-partial' }), new Uint8Array([101, 120, 97, 102]));
130+
s2t.deepEqual(method('ZXhhZg==', { lastChunkHandling: 'strict' }), new Uint8Array([101, 120, 97, 102]));
131+
132+
// no padding
133+
s2t.deepEqual(method('ZXhhZg'), new Uint8Array([101, 120, 97, 102]));
134+
s2t.deepEqual(method('ZXhhZg', { lastChunkHandling: 'loose' }), new Uint8Array([101, 120, 97, 102]));
135+
s2t.deepEqual(method('ZXhhZg', { lastChunkHandling: 'stop-before-partial' }), new Uint8Array([101, 120, 97]));
136+
s2t['throws'](
137+
function () { method('ZXhhZg', { lastChunkHandling: 'strict' }); },
138+
SyntaxError
139+
);
140+
141+
// non-zero padding bits
142+
s2t.deepEqual(method('ZXhhZh=='), new Uint8Array([101, 120, 97, 102]));
143+
s2t.deepEqual(method('ZXhhZh==', { lastChunkHandling: 'loose' }), new Uint8Array([101, 120, 97, 102]));
144+
s2t.deepEqual(method('ZXhhZh==', { lastChunkHandling: 'stop-before-partial' }), new Uint8Array([101, 120, 97, 102]));
145+
s2t['throws'](
146+
function () { method('ZXhhZh==', { lastChunkHandling: 'strict' }); },
147+
SyntaxError
148+
);
149+
150+
// non-zero padding bits, no padding
151+
s2t.deepEqual(method('ZXhhZh'), new Uint8Array([101, 120, 97, 102]));
152+
s2t.deepEqual(method('ZXhhZh', { lastChunkHandling: 'loose' }), new Uint8Array([101, 120, 97, 102]));
153+
s2t.deepEqual(method('ZXhhZh', { lastChunkHandling: 'stop-before-partial' }), new Uint8Array([101, 120, 97]));
154+
s2t['throws'](
155+
function () { method('ZXhhZh', { lastChunkHandling: 'strict' }); },
156+
SyntaxError
157+
);
158+
159+
// malformed padding
160+
s2t['throws'](
161+
function () { method('ZXhhZg='); },
162+
SyntaxError
163+
);
164+
s2t['throws'](
165+
function () { method('ZXhhZg=', { lastChunkHandling: 'loose' }); },
166+
SyntaxError
167+
);
168+
s2t.deepEqual(method('ZXhhZg=', { lastChunkHandling: 'stop-before-partial' }), new Uint8Array([101, 120, 97]));
169+
s2t['throws'](
170+
function () { method('ZXhhZg=', { lastChunkHandling: 'strict' }); },
171+
SyntaxError
172+
);
173+
174+
s2t.end();
175+
});
176+
177+
st.test('test262: test/built-ins/Uint8Array/fromBase64/option-coercion.js', function (s2t) {
178+
var throwyToString = {};
179+
var results = s2t.intercept(
180+
throwyToString,
181+
'toString',
182+
{ value: function () { throw new EvalError('toString called'); } }
183+
);
184+
185+
s2t['throws'](
186+
function () { method('Zg==', { alphabet: throwyToString }); },
187+
TypeError
188+
);
189+
s2t.deepEqual(results(), []);
190+
191+
s2t['throws'](
192+
function () { method('Zg==', { lastChunkHandling: throwyToString }); },
193+
TypeError
194+
);
195+
s2t.deepEqual(results(), []);
196+
197+
s2t.test('getters', { skip: !defineProperties.supportsDescriptors }, function (s3t) {
198+
var base64UrlOptions = {};
199+
var alphabetResults = s3t.intercept(base64UrlOptions, 'alphabet', { get: function () { return 'base64url'; } });
200+
201+
var arr = method('x-_y', base64UrlOptions);
202+
s3t.deepEqual(arr, new Uint8Array([199, 239, 242]));
203+
s3t.deepEqual(alphabetResults(), [
204+
{
205+
type: 'get',
206+
success: true,
207+
value: 'base64url',
208+
args: [],
209+
receiver: base64UrlOptions
210+
}
211+
]);
212+
213+
var strictOptions = {};
214+
var strictResults = s3t.intercept(strictOptions, 'lastChunkHandling', { get: function () { return 'strict'; } });
215+
216+
var arr2 = method('Zg==', strictOptions);
217+
s3t.deepEqual(arr2, new Uint8Array([102]));
218+
s3t.deepEqual(strictResults(), [
219+
{
220+
type: 'get',
221+
success: true,
222+
value: 'strict',
223+
args: [],
224+
receiver: strictOptions
225+
}
226+
]);
227+
228+
s3t.end();
229+
});
230+
231+
s2t.end();
232+
});
233+
234+
st.test('test262: test/built-ins/Uint8Array/fromBase64/results.js', function (s2t) {
235+
// standard test vectors from https://datatracker.ietf.org/doc/html/rfc4648#section-10
236+
var standardBase64Vectors = [
237+
['', []],
238+
['Zg==', [102]],
239+
['Zm8=', [102, 111]],
240+
['Zm9v', [102, 111, 111]],
241+
['Zm9vYg==', [102, 111, 111, 98]],
242+
['Zm9vYmE=', [102, 111, 111, 98, 97]],
243+
['Zm9vYmFy', [102, 111, 111, 98, 97, 114]]
244+
];
245+
246+
forEach(standardBase64Vectors, function (pair) {
247+
var arr = method(pair[0]);
248+
s2t.equal(getProto(arr), Uint8Array.prototype, 'decoding ' + pair[0]);
249+
s2t.deepEqual(arr, new Uint8Array(pair[1]), 'decoding ' + pair[0]);
250+
});
251+
252+
s2t.end();
253+
});
254+
255+
st.test('test262: test/built-ins/Uint8Array/fromBase64/string-coercion.js', function (s2t) {
256+
var throwyToString = {};
257+
var toStringResults = s2t.intercept(
258+
throwyToString,
259+
'toString',
260+
{ value: function () { throw new EvalError('toString called'); } }
261+
);
262+
263+
s2t['throws'](
264+
function () { method(throwyToString); },
265+
TypeError
266+
);
267+
s2t.deepEqual(toStringResults(), []);
268+
269+
s2t.test('getters', { skip: !defineProperties.supportsDescriptors }, function (s3t) {
270+
var touchyOptions = {};
271+
var alphaResults = s3t.intercept(
272+
touchyOptions,
273+
'alphabet',
274+
{ get: function () { throw new EvalError('alphabet accessed'); } }
275+
);
276+
var lastChunkResults = s3t.intercept(
277+
touchyOptions,
278+
'lastChunkHandling',
279+
{ get: function () { throw new EvalError('alphabet accessed'); } }
280+
);
281+
282+
s3t['throws'](
283+
function () { method(throwyToString, touchyOptions); },
284+
TypeError
285+
);
286+
s3t.deepEqual(toStringResults(), []);
287+
s3t.deepEqual(alphaResults(), []);
288+
s3t.deepEqual(lastChunkResults(), []);
289+
290+
s3t.end();
291+
});
292+
293+
s2t.end();
294+
});
295+
296+
st.test('test262: test/built-ins/Uint8Array/fromBase64/whitespace.js', function (s2t) {
297+
var whitespaceKinds = [
298+
['Z g==', 'space'],
299+
['Z\tg==', 'tab'],
300+
['Z\x0Ag==', 'LF'],
301+
['Z\x0Cg==', 'FF'],
302+
['Z\x0Dg==', 'CR']
303+
];
304+
forEach(whitespaceKinds, function (pair) {
305+
var arr = method(pair[0]);
306+
s2t.deepEqual(arr, new Uint8Array([102]), 'ascii whitespace: ' + pair[1]);
307+
});
308+
309+
s2t.end();
310+
});
311+
81312
st.end();
82313
});
83314
},

0 commit comments

Comments
 (0)