Files
reverse-engineering-anything/tests/fixtures/browserFixtureHttpTargets.mjs
T

161 lines
5.2 KiB
JavaScript

import assert from "node:assert/strict";
import { once } from "node:events";
import { createServer, request } from "node:http";
import { networkInterfaces } from "node:os";
import {
closeVerifierResources,
withVerifierCleanup,
} from "../../scripts/lib/verifier-cleanup.mjs";
const kind = process.argv[2];
const owned = [];
let alternateRequests = 0;
let site;
const upstream = async (name) => {
const server = createServer((incoming, response) => {
if (name === "alternate") alternateRequests += 1;
response.setHeader("Content-Type", "application/json");
response.end(JSON.stringify({ name, target: incoming.url }));
});
owned.push(server);
server.listen(0, "127.0.0.1");
await once(server, "listening");
return `http://127.0.0.1:${server.address().port}`;
};
const send = (origin, target) =>
new Promise((resolve, reject) => {
const selected = new URL(origin);
const pending = request(
{
hostname: selected.hostname,
port: selected.port,
path: target,
timeout: 2_000,
},
(response) => {
const chunks = [];
response.on("data", (chunk) => chunks.push(chunk));
response.on("error", reject);
response.on("end", () =>
resolve({
status: response.statusCode,
headers: response.headers,
body: Buffer.concat(chunks).toString("utf8"),
}),
);
},
);
pending.on("error", reject);
pending.on("timeout", () =>
pending.destroy(new Error("Fixture HTTP request timed out")),
);
pending.end();
});
await withVerifierCleanup(async () => {
let origin;
let ordinary;
let expected;
if (kind === "proxy") {
const configured = await upstream("configured");
const alternate = await upstream("alternate");
const { startProxy } =
await import("../../scripts/lib/browser-scenario-service-worker-setup-e2e.mjs");
site = await startProxy(configured, "http-discovery");
origin = site.endpoint;
ordinary = "/probe?x=1";
expected = JSON.stringify({ name: "configured", target: ordinary });
for (const [target, path] of [
["/probe?x=a%20b&x=2", "/probe?x=a%20b&x=2"],
[`${alternate}/probe?absolute=1`, "/probe?absolute=1"],
[
`${alternate.replace("http:", "")}/probe?x=1`,
`${alternate.replace("http:", "")}/probe?x=1`,
],
["https://unused.invalid/probe?escaped=%2f", "/probe?escaped=%2f"],
["/%40scope%2fmodule?x=%252f", "/%40scope%2fmodule?x=%252f"],
]) {
const reply = await send(origin, target);
assert.equal(reply.status, 200);
assert.deepEqual(JSON.parse(reply.body), {
name: "configured",
target: path,
});
}
assert.equal(alternateRequests, 0);
} else if (kind === "workers") {
const { startSite } =
await import("../../scripts/lib/browser-scenario-service-workers-e2e.mjs");
site = await startSite();
origin = site.origin;
ordinary = "/probe";
expected = "network";
} else {
assert.equal(kind, "storage");
const { startStorageSite } =
await import("../../scripts/lib/browser-scenario-storage-e2e.mjs");
site = await startStorageSite();
origin = site.origin;
ordinary = "/storage-main";
}
const checkOrdinary = async () => {
const reply = await send(origin, ordinary);
assert.equal(reply.status, 200);
if (expected !== undefined) assert.equal(reply.body, expected);
else assert(reply.body.includes('id="main-report"'));
};
await checkOrdinary();
if (process.argv[3] === "listener-scope") {
assert.equal(kind, "storage");
const named = new URL(origin);
named.hostname = "localhost";
const localhost = await send(named.href, ordinary);
assert.equal(localhost.status, 200);
assert(localhost.body.includes('id="main-report"'));
const address = Object.values(networkInterfaces())
.flatMap((entries) => entries ?? [])
.find((entry) => entry.family === "IPv4" && !entry.internal);
assert(
address,
"Listener scope probe requires an owned non-loopback IPv4 interface",
);
const nonLoopback = new URL(origin);
nonLoopback.hostname = address.address;
await assert.rejects(send(nonLoopback.href, ordinary), {
code: "ECONNREFUSED",
});
console.log("storage: localhost served; non-loopback connection refused");
}
for (const target of [
"http://[",
"ftp://127.0.0.1/probe",
"ws://127.0.0.1/probe",
]) {
const reply = await send(origin, target);
assert.equal(reply.status, 400);
assert.equal(reply.headers["content-type"], "text/plain; charset=utf-8");
assert.equal(reply.headers["x-content-type-options"], "nosniff");
assert.equal(reply.body, "Invalid HTTP request target");
await checkOrdinary();
}
await site.close();
site = undefined;
await assert.rejects(send(origin, ordinary));
console.log(
`${kind}: target routing, invalid-target recovery and physical close passed`,
);
}, [
() => site?.close(),
() =>
closeVerifierResources(
owned.map((server) => async () => {
server.closeAllConnections();
await new Promise((resolve, reject) =>
server.close((cause) => (cause ? reject(cause) : resolve())),
);
}),
),
]);