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()
})