import { config } from "@/lib/config";
const ALLOWED_TAGS = new Set([
"a",
"b",
"blockquote",
"br",
"code",
"em",
"h1",
"h2",
"h3",
"h4",
"h5",
"h6",
"i",
"li",
"ol",
"p",
"pre",
"s",
"strong",
"strike",
"u",
"ul",
]);
const BLOCKED_TAGS = /<(script|style|iframe|object|embed|svg|math|template)[\s\S]*?<\/\1>/gi;
const TAG_RE = /<\/?([a-zA-Z][a-zA-Z0-9-]*)([^>]*)>/g;
const ATTR_RE = /([a-zA-Z_:][a-zA-Z0-9_:.-]*)\s*=\s*("[^"]*"|'[^']*'|[^\s"'=<>`]+)/g;
export function sanitizeUrl(url: string) {
const trimmed = url.trim();
if (!trimmed) return "";
try {
const parsed = new URL(trimmed, config.site.url);
if (parsed.protocol === "http:" || parsed.protocol === "https:" || parsed.protocol === "mailto:") {
return trimmed;
}
} catch {
return "";
}
return "";
}
function escapeAttribute(value: string) {
return value
.replace(/&/g, "&")
.replace(/"/g, """)
.replace(//g, ">");
}
export function sanitizeHtml(html: string) {
return html.replace(BLOCKED_TAGS, "").replace(TAG_RE, (tag, rawName: string, rawAttrs: string) => {
const name = rawName.toLowerCase();
if (!ALLOWED_TAGS.has(name)) return "";
if (tag.startsWith("")) {
return `${name}>`;
}
if (name !== "a") {
return `<${name}>`;
}
let href = "";
for (const match of rawAttrs.matchAll(ATTR_RE)) {
if (match[1].toLowerCase() === "href") {
href = sanitizeUrl(match[2].replace(/^["']|["']$/g, ""));
break;
}
}
return href
? ``
: "";
});
}