@fnlb-project/stanza
Version:
Modern XMPP in the browser, with a JSON API
144 lines (143 loc) • 4.43 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.generate = generate;
exports.verify = verify;
const platform_1 = require("../platform");
const Utils_1 = require("../Utils");
function escape(value) {
return platform_1.Buffer.from(value.replace(/</g, '<'), 'utf-8');
}
function encodeIdentities(identities = []) {
const result = [];
const existing = new Set();
for (const { category, type, lang, name } of identities) {
const encoded = `${category}/${type}/${lang || ''}/${name || ''}`;
if (existing.has(encoded)) {
return null;
}
existing.add(encoded);
result.push(escape(encoded));
}
result.sort(Utils_1.octetCompare);
return result;
}
function encodeFeatures(features = []) {
const result = [];
const existing = new Set();
for (const feature of features) {
if (existing.has(feature)) {
return null;
}
existing.add(feature);
result.push(escape(feature));
}
result.sort(Utils_1.octetCompare);
return result;
}
function encodeFields(fields = []) {
const sortedFields = [];
for (const field of fields) {
if (field.name === 'FORM_TYPE') {
continue;
}
if (field.rawValues) {
sortedFields.push({
name: escape(field.name),
values: field.rawValues.map(val => escape(val)).sort(Utils_1.octetCompare)
});
}
else if (Array.isArray(field.value)) {
sortedFields.push({
name: escape(field.name),
values: field.value.map(val => escape(val)).sort(Utils_1.octetCompare)
});
}
else if (field.value === true || field.value === false) {
sortedFields.push({
name: escape(field.name),
values: [escape(field.value ? '1' : '0')]
});
}
else {
sortedFields.push({
name: escape(field.name),
values: [escape(field.value || '')]
});
}
}
sortedFields.sort((a, b) => (0, Utils_1.octetCompare)(a.name, b.name));
const result = [];
for (const field of sortedFields) {
result.push(field.name);
for (const value of field.values) {
result.push(value);
}
}
return result;
}
function encodeForms(extensions = []) {
const forms = [];
const types = new Set();
for (const form of extensions) {
let type;
for (const field of form.fields || []) {
if (!(field.name === 'FORM_TYPE' && field.type === 'hidden')) {
continue;
}
if (field.rawValues && field.rawValues.length === 1) {
type = escape(field.rawValues[0]);
break;
}
if (field.value && typeof field.value === 'string') {
type = escape(field.value);
break;
}
}
if (!type) {
continue;
}
if (types.has(type.toString())) {
return null;
}
types.add(type.toString());
forms.push({ type, form });
}
forms.sort((a, b) => (0, Utils_1.octetCompare)(a.type, b.type));
const results = [];
for (const form of forms) {
results.push(form.type);
const fields = encodeFields(form.form.fields);
for (const field of fields) {
results.push(field);
}
}
return results;
}
function generate(info, hashName) {
const S = [];
const separator = platform_1.Buffer.from('<', 'utf8');
const append = (b1) => {
S.push(b1);
S.push(separator);
};
const identities = encodeIdentities(info.identities);
const features = encodeFeatures(info.features);
const extensions = encodeForms(info.extensions);
if (!identities || !features || !extensions) {
return null;
}
for (const id of identities) {
append(id);
}
for (const feature of features) {
append(feature);
}
for (const form of extensions) {
append(form);
}
return (0, platform_1.createHash)(hashName).update(platform_1.Buffer.concat(S)).digest('base64');
}
function verify(info, hashName, check) {
const computed = generate(info, hashName);
return !!computed && computed === check;
}