@plurnk/plurnk-schemes-http 1.3.12 → 1.4.0
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/.env.defaults +13 -11
- package/README.md +70 -23
- package/SPEC.md +461 -124
- package/dist/Browser.d.ts +2 -11
- package/dist/Browser.d.ts.map +1 -1
- package/dist/Browser.js +91 -69
- package/dist/Browser.js.map +1 -1
- package/dist/ContentType.d.ts +2 -0
- package/dist/ContentType.d.ts.map +1 -0
- package/dist/ContentType.js +15 -0
- package/dist/ContentType.js.map +1 -0
- package/dist/ErrorDetail.d.ts +6 -0
- package/dist/ErrorDetail.d.ts.map +1 -0
- package/dist/ErrorDetail.js +19 -0
- package/dist/ErrorDetail.js.map +1 -0
- package/dist/Guard.d.ts +1 -1
- package/dist/Guard.d.ts.map +1 -1
- package/dist/Guard.js +94 -53
- package/dist/Guard.js.map +1 -1
- package/dist/Http.d.ts.map +1 -1
- package/dist/Http.js +782 -273
- package/dist/Http.js.map +1 -1
- package/dist/WebFetcher.d.ts +38 -2
- package/dist/WebFetcher.d.ts.map +1 -1
- package/dist/WebFetcher.js +189 -48
- package/dist/WebFetcher.js.map +1 -1
- package/dist/Ws.d.ts +5 -1
- package/dist/Ws.d.ts.map +1 -1
- package/dist/Ws.js +431 -93
- package/dist/Ws.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -9
- package/dist/index.js.map +1 -1
- package/docs/http.md +95 -53
- package/docs/wss.md +39 -43
- package/package.json +14 -11
- package/scripts/install-browser.mjs +0 -19
package/dist/Browser.d.ts
CHANGED
|
@@ -3,15 +3,7 @@ interface PwResponse {
|
|
|
3
3
|
statusText(): string;
|
|
4
4
|
headers(): Record<string, string>;
|
|
5
5
|
}
|
|
6
|
-
interface PwRoute {
|
|
7
|
-
request(): {
|
|
8
|
-
url(): string;
|
|
9
|
-
};
|
|
10
|
-
continue(): Promise<void>;
|
|
11
|
-
abort(): Promise<void>;
|
|
12
|
-
}
|
|
13
6
|
interface PwPage {
|
|
14
|
-
route(pattern: string, handler: (route: PwRoute) => Promise<void>): Promise<void>;
|
|
15
7
|
goto(url: string, opts: {
|
|
16
8
|
waitUntil: "networkidle";
|
|
17
9
|
timeout: number;
|
|
@@ -70,14 +62,13 @@ export default class Browser {
|
|
|
70
62
|
#private;
|
|
71
63
|
constructor(factory?: ChromiumFactory);
|
|
72
64
|
ready(): Promise<PlaywrightMethod>;
|
|
73
|
-
render(url: string, { workerId, signal, headers,
|
|
65
|
+
render(url: string, { workerId, signal, headers, timeout }: {
|
|
74
66
|
workerId: number;
|
|
75
67
|
signal?: AbortSignal;
|
|
76
68
|
headers?: ReadonlyArray<readonly [string, string]>;
|
|
77
|
-
guard?: (url: string) => Promise<boolean>;
|
|
78
69
|
timeout?: number;
|
|
79
70
|
}): Promise<RenderResult>;
|
|
80
|
-
closeContext(workerId: number): void
|
|
71
|
+
closeContext(workerId: number): Promise<void>;
|
|
81
72
|
close(): Promise<void>;
|
|
82
73
|
}
|
|
83
74
|
export {};
|
package/dist/Browser.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Browser.d.ts","sourceRoot":"","sources":["../src/Browser.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Browser.d.ts","sourceRoot":"","sources":["../src/Browser.ts"],"names":[],"mappings":"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"}
|
package/dist/Browser.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
// and a future plurnk browser-troubleshooting MCP sits on the same warm pool.
|
|
1
|
+
// Headless-Chromium render foundation {§render-lifecycle}, derived from
|
|
2
|
+
// @possumtech/rummy.web's MIT WebFetcher by the same author. It is a standalone
|
|
3
|
+
// package surface whose initialization and shutdown follow {§handler-lifecycle}.
|
|
5
4
|
//
|
|
6
5
|
// Scope here is render-ONLY: navigate, let JS run + hydration settle, serialize
|
|
7
6
|
// the final DOM. It returns the true rendered page; it never cleans, strips,
|
|
@@ -13,13 +12,8 @@
|
|
|
13
12
|
// a minimal structural seam (`ChromiumEngine`) so it can be injected — fakes
|
|
14
13
|
// in unit tests, and a remote CDP endpoint (Lightpanda/browserless/shared
|
|
15
14
|
// chromium) swapped in via env with zero code change.
|
|
16
|
-
// Mobile device emulation (a Pixel-5-class profile)
|
|
17
|
-
//
|
|
18
|
-
// hint, not a per-host rewrite (schemes-http#4). Default ON; set
|
|
19
|
-
// PLURNK_SCHEMES_HTTP_MOBILE=0 to render as desktop (escape hatch for the tail
|
|
20
|
-
// of sites that serve degraded/blocked mobile content). NOTE: Wikipedia gates
|
|
21
|
-
// mobile on the domain, not the UA, so this is a no-op there — by design; its
|
|
22
|
-
// desktop page already extracts clean.
|
|
15
|
+
// Mobile device emulation (a Pixel-5-class profile) is the configured default;
|
|
16
|
+
// PLURNK_SCHEMES_HTTP_MOBILE=0 selects a desktop context {§http-config}.
|
|
23
17
|
// The ONE browser identity both acquisition paths present (render context AND
|
|
24
18
|
// the byte-path fetch) — ordinary Chrome traffic, never an automated-client or
|
|
25
19
|
// plurnk fingerprint. Model-supplied {User-Agent: …} target blocks override it.
|
|
@@ -31,20 +25,16 @@ const MOBILE_CONTEXT = Object.freeze({
|
|
|
31
25
|
isMobile: true,
|
|
32
26
|
hasTouch: true,
|
|
33
27
|
});
|
|
34
|
-
//
|
|
35
|
-
// so an unset value is a config crash, not a silent default. "0" = desktop.
|
|
28
|
+
// Required floor-set knob; unset is a configuration failure {§http-config}.
|
|
36
29
|
const mobileEmulation = () => {
|
|
37
30
|
const raw = process.env.PLURNK_SCHEMES_HTTP_MOBILE;
|
|
38
31
|
if (raw === undefined)
|
|
39
32
|
throw new Error("Browser: required env PLURNK_SCHEMES_HTTP_MOBILE is unset — see .env.defaults");
|
|
40
33
|
return raw === "0" ? undefined : MOBILE_CONTEXT;
|
|
41
34
|
};
|
|
42
|
-
// Required numeric
|
|
43
|
-
//
|
|
44
|
-
//
|
|
45
|
-
// unset or non-numeric value fails hard at first use, naming the var. All
|
|
46
|
-
// three numerics are render-path-only, so a byte-fetch-only deployment never
|
|
47
|
-
// pays them — but a rendering one must configure them.
|
|
35
|
+
// Required numeric lookup. `.env.defaults` owns values; call sites own when a
|
|
36
|
+
// value becomes necessary, and no in-code fallback hides its absence
|
|
37
|
+
// {§http-config}.
|
|
48
38
|
export const requireNumEnv = (key) => {
|
|
49
39
|
const raw = process.env[key];
|
|
50
40
|
if (raw === undefined)
|
|
@@ -56,10 +46,7 @@ export const requireNumEnv = (key) => {
|
|
|
56
46
|
};
|
|
57
47
|
// Default factory: lazy-import the real chromium. The cast bridges Playwright's
|
|
58
48
|
// full type to our structural view at the single trusted library boundary.
|
|
59
|
-
const defaultFactory = async () =>
|
|
60
|
-
process.env.PLAYWRIGHT_BROWSERS_PATH ??= "0";
|
|
61
|
-
return (await import("playwright")).chromium;
|
|
62
|
-
};
|
|
49
|
+
const defaultFactory = async () => (await import("playwright")).chromium;
|
|
63
50
|
const requireBoolEnv = (key) => {
|
|
64
51
|
const raw = process.env[key];
|
|
65
52
|
if (raw !== "0" && raw !== "1")
|
|
@@ -115,9 +102,9 @@ export default class Browser {
|
|
|
115
102
|
#factory;
|
|
116
103
|
#browser = null;
|
|
117
104
|
#launching = null;
|
|
118
|
-
// One BrowserContext per worker — cookies / cache /
|
|
119
|
-
// that opened it, no cross-worker bleed.
|
|
120
|
-
//
|
|
105
|
+
// One atomically acquired BrowserContext per worker — cookies / cache /
|
|
106
|
+
// storage scoped to the worker that opened it, no cross-worker bleed.
|
|
107
|
+
// Promises make overlapping first renders share the same acquisition.
|
|
121
108
|
#contexts = new Map();
|
|
122
109
|
#idleTimer = null;
|
|
123
110
|
// Inject a factory in tests; production lazy-imports playwright.
|
|
@@ -133,44 +120,60 @@ export default class Browser {
|
|
|
133
120
|
// Render a URL to its final serialized DOM. Opens a page in the worker's
|
|
134
121
|
// context, navigates with the settle+salvage strategy, serializes, closes
|
|
135
122
|
// the page. Throws on navigation failure (the caller maps it to a status).
|
|
136
|
-
async render(url, { workerId, signal, headers,
|
|
123
|
+
async render(url, { workerId, signal, headers, timeout = requireNumEnv("PLURNK_SCHEMES_HTTP_FETCH_TIMEOUT") }) {
|
|
137
124
|
const context = await this.#getContext(workerId);
|
|
138
125
|
const page = await context.newPage();
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
//
|
|
142
|
-
//
|
|
143
|
-
//
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
await page.setExtraHTTPHeaders(Object.fromEntries(headers));
|
|
152
|
-
// Abort cascades by closing the page — an in-flight goto rejects with
|
|
153
|
-
// "Target closed", surfacing promptly instead of blocking on timeout.
|
|
154
|
-
const onAbort = () => { page.close().catch(() => { }); };
|
|
126
|
+
let closeFailure;
|
|
127
|
+
let closePromise;
|
|
128
|
+
// {§render-lifecycle} Abort and final settlement share one immediately
|
|
129
|
+
// observed close promise, so neither a duplicate call nor an unhandled
|
|
130
|
+
// rejection can escape the render boundary.
|
|
131
|
+
const closePage = () => {
|
|
132
|
+
closePromise ??= Promise.resolve()
|
|
133
|
+
.then(() => page.close())
|
|
134
|
+
.catch((cause) => { closeFailure = { cause }; });
|
|
135
|
+
return closePromise;
|
|
136
|
+
};
|
|
137
|
+
const onAbort = () => { void closePage(); };
|
|
155
138
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
156
|
-
|
|
157
|
-
// retroactively, so close now — the navigation must not proceed.
|
|
158
|
-
if (signal?.aborted)
|
|
159
|
-
onAbort();
|
|
139
|
+
let outcome;
|
|
160
140
|
try {
|
|
141
|
+
if (signal?.aborted) {
|
|
142
|
+
onAbort();
|
|
143
|
+
signal.throwIfAborted();
|
|
144
|
+
}
|
|
145
|
+
// Playwright's page header API is single-valued; byte acquisition preserves duplicates.
|
|
146
|
+
if (headers && headers.length > 0)
|
|
147
|
+
await page.setExtraHTTPHeaders(Object.fromEntries(headers));
|
|
161
148
|
const response = await this.#safeGoto(page, url, timeout);
|
|
162
149
|
const html = await page.content();
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
150
|
+
outcome = {
|
|
151
|
+
result: {
|
|
152
|
+
status: response?.status() ?? 200,
|
|
153
|
+
statusText: response?.statusText() ?? "",
|
|
154
|
+
headers: response ? Object.entries(response.headers()) : [],
|
|
155
|
+
html,
|
|
156
|
+
},
|
|
168
157
|
};
|
|
169
158
|
}
|
|
159
|
+
catch (cause) {
|
|
160
|
+
outcome = { cause };
|
|
161
|
+
}
|
|
170
162
|
finally {
|
|
171
163
|
signal?.removeEventListener("abort", onAbort);
|
|
172
|
-
await
|
|
164
|
+
await closePage();
|
|
165
|
+
}
|
|
166
|
+
if (outcome === undefined)
|
|
167
|
+
throw new Error("Browser render settled without an outcome");
|
|
168
|
+
if ("cause" in outcome) {
|
|
169
|
+
if (closeFailure !== undefined) {
|
|
170
|
+
throw new AggregateError([outcome.cause, closeFailure.cause], "Browser render and page close failed");
|
|
171
|
+
}
|
|
172
|
+
throw outcome.cause;
|
|
173
173
|
}
|
|
174
|
+
if (closeFailure !== undefined)
|
|
175
|
+
throw closeFailure.cause;
|
|
176
|
+
return outcome.result;
|
|
174
177
|
}
|
|
175
178
|
// page.goto with the salvage path. networkidle timing out while the DOM has
|
|
176
179
|
// already rendered substantive body text is the chatty-page case: the
|
|
@@ -230,27 +233,40 @@ export default class Browser {
|
|
|
230
233
|
// PLURNK_SCHEMES_HTTP_MOBILE=0.
|
|
231
234
|
async #getContext(workerId) {
|
|
232
235
|
this.#touchIdle();
|
|
233
|
-
|
|
234
|
-
if (
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
236
|
+
let context = this.#contexts.get(workerId);
|
|
237
|
+
if (context === undefined) {
|
|
238
|
+
context = (async () => {
|
|
239
|
+
const browser = await this.#getBrowser();
|
|
240
|
+
return browser.newContext(mobileEmulation());
|
|
241
|
+
})();
|
|
242
|
+
this.#contexts.set(workerId, context);
|
|
243
|
+
}
|
|
244
|
+
try {
|
|
245
|
+
return await context;
|
|
246
|
+
}
|
|
247
|
+
catch (error) {
|
|
248
|
+
if (this.#contexts.get(workerId) === context)
|
|
249
|
+
this.#contexts.delete(workerId);
|
|
250
|
+
throw error;
|
|
251
|
+
}
|
|
240
252
|
}
|
|
241
253
|
// Drop the worker's context (run end or abort). Closing it cascades to any
|
|
242
|
-
// in-flight page in that context.
|
|
243
|
-
closeContext(workerId) {
|
|
254
|
+
// in-flight page in that context.
|
|
255
|
+
async closeContext(workerId) {
|
|
244
256
|
const context = this.#contexts.get(workerId);
|
|
245
257
|
if (!context)
|
|
246
258
|
return;
|
|
247
259
|
this.#contexts.delete(workerId);
|
|
248
|
-
context.close()
|
|
260
|
+
await (await context).close();
|
|
249
261
|
}
|
|
250
262
|
#touchIdle() {
|
|
251
263
|
if (this.#idleTimer)
|
|
252
264
|
clearTimeout(this.#idleTimer);
|
|
253
|
-
this.#idleTimer = setTimeout(() => {
|
|
265
|
+
this.#idleTimer = setTimeout(() => {
|
|
266
|
+
void this.close().catch((error) => {
|
|
267
|
+
console.error("Browser idle cleanup failed", { error });
|
|
268
|
+
});
|
|
269
|
+
}, requireNumEnv("PLURNK_SCHEMES_HTTP_IDLE_TIMEOUT"));
|
|
254
270
|
this.#idleTimer.unref?.();
|
|
255
271
|
}
|
|
256
272
|
// Tear everything down: per-worker contexts then the browser. In CDP mode
|
|
@@ -262,12 +278,18 @@ export default class Browser {
|
|
|
262
278
|
}
|
|
263
279
|
const contexts = [...this.#contexts.values()];
|
|
264
280
|
this.#contexts.clear();
|
|
265
|
-
await Promise.allSettled(contexts.map((
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
this.#browser = null;
|
|
269
|
-
}
|
|
281
|
+
const contextResults = await Promise.allSettled(contexts.map(async (context) => (await context).close()));
|
|
282
|
+
const browser = this.#browser;
|
|
283
|
+
this.#browser = null;
|
|
270
284
|
this.#launching = null;
|
|
285
|
+
const browserResults = browser === null ? [] : await Promise.allSettled([browser.close()]);
|
|
286
|
+
const errors = [...contextResults, ...browserResults]
|
|
287
|
+
.filter((result) => result.status === "rejected")
|
|
288
|
+
.flatMap((result) => result.reason instanceof AggregateError
|
|
289
|
+
? [...result.reason.errors]
|
|
290
|
+
: [result.reason]);
|
|
291
|
+
if (errors.length > 0)
|
|
292
|
+
throw new AggregateError(errors, "Browser shutdown failed");
|
|
271
293
|
}
|
|
272
294
|
}
|
|
273
295
|
//# sourceMappingURL=Browser.js.map
|
package/dist/Browser.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Browser.js","sourceRoot":"","sources":["../src/Browser.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"Browser.js","sourceRoot":"","sources":["../src/Browser.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ContentType.d.ts","sourceRoot":"","sources":["../src/ContentType.ts"],"names":[],"mappings":"AAOA,eAAO,MAAM,gBAAgB,gBAAiB,MAAM,GAAG,IAAI,KAAG,MAO7D,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// HTTP response media-type normalization {§http-text-decoding}. Classification
|
|
2
|
+
// consumes the WHATWG MIME essence; invalid or absent metadata is not text proof.
|
|
3
|
+
import { MIMEType } from "node:util";
|
|
4
|
+
const UNKNOWN_RESPONSE_MIMETYPE = "application/octet-stream";
|
|
5
|
+
export const responseMimetype = (contentType) => {
|
|
6
|
+
if (contentType === null)
|
|
7
|
+
return UNKNOWN_RESPONSE_MIMETYPE;
|
|
8
|
+
try {
|
|
9
|
+
return new MIMEType(contentType).essence;
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return UNKNOWN_RESPONSE_MIMETYPE;
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
//# sourceMappingURL=ContentType.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ContentType.js","sourceRoot":"","sources":["../src/ContentType.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,kFAAkF;AAElF,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAErC,MAAM,yBAAyB,GAAG,0BAA0B,CAAC;AAE7D,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,WAA0B,EAAU,EAAE;IACnE,IAAI,WAAW,KAAK,IAAI;QAAE,OAAO,yBAAyB,CAAC;IAC3D,IAAI,CAAC;QACD,OAAO,IAAI,QAAQ,CAAC,WAAW,CAAC,CAAC,OAAO,CAAC;IAC7C,CAAC;IAAC,MAAM,CAAC;QACL,OAAO,yBAAyB,CAAC;IACrC,CAAC;AACL,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ErrorDetail.d.ts","sourceRoot":"","sources":["../src/ErrorDetail.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,kBAAkB,2CAA2C,CAAC;AAE3E,MAAM,CAAC,OAAO,OAAO,WAAW;IAC5B,MAAM,CAAC,eAAe,IAAI,MAAM,CAU/B;IAED,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAGpD;CACJ"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export const ERROR_DETAIL_LIMIT = "PLURNK_SCHEMES_HTTP_ERROR_DETAIL_LIMIT";
|
|
2
|
+
export default class ErrorDetail {
|
|
3
|
+
static configuredLimit() {
|
|
4
|
+
const raw = process.env[ERROR_DETAIL_LIMIT];
|
|
5
|
+
if (raw === undefined || raw === "") {
|
|
6
|
+
throw new Error(`${ERROR_DETAIL_LIMIT} must be set.`);
|
|
7
|
+
}
|
|
8
|
+
const limit = Number(raw);
|
|
9
|
+
if (!Number.isSafeInteger(limit) || limit < 0) {
|
|
10
|
+
throw new Error(`${ERROR_DETAIL_LIMIT} must be a non-negative integer.`);
|
|
11
|
+
}
|
|
12
|
+
return limit;
|
|
13
|
+
}
|
|
14
|
+
static preview(value, limit) {
|
|
15
|
+
const text = value instanceof Error ? value.message : String(value);
|
|
16
|
+
return text.length > limit ? `${text.slice(0, limit)}...` : text;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=ErrorDetail.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ErrorDetail.js","sourceRoot":"","sources":["../src/ErrorDetail.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,kBAAkB,GAAG,wCAAwC,CAAC;AAE3E,MAAM,CAAC,OAAO,OAAO,WAAW;IAC5B,MAAM,CAAC,eAAe;QAClB,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;QAC5C,IAAI,GAAG,KAAK,SAAS,IAAI,GAAG,KAAK,EAAE,EAAE,CAAC;YAClC,MAAM,IAAI,KAAK,CAAC,GAAG,kBAAkB,eAAe,CAAC,CAAC;QAC1D,CAAC;QACD,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;QAC1B,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;YAC5C,MAAM,IAAI,KAAK,CAAC,GAAG,kBAAkB,kCAAkC,CAAC,CAAC;QAC7E,CAAC;QACD,OAAO,KAAK,CAAC;IACjB,CAAC;IAED,MAAM,CAAC,OAAO,CAAC,KAAc,EAAE,KAAa;QACxC,MAAM,IAAI,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACpE,OAAO,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;IACrE,CAAC;CACJ"}
|
package/dist/Guard.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ export default class Guard {
|
|
|
8
8
|
static fetch(raw: string, init: {
|
|
9
9
|
method: string;
|
|
10
10
|
body: string | undefined;
|
|
11
|
-
headers:
|
|
11
|
+
headers: ReadonlyArray<readonly [string, string]>;
|
|
12
12
|
}, signal: AbortSignal): Promise<Response>;
|
|
13
13
|
}
|
|
14
14
|
//# sourceMappingURL=Guard.d.ts.map
|
package/dist/Guard.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Guard.d.ts","sourceRoot":"","sources":["../src/Guard.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Guard.d.ts","sourceRoot":"","sources":["../src/Guard.ts"],"names":[],"mappings":"AA+DA,qBAAa,iBAAkB,SAAQ,KAAK;IACxC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,YAAY,GAAG,EAAE,MAAM,EAKtB;CACJ;AAED,MAAM,CAAC,OAAO,OAAO,KAAK;IAGtB,MAAM,CAAC,eAAe,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAM1C;IAID,OAAa,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CActD;IAOD,OAAa,KAAK,CACd,GAAG,EAAE,MAAM,EACX,IAAI,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;QAAC,OAAO,EAAE,aAAa,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;KAAE,EACrG,MAAM,EAAE,WAAW,GACpB,OAAO,CAAC,QAAQ,CAAC,CA4BnB;CACJ"}
|
package/dist/Guard.js
CHANGED
|
@@ -1,59 +1,83 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// are followed MANUALLY and re-guarded per hop. The residual DNS-rebinding
|
|
6
|
-
// sliver (the runtime re-resolves after our check) is accepted day-one, same as
|
|
7
|
-
// the reference. Byte path only day-one; the chromium render navigation is a
|
|
8
|
-
// separate surface tracked on #456.
|
|
1
|
+
// {§automatic-fetch-check} — WebFetcher's initial byte target and every
|
|
2
|
+
// redirect hop must resolve only to ordinary globally reachable unicast
|
|
3
|
+
// addresses. Redirects are followed manually so checking and Fetch-standard
|
|
4
|
+
// request transitions share one automatic-acquisition seam.
|
|
9
5
|
import dns from "node:dns/promises";
|
|
10
6
|
import net from "node:net";
|
|
11
7
|
import { requireNumEnv } from "./Browser.js";
|
|
12
|
-
|
|
13
|
-
|
|
8
|
+
const BLOCKED = new net.BlockList();
|
|
9
|
+
for (const [network, prefix] of [
|
|
10
|
+
["0.0.0.0", 8],
|
|
11
|
+
["10.0.0.0", 8],
|
|
12
|
+
["100.64.0.0", 10],
|
|
13
|
+
["127.0.0.0", 8],
|
|
14
|
+
["169.254.0.0", 16],
|
|
15
|
+
["172.16.0.0", 12],
|
|
16
|
+
["192.0.0.0", 24],
|
|
17
|
+
["192.0.2.0", 24],
|
|
18
|
+
["192.88.99.0", 24],
|
|
19
|
+
["192.168.0.0", 16],
|
|
20
|
+
["198.18.0.0", 15],
|
|
21
|
+
["198.51.100.0", 24],
|
|
22
|
+
["203.0.113.0", 24],
|
|
23
|
+
["224.0.0.0", 4],
|
|
24
|
+
["240.0.0.0", 4],
|
|
25
|
+
])
|
|
26
|
+
BLOCKED.addSubnet(network, prefix, "ipv4");
|
|
27
|
+
for (const [network, prefix] of [
|
|
28
|
+
["2001::", 23],
|
|
29
|
+
["2001:db8::", 32],
|
|
30
|
+
["2002::", 16],
|
|
31
|
+
["3fff::", 20],
|
|
32
|
+
])
|
|
33
|
+
BLOCKED.addSubnet(network, prefix, "ipv6");
|
|
34
|
+
const IPV4_MAPPED = new net.BlockList();
|
|
35
|
+
IPV4_MAPPED.addSubnet("0.0.0.0", 0, "ipv4");
|
|
36
|
+
const GLOBAL_IPV6 = new net.BlockList();
|
|
37
|
+
GLOBAL_IPV6.addSubnet("2000::", 3, "ipv6");
|
|
38
|
+
const REDIRECT_STATUSES = new Set([301, 302, 303, 307, 308]);
|
|
39
|
+
const BODY_HEADERS = new Set(["content-encoding", "content-language", "content-location", "content-type"]);
|
|
40
|
+
const AUTHORIZATION_HEADERS = new Set(["authorization"]);
|
|
41
|
+
const withoutHeaders = (headers, names) => headers
|
|
42
|
+
.filter(([name]) => !names.has(name.toLowerCase()))
|
|
43
|
+
.map(([name, value]) => [name, value]);
|
|
44
|
+
const safeUrl = (raw) => {
|
|
45
|
+
try {
|
|
46
|
+
const url = new URL(raw);
|
|
47
|
+
url.username = "";
|
|
48
|
+
url.password = "";
|
|
49
|
+
return url.href;
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return raw;
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
// A target (or redirect hop) that automatic acquisition cannot follow.
|
|
56
|
+
// WebFetcher collapses this with other unavailable-page outcomes to `null`.
|
|
14
57
|
export class GuardBlockedError extends Error {
|
|
15
58
|
url;
|
|
16
59
|
constructor(url) {
|
|
17
|
-
|
|
60
|
+
const sanitized = safeUrl(url);
|
|
61
|
+
super(`Automatic fetch check: ${sanitized} is not a public http(s) target`);
|
|
18
62
|
this.name = "GuardBlockedError";
|
|
19
|
-
this.url =
|
|
63
|
+
this.url = sanitized;
|
|
20
64
|
}
|
|
21
65
|
}
|
|
22
66
|
export default class Guard {
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
// 0.0.0.0/8; v6 ULA + link-local + v4-mapped re-checked as v4.
|
|
67
|
+
// Static protocol ranges, not operator policy. IPv4-mapped IPv6 is checked
|
|
68
|
+
// against the same v4 block list after WHATWG URL canonicalization.
|
|
26
69
|
static isPublicAddress(ip) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
if (a === 0 || a === 10 || a === 127)
|
|
30
|
-
return false;
|
|
31
|
-
if (a === 169 && b === 254)
|
|
32
|
-
return false;
|
|
33
|
-
if (a === 172 && b >= 16 && b <= 31)
|
|
34
|
-
return false;
|
|
35
|
-
if (a === 192 && b === 168)
|
|
36
|
-
return false;
|
|
37
|
-
if (a === 100 && b >= 64 && b <= 127)
|
|
38
|
-
return false;
|
|
39
|
-
return true;
|
|
40
|
-
}
|
|
41
|
-
const v6 = ip.toLowerCase();
|
|
42
|
-
if (v6 === "::" || v6 === "::1")
|
|
43
|
-
return false;
|
|
44
|
-
if (v6.startsWith("fc") || v6.startsWith("fd"))
|
|
45
|
-
return false;
|
|
46
|
-
if (/^fe[89ab]/.test(v6))
|
|
70
|
+
const family = net.isIP(ip);
|
|
71
|
+
if (family === 0)
|
|
47
72
|
return false;
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
73
|
+
if (family === 4)
|
|
74
|
+
return !BLOCKED.check(ip, "ipv4");
|
|
75
|
+
if (IPV4_MAPPED.check(ip, "ipv6"))
|
|
76
|
+
return !BLOCKED.check(ip, "ipv6");
|
|
77
|
+
return GLOBAL_IPV6.check(ip, "ipv6") && !BLOCKED.check(ip, "ipv6");
|
|
52
78
|
}
|
|
53
|
-
// http(s)
|
|
79
|
+
// http(s) only, no localhost, and EVERY resolved address public. An IP
|
|
54
80
|
// literal skips DNS; a hostname resolves and every A/AAAA must be public.
|
|
55
|
-
// ws:/wss: ride the same range check — a WebSocket into private space is the
|
|
56
|
-
// same SSRF as a fetch (the Ws scheme guards its target through here).
|
|
57
81
|
static async isPublicUrl(raw) {
|
|
58
82
|
let url;
|
|
59
83
|
try {
|
|
@@ -62,7 +86,9 @@ export default class Guard {
|
|
|
62
86
|
catch {
|
|
63
87
|
return false;
|
|
64
88
|
}
|
|
65
|
-
if (!["http:", "https:"
|
|
89
|
+
if (!["http:", "https:"].includes(url.protocol))
|
|
90
|
+
return false;
|
|
91
|
+
if (url.username !== "" || url.password !== "")
|
|
66
92
|
return false;
|
|
67
93
|
const host = url.hostname.toLowerCase().replace(/^\[|\]$/g, "");
|
|
68
94
|
if (host === "localhost" || host.endsWith(".localhost"))
|
|
@@ -77,31 +103,46 @@ export default class Guard {
|
|
|
77
103
|
return false; // unresolvable is dead anyway
|
|
78
104
|
}
|
|
79
105
|
}
|
|
80
|
-
//
|
|
81
|
-
// manually (floor-set knob — unset crashes). Method
|
|
82
|
-
//
|
|
83
|
-
//
|
|
84
|
-
//
|
|
106
|
+
// Automatic fetch: re-check every hop, follow up to PLURNK_SCHEMES_HTTP_REDIRECTS
|
|
107
|
+
// manually (floor-set knob — unset crashes). Method, body, body headers, and
|
|
108
|
+
// cross-origin Authorization follow WHATWG HTTP-redirect fetch. Throws
|
|
109
|
+
// GuardBlockedError on a non-public hop; hands back the final response
|
|
110
|
+
// (including a terminal redirect when hops run out) otherwise.
|
|
85
111
|
static async fetch(raw, init, signal) {
|
|
86
112
|
let target = raw;
|
|
87
113
|
let method = init.method;
|
|
88
114
|
let body = init.body;
|
|
115
|
+
let headers = init.headers.map(([name, value]) => [name, value]);
|
|
89
116
|
let hops = requireNumEnv("PLURNK_SCHEMES_HTTP_REDIRECTS");
|
|
90
117
|
while (true) {
|
|
91
|
-
|
|
118
|
+
let current;
|
|
119
|
+
try {
|
|
120
|
+
current = new URL(target);
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
92
123
|
throw new GuardBlockedError(target);
|
|
93
|
-
|
|
94
|
-
if (
|
|
124
|
+
}
|
|
125
|
+
if (!["http:", "https:"].includes(current.protocol) || !(await Guard.isPublicUrl(current.href))) {
|
|
126
|
+
throw new GuardBlockedError(current.href);
|
|
127
|
+
}
|
|
128
|
+
const response = await fetch(current.href, { method, body, headers, signal, redirect: "manual" });
|
|
129
|
+
if (!REDIRECT_STATUSES.has(response.status))
|
|
95
130
|
return response;
|
|
96
131
|
const location = response.headers.get("location");
|
|
97
132
|
if (location === null || hops <= 0)
|
|
98
133
|
return response;
|
|
134
|
+
await response.body?.cancel();
|
|
99
135
|
hops -= 1;
|
|
100
|
-
|
|
101
|
-
if (
|
|
136
|
+
const next = new URL(location, current);
|
|
137
|
+
if (((response.status === 301 || response.status === 302) && method === "POST")
|
|
138
|
+
|| (response.status === 303 && method !== "GET" && method !== "HEAD")) {
|
|
102
139
|
method = "GET";
|
|
103
140
|
body = undefined;
|
|
141
|
+
headers = withoutHeaders(headers, BODY_HEADERS);
|
|
104
142
|
}
|
|
143
|
+
if (current.origin !== next.origin)
|
|
144
|
+
headers = withoutHeaders(headers, AUTHORIZATION_HEADERS);
|
|
145
|
+
target = next.href;
|
|
105
146
|
}
|
|
106
147
|
}
|
|
107
148
|
}
|