UNPKG

@fnlb-project/stanza

Version:

Modern XMPP in the browser, with a JSON API

275 lines (274 loc) 7.89 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.escapeLocal = escapeLocal; exports.unescapeLocal = unescapeLocal; exports.prepare = prepare; exports.create = create; exports.createFull = createFull; exports.parse = parse; exports.allowedResponders = allowedResponders; exports.equal = equal; exports.equalBare = equalBare; exports.isFull = isFull; exports.isBare = isBare; exports.getLocal = getLocal; exports.getDomain = getDomain; exports.getResource = getResource; exports.toBare = toBare; exports.parseURI = parseURI; exports.toURI = toURI; const stringprep_1 = require("./lib/stringprep"); function escapeLocal(val = '') { return val .replace(/^\s+|\s+$/g, '') .replace(/\\5c/g, '\\5c5c') .replace(/\\20/g, '\\5c20') .replace(/\\22/g, '\\5c22') .replace(/\\26/g, '\\5c26') .replace(/\\27/g, '\\5c27') .replace(/\\2f/g, '\\5c2f') .replace(/\\3a/g, '\\5c3a') .replace(/\\3c/g, '\\5c3c') .replace(/\\3e/g, '\\5c3e') .replace(/\\40/g, '\\5c40') .replace(/ /g, '\\20') .replace(/"/g, '\\22') .replace(/&/g, '\\26') .replace(/'/g, '\\27') .replace(/\//g, '\\2f') .replace(/:/g, '\\3a') .replace(/</g, '\\3c') .replace(/>/g, '\\3e') .replace(/@/g, '\\40'); } function unescapeLocal(val) { return val .replace(/\\20/g, ' ') .replace(/\\22/g, '"') .replace(/\\26/g, '&') .replace(/\\27/g, `'`) .replace(/\\2f/g, '/') .replace(/\\3a/g, ':') .replace(/\\3c/g, '<') .replace(/\\3e/g, '>') .replace(/\\40/g, '@') .replace(/\\5c/g, '\\'); } function prepare(data) { let local = data.local || ''; let domain = data.domain; let resource = data.resource || ''; if (local) { local = (0, stringprep_1.nodeprep)(local); } if (resource) { resource = (0, stringprep_1.resourceprep)(resource); } if (domain[domain.length - 1] === '.') { domain = domain.slice(0, domain.length - 1); } domain = (0, stringprep_1.nameprep)(domain.split('.').join('.')); return { domain, local, resource }; } function create(data, opts = {}) { let localPart = data.local; if (!opts.escaped) { localPart = escapeLocal(data.local); } const prep = !opts.prepared ? prepare({ local: localPart, domain: data.domain, resource: data.resource }) : data; const bareJID = localPart ? `${localPart}@${prep.domain}` : prep.domain; if (prep.resource) { return `${bareJID}/${prep.resource}`; } return bareJID; } function createFull(bare, resource) { if (resource) { return `${toBare(bare)}/${resource}`; } else { return toBare(bare); } } function parse(jid = '') { let local = ''; let domain = ''; let resource = ''; const resourceStart = jid.indexOf('/'); if (resourceStart > 0) { resource = jid.slice(resourceStart + 1); jid = jid.slice(0, resourceStart); } const localEnd = jid.indexOf('@'); if (localEnd > 0) { local = jid.slice(0, localEnd); jid = jid.slice(localEnd + 1); } domain = jid; const prepped = prepare({ domain, local, resource }); return { bare: create({ local: prepped.local, domain: prepped.domain }, { escaped: true, prepared: true }), domain: prepped.domain, full: create(prepped, { escaped: true, prepared: true }), local: unescapeLocal(prepped.local), resource: prepped.resource }; } function allowedResponders(jid1, jid2) { const allowed = new Set(); allowed.add(undefined); allowed.add(''); if (jid1) { const split1 = parse(jid1); allowed.add(split1.full); allowed.add(split1.bare); allowed.add(split1.domain); } if (jid2) { const split2 = parse(jid2); allowed.add(split2.domain); allowed.add(split2.bare); allowed.add(split2.full); } return allowed; } function equal(jid1, jid2) { if (!jid1 || !jid2) { return false; } const parsed1 = parse(jid1); const parsed2 = parse(jid2); return (parsed1.local === parsed2.local && parsed1.domain === parsed2.domain && parsed1.resource === parsed2.resource); } function equalBare(jid1, jid2) { if (!jid1 || !jid2) { return false; } const parsed1 = parse(jid1); const parsed2 = parse(jid2); return parsed1.local === parsed2.local && parsed1.domain === parsed2.domain; } function isFull(jid) { const parsed = parse(jid); return !!parsed.resource; } function isBare(jid) { return !isFull(jid); } function getLocal(jid = '') { return parse(jid).local; } function getDomain(jid = '') { return parse(jid).domain; } function getResource(jid = '') { return parse(jid).resource; } function toBare(jid = '') { return parse(jid).bare; } function parseURI(val) { const parsed = new URL(val); if (parsed.protocol !== 'xmpp:') { throw new Error('Invalid XMPP URI, wrong protocol: ' + parsed.protocol); } const identity = parsed.hostname ? parsed.username ? create({ domain: decodeURIComponent(parsed.hostname), local: decodeURIComponent(parsed.username) }, { escaped: true }) : decodeURIComponent(parsed.hostname) : undefined; const jid = parse(decodeURIComponent(identity ? parsed.pathname.substr(1) : parsed.pathname)) .full; const hasParameters = parsed.search && parsed.search.indexOf(';') >= 1; const parameterString = hasParameters ? parsed.search.substr(parsed.search.indexOf(';') + 1) : ''; const action = parsed.search ? decodeURIComponent(parsed.search.substr(1, hasParameters ? parsed.search.indexOf(';') - 1 : undefined)) : undefined; const params = {}; for (const token of parameterString.split(';')) { const [name, value] = token.split('=').map(decodeURIComponent); if (!params[name]) { params[name] = value; } else { const existing = params[name]; if (Array.isArray(existing)) { existing.push(value); } else { params[name] = [existing, value]; } } } return { action, identity, jid, parameters: params }; } function toURI(data) { const parts = ['xmpp:']; const pushJID = (jid, allowResource) => { const res = parse(jid); if (res.local) { parts.push(encodeURIComponent(escapeLocal(res.local))); parts.push('@'); } parts.push(encodeURIComponent(res.domain)); if (allowResource && res.resource) { parts.push('/'); parts.push(encodeURIComponent(res.resource)); } }; if (data.identity) { parts.push('//'); pushJID(data.identity, false); if (data.jid) { parts.push('/'); } } if (data.jid) { pushJID(data.jid, true); } if (data.action) { parts.push('?'); parts.push(encodeURIComponent(data.action)); } for (const [name, values] of Object.entries(data.parameters || {})) { for (const val of Array.isArray(values) ? values : [values]) { parts.push(';'); parts.push(encodeURIComponent(name)); if (val !== undefined) { parts.push('='); parts.push(encodeURIComponent(val)); } } } return parts.join(''); }