2024-05-03 05:20:24 +02:00
|
|
|
import { stringifyEntitiesLight } from "stringify-entities";
|
2024-05-05 07:13:23 +02:00
|
|
|
import xss, { type IFilterXSSOptions } from "xss";
|
2024-05-03 02:44:49 +02:00
|
|
|
|
2024-11-02 00:43:33 +01:00
|
|
|
export const sanitizedHtmlStrip = (html: string): Promise<string> => {
|
2024-05-03 05:20:24 +02:00
|
|
|
return sanitizeHtml(html, {
|
|
|
|
|
whiteList: {},
|
|
|
|
|
});
|
|
|
|
|
};
|
2023-10-17 00:03:29 +02:00
|
|
|
|
2024-06-13 04:26:43 +02:00
|
|
|
export const sanitizeHtmlInline = (
|
2024-05-12 03:27:19 +02:00
|
|
|
html: string,
|
|
|
|
|
extraConfig?: IFilterXSSOptions,
|
2024-11-02 00:43:33 +01:00
|
|
|
): Promise<string> => {
|
2024-05-12 03:27:19 +02:00
|
|
|
return sanitizeHtml(html, {
|
|
|
|
|
whiteList: {
|
|
|
|
|
a: ["href", "title", "target", "rel", "class"],
|
|
|
|
|
p: ["class"],
|
|
|
|
|
b: ["class"],
|
|
|
|
|
i: ["class"],
|
|
|
|
|
em: ["class"],
|
|
|
|
|
strong: ["class"],
|
|
|
|
|
del: ["class"],
|
|
|
|
|
u: ["class"],
|
|
|
|
|
font: ["color", "size", "face", "class"],
|
|
|
|
|
strike: ["class"],
|
|
|
|
|
mark: ["class"],
|
|
|
|
|
small: ["class"],
|
|
|
|
|
},
|
|
|
|
|
...extraConfig,
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
2024-05-03 01:53:10 +02:00
|
|
|
export const sanitizeHtml = async (
|
|
|
|
|
html: string,
|
2024-05-03 05:20:24 +02:00
|
|
|
extraConfig?: IFilterXSSOptions,
|
2024-11-02 00:43:33 +01:00
|
|
|
): Promise<string> => {
|
2024-05-03 05:20:24 +02:00
|
|
|
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"],
|
2024-05-12 03:27:19 +02:00
|
|
|
h1: ["class"],
|
|
|
|
|
h2: ["class"],
|
|
|
|
|
h3: ["class"],
|
|
|
|
|
h4: ["class"],
|
|
|
|
|
h5: ["class"],
|
|
|
|
|
h6: ["class"],
|
|
|
|
|
img: ["src", "alt", "title", "class"],
|
|
|
|
|
font: ["color", "size", "face", "class"],
|
|
|
|
|
table: ["class"],
|
|
|
|
|
tr: ["class"],
|
|
|
|
|
td: ["class"],
|
|
|
|
|
th: ["class"],
|
|
|
|
|
tbody: ["class"],
|
|
|
|
|
thead: ["class"],
|
|
|
|
|
tfoot: ["class"],
|
|
|
|
|
hr: ["class"],
|
|
|
|
|
strike: ["class"],
|
|
|
|
|
figcaption: ["class"],
|
|
|
|
|
figure: ["class"],
|
|
|
|
|
mark: ["class"],
|
|
|
|
|
summary: ["class"],
|
|
|
|
|
details: ["class"],
|
|
|
|
|
caption: ["class"],
|
|
|
|
|
small: ["class"],
|
|
|
|
|
video: ["class", "src", "controls"],
|
|
|
|
|
audio: ["class", "src", "controls"],
|
|
|
|
|
source: ["src", "type"],
|
|
|
|
|
track: ["src", "label", "kind"],
|
2024-11-19 11:20:24 +01:00
|
|
|
input: ["type", "checked", "disabled", "class"],
|
2024-04-07 07:30:49 +02:00
|
|
|
},
|
2024-05-03 05:20:24 +02:00
|
|
|
stripIgnoreTag: false,
|
2024-11-02 00:43:33 +01:00
|
|
|
escapeHtml: (unsafeHtml): string =>
|
2024-05-03 05:20:24 +02:00
|
|
|
stringifyEntitiesLight(unsafeHtml, {
|
|
|
|
|
escapeOnly: true,
|
|
|
|
|
}),
|
2024-05-03 01:25:32 +02:00
|
|
|
...extraConfig,
|
2024-05-03 05:20:24 +02:00
|
|
|
});
|
2023-10-17 00:03:29 +02:00
|
|
|
|
2024-04-07 07:30:49 +02:00
|
|
|
// 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",
|
2024-11-19 11:20:24 +01:00
|
|
|
"task-list-item-checkbox",
|
2024-04-07 07:30:49 +02:00
|
|
|
];
|
2023-10-17 00:03:29 +02:00
|
|
|
|
2024-04-07 07:30:49 +02:00
|
|
|
return await new HTMLRewriter()
|
|
|
|
|
.on("*[class]", {
|
2024-11-02 00:43:33 +01:00
|
|
|
element(element): void {
|
2024-04-07 07:30:49 +02:00
|
|
|
const classes = element.getAttribute("class")?.split(" ") ?? [];
|
2023-10-17 00:03:29 +02:00
|
|
|
|
2024-04-07 07:30:49 +02:00
|
|
|
for (const className of classes) {
|
|
|
|
|
if (
|
|
|
|
|
!allowedClasses.some((allowedClass) =>
|
|
|
|
|
className.startsWith(allowedClass),
|
|
|
|
|
)
|
|
|
|
|
) {
|
|
|
|
|
element.removeAttribute("class");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
})
|
2024-11-19 11:20:24 +01:00
|
|
|
// Only allow disabled checkbox input
|
|
|
|
|
.on("input", {
|
|
|
|
|
element(element): void {
|
2024-11-19 11:32:16 +01:00
|
|
|
if (element.getAttribute("type") === "checkbox") {
|
|
|
|
|
element.setAttribute("disabled", "");
|
|
|
|
|
} else {
|
|
|
|
|
element.remove();
|
2024-11-19 11:20:24 +01:00
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
})
|
2024-04-07 07:30:49 +02:00
|
|
|
.transform(new Response(sanitizedHtml))
|
2024-05-03 01:25:32 +02:00
|
|
|
.text();
|
2023-10-17 00:03:29 +02:00
|
|
|
};
|