server/utils/sanitization.ts
2024-05-04 19:13:23 -10:00

72 lines
1.9 KiB
TypeScript

import { stringifyEntitiesLight } from "stringify-entities";
import xss, { type IFilterXSSOptions } from "xss";
export const sanitizedHtmlStrip = (html: string) => {
return sanitizeHtml(html, {
whiteList: {},
});
};
export const sanitizeHtml = async (
html: string,
extraConfig?: IFilterXSSOptions,
) => {
const sanitizedHtml = xss(html, {
whiteList: {
a: ["href", "title", "target", "rel", "class"],
p: ["class"],
br: ["class"],
b: ["class"],
i: ["class"],
em: ["class"],
strong: ["class"],
del: ["class"],
code: ["class"],
u: ["class"],
pre: ["class"],
ul: ["class"],
ol: ["class"],
li: ["class"],
blockquote: ["class"],
},
stripIgnoreTag: false,
escapeHtml: (unsafeHtml) =>
stringifyEntitiesLight(unsafeHtml, {
escapeOnly: true,
}),
...extraConfig,
});
// Check text to only allow h-*, p-*, u-*, dt-*, e-*, mention, hashtag, ellipsis, invisible classes
const allowedClasses = [
"h-",
"p-",
"u-",
"dt-",
"e-",
"mention",
"hashtag",
"ellipsis",
"invisible",
];
return await new HTMLRewriter()
.on("*[class]", {
element(element) {
const classes = element.getAttribute("class")?.split(" ") ?? [];
for (const className of classes) {
if (
!allowedClasses.some((allowedClass) =>
className.startsWith(allowedClass),
)
) {
element.removeAttribute("class");
}
}
},
})
.transform(new Response(sanitizedHtml))
.text();
};