import { describe, expect, it, vi } from "vitest";
import {
fetchPublic,
isPublicHost,
isPublicUrl,
publicOnly,
readTextCapped,
UnsafeTargetError
} from "#lib/publicFetch.ts";
const prod = false;
describe("isPublicHost", () => {
it("allows public names", () => {
expect(isPublicHost("bsky.social")).toBe(true);
expect(isPublicHost("pds.tngl.boltless.dev")).toBe(true);
});
it("blocks ip literals, service names and internal suffixes", () => {
for (const host of [
"127.0.0.1",
"169.254.169.254",
"[::1]",
"localhost",
"bobbin",
"metadata.google.internal",
"app.localhost",
"nas.local",
"vm.home.arpa",
""
])
expect(isPublicHost(host), host).toBe(false);
});
it("treats a trailing dot as the same name", () => {
expect(isPublicHost("localhost.")).toBe(false);
expect(isPublicHost("metadata.google.internal.")).toBe(false);
});
});
describe("isPublicUrl", () => {
it("needs https outside dev", () => {
expect(isPublicUrl(new URL("https://pds.example.com"), prod)).toBe(true);
expect(isPublicUrl(new URL("http://pds.example.com"), prod)).toBe(false);
// the url parser normalises numeric hosts to dotted ipv4
expect(isPublicUrl(new URL("https://2130706433"), prod)).toBe(false);
});
it("lets anything through in dev", () => {
expect(isPublicUrl(new URL("http://localhost:2583"), true)).toBe(true);
});
});
const redirect = (to: string) => new Response(null, { status: 302, headers: { location: to } });
describe("fetchPublic", () => {
it("refuses a private target without fetching", async () => {
const fetch = vi.fn();
await expect(
fetchPublic(fetch, "http://127.0.0.1:8090/x", {}, prod)
).rejects.toBeInstanceOf(UnsafeTargetError);
expect(fetch).not.toHaveBeenCalled();
});
it("follows a redirect to another public host", async () => {
const fetch = vi
.fn()
.mockResolvedValueOnce(redirect("https://cdn.example.com/blob"))
.mockResolvedValueOnce(new Response("ok"));
const res = await fetchPublic(fetch, "https://pds.example.com/x", {}, prod);
expect(await res.text()).toBe("ok");
expect(fetch.mock.calls.map(([u]) => String(u))).toEqual([
"https://pds.example.com/x",
"https://cdn.example.com/blob"
]);
expect(fetch.mock.calls[0][1]).toMatchObject({ redirect: "manual" });
});
it("refuses a redirect into the private network", async () => {
const fetch = vi.fn().mockResolvedValueOnce(redirect("http://169.254.169.254/latest"));
await expect(
fetchPublic(fetch, "https://pds.example.com/x", {}, prod)
).rejects.toBeInstanceOf(UnsafeTargetError);
expect(fetch).toHaveBeenCalledTimes(1);
});
it("gives up on a redirect loop", async () => {
const fetch = vi.fn(() => Promise.resolve(redirect("https://pds.example.com/x")));
await expect(fetchPublic(fetch, "https://pds.example.com/x", {}, prod)).rejects.toThrow(
/too many redirects/
);
});
});
describe("publicOnly", () => {
it("rejects private targets and forbids redirects for the rest", async () => {
const fetch = vi.fn(() => Promise.resolve(new Response("ok")));
const guarded = publicOnly(fetch, prod);
await expect(guarded("http://10.0.0.5:9000/xrpc/x")).rejects.toBeInstanceOf(
UnsafeTargetError
);
await guarded("https://spindle.example.com/xrpc/x");
expect(fetch).toHaveBeenCalledTimes(1);
expect(fetch.mock.calls[0]).toEqual([
"https://spindle.example.com/xrpc/x",
{ redirect: "error" }
]);
});
});
describe("readTextCapped", () => {
it("reads a body under the cap", async () => {
expect(await readTextCapped(new Response("héllo"), 100)).toBe("héllo");
});
it("fails past the cap, declared or streamed", async () => {
await expect(
readTextCapped(new Response("x", { headers: { "content-length": "999" } }), 10)
).rejects.toThrow(/over 10 bytes/);
await expect(readTextCapped(new Response("x".repeat(50)), 10)).rejects.toThrow(
/over 10 bytes/
);
});
});