mirror of
https://github.com/versia-pub/server.git
synced 2025-12-06 08:28:19 +01:00
feat(api): ✨ Allow more HTML tags in Markdown
This commit is contained in:
parent
4ce5dfeae3
commit
b979daa39a
|
|
@ -1,7 +1,7 @@
|
|||
import { applyConfig, auth, handleZodError, qs } from "@api";
|
||||
import { zValidator } from "@hono/zod-validator";
|
||||
import { errorResponse, jsonResponse } from "@response";
|
||||
import { sanitizeHtml, sanitizedHtmlStrip } from "@sanitization";
|
||||
import { sanitizedHtmlStrip } from "@sanitization";
|
||||
import { config } from "config-manager";
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import type { Hono } from "hono";
|
||||
|
|
@ -224,17 +224,25 @@ export default (app: Hono) =>
|
|||
self.source.fields = [];
|
||||
for (const field of fields_attributes) {
|
||||
// Can be Markdown or plaintext, also has emojis
|
||||
const parsedName = await contentToHtml({
|
||||
"text/markdown": {
|
||||
content: field.name,
|
||||
const parsedName = await contentToHtml(
|
||||
{
|
||||
"text/markdown": {
|
||||
content: field.name,
|
||||
},
|
||||
},
|
||||
});
|
||||
undefined,
|
||||
true,
|
||||
);
|
||||
|
||||
const parsedValue = await contentToHtml({
|
||||
"text/markdown": {
|
||||
content: field.value,
|
||||
const parsedValue = await contentToHtml(
|
||||
{
|
||||
"text/markdown": {
|
||||
content: field.value,
|
||||
},
|
||||
},
|
||||
});
|
||||
undefined,
|
||||
true,
|
||||
);
|
||||
|
||||
// Parse emojis
|
||||
const nameEmojis = await parseEmojis(parsedName);
|
||||
|
|
|
|||
|
|
@ -394,5 +394,37 @@ describe(meta.route, () => {
|
|||
"uwu <script>alert('Hello, world!');</script>",
|
||||
);
|
||||
});
|
||||
|
||||
test("should rewrite all image and video src to go through proxy", async () => {
|
||||
const response = await sendTestRequest(
|
||||
new Request(new URL(meta.route, config.http.base_url), {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Bearer ${tokens[0].accessToken}`,
|
||||
},
|
||||
body: new URLSearchParams({
|
||||
status: "<img src='https://example.com/image.jpg'> <video src='https://example.com/video.mp4'> Test!",
|
||||
federate: "false",
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.headers.get("content-type")).toBe(
|
||||
"application/json",
|
||||
);
|
||||
|
||||
const object = (await response.json()) as APIStatus;
|
||||
// Proxy url is base_url/media/proxy/<base64url encoded url>
|
||||
expect(object.content).toBe(
|
||||
`<p><img src="${config.http.base_url}/media/proxy/${Buffer.from(
|
||||
"https://example.com/image.jpg",
|
||||
).toString("base64url")}"> <video src="${
|
||||
config.http.base_url
|
||||
}/media/proxy/${Buffer.from(
|
||||
"https://example.com/video.mp4",
|
||||
).toString("base64url")}"> Test!</p>`,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -7,6 +7,29 @@ export const sanitizedHtmlStrip = (html: string) => {
|
|||
});
|
||||
};
|
||||
|
||||
export const sanitizeHtmlInline = async (
|
||||
html: string,
|
||||
extraConfig?: IFilterXSSOptions,
|
||||
) => {
|
||||
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,
|
||||
});
|
||||
};
|
||||
|
||||
export const sanitizeHtml = async (
|
||||
html: string,
|
||||
extraConfig?: IFilterXSSOptions,
|
||||
|
|
@ -28,6 +51,34 @@ export const sanitizeHtml = async (
|
|||
ol: ["class"],
|
||||
li: ["class"],
|
||||
blockquote: ["class"],
|
||||
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"],
|
||||
},
|
||||
stripIgnoreTag: false,
|
||||
escapeHtml: (unsafeHtml) =>
|
||||
|
|
|
|||
Loading…
Reference in a new issue