/**
* 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