/** * Shared HTML-escaping and URL-safety utilities. * * `escapeHtml` / `escapeAttr` are the single source of truth for escaping * user-supplied strings before they are interpolated into exported/rendered * HTML. `safeUrl` blocks dangerous URL schemes (javascript:, vbscript:, * data:text/html) including whitespace/case/entity-obfuscated variants. */ /** Escapes &, <, >, " for safe use in text content and double-quoted attributes. */ export function escapeHtml(s: string): string { if (typeof s !== 'string') { if (s === null || s === undefined) return ''; s = String(s); } // Ampersand MUST be escaped first, otherwise the entities produced by the // other replacements would themselves be re-escaped (double-escaping). return s .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"'); } /** Escapes everything escapeHtml does, plus ' and ` for use in any attribute context. */ export function escapeAttr(s: string): string { if (typeof s !== 'string') { if (s === null || s === undefined) return ''; s = String(s); } return escapeHtml(s) .replace(/'/g, ''') .replace(/`/g, '`'); } // Dangerous scheme prefixes, checked against a normalized copy with all // colons removed (see safeUrl) so that colon-splicing obfuscation like // "java:script:alert(1)" can't slip past a literal "javascript:" check. // M-5: `data:image/svg+xml` is blocked alongside `data:text/html` -- an SVG // document can carry an inline