@fedify/vocab-runtime 2.4.5-dev.2462 → 2.4.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/deno.json +5 -2
- package/dist/{docloader-BMHljyYb.d.cts → docloader-DOWjnNCz.d.cts} +4 -8
- package/dist/{docloader-BMHljyYb.d.ts → docloader-DOWjnNCz.d.ts} +4 -8
- package/dist/internal/jsonld-cache.cjs +1 -1
- package/dist/internal/jsonld-cache.d.cts +1 -1
- package/dist/internal/jsonld-cache.d.ts +1 -1
- package/dist/internal/jsonld-cache.js +1 -1
- package/dist/internal/portable-dereference.cjs +1 -1
- package/dist/internal/portable-dereference.d.cts +2 -2
- package/dist/internal/portable-dereference.d.ts +2 -2
- package/dist/internal/portable-dereference.js +1 -1
- package/dist/internal.cjs +6 -0
- package/dist/internal.d.cts +108 -0
- package/dist/internal.d.ts +108 -0
- package/dist/internal.js +3 -0
- package/dist/mod.cjs +104 -38
- package/dist/mod.d.cts +3 -336
- package/dist/mod.d.ts +4 -336
- package/dist/mod.js +104 -38
- package/dist/{portable-6Aj-7dZJ.d.cts → portable-CTnZWX7J.d.cts} +1 -1
- package/dist/{portable-nkTiqH0w.d.ts → portable-Cq-rhQgY.d.ts} +1 -1
- package/dist/public-fetch-Dvfvawei.cjs +527 -0
- package/dist/public-fetch-ziZ39L5I.js +510 -0
- package/dist/tests/{body-Dlnsb-QX.cjs → body-BO79kuv1.cjs} +1 -1
- package/dist/tests/{body-BXrFIcFa.mjs → body-CUzCsDZ4.mjs} +1 -1
- package/dist/tests/body.test.cjs +1 -1
- package/dist/tests/body.test.mjs +1 -1
- package/dist/tests/decimal.test.cjs +3 -3
- package/dist/tests/decimal.test.mjs +3 -3
- package/dist/tests/{docloader-c9BQWaF1.cjs → docloader-B79_YgeY.cjs} +111 -39
- package/dist/tests/{docloader-DsuF8YDf.mjs → docloader-C5ZHGEfO.mjs} +106 -40
- package/dist/tests/docloader.test.cjs +531 -1293
- package/dist/tests/docloader.test.mjs +472 -1252
- package/dist/tests/esm-Ds-eHTfY.mjs +1229 -0
- package/dist/tests/esm-Q2tdh5f3.cjs +1215 -0
- package/dist/tests/internal/portable-dereference.test.cjs +3 -3
- package/dist/tests/internal/portable-dereference.test.mjs +3 -3
- package/dist/tests/{jsonld-cache-BEXiiM-V.cjs → jsonld-cache-DKIWKxEX.cjs} +1 -1
- package/dist/tests/{jsonld-cache-DFnWsLuZ.mjs → jsonld-cache-KH40z9_7.mjs} +1 -1
- package/dist/tests/jsonld-cache.test.cjs +2 -2
- package/dist/tests/jsonld-cache.test.mjs +2 -2
- package/dist/tests/node-BvS3AhZ2.cjs +89 -0
- package/dist/tests/node-Cg4pRuYy.mjs +84 -0
- package/dist/tests/pin-grant-CPrpk1l4.cjs +70 -0
- package/dist/tests/pin-grant-H5XhM1Xs.mjs +53 -0
- package/dist/tests/pin-grant.test.cjs +87 -0
- package/dist/tests/pin-grant.test.d.cts +1 -0
- package/dist/tests/pin-grant.test.d.mts +1 -0
- package/dist/tests/pin-grant.test.mjs +88 -0
- package/dist/tests/pinners/bun.test.cjs +166 -0
- package/dist/tests/pinners/bun.test.d.cts +1 -0
- package/dist/tests/pinners/bun.test.d.mts +1 -0
- package/dist/tests/pinners/bun.test.mjs +167 -0
- package/dist/tests/pinners/node.test.cjs +269 -0
- package/dist/tests/pinners/node.test.d.cts +1 -0
- package/dist/tests/pinners/node.test.d.mts +1 -0
- package/dist/tests/pinners/node.test.mjs +268 -0
- package/dist/tests/pinners/pinners.test.cjs +196 -0
- package/dist/tests/pinners/pinners.test.d.cts +1 -0
- package/dist/tests/pinners/pinners.test.d.mts +1 -0
- package/dist/tests/pinners/pinners.test.mjs +197 -0
- package/dist/tests/pinners/testdata/node-child.cjs +135 -0
- package/dist/tests/pinners/testdata/node-child.d.cts +1 -0
- package/dist/tests/pinners/testdata/node-child.d.mts +1 -0
- package/dist/tests/pinners/testdata/node-child.mjs +134 -0
- package/dist/tests/pinners-CJi9ZkYs.cjs +220 -0
- package/dist/tests/pinners-zSofbiy2.mjs +203 -0
- package/dist/tests/{portable-dereference-DmTYDy2p.mjs → portable-dereference-BHRE5Pkm.mjs} +2 -2
- package/dist/tests/{portable-dereference-qTLMqe2_.cjs → portable-dereference-mRHrC7PI.cjs} +2 -2
- package/dist/tests/{portable-media-M7idvaUq.mjs → portable-media-BpVpe1gj.mjs} +3 -3
- package/dist/tests/{portable-media-CS4czG6X.cjs → portable-media-C3yY0W5E.cjs} +3 -3
- package/dist/tests/portable-media.test.cjs +1 -1
- package/dist/tests/portable-media.test.mjs +1 -1
- package/dist/tests/portable-workers.test.cjs +2 -2
- package/dist/tests/portable-workers.test.mjs +2 -2
- package/dist/tests/public-fetch-DNkPoks1.mjs +170 -0
- package/dist/tests/public-fetch-U822jbKB.cjs +186 -0
- package/dist/tests/public-fetch.test.cjs +1073 -0
- package/dist/tests/public-fetch.test.d.cts +1 -0
- package/dist/tests/public-fetch.test.d.mts +1 -0
- package/dist/tests/public-fetch.test.mjs +1074 -0
- package/dist/tests/{request-C336eT6A.mjs → request-3AP-HBJ6.mjs} +1 -1
- package/dist/tests/{request-CgTgsBUA.cjs → request-BhopJTZd.cjs} +1 -1
- package/dist/tests/request.test.cjs +1 -1
- package/dist/tests/request.test.mjs +1 -1
- package/dist/tests/support-BmzI3dRJ.cjs +363 -0
- package/dist/tests/support-CiHDVJgh.mjs +272 -0
- package/dist/tests/{url-BNakuZ8k.cjs → url-Bdj-rTcC.cjs} +88 -12
- package/dist/tests/{url-DMxmp7ZG.mjs → url-COj5Q8mb.mjs} +65 -13
- package/dist/tests/url.test.cjs +4 -1
- package/dist/tests/url.test.mjs +4 -1
- package/dist/url-4EE56sNP.d.cts +352 -0
- package/dist/url-4EE56sNP.d.ts +352 -0
- package/dist/{url-DrGTR8yv.cjs → url-DIKZnaJv.cjs} +88 -12
- package/dist/{url-Dzyp-NsC.js → url-mWveR5GA.js} +65 -13
- package/package.json +14 -3
- package/src/docloader.test.ts +696 -48
- package/src/docloader.ts +186 -75
- package/src/internal.ts +13 -0
- package/src/pin-grant.test.ts +118 -0
- package/src/pin-grant.ts +99 -0
- package/src/pinners/bun.test.ts +278 -0
- package/src/pinners/bun.ts +223 -0
- package/src/pinners/mod.ts +59 -0
- package/src/pinners/node.test.ts +415 -0
- package/src/pinners/node.ts +144 -0
- package/src/pinners/pinners.test.ts +316 -0
- package/src/pinners/testdata/node-child.ts +147 -0
- package/src/pinners/testdata/proxy-child.ts +82 -0
- package/src/pinners/testdata/support.ts +343 -0
- package/src/pinners/testdata/tls-cert.pem +12 -0
- package/src/pinners/testdata/tls-child.ts +88 -0
- package/src/pinners/testdata/tls-key.pem +5 -0
- package/src/pinners/types.ts +48 -0
- package/src/public-fetch.test.ts +1286 -0
- package/src/public-fetch.ts +338 -0
- package/src/testdata/public-fetch-deno-worker.ts +74 -0
- package/src/url.test.ts +11 -0
- package/src/url.ts +113 -22
- package/tsdown.config.ts +7 -2
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import { r as revokePinGrant, t as issuePinGrant } from "../pin-grant-H5XhM1Xs.mjs";
|
|
2
|
+
import { i as assertPublicAddress, n as UrlError } from "../url-COj5Q8mb.mjs";
|
|
3
|
+
import { t as createNodePinner } from "../node-Cg4pRuYy.mjs";
|
|
4
|
+
import { t as createPublicFetch } from "../public-fetch-DNkPoks1.mjs";
|
|
5
|
+
import { a as ipv6, c as isNode, d as resolveTestdata, f as runChild, h as startRecordingServer, i as ipv4, l as listen, m as startRecordingProxy, n as closeServer, p as startCountingServer, r as getLoopbackSupport, t as allowAll, u as resolveCompiledTestdata } from "../support-CiHDVJgh.mjs";
|
|
6
|
+
import process from "node:process";
|
|
7
|
+
import { deepStrictEqual, ok, rejects, strictEqual } from "node:assert";
|
|
8
|
+
import { test } from "@fedify/fixture";
|
|
9
|
+
import { readFileSync } from "node:fs";
|
|
10
|
+
import { createServer } from "node:https";
|
|
11
|
+
//#region src/pinners/node.test.ts
|
|
12
|
+
/**
|
|
13
|
+
* Creates a Node.js pinner whose undici agent the test can also use
|
|
14
|
+
* directly, by injecting an `Agent` class that remembers its instance.
|
|
15
|
+
*/
|
|
16
|
+
async function createPinnerWithAgent(options) {
|
|
17
|
+
const undici = await import("undici");
|
|
18
|
+
const agents = [];
|
|
19
|
+
class Agent extends undici.Agent {
|
|
20
|
+
constructor(options) {
|
|
21
|
+
super(options);
|
|
22
|
+
agents.push(this);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
const pinner = createNodePinner({
|
|
26
|
+
...options,
|
|
27
|
+
undici: { Agent }
|
|
28
|
+
});
|
|
29
|
+
strictEqual(agents.length, 1);
|
|
30
|
+
return {
|
|
31
|
+
pinner,
|
|
32
|
+
agent: agents[0]
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Sends a request through the agent directly, without redeeming a grant.
|
|
37
|
+
*/
|
|
38
|
+
function fetchThrough(agent, url) {
|
|
39
|
+
return fetch(url, { dispatcher: agent });
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Whether the error is the failure of the agent's lookup to find a live
|
|
43
|
+
* grant for the hostname.
|
|
44
|
+
*/
|
|
45
|
+
function isNoValidatedAddress(error) {
|
|
46
|
+
return error instanceof TypeError && error.cause instanceof UrlError && error.cause.message.startsWith("No validated address to connect to:");
|
|
47
|
+
}
|
|
48
|
+
/** Responds without keeping the connection alive. */
|
|
49
|
+
function respondAndClose(_, res) {
|
|
50
|
+
res.setHeader("Connection", "close");
|
|
51
|
+
res.end("pinned");
|
|
52
|
+
}
|
|
53
|
+
test("createNodePinner() checks IP literal hostnames before fetching", { ignore: !isNode }, async () => {
|
|
54
|
+
const { server, port, requests } = await startRecordingServer();
|
|
55
|
+
try {
|
|
56
|
+
const pinner = createNodePinner({
|
|
57
|
+
verifyAddress: assertPublicAddress,
|
|
58
|
+
undici: await import("undici")
|
|
59
|
+
});
|
|
60
|
+
const url = new URL(`http://127.0.0.1:${port}/`);
|
|
61
|
+
const grant = issuePinGrant(url, [ipv4("127.0.0.1")]);
|
|
62
|
+
await rejects(() => pinner.fetch(grant, new Request(url)), UrlError);
|
|
63
|
+
deepStrictEqual(requests, []);
|
|
64
|
+
} finally {
|
|
65
|
+
await closeServer(server);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
test("createNodePinner() connects only for live grants", { ignore: !isNode }, async () => {
|
|
69
|
+
const { server, port, requests } = await startRecordingServer("127.0.0.1", respondAndClose);
|
|
70
|
+
try {
|
|
71
|
+
const { pinner, agent } = await createPinnerWithAgent(allowAll);
|
|
72
|
+
const url = new URL(`http://pinned-host.invalid:${port}/`);
|
|
73
|
+
await rejects(() => fetchThrough(agent, url), isNoValidatedAddress);
|
|
74
|
+
const grant = issuePinGrant(url, [ipv4("127.0.0.1")]);
|
|
75
|
+
deepStrictEqual(await (await pinner.fetch(grant, new Request(url))).text(), "pinned");
|
|
76
|
+
deepStrictEqual(await (await fetchThrough(agent, url)).text(), "pinned");
|
|
77
|
+
await rejects(() => fetchThrough(agent, `http://other-host.invalid:${port}/`), isNoValidatedAddress);
|
|
78
|
+
revokePinGrant(grant);
|
|
79
|
+
await rejects(() => pinner.fetch(grant, new Request(url)), TypeError);
|
|
80
|
+
await rejects(() => fetchThrough(agent, url), isNoValidatedAddress);
|
|
81
|
+
deepStrictEqual(requests.length, 2);
|
|
82
|
+
} finally {
|
|
83
|
+
await closeServer(server);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
test("createNodePinner() forgets released grants", { ignore: !isNode }, async () => {
|
|
87
|
+
const { server, port, requests } = await startRecordingServer("127.0.0.1", respondAndClose);
|
|
88
|
+
try {
|
|
89
|
+
const { pinner, agent } = await createPinnerWithAgent(allowAll);
|
|
90
|
+
const url = new URL(`http://pinned-host.invalid:${port}/`);
|
|
91
|
+
const grants = [issuePinGrant(url, [ipv4("127.0.0.1")]), issuePinGrant(url, [ipv4("127.0.0.1")])];
|
|
92
|
+
for (const grant of grants) deepStrictEqual(await (await pinner.fetch(grant, new Request(url))).text(), "pinned");
|
|
93
|
+
await pinner.release(grants[0]);
|
|
94
|
+
deepStrictEqual(await (await fetchThrough(agent, url)).text(), "pinned");
|
|
95
|
+
await pinner.release(grants[1]);
|
|
96
|
+
await rejects(() => fetchThrough(agent, url), isNoValidatedAddress);
|
|
97
|
+
await pinner.release(grants[1]);
|
|
98
|
+
deepStrictEqual(requests.length, 3);
|
|
99
|
+
} finally {
|
|
100
|
+
await closeServer(server);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
test("createNodePinner() connects to the next address on failure", { ignore: !isNode }, async () => {
|
|
104
|
+
if (!(await getLoopbackSupport()).ipv6) return;
|
|
105
|
+
const { server, port, requests } = await startRecordingServer();
|
|
106
|
+
try {
|
|
107
|
+
const pinner = createNodePinner({
|
|
108
|
+
...allowAll,
|
|
109
|
+
undici: await import("undici")
|
|
110
|
+
});
|
|
111
|
+
const url = new URL(`http://pinned-host.invalid:${port}/`);
|
|
112
|
+
const grant = issuePinGrant(url, [ipv6("::1"), ipv4("127.0.0.1")]);
|
|
113
|
+
deepStrictEqual(await (await pinner.fetch(grant, new Request(url))).text(), "pinned");
|
|
114
|
+
deepStrictEqual(await (await pinner.fetch(grant, new Request(url, {
|
|
115
|
+
method: "POST",
|
|
116
|
+
body: "payload"
|
|
117
|
+
}))).text(), "pinned");
|
|
118
|
+
deepStrictEqual(requests.map(({ method, localAddress }) => [method, localAddress]), [["GET", "127.0.0.1"], ["POST", "127.0.0.1"]]);
|
|
119
|
+
} finally {
|
|
120
|
+
await closeServer(server);
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
/**
|
|
124
|
+
* Replaces the global `fetch()` while running the given function, as
|
|
125
|
+
* fetch-mock does, but returns the given responses as they are, while
|
|
126
|
+
* fetch-mock returns clones (tees) of them.
|
|
127
|
+
*/
|
|
128
|
+
async function withFetchStub(respond, fn) {
|
|
129
|
+
const original = globalThis.fetch;
|
|
130
|
+
globalThis.fetch = (_, init) => Promise.resolve(respond(init));
|
|
131
|
+
try {
|
|
132
|
+
return await fn();
|
|
133
|
+
} finally {
|
|
134
|
+
globalThis.fetch = original;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
test("createNodePinner() forgets the grants of closed sessions", { ignore: !isNode }, async (t) => {
|
|
138
|
+
for (const cancelFails of [false, true]) await t.step(`cancelFails: ${cancelFails}`, async () => {
|
|
139
|
+
const checked = [];
|
|
140
|
+
const { pinner, agent } = await createPinnerWithAgent({ verifyAddress({ address }) {
|
|
141
|
+
checked.push(address);
|
|
142
|
+
throw new UrlError(`Refused for the test: ${address}`);
|
|
143
|
+
} });
|
|
144
|
+
const released = [];
|
|
145
|
+
const observed = {
|
|
146
|
+
supports: (url) => pinner.supports(url),
|
|
147
|
+
fetch: (grant, request, signal) => pinner.fetch(grant, request, signal),
|
|
148
|
+
async release(grant) {
|
|
149
|
+
released.push(grant);
|
|
150
|
+
await pinner.release(grant);
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
const { openPublicFetch } = createPublicFetch({
|
|
154
|
+
pinner: () => Promise.resolve(observed),
|
|
155
|
+
lookup: () => Promise.reject(/* @__PURE__ */ new Error("Not used."))
|
|
156
|
+
});
|
|
157
|
+
const url = "http://pinned-host.invalid/";
|
|
158
|
+
const session = await openPublicFetch(url, { lookup: () => Promise.resolve([ipv4("8.8.8.8")]) });
|
|
159
|
+
const dispatchers = [];
|
|
160
|
+
let cancelled = 0;
|
|
161
|
+
const response = await withFetchStub((init) => {
|
|
162
|
+
dispatchers.push(init.dispatcher);
|
|
163
|
+
return new Response(new ReadableStream({ cancel() {
|
|
164
|
+
cancelled++;
|
|
165
|
+
if (cancelFails) throw new Error("Failed to cancel.");
|
|
166
|
+
} }));
|
|
167
|
+
}, () => session.fetch(url));
|
|
168
|
+
deepStrictEqual(dispatchers, [agent]);
|
|
169
|
+
await rejects(() => fetchThrough(agent, url), (error) => error instanceof TypeError && error.cause instanceof UrlError && error.cause.message === "Refused for the test: 8.8.8.8");
|
|
170
|
+
deepStrictEqual(checked, ["8.8.8.8"]);
|
|
171
|
+
ok(!response.bodyUsed);
|
|
172
|
+
await session.close();
|
|
173
|
+
deepStrictEqual(cancelled, 1);
|
|
174
|
+
deepStrictEqual(released.length, 1);
|
|
175
|
+
await rejects(() => fetchThrough(agent, url), isNoValidatedAddress);
|
|
176
|
+
deepStrictEqual(checked, ["8.8.8.8"]);
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
test("createNodePinner() bypasses environment proxies", { ignore: !isNode }, async (t) => {
|
|
180
|
+
const major = Number(process.versions.node.split(".")[0]);
|
|
181
|
+
const names = [
|
|
182
|
+
"HTTP_PROXY",
|
|
183
|
+
"HTTPS_PROXY",
|
|
184
|
+
"ALL_PROXY"
|
|
185
|
+
];
|
|
186
|
+
for (const useEnvProxy of [false, true]) await t.step(`NODE_USE_ENV_PROXY: ${useEnvProxy}`, async () => {
|
|
187
|
+
const proxy = await startRecordingProxy({ refuseTunnels: true });
|
|
188
|
+
const origin = await startRecordingServer();
|
|
189
|
+
const tls = await startCountingServer();
|
|
190
|
+
try {
|
|
191
|
+
const proxyUrl = `http://127.0.0.1:${proxy.port}`;
|
|
192
|
+
const output = await runChild(resolveCompiledTestdata("node-child.mjs", import.meta.url), [
|
|
193
|
+
"proxy",
|
|
194
|
+
String(origin.port),
|
|
195
|
+
String(tls.port)
|
|
196
|
+
], {
|
|
197
|
+
...Object.fromEntries(names.map((name) => [name, proxyUrl])),
|
|
198
|
+
...useEnvProxy ? { NODE_USE_ENV_PROXY: "1" } : {}
|
|
199
|
+
});
|
|
200
|
+
const targets = JSON.stringify(proxy.targets);
|
|
201
|
+
deepStrictEqual(output.version, process.version);
|
|
202
|
+
const controlTargets = [`GET http://127.0.0.1:${origin.port}/control`, `CONNECT 127.0.0.1:${origin.port}`];
|
|
203
|
+
if (useEnvProxy && major >= 24) ok(proxy.targets.some((target) => controlTargets.includes(target)), targets);
|
|
204
|
+
deepStrictEqual(proxy.targets.filter((target) => !controlTargets.includes(target)), [], targets);
|
|
205
|
+
deepStrictEqual(output.http, {
|
|
206
|
+
status: 200,
|
|
207
|
+
body: "pinned"
|
|
208
|
+
});
|
|
209
|
+
deepStrictEqual(origin.requests.filter(({ url }) => url === "/pinned").map(({ host }) => host), [`pinned-host.invalid:${origin.port}`]);
|
|
210
|
+
ok(output.https.error != null, JSON.stringify(output.https));
|
|
211
|
+
deepStrictEqual(tls.counter.connections, 1);
|
|
212
|
+
} finally {
|
|
213
|
+
await closeServer(proxy.server);
|
|
214
|
+
await closeServer(origin.server);
|
|
215
|
+
await closeServer(tls.server);
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
test("createNodePinner() verifies certificates against the hostname", { ignore: !isNode }, async () => {
|
|
220
|
+
const certPath = resolveTestdata("tls-cert.pem", import.meta.url);
|
|
221
|
+
const hosts = [];
|
|
222
|
+
const server = createServer({
|
|
223
|
+
cert: readFileSync(certPath),
|
|
224
|
+
key: readFileSync(resolveTestdata("tls-key.pem", import.meta.url))
|
|
225
|
+
}, (req, res) => {
|
|
226
|
+
hosts.push(req.headers.host ?? "");
|
|
227
|
+
res.end(req.headers.host);
|
|
228
|
+
});
|
|
229
|
+
const port = await listen(server);
|
|
230
|
+
try {
|
|
231
|
+
const output = await runChild(resolveCompiledTestdata("node-child.mjs", import.meta.url), ["tls", String(port)], { NODE_EXTRA_CA_CERTS: certPath });
|
|
232
|
+
deepStrictEqual(output.version, process.version);
|
|
233
|
+
deepStrictEqual(output.results, [
|
|
234
|
+
{
|
|
235
|
+
case: "pinned",
|
|
236
|
+
status: 200,
|
|
237
|
+
body: `pinned-host.invalid:${port}`
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
case: "unlisted",
|
|
241
|
+
error: "ERR_TLS_CERT_ALTNAME_INVALID"
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
case: "unlisted-again",
|
|
245
|
+
error: "ERR_TLS_CERT_ALTNAME_INVALID"
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
case: "alternate",
|
|
249
|
+
status: 200,
|
|
250
|
+
body: `alternate-host.invalid:${port}`
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
case: "signals",
|
|
254
|
+
status: 200,
|
|
255
|
+
body: `pinned-host.invalid:${port}`
|
|
256
|
+
}
|
|
257
|
+
], JSON.stringify(output.results));
|
|
258
|
+
deepStrictEqual(hosts, [
|
|
259
|
+
`pinned-host.invalid:${port}`,
|
|
260
|
+
`alternate-host.invalid:${port}`,
|
|
261
|
+
`pinned-host.invalid:${port}`
|
|
262
|
+
]);
|
|
263
|
+
} finally {
|
|
264
|
+
await closeServer(server);
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
//#endregion
|
|
268
|
+
export {};
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
const require_pin_grant = require("../pin-grant-CPrpk1l4.cjs");
|
|
2
|
+
const require_url = require("../url-Bdj-rTcC.cjs");
|
|
3
|
+
const require_pinners = require("../pinners-CJi9ZkYs.cjs");
|
|
4
|
+
const require_node = require("../node-BvS3AhZ2.cjs");
|
|
5
|
+
const require_support = require("../support-BmzI3dRJ.cjs");
|
|
6
|
+
let node_assert = require("node:assert");
|
|
7
|
+
let node_net = require("node:net");
|
|
8
|
+
let _fedify_fixture = require("@fedify/fixture");
|
|
9
|
+
let node_http = require("node:http");
|
|
10
|
+
//#region src/pinners/pinners.test.ts
|
|
11
|
+
/**
|
|
12
|
+
* The pinners of all runtimes. The behaviors they share are tested here, and
|
|
13
|
+
* the ones specific to each runtime in *bun.test.ts* and *node.test.ts*.
|
|
14
|
+
*/
|
|
15
|
+
const pinners = [{
|
|
16
|
+
name: "createBunPinner()",
|
|
17
|
+
connects: require_support.isBun,
|
|
18
|
+
checksBeforeFetch: true,
|
|
19
|
+
create: (options) => Promise.resolve(require_pinners.createBunPinner(options))
|
|
20
|
+
}, {
|
|
21
|
+
name: "createNodePinner()",
|
|
22
|
+
connects: require_support.isNode,
|
|
23
|
+
checksBeforeFetch: false,
|
|
24
|
+
create: async (options) => require_node.createNodePinner({
|
|
25
|
+
...options,
|
|
26
|
+
undici: await import("undici")
|
|
27
|
+
})
|
|
28
|
+
}];
|
|
29
|
+
for (const { name, connects, checksBeforeFetch, create } of pinners) {
|
|
30
|
+
(0, _fedify_fixture.test)(`${name} supports only HTTP and HTTPS`, async () => {
|
|
31
|
+
const pinner = await create(require_support.allowAll);
|
|
32
|
+
(0, node_assert.ok)(pinner.supports(new URL("http://pinned-host.invalid/")));
|
|
33
|
+
(0, node_assert.ok)(pinner.supports(new URL("https://pinned-host.invalid/")));
|
|
34
|
+
(0, node_assert.ok)(!pinner.supports(new URL("ftp://pinned-host.invalid/")));
|
|
35
|
+
(0, node_assert.ok)(!pinner.supports(new URL("ws://pinned-host.invalid/")));
|
|
36
|
+
});
|
|
37
|
+
(0, _fedify_fixture.test)(`${name} checks addresses right before connecting`, { ignore: !connects && !checksBeforeFetch }, async () => {
|
|
38
|
+
const { server, port, requests } = await require_support.startRecordingServer();
|
|
39
|
+
try {
|
|
40
|
+
const pinner = await create({ verifyAddress: require_url.assertPublicAddress });
|
|
41
|
+
const url = new URL(`http://pinned-host.invalid:${port}/`);
|
|
42
|
+
const grant = require_pin_grant.issuePinGrant(url, [require_support.ipv4("127.0.0.1")]);
|
|
43
|
+
for (const method of ["GET", "POST"]) await (0, node_assert.rejects)(() => pinner.fetch(grant, new Request(url, { method })), require_support.isAddressRejection);
|
|
44
|
+
(0, node_assert.deepStrictEqual)(requests, []);
|
|
45
|
+
} finally {
|
|
46
|
+
await require_support.closeServer(server);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
(0, _fedify_fixture.test)(`${name} does not fall back past a failed check`, { ignore: !connects && !checksBeforeFetch }, async () => {
|
|
50
|
+
const { server, port, requests } = await require_support.startRecordingServer();
|
|
51
|
+
try {
|
|
52
|
+
const checked = [];
|
|
53
|
+
const pinner = await create({ verifyAddress({ address }) {
|
|
54
|
+
checked.push(address);
|
|
55
|
+
if (address !== "127.0.0.1") throw new require_url.UrlError(`Invalid or private address: ${address}`);
|
|
56
|
+
} });
|
|
57
|
+
const url = new URL(`http://pinned-host.invalid:${port}/`);
|
|
58
|
+
const grant = require_pin_grant.issuePinGrant(url, [require_support.ipv4("192.0.2.1"), require_support.ipv4("127.0.0.1")]);
|
|
59
|
+
await (0, node_assert.rejects)(() => pinner.fetch(grant, new Request(url)), require_support.isAddressRejection);
|
|
60
|
+
(0, node_assert.deepStrictEqual)(checked, ["192.0.2.1"]);
|
|
61
|
+
(0, node_assert.deepStrictEqual)(requests, []);
|
|
62
|
+
} finally {
|
|
63
|
+
await require_support.closeServer(server);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
(0, _fedify_fixture.test)(`${name} redeems the grant for every request`, async () => {
|
|
67
|
+
const { server, port, requests } = await require_support.startRecordingServer();
|
|
68
|
+
try {
|
|
69
|
+
const pinner = await create(require_support.allowAll);
|
|
70
|
+
const url = new URL(`http://pinned-host.invalid:${port}/`);
|
|
71
|
+
const grant = require_pin_grant.issuePinGrant(url, [require_support.ipv4("127.0.0.1")]);
|
|
72
|
+
for (const other of [
|
|
73
|
+
`http://other-host.invalid:${port}/`,
|
|
74
|
+
`https://pinned-host.invalid:${port}/`,
|
|
75
|
+
`http://pinned-host.invalid:${port + 1}/`
|
|
76
|
+
]) await (0, node_assert.rejects)(() => pinner.fetch(grant, new Request(other)), TypeError);
|
|
77
|
+
for (const forged of [{}, Object.freeze(Object.create(null))]) await (0, node_assert.rejects)(() => pinner.fetch(forged, new Request(url)), TypeError);
|
|
78
|
+
require_pin_grant.revokePinGrant(grant);
|
|
79
|
+
await (0, node_assert.rejects)(() => pinner.fetch(grant, new Request(url)), TypeError);
|
|
80
|
+
(0, node_assert.deepStrictEqual)(requests, []);
|
|
81
|
+
} finally {
|
|
82
|
+
await require_support.closeServer(server);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
(0, _fedify_fixture.test)(`${name} connects to the pinned address`, { ignore: !connects }, async () => {
|
|
86
|
+
const { server, port, requests } = await require_support.startRecordingServer();
|
|
87
|
+
try {
|
|
88
|
+
const pinner = await create(require_support.allowAll);
|
|
89
|
+
const url = new URL(`http://pinned-host.invalid:${port}/path?query`);
|
|
90
|
+
const grant = require_pin_grant.issuePinGrant(url, [require_support.ipv4("127.0.0.1")]);
|
|
91
|
+
const response = await pinner.fetch(grant, new Request(url, { headers: { "X-Test": "header" } }));
|
|
92
|
+
(0, node_assert.deepStrictEqual)(response.status, 200);
|
|
93
|
+
(0, node_assert.deepStrictEqual)(await response.text(), "pinned");
|
|
94
|
+
(0, node_assert.deepStrictEqual)(await (await pinner.fetch(grant, new Request(url, {
|
|
95
|
+
method: "POST",
|
|
96
|
+
body: "payload"
|
|
97
|
+
}))).text(), "pinned");
|
|
98
|
+
(0, node_assert.deepStrictEqual)(requests.map(({ method, url, host, body }) => ({
|
|
99
|
+
method,
|
|
100
|
+
url,
|
|
101
|
+
host,
|
|
102
|
+
body
|
|
103
|
+
})), [{
|
|
104
|
+
method: "GET",
|
|
105
|
+
url: "/path?query",
|
|
106
|
+
host: `pinned-host.invalid:${port}`,
|
|
107
|
+
body: ""
|
|
108
|
+
}, {
|
|
109
|
+
method: "POST",
|
|
110
|
+
url: "/path?query",
|
|
111
|
+
host: `pinned-host.invalid:${port}`,
|
|
112
|
+
body: "payload"
|
|
113
|
+
}]);
|
|
114
|
+
} finally {
|
|
115
|
+
await require_support.closeServer(server);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
(0, _fedify_fixture.test)(`${name} does not follow redirects`, { ignore: !connects }, async () => {
|
|
119
|
+
const { server, port, requests } = await require_support.startRecordingServer("127.0.0.1", (_, res) => {
|
|
120
|
+
res.writeHead(302, { Location: "/target" });
|
|
121
|
+
res.end();
|
|
122
|
+
});
|
|
123
|
+
try {
|
|
124
|
+
const pinner = await create(require_support.allowAll);
|
|
125
|
+
const url = new URL(`http://pinned-host.invalid:${port}/`);
|
|
126
|
+
const grant = require_pin_grant.issuePinGrant(url, [require_support.ipv4("127.0.0.1")]);
|
|
127
|
+
const response = await pinner.fetch(grant, new Request(url, { redirect: "follow" }));
|
|
128
|
+
(0, node_assert.deepStrictEqual)(response.status, 302);
|
|
129
|
+
(0, node_assert.deepStrictEqual)(response.headers.get("Location"), "/target");
|
|
130
|
+
(0, node_assert.deepStrictEqual)(requests.length, 1);
|
|
131
|
+
} finally {
|
|
132
|
+
await require_support.closeServer(server);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
(0, _fedify_fixture.test)(`${name} aborts requests on either signal`, { ignore: !connects }, async () => {
|
|
136
|
+
let notifyReceived = () => {};
|
|
137
|
+
const server = (0, node_http.createServer)(() => notifyReceived());
|
|
138
|
+
const port = await require_support.listen(server);
|
|
139
|
+
try {
|
|
140
|
+
const pinner = await create(require_support.allowAll);
|
|
141
|
+
const url = new URL(`http://pinned-host.invalid:${port}/`);
|
|
142
|
+
const grant = require_pin_grant.issuePinGrant(url, [require_support.ipv4("127.0.0.1")]);
|
|
143
|
+
for (const which of ["request", "separate"]) {
|
|
144
|
+
const requestController = new AbortController();
|
|
145
|
+
const separateController = new AbortController();
|
|
146
|
+
const received = new Promise((resolve) => notifyReceived = resolve);
|
|
147
|
+
const response = pinner.fetch(grant, new Request(url, { signal: requestController.signal }), separateController.signal);
|
|
148
|
+
await received;
|
|
149
|
+
(which === "request" ? requestController : separateController).abort();
|
|
150
|
+
await (0, node_assert.rejects)(response, { name: "AbortError" }, which);
|
|
151
|
+
}
|
|
152
|
+
} finally {
|
|
153
|
+
await require_support.closeServer(server);
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
(0, _fedify_fixture.test)(`${name} does not deliver a POST request twice`, { ignore: !connects }, async () => {
|
|
157
|
+
if (!(await require_support.getLoopbackSupport()).dualStack) return;
|
|
158
|
+
let received = 0;
|
|
159
|
+
const server = (0, node_net.createServer)((socket) => {
|
|
160
|
+
let data = "";
|
|
161
|
+
socket.setEncoding("latin1");
|
|
162
|
+
socket.on("error", () => {});
|
|
163
|
+
socket.on("data", (chunk) => {
|
|
164
|
+
data += chunk;
|
|
165
|
+
const headEnd = data.indexOf("\r\n\r\n");
|
|
166
|
+
if (headEnd < 0) return;
|
|
167
|
+
const length = /^content-length:\s*(\d+)/im.exec(data.slice(0, headEnd))?.[1];
|
|
168
|
+
if (!(length == null ? data.endsWith("\r\n0\r\n\r\n") : data.length - headEnd - 4 >= Number(length))) return;
|
|
169
|
+
received++;
|
|
170
|
+
socket.destroy();
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
const port = await require_support.listen(server, "::");
|
|
174
|
+
try {
|
|
175
|
+
const pinner = await create(require_support.allowAll);
|
|
176
|
+
const url = new URL(`http://pinned-host.invalid:${port}/inbox`);
|
|
177
|
+
const grant = require_pin_grant.issuePinGrant(url, [require_support.ipv4("127.0.0.1"), require_support.ipv6("::1")]);
|
|
178
|
+
await (0, node_assert.rejects)(() => pinner.fetch(grant, new Request(url, {
|
|
179
|
+
method: "POST",
|
|
180
|
+
body: "activity"
|
|
181
|
+
})));
|
|
182
|
+
(0, node_assert.deepStrictEqual)(received, 1);
|
|
183
|
+
require_pin_grant.revokePinGrant(grant);
|
|
184
|
+
await pinner.release(grant);
|
|
185
|
+
const second = require_pin_grant.issuePinGrant(url, [require_support.ipv6("::1")]);
|
|
186
|
+
await (0, node_assert.rejects)(() => pinner.fetch(second, new Request(url, {
|
|
187
|
+
method: "POST",
|
|
188
|
+
body: "activity"
|
|
189
|
+
})));
|
|
190
|
+
(0, node_assert.deepStrictEqual)(received, 2);
|
|
191
|
+
} finally {
|
|
192
|
+
await require_support.closeServer(server);
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
//#endregion
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { r as revokePinGrant, t as issuePinGrant } from "../pin-grant-H5XhM1Xs.mjs";
|
|
2
|
+
import { i as assertPublicAddress, n as UrlError } from "../url-COj5Q8mb.mjs";
|
|
3
|
+
import { r as createBunPinner } from "../pinners-zSofbiy2.mjs";
|
|
4
|
+
import { t as createNodePinner } from "../node-Cg4pRuYy.mjs";
|
|
5
|
+
import { a as ipv6, c as isNode, h as startRecordingServer, i as ipv4, l as listen, n as closeServer, o as isAddressRejection, r as getLoopbackSupport, s as isBun, t as allowAll } from "../support-CiHDVJgh.mjs";
|
|
6
|
+
import { deepStrictEqual, ok, rejects } from "node:assert";
|
|
7
|
+
import { createServer } from "node:net";
|
|
8
|
+
import { test } from "@fedify/fixture";
|
|
9
|
+
import { createServer as createServer$1 } from "node:http";
|
|
10
|
+
//#region src/pinners/pinners.test.ts
|
|
11
|
+
/**
|
|
12
|
+
* The pinners of all runtimes. The behaviors they share are tested here, and
|
|
13
|
+
* the ones specific to each runtime in *bun.test.ts* and *node.test.ts*.
|
|
14
|
+
*/
|
|
15
|
+
const pinners = [{
|
|
16
|
+
name: "createBunPinner()",
|
|
17
|
+
connects: isBun,
|
|
18
|
+
checksBeforeFetch: true,
|
|
19
|
+
create: (options) => Promise.resolve(createBunPinner(options))
|
|
20
|
+
}, {
|
|
21
|
+
name: "createNodePinner()",
|
|
22
|
+
connects: isNode,
|
|
23
|
+
checksBeforeFetch: false,
|
|
24
|
+
create: async (options) => createNodePinner({
|
|
25
|
+
...options,
|
|
26
|
+
undici: await import("undici")
|
|
27
|
+
})
|
|
28
|
+
}];
|
|
29
|
+
for (const { name, connects, checksBeforeFetch, create } of pinners) {
|
|
30
|
+
test(`${name} supports only HTTP and HTTPS`, async () => {
|
|
31
|
+
const pinner = await create(allowAll);
|
|
32
|
+
ok(pinner.supports(new URL("http://pinned-host.invalid/")));
|
|
33
|
+
ok(pinner.supports(new URL("https://pinned-host.invalid/")));
|
|
34
|
+
ok(!pinner.supports(new URL("ftp://pinned-host.invalid/")));
|
|
35
|
+
ok(!pinner.supports(new URL("ws://pinned-host.invalid/")));
|
|
36
|
+
});
|
|
37
|
+
test(`${name} checks addresses right before connecting`, { ignore: !connects && !checksBeforeFetch }, async () => {
|
|
38
|
+
const { server, port, requests } = await startRecordingServer();
|
|
39
|
+
try {
|
|
40
|
+
const pinner = await create({ verifyAddress: assertPublicAddress });
|
|
41
|
+
const url = new URL(`http://pinned-host.invalid:${port}/`);
|
|
42
|
+
const grant = issuePinGrant(url, [ipv4("127.0.0.1")]);
|
|
43
|
+
for (const method of ["GET", "POST"]) await rejects(() => pinner.fetch(grant, new Request(url, { method })), isAddressRejection);
|
|
44
|
+
deepStrictEqual(requests, []);
|
|
45
|
+
} finally {
|
|
46
|
+
await closeServer(server);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
test(`${name} does not fall back past a failed check`, { ignore: !connects && !checksBeforeFetch }, async () => {
|
|
50
|
+
const { server, port, requests } = await startRecordingServer();
|
|
51
|
+
try {
|
|
52
|
+
const checked = [];
|
|
53
|
+
const pinner = await create({ verifyAddress({ address }) {
|
|
54
|
+
checked.push(address);
|
|
55
|
+
if (address !== "127.0.0.1") throw new UrlError(`Invalid or private address: ${address}`);
|
|
56
|
+
} });
|
|
57
|
+
const url = new URL(`http://pinned-host.invalid:${port}/`);
|
|
58
|
+
const grant = issuePinGrant(url, [ipv4("192.0.2.1"), ipv4("127.0.0.1")]);
|
|
59
|
+
await rejects(() => pinner.fetch(grant, new Request(url)), isAddressRejection);
|
|
60
|
+
deepStrictEqual(checked, ["192.0.2.1"]);
|
|
61
|
+
deepStrictEqual(requests, []);
|
|
62
|
+
} finally {
|
|
63
|
+
await closeServer(server);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
test(`${name} redeems the grant for every request`, async () => {
|
|
67
|
+
const { server, port, requests } = await startRecordingServer();
|
|
68
|
+
try {
|
|
69
|
+
const pinner = await create(allowAll);
|
|
70
|
+
const url = new URL(`http://pinned-host.invalid:${port}/`);
|
|
71
|
+
const grant = issuePinGrant(url, [ipv4("127.0.0.1")]);
|
|
72
|
+
for (const other of [
|
|
73
|
+
`http://other-host.invalid:${port}/`,
|
|
74
|
+
`https://pinned-host.invalid:${port}/`,
|
|
75
|
+
`http://pinned-host.invalid:${port + 1}/`
|
|
76
|
+
]) await rejects(() => pinner.fetch(grant, new Request(other)), TypeError);
|
|
77
|
+
for (const forged of [{}, Object.freeze(Object.create(null))]) await rejects(() => pinner.fetch(forged, new Request(url)), TypeError);
|
|
78
|
+
revokePinGrant(grant);
|
|
79
|
+
await rejects(() => pinner.fetch(grant, new Request(url)), TypeError);
|
|
80
|
+
deepStrictEqual(requests, []);
|
|
81
|
+
} finally {
|
|
82
|
+
await closeServer(server);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
test(`${name} connects to the pinned address`, { ignore: !connects }, async () => {
|
|
86
|
+
const { server, port, requests } = await startRecordingServer();
|
|
87
|
+
try {
|
|
88
|
+
const pinner = await create(allowAll);
|
|
89
|
+
const url = new URL(`http://pinned-host.invalid:${port}/path?query`);
|
|
90
|
+
const grant = issuePinGrant(url, [ipv4("127.0.0.1")]);
|
|
91
|
+
const response = await pinner.fetch(grant, new Request(url, { headers: { "X-Test": "header" } }));
|
|
92
|
+
deepStrictEqual(response.status, 200);
|
|
93
|
+
deepStrictEqual(await response.text(), "pinned");
|
|
94
|
+
deepStrictEqual(await (await pinner.fetch(grant, new Request(url, {
|
|
95
|
+
method: "POST",
|
|
96
|
+
body: "payload"
|
|
97
|
+
}))).text(), "pinned");
|
|
98
|
+
deepStrictEqual(requests.map(({ method, url, host, body }) => ({
|
|
99
|
+
method,
|
|
100
|
+
url,
|
|
101
|
+
host,
|
|
102
|
+
body
|
|
103
|
+
})), [{
|
|
104
|
+
method: "GET",
|
|
105
|
+
url: "/path?query",
|
|
106
|
+
host: `pinned-host.invalid:${port}`,
|
|
107
|
+
body: ""
|
|
108
|
+
}, {
|
|
109
|
+
method: "POST",
|
|
110
|
+
url: "/path?query",
|
|
111
|
+
host: `pinned-host.invalid:${port}`,
|
|
112
|
+
body: "payload"
|
|
113
|
+
}]);
|
|
114
|
+
} finally {
|
|
115
|
+
await closeServer(server);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
test(`${name} does not follow redirects`, { ignore: !connects }, async () => {
|
|
119
|
+
const { server, port, requests } = await startRecordingServer("127.0.0.1", (_, res) => {
|
|
120
|
+
res.writeHead(302, { Location: "/target" });
|
|
121
|
+
res.end();
|
|
122
|
+
});
|
|
123
|
+
try {
|
|
124
|
+
const pinner = await create(allowAll);
|
|
125
|
+
const url = new URL(`http://pinned-host.invalid:${port}/`);
|
|
126
|
+
const grant = issuePinGrant(url, [ipv4("127.0.0.1")]);
|
|
127
|
+
const response = await pinner.fetch(grant, new Request(url, { redirect: "follow" }));
|
|
128
|
+
deepStrictEqual(response.status, 302);
|
|
129
|
+
deepStrictEqual(response.headers.get("Location"), "/target");
|
|
130
|
+
deepStrictEqual(requests.length, 1);
|
|
131
|
+
} finally {
|
|
132
|
+
await closeServer(server);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
test(`${name} aborts requests on either signal`, { ignore: !connects }, async () => {
|
|
136
|
+
let notifyReceived = () => {};
|
|
137
|
+
const server = createServer$1(() => notifyReceived());
|
|
138
|
+
const port = await listen(server);
|
|
139
|
+
try {
|
|
140
|
+
const pinner = await create(allowAll);
|
|
141
|
+
const url = new URL(`http://pinned-host.invalid:${port}/`);
|
|
142
|
+
const grant = issuePinGrant(url, [ipv4("127.0.0.1")]);
|
|
143
|
+
for (const which of ["request", "separate"]) {
|
|
144
|
+
const requestController = new AbortController();
|
|
145
|
+
const separateController = new AbortController();
|
|
146
|
+
const received = new Promise((resolve) => notifyReceived = resolve);
|
|
147
|
+
const response = pinner.fetch(grant, new Request(url, { signal: requestController.signal }), separateController.signal);
|
|
148
|
+
await received;
|
|
149
|
+
(which === "request" ? requestController : separateController).abort();
|
|
150
|
+
await rejects(response, { name: "AbortError" }, which);
|
|
151
|
+
}
|
|
152
|
+
} finally {
|
|
153
|
+
await closeServer(server);
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
test(`${name} does not deliver a POST request twice`, { ignore: !connects }, async () => {
|
|
157
|
+
if (!(await getLoopbackSupport()).dualStack) return;
|
|
158
|
+
let received = 0;
|
|
159
|
+
const server = createServer((socket) => {
|
|
160
|
+
let data = "";
|
|
161
|
+
socket.setEncoding("latin1");
|
|
162
|
+
socket.on("error", () => {});
|
|
163
|
+
socket.on("data", (chunk) => {
|
|
164
|
+
data += chunk;
|
|
165
|
+
const headEnd = data.indexOf("\r\n\r\n");
|
|
166
|
+
if (headEnd < 0) return;
|
|
167
|
+
const length = /^content-length:\s*(\d+)/im.exec(data.slice(0, headEnd))?.[1];
|
|
168
|
+
if (!(length == null ? data.endsWith("\r\n0\r\n\r\n") : data.length - headEnd - 4 >= Number(length))) return;
|
|
169
|
+
received++;
|
|
170
|
+
socket.destroy();
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
const port = await listen(server, "::");
|
|
174
|
+
try {
|
|
175
|
+
const pinner = await create(allowAll);
|
|
176
|
+
const url = new URL(`http://pinned-host.invalid:${port}/inbox`);
|
|
177
|
+
const grant = issuePinGrant(url, [ipv4("127.0.0.1"), ipv6("::1")]);
|
|
178
|
+
await rejects(() => pinner.fetch(grant, new Request(url, {
|
|
179
|
+
method: "POST",
|
|
180
|
+
body: "activity"
|
|
181
|
+
})));
|
|
182
|
+
deepStrictEqual(received, 1);
|
|
183
|
+
revokePinGrant(grant);
|
|
184
|
+
await pinner.release(grant);
|
|
185
|
+
const second = issuePinGrant(url, [ipv6("::1")]);
|
|
186
|
+
await rejects(() => pinner.fetch(second, new Request(url, {
|
|
187
|
+
method: "POST",
|
|
188
|
+
body: "activity"
|
|
189
|
+
})));
|
|
190
|
+
deepStrictEqual(received, 2);
|
|
191
|
+
} finally {
|
|
192
|
+
await closeServer(server);
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
//#endregion
|
|
197
|
+
export {};
|