Files
node/test/parallel/test-buffer-islatin1.js
James M Snell 8f43e8884b buffer: add isLatin1
Implements a fast check to determine if a string is
a valid byte string (only chars <= 0xff).

Signed-off-by: James M Snell <jasnell@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/66298
Reviewed-By: René <contact.9a5d6388@renegade334.me.uk>
Reviewed-By: Filip Skokan <panva.ip@gmail.com>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Robert Nagy <ronagy@icloud.com>
2026-09-28 04:22:37 +00:00

86 lines
3.0 KiB
JavaScript

'use strict';
require('../common');
const assert = require('assert');
const { isLatin1 } = require('buffer');
function reference(str) {
for (let i = 0; i < str.length; i++) {
if (str.charCodeAt(i) > 0xFF) return false;
}
return true;
}
// Basic cases.
assert.strictEqual(isLatin1(''), true);
assert.strictEqual(isLatin1('hello'), true);
assert.strictEqual(isLatin1('\x00'), true);
assert.strictEqual(isLatin1('\x7f\x80'), true);
assert.strictEqual(isLatin1('caf\u00e9'), true);
assert.strictEqual(isLatin1('\u00ff'), true);
assert.strictEqual(isLatin1('\u0100'), false);
assert.strictEqual(isLatin1('\u20ac'), false);
assert.strictEqual(isLatin1('\uffff'), false);
// Surrogate pairs and lone surrogates are > 0xFF.
assert.strictEqual(isLatin1('\ud83d\ude00'), false);
assert.strictEqual(isLatin1('\ud800'), false);
assert.strictEqual(isLatin1('\udfff'), false);
// Position of the offending code unit must not matter, and long strings must
// exercise the vectorized paths.
for (const len of [1, 7, 8, 15, 16, 31, 32, 33, 63, 64, 65, 1000, 4099]) {
const base = 'a\u00ff'.repeat(len).slice(0, len);
assert.strictEqual(isLatin1(base), true);
for (const pos of [0, len >> 1, len - 1]) {
for (const ch of ['\u0100', '\u1234', '\ud800', '\uffff']) {
const str = base.slice(0, pos) + ch + base.slice(pos + 1);
assert.strictEqual(isLatin1(str), false, `len=${len} pos=${pos}`);
}
}
}
// Strings stored with a two-byte representation that only contain code units
// <= 0xFF must still be reported as latin1.
{
const twoByte = '\u0100' + 'abc\u00e9\u00ff'.repeat(100);
const sliced = twoByte.slice(1);
assert.strictEqual(isLatin1(twoByte), false);
assert.strictEqual(isLatin1(sliced), true);
assert.strictEqual(isLatin1(twoByte.substring(1, 20)), true);
}
// Cons strings (results of concatenation) with mixed representations.
{
let cons = '';
for (let i = 0; i < 100; i++) cons += `x${i}\u00e9`;
assert.strictEqual(isLatin1(cons), true);
assert.strictEqual(isLatin1(cons + '\u0100'), false);
assert.strictEqual(isLatin1('\u0100' + cons), false);
assert.strictEqual(isLatin1(cons + '\u0100'.slice(1) + cons), true);
}
// Randomized comparison with the reference implementation.
for (let i = 0; i < 1000; i++) {
const len = Math.floor(Math.random() * 100);
const max = Math.random() < 0.5 ? 0x100 : 0x10000;
let str = '';
for (let j = 0; j < len; j++) {
// Keep the probability of producing a code unit > 0xFF low so that
// both outcomes are exercised.
const code = Math.random() < 0.98 ?
Math.floor(Math.random() * 0x100) :
Math.floor(Math.random() * max);
str += String.fromCharCode(code);
}
assert.strictEqual(isLatin1(str), reference(str), JSON.stringify(str));
}
// Invalid argument types.
[
undefined, null, 1, 1n, true, {}, [], Symbol('a'),
Buffer.from('a'), new Uint8Array(1), new ArrayBuffer(1),
new String('a'),
].forEach((input) => {
assert.throws(() => isLatin1(input), { code: 'ERR_INVALID_ARG_TYPE' });
});