Skip to content
File

Blob: tests/client/email-html/dom.test.ts

typescript116 lines
1import { describe, expect, it } from "vitest";
2import { buildExternalLinkRedirectHref } from "@/client/lib/external-link";
3import { createBlockedImagePlaceholder, sanitizeElementAttributes } from "@/client/lib/email-html/dom";
4 
5function createDocument() {
6 return new DOMParser().parseFromString("<html><body></body></html>", "text/html");
7}
8 
9describe("createBlockedImagePlaceholder", () => {
10 it("includes the provided alt text in the placeholder message", () => {
11 const doc = createDocument();
12 
13 const placeholder = createBlockedImagePlaceholder(doc, "Hero image");
14 
15 expect(placeholder.getAttribute("data-remote-blocked")).toBe("image");
16 expect(placeholder.textContent).toBe("Remote image blocked: Hero image");
17 });
18 
19 it("falls back to the generic placeholder message when alt text is missing", () => {
20 const doc = createDocument();
21 
22 const placeholder = createBlockedImagePlaceholder(doc, null);
23 
24 expect(placeholder.textContent).toBe("Remote image blocked. Load remote content to display it.");
25 });
26});
27 
28describe("sanitizeElementAttributes", () => {
29 it("removes event handlers, srcdoc, and unsafe navigation urls", () => {
30 const doc = createDocument();
31 const element = doc.createElement("a");
32 element.setAttribute("onclick", "alert('xss')");
33 element.setAttribute("srcdoc", "<p>bad</p>");
34 element.setAttribute("href", "javascript:alert('xss')");
35 
36 const blockedRemoteContent = sanitizeElementAttributes(element, false);
37 
38 expect(blockedRemoteContent).toBe(false);
39 expect(element.hasAttribute("onclick")).toBe(false);
40 expect(element.hasAttribute("srcdoc")).toBe(false);
41 expect(element.hasAttribute("href")).toBe(false);
42 });
43 
44 it("rewrites remote navigation links through the external redirect", () => {
45 const doc = createDocument();
46 const element = doc.createElement("a");
47 element.setAttribute("href", "https://example.com/path?q=1");
48 
49 const blockedRemoteContent = sanitizeElementAttributes(element, false);
50 
51 expect(blockedRemoteContent).toBe(false);
52 expect(element.getAttribute("href")).toBe(buildExternalLinkRedirectHref("https://example.com/path?q=1"));
53 });
54 
55 it("preserves allowed mailto navigation urls", () => {
56 const doc = createDocument();
57 const element = doc.createElement("a");
58 element.setAttribute("href", "mailto:test@example.com");
59 
60 const blockedRemoteContent = sanitizeElementAttributes(element, false);
61 
62 expect(blockedRemoteContent).toBe(false);
63 expect(element.getAttribute("href")).toBe("mailto:test@example.com");
64 });
65 
66 it("sanitizes inline styles and reports blocked remote content", () => {
67 const doc = createDocument();
68 const element = doc.createElement("div");
69 element.setAttribute(
70 "style",
71 "background-image:url('https://images.example.com/hero.png'); color: blue; expression(alert('xss'))",
72 );
73 
74 const blockedRemoteContent = sanitizeElementAttributes(element, false);
75 
76 expect(blockedRemoteContent).toBe(true);
77 expect(element.getAttribute("style")).toContain("background-image:none");
78 expect(element.getAttribute("style")).toContain("color: blue");
79 expect(element.getAttribute("style")).not.toContain("expression");
80 });
81 
82 it("removes remote auto-loading attributes when remote content is disabled", () => {
83 const doc = createDocument();
84 const element = doc.createElement("img");
85 element.setAttribute("src", "https://images.example.com/hero.png");
86 element.setAttribute("srcset", "https://images.example.com/hero.png 1x");
87 element.setAttribute("poster", "https://images.example.com/poster.png");
88 
89 const blockedRemoteContent = sanitizeElementAttributes(element, false);
90 
91 expect(blockedRemoteContent).toBe(true);
92 expect(element.hasAttribute("src")).toBe(false);
93 expect(element.hasAttribute("srcset")).toBe(false);
94 expect(element.hasAttribute("poster")).toBe(false);
95 });
96 
97 it("preserves inline resources and remote sources when remote content is enabled", () => {
98 const doc = createDocument();
99 const inlineImage = doc.createElement("img");
100 inlineImage.setAttribute("src", "cid:inline-image");
101 
102 const remoteImage = doc.createElement("img");
103 remoteImage.setAttribute("src", "https://images.example.com/hero.png");
104 remoteImage.setAttribute("srcset", "https://images.example.com/hero.png 1x");
105 
106 const inlineBlocked = sanitizeElementAttributes(inlineImage, false);
107 const remoteBlocked = sanitizeElementAttributes(remoteImage, true);
108 
109 expect(inlineBlocked).toBe(false);
110 expect(inlineImage.getAttribute("src")).toBe("cid:inline-image");
111 expect(remoteBlocked).toBe(false);
112 expect(remoteImage.getAttribute("src")).toBe("https://images.example.com/hero.png");
113 expect(remoteImage.getAttribute("srcset")).toBe("https://images.example.com/hero.png 1x");
114 });
115});