UNPKG

@fnlb-project/stanza

Version:

Modern XMPP in the browser, with a JSON API

201 lines (200 loc) 7.02 kB
import { ucs2Decode, ucs2Encode } from '../../Utils'; import { TABLE_DATA } from './Tables'; export class Table { constructor(name, points) { this.singles = new Set(); this.ranges = []; this.mappings = new Map(); const data = TABLE_DATA[name]; this.name = name; if (data) { if (data.s) { this.singles = new Set(data.s.split('|').map(s => parseInt(s, 32))); } if (data.r) { this.ranges = data.r.split('|').map(r => { const [start, end] = r.split(':'); return [parseInt(start, 32), parseInt(end, 32)]; }); } if (data.m) { this.mappings = new Map(data.m.split('|').map(m => { const [point, mapping] = m.split(':'); const mappedPoints = mapping.split(';').map(p => parseInt(p, 32)); return [parseInt(point, 32), mappedPoints]; })); } } else if (points) { this.singles = new Set(points); } } contains(codePoint) { if (this.singles.has(codePoint)) { return true; } let left = 0; let right = this.ranges.length - 1; while (left <= right) { const pivot = Math.floor((left + right) / 2); const range = this.ranges[pivot]; if (codePoint < range[0]) { right = pivot - 1; continue; } if (codePoint > range[1]) { left = pivot + 1; continue; } return true; } return false; } hasMapping(codePoint) { return this.mappings.has(codePoint) || this.contains(codePoint); } map(codePoint) { if (this.contains(codePoint) && !this.mappings.has(codePoint)) { return String.fromCodePoint(codePoint).toLowerCase().codePointAt(0); } return this.mappings.get(codePoint) || null; } } export const A1 = new Table('A.1'); export const B1 = new Table('B.1'); export const B2 = new Table('B.2'); export const B3 = new Table('B.3'); export const C11 = new Table('C.1.1'); export const C12 = new Table('C.1.2'); export const C21 = new Table('C.2.1'); export const C22 = new Table('C.2.2'); export const C3 = new Table('C.3'); export const C4 = new Table('C.4'); export const C5 = new Table('C.5'); export const C6 = new Table('C.6'); export const C7 = new Table('C.7'); export const C8 = new Table('C.8'); export const C9 = new Table('C.9'); export const D1 = new Table('D.1'); export const D2 = new Table('D.2'); // Shortcut some of the simpler table operations B1.map = () => { return null; }; C11.contains = (codePoint) => codePoint === 32; C12.map = (codePoint) => { return C12.contains(codePoint) ? 32 : null; }; export function prepare(profile, allowUnassigned, input = '') { const inputCodePoints = ucs2Decode(input); let mappedCodePoints = []; for (const codePoint of inputCodePoints) { if (!allowUnassigned && profile.unassigned.contains(codePoint)) { throw new Error('Unassigned code point: x' + codePoint.toString(16)); } let hasMapping = false; for (const mappingTable of profile.mappings) { if (!mappingTable.hasMapping(codePoint)) { continue; } hasMapping = true; const mappedPoint = mappingTable.map(codePoint); if (!mappedPoint) { continue; } if (Array.isArray(mappedPoint)) { mappedCodePoints = mappedCodePoints.concat(mappedPoint); } else { mappedCodePoints.push(mappedPoint); } } if (!hasMapping) { mappedCodePoints.push(codePoint); } } let normalizedCodePoints = mappedCodePoints; if (profile.normalize) { const mappedString = ucs2Encode(mappedCodePoints); // Convertimos de nuevo a cadena const normalizedString = mappedString.normalize('NFKC'); normalizedCodePoints = ucs2Decode(normalizedString); } let hasRandALCat = false; let hasLCat = false; for (const codePoint of normalizedCodePoints) { for (const prohibited of profile.prohibited) { if (prohibited.contains(codePoint)) { throw new Error('Prohibited code point: x' + codePoint.toString(16)); } } if (!allowUnassigned && profile.unassigned.contains(codePoint)) { // istanbul ignore next throw new Error('Prohibited code point: x' + codePoint.toString(16)); } if (profile.bidirectional) { hasRandALCat = hasRandALCat || D1.contains(codePoint); hasLCat = hasLCat || D2.contains(codePoint); } } if (profile.bidirectional) { if (hasRandALCat && hasLCat) { throw new Error('String contained both LCat and RandALCat code points'); } if (hasRandALCat && (!D1.contains(normalizedCodePoints[0]) || !D1.contains(normalizedCodePoints[normalizedCodePoints.length - 1]))) { throw new Error('String containing RandALCat code points must start and end with RandALCat code points'); } } return ucs2Encode(normalizedCodePoints); } const NamePrepProfile = { bidirectional: true, mappings: [B1, B2], normalize: true, prohibited: [C12, C22, C3, C4, C5, C6, C7, C8, C9], unassigned: A1 }; export function nameprep(str, allowUnassigned = true) { return prepare(NamePrepProfile, allowUnassigned, str); } export const NodePrepProhibited = new Table('NodePrepProhibited', [ 0x22, 0x26, 0x27, 0x2f, 0x3a, 0x3c, 0x3e, 0x40 ]); const NodePrepProfile = { bidirectional: true, mappings: [B1, B2], normalize: true, prohibited: [C11, C12, C21, C22, C3, C4, C5, C6, C7, C8, C9, NodePrepProhibited], unassigned: A1 }; export function nodeprep(str, allowUnassigned = true) { return prepare(NodePrepProfile, allowUnassigned, str); } const ResourcePrepProfile = { bidirectional: true, mappings: [B1], normalize: true, prohibited: [C12, C21, C22, C3, C4, C5, C6, C7, C8, C9], unassigned: A1 }; export function resourceprep(str, allowUnassigned = true) { return prepare(ResourcePrepProfile, allowUnassigned, str); } const SASLPrepProfile = { bidirectional: true, mappings: [C12, B1], normalize: true, prohibited: [C12, C21, C22, C3, C4, C5, C6, C7, C8, C9], unassigned: A1 }; export function saslprep(str, allowUnassigned = false) { return prepare(SASLPrepProfile, allowUnassigned, str); }