/**
* Utilities for safely rendering user-authored text in outgoing emails.
*
* Company-editable email texts (see company_settings.invoice_email_texts)
* are untrusted input: they must be escaped before interpolation into HTML
* templates and kept single-line when used as mail headers.
*/
export function escapeHtml(input: string): string {
return input
.replace(/&/g, '&')
.replace(//g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''')
}
// Escape FIRST, then convert newlines, order matters: a '
' typed by the
// user must arrive escaped; only OUR
survives.
export function userTextToHtml(input: string): string {
return escapeHtml(input).replace(/\r\n|\r|\n/g, '
')
}
// Mail-header hygiene: a subject must be a single line (header injection).
export function sanitizeSubjectLine(input: string): string {
return input.replace(/[\r\n]+/g, ' ').trim()
}
// Fixed-set {x} substitution. Single pass: substituted VALUES are never
// re-scanned, so a customer named '{belopp}' stays literal. Unknown keys are
// left as-is so the user sees and fixes typos. Keys are matched after
// trim + toLowerCase (forgiving of '{ Förnamn }').
export function applyPlaceholders(
template: string,
values: Record,
): string {
return template.replace(/\{([^{}]*)\}/g, (match, rawKey: string) => {
const value = values[rawKey.trim().toLowerCase()]
return value !== undefined ? value : match
})
}