/** * Tests for the Bilibili 专栏 HTML sanitizer. The sanitizer runs in a real * (happy-dom) browser-like environment so it exercises the actual DOMParser * + Element APIs the production code uses. Each test parses the cleaned * output back through DOMParser and asserts on the resulting tree shape. */ import { describe, it, expect } from "vitest"; import { toBilibiliHtml } from "../bilibili"; function parseFragment(html: string): HTMLBodyElement { return new DOMParser().parseFromString(`
${html}`, "text/html") .body as HTMLBodyElement; } describe("toBilibiliHtml — whitelist enforcement", () => { it("strips ", ); const body = parseFragment(html); expect(body.querySelector("script")).toBeNull(); expect(body.querySelector("iframe")).toBeNull(); expect(body.querySelector("style")).toBeNull(); expect(body.querySelector("p")?.textContent).toBe("keep"); }); it("unwrapsa
b
kq
hi
', ); const p = parseFragment(html).querySelector("p"); expect(p?.getAttribute("class")).toBeNull(); expect(p?.getAttribute("data-x")).toBeNull(); expect(p?.getAttribute("onclick")).toBeNull(); expect(p?.getAttribute("id")).toBeNull(); expect(p?.textContent).toBe("hi"); }); it("keeps href and title on , drops javascript: hrefs", () => { const html = toBilibiliHtml( '', ); const anchors = Array.from(parseFragment(html).querySelectorAll("a")); expect(anchors[0].getAttribute("href")).toBe("https://example.com"); expect(anchors[0].getAttribute("title")).toBe("t"); expect(anchors[1].hasAttribute("href")).toBe(false); }); it("keeps only language-* classes on", () => {
const html = toBilibiliHtml(
'x
',
);
const code = parseFragment(html).querySelector("code");
// Whitespace allowed, but the only token retained is language-ts.
const tokens = (code?.getAttribute("class") ?? "")
.split(/\s+/)
.filter(Boolean);
expect(tokens).toEqual(["language-ts"]);
});
it("removes the class attribute entirely when no language-* token survives", () => {
const html = toBilibiliHtml(
'x
',
);
const code = parseFragment(html).querySelector("code");
expect(code?.hasAttribute("class")).toBe(false);
});
});
describe("toBilibiliHtml — image rewriting", () => {
it("leaves bilibili CDN images intact", () => {
const html = toBilibiliHtml(
'
',
);
const img = parseFragment(html).querySelector("img");
expect(img?.getAttribute("src")).toBe("https://i0.hdslb.com/x.png");
expect(img?.getAttribute("alt")).toBe("a");
expect(img?.hasAttribute("data-bili-placeholder")).toBe(false);
});
it("replaces non-CDN images with a placeholder SVG and stashes the original src", () => {
const html = toBilibiliHtml(
'
',
);
const img = parseFragment(html).querySelector("img");
expect(img?.getAttribute("data-original-src")).toBe(
"https://imgur.com/foo.png",
);
expect(img?.getAttribute("data-bili-placeholder")).toBe("true");
expect(img?.getAttribute("src")).toMatch(/^data:image\/svg\+xml/);
expect(img?.getAttribute("alt")).toBe("ext");
});
it("rejects lookalike hostnames (no subdomain dot before the literal)", () => {
// `evilhdslb.com` and `notbilibili.com` would slip past a `[^/]*` regex
// and skip the placeholder swap — both must be treated as non-CDN.
for (const src of [
"https://evilhdslb.com/foo.png",
"https://notbilibili.com/foo.png",
]) {
const html = toBilibiliHtml(`
`);
const img = parseFragment(html).querySelector("img");
expect(img?.getAttribute("data-bili-placeholder")).toBe("true");
expect(img?.getAttribute("data-original-src")).toBe(src);
}
});
it("accepts subdomains of the bilibili CDN", () => {
for (const src of [
"https://i0.hdslb.com/bfs/x.png",
"https://album.bilibili.com/x.png",
"https://hdslb.com/x.png", // bare apex
"https://bilibili.com/foo.png", // bare apex
]) {
const html = toBilibiliHtml(`
`);
const img = parseFragment(html).querySelector("img");
expect(img?.hasAttribute("data-bili-placeholder")).toBe(false);
expect(img?.getAttribute("src")).toBe(src);
}
});
});