Initial curated CertScope source from fe2bbe3ca5c98bf64be26a09c6c58ee805a7f87e
This commit is contained in:
commit
7348faf6c0
65 files changed
+12509
No files matched your search
@@ -0,0 +1,98 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { followRedirectChain, requestOnce, validateProbeUrl } from "./http";
|
||||
|
||||
const response = (url: string, status: number, location: string | null = null) => ({ url, status, location, headers: {} });
|
||||
const publicResolution = (addresses: string[]) => ({ addresses, families: { ipv4: { addresses, status: "ok" as const, error: null }, ipv6: { addresses: [], status: "absent" as const, error: null } }, _meta: { status: "ok" as const, partial: false, errors: [] } });
|
||||
|
||||
describe("HTTP target policy", () => {
|
||||
it.each(["ftp://example.com/", "https://user:pass@example.com/", "https://example.com:8443/", "https://127.0.0.1/"])('rejects unsafe target %s', (value) => {
|
||||
expect(() => validateProbeUrl(new URL(value))).toThrow();
|
||||
});
|
||||
|
||||
it("accepts standard public-host HTTP(S) targets", () => {
|
||||
expect(() => validateProbeUrl(new URL("https://example.com/path?q=1"))).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe("redirect chain", () => {
|
||||
it("follows bounded valid redirects with an injected transport", async () => {
|
||||
const requester = async (url: URL) => url.pathname === "/"
|
||||
? response(url.toString(), 302, "/final")
|
||||
: response(url.toString(), 200);
|
||||
const chain = await followRedirectChain(new URL("https://example.com/"), requester);
|
||||
expect(chain).toHaveLength(2);
|
||||
expect(chain[1].status).toBe(200);
|
||||
});
|
||||
|
||||
it("records relative and protocol-relative redirects", async () => {
|
||||
const requester = async (url: URL) => url.pathname === "/" ? response(url.toString(), 301, "//example.com/final") : response(url.toString(), 204);
|
||||
const chain = await followRedirectChain(new URL("https://example.com/"), requester);
|
||||
expect(chain.at(-1)?.status).toBe(204);
|
||||
});
|
||||
|
||||
it("blocks HTTPS to HTTP downgrades before requesting the target", async () => {
|
||||
const requested: string[] = [];
|
||||
const requester = async (url: URL) => { requested.push(url.toString()); return response(url.toString(), 302, "http://example.com/"); };
|
||||
const chain = await followRedirectChain(new URL("https://example.com/"), requester);
|
||||
expect(chain.at(-1)).toMatchObject({ code: "HTTPS_DOWNGRADE" });
|
||||
expect(requested).toEqual(["https://example.com/"]);
|
||||
});
|
||||
|
||||
it("detects self-loops and multi-hop normalized loops", async () => {
|
||||
const self = await followRedirectChain(new URL("https://example.com/"), async (url) => response(url.toString(), 301, url.toString()));
|
||||
expect(self.at(-1)).toMatchObject({ code: "REDIRECT_LOOP" });
|
||||
const cycle = await followRedirectChain(new URL("https://example.com/a"), async (url) => url.pathname === "/a" ? response(url.toString(), 302, "/b") : response(url.toString(), 302, "/a"));
|
||||
expect(cycle.at(-1)).toMatchObject({ code: "REDIRECT_LOOP" });
|
||||
const equivalent = await followRedirectChain(new URL("https://EXAMPLE.com./"), async (url) => response(url.toString(), 302, "https://example.com/"));
|
||||
expect(equivalent.at(-1)).toMatchObject({ code: "REDIRECT_LOOP" });
|
||||
});
|
||||
|
||||
it("records unsafe redirects and redirect budget exhaustion", async () => {
|
||||
const unsafe = await followRedirectChain(new URL("https://example.com/"), async (url) => response(url.toString(), 302, "http://127.0.0.1/admin"));
|
||||
expect(unsafe.at(-1)).toMatchObject({ error: expect.any(String), code: "REDIRECT_TARGET_BLOCKED" });
|
||||
const limited = await followRedirectChain(new URL("https://example.com/"), async (url) => response(url.toString(), 302, "/again"), 1);
|
||||
expect(limited.at(-1)).toMatchObject({ code: "REDIRECT_LIMIT" });
|
||||
});
|
||||
|
||||
it("redacts redirect userinfo from error evidence", async () => {
|
||||
const chain = await followRedirectChain(new URL("https://example.com/"), async (url) => response(url.toString(), 302, "https://user:secret@example.com/private"));
|
||||
expect(chain.at(-1)).toMatchObject({ code: "REDIRECT_TARGET_BLOCKED" });
|
||||
expect(JSON.stringify(chain)).not.toContain("secret");
|
||||
});
|
||||
|
||||
it("enforces the total deadline across slow hops", async () => {
|
||||
const started = Date.now();
|
||||
const chain = await followRedirectChain(new URL("https://example.com/"), async (url) => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
return response(url.toString(), 200);
|
||||
}, 3, Date.now() + 10);
|
||||
expect(chain.at(-1)?.error).toBe("HTTP request timed out.");
|
||||
expect(Date.now() - started).toBeLessThan(100);
|
||||
});
|
||||
});
|
||||
|
||||
describe("multi-address HTTP strategy", () => {
|
||||
it("tries the next validated public address after a transport failure", async () => {
|
||||
const attempts: string[] = [];
|
||||
const result = await requestOnce(new URL("https://example.com/"), Date.now() + 1_000, {
|
||||
resolve: async () => publicResolution(["1.1.1.1", "2.2.2.2"]),
|
||||
request: async (url, address) => {
|
||||
attempts.push(address);
|
||||
if (address === "1.1.1.1") throw Object.assign(new Error("refused"), { code: "ECONNREFUSED" });
|
||||
return response(url.toString(), 200);
|
||||
},
|
||||
});
|
||||
expect(result).toMatchObject({ status: 200, address: "2.2.2.2" });
|
||||
expect(attempts).toEqual(["1.1.1.1", "2.2.2.2"]);
|
||||
expect(result.attempts).toEqual([{ address: "1.1.1.1", error: "HTTP request failed (ECONNREFUSED)." }]);
|
||||
});
|
||||
|
||||
it("rejects an unsafe address returned by a resolver before any request", async () => {
|
||||
const attempted: string[] = [];
|
||||
await expect(requestOnce(new URL("https://example.com/"), Date.now() + 1_000, {
|
||||
resolve: async () => publicResolution(["1.1.1.1", "127.0.0.1"]),
|
||||
request: async (_url, address) => { attempted.push(address); return response("https://example.com/", 200); },
|
||||
})).rejects.toThrow(/non-public address space/);
|
||||
expect(attempted).toEqual([]);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user