@frontmcp/utils 1.8.0 → 1.8.2
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/content/content.d.ts +1 -1
- package/env/browser-env.js +18 -3
- package/env/edge-scope.d.ts +7 -0
- package/env/node-env.js +18 -2
- package/esm/env/browser-env.mjs +16 -3
- package/esm/env/node-env.mjs +16 -2
- package/esm/index.mjs +163 -23
- package/index.d.ts +2 -0
- package/index.js +168 -23
- package/net/index.d.ts +4 -0
- package/net/ip-address.d.ts +35 -0
- package/net/redirect.d.ts +9 -0
- package/package.json +2 -2
package/content/content.d.ts
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* - Error → { name, message, stack }
|
|
15
15
|
* - Map → plain object
|
|
16
16
|
* - Set → array
|
|
17
|
-
* - Circular references → dropped (undefined)
|
|
17
|
+
* - Circular references → dropped (undefined); a value referenced more than once without a cycle is kept each time
|
|
18
18
|
*
|
|
19
19
|
* @param value - Any JavaScript value
|
|
20
20
|
* @returns JSON-safe version of the value
|
package/env/browser-env.js
CHANGED
|
@@ -32,6 +32,23 @@ __export(browser_env_exports, {
|
|
|
32
32
|
supportsAnsi: () => supportsAnsi
|
|
33
33
|
});
|
|
34
34
|
module.exports = __toCommonJS(browser_env_exports);
|
|
35
|
+
|
|
36
|
+
// libs/utils/src/env/edge-scope.ts
|
|
37
|
+
function hasEdgeGlobalScope() {
|
|
38
|
+
if (typeof globalThis === "undefined") return false;
|
|
39
|
+
if ("EdgeRuntime" in globalThis) return true;
|
|
40
|
+
if (!("caches" in globalThis) || "window" in globalThis) return false;
|
|
41
|
+
return !isBrowserWorkerScope();
|
|
42
|
+
}
|
|
43
|
+
function isBrowserWorkerScope() {
|
|
44
|
+
const userAgent = typeof navigator === "undefined" ? void 0 : navigator.userAgent;
|
|
45
|
+
if (userAgent === void 0 || userAgent === "Cloudflare-Workers") return false;
|
|
46
|
+
return ["DedicatedWorkerGlobalScope", "SharedWorkerGlobalScope", "ServiceWorkerGlobalScope"].some(
|
|
47
|
+
(scopeName) => scopeName in globalThis
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// libs/utils/src/env/browser-env.ts
|
|
35
52
|
function getEnv(_key, defaultValue) {
|
|
36
53
|
return defaultValue;
|
|
37
54
|
}
|
|
@@ -53,9 +70,7 @@ function isDebug() {
|
|
|
53
70
|
function setEnv(_key, _value) {
|
|
54
71
|
}
|
|
55
72
|
function isEdgeRuntime() {
|
|
56
|
-
|
|
57
|
-
if (typeof globalThis !== "undefined" && "caches" in globalThis && !("window" in globalThis)) return true;
|
|
58
|
-
return false;
|
|
73
|
+
return hasEdgeGlobalScope();
|
|
59
74
|
}
|
|
60
75
|
function isServerless() {
|
|
61
76
|
return false;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether the global scope looks like an edge runtime (Cloudflare Workers, Vercel Edge, Deno Deploy):
|
|
3
|
+
* `EdgeRuntime` is defined, or `caches` is present without `window`. A browser Web Worker also has
|
|
4
|
+
* `caches` and no `window`, and defines its dedicated, shared or service worker scope. Cloudflare's workerd
|
|
5
|
+
* defines `ServiceWorkerGlobalScope` too, so it is told apart by its user agent, or by having no `navigator`.
|
|
6
|
+
*/
|
|
7
|
+
export declare function hasEdgeGlobalScope(): boolean;
|
package/env/node-env.js
CHANGED
|
@@ -32,6 +32,23 @@ __export(node_env_exports, {
|
|
|
32
32
|
supportsAnsi: () => supportsAnsi
|
|
33
33
|
});
|
|
34
34
|
module.exports = __toCommonJS(node_env_exports);
|
|
35
|
+
|
|
36
|
+
// libs/utils/src/env/edge-scope.ts
|
|
37
|
+
function hasEdgeGlobalScope() {
|
|
38
|
+
if (typeof globalThis === "undefined") return false;
|
|
39
|
+
if ("EdgeRuntime" in globalThis) return true;
|
|
40
|
+
if (!("caches" in globalThis) || "window" in globalThis) return false;
|
|
41
|
+
return !isBrowserWorkerScope();
|
|
42
|
+
}
|
|
43
|
+
function isBrowserWorkerScope() {
|
|
44
|
+
const userAgent = typeof navigator === "undefined" ? void 0 : navigator.userAgent;
|
|
45
|
+
if (userAgent === void 0 || userAgent === "Cloudflare-Workers") return false;
|
|
46
|
+
return ["DedicatedWorkerGlobalScope", "SharedWorkerGlobalScope", "ServiceWorkerGlobalScope"].some(
|
|
47
|
+
(scopeName) => scopeName in globalThis
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// libs/utils/src/env/node-env.ts
|
|
35
52
|
function getEnv(key, defaultValue) {
|
|
36
53
|
return process.env[key] ?? defaultValue;
|
|
37
54
|
}
|
|
@@ -55,8 +72,7 @@ function setEnv(key, value) {
|
|
|
55
72
|
process.env[key] = value;
|
|
56
73
|
}
|
|
57
74
|
function isEdgeRuntime() {
|
|
58
|
-
if (
|
|
59
|
-
if (typeof globalThis !== "undefined" && "caches" in globalThis && !("window" in globalThis)) return true;
|
|
75
|
+
if (hasEdgeGlobalScope()) return true;
|
|
60
76
|
return process.env["EDGE_RUNTIME"] !== void 0 && process.env["VERCEL_ENV"] !== void 0;
|
|
61
77
|
}
|
|
62
78
|
function isServerless() {
|
package/esm/env/browser-env.mjs
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
import { createRequire as __frontmcpCreateRequire } from 'module';
|
|
2
2
|
const require = __frontmcpCreateRequire(import.meta.url || 'file:///');
|
|
3
|
+
// libs/utils/src/env/edge-scope.ts
|
|
4
|
+
function hasEdgeGlobalScope() {
|
|
5
|
+
if (typeof globalThis === "undefined") return false;
|
|
6
|
+
if ("EdgeRuntime" in globalThis) return true;
|
|
7
|
+
if (!("caches" in globalThis) || "window" in globalThis) return false;
|
|
8
|
+
return !isBrowserWorkerScope();
|
|
9
|
+
}
|
|
10
|
+
function isBrowserWorkerScope() {
|
|
11
|
+
const userAgent = typeof navigator === "undefined" ? void 0 : navigator.userAgent;
|
|
12
|
+
if (userAgent === void 0 || userAgent === "Cloudflare-Workers") return false;
|
|
13
|
+
return ["DedicatedWorkerGlobalScope", "SharedWorkerGlobalScope", "ServiceWorkerGlobalScope"].some(
|
|
14
|
+
(scopeName) => scopeName in globalThis
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
3
18
|
// libs/utils/src/env/browser-env.ts
|
|
4
19
|
function getEnv(_key, defaultValue) {
|
|
5
20
|
return defaultValue;
|
|
@@ -22,9 +37,7 @@ function isDebug() {
|
|
|
22
37
|
function setEnv(_key, _value) {
|
|
23
38
|
}
|
|
24
39
|
function isEdgeRuntime() {
|
|
25
|
-
|
|
26
|
-
if (typeof globalThis !== "undefined" && "caches" in globalThis && !("window" in globalThis)) return true;
|
|
27
|
-
return false;
|
|
40
|
+
return hasEdgeGlobalScope();
|
|
28
41
|
}
|
|
29
42
|
function isServerless() {
|
|
30
43
|
return false;
|
package/esm/env/node-env.mjs
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
import { createRequire as __frontmcpCreateRequire } from 'module';
|
|
2
2
|
const require = __frontmcpCreateRequire(import.meta.url || 'file:///');
|
|
3
|
+
// libs/utils/src/env/edge-scope.ts
|
|
4
|
+
function hasEdgeGlobalScope() {
|
|
5
|
+
if (typeof globalThis === "undefined") return false;
|
|
6
|
+
if ("EdgeRuntime" in globalThis) return true;
|
|
7
|
+
if (!("caches" in globalThis) || "window" in globalThis) return false;
|
|
8
|
+
return !isBrowserWorkerScope();
|
|
9
|
+
}
|
|
10
|
+
function isBrowserWorkerScope() {
|
|
11
|
+
const userAgent = typeof navigator === "undefined" ? void 0 : navigator.userAgent;
|
|
12
|
+
if (userAgent === void 0 || userAgent === "Cloudflare-Workers") return false;
|
|
13
|
+
return ["DedicatedWorkerGlobalScope", "SharedWorkerGlobalScope", "ServiceWorkerGlobalScope"].some(
|
|
14
|
+
(scopeName) => scopeName in globalThis
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
3
18
|
// libs/utils/src/env/node-env.ts
|
|
4
19
|
function getEnv(key, defaultValue) {
|
|
5
20
|
return process.env[key] ?? defaultValue;
|
|
@@ -24,8 +39,7 @@ function setEnv(key, value) {
|
|
|
24
39
|
process.env[key] = value;
|
|
25
40
|
}
|
|
26
41
|
function isEdgeRuntime() {
|
|
27
|
-
if (
|
|
28
|
-
if (typeof globalThis !== "undefined" && "caches" in globalThis && !("window" in globalThis)) return true;
|
|
42
|
+
if (hasEdgeGlobalScope()) return true;
|
|
29
43
|
return process.env["EDGE_RUNTIME"] !== void 0 && process.env["VERCEL_ENV"] !== void 0;
|
|
30
44
|
}
|
|
31
45
|
function isServerless() {
|
package/esm/index.mjs
CHANGED
|
@@ -4378,7 +4378,18 @@ import { pathToFileURL } from "url";
|
|
|
4378
4378
|
|
|
4379
4379
|
// libs/utils/src/content/content.ts
|
|
4380
4380
|
function sanitizeToJson(value) {
|
|
4381
|
-
const
|
|
4381
|
+
const ancestors = /* @__PURE__ */ new WeakSet();
|
|
4382
|
+
function withinAncestors(container, build) {
|
|
4383
|
+
if (ancestors.has(container)) {
|
|
4384
|
+
return void 0;
|
|
4385
|
+
}
|
|
4386
|
+
ancestors.add(container);
|
|
4387
|
+
try {
|
|
4388
|
+
return build();
|
|
4389
|
+
} finally {
|
|
4390
|
+
ancestors.delete(container);
|
|
4391
|
+
}
|
|
4392
|
+
}
|
|
4382
4393
|
function sanitize(val) {
|
|
4383
4394
|
if (typeof val === "function" || typeof val === "symbol") {
|
|
4384
4395
|
return void 0;
|
|
@@ -4397,35 +4408,31 @@ function sanitizeToJson(value) {
|
|
|
4397
4408
|
};
|
|
4398
4409
|
}
|
|
4399
4410
|
if (val instanceof Map) {
|
|
4400
|
-
|
|
4401
|
-
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
|
|
4411
|
+
return withinAncestors(val, () => {
|
|
4412
|
+
const obj = {};
|
|
4413
|
+
for (const [k, v] of val.entries()) {
|
|
4414
|
+
obj[String(k)] = sanitize(v);
|
|
4415
|
+
}
|
|
4416
|
+
return obj;
|
|
4417
|
+
});
|
|
4405
4418
|
}
|
|
4406
4419
|
if (val instanceof Set) {
|
|
4407
|
-
return Array.from(val).map(sanitize);
|
|
4420
|
+
return withinAncestors(val, () => Array.from(val).map(sanitize));
|
|
4408
4421
|
}
|
|
4409
4422
|
if (Array.isArray(val)) {
|
|
4410
|
-
|
|
4411
|
-
return void 0;
|
|
4412
|
-
}
|
|
4413
|
-
seen.add(val);
|
|
4414
|
-
return val.map(sanitize);
|
|
4423
|
+
return withinAncestors(val, () => val.map(sanitize));
|
|
4415
4424
|
}
|
|
4416
4425
|
if (val && typeof val === "object") {
|
|
4417
|
-
|
|
4418
|
-
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
|
|
4422
|
-
|
|
4423
|
-
|
|
4424
|
-
if (clean !== void 0) {
|
|
4425
|
-
sanitized[key] = clean;
|
|
4426
|
+
return withinAncestors(val, () => {
|
|
4427
|
+
const sanitized = {};
|
|
4428
|
+
for (const [key, value2] of Object.entries(val)) {
|
|
4429
|
+
const clean = sanitize(value2);
|
|
4430
|
+
if (clean !== void 0) {
|
|
4431
|
+
sanitized[key] = clean;
|
|
4432
|
+
}
|
|
4426
4433
|
}
|
|
4427
|
-
|
|
4428
|
-
|
|
4434
|
+
return sanitized;
|
|
4435
|
+
});
|
|
4429
4436
|
}
|
|
4430
4437
|
return val;
|
|
4431
4438
|
}
|
|
@@ -4587,6 +4594,134 @@ function getCookie(cookieHeader, name) {
|
|
|
4587
4594
|
return parseCookies(cookieHeader)[name];
|
|
4588
4595
|
}
|
|
4589
4596
|
|
|
4597
|
+
// libs/utils/src/net/ip-address.ts
|
|
4598
|
+
var IPV4_OCTET_PATTERN = /^(0|[1-9]\d{0,2})$/;
|
|
4599
|
+
var IPV6_GROUP_PATTERN = /^[0-9a-f]{1,4}$/;
|
|
4600
|
+
var IPV4_BLOCKS = compileBlocks(
|
|
4601
|
+
[
|
|
4602
|
+
["0.0.0.0/8", "this-network"],
|
|
4603
|
+
["10.0.0.0/8", "private"],
|
|
4604
|
+
["100.64.0.0/10", "carrier-grade-nat"],
|
|
4605
|
+
["127.0.0.0/8", "loopback"],
|
|
4606
|
+
["169.254.0.0/16", "link-local"],
|
|
4607
|
+
["172.16.0.0/12", "private"],
|
|
4608
|
+
["192.0.0.0/24", "ietf-protocol"],
|
|
4609
|
+
["192.168.0.0/16", "private"],
|
|
4610
|
+
["198.18.0.0/15", "benchmarking"],
|
|
4611
|
+
["224.0.0.0/4", "multicast"],
|
|
4612
|
+
["255.255.255.255/32", "reserved"],
|
|
4613
|
+
["240.0.0.0/4", "reserved"]
|
|
4614
|
+
],
|
|
4615
|
+
parseIpv4
|
|
4616
|
+
);
|
|
4617
|
+
var IPV6_BLOCKS = compileBlocks(
|
|
4618
|
+
[
|
|
4619
|
+
["::/128", "unspecified"],
|
|
4620
|
+
["::1/128", "loopback"],
|
|
4621
|
+
["fd00:ec2::254/128", "cloud-metadata"],
|
|
4622
|
+
["fc00::/7", "private"],
|
|
4623
|
+
["fe80::/10", "link-local"],
|
|
4624
|
+
["fec0::/10", "site-local"],
|
|
4625
|
+
// RFC 8215 local-use NAT64: where the IPv4 address sits depends on the operator's prefix length.
|
|
4626
|
+
["64:ff9b:1::/48", "local-use-nat64"],
|
|
4627
|
+
["ff00::/8", "multicast"]
|
|
4628
|
+
],
|
|
4629
|
+
expandIpv6
|
|
4630
|
+
);
|
|
4631
|
+
var IPV4_EMBEDDINGS = compileBlocks(
|
|
4632
|
+
[
|
|
4633
|
+
["::ffff:0:0/96", 6],
|
|
4634
|
+
["::ffff:0:0:0/96", 6],
|
|
4635
|
+
["::/96", 6],
|
|
4636
|
+
["64:ff9b::/96", 6],
|
|
4637
|
+
["2002::/16", 1]
|
|
4638
|
+
],
|
|
4639
|
+
expandIpv6
|
|
4640
|
+
);
|
|
4641
|
+
function parseIpv4(address) {
|
|
4642
|
+
const parts = address.split(".");
|
|
4643
|
+
if (parts.length !== 4 || !parts.every((part) => IPV4_OCTET_PATTERN.test(part))) return void 0;
|
|
4644
|
+
const octets = parts.map(Number);
|
|
4645
|
+
return octets.every((octet) => octet <= 255) ? octets : void 0;
|
|
4646
|
+
}
|
|
4647
|
+
function expandIpv6(address) {
|
|
4648
|
+
const zoneIndex = address.indexOf("%");
|
|
4649
|
+
let text = (zoneIndex === -1 ? address : address.slice(0, zoneIndex)).toLowerCase();
|
|
4650
|
+
const lastColonIndex = text.lastIndexOf(":");
|
|
4651
|
+
const trailingSegment = text.slice(lastColonIndex + 1);
|
|
4652
|
+
if (lastColonIndex !== -1 && trailingSegment.includes(".")) {
|
|
4653
|
+
const octets = parseIpv4(trailingSegment);
|
|
4654
|
+
if (!octets) return void 0;
|
|
4655
|
+
const highGroup = (octets[0] << 8 | octets[1]).toString(16);
|
|
4656
|
+
const lowGroup = (octets[2] << 8 | octets[3]).toString(16);
|
|
4657
|
+
text = `${text.slice(0, lastColonIndex + 1)}${highGroup}:${lowGroup}`;
|
|
4658
|
+
}
|
|
4659
|
+
const halves = text.split("::");
|
|
4660
|
+
if (halves.length > 2) return void 0;
|
|
4661
|
+
const head = parseIpv6Groups(halves[0]);
|
|
4662
|
+
if (!head) return void 0;
|
|
4663
|
+
if (halves.length === 1) return head.length === 8 ? head : void 0;
|
|
4664
|
+
const tail = parseIpv6Groups(halves[1]);
|
|
4665
|
+
if (!tail || head.length + tail.length > 7) return void 0;
|
|
4666
|
+
return [...head, ...new Array(8 - head.length - tail.length).fill(0), ...tail];
|
|
4667
|
+
}
|
|
4668
|
+
function extractEmbeddedIpv4(groups) {
|
|
4669
|
+
const octets = findEmbeddedIpv4Octets(groups);
|
|
4670
|
+
return octets ? octets.join(".") : void 0;
|
|
4671
|
+
}
|
|
4672
|
+
function classifyIpAddress(address) {
|
|
4673
|
+
const isBracketed = address.startsWith("[") && address.endsWith("]");
|
|
4674
|
+
const octets = isBracketed ? void 0 : parseIpv4(address);
|
|
4675
|
+
if (octets) return classifyIpv4(octets);
|
|
4676
|
+
const groups = expandIpv6(isBracketed ? address.slice(1, -1) : address);
|
|
4677
|
+
if (!groups) return void 0;
|
|
4678
|
+
const block = IPV6_BLOCKS.find((candidate) => matchesBlock(groups, candidate, 16));
|
|
4679
|
+
if (block) return { family: 6, range: block.value, cidr: block.cidr };
|
|
4680
|
+
const embeddedOctets = findEmbeddedIpv4Octets(groups);
|
|
4681
|
+
if (!embeddedOctets) return { family: 6, range: "public" };
|
|
4682
|
+
return { ...classifyIpv4(embeddedOctets), family: 6, embeddedIpv4: embeddedOctets.join(".") };
|
|
4683
|
+
}
|
|
4684
|
+
function classifyIpv4(octets) {
|
|
4685
|
+
const block = IPV4_BLOCKS.find((candidate) => matchesBlock(octets, candidate, 8));
|
|
4686
|
+
return block ? { family: 4, range: block.value, cidr: block.cidr } : { family: 4, range: "public" };
|
|
4687
|
+
}
|
|
4688
|
+
function findEmbeddedIpv4Octets(groups) {
|
|
4689
|
+
const embedding = IPV4_EMBEDDINGS.find((block) => matchesBlock(groups, block, 16));
|
|
4690
|
+
if (!embedding) return void 0;
|
|
4691
|
+
const highGroup = groups[embedding.value];
|
|
4692
|
+
const lowGroup = groups[embedding.value + 1];
|
|
4693
|
+
return [highGroup >> 8, highGroup & 255, lowGroup >> 8, lowGroup & 255];
|
|
4694
|
+
}
|
|
4695
|
+
function parseIpv6Groups(part) {
|
|
4696
|
+
if (part === "") return [];
|
|
4697
|
+
const groups = part.split(":");
|
|
4698
|
+
if (!groups.every((group) => IPV6_GROUP_PATTERN.test(group))) return void 0;
|
|
4699
|
+
return groups.map((group) => parseInt(group, 16));
|
|
4700
|
+
}
|
|
4701
|
+
function matchesBlock(words, block, bitsPerWord) {
|
|
4702
|
+
let remainingBits = block.prefixLength;
|
|
4703
|
+
for (let index = 0; remainingBits > 0; index++) {
|
|
4704
|
+
const comparedBits = Math.min(bitsPerWord, remainingBits);
|
|
4705
|
+
const ignoredBits = bitsPerWord - comparedBits;
|
|
4706
|
+
if (words[index] >> ignoredBits !== block.network[index] >> ignoredBits) return false;
|
|
4707
|
+
remainingBits -= comparedBits;
|
|
4708
|
+
}
|
|
4709
|
+
return true;
|
|
4710
|
+
}
|
|
4711
|
+
function compileBlocks(entries, parseNetwork) {
|
|
4712
|
+
return entries.map(([cidr, value]) => {
|
|
4713
|
+
const [networkText, prefixText] = cidr.split("/");
|
|
4714
|
+
const network = parseNetwork(networkText);
|
|
4715
|
+
if (!network) throw new Error(`Invalid network in address block "${cidr}"`);
|
|
4716
|
+
return { cidr, network, prefixLength: Number(prefixText), value };
|
|
4717
|
+
});
|
|
4718
|
+
}
|
|
4719
|
+
|
|
4720
|
+
// libs/utils/src/net/redirect.ts
|
|
4721
|
+
function isRedirectResponse(response) {
|
|
4722
|
+
return response.type === "opaqueredirect" || response.status >= 300 && response.status < 400;
|
|
4723
|
+
}
|
|
4724
|
+
|
|
4590
4725
|
// libs/utils/src/index.ts
|
|
4591
4726
|
init_fs2();
|
|
4592
4727
|
|
|
@@ -5776,6 +5911,7 @@ export {
|
|
|
5776
5911
|
buildSetCookie,
|
|
5777
5912
|
bytesToHex,
|
|
5778
5913
|
checkEntryAvailability,
|
|
5914
|
+
classifyIpAddress,
|
|
5779
5915
|
clearCachedSecret,
|
|
5780
5916
|
collapseChar,
|
|
5781
5917
|
collapseWhitespace,
|
|
@@ -5808,11 +5944,13 @@ export {
|
|
|
5808
5944
|
escapeHtmlAttr,
|
|
5809
5945
|
escapeJsString,
|
|
5810
5946
|
escapeScriptClose,
|
|
5947
|
+
expandIpv6,
|
|
5811
5948
|
expandTemplate,
|
|
5812
5949
|
expandUriTemplate,
|
|
5813
5950
|
expiresAtToTTL,
|
|
5814
5951
|
extname,
|
|
5815
5952
|
extractBracedParams,
|
|
5953
|
+
extractEmbeddedIpv4,
|
|
5816
5954
|
extractTemplateParams,
|
|
5817
5955
|
extractUriScheme,
|
|
5818
5956
|
fileExists,
|
|
@@ -5852,6 +5990,7 @@ export {
|
|
|
5852
5990
|
isNode,
|
|
5853
5991
|
isPatternSafe,
|
|
5854
5992
|
isProduction,
|
|
5993
|
+
isRedirectResponse,
|
|
5855
5994
|
isRsaPssAlg,
|
|
5856
5995
|
isSecretCached,
|
|
5857
5996
|
isSecretKeyData,
|
|
@@ -5874,6 +6013,7 @@ export {
|
|
|
5874
6013
|
mkdtemp,
|
|
5875
6014
|
normalizeTTL,
|
|
5876
6015
|
parseCookies,
|
|
6016
|
+
parseIpv4,
|
|
5877
6017
|
parseKeyData,
|
|
5878
6018
|
parseSecretData,
|
|
5879
6019
|
parseUriTemplate,
|
package/index.d.ts
CHANGED
|
@@ -13,6 +13,8 @@ export { trimSlashes, joinPath, pathResolve, pathJoin, basename, dirname, extnam
|
|
|
13
13
|
export { sanitizeToJson, inferMimeType, findNonFiniteNumber } from './content';
|
|
14
14
|
export { validateBaseUrl, buildSetCookie, parseCookies, getCookie, isLocalhost, isSecureRequest } from './http';
|
|
15
15
|
export type { CookieOptions, SecureDetectionRequest } from './http';
|
|
16
|
+
export { classifyIpAddress, expandIpv6, extractEmbeddedIpv4, parseIpv4, isRedirectResponse } from './net';
|
|
17
|
+
export type { IpAddressClassification, IpAddressRange, SpecialPurposeIpRange, RedirectCandidate } from './net';
|
|
16
18
|
export { readFile, readFileSync, readFileBuffer, writeFile, mkdir, rename, unlink, stat, copyFile, cp, readdir, readdirSync, realpath, rm, mkdtemp, symlink, access, fileExists, readJSON, writeJSON, ensureDir, ensureDirSync, isDirEmpty, runCmd, watchFile, getSpawnFn, } from './fs';
|
|
17
19
|
export type { FileWatcherHandle } from './fs';
|
|
18
20
|
export { escapeHtml, escapeHtmlAttr, escapeJsString, escapeScriptClose, safeJsonForScript } from './escape';
|
package/index.js
CHANGED
|
@@ -4022,6 +4022,7 @@ __export(index_exports, {
|
|
|
4022
4022
|
buildSetCookie: () => buildSetCookie,
|
|
4023
4023
|
bytesToHex: () => bytesToHex,
|
|
4024
4024
|
checkEntryAvailability: () => checkEntryAvailability,
|
|
4025
|
+
classifyIpAddress: () => classifyIpAddress,
|
|
4025
4026
|
clearCachedSecret: () => clearCachedSecret,
|
|
4026
4027
|
collapseChar: () => collapseChar,
|
|
4027
4028
|
collapseWhitespace: () => collapseWhitespace,
|
|
@@ -4054,11 +4055,13 @@ __export(index_exports, {
|
|
|
4054
4055
|
escapeHtmlAttr: () => escapeHtmlAttr,
|
|
4055
4056
|
escapeJsString: () => escapeJsString,
|
|
4056
4057
|
escapeScriptClose: () => escapeScriptClose,
|
|
4058
|
+
expandIpv6: () => expandIpv6,
|
|
4057
4059
|
expandTemplate: () => expandTemplate,
|
|
4058
4060
|
expandUriTemplate: () => expandUriTemplate,
|
|
4059
4061
|
expiresAtToTTL: () => expiresAtToTTL,
|
|
4060
4062
|
extname: () => import_path.extname,
|
|
4061
4063
|
extractBracedParams: () => extractBracedParams,
|
|
4064
|
+
extractEmbeddedIpv4: () => extractEmbeddedIpv4,
|
|
4062
4065
|
extractTemplateParams: () => extractTemplateParams,
|
|
4063
4066
|
extractUriScheme: () => extractUriScheme,
|
|
4064
4067
|
fileExists: () => fileExists,
|
|
@@ -4098,6 +4101,7 @@ __export(index_exports, {
|
|
|
4098
4101
|
isNode: () => isNode,
|
|
4099
4102
|
isPatternSafe: () => isPatternSafe,
|
|
4100
4103
|
isProduction: () => import_env3.isProduction,
|
|
4104
|
+
isRedirectResponse: () => isRedirectResponse,
|
|
4101
4105
|
isRsaPssAlg: () => isRsaPssAlg,
|
|
4102
4106
|
isSecretCached: () => isSecretCached,
|
|
4103
4107
|
isSecretKeyData: () => isSecretKeyData,
|
|
@@ -4120,6 +4124,7 @@ __export(index_exports, {
|
|
|
4120
4124
|
mkdtemp: () => mkdtemp,
|
|
4121
4125
|
normalizeTTL: () => normalizeTTL,
|
|
4122
4126
|
parseCookies: () => parseCookies,
|
|
4127
|
+
parseIpv4: () => parseIpv4,
|
|
4123
4128
|
parseKeyData: () => parseKeyData,
|
|
4124
4129
|
parseSecretData: () => parseSecretData,
|
|
4125
4130
|
parseUriTemplate: () => parseUriTemplate,
|
|
@@ -4603,7 +4608,18 @@ var import_url = require("url");
|
|
|
4603
4608
|
|
|
4604
4609
|
// libs/utils/src/content/content.ts
|
|
4605
4610
|
function sanitizeToJson(value) {
|
|
4606
|
-
const
|
|
4611
|
+
const ancestors = /* @__PURE__ */ new WeakSet();
|
|
4612
|
+
function withinAncestors(container, build) {
|
|
4613
|
+
if (ancestors.has(container)) {
|
|
4614
|
+
return void 0;
|
|
4615
|
+
}
|
|
4616
|
+
ancestors.add(container);
|
|
4617
|
+
try {
|
|
4618
|
+
return build();
|
|
4619
|
+
} finally {
|
|
4620
|
+
ancestors.delete(container);
|
|
4621
|
+
}
|
|
4622
|
+
}
|
|
4607
4623
|
function sanitize(val) {
|
|
4608
4624
|
if (typeof val === "function" || typeof val === "symbol") {
|
|
4609
4625
|
return void 0;
|
|
@@ -4622,35 +4638,31 @@ function sanitizeToJson(value) {
|
|
|
4622
4638
|
};
|
|
4623
4639
|
}
|
|
4624
4640
|
if (val instanceof Map) {
|
|
4625
|
-
|
|
4626
|
-
|
|
4627
|
-
|
|
4628
|
-
|
|
4629
|
-
|
|
4641
|
+
return withinAncestors(val, () => {
|
|
4642
|
+
const obj = {};
|
|
4643
|
+
for (const [k, v] of val.entries()) {
|
|
4644
|
+
obj[String(k)] = sanitize(v);
|
|
4645
|
+
}
|
|
4646
|
+
return obj;
|
|
4647
|
+
});
|
|
4630
4648
|
}
|
|
4631
4649
|
if (val instanceof Set) {
|
|
4632
|
-
return Array.from(val).map(sanitize);
|
|
4650
|
+
return withinAncestors(val, () => Array.from(val).map(sanitize));
|
|
4633
4651
|
}
|
|
4634
4652
|
if (Array.isArray(val)) {
|
|
4635
|
-
|
|
4636
|
-
return void 0;
|
|
4637
|
-
}
|
|
4638
|
-
seen.add(val);
|
|
4639
|
-
return val.map(sanitize);
|
|
4653
|
+
return withinAncestors(val, () => val.map(sanitize));
|
|
4640
4654
|
}
|
|
4641
4655
|
if (val && typeof val === "object") {
|
|
4642
|
-
|
|
4643
|
-
|
|
4644
|
-
|
|
4645
|
-
|
|
4646
|
-
|
|
4647
|
-
|
|
4648
|
-
|
|
4649
|
-
if (clean !== void 0) {
|
|
4650
|
-
sanitized[key] = clean;
|
|
4656
|
+
return withinAncestors(val, () => {
|
|
4657
|
+
const sanitized = {};
|
|
4658
|
+
for (const [key, value2] of Object.entries(val)) {
|
|
4659
|
+
const clean = sanitize(value2);
|
|
4660
|
+
if (clean !== void 0) {
|
|
4661
|
+
sanitized[key] = clean;
|
|
4662
|
+
}
|
|
4651
4663
|
}
|
|
4652
|
-
|
|
4653
|
-
|
|
4664
|
+
return sanitized;
|
|
4665
|
+
});
|
|
4654
4666
|
}
|
|
4655
4667
|
return val;
|
|
4656
4668
|
}
|
|
@@ -4812,6 +4824,134 @@ function getCookie(cookieHeader, name) {
|
|
|
4812
4824
|
return parseCookies(cookieHeader)[name];
|
|
4813
4825
|
}
|
|
4814
4826
|
|
|
4827
|
+
// libs/utils/src/net/ip-address.ts
|
|
4828
|
+
var IPV4_OCTET_PATTERN = /^(0|[1-9]\d{0,2})$/;
|
|
4829
|
+
var IPV6_GROUP_PATTERN = /^[0-9a-f]{1,4}$/;
|
|
4830
|
+
var IPV4_BLOCKS = compileBlocks(
|
|
4831
|
+
[
|
|
4832
|
+
["0.0.0.0/8", "this-network"],
|
|
4833
|
+
["10.0.0.0/8", "private"],
|
|
4834
|
+
["100.64.0.0/10", "carrier-grade-nat"],
|
|
4835
|
+
["127.0.0.0/8", "loopback"],
|
|
4836
|
+
["169.254.0.0/16", "link-local"],
|
|
4837
|
+
["172.16.0.0/12", "private"],
|
|
4838
|
+
["192.0.0.0/24", "ietf-protocol"],
|
|
4839
|
+
["192.168.0.0/16", "private"],
|
|
4840
|
+
["198.18.0.0/15", "benchmarking"],
|
|
4841
|
+
["224.0.0.0/4", "multicast"],
|
|
4842
|
+
["255.255.255.255/32", "reserved"],
|
|
4843
|
+
["240.0.0.0/4", "reserved"]
|
|
4844
|
+
],
|
|
4845
|
+
parseIpv4
|
|
4846
|
+
);
|
|
4847
|
+
var IPV6_BLOCKS = compileBlocks(
|
|
4848
|
+
[
|
|
4849
|
+
["::/128", "unspecified"],
|
|
4850
|
+
["::1/128", "loopback"],
|
|
4851
|
+
["fd00:ec2::254/128", "cloud-metadata"],
|
|
4852
|
+
["fc00::/7", "private"],
|
|
4853
|
+
["fe80::/10", "link-local"],
|
|
4854
|
+
["fec0::/10", "site-local"],
|
|
4855
|
+
// RFC 8215 local-use NAT64: where the IPv4 address sits depends on the operator's prefix length.
|
|
4856
|
+
["64:ff9b:1::/48", "local-use-nat64"],
|
|
4857
|
+
["ff00::/8", "multicast"]
|
|
4858
|
+
],
|
|
4859
|
+
expandIpv6
|
|
4860
|
+
);
|
|
4861
|
+
var IPV4_EMBEDDINGS = compileBlocks(
|
|
4862
|
+
[
|
|
4863
|
+
["::ffff:0:0/96", 6],
|
|
4864
|
+
["::ffff:0:0:0/96", 6],
|
|
4865
|
+
["::/96", 6],
|
|
4866
|
+
["64:ff9b::/96", 6],
|
|
4867
|
+
["2002::/16", 1]
|
|
4868
|
+
],
|
|
4869
|
+
expandIpv6
|
|
4870
|
+
);
|
|
4871
|
+
function parseIpv4(address) {
|
|
4872
|
+
const parts = address.split(".");
|
|
4873
|
+
if (parts.length !== 4 || !parts.every((part) => IPV4_OCTET_PATTERN.test(part))) return void 0;
|
|
4874
|
+
const octets = parts.map(Number);
|
|
4875
|
+
return octets.every((octet) => octet <= 255) ? octets : void 0;
|
|
4876
|
+
}
|
|
4877
|
+
function expandIpv6(address) {
|
|
4878
|
+
const zoneIndex = address.indexOf("%");
|
|
4879
|
+
let text = (zoneIndex === -1 ? address : address.slice(0, zoneIndex)).toLowerCase();
|
|
4880
|
+
const lastColonIndex = text.lastIndexOf(":");
|
|
4881
|
+
const trailingSegment = text.slice(lastColonIndex + 1);
|
|
4882
|
+
if (lastColonIndex !== -1 && trailingSegment.includes(".")) {
|
|
4883
|
+
const octets = parseIpv4(trailingSegment);
|
|
4884
|
+
if (!octets) return void 0;
|
|
4885
|
+
const highGroup = (octets[0] << 8 | octets[1]).toString(16);
|
|
4886
|
+
const lowGroup = (octets[2] << 8 | octets[3]).toString(16);
|
|
4887
|
+
text = `${text.slice(0, lastColonIndex + 1)}${highGroup}:${lowGroup}`;
|
|
4888
|
+
}
|
|
4889
|
+
const halves = text.split("::");
|
|
4890
|
+
if (halves.length > 2) return void 0;
|
|
4891
|
+
const head = parseIpv6Groups(halves[0]);
|
|
4892
|
+
if (!head) return void 0;
|
|
4893
|
+
if (halves.length === 1) return head.length === 8 ? head : void 0;
|
|
4894
|
+
const tail = parseIpv6Groups(halves[1]);
|
|
4895
|
+
if (!tail || head.length + tail.length > 7) return void 0;
|
|
4896
|
+
return [...head, ...new Array(8 - head.length - tail.length).fill(0), ...tail];
|
|
4897
|
+
}
|
|
4898
|
+
function extractEmbeddedIpv4(groups) {
|
|
4899
|
+
const octets = findEmbeddedIpv4Octets(groups);
|
|
4900
|
+
return octets ? octets.join(".") : void 0;
|
|
4901
|
+
}
|
|
4902
|
+
function classifyIpAddress(address) {
|
|
4903
|
+
const isBracketed = address.startsWith("[") && address.endsWith("]");
|
|
4904
|
+
const octets = isBracketed ? void 0 : parseIpv4(address);
|
|
4905
|
+
if (octets) return classifyIpv4(octets);
|
|
4906
|
+
const groups = expandIpv6(isBracketed ? address.slice(1, -1) : address);
|
|
4907
|
+
if (!groups) return void 0;
|
|
4908
|
+
const block = IPV6_BLOCKS.find((candidate) => matchesBlock(groups, candidate, 16));
|
|
4909
|
+
if (block) return { family: 6, range: block.value, cidr: block.cidr };
|
|
4910
|
+
const embeddedOctets = findEmbeddedIpv4Octets(groups);
|
|
4911
|
+
if (!embeddedOctets) return { family: 6, range: "public" };
|
|
4912
|
+
return { ...classifyIpv4(embeddedOctets), family: 6, embeddedIpv4: embeddedOctets.join(".") };
|
|
4913
|
+
}
|
|
4914
|
+
function classifyIpv4(octets) {
|
|
4915
|
+
const block = IPV4_BLOCKS.find((candidate) => matchesBlock(octets, candidate, 8));
|
|
4916
|
+
return block ? { family: 4, range: block.value, cidr: block.cidr } : { family: 4, range: "public" };
|
|
4917
|
+
}
|
|
4918
|
+
function findEmbeddedIpv4Octets(groups) {
|
|
4919
|
+
const embedding = IPV4_EMBEDDINGS.find((block) => matchesBlock(groups, block, 16));
|
|
4920
|
+
if (!embedding) return void 0;
|
|
4921
|
+
const highGroup = groups[embedding.value];
|
|
4922
|
+
const lowGroup = groups[embedding.value + 1];
|
|
4923
|
+
return [highGroup >> 8, highGroup & 255, lowGroup >> 8, lowGroup & 255];
|
|
4924
|
+
}
|
|
4925
|
+
function parseIpv6Groups(part) {
|
|
4926
|
+
if (part === "") return [];
|
|
4927
|
+
const groups = part.split(":");
|
|
4928
|
+
if (!groups.every((group) => IPV6_GROUP_PATTERN.test(group))) return void 0;
|
|
4929
|
+
return groups.map((group) => parseInt(group, 16));
|
|
4930
|
+
}
|
|
4931
|
+
function matchesBlock(words, block, bitsPerWord) {
|
|
4932
|
+
let remainingBits = block.prefixLength;
|
|
4933
|
+
for (let index = 0; remainingBits > 0; index++) {
|
|
4934
|
+
const comparedBits = Math.min(bitsPerWord, remainingBits);
|
|
4935
|
+
const ignoredBits = bitsPerWord - comparedBits;
|
|
4936
|
+
if (words[index] >> ignoredBits !== block.network[index] >> ignoredBits) return false;
|
|
4937
|
+
remainingBits -= comparedBits;
|
|
4938
|
+
}
|
|
4939
|
+
return true;
|
|
4940
|
+
}
|
|
4941
|
+
function compileBlocks(entries, parseNetwork) {
|
|
4942
|
+
return entries.map(([cidr, value]) => {
|
|
4943
|
+
const [networkText, prefixText] = cidr.split("/");
|
|
4944
|
+
const network = parseNetwork(networkText);
|
|
4945
|
+
if (!network) throw new Error(`Invalid network in address block "${cidr}"`);
|
|
4946
|
+
return { cidr, network, prefixLength: Number(prefixText), value };
|
|
4947
|
+
});
|
|
4948
|
+
}
|
|
4949
|
+
|
|
4950
|
+
// libs/utils/src/net/redirect.ts
|
|
4951
|
+
function isRedirectResponse(response) {
|
|
4952
|
+
return response.type === "opaqueredirect" || response.status >= 300 && response.status < 400;
|
|
4953
|
+
}
|
|
4954
|
+
|
|
4815
4955
|
// libs/utils/src/index.ts
|
|
4816
4956
|
init_fs2();
|
|
4817
4957
|
|
|
@@ -5991,6 +6131,7 @@ function setMachineIdOverride(id) {
|
|
|
5991
6131
|
buildSetCookie,
|
|
5992
6132
|
bytesToHex,
|
|
5993
6133
|
checkEntryAvailability,
|
|
6134
|
+
classifyIpAddress,
|
|
5994
6135
|
clearCachedSecret,
|
|
5995
6136
|
collapseChar,
|
|
5996
6137
|
collapseWhitespace,
|
|
@@ -6023,11 +6164,13 @@ function setMachineIdOverride(id) {
|
|
|
6023
6164
|
escapeHtmlAttr,
|
|
6024
6165
|
escapeJsString,
|
|
6025
6166
|
escapeScriptClose,
|
|
6167
|
+
expandIpv6,
|
|
6026
6168
|
expandTemplate,
|
|
6027
6169
|
expandUriTemplate,
|
|
6028
6170
|
expiresAtToTTL,
|
|
6029
6171
|
extname,
|
|
6030
6172
|
extractBracedParams,
|
|
6173
|
+
extractEmbeddedIpv4,
|
|
6031
6174
|
extractTemplateParams,
|
|
6032
6175
|
extractUriScheme,
|
|
6033
6176
|
fileExists,
|
|
@@ -6067,6 +6210,7 @@ function setMachineIdOverride(id) {
|
|
|
6067
6210
|
isNode,
|
|
6068
6211
|
isPatternSafe,
|
|
6069
6212
|
isProduction,
|
|
6213
|
+
isRedirectResponse,
|
|
6070
6214
|
isRsaPssAlg,
|
|
6071
6215
|
isSecretCached,
|
|
6072
6216
|
isSecretKeyData,
|
|
@@ -6089,6 +6233,7 @@ function setMachineIdOverride(id) {
|
|
|
6089
6233
|
mkdtemp,
|
|
6090
6234
|
normalizeTTL,
|
|
6091
6235
|
parseCookies,
|
|
6236
|
+
parseIpv4,
|
|
6092
6237
|
parseKeyData,
|
|
6093
6238
|
parseSecretData,
|
|
6094
6239
|
parseUriTemplate,
|
package/net/index.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { classifyIpAddress, expandIpv6, extractEmbeddedIpv4, parseIpv4 } from './ip-address';
|
|
2
|
+
export type { IpAddressClassification, IpAddressRange, SpecialPurposeIpRange } from './ip-address';
|
|
3
|
+
export { isRedirectResponse } from './redirect';
|
|
4
|
+
export type { RedirectCandidate } from './redirect';
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IP-literal parsing and special-purpose range classification, shared by every SSRF guard.
|
|
3
|
+
*
|
|
4
|
+
* An IPv6 address has many spellings of the same destination (`::ffff:169.254.169.254`,
|
|
5
|
+
* `[::ffff:a9fe:a9fe]`, `0:0:0:0:0:ffff:a9fe:a9fe`) and several forms that carry an IPv4 address
|
|
6
|
+
* a translator or tunnel will reach. Guards therefore classify the parsed value, never the text.
|
|
7
|
+
*/
|
|
8
|
+
export type SpecialPurposeIpRange = 'unspecified' | 'this-network' | 'loopback' | 'private' | 'carrier-grade-nat' | 'link-local' | 'cloud-metadata' | 'ietf-protocol' | 'benchmarking' | 'site-local' | 'multicast' | 'local-use-nat64' | 'reserved';
|
|
9
|
+
export type IpAddressRange = 'public' | SpecialPurposeIpRange;
|
|
10
|
+
/**
|
|
11
|
+
* `cidr` is the special-purpose block that matched (e.g. `172.16.0.0/12`); `embeddedIpv4` is the
|
|
12
|
+
* IPv4 address an IPv6 address carries (mapped, translated, compatible, NAT64 or 6to4).
|
|
13
|
+
*/
|
|
14
|
+
export type IpAddressClassification = {
|
|
15
|
+
family: 4 | 6;
|
|
16
|
+
embeddedIpv4?: string;
|
|
17
|
+
} & ({
|
|
18
|
+
range: 'public';
|
|
19
|
+
} | {
|
|
20
|
+
range: SpecialPurposeIpRange;
|
|
21
|
+
cidr: string;
|
|
22
|
+
});
|
|
23
|
+
/** Parse a strict dotted-quad IPv4 address; leading zeros are refused because some stacks read them as octal. */
|
|
24
|
+
export declare function parseIpv4(address: string): number[] | undefined;
|
|
25
|
+
/** Expand an IPv6 address (zone id stripped, trailing dotted quad allowed) to its eight 16-bit groups. */
|
|
26
|
+
export declare function expandIpv6(address: string): number[] | undefined;
|
|
27
|
+
/** The IPv4 address carried by an expanded IPv6 address, where it carries one. */
|
|
28
|
+
export declare function extractEmbeddedIpv4(groups: readonly number[]): string | undefined;
|
|
29
|
+
/**
|
|
30
|
+
* Classify an IPv4 or IPv6 literal (IPv6 optionally in URL brackets) by the special-purpose block
|
|
31
|
+
* it falls in. An IPv6 address that carries an IPv4 address is judged by that IPv4 address.
|
|
32
|
+
*
|
|
33
|
+
* @returns undefined when the input is not a well-formed IP literal.
|
|
34
|
+
*/
|
|
35
|
+
export declare function classifyIpAddress(address: string): IpAddressClassification | undefined;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface RedirectCandidate {
|
|
2
|
+
status: number;
|
|
3
|
+
type?: string;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Whether a `fetch` response made with `redirect: 'manual'` is a redirect. Browser runtimes return
|
|
7
|
+
* an `opaqueredirect` response with status 0 and no readable `Location`, so status alone misses it.
|
|
8
|
+
*/
|
|
9
|
+
export declare function isRedirectResponse(response: RedirectCandidate): boolean;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@frontmcp/utils",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.2",
|
|
4
4
|
"description": "Shared utility functions for FrontMCP - string manipulation, URI handling, path utilities, and more",
|
|
5
5
|
"author": "AgentFront <info@agentfront.dev>",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"@noble/hashes": "^2.0.1"
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
|
35
|
-
"@frontmcp/lazy-zod": "1.8.
|
|
35
|
+
"@frontmcp/lazy-zod": "1.8.2",
|
|
36
36
|
"@vercel/kv": "^2.0.0 || ^3.0.0",
|
|
37
37
|
"ioredis": "^5.0.0"
|
|
38
38
|
},
|