← back to Small Business Builder
src/lib/escape.js
48 lines
// Tiny HTML escapes for safe template rendering
export function esc(s) {
if (s === null || s === undefined) return '';
return String(s)
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
export function attr(s) { return esc(s); }
// Escape a stringified JSON blob for safe embedding inside
// <script type="application/ld+json">. Without this, content containing
// "</script>" (or other angle brackets / ampersands) can break out of the
// script tag (XSS). Use this before interpolating ANY ld+json blob.
// (Refactor: was duplicated in profile.js, corridor.js, corridor-map.js,
// near.js, fresh.js, category.js, proprietors.js — single source of truth.)
export function escJsonLd(json) {
return String(json)
.replace(/</g, '\\u003c')
.replace(/>/g, '\\u003e')
.replace(/&/g, '\\u0026');
}
// Reject href values that aren't a real http/https URL. Returns the
// trimmed URL string when safe, null otherwise. Use to neutralize
// stored-XSS via `javascript:` / `data:` href values that snuck into the DB.
export function safeHref(raw) {
if (typeof raw !== 'string') return null;
const u = raw.trim();
if (!u) return null;
try {
const p = new URL(u);
if (p.protocol === 'http:' || p.protocol === 'https:') return p.toString();
} catch {}
return null;
}
// Validate user-supplied hex colors before they hit a <style> block.
// Returns the color if it matches #rgb / #rgba / #rrggbb / #rrggbbaa, else fallback.
// This blocks stored-XSS payloads of the form `</style><script>...</script>`
// being interpolated as CSS variable values.
export function safeColor(c, fallback = '#1f2937') {
if (typeof c !== 'string') return fallback;
return /^#[0-9a-f]{3,8}$/i.test(c.trim()) ? c.trim() : fallback;
}