@fgv/ts-extras 5.1.0-46 → 5.1.0-47
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/README.md +90 -0
- package/dist/index.browser.js +3 -1
- package/dist/index.browser.js.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/packlets/safer-fetch/addressClassification.js +438 -0
- package/dist/packlets/safer-fetch/addressClassification.js.map +1 -0
- package/dist/packlets/safer-fetch/addressPolicy.js +119 -0
- package/dist/packlets/safer-fetch/addressPolicy.js.map +1 -0
- package/dist/packlets/safer-fetch/contentType.js +120 -0
- package/dist/packlets/safer-fetch/contentType.js.map +1 -0
- package/dist/packlets/safer-fetch/deadline.js +254 -0
- package/dist/packlets/safer-fetch/deadline.js.map +1 -0
- package/dist/packlets/safer-fetch/defaults.js +131 -0
- package/dist/packlets/safer-fetch/defaults.js.map +1 -0
- package/dist/packlets/safer-fetch/failureReason.js +21 -0
- package/dist/packlets/safer-fetch/failureReason.js.map +1 -0
- package/dist/packlets/safer-fetch/guards.js +148 -0
- package/dist/packlets/safer-fetch/guards.js.map +1 -0
- package/dist/packlets/safer-fetch/index.browser.js +58 -0
- package/dist/packlets/safer-fetch/index.browser.js.map +1 -0
- package/dist/packlets/safer-fetch/index.js +52 -0
- package/dist/packlets/safer-fetch/index.js.map +1 -0
- package/dist/packlets/safer-fetch/model.js +21 -0
- package/dist/packlets/safer-fetch/model.js.map +1 -0
- package/dist/packlets/safer-fetch/nodeAddressGuard.js +191 -0
- package/dist/packlets/safer-fetch/nodeAddressGuard.js.map +1 -0
- package/dist/packlets/safer-fetch/redirect.js +93 -0
- package/dist/packlets/safer-fetch/redirect.js.map +1 -0
- package/dist/packlets/safer-fetch/retry.js +188 -0
- package/dist/packlets/safer-fetch/retry.js.map +1 -0
- package/dist/packlets/safer-fetch/saferFetch.js +755 -0
- package/dist/packlets/safer-fetch/saferFetch.js.map +1 -0
- package/dist/packlets/safer-fetch/transport.js +50 -0
- package/dist/packlets/safer-fetch/transport.js.map +1 -0
- package/dist/ts-extras.d.ts +1337 -0
- package/lib/index.browser.d.ts +2 -1
- package/lib/index.browser.d.ts.map +1 -1
- package/lib/index.browser.js +4 -1
- package/lib/index.browser.js.map +1 -1
- package/lib/index.d.ts +2 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +3 -1
- package/lib/index.js.map +1 -1
- package/lib/packlets/safer-fetch/addressClassification.d.ts +144 -0
- package/lib/packlets/safer-fetch/addressClassification.d.ts.map +1 -0
- package/lib/packlets/safer-fetch/addressClassification.js +441 -0
- package/lib/packlets/safer-fetch/addressClassification.js.map +1 -0
- package/lib/packlets/safer-fetch/addressPolicy.d.ts +113 -0
- package/lib/packlets/safer-fetch/addressPolicy.d.ts.map +1 -0
- package/lib/packlets/safer-fetch/addressPolicy.js +123 -0
- package/lib/packlets/safer-fetch/addressPolicy.js.map +1 -0
- package/lib/packlets/safer-fetch/contentType.d.ts +51 -0
- package/lib/packlets/safer-fetch/contentType.d.ts.map +1 -0
- package/lib/packlets/safer-fetch/contentType.js +127 -0
- package/lib/packlets/safer-fetch/contentType.js.map +1 -0
- package/lib/packlets/safer-fetch/deadline.d.ts +138 -0
- package/lib/packlets/safer-fetch/deadline.d.ts.map +1 -0
- package/lib/packlets/safer-fetch/deadline.js +258 -0
- package/lib/packlets/safer-fetch/deadline.js.map +1 -0
- package/lib/packlets/safer-fetch/defaults.d.ts +108 -0
- package/lib/packlets/safer-fetch/defaults.d.ts.map +1 -0
- package/lib/packlets/safer-fetch/defaults.js +134 -0
- package/lib/packlets/safer-fetch/defaults.js.map +1 -0
- package/lib/packlets/safer-fetch/failureReason.d.ts +148 -0
- package/lib/packlets/safer-fetch/failureReason.d.ts.map +1 -0
- package/lib/packlets/safer-fetch/failureReason.js +22 -0
- package/lib/packlets/safer-fetch/failureReason.js.map +1 -0
- package/lib/packlets/safer-fetch/guards.d.ts +62 -0
- package/lib/packlets/safer-fetch/guards.d.ts.map +1 -0
- package/lib/packlets/safer-fetch/guards.js +153 -0
- package/lib/packlets/safer-fetch/guards.js.map +1 -0
- package/lib/packlets/safer-fetch/index.browser.d.ts +33 -0
- package/lib/packlets/safer-fetch/index.browser.d.ts.map +1 -0
- package/lib/packlets/safer-fetch/index.browser.js +82 -0
- package/lib/packlets/safer-fetch/index.browser.js.map +1 -0
- package/lib/packlets/safer-fetch/index.d.ts +27 -0
- package/lib/packlets/safer-fetch/index.d.ts.map +1 -0
- package/lib/packlets/safer-fetch/index.js +78 -0
- package/lib/packlets/safer-fetch/index.js.map +1 -0
- package/lib/packlets/safer-fetch/model.d.ts +373 -0
- package/lib/packlets/safer-fetch/model.d.ts.map +1 -0
- package/lib/packlets/safer-fetch/model.js +22 -0
- package/lib/packlets/safer-fetch/model.js.map +1 -0
- package/lib/packlets/safer-fetch/nodeAddressGuard.d.ts +129 -0
- package/lib/packlets/safer-fetch/nodeAddressGuard.d.ts.map +1 -0
- package/lib/packlets/safer-fetch/nodeAddressGuard.js +196 -0
- package/lib/packlets/safer-fetch/nodeAddressGuard.js.map +1 -0
- package/lib/packlets/safer-fetch/redirect.d.ts +62 -0
- package/lib/packlets/safer-fetch/redirect.d.ts.map +1 -0
- package/lib/packlets/safer-fetch/redirect.js +98 -0
- package/lib/packlets/safer-fetch/redirect.js.map +1 -0
- package/lib/packlets/safer-fetch/retry.d.ts +162 -0
- package/lib/packlets/safer-fetch/retry.d.ts.map +1 -0
- package/lib/packlets/safer-fetch/retry.js +197 -0
- package/lib/packlets/safer-fetch/retry.js.map +1 -0
- package/lib/packlets/safer-fetch/saferFetch.d.ts +108 -0
- package/lib/packlets/safer-fetch/saferFetch.d.ts.map +1 -0
- package/lib/packlets/safer-fetch/saferFetch.js +760 -0
- package/lib/packlets/safer-fetch/saferFetch.js.map +1 -0
- package/lib/packlets/safer-fetch/transport.d.ts +18 -0
- package/lib/packlets/safer-fetch/transport.d.ts.map +1 -0
- package/lib/packlets/safer-fetch/transport.js +53 -0
- package/lib/packlets/safer-fetch/transport.js.map +1 -0
- package/package.json +17 -7
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Copyright (c) 2026 Erik Fortune
|
|
3
|
+
//
|
|
4
|
+
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
+
// of this software and associated documentation files (the "Software"), to deal
|
|
6
|
+
// in the Software without restriction, including without limitation the rights
|
|
7
|
+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
// copies of the Software, and to permit persons to whom the Software is
|
|
9
|
+
// furnished to do so, subject to the following conditions:
|
|
10
|
+
//
|
|
11
|
+
// The above copyright notice and this permission notice shall be included in all
|
|
12
|
+
// copies or substantial portions of the Software.
|
|
13
|
+
//
|
|
14
|
+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
|
+
// SOFTWARE.
|
|
21
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
+
exports.classifyAddress = classifyAddress;
|
|
23
|
+
// Address classification is prefix arithmetic: every range test is a mask and a
|
|
24
|
+
// compare, and every IPv6 group/byte conversion is a shift. Spelling those as
|
|
25
|
+
// arithmetic instead would be strictly harder to audit against the RFCs, which
|
|
26
|
+
// is the opposite of what this file needs. The repo already takes per-site
|
|
27
|
+
// disables for the same reason in `crypto-utils/keystore` and `ts-random`.
|
|
28
|
+
/* eslint-disable no-bitwise */
|
|
29
|
+
const ts_utils_1 = require("@fgv/ts-utils");
|
|
30
|
+
/**
|
|
31
|
+
* IPv4 special-purpose ranges, most specific first. The broadcast address is
|
|
32
|
+
* listed ahead of `240.0.0.0/4` because it falls inside it.
|
|
33
|
+
*/
|
|
34
|
+
const IPV4_RANGES = [
|
|
35
|
+
{ network: 0xffffffff, prefixLength: 32, classification: 'broadcast' },
|
|
36
|
+
{ network: 0x00000000, prefixLength: 8, classification: 'unspecified' },
|
|
37
|
+
{ network: 0x0a000000, prefixLength: 8, classification: 'private' },
|
|
38
|
+
{ network: 0x64400000, prefixLength: 10, classification: 'carrier-grade-nat' },
|
|
39
|
+
{ network: 0x7f000000, prefixLength: 8, classification: 'loopback' },
|
|
40
|
+
{ network: 0xa9fe0000, prefixLength: 16, classification: 'link-local' },
|
|
41
|
+
{ network: 0xac100000, prefixLength: 12, classification: 'private' },
|
|
42
|
+
{ network: 0xc0000000, prefixLength: 24, classification: 'protocol-assignment' },
|
|
43
|
+
{ network: 0xc0000200, prefixLength: 24, classification: 'documentation' },
|
|
44
|
+
{ network: 0xc0586300, prefixLength: 24, classification: 'reserved' },
|
|
45
|
+
{ network: 0xc0a80000, prefixLength: 16, classification: 'private' },
|
|
46
|
+
{ network: 0xc6120000, prefixLength: 15, classification: 'benchmarking' },
|
|
47
|
+
{ network: 0xc6336400, prefixLength: 24, classification: 'documentation' },
|
|
48
|
+
{ network: 0xcb007100, prefixLength: 24, classification: 'documentation' },
|
|
49
|
+
{ network: 0xe0000000, prefixLength: 4, classification: 'multicast' },
|
|
50
|
+
{ network: 0xf0000000, prefixLength: 4, classification: 'reserved' }
|
|
51
|
+
];
|
|
52
|
+
/**
|
|
53
|
+
* IPv6 special-purpose ranges, most specific first. Ranges whose addresses
|
|
54
|
+
* embed an IPv4 address are handled separately, before this table is consulted,
|
|
55
|
+
* and anything outside global unicast (`2000::/3`) that matches nothing here is
|
|
56
|
+
* classified `'reserved'` — so this table only needs to name the ranges that
|
|
57
|
+
* deserve a more specific classification than that fallback gives them.
|
|
58
|
+
*/
|
|
59
|
+
const IPV6_RANGES = [
|
|
60
|
+
{ prefix: [0x20, 0x01, 0x0d, 0xb8], prefixLength: 32, classification: 'documentation' },
|
|
61
|
+
{ prefix: [0x20, 0x01, 0x00], prefixLength: 23, classification: 'protocol-assignment' },
|
|
62
|
+
{ prefix: [0x3f, 0xff, 0x00], prefixLength: 20, classification: 'documentation' },
|
|
63
|
+
{ prefix: [0xfc], prefixLength: 7, classification: 'unique-local' },
|
|
64
|
+
{ prefix: [0xfe, 0x80], prefixLength: 10, classification: 'link-local' },
|
|
65
|
+
{ prefix: [0xff], prefixLength: 8, classification: 'multicast' }
|
|
66
|
+
];
|
|
67
|
+
/** The first three bits of a global unicast IPv6 address (`2000::/3`). */
|
|
68
|
+
const IPV6_GLOBAL_UNICAST_MASK = 0xe0;
|
|
69
|
+
const IPV6_GLOBAL_UNICAST_VALUE = 0x20;
|
|
70
|
+
/**
|
|
71
|
+
* Parses one component of an IPv4 literal using `inet_aton` conventions: a
|
|
72
|
+
* `0x`/`0X` prefix selects hexadecimal, an otherwise-leading `0` selects octal,
|
|
73
|
+
* and anything else is decimal.
|
|
74
|
+
*
|
|
75
|
+
* This is deliberately permissive about *radix* because the WHATWG URL parser
|
|
76
|
+
* is: `http://0177.0.0.1/` and `http://2130706433/` are both `127.0.0.1`, and a
|
|
77
|
+
* classifier that only understands dotted decimal is bypassed by either.
|
|
78
|
+
*/
|
|
79
|
+
function parseIpv4Component(text) {
|
|
80
|
+
if (text.length === 0) {
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
let radix = 10;
|
|
84
|
+
let digits = text;
|
|
85
|
+
if (text.length > 1 && (text[1] === 'x' || text[1] === 'X') && text[0] === '0') {
|
|
86
|
+
radix = 16;
|
|
87
|
+
digits = text.slice(2);
|
|
88
|
+
}
|
|
89
|
+
else if (text.length > 1 && text[0] === '0') {
|
|
90
|
+
radix = 8;
|
|
91
|
+
digits = text.slice(1);
|
|
92
|
+
}
|
|
93
|
+
// The WHATWG IPv4 number parser returns 0 when stripping the radix prefix
|
|
94
|
+
// leaves nothing behind, so a bare `0x` is zero: `127.0x.1` is `127.0.0.1`.
|
|
95
|
+
// Rejecting it here would leave a live encoding bypass.
|
|
96
|
+
if (digits.length === 0) {
|
|
97
|
+
return 0;
|
|
98
|
+
}
|
|
99
|
+
const allowed = radix === 16 ? /^[0-9a-fA-F]+$/ : radix === 8 ? /^[0-7]+$/ : /^[0-9]+$/;
|
|
100
|
+
if (!allowed.test(digits)) {
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
const value = Number.parseInt(digits, radix);
|
|
104
|
+
return Number.isSafeInteger(value) ? value : undefined;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Parses an IPv4 literal in any of the forms the platform URL parser accepts
|
|
108
|
+
* (`a.b.c.d`, `a.b.c`, `a.b`, `a`), returning the address as an unsigned 32-bit
|
|
109
|
+
* value. The final component absorbs all remaining bytes, so `127.1` is
|
|
110
|
+
* `127.0.0.1` and `2130706433` is also `127.0.0.1`.
|
|
111
|
+
*/
|
|
112
|
+
function parseIpv4(text) {
|
|
113
|
+
const parts = text.split('.');
|
|
114
|
+
if (parts.length > 4) {
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
117
|
+
const values = [];
|
|
118
|
+
for (const part of parts) {
|
|
119
|
+
const value = parseIpv4Component(part);
|
|
120
|
+
if (value === undefined) {
|
|
121
|
+
return undefined;
|
|
122
|
+
}
|
|
123
|
+
values.push(value);
|
|
124
|
+
}
|
|
125
|
+
const leading = values.length - 1;
|
|
126
|
+
for (let i = 0; i < leading; i++) {
|
|
127
|
+
if (values[i] > 0xff) {
|
|
128
|
+
return undefined;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (values[leading] > Math.pow(256, 4 - leading) - 1) {
|
|
132
|
+
return undefined;
|
|
133
|
+
}
|
|
134
|
+
let address = values[leading];
|
|
135
|
+
for (let i = 0; i < leading; i++) {
|
|
136
|
+
address += values[i] * Math.pow(256, 3 - i);
|
|
137
|
+
}
|
|
138
|
+
return address >>> 0;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Parses a strict dotted-quad IPv4 literal — exactly four decimal components,
|
|
142
|
+
* each `0..255`, with no octal or hexadecimal forms. This is the grammar RFC
|
|
143
|
+
* 4291 permits for the IPv4 tail of an IPv6 literal, and it is deliberately
|
|
144
|
+
* stricter than {@link parseIpv4}: an unparseable literal fails closed.
|
|
145
|
+
*/
|
|
146
|
+
function parseStrictDottedQuad(text) {
|
|
147
|
+
const parts = text.split('.');
|
|
148
|
+
if (parts.length !== 4) {
|
|
149
|
+
return undefined;
|
|
150
|
+
}
|
|
151
|
+
let address = 0;
|
|
152
|
+
for (const part of parts) {
|
|
153
|
+
if (!/^(0|[1-9][0-9]{0,2})$/.test(part)) {
|
|
154
|
+
return undefined;
|
|
155
|
+
}
|
|
156
|
+
const value = Number.parseInt(part, 10);
|
|
157
|
+
if (value > 0xff) {
|
|
158
|
+
return undefined;
|
|
159
|
+
}
|
|
160
|
+
address = address * 256 + value;
|
|
161
|
+
}
|
|
162
|
+
return address >>> 0;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Parses a colon-separated run of IPv6 groups, allowing a trailing embedded
|
|
166
|
+
* dotted-quad IPv4 literal which expands to the final two groups.
|
|
167
|
+
*
|
|
168
|
+
* @param allowEmbeddedIpv4 - whether this run may end in a dotted-quad IPv4
|
|
169
|
+
* literal. Only the run that ends the whole address may, so the run *before* a
|
|
170
|
+
* `::` never does: `1.2.3.4::` is malformed, not `102:304::`.
|
|
171
|
+
*/
|
|
172
|
+
function parseIpv6Groups(text, allowEmbeddedIpv4) {
|
|
173
|
+
if (text.length === 0) {
|
|
174
|
+
return [];
|
|
175
|
+
}
|
|
176
|
+
const parts = text.split(':');
|
|
177
|
+
const groups = [];
|
|
178
|
+
for (let i = 0; i < parts.length; i++) {
|
|
179
|
+
const part = parts[i];
|
|
180
|
+
if (part.indexOf('.') >= 0) {
|
|
181
|
+
if (!allowEmbeddedIpv4 || i !== parts.length - 1) {
|
|
182
|
+
return undefined;
|
|
183
|
+
}
|
|
184
|
+
const embedded = parseStrictDottedQuad(part);
|
|
185
|
+
if (embedded === undefined) {
|
|
186
|
+
return undefined;
|
|
187
|
+
}
|
|
188
|
+
groups.push((embedded >>> 16) & 0xffff, embedded & 0xffff);
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
if (!/^[0-9a-fA-F]{1,4}$/.test(part)) {
|
|
192
|
+
return undefined;
|
|
193
|
+
}
|
|
194
|
+
groups.push(Number.parseInt(part, 16));
|
|
195
|
+
}
|
|
196
|
+
return groups;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Parses an IPv6 literal into its 16 constituent bytes.
|
|
200
|
+
*/
|
|
201
|
+
function parseIpv6(text) {
|
|
202
|
+
const doubleColon = text.indexOf('::');
|
|
203
|
+
let groups;
|
|
204
|
+
if (doubleColon >= 0) {
|
|
205
|
+
if (text.indexOf('::', doubleColon + 1) >= 0) {
|
|
206
|
+
return undefined;
|
|
207
|
+
}
|
|
208
|
+
const head = parseIpv6Groups(text.slice(0, doubleColon), false);
|
|
209
|
+
const tail = parseIpv6Groups(text.slice(doubleColon + 2), true);
|
|
210
|
+
if (head === undefined || tail === undefined) {
|
|
211
|
+
return undefined;
|
|
212
|
+
}
|
|
213
|
+
const elided = 8 - head.length - tail.length;
|
|
214
|
+
if (elided < 1) {
|
|
215
|
+
return undefined;
|
|
216
|
+
}
|
|
217
|
+
groups = head.concat(new Array(elided).fill(0), tail);
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
groups = parseIpv6Groups(text, true);
|
|
221
|
+
if (groups === undefined || groups.length !== 8) {
|
|
222
|
+
return undefined;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
const bytes = new Uint8Array(16);
|
|
226
|
+
for (let i = 0; i < 8; i++) {
|
|
227
|
+
bytes[i * 2] = (groups[i] >>> 8) & 0xff;
|
|
228
|
+
bytes[i * 2 + 1] = groups[i] & 0xff;
|
|
229
|
+
}
|
|
230
|
+
return bytes;
|
|
231
|
+
}
|
|
232
|
+
function ipv4ToString(address) {
|
|
233
|
+
return [(address >>> 24) & 0xff, (address >>> 16) & 0xff, (address >>> 8) & 0xff, address & 0xff].join('.');
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Renders the RFC 5952 canonical text form of an IPv6 address: lowercase, no
|
|
237
|
+
* leading zeros in a group, and the leftmost longest run of two or more
|
|
238
|
+
* all-zero groups replaced by `::`.
|
|
239
|
+
*/
|
|
240
|
+
function ipv6ToString(bytes) {
|
|
241
|
+
const groups = [];
|
|
242
|
+
for (let i = 0; i < 8; i++) {
|
|
243
|
+
groups.push((bytes[i * 2] << 8) | bytes[i * 2 + 1]);
|
|
244
|
+
}
|
|
245
|
+
let bestStart = -1;
|
|
246
|
+
let bestLength = 0;
|
|
247
|
+
let runStart = -1;
|
|
248
|
+
let runLength = 0;
|
|
249
|
+
for (let i = 0; i < 8; i++) {
|
|
250
|
+
if (groups[i] === 0) {
|
|
251
|
+
if (runStart < 0) {
|
|
252
|
+
runStart = i;
|
|
253
|
+
runLength = 0;
|
|
254
|
+
}
|
|
255
|
+
runLength++;
|
|
256
|
+
if (runLength > bestLength) {
|
|
257
|
+
bestStart = runStart;
|
|
258
|
+
bestLength = runLength;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
else {
|
|
262
|
+
runStart = -1;
|
|
263
|
+
runLength = 0;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
const rendered = groups.map((g) => g.toString(16));
|
|
267
|
+
if (bestLength < 2) {
|
|
268
|
+
return rendered.join(':');
|
|
269
|
+
}
|
|
270
|
+
const head = rendered.slice(0, bestStart).join(':');
|
|
271
|
+
const tail = rendered.slice(bestStart + bestLength).join(':');
|
|
272
|
+
return `${head}::${tail}`;
|
|
273
|
+
}
|
|
274
|
+
function classifyIpv4Value(address) {
|
|
275
|
+
for (const range of IPV4_RANGES) {
|
|
276
|
+
const mask = (0xffffffff << (32 - range.prefixLength)) >>> 0;
|
|
277
|
+
if ((address & mask) >>> 0 === range.network) {
|
|
278
|
+
return range.classification;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return 'public';
|
|
282
|
+
}
|
|
283
|
+
function matchesIpv6Range(bytes, range) {
|
|
284
|
+
const fullBytes = range.prefixLength >>> 3;
|
|
285
|
+
for (let i = 0; i < fullBytes; i++) {
|
|
286
|
+
if (bytes[i] !== range.prefix[i]) {
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
const remainingBits = range.prefixLength & 7;
|
|
291
|
+
if (remainingBits === 0) {
|
|
292
|
+
return true;
|
|
293
|
+
}
|
|
294
|
+
const mask = (0xff << (8 - remainingBits)) & 0xff;
|
|
295
|
+
return (bytes[fullBytes] & mask) === (range.prefix[fullBytes] & mask);
|
|
296
|
+
}
|
|
297
|
+
function allZero(bytes, start, end) {
|
|
298
|
+
for (let i = start; i < end; i++) {
|
|
299
|
+
if (bytes[i] !== 0) {
|
|
300
|
+
return false;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return true;
|
|
304
|
+
}
|
|
305
|
+
function readIpv4At(bytes, offset) {
|
|
306
|
+
return (((bytes[offset] << 24) | (bytes[offset + 1] << 16) | (bytes[offset + 2] << 8) | bytes[offset + 3]) >>> 0);
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Detects the IPv4 address embedded in an IPv6 address, if any. `::` and `::1`
|
|
310
|
+
* are excluded before the IPv4-compatible check so that the unspecified and
|
|
311
|
+
* loopback addresses keep their own classifications.
|
|
312
|
+
*/
|
|
313
|
+
function detectIpv6Embedding(bytes) {
|
|
314
|
+
if (allZero(bytes, 0, 10) && bytes[10] === 0xff && bytes[11] === 0xff) {
|
|
315
|
+
return { kind: 'ipv4-mapped', value: readIpv4At(bytes, 12) };
|
|
316
|
+
}
|
|
317
|
+
if (bytes[0] === 0x00 &&
|
|
318
|
+
bytes[1] === 0x64 &&
|
|
319
|
+
bytes[2] === 0xff &&
|
|
320
|
+
bytes[3] === 0x9b &&
|
|
321
|
+
allZero(bytes, 4, 12)) {
|
|
322
|
+
return { kind: 'nat64', value: readIpv4At(bytes, 12) };
|
|
323
|
+
}
|
|
324
|
+
if (bytes[0] === 0x20 && bytes[1] === 0x02) {
|
|
325
|
+
return { kind: '6to4', value: readIpv4At(bytes, 2) };
|
|
326
|
+
}
|
|
327
|
+
if (allZero(bytes, 0, 12)) {
|
|
328
|
+
return { kind: 'ipv4-compatible', value: readIpv4At(bytes, 12) };
|
|
329
|
+
}
|
|
330
|
+
return undefined;
|
|
331
|
+
}
|
|
332
|
+
function classifyIpv6Bytes(bytes) {
|
|
333
|
+
const canonical = ipv6ToString(bytes);
|
|
334
|
+
if (allZero(bytes, 0, 16)) {
|
|
335
|
+
return { address: canonical, canonical, family: 'ipv6', classification: 'unspecified' };
|
|
336
|
+
}
|
|
337
|
+
if (allZero(bytes, 0, 15) && bytes[15] === 0x01) {
|
|
338
|
+
return { address: canonical, canonical, family: 'ipv6', classification: 'loopback' };
|
|
339
|
+
}
|
|
340
|
+
const embedding = detectIpv6Embedding(bytes);
|
|
341
|
+
if (embedding !== undefined) {
|
|
342
|
+
return {
|
|
343
|
+
address: canonical,
|
|
344
|
+
canonical,
|
|
345
|
+
family: 'ipv6',
|
|
346
|
+
classification: classifyIpv4Value(embedding.value),
|
|
347
|
+
embeddedIpv4: { kind: embedding.kind, address: ipv4ToString(embedding.value) }
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
for (const range of IPV6_RANGES) {
|
|
351
|
+
if (matchesIpv6Range(bytes, range)) {
|
|
352
|
+
return { address: canonical, canonical, family: 'ipv6', classification: range.classification };
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
// Only `2000::/3` is assigned as global unicast. Everything else that reached
|
|
356
|
+
// this point is unassigned or reserved for future use, so it is never public.
|
|
357
|
+
const classification = (bytes[0] & IPV6_GLOBAL_UNICAST_MASK) === IPV6_GLOBAL_UNICAST_VALUE ? 'public' : 'reserved';
|
|
358
|
+
return { address: canonical, canonical, family: 'ipv6', classification };
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Normalizes the textual forms an address can arrive in before parsing:
|
|
362
|
+
* surrounding whitespace and the square brackets a URL `hostname` carries for
|
|
363
|
+
* an IPv6 literal. A zone identifier is *not* stripped here — it is stripped on
|
|
364
|
+
* the IPv6 path only, because `8.8.8.8%something` is not an address with a zone
|
|
365
|
+
* and must fail rather than be read as `8.8.8.8`.
|
|
366
|
+
*/
|
|
367
|
+
function normalizeAddressText(address) {
|
|
368
|
+
const text = address.trim();
|
|
369
|
+
if (text.length > 1 && text[0] === '[' && text[text.length - 1] === ']') {
|
|
370
|
+
return text.slice(1, -1);
|
|
371
|
+
}
|
|
372
|
+
return text;
|
|
373
|
+
}
|
|
374
|
+
/** Strips an IPv6 zone identifier (`fe80::1%eth0`). */
|
|
375
|
+
function stripIpv6Zone(text) {
|
|
376
|
+
const zone = text.indexOf('%');
|
|
377
|
+
return zone >= 0 ? text.slice(0, zone) : text;
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Classifies a single IP address literal.
|
|
381
|
+
*
|
|
382
|
+
* The function is pure, synchronous, and deterministic — it performs no name
|
|
383
|
+
* resolution and no I/O. Input that is not an IP address literal (a DNS
|
|
384
|
+
* hostname, for instance) fails; callers that accept hostnames should treat a
|
|
385
|
+
* failure here as "not a literal" and resolve the name, then classify each
|
|
386
|
+
* resolved address.
|
|
387
|
+
*
|
|
388
|
+
* Accepted forms:
|
|
389
|
+
*
|
|
390
|
+
* - dotted-quad IPv4 (`169.254.169.254`)
|
|
391
|
+
* - the shortened and non-decimal IPv4 forms the WHATWG URL parser accepts
|
|
392
|
+
* (`127.1`, `2130706433`, `0177.0.0.1`, `0x7f.1`)
|
|
393
|
+
* - IPv6, with or without `::` compression, optionally bracketed as a URL
|
|
394
|
+
* `hostname` is (`[::1]`) and optionally carrying a zone id (`fe80::1%eth0`)
|
|
395
|
+
* - IPv6 forms that embed an IPv4 address — IPv4-mapped, IPv4-compatible,
|
|
396
|
+
* NAT64 and 6to4 — which are classified by their embedded IPv4 address
|
|
397
|
+
*
|
|
398
|
+
* A trailing dot is accepted on an IPv4 literal (`127.0.0.1.`), matching URL
|
|
399
|
+
* hostname normalization.
|
|
400
|
+
*
|
|
401
|
+
* **Classify a URL's `hostname`, never the raw URL text.** This function does
|
|
402
|
+
* not apply IDNA/Unicode normalization, and that step is not cosmetic: the
|
|
403
|
+
* platform's URL parser reads `http://127.0.0.1/`, `http://127。0。0。1/` and
|
|
404
|
+
* even `http://⑫7.0.0.1/` as the host `127.0.0.1`, because IDNA normalizes
|
|
405
|
+
* fullwidth digits, the ideographic full stop and circled numbers to their
|
|
406
|
+
* ASCII equivalents. Handed one of those strings directly this function fails —
|
|
407
|
+
* which is fail-closed, but only because the caller is then expected to treat
|
|
408
|
+
* "not a literal" as "resolve it as a name". Reading `new URL(...).hostname`
|
|
409
|
+
* gets the normalization for free and is the only supported use.
|
|
410
|
+
*
|
|
411
|
+
* @param address - the address literal to classify.
|
|
412
|
+
* @returns `Success` with the {@link IClassifiedAddress | classification},
|
|
413
|
+
* or `Failure` if the supplied text is not a well-formed IP address literal.
|
|
414
|
+
* @public
|
|
415
|
+
*/
|
|
416
|
+
function classifyAddress(address) {
|
|
417
|
+
const text = normalizeAddressText(address);
|
|
418
|
+
if (text.length === 0) {
|
|
419
|
+
return (0, ts_utils_1.fail)(`"${address}": not a valid IP address literal (empty)`);
|
|
420
|
+
}
|
|
421
|
+
if (text.indexOf(':') >= 0) {
|
|
422
|
+
const bytes = parseIpv6(stripIpv6Zone(text));
|
|
423
|
+
if (bytes === undefined) {
|
|
424
|
+
return (0, ts_utils_1.fail)(`"${address}": not a valid IPv6 address literal`);
|
|
425
|
+
}
|
|
426
|
+
return (0, ts_utils_1.succeed)(Object.assign(Object.assign({}, classifyIpv6Bytes(bytes)), { address }));
|
|
427
|
+
}
|
|
428
|
+
const v4Text = text.length > 1 && text[text.length - 1] === '.' ? text.slice(0, -1) : text;
|
|
429
|
+
const value = parseIpv4(v4Text);
|
|
430
|
+
if (value === undefined) {
|
|
431
|
+
return (0, ts_utils_1.fail)(`"${address}": not a valid IP address literal`);
|
|
432
|
+
}
|
|
433
|
+
const canonical = ipv4ToString(value);
|
|
434
|
+
return (0, ts_utils_1.succeed)({
|
|
435
|
+
address,
|
|
436
|
+
canonical,
|
|
437
|
+
family: 'ipv4',
|
|
438
|
+
classification: classifyIpv4Value(value)
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
//# sourceMappingURL=addressClassification.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"addressClassification.js","sourceRoot":"","sources":["../../../src/packlets/safer-fetch/addressClassification.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) 2026 Erik Fortune\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\n// Address classification is prefix arithmetic: every range test is a mask and a\n// compare, and every IPv6 group/byte conversion is a shift. Spelling those as\n// arithmetic instead would be strictly harder to audit against the RFCs, which\n// is the opposite of what this file needs. The repo already takes per-site\n// disables for the same reason in `crypto-utils/keystore` and `ts-random`.\n/* eslint-disable no-bitwise */\n\nimport { type Result, fail, succeed } from '@fgv/ts-utils';\n\n/**\n * The address family of a parsed IP address literal.\n * @public\n */\nexport type AddressFamily = 'ipv4' | 'ipv6';\n\n/**\n * The security-relevant classification of an IP address.\n *\n * `'public'` is the only classification that describes an address which is\n * globally routable on the public internet; every other value names a\n * special-purpose range that a server-side fetch reaching untrusted input\n * should not be able to reach by default.\n *\n * @public\n */\nexport type AddressClassification =\n /** Globally routable unicast — the only classification a default guard permits. */\n | 'public'\n /** `0.0.0.0/8` and `::` — \"this host on this network\"; routes to localhost on Linux. */\n | 'unspecified'\n /** `127.0.0.0/8` and `::1`. */\n | 'loopback'\n /**\n * `169.254.0.0/16` and `fe80::/10`. The IPv4 range contains the cloud\n * instance-metadata endpoint (`169.254.169.254`) and is the single\n * highest-value SSRF target.\n */\n | 'link-local'\n /** RFC 1918 — `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`. */\n | 'private'\n /** RFC 4193 IPv6 unique-local — `fc00::/7`. */\n | 'unique-local'\n /** RFC 6598 carrier-grade NAT — `100.64.0.0/10`. Frequently carrier or container internal. */\n | 'carrier-grade-nat'\n /** RFC 2544 benchmarking — `198.18.0.0/15`. */\n | 'benchmarking'\n /** Documentation ranges — `192.0.2.0/24`, `198.51.100.0/24`, `203.0.113.0/24`, `2001:db8::/32`. */\n | 'documentation'\n /** IETF protocol assignments — `192.0.0.0/24` and `2001::/23`. */\n | 'protocol-assignment'\n /** `224.0.0.0/4` and `ff00::/8`. */\n | 'multicast'\n /** `255.255.255.255/32`. */\n | 'broadcast'\n /** Every other special-purpose or future-use range (e.g. `240.0.0.0/4`, `fec0::/10`). */\n | 'reserved';\n\n/**\n * The way an IPv6 address carried the IPv4 address whose classification was used.\n *\n * Each of these is a documented SSRF bypass: an address such as\n * `::ffff:169.254.169.254` is an IPv6 address by family, but the address the\n * connection actually reaches is the embedded IPv4 one, so the embedded\n * address is what must be classified.\n *\n * @public\n */\nexport type Ipv4EmbeddingKind =\n /** `::ffff:0:0/96` — the IPv4-mapped IPv6 form. */\n | 'ipv4-mapped'\n /** `::/96` — the deprecated (RFC 4291) IPv4-compatible form. */\n | 'ipv4-compatible'\n /** `64:ff9b::/96` — the RFC 6052 well-known NAT64 prefix. */\n | 'nat64'\n /** `2002::/16` — the RFC 3056 6to4 form, whose IPv4 lives in bits 16..47. */\n | '6to4';\n\n/**\n * The IPv4 address an IPv6 address embedded, and the way it embedded it.\n * @public\n */\nexport interface IEmbeddedIpv4 {\n /** The encoding that carried the IPv4 address. */\n readonly kind: Ipv4EmbeddingKind;\n /** The dotted-quad form of the embedded IPv4 address. */\n readonly address: string;\n}\n\n/**\n * The result of classifying a single IP address literal.\n * @public\n */\nexport interface IClassifiedAddress {\n /** The address exactly as supplied to {@link classifyAddress}. */\n readonly address: string;\n /**\n * A canonical textual form of the address: dotted-quad for IPv4, and\n * lowercase RFC 5952 hex-group form (with `::` run compression) for IPv6.\n * Note that IPv6 addresses embedding an IPv4 address canonicalize to their\n * hex-group form (`::ffff:a9fe:a9fe`), not to the dotted-quad mixed form;\n * `embeddedIpv4` carries the readable IPv4 value.\n */\n readonly canonical: string;\n /** The address family of the literal as supplied — never the embedded family. */\n readonly family: AddressFamily;\n /**\n * The classification used for policy decisions. For an IPv6 address that\n * embeds an IPv4 address, this is the classification of the *embedded* IPv4\n * address.\n */\n readonly classification: AddressClassification;\n /**\n * Present exactly when `classification` was derived from an IPv4 address\n * embedded in this IPv6 address, and absent otherwise.\n */\n readonly embeddedIpv4?: IEmbeddedIpv4;\n}\n\ninterface IIpv4Range {\n readonly network: number;\n readonly prefixLength: number;\n readonly classification: AddressClassification;\n}\n\ninterface IIpv6Range {\n /** Leading bytes of the prefix; length is `ceil(prefixLength / 8)`. */\n readonly prefix: ReadonlyArray<number>;\n readonly prefixLength: number;\n readonly classification: AddressClassification;\n}\n\n/**\n * IPv4 special-purpose ranges, most specific first. The broadcast address is\n * listed ahead of `240.0.0.0/4` because it falls inside it.\n */\nconst IPV4_RANGES: ReadonlyArray<IIpv4Range> = [\n { network: 0xffffffff, prefixLength: 32, classification: 'broadcast' },\n { network: 0x00000000, prefixLength: 8, classification: 'unspecified' },\n { network: 0x0a000000, prefixLength: 8, classification: 'private' },\n { network: 0x64400000, prefixLength: 10, classification: 'carrier-grade-nat' },\n { network: 0x7f000000, prefixLength: 8, classification: 'loopback' },\n { network: 0xa9fe0000, prefixLength: 16, classification: 'link-local' },\n { network: 0xac100000, prefixLength: 12, classification: 'private' },\n { network: 0xc0000000, prefixLength: 24, classification: 'protocol-assignment' },\n { network: 0xc0000200, prefixLength: 24, classification: 'documentation' },\n { network: 0xc0586300, prefixLength: 24, classification: 'reserved' },\n { network: 0xc0a80000, prefixLength: 16, classification: 'private' },\n { network: 0xc6120000, prefixLength: 15, classification: 'benchmarking' },\n { network: 0xc6336400, prefixLength: 24, classification: 'documentation' },\n { network: 0xcb007100, prefixLength: 24, classification: 'documentation' },\n { network: 0xe0000000, prefixLength: 4, classification: 'multicast' },\n { network: 0xf0000000, prefixLength: 4, classification: 'reserved' }\n];\n\n/**\n * IPv6 special-purpose ranges, most specific first. Ranges whose addresses\n * embed an IPv4 address are handled separately, before this table is consulted,\n * and anything outside global unicast (`2000::/3`) that matches nothing here is\n * classified `'reserved'` — so this table only needs to name the ranges that\n * deserve a more specific classification than that fallback gives them.\n */\nconst IPV6_RANGES: ReadonlyArray<IIpv6Range> = [\n { prefix: [0x20, 0x01, 0x0d, 0xb8], prefixLength: 32, classification: 'documentation' },\n { prefix: [0x20, 0x01, 0x00], prefixLength: 23, classification: 'protocol-assignment' },\n { prefix: [0x3f, 0xff, 0x00], prefixLength: 20, classification: 'documentation' },\n { prefix: [0xfc], prefixLength: 7, classification: 'unique-local' },\n { prefix: [0xfe, 0x80], prefixLength: 10, classification: 'link-local' },\n { prefix: [0xff], prefixLength: 8, classification: 'multicast' }\n];\n\n/** The first three bits of a global unicast IPv6 address (`2000::/3`). */\nconst IPV6_GLOBAL_UNICAST_MASK: number = 0xe0;\nconst IPV6_GLOBAL_UNICAST_VALUE: number = 0x20;\n\n/**\n * Parses one component of an IPv4 literal using `inet_aton` conventions: a\n * `0x`/`0X` prefix selects hexadecimal, an otherwise-leading `0` selects octal,\n * and anything else is decimal.\n *\n * This is deliberately permissive about *radix* because the WHATWG URL parser\n * is: `http://0177.0.0.1/` and `http://2130706433/` are both `127.0.0.1`, and a\n * classifier that only understands dotted decimal is bypassed by either.\n */\nfunction parseIpv4Component(text: string): number | undefined {\n if (text.length === 0) {\n return undefined;\n }\n\n let radix: number = 10;\n let digits: string = text;\n if (text.length > 1 && (text[1] === 'x' || text[1] === 'X') && text[0] === '0') {\n radix = 16;\n digits = text.slice(2);\n } else if (text.length > 1 && text[0] === '0') {\n radix = 8;\n digits = text.slice(1);\n }\n\n // The WHATWG IPv4 number parser returns 0 when stripping the radix prefix\n // leaves nothing behind, so a bare `0x` is zero: `127.0x.1` is `127.0.0.1`.\n // Rejecting it here would leave a live encoding bypass.\n if (digits.length === 0) {\n return 0;\n }\n\n const allowed: RegExp = radix === 16 ? /^[0-9a-fA-F]+$/ : radix === 8 ? /^[0-7]+$/ : /^[0-9]+$/;\n if (!allowed.test(digits)) {\n return undefined;\n }\n\n const value: number = Number.parseInt(digits, radix);\n return Number.isSafeInteger(value) ? value : undefined;\n}\n\n/**\n * Parses an IPv4 literal in any of the forms the platform URL parser accepts\n * (`a.b.c.d`, `a.b.c`, `a.b`, `a`), returning the address as an unsigned 32-bit\n * value. The final component absorbs all remaining bytes, so `127.1` is\n * `127.0.0.1` and `2130706433` is also `127.0.0.1`.\n */\nfunction parseIpv4(text: string): number | undefined {\n const parts: string[] = text.split('.');\n if (parts.length > 4) {\n return undefined;\n }\n\n const values: number[] = [];\n for (const part of parts) {\n const value: number | undefined = parseIpv4Component(part);\n if (value === undefined) {\n return undefined;\n }\n values.push(value);\n }\n\n const leading: number = values.length - 1;\n for (let i: number = 0; i < leading; i++) {\n if (values[i] > 0xff) {\n return undefined;\n }\n }\n if (values[leading] > Math.pow(256, 4 - leading) - 1) {\n return undefined;\n }\n\n let address: number = values[leading];\n for (let i: number = 0; i < leading; i++) {\n address += values[i] * Math.pow(256, 3 - i);\n }\n return address >>> 0;\n}\n\n/**\n * Parses a strict dotted-quad IPv4 literal — exactly four decimal components,\n * each `0..255`, with no octal or hexadecimal forms. This is the grammar RFC\n * 4291 permits for the IPv4 tail of an IPv6 literal, and it is deliberately\n * stricter than {@link parseIpv4}: an unparseable literal fails closed.\n */\nfunction parseStrictDottedQuad(text: string): number | undefined {\n const parts: string[] = text.split('.');\n if (parts.length !== 4) {\n return undefined;\n }\n\n let address: number = 0;\n for (const part of parts) {\n if (!/^(0|[1-9][0-9]{0,2})$/.test(part)) {\n return undefined;\n }\n const value: number = Number.parseInt(part, 10);\n if (value > 0xff) {\n return undefined;\n }\n address = address * 256 + value;\n }\n return address >>> 0;\n}\n\n/**\n * Parses a colon-separated run of IPv6 groups, allowing a trailing embedded\n * dotted-quad IPv4 literal which expands to the final two groups.\n *\n * @param allowEmbeddedIpv4 - whether this run may end in a dotted-quad IPv4\n * literal. Only the run that ends the whole address may, so the run *before* a\n * `::` never does: `1.2.3.4::` is malformed, not `102:304::`.\n */\nfunction parseIpv6Groups(text: string, allowEmbeddedIpv4: boolean): number[] | undefined {\n if (text.length === 0) {\n return [];\n }\n\n const parts: string[] = text.split(':');\n const groups: number[] = [];\n for (let i: number = 0; i < parts.length; i++) {\n const part: string = parts[i];\n if (part.indexOf('.') >= 0) {\n if (!allowEmbeddedIpv4 || i !== parts.length - 1) {\n return undefined;\n }\n const embedded: number | undefined = parseStrictDottedQuad(part);\n if (embedded === undefined) {\n return undefined;\n }\n groups.push((embedded >>> 16) & 0xffff, embedded & 0xffff);\n continue;\n }\n if (!/^[0-9a-fA-F]{1,4}$/.test(part)) {\n return undefined;\n }\n groups.push(Number.parseInt(part, 16));\n }\n return groups;\n}\n\n/**\n * Parses an IPv6 literal into its 16 constituent bytes.\n */\nfunction parseIpv6(text: string): Uint8Array | undefined {\n const doubleColon: number = text.indexOf('::');\n let groups: number[] | undefined;\n\n if (doubleColon >= 0) {\n if (text.indexOf('::', doubleColon + 1) >= 0) {\n return undefined;\n }\n const head: number[] | undefined = parseIpv6Groups(text.slice(0, doubleColon), false);\n const tail: number[] | undefined = parseIpv6Groups(text.slice(doubleColon + 2), true);\n if (head === undefined || tail === undefined) {\n return undefined;\n }\n const elided: number = 8 - head.length - tail.length;\n if (elided < 1) {\n return undefined;\n }\n groups = head.concat(new Array<number>(elided).fill(0), tail);\n } else {\n groups = parseIpv6Groups(text, true);\n if (groups === undefined || groups.length !== 8) {\n return undefined;\n }\n }\n\n const bytes: Uint8Array = new Uint8Array(16);\n for (let i: number = 0; i < 8; i++) {\n bytes[i * 2] = (groups[i] >>> 8) & 0xff;\n bytes[i * 2 + 1] = groups[i] & 0xff;\n }\n return bytes;\n}\n\nfunction ipv4ToString(address: number): string {\n return [(address >>> 24) & 0xff, (address >>> 16) & 0xff, (address >>> 8) & 0xff, address & 0xff].join('.');\n}\n\n/**\n * Renders the RFC 5952 canonical text form of an IPv6 address: lowercase, no\n * leading zeros in a group, and the leftmost longest run of two or more\n * all-zero groups replaced by `::`.\n */\nfunction ipv6ToString(bytes: Uint8Array): string {\n const groups: number[] = [];\n for (let i: number = 0; i < 8; i++) {\n groups.push((bytes[i * 2] << 8) | bytes[i * 2 + 1]);\n }\n\n let bestStart: number = -1;\n let bestLength: number = 0;\n let runStart: number = -1;\n let runLength: number = 0;\n for (let i: number = 0; i < 8; i++) {\n if (groups[i] === 0) {\n if (runStart < 0) {\n runStart = i;\n runLength = 0;\n }\n runLength++;\n if (runLength > bestLength) {\n bestStart = runStart;\n bestLength = runLength;\n }\n } else {\n runStart = -1;\n runLength = 0;\n }\n }\n\n const rendered: string[] = groups.map((g) => g.toString(16));\n if (bestLength < 2) {\n return rendered.join(':');\n }\n const head: string = rendered.slice(0, bestStart).join(':');\n const tail: string = rendered.slice(bestStart + bestLength).join(':');\n return `${head}::${tail}`;\n}\n\nfunction classifyIpv4Value(address: number): AddressClassification {\n for (const range of IPV4_RANGES) {\n const mask: number = (0xffffffff << (32 - range.prefixLength)) >>> 0;\n if ((address & mask) >>> 0 === range.network) {\n return range.classification;\n }\n }\n return 'public';\n}\n\nfunction matchesIpv6Range(bytes: Uint8Array, range: IIpv6Range): boolean {\n const fullBytes: number = range.prefixLength >>> 3;\n for (let i: number = 0; i < fullBytes; i++) {\n if (bytes[i] !== range.prefix[i]) {\n return false;\n }\n }\n const remainingBits: number = range.prefixLength & 7;\n if (remainingBits === 0) {\n return true;\n }\n const mask: number = (0xff << (8 - remainingBits)) & 0xff;\n return (bytes[fullBytes] & mask) === (range.prefix[fullBytes] & mask);\n}\n\nfunction allZero(bytes: Uint8Array, start: number, end: number): boolean {\n for (let i: number = start; i < end; i++) {\n if (bytes[i] !== 0) {\n return false;\n }\n }\n return true;\n}\n\nfunction readIpv4At(bytes: Uint8Array, offset: number): number {\n return (\n ((bytes[offset] << 24) | (bytes[offset + 1] << 16) | (bytes[offset + 2] << 8) | bytes[offset + 3]) >>> 0\n );\n}\n\ninterface IIpv6Embedding {\n readonly kind: Ipv4EmbeddingKind;\n readonly value: number;\n}\n\n/**\n * Detects the IPv4 address embedded in an IPv6 address, if any. `::` and `::1`\n * are excluded before the IPv4-compatible check so that the unspecified and\n * loopback addresses keep their own classifications.\n */\nfunction detectIpv6Embedding(bytes: Uint8Array): IIpv6Embedding | undefined {\n if (allZero(bytes, 0, 10) && bytes[10] === 0xff && bytes[11] === 0xff) {\n return { kind: 'ipv4-mapped', value: readIpv4At(bytes, 12) };\n }\n if (\n bytes[0] === 0x00 &&\n bytes[1] === 0x64 &&\n bytes[2] === 0xff &&\n bytes[3] === 0x9b &&\n allZero(bytes, 4, 12)\n ) {\n return { kind: 'nat64', value: readIpv4At(bytes, 12) };\n }\n if (bytes[0] === 0x20 && bytes[1] === 0x02) {\n return { kind: '6to4', value: readIpv4At(bytes, 2) };\n }\n if (allZero(bytes, 0, 12)) {\n return { kind: 'ipv4-compatible', value: readIpv4At(bytes, 12) };\n }\n return undefined;\n}\n\nfunction classifyIpv6Bytes(bytes: Uint8Array): IClassifiedAddress {\n const canonical: string = ipv6ToString(bytes);\n\n if (allZero(bytes, 0, 16)) {\n return { address: canonical, canonical, family: 'ipv6', classification: 'unspecified' };\n }\n if (allZero(bytes, 0, 15) && bytes[15] === 0x01) {\n return { address: canonical, canonical, family: 'ipv6', classification: 'loopback' };\n }\n\n const embedding: IIpv6Embedding | undefined = detectIpv6Embedding(bytes);\n if (embedding !== undefined) {\n return {\n address: canonical,\n canonical,\n family: 'ipv6',\n classification: classifyIpv4Value(embedding.value),\n embeddedIpv4: { kind: embedding.kind, address: ipv4ToString(embedding.value) }\n };\n }\n\n for (const range of IPV6_RANGES) {\n if (matchesIpv6Range(bytes, range)) {\n return { address: canonical, canonical, family: 'ipv6', classification: range.classification };\n }\n }\n\n // Only `2000::/3` is assigned as global unicast. Everything else that reached\n // this point is unassigned or reserved for future use, so it is never public.\n const classification: AddressClassification =\n (bytes[0] & IPV6_GLOBAL_UNICAST_MASK) === IPV6_GLOBAL_UNICAST_VALUE ? 'public' : 'reserved';\n return { address: canonical, canonical, family: 'ipv6', classification };\n}\n\n/**\n * Normalizes the textual forms an address can arrive in before parsing:\n * surrounding whitespace and the square brackets a URL `hostname` carries for\n * an IPv6 literal. A zone identifier is *not* stripped here — it is stripped on\n * the IPv6 path only, because `8.8.8.8%something` is not an address with a zone\n * and must fail rather than be read as `8.8.8.8`.\n */\nfunction normalizeAddressText(address: string): string {\n const text: string = address.trim();\n if (text.length > 1 && text[0] === '[' && text[text.length - 1] === ']') {\n return text.slice(1, -1);\n }\n return text;\n}\n\n/** Strips an IPv6 zone identifier (`fe80::1%eth0`). */\nfunction stripIpv6Zone(text: string): string {\n const zone: number = text.indexOf('%');\n return zone >= 0 ? text.slice(0, zone) : text;\n}\n\n/**\n * Classifies a single IP address literal.\n *\n * The function is pure, synchronous, and deterministic — it performs no name\n * resolution and no I/O. Input that is not an IP address literal (a DNS\n * hostname, for instance) fails; callers that accept hostnames should treat a\n * failure here as \"not a literal\" and resolve the name, then classify each\n * resolved address.\n *\n * Accepted forms:\n *\n * - dotted-quad IPv4 (`169.254.169.254`)\n * - the shortened and non-decimal IPv4 forms the WHATWG URL parser accepts\n * (`127.1`, `2130706433`, `0177.0.0.1`, `0x7f.1`)\n * - IPv6, with or without `::` compression, optionally bracketed as a URL\n * `hostname` is (`[::1]`) and optionally carrying a zone id (`fe80::1%eth0`)\n * - IPv6 forms that embed an IPv4 address — IPv4-mapped, IPv4-compatible,\n * NAT64 and 6to4 — which are classified by their embedded IPv4 address\n *\n * A trailing dot is accepted on an IPv4 literal (`127.0.0.1.`), matching URL\n * hostname normalization.\n *\n * **Classify a URL's `hostname`, never the raw URL text.** This function does\n * not apply IDNA/Unicode normalization, and that step is not cosmetic: the\n * platform's URL parser reads `http://127.0.0.1/`, `http://127。0。0。1/` and\n * even `http://⑫7.0.0.1/` as the host `127.0.0.1`, because IDNA normalizes\n * fullwidth digits, the ideographic full stop and circled numbers to their\n * ASCII equivalents. Handed one of those strings directly this function fails —\n * which is fail-closed, but only because the caller is then expected to treat\n * \"not a literal\" as \"resolve it as a name\". Reading `new URL(...).hostname`\n * gets the normalization for free and is the only supported use.\n *\n * @param address - the address literal to classify.\n * @returns `Success` with the {@link IClassifiedAddress | classification},\n * or `Failure` if the supplied text is not a well-formed IP address literal.\n * @public\n */\nexport function classifyAddress(address: string): Result<IClassifiedAddress> {\n const text: string = normalizeAddressText(address);\n if (text.length === 0) {\n return fail(`\"${address}\": not a valid IP address literal (empty)`);\n }\n\n if (text.indexOf(':') >= 0) {\n const bytes: Uint8Array | undefined = parseIpv6(stripIpv6Zone(text));\n if (bytes === undefined) {\n return fail(`\"${address}\": not a valid IPv6 address literal`);\n }\n return succeed({ ...classifyIpv6Bytes(bytes), address });\n }\n\n const v4Text: string = text.length > 1 && text[text.length - 1] === '.' ? text.slice(0, -1) : text;\n const value: number | undefined = parseIpv4(v4Text);\n if (value === undefined) {\n return fail(`\"${address}\": not a valid IP address literal`);\n }\n const canonical: string = ipv4ToString(value);\n return succeed({\n address,\n canonical,\n family: 'ipv4',\n classification: classifyIpv4Value(value)\n });\n}\n"]}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { type Result } from '@fgv/ts-utils';
|
|
2
|
+
import { type IClassifiedAddress } from './addressClassification';
|
|
3
|
+
/**
|
|
4
|
+
* The evidence behind an allowed address-policy decision.
|
|
5
|
+
* @public
|
|
6
|
+
*/
|
|
7
|
+
export interface IAddressCheckVerdict {
|
|
8
|
+
/** The {@link IAddressPolicy.name | name} of the policy that produced the verdict. */
|
|
9
|
+
readonly policy: string;
|
|
10
|
+
/**
|
|
11
|
+
* The classification of every address the policy examined, in the order
|
|
12
|
+
* supplied. Empty for {@link allowAnyAddressPolicy}, which classifies nothing.
|
|
13
|
+
*/
|
|
14
|
+
readonly addresses: ReadonlyArray<IClassifiedAddress>;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* A pure, synchronous decision over a set of IP addresses.
|
|
18
|
+
*
|
|
19
|
+
* A policy performs no name resolution and no I/O: a caller resolves a
|
|
20
|
+
* hostname to its addresses and hands the whole list to
|
|
21
|
+
* {@link IAddressPolicy.checkAddresses}. The list contract is
|
|
22
|
+
* **reject-if-any** — a hostname that resolves to one public and one private
|
|
23
|
+
* address is disallowed, because without connect-time address pinning there is
|
|
24
|
+
* no guarantee which address the connection will use.
|
|
25
|
+
*
|
|
26
|
+
* @public
|
|
27
|
+
*/
|
|
28
|
+
export interface IAddressPolicy {
|
|
29
|
+
/**
|
|
30
|
+
* A stable, greppable identifier for the posture this policy implements.
|
|
31
|
+
* Surfaced in the failure message when an address is disallowed.
|
|
32
|
+
*/
|
|
33
|
+
readonly name: string;
|
|
34
|
+
/**
|
|
35
|
+
* Decides whether a connection may be made to a destination that resolved to
|
|
36
|
+
* `addresses`.
|
|
37
|
+
*
|
|
38
|
+
* @param addresses - every address the destination resolved to, or the
|
|
39
|
+
* single literal address when the destination was an IP literal.
|
|
40
|
+
* @returns `Success` with the {@link IAddressCheckVerdict | verdict} when
|
|
41
|
+
* *every* address is permitted, `Failure` naming every address that is not.
|
|
42
|
+
* An empty list is a failure for any policy that offers a guarantee: nothing
|
|
43
|
+
* was verified, so nothing may be reached.
|
|
44
|
+
*/
|
|
45
|
+
checkAddresses(addresses: ReadonlyArray<string>): Result<IAddressCheckVerdict>;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Options shared by {@link blockPrivateNetworksPolicy} and the address guard built over it.
|
|
49
|
+
* @public
|
|
50
|
+
*/
|
|
51
|
+
export interface IBlockPrivateNetworksOptions {
|
|
52
|
+
/**
|
|
53
|
+
* Permits `127.0.0.0/8` and `::1` (and their IPv4-mapped forms). Off by
|
|
54
|
+
* default: the polarity that ships a guard permitting `http://127.0.0.1:6379/`
|
|
55
|
+
* so a local-development convenience keeps working is the wrong one. A
|
|
56
|
+
* caller talking to a local sidecar opts in here, and the opt-in is
|
|
57
|
+
* independently greppable at the call site.
|
|
58
|
+
*/
|
|
59
|
+
readonly allowLoopback?: boolean;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Creates the recommended address policy: every address must be a globally
|
|
63
|
+
* routable public unicast address.
|
|
64
|
+
*
|
|
65
|
+
* Rejected, in both their plain and their IPv6-embedded encodings: loopback,
|
|
66
|
+
* the link-local range that carries the cloud instance-metadata endpoint
|
|
67
|
+
* (`169.254.169.254`), RFC 1918 private ranges, IPv6 unique-local, carrier-grade
|
|
68
|
+
* NAT (`100.64.0.0/10`), the unspecified address, multicast, broadcast,
|
|
69
|
+
* benchmarking, documentation and every other reserved range. The encoding
|
|
70
|
+
* bypasses covered are IPv4-mapped IPv6 (`::ffff:169.254.169.254`),
|
|
71
|
+
* IPv4-compatible IPv6, NAT64 (`64:ff9b::a9fe:a9fe`), 6to4 (`2002:a9fe:a9fe::`)
|
|
72
|
+
* and the shortened / octal / hexadecimal / decimal IPv4 literal forms
|
|
73
|
+
* (`127.1`, `0177.0.0.1`, `0x7f.1`, `2130706433`).
|
|
74
|
+
*
|
|
75
|
+
* **What this does not protect against.** The policy classifies addresses a
|
|
76
|
+
* caller has already resolved; it cannot see the address the connection
|
|
77
|
+
* ultimately uses. A hostile DNS server that answers the resolution with a
|
|
78
|
+
* public address and the connect with a private one is not stopped by this
|
|
79
|
+
* policy — closing that requires connecting to a pinned address. The policy
|
|
80
|
+
* also says nothing about scheme, host, port, redirects or response content.
|
|
81
|
+
*
|
|
82
|
+
* This is the **policy-layer** factory. It is not what a safer-fetch call's
|
|
83
|
+
* `addressGuard` option takes: an entry point takes an `IAddressGuard`, which is
|
|
84
|
+
* the asynchronous, hop-chain-aware, name-resolving half. `blockPrivateNetworks`
|
|
85
|
+
* (Node only) is the guard that resolves a hostname and delegates every resolved
|
|
86
|
+
* address to this policy; reach for that unless you are classifying an address
|
|
87
|
+
* list you already hold.
|
|
88
|
+
*
|
|
89
|
+
* @param options - optional {@link IBlockPrivateNetworksOptions | relaxations}
|
|
90
|
+
* of the default posture.
|
|
91
|
+
* @returns the policy. Construction cannot fail.
|
|
92
|
+
* @public
|
|
93
|
+
*/
|
|
94
|
+
export declare function blockPrivateNetworksPolicy(options?: IBlockPrivateNetworksOptions): IAddressPolicy;
|
|
95
|
+
/**
|
|
96
|
+
* Creates a policy that permits every address, including loopback, link-local
|
|
97
|
+
* and private ones.
|
|
98
|
+
*
|
|
99
|
+
* **This policy provides no protection whatsoever.** It classifies nothing,
|
|
100
|
+
* rejects nothing, and never fails — not even for an empty address list or an
|
|
101
|
+
* address that is not a well-formed literal. It exists so that choosing to go
|
|
102
|
+
* without an address guarantee is a deliberate, named, greppable act at the
|
|
103
|
+
* call site rather than something reachable by omission, and so that tests and
|
|
104
|
+
* genuinely trusted-input paths do not hand-roll something worse.
|
|
105
|
+
*
|
|
106
|
+
* It is the only correct choice in a browser, where neither name resolution nor
|
|
107
|
+
* redirect interposition is available and no address guarantee is possible.
|
|
108
|
+
*
|
|
109
|
+
* @returns the policy.
|
|
110
|
+
* @public
|
|
111
|
+
*/
|
|
112
|
+
export declare function allowAnyAddressPolicy(): IAddressPolicy;
|
|
113
|
+
//# sourceMappingURL=addressPolicy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"addressPolicy.d.ts","sourceRoot":"","sources":["../../../src/packlets/safer-fetch/addressPolicy.ts"],"names":[],"mappings":"AAoBA,OAAO,EAAE,KAAK,MAAM,EAA6B,MAAM,eAAe,CAAC;AACvE,OAAO,EAEL,KAAK,kBAAkB,EAExB,MAAM,yBAAyB,CAAC;AAEjC;;;GAGG;AACH,MAAM,WAAW,oBAAoB;IACnC,sFAAsF;IACtF,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,QAAQ,CAAC,SAAS,EAAE,aAAa,CAAC,kBAAkB,CAAC,CAAC;CACvD;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAEtB;;;;;;;;;;OAUG;IACH,cAAc,CAAC,SAAS,EAAE,aAAa,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,oBAAoB,CAAC,CAAC;CAChF;AAED;;;GAGG;AACH,MAAM,WAAW,4BAA4B;IAC3C;;;;;;OAMG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,CAAC;CAClC;AA2CD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,CAAC,EAAE,4BAA4B,GAAG,cAAc,CAWjG;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,qBAAqB,IAAI,cAAc,CAWtD"}
|