Spec 11 §6.1: sp-sub-dial-user asks /api/internal/caller-name (func_curl, 2 s at most; this machine only, with an APP_SECRET-derived key, not logged) for the ringing user's name for the caller, and sets it on each ringing leg (pre-dial handler, CONNECTEDLINE), never on the call, so forwards and other members don't see someone else's contacts. Ring group/queue/number prefixes kept. Contrack lookups never wait (cache, contactsWarm every 5 min). Proven on real Asterisk with baresip: apps/api/test/asterisk/cnam-test.ts.
77 lines
3.2 KiB
TypeScript
77 lines
3.2 KiB
TypeScript
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
// Every value that goes into generated Asterisk config passes through one of these (03 §2).
|
|
// The API validates with the same rules first, so a throw here means a bug: apply fails loudly.
|
|
|
|
export class SanitizeError extends Error {}
|
|
|
|
function check(re: RegExp, kind: string, max = 200) {
|
|
return (v: string): string => {
|
|
if (typeof v !== 'string' || v.length > max || !re.test(v))
|
|
throw new SanitizeError(`${kind}: invalid value ${JSON.stringify(String(v).slice(0, 40))}`);
|
|
return v;
|
|
};
|
|
}
|
|
|
|
/** Internal numbers, DIDs. */
|
|
export const digits = check(/^[0-9]+$/, 'digits', 32);
|
|
/** Forward targets, external numbers. */
|
|
export const dialable = check(/^[0-9*#+]+$/, 'dialable', 32);
|
|
/** Outbound route patterns. */
|
|
export const pattern = check(/^[0-9XZN.![\]-]+$/, 'pattern', 32);
|
|
/** Office hours times "08:30"; "24:00" is the end of the day. */
|
|
export const hhmm = check(/^(?:[01]\d|2[0-3]):[0-5]\d$|^24:00$/, 'time', 5);
|
|
/** Day names as GotoIfTime wants them. */
|
|
export const weekday = check(/^(?:mon|tue|wed|thu|fri|sat|sun)$/, 'weekday', 3);
|
|
/** One IVR key. */
|
|
export const ivrDigit = check(/^[0-9*#]$/, 'ivr digit', 1);
|
|
/** Sound file names for Playback/Background. */
|
|
export const sound = check(/^[a-z0-9/_-]+$/, 'sound', 100);
|
|
/** Context-name parts (uuids), MOH class names. */
|
|
export const ident = check(/^[a-z0-9-]+$/, 'ident', 64);
|
|
/** Generated SIP passwords. */
|
|
/** The api on this machine, e.g. http://127.0.0.1:8080/api/internal/caller-name. */
|
|
export const localUrl = check(
|
|
/^http:\/\/(?:127\.0\.0\.1|localhost):[0-9]{1,5}\/[a-z0-9/_-]+$/,
|
|
'local url',
|
|
100,
|
|
);
|
|
export const secret = check(/^[A-Za-z0-9]+$/, 'secret', 64);
|
|
/** Trunk usernames (voip.ms sub-accounts like 123456_pbx). */
|
|
export const sipUser = check(/^[A-Za-z0-9_.-]+$/, 'sip user', 64);
|
|
/** Passwords typed by an admin (trunks): no ';' (comment), no line breaks, no edge spaces. */
|
|
export const typedSecret = check(/^[^\s;](?:[^\r\n;]*[^\s;])?$/, 'password', 128);
|
|
/** Codec names. */
|
|
export const codec = check(/^(?:ulaw|alaw|g722)$/, 'codec', 4);
|
|
/** Voicemail / conference PINs. */
|
|
export const pin = check(/^[0-9]{4,10}$/, 'pin', 10);
|
|
/** IANA time zone names, e.g. America/Chicago, Etc/GMT+6. */
|
|
export const timezone = check(/^[A-Za-z][A-Za-z0-9_+/-]*$/, 'timezone', 64);
|
|
/** Trunk server host: a hostname or an IPv4 address. */
|
|
export const host = check(
|
|
/^(?=.{1,253}$)(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?\.)*[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?$/,
|
|
'host',
|
|
253,
|
|
);
|
|
const octet = '(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)';
|
|
/** IPv4 address. */
|
|
export const ipv4 = check(new RegExp(`^${octet}(?:\\.${octet}){3}$`), 'ipv4', 15);
|
|
/** IPv4 CIDR, e.g. local nets. */
|
|
export const cidr = check(
|
|
new RegExp(`^${octet}(?:\\.${octet}){3}/(?:3[0-2]|[12]?\\d)$`),
|
|
'cidr',
|
|
18,
|
|
);
|
|
|
|
/**
|
|
* Caller ID names and labels: removes characters that mean something in dialplan or config
|
|
* (" ' \ ; , ( ) [ ] { } $ < > |) and control characters, trims, max 40. Never throws.
|
|
*/
|
|
export function name(v: string): string {
|
|
return String(v)
|
|
.replace(/[\p{Cc}\p{Cf}\u2028\u2029]/gu, '') // controls, incl. CR/LF, and invisible format chars
|
|
.replace(/["'\\;,()[\]{}$<>|]/g, '')
|
|
.trim()
|
|
.slice(0, 40)
|
|
.trim();
|
|
}
|