import { expect, test } from "vitest" import { toSafeImageUrl, toSafeLinkUrl, } from "./safeUrls.ts" test("an ordinary link survives untouched", () => { for (const url of [ "https://example.invalid/runbook", "http://example.invalid/runbook?id=1#top", "mailto:someone@example.invalid", "tel:+15550000000", "./RELEASING.md", "../docs/decisions/README.md", "#the-rack-move", "?tab=history", "//example.invalid/protocol-relative", // The colon is inside a path segment, so there is no scheme // here at all — the character class in `SCHEME_PATTERN` stops at // the slash, and this is the case that proves it. "notes/2026:rack.md", ]) { expect(toSafeLinkUrl(url)).toBe(url) } }) test("a script URL is not a link, however it is spelled", () => { for (const url of [ "javascript:alert(1)", "JavaScript:alert(1)", "JAVASCRIPT:alert(1)", // The three characters a browser strips out of a scheme before // it runs it. A blocklist misses every one of these. "java\tscript:alert(1)", "java\nscript:alert(1)", "java\rscript:alert(1)", " javascript:alert(1)", "javascript:alert(1)", "vbscript:msgbox(1)", "data:text/html,", "file:///etc/passwd", ]) { expect(toSafeLinkUrl(url)).toBeUndefined() } }) test("angle brackets are stripped, not carried into the href", () => { expect( toSafeLinkUrl(""), ).toBe("https://example.invalid/a b") // …and they are no way around the guard either. expect( toSafeLinkUrl(""), ).toBeUndefined() }) test("an image takes http, https and an inline image", () => { for (const url of [ "https://example.invalid/rack.png", "http://example.invalid/rack.png", "./docs/images/rack.png", "data:image/png;base64,iVBORw0KGgo=", "data:image/svg+xml,%3Csvg%3E%3C/svg%3E", ]) { expect(toSafeImageUrl(url)).toBe(url) } }) test("an image takes no other data URL and no script URL", () => { for (const url of [ "data:text/html,", "data:text/html;base64,PHNjcmlwdD4=", // No comma and no semicolon after the type, so it is not a // well-formed data URL and does not get the benefit of the // doubt. "data:image/png", "javascript:alert(1)", "file:///etc/passwd", ]) { expect(toSafeImageUrl(url)).toBeUndefined() } }) test("mailto is a link and is not an image", () => { // The two lists are genuinely different rather than one list // called twice: an `href` may open a mail client, an `src` may // not fetch one. expect(toSafeLinkUrl("mailto:a@example.invalid")).toBe( "mailto:a@example.invalid", ) expect( toSafeImageUrl("mailto:a@example.invalid"), ).toBeUndefined() })