'use strict'; // 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$1 = '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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; } } // created 2024-09-13T06:42:45.675Z // compressed base64-encoded blob for include-nf 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: a79d5f9b1879a7b416aa659f4a3d788f80a8cf5f0ab955a456592c02f556a28c var COMPRESSED = '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'; // https://unicode.org/reports/tr15/ // for reference implementation // see: /derive/nf.js // algorithmic hangul // https://www.unicode.org/versions/Unicode15.0.0/ch03.pdf (page 144) const S0 = 0xAC00; const L0 = 0x1100; const V0 = 0x1161; const T0 = 0x11A7; const L_COUNT = 19; const V_COUNT = 21; const T_COUNT = 28; const N_COUNT = V_COUNT * T_COUNT; const S_COUNT = L_COUNT * N_COUNT; const S1 = S0 + S_COUNT; const L1 = L0 + L_COUNT; const V1 = V0 + V_COUNT; const T1 = T0 + T_COUNT; function unpack_cc(packed) { return (packed >> 24) & 0xFF; } function unpack_cp(packed) { return packed & 0xFFFFFF; } let SHIFTED_RANK, EXCLUSIONS, DECOMP, RECOMP; // export function nf_deinit() { // if (!SHIFTED_RANK) return; // SHIFTED_RANK = EXCLUSIONS = DECOMP = RECOMP = undefined; // } function init$1() { //console.time('nf'); let r = read_compressed_payload(COMPRESSED); SHIFTED_RANK = new Map(read_sorted_arrays(r).flatMap((v, i) => v.map(x => [x, (i+1) << 24]))); // pre-shifted EXCLUSIONS = new Set(read_sorted(r)); DECOMP = new Map(); RECOMP = new Map(); for (let [cp, cps] of read_mapped(r)) { if (!EXCLUSIONS.has(cp) && cps.length == 2) { let [a, b] = cps; let bucket = RECOMP.get(a); if (!bucket) { bucket = new Map(); RECOMP.set(a, bucket); } bucket.set(b, cp); } DECOMP.set(cp, cps.reverse()); // stored reversed } //console.timeEnd('nf'); // 20230905: 11ms } function is_hangul(cp) { return cp >= S0 && cp < S1; } function compose_pair(a, b) { if (a >= L0 && a < L1 && b >= V0 && b < V1) { return S0 + (a - L0) * N_COUNT + (b - V0) * T_COUNT; } else if (is_hangul(a) && b > T0 && b < T1 && (a - S0) % T_COUNT == 0) { return a + (b - T0); } else { let recomp = RECOMP.get(a); if (recomp) { recomp = recomp.get(b); if (recomp) { return recomp; } } return -1; } } function decomposed(cps) { if (!SHIFTED_RANK) init$1(); let ret = []; let buf = []; let check_order = false; function add(cp) { let cc = SHIFTED_RANK.get(cp); if (cc) { check_order = true; cp |= cc; } ret.push(cp); } for (let cp of cps) { while (true) { if (cp < 0x80) { ret.push(cp); } else if (is_hangul(cp)) { let s_index = cp - S0; let l_index = s_index / N_COUNT | 0; let v_index = (s_index % N_COUNT) / T_COUNT | 0; let t_index = s_index % T_COUNT; add(L0 + l_index); add(V0 + v_index); if (t_index > 0) add(T0 + t_index); } else { let mapped = DECOMP.get(cp); if (mapped) { buf.push(...mapped); } else { add(cp); } } if (!buf.length) break; cp = buf.pop(); } } if (check_order && ret.length > 1) { let prev_cc = unpack_cc(ret[0]); for (let i = 1; i < ret.length; i++) { let cc = unpack_cc(ret[i]); if (cc == 0 || prev_cc <= cc) { prev_cc = cc; continue; } let j = i-1; while (true) { let tmp = ret[j+1]; ret[j+1] = ret[j]; ret[j] = tmp; if (!j) break; prev_cc = unpack_cc(ret[--j]); if (prev_cc <= cc) break; } prev_cc = unpack_cc(ret[i]); } } return ret; } function composed_from_decomposed(v) { let ret = []; let stack = []; let prev_cp = -1; let prev_cc = 0; for (let packed of v) { let cc = unpack_cc(packed); let cp = unpack_cp(packed); if (prev_cp == -1) { if (cc == 0) { prev_cp = cp; } else { ret.push(cp); } } else if (prev_cc > 0 && prev_cc >= cc) { if (cc == 0) { ret.push(prev_cp, ...stack); stack.length = 0; prev_cp = cp; } else { stack.push(cp); } prev_cc = cc; } else { let composed = compose_pair(prev_cp, cp); if (composed >= 0) { prev_cp = composed; } else if (prev_cc == 0 && cc == 0) { ret.push(prev_cp); prev_cp = cp; } else { stack.push(cp); prev_cc = cc; } } } if (prev_cp >= 0) { ret.push(prev_cp, ...stack); } return ret; } // note: cps can be iterable function nfd(cps) { return decomposed(cps).map(unpack_cp); } function nfc(cps) { return composed_from_decomposed(decomposed(cps)); } 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$1); 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 codepoints // returns the longest valid emoji sequence (or undefined if no match) // *MUTATES* the supplied array // disallows interleaved ignored characters // fills (optional) eaten array with matched codepoints function consume_emoji_reversed(cps, eaten) { let node = EMOJI_ROOT; let emoji; let pos = cps.length; while (pos) { node = node.get(cps[--pos]); if (!node) break; let {V} = node; if (V) { // this is a valid emoji (so far) emoji = V; if (eaten) eaten.push(...cps.slice(pos).reverse()); // (optional) copy input, used for ens_tokenize() cps.length = pos; // truncate } } return emoji; } // ************************************************************ // tokenizer const TY_VALID = 'valid'; const TY_MAPPED = 'mapped'; const TY_IGNORED = 'ignored'; const TY_DISALLOWED = 'disallowed'; const TY_EMOJI = 'emoji'; const TY_NFC = 'nfc'; const TY_STOP = 'stop'; function ens_tokenize(name, { nf = true, // collapse unnormalized runs into a single token } = {}) { init(); let input = explode_cp(name).reverse(); let eaten = []; let tokens = []; while (input.length) { let emoji = consume_emoji_reversed(input, eaten); if (emoji) { tokens.push({ type: TY_EMOJI, emoji: emoji.slice(), // copy emoji input: eaten, cps: filter_fe0f(emoji) }); eaten = []; // reset buffer } else { let cp = input.pop(); if (cp == STOP) { tokens.push({type: TY_STOP, cp}); } else if (VALID.has(cp)) { tokens.push({type: TY_VALID, cps: [cp]}); } else if (IGNORED.has(cp)) { tokens.push({type: TY_IGNORED, cp}); } else { let cps = MAPPED.get(cp); if (cps) { tokens.push({type: TY_MAPPED, cp, cps: cps.slice()}); } else { tokens.push({type: TY_DISALLOWED, cp}); } } } } if (nf) { for (let i = 0, start = -1; i < tokens.length; i++) { let token = tokens[i]; if (is_valid_or_mapped(token.type)) { if (requires_check(token.cps)) { // normalization might be needed let end = i + 1; for (let pos = end; pos < tokens.length; pos++) { // find adjacent text let {type, cps} = tokens[pos]; if (is_valid_or_mapped(type)) { if (!requires_check(cps)) break; end = pos + 1; } else if (type !== TY_IGNORED) { // || type !== TY_DISALLOWED) { break; } } if (start < 0) start = i; let slice = tokens.slice(start, end); let cps0 = slice.flatMap(x => is_valid_or_mapped(x.type) ? x.cps : []); // strip junk tokens let cps = nfc(cps0); if (compare_arrays(cps, cps0)) { // bundle into an nfc token tokens.splice(start, end - start, { type: TY_NFC, input: cps0, // there are 3 states: tokens0 ==(process)=> input ==(nfc)=> tokens/cps cps, tokens0: collapse_valid_tokens(slice), tokens: ens_tokenize(str_from_cps(cps), {nf: false}) }); i = start; } else { i = end - 1; // skip to end of slice } start = -1; // reset } else { start = i; // remember last } } else if (token.type !== TY_IGNORED) { // 20221024: is this correct? start = -1; // reset } } } return collapse_valid_tokens(tokens); } function is_valid_or_mapped(type) { return type == TY_VALID || type == TY_MAPPED; } function requires_check(cps) { return cps.some(cp => NFC_CHECK.has(cp)); } function collapse_valid_tokens(tokens) { for (let i = 0; i < tokens.length; i++) { if (tokens[i].type == TY_VALID) { let j = i + 1; while (j < tokens.length && tokens[j].type == TY_VALID) j++; tokens.splice(i, j - i, {type: TY_VALID, cps: tokens.slice(i, j).flatMap(x => x.cps)}); } } return tokens; } exports.ens_beautify = ens_beautify; exports.ens_emoji = ens_emoji; exports.ens_normalize = ens_normalize; exports.ens_normalize_fragment = ens_normalize_fragment; exports.ens_split = ens_split; exports.ens_tokenize = ens_tokenize; exports.is_combining_mark = is_combining_mark; exports.nfc = nfc; exports.nfd = nfd; exports.safe_str_from_cps = safe_str_from_cps; exports.should_escape = should_escape;