@adraffy/ens-normalize
Version:
Ethereum Name Service (ENS) Name Normalizer
941 lines (873 loc) • 53.9 kB
JavaScript
// created 2024-09-13T06:42:45.675Z
// compressed base64-encoded blob for include-ens data
// source: https://github.com/adraffy/ens-normalize.js/blob/main/src/make.js
// see: https://github.com/adraffy/ens-normalize.js#security
// SHA-256: 0ca3917bee1eee342d64c631cb1dbaac37b167d848887d59f6d68328dc99ac09
var COMPRESSED = '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';
const FENCED = new Map([[8217,"apostrophe"],[8260,"fraction slash"],[12539,"middle dot"]]);
const NSM_MAX = 4;
function decode_arithmetic(bytes) {
let pos = 0;
function u16() { return (bytes[pos++] << 8) | bytes[pos++]; }
// decode the frequency table
let symbol_count = u16();
let total = 1;
let acc = [0, 1]; // first symbol has frequency 1
for (let i = 1; i < symbol_count; i++) {
acc.push(total += u16());
}
// skip the sized-payload that the last 3 symbols index into
let skip = u16();
let pos_payload = pos;
pos += skip;
let read_width = 0;
let read_buffer = 0;
function read_bit() {
if (read_width == 0) {
// this will read beyond end of buffer
// but (undefined|0) => zero pad
read_buffer = (read_buffer << 8) | bytes[pos++];
read_width = 8;
}
return (read_buffer >> --read_width) & 1;
}
const N = 31;
const FULL = 2**N;
const HALF = FULL >>> 1;
const QRTR = HALF >> 1;
const MASK = FULL - 1;
// fill register
let register = 0;
for (let i = 0; i < N; i++) register = (register << 1) | read_bit();
let symbols = [];
let low = 0;
let range = FULL; // treat like a float
while (true) {
let value = Math.floor((((register - low + 1) * total) - 1) / range);
let start = 0;
let end = symbol_count;
while (end - start > 1) { // binary search
let mid = (start + end) >>> 1;
if (value < acc[mid]) {
end = mid;
} else {
start = mid;
}
}
if (start == 0) break; // first symbol is end mark
symbols.push(start);
let a = low + Math.floor(range * acc[start] / total);
let b = low + Math.floor(range * acc[start+1] / total) - 1;
while (((a ^ b) & HALF) == 0) {
register = (register << 1) & MASK | read_bit();
a = (a << 1) & MASK;
b = (b << 1) & MASK | 1;
}
while (a & ~b & QRTR) {
register = (register & HALF) | ((register << 1) & (MASK >>> 1)) | read_bit();
a = (a << 1) ^ HALF;
b = ((b ^ HALF) << 1) | HALF | 1;
}
low = a;
range = 1 + b - a;
}
let offset = symbol_count - 4;
return symbols.map(x => { // index into payload
switch (x - offset) {
case 3: return offset + 0x10100 + ((bytes[pos_payload++] << 16) | (bytes[pos_payload++] << 8) | bytes[pos_payload++]);
case 2: return offset + 0x100 + ((bytes[pos_payload++] << 8) | bytes[pos_payload++]);
case 1: return offset + bytes[pos_payload++];
default: return x - 1;
}
});
}
// returns an iterator which returns the next symbol
function read_payload(v) {
let pos = 0;
return () => v[pos++];
}
function read_compressed_payload(s) {
return read_payload(decode_arithmetic(unsafe_atob(s)));
}
// unsafe in the sense:
// expected well-formed Base64 w/o padding
// 20220922: added for https://github.com/adraffy/ens-normalize.js/issues/4
function unsafe_atob(s) {
let lookup = [];
[...'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'].forEach((c, i) => lookup[c.charCodeAt(0)] = i);
let n = s.length;
let ret = new Uint8Array((6 * n) >> 3);
for (let i = 0, pos = 0, width = 0, carry = 0; i < n; i++) {
carry = (carry << 6) | lookup[s.charCodeAt(i)];
width += 6;
if (width >= 8) {
ret[pos++] = (carry >> (width -= 8));
}
}
return ret;
}
// eg. [0,1,2,3...] => [0,-1,1,-2,...]
function signed(i) {
return (i & 1) ? (~i >> 1) : (i >> 1);
}
function read_deltas(n, next) {
let v = Array(n);
for (let i = 0, x = 0; i < n; i++) v[i] = x += signed(next());
return v;
}
// [123][5] => [0 3] [1 1] [0 0]
function read_sorted(next, prev = 0) {
let ret = [];
while (true) {
let x = next();
let n = next();
if (!n) break;
prev += x;
for (let i = 0; i < n; i++) {
ret.push(prev + i);
}
prev += n + 1;
}
return ret;
}
function read_sorted_arrays(next) {
return read_array_while(() => {
let v = read_sorted(next);
if (v.length) return v;
});
}
// returns map of x => ys
function read_mapped(next) {
let ret = [];
while (true) {
let w = next();
if (w == 0) break;
ret.push(read_linear_table(w, next));
}
while (true) {
let w = next() - 1;
if (w < 0) break;
ret.push(read_replacement_table(w, next));
}
return ret.flat();
}
// read until next is falsy
// return array of read values
function read_array_while(next) {
let v = [];
while (true) {
let x = next(v.length);
if (!x) break;
v.push(x);
}
return v;
}
// read w columns of length n
// return as n rows of length w
function read_transposed(n, w, next) {
let m = Array(n).fill().map(() => []);
for (let i = 0; i < w; i++) {
read_deltas(n, next).forEach((x, j) => m[j].push(x));
}
return m;
}
// returns [[x, ys], [x+dx, ys+dy], [x+2*dx, ys+2*dy], ...]
// where dx/dy = steps, n = run size, w = length of y
function read_linear_table(w, next) {
let dx = 1 + next();
let dy = next();
let vN = read_array_while(next);
let m = read_transposed(vN.length, 1+w, next);
return m.flatMap((v, i) => {
let [x, ...ys] = v;
return Array(vN[i]).fill().map((_, j) => {
let j_dy = j * dy;
return [x + j * dx, ys.map(y => y + j_dy)];
});
});
}
// return [[x, ys...], ...]
// where w = length of y
function read_replacement_table(w, next) {
let n = 1 + next();
let m = read_transposed(n, 1+w, next);
return m.map(v => [v[0], v.slice(1)]);
}
function read_trie(next) {
let ret = [];
let sorted = read_sorted(next);
expand(decode([]), []);
return ret; // not sorted
function decode(Q) { // characters that lead into this node
let S = next(); // state: valid, save, check
let B = read_array_while(() => { // buckets leading to new nodes
let cps = read_sorted(next).map(i => sorted[i]);
if (cps.length) return decode(cps);
});
return {S, B, Q};
}
function expand({S, B}, cps, saved) {
if (S & 4 && saved === cps[cps.length-1]) return;
if (S & 2) saved = cps[cps.length-1];
if (S & 1) ret.push(cps);
for (let br of B) {
for (let cp of br.Q) {
expand(br, [...cps, cp], saved);
}
}
}
}
function hex_cp(cp) {
return cp.toString(16).toUpperCase().padStart(2, '0');
}
function quote_cp(cp) {
return `{${hex_cp(cp)}}`; // raffy convention: like "\u{X}" w/o the "\u"
}
/*
export function explode_cp(s) {
return [...s].map(c => c.codePointAt(0));
}
*/
function explode_cp(s) { // this is about 2x faster
let cps = [];
for (let pos = 0, len = s.length; pos < len; ) {
let cp = s.codePointAt(pos);
pos += cp < 0x10000 ? 1 : 2;
cps.push(cp);
}
return cps;
}
function str_from_cps(cps) {
const chunk = 4096;
let len = cps.length;
if (len < chunk) return String.fromCodePoint(...cps);
let buf = [];
for (let i = 0; i < len; ) {
buf.push(String.fromCodePoint(...cps.slice(i, i += chunk)));
}
return buf.join('');
}
function compare_arrays(a, b) {
let n = a.length;
let c = n - b.length;
for (let i = 0; c == 0 && i < n; i++) c = a[i] - b[i];
return c;
}
function array_replace(v, a, b) {
let prev = 0;
while (true) {
let next = v.indexOf(a, prev);
if (next < 0) break;
v[next] = b;
prev = next + 1;
}
}
// reverse polyfill
// replace custom nf implementation with system implementation
// (saves approximately 6KB)
function nf(cps, form) {
return explode_cp(str_from_cps(cps).normalize(form));
}
function nfc(cps) {
return nf(cps, 'NFC');
}
function nfd(cps) {
return nf(cps, 'NFD');
}
const HYPHEN = 0x2D;
const STOP = 0x2E;
const STOP_CH = '.';
const FE0F = 0xFE0F;
const UNIQUE_PH = 1;
// 20230913: replace [...v] with Array_from(v) to avoid large spreads
const Array_from = x => Array.from(x); // Array.from.bind(Array);
function group_has_cp(g, cp) {
// 20230913: keep primary and secondary distinct instead of creating valid union
return g.P.has(cp) || g.Q.has(cp);
}
class Emoji extends Array {
get is_emoji() { return true; } // free tagging system
}
let MAPPED, IGNORED, CM, NSM, ESCAPE, NFC_CHECK, GROUPS, WHOLE_VALID, WHOLE_MAP, VALID, EMOJI_LIST, EMOJI_ROOT;
// export function ens_deinit() {
// nf_deinit();
// if (!MAPPED) return;
// MAPPED = IGNORED = CM = NSM = ESCAPE = NFC_CHECK = GROUPS = WHOLE_VALID = WHOLE_MAP = VALID = EMOJI_LIST = EMOJI_ROOT = undefined;
// }
function init() {
if (MAPPED) return;
let r = read_compressed_payload(COMPRESSED);
const read_sorted_array = () => read_sorted(r);
const read_sorted_set = () => new Set(read_sorted_array());
const set_add_many = (set, v) => v.forEach(x => set.add(x));
MAPPED = new Map(read_mapped(r));
IGNORED = read_sorted_set(); // ignored characters are not valid, so just read raw codepoints
/*
// direct include from payload is smaller than the decompression code
const FENCED = new Map(read_array_while(() => {
let cp = r();
if (cp) return [cp, read_str(r())];
}));
*/
// 20230217: we still need all CM for proper error formatting
// but norm only needs NSM subset that are potentially-valid
CM = read_sorted_array();
NSM = new Set(read_sorted_array().map(i => CM[i]));
CM = new Set(CM);
ESCAPE = read_sorted_set(); // characters that should not be printed
NFC_CHECK = read_sorted_set(); // only needed to illustrate ens_tokenize() transformations
let chunks = read_sorted_arrays(r);
let unrestricted = r();
//const read_chunked = () => new Set(read_sorted_array().flatMap(i => chunks[i]).concat(read_sorted_array()));
const read_chunked = () => {
// 20230921: build set in parts, 2x faster
let set = new Set();
read_sorted_array().forEach(i => set_add_many(set, chunks[i]));
set_add_many(set, read_sorted_array());
return set;
};
GROUPS = read_array_while(i => {
// minifier property mangling seems unsafe
// so these are manually renamed to single chars
let N = read_array_while(r).map(x => x+0x60);
if (N.length) {
let R = i >= unrestricted; // unrestricted then restricted
N[0] -= 32; // capitalize
N = str_from_cps(N);
if (R) N=`Restricted[${N}]`;
let P = read_chunked(); // primary
let Q = read_chunked(); // secondary
let M = !r(); // not-whitelisted, check for NSM
// *** this code currently isn't needed ***
/*
let V = [...P, ...Q].sort((a, b) => a-b); // derive: sorted valid
let M = r()-1; // number of combining mark
if (M < 0) { // whitelisted
M = new Map(read_array_while(() => {
let i = r();
if (i) return [V[i-1], read_array_while(() => {
let v = read_array_while(r);
if (v.length) return v.map(x => x-1);
})];
}));
}*/
return {N, P, Q, M, R};
}
});
// decode compressed wholes
WHOLE_VALID = read_sorted_set();
WHOLE_MAP = new Map();
let wholes = read_sorted_array().concat(Array_from(WHOLE_VALID)).sort((a, b) => a-b); // must be sorted
wholes.forEach((cp, i) => {
let d = r();
let w = wholes[i] = d ? wholes[i-d] : {V: [], M: new Map()};
w.V.push(cp); // add to member set
if (!WHOLE_VALID.has(cp)) {
WHOLE_MAP.set(cp, w); // register with whole map
}
});
// compute confusable-extent complements
// usage: WHOLE_MAP.get(cp).M.get(cp) = complement set
for (let {V, M} of new Set(WHOLE_MAP.values())) {
// connect all groups that have each whole character
let recs = [];
for (let cp of V) {
let gs = GROUPS.filter(g => group_has_cp(g, cp));
let rec = recs.find(({G}) => gs.some(g => G.has(g)));
if (!rec) {
rec = {G: new Set(), V: []};
recs.push(rec);
}
rec.V.push(cp);
set_add_many(rec.G, gs);
}
// per character cache groups which are not a member of the extent
let union = recs.flatMap(x => Array_from(x.G)); // all of the groups used by this whole
for (let {G, V} of recs) {
let complement = new Set(union.filter(g => !G.has(g))); // groups not covered by the extent
for (let cp of V) {
M.set(cp, complement); // this is the same reference
}
}
}
// compute valid set
// 20230924: VALID was union but can be re-used
VALID = new Set(); // exists in 1+ groups
let multi = new Set(); // exists in 2+ groups
const add_to_union = cp => VALID.has(cp) ? multi.add(cp) : VALID.add(cp);
for (let g of GROUPS) {
for (let cp of g.P) add_to_union(cp);
for (let cp of g.Q) add_to_union(cp);
}
// dual purpose WHOLE_MAP: return placeholder if unique non-confusable
for (let cp of VALID) {
if (!WHOLE_MAP.has(cp) && !multi.has(cp)) {
WHOLE_MAP.set(cp, UNIQUE_PH);
}
}
// add all decomposed parts
// see derive: "Valid is Closed (via Brute-force)"
set_add_many(VALID, nfd(VALID));
// decode emoji
// 20230719: emoji are now fully-expanded to avoid quirk logic
EMOJI_LIST = read_trie(r).map(v => Emoji.from(v)).sort(compare_arrays);
EMOJI_ROOT = new Map(); // this has approx 7K nodes (2+ per emoji)
for (let cps of EMOJI_LIST) {
// 20230719: change to *slightly* stricter algorithm which disallows
// insertion of misplaced FE0F in emoji sequences (matching ENSIP-15)
// example: beautified [A B] (eg. flag emoji)
// before: allow: [A FE0F B], error: [A FE0F FE0F B]
// after: error: both
// note: this code now matches ENSNormalize.{cs,java} logic
let prev = [EMOJI_ROOT];
for (let cp of cps) {
let next = prev.map(node => {
let child = node.get(cp);
if (!child) {
// should this be object?
// (most have 1-2 items, few have many)
// 20230719: no, v8 default map is 4?
child = new Map();
node.set(cp, child);
}
return child;
});
if (cp === FE0F) {
prev.push(...next); // less than 20 elements
} else {
prev = next;
}
}
for (let x of prev) {
x.V = cps;
}
}
}
// if escaped: {HEX}
// else: "x" {HEX}
function quoted_cp(cp) {
return (should_escape(cp) ? '' : `${bidi_qq(safe_str_from_cps([cp]))} `) + quote_cp(cp);
}
// 20230211: some messages can be mixed-directional and result in spillover
// use 200E after a quoted string to force the remainder of a string from
// acquring the direction of the quote
// https://www.w3.org/International/questions/qa-bidi-unicode-controls#exceptions
function bidi_qq(s) {
return `"${s}"\u200E`; // strong LTR
}
function check_label_extension(cps) {
if (cps.length >= 4 && cps[2] == HYPHEN && cps[3] == HYPHEN) {
throw new Error(`invalid label extension: "${str_from_cps(cps.slice(0, 4))}"`); // this can only be ascii so cant be bidi
}
}
function check_leading_underscore(cps) {
const UNDERSCORE = 0x5F;
for (let i = cps.lastIndexOf(UNDERSCORE); i > 0; ) {
if (cps[--i] !== UNDERSCORE) {
throw new Error('underscore allowed only at start');
}
}
}
// check that a fenced cp is not leading, trailing, or touching another fenced cp
function check_fenced(cps) {
let cp = cps[0];
let prev = FENCED.get(cp);
if (prev) throw error_placement(`leading ${prev}`);
let n = cps.length;
let last = -1; // prevents trailing from throwing
for (let i = 1; i < n; i++) {
cp = cps[i];
let match = FENCED.get(cp);
if (match) {
// since cps[0] isn't fenced, cps[1] cannot throw
if (last == i) throw error_placement(`${prev} + ${match}`);
last = i + 1;
prev = match;
}
}
if (last == n) throw error_placement(`trailing ${prev}`);
}
// create a safe to print string
// invisibles are escaped
// leading cm uses placeholder
// if cps exceed max, middle truncate with ellipsis
// quoter(cp) => string, eg. 3000 => "{3000}"
// note: in html, you'd call this function then replace [<>&] with entities
function safe_str_from_cps(cps, max = Infinity, quoter = quote_cp) {
//if (Number.isInteger(cps)) cps = [cps];
//if (!Array.isArray(cps)) throw new TypeError(`expected codepoints`);
let buf = [];
if (is_combining_mark(cps[0])) buf.push('◌');
if (cps.length > max) {
max >>= 1;
cps = [...cps.slice(0, max), 0x2026, ...cps.slice(-max)];
}
let prev = 0;
let n = cps.length;
for (let i = 0; i < n; i++) {
let cp = cps[i];
if (should_escape(cp)) {
buf.push(str_from_cps(cps.slice(prev, i)));
buf.push(quoter(cp));
prev = i + 1;
}
}
buf.push(str_from_cps(cps.slice(prev, n)));
return buf.join('');
}
// note: set(s) cannot be exposed because they can be modified
// note: Object.freeze() doesn't work
function is_combining_mark(cp, only_nsm) { // 20240127: add extra argument
init();
return only_nsm ? NSM.has(cp) : CM.has(cp);
}
function should_escape(cp) {
init();
return ESCAPE.has(cp);
}
// return all supported emoji as fully-qualified emoji
// ordered by length then lexicographic
function ens_emoji() {
init();
return EMOJI_LIST.map(x => x.slice()); // emoji are exposed so copy
}
function ens_normalize_fragment(frag, decompose) {
init();
let nf = decompose ? nfd : nfc;
return frag.split(STOP_CH).map(label => str_from_cps(tokens_from_str(explode_cp(label), nf, filter_fe0f).flat())).join(STOP_CH);
}
function ens_normalize(name) {
return flatten(split(name, nfc, filter_fe0f));
}
function ens_beautify(name) {
let labels = split(name, nfc, x => x); // emoji not exposed
for (let {type, output, error} of labels) {
if (error) break; // flatten will throw
// replace leading/trailing hyphen
// 20230121: consider beautifing all or leading/trailing hyphen to unicode variant
// not exactly the same in every font, but very similar: "-" vs "‐"
/*
const UNICODE_HYPHEN = 0x2010;
// maybe this should replace all for visual consistancy?
// `node tools/reg-count.js regex ^-\{2,\}` => 592
//for (let i = 0; i < output.length; i++) if (output[i] == 0x2D) output[i] = 0x2010;
if (output[0] == HYPHEN) output[0] = UNICODE_HYPHEN;
let end = output.length-1;
if (output[end] == HYPHEN) output[end] = UNICODE_HYPHEN;
*/
// 20230123: WHATWG URL uses "CheckHyphens" false
// https://url.spec.whatwg.org/#idna
// update ethereum symbol
// ξ => Ξ if not greek
if (type !== 'Greek') array_replace(output, 0x3BE, 0x39E);
// 20221213: fixes bidi subdomain issue, but breaks invariant (200E is disallowed)
// could be fixed with special case for: 2D (.) + 200E (LTR)
// https://discuss.ens.domains/t/bidi-label-ordering-spoof/15824
//output.splice(0, 0, 0x200E);
}
return flatten(labels);
}
function ens_split(name, preserve_emoji) {
return split(name, nfc, preserve_emoji ? x => x.slice() : filter_fe0f); // emoji are exposed so copy
}
function split(name, nf, ef) {
if (!name) return []; // 20230719: empty name allowance
init();
let offset = 0;
// https://unicode.org/reports/tr46/#Validity_Criteria
// 4.) "The label must not contain a U+002E ( . ) FULL STOP."
return name.split(STOP_CH).map(label => {
let input = explode_cp(label);
let info = {
input,
offset, // codepoint, not substring!
};
offset += input.length + 1; // + stop
try {
// 1.) "The label must be in Unicode Normalization Form NFC"
let tokens = info.tokens = tokens_from_str(input, nf, ef);
let token_count = tokens.length;
let type;
if (!token_count) { // the label was effectively empty (could of had ignored characters)
//norm = [];
//type = 'None'; // use this instead of next match, "ASCII"
// 20230120: change to strict
// https://discuss.ens.domains/t/ens-name-normalization-2nd/14564/59
throw new Error(`empty label`);
}
let norm = info.output = tokens.flat();
check_leading_underscore(norm);
let emoji = info.emoji = token_count > 1 || tokens[0].is_emoji; // same as: tokens.some(x => x.is_emoji);
if (!emoji && norm.every(cp => cp < 0x80)) { // special case for ascii
// 20230123: matches matches WHATWG, see note 3.3
check_label_extension(norm); // only needed for ascii
// cant have fenced
// cant have cm
// cant have wholes
// see derive: "Fastpath ASCII"
type = 'ASCII';
} else {
let chars = tokens.flatMap(x => x.is_emoji ? [] : x); // all of the nfc tokens concat together
if (!chars.length) { // theres no text, just emoji
type = 'Emoji';
} else {
// 5.) "The label must not begin with a combining mark, that is: General_Category=Mark."
if (CM.has(norm[0])) throw error_placement('leading combining mark');
for (let i = 1; i < token_count; i++) { // we've already checked the first token
let cps = tokens[i];
if (!cps.is_emoji && CM.has(cps[0])) { // every text token has emoji neighbors, eg. EtEEEtEt...
// bidi_qq() not needed since emoji is LTR and cps is a CM
throw error_placement(`emoji + combining mark: "${str_from_cps(tokens[i-1])} + ${safe_str_from_cps([cps[0]])}"`);
}
}
check_fenced(norm);
let unique = Array_from(new Set(chars));
let [g] = determine_group(unique); // take the first match
// see derive: "Matching Groups have Same CM Style"
// alternative: could form a hybrid type: Latin/Japanese/...
check_group(g, chars); // need text in order
check_whole(g, unique); // only need unique text (order would be required for multiple-char confusables)
type = g.N;
// 20230121: consider exposing restricted flag
// it's simpler to just check for 'Restricted'
// or even better: type.endsWith(']')
//if (g.R) info.restricted = true;
}
}
info.type = type;
} catch (err) {
info.error = err; // use full error object
}
return info;
});
}
function check_whole(group, unique) {
let maker;
let shared = [];
for (let cp of unique) {
let whole = WHOLE_MAP.get(cp);
if (whole === UNIQUE_PH) return; // unique, non-confusable
if (whole) {
let set = whole.M.get(cp); // groups which have a character that look-like this character
maker = maker ? maker.filter(g => set.has(g)) : Array_from(set);
if (!maker.length) return; // confusable intersection is empty
} else {
shared.push(cp);
}
}
if (maker) {
// we have 1+ confusable
// check if any of the remaining groups
// contain the shared characters too
for (let g of maker) {
if (shared.every(cp => group_has_cp(g, cp))) {
throw new Error(`whole-script confusable: ${group.N}/${g.N}`);
}
}
}
}
// assumption: unique.size > 0
// returns list of matching groups
function determine_group(unique) {
let groups = GROUPS;
for (let cp of unique) {
// note: we need to dodge CM that are whitelisted
// but that code isn't currently necessary
let gs = groups.filter(g => group_has_cp(g, cp));
if (!gs.length) {
if (!GROUPS.some(g => group_has_cp(g, cp))) {
// the character was composed of valid parts
// but it's NFC form is invalid
// 20230716: change to more exact statement, see: ENSNormalize.{cs,java}
// note: this doesn't have to be a composition
// 20230720: change to full check
throw error_disallowed(cp); // this should be rare
} else {
// there is no group that contains all these characters
// throw using the highest priority group that matched
// https://www.unicode.org/reports/tr39/#mixed_script_confusables
throw error_group_member(groups[0], cp);
}
}
groups = gs;
if (gs.length == 1) break; // there is only one group left
}
// there are at least 1 group(s) with all of these characters
return groups;
}
// throw on first error
function flatten(split) {
return split.map(({input, error, output}) => {
if (error) {
// don't print label again if just a single label
let msg = error.message;
// bidi_qq() only necessary if msg is digits
throw new Error(split.length == 1 ? msg : `Invalid label ${bidi_qq(safe_str_from_cps(input, 63))}: ${msg}`);
}
return str_from_cps(output);
}).join(STOP_CH);
}
function error_disallowed(cp) {
// TODO: add cp to error?
return new Error(`disallowed character: ${quoted_cp(cp)}`);
}
function error_group_member(g, cp) {
let quoted = quoted_cp(cp);
let gg = GROUPS.find(g => g.P.has(cp)); // only check primary
if (gg) {
quoted = `${gg.N} ${quoted}`;
}
return new Error(`illegal mixture: ${g.N} + ${quoted}`);
}
function error_placement(where) {
return new Error(`illegal placement: ${where}`);
}
// assumption: cps.length > 0
// assumption: cps[0] isn't a CM
// assumption: the previous character isn't an emoji
function check_group(g, cps) {
for (let cp of cps) {
if (!group_has_cp(g, cp)) {
// for whitelisted scripts, this will throw illegal mixture on invalid cm, eg. "e{300}{300}"
// at the moment, it's unnecessary to introduce an extra error type
// until there exists a whitelisted multi-character
// eg. if (M < 0 && is_combining_mark(cp)) { ... }
// there are 3 cases:
// 1. illegal cm for wrong group => mixture error
// 2. illegal cm for same group => cm error
// requires set of whitelist cm per group:
// eg. new Set([...g.P, ...g.Q].flatMap(nfc).filter(cp => CM.has(cp)))
// 3. wrong group => mixture error
throw error_group_member(g, cp);
}
}
//if (M >= 0) { // we have a known fixed cm count
if (g.M) { // we need to check for NSM
let decomposed = nfd(cps);
for (let i = 1, e = decomposed.length; i < e; i++) { // see: assumption
// 20230210: bugfix: using cps instead of decomposed h/t Carbon225
/*
if (CM.has(decomposed[i])) {
let j = i + 1;
while (j < e && CM.has(decomposed[j])) j++;
if (j - i > M) {
throw new Error(`too many combining marks: ${g.N} ${bidi_qq(str_from_cps(decomposed.slice(i-1, j)))} (${j-i}/${M})`);
}
i = j;
}
*/
// 20230217: switch to NSM counting
// https://www.unicode.org/reports/tr39/#Optional_Detection
if (NSM.has(decomposed[i])) {
let j = i + 1;
for (let cp; j < e && NSM.has(cp = decomposed[j]); j++) {
// a. Forbid sequences of the same nonspacing mark.
for (let k = i; k < j; k++) { // O(n^2) but n < 100
if (decomposed[k] == cp) {
throw new Error(`duplicate non-spacing marks: ${quoted_cp(cp)}`);
}
}
}
// parse to end so we have full nsm count
// b. Forbid sequences of more than 4 nonspacing marks (gc=Mn or gc=Me).
if (j - i > NSM_MAX) {
// note: this slice starts with a base char or spacing-mark cm
throw new Error(`excessive non-spacing marks: ${bidi_qq(safe_str_from_cps(decomposed.slice(i-1, j)))} (${j-i}/${NSM_MAX})`);
}
i = j;
}
}
}
// *** this code currently isn't needed ***
/*
let cm_whitelist = M instanceof Map;
for (let i = 0, e = cps.length; i < e; ) {
let cp = cps[i++];
let seqs = cm_whitelist && M.get(cp);
if (seqs) {
// list of codepoints that can follow
// if this exists, this will always be 1+
let j = i;
while (j < e && CM.has(cps[j])) j++;
let cms = cps.slice(i, j);
let match = seqs.find(seq => !compare_arrays(seq, cms));
if (!match) throw new Error(`disallowed combining mark sequence: "${safe_str_from_cps([cp, ...cms])}"`);
i = j;
} else if (!V.has(cp)) {
// https://www.unicode.org/reports/tr39/#mixed_script_confusables
let quoted = quoted_cp(cp);
for (let cp of cps) {
let u = UNIQUE.get(cp);
if (u && u !== g) {
// if both scripts are restricted this error is confusing
// because we don't differentiate RestrictedA from RestrictedB
if (!u.R) quoted = `${quoted} is ${u.N}`;
break;
}
}
throw new Error(`disallowed ${g.N} character: ${quoted}`);
//throw new Error(`disallowed character: ${quoted} (expected ${g.N})`);
//throw new Error(`${g.N} does not allow: ${quoted}`);
}
}
if (!cm_whitelist) {
let decomposed = nfd(cps);
for (let i = 1, e = decomposed.length; i < e; i++) { // we know it can't be cm leading
if (CM.has(decomposed[i])) {
let j = i + 1;
while (j < e && CM.has(decomposed[j])) j++;
if (j - i > M) {
throw new Error(`too many combining marks: "${str_from_cps(decomposed.slice(i-1, j))}" (${j-i}/${M})`);
}
i = j;
}
}
}
*/
}
// given a list of codepoints
// returns a list of lists, where emoji are a fully-qualified (as Array subclass)
// eg. explode_cp("abc💩d") => [[61, 62, 63], Emoji[1F4A9, FE0F], [64]]
// 20230818: rename for 'process' name collision h/t Javarome
// https://github.com/adraffy/ens-normalize.js/issues/23
function tokens_from_str(input, nf, ef) {
let ret = [];
let chars = [];
input = input.slice().reverse(); // flip so we can pop
while (input.length) {
let emoji = consume_emoji_reversed(input);
if (emoji) {
if (chars.length) {
ret.push(nf(chars));
chars = [];
}
ret.push(ef(emoji));
} else {
let cp = input.pop();
if (VALID.has(cp)) {
chars.push(cp);
} else {
let cps = MAPPED.get(cp);
if (cps) {
chars.push(...cps); // less than 10 elements
} else if (!IGNORED.has(cp)) {
// 20230912: unicode 15.1 changed the order of processing such that
// disallowed parts are only rejected after NFC
// https://unicode.org/reports/tr46/#Validity_Criteria
// this doesn't impact normalization as of today
// technically, this error can be removed as the group logic will apply similar logic
// however the error type might be less clear
throw error_disallowed(cp);
}
}
}
}
if (chars.length) {
ret.push(nf(chars));
}
return ret;
}
function filter_fe0f(cps) {
return cps.filter(cp => cp != FE0F);
}
// given array of codepoint