@nullix/zod-mongoose-studio 3.3.0 → 3.3.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/.output/nitro.json +1 -1
- package/.output/public/_fonts/57NSSoFy1VLVs2gqly8Ls9awBnZMFyXGrefpmqvdqmc-zJfbBtpgM4cDmcXBsqZNW79_kFnlpPd62b48glgdydA.woff2 +0 -0
- package/.output/public/_fonts/8VR2wSMN-3U4NbWAVYXlkRV6hA0jFBXP-0RtL3X7fko-x2gYI4qfmkRdxyQQUPaBZdZdgl1TeVrquF_TxHeM4lM.woff2 +0 -0
- package/.output/public/_fonts/GsKUclqeNLJ96g5AU593ug6yanivOiwjW_7zESNPChw-jHA4tBeM1bjF7LATGUpfBuSTyomIFrWBTzjF7txVYfg.woff2 +0 -0
- package/.output/public/_fonts/Ld1FnTo3yTIwDyGfTQ5-Fws9AWsCbKfMvgxduXr7JcY-W25bL8NF1fjpLRSOgJb7RoZPHqGQNwMTM7S9tHVoxx8.woff2 +0 -0
- package/.output/public/_fonts/NdzqRASp2bovDUhQT1IRE_EMqKJ2KYQdTCfFcBvL8yw-KhwZiS86o3fErOe5GGMExHUemmI_dBfaEFxjISZrBd0.woff2 +0 -0
- package/.output/public/_fonts/iTkrULNFJJkTvihIg1Vqi5IODRH_9btXCioVF5l98I8-AndUyau2HR2felA_ra8V2mutQgschhasE5FD1dXGJX8.woff2 +0 -0
- package/.output/public/_nuxt/B6lqezyA.js +27 -0
- package/.output/public/_nuxt/{IxYCG8hB.js → B7ScFmDB.js} +1 -1
- package/.output/public/_nuxt/B8gqXD2U.js +1 -0
- package/.output/public/_nuxt/{ikxafTHA.js → BC9tFNFa.js} +180 -184
- package/.output/public/_nuxt/{qwxNOyg7.js → Bbi9UBvi.js} +1 -1
- package/.output/public/_nuxt/{CNZorjDf.js → BcYnCaqL.js} +1 -1
- package/.output/public/_nuxt/{BzhSDy30.js → BlZ0x31M.js} +1 -1
- package/.output/public/_nuxt/Bw82IGM8.js +1 -0
- package/.output/public/_nuxt/{qQyvCkbV.js → C8xmwXC2.js} +1 -1
- package/.output/public/_nuxt/{Ce-T_1aD.js → C919AVYR.js} +1 -1
- package/.output/public/_nuxt/{nDZOJ7rN.js → CsQL5F-l.js} +2 -2
- package/.output/public/_nuxt/Czp4_2o-.js +33 -0
- package/.output/public/_nuxt/{CC3n3Aga.js → DJa7_7Hz.js} +1 -1
- package/.output/public/_nuxt/DKK0Mrrg.js +1 -0
- package/.output/public/_nuxt/DbUZZZZu.js +6 -0
- package/.output/public/_nuxt/{B7P0wQ35.js → DbqvVYbv.js} +1 -1
- package/.output/public/_nuxt/{Ick_23I4.js → JkiDMdYG.js} +1 -1
- package/.output/public/_nuxt/{81vqh61j.js → OVQ3C2Ge.js} +1 -1
- package/.output/public/_nuxt/{Cs8GXJCB.js → PXBhK7S_.js} +1 -1
- package/.output/public/_nuxt/{CAd-qHfI.js → YC-RLggm.js} +1 -1
- package/.output/public/_nuxt/builds/latest.json +1 -1
- package/.output/public/_nuxt/builds/meta/46174b5e-cab6-4a73-95f8-66a1c9b1be2b.json +1 -0
- package/.output/public/_nuxt/{tzj4nvDE.js → d1NBz903.js} +2 -2
- package/.output/public/_nuxt/{entry.J_CbC0oY.css → entry.DaqrxXuH.css} +1 -1
- package/.output/public/_nuxt/error-404.CTSXlFYa.css +1 -0
- package/.output/public/_nuxt/error-500.DqEfQnVZ.css +1 -0
- package/.output/public/_nuxt/{Bd32GOUy.js → iGzUozbF.js} +1 -1
- package/.output/public/_nuxt/{DjPT-NCE.js → iKW_mt0p.js} +1 -1
- package/.output/server/chunks/_/error-500.mjs.map +1 -1
- package/.output/server/chunks/build/client.precomputed.mjs +1 -1
- package/.output/server/chunks/build/{error-404-BN-7rNE4.mjs → error-404-KRkA0DPI.mjs} +10 -4
- package/.output/server/chunks/build/error-404-KRkA0DPI.mjs.map +1 -0
- package/.output/server/chunks/build/error-404-styles.I0NYw-Bn.mjs +8 -0
- package/.output/server/chunks/build/error-404-styles.I0NYw-Bn.mjs.map +1 -0
- package/.output/server/chunks/build/{error-500-DhIQ-2yt.mjs → error-500-DIIFf55p.mjs} +10 -4
- package/.output/server/chunks/build/error-500-DIIFf55p.mjs.map +1 -0
- package/.output/server/chunks/build/error-500-styles.D1cs9hQy.mjs +8 -0
- package/.output/server/chunks/build/error-500-styles.D1cs9hQy.mjs.map +1 -0
- package/.output/server/chunks/build/index-CB1N4gax.mjs +557 -0
- package/.output/server/chunks/build/index-CB1N4gax.mjs.map +1 -0
- package/.output/server/chunks/build/server.mjs +326 -248
- package/.output/server/chunks/build/server.mjs.map +1 -1
- package/.output/server/chunks/build/styles.mjs +4 -4
- package/.output/server/chunks/nitro/nitro.mjs +2319 -852
- package/.output/server/chunks/nitro/nitro.mjs.map +1 -1
- package/.output/server/chunks/routes/api/editor-types.get.mjs +113 -0
- package/.output/server/chunks/routes/api/editor-types.get.mjs.map +1 -0
- package/.output/server/chunks/routes/api/parse.post.mjs +26 -1448
- package/.output/server/chunks/routes/api/parse.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/resolve.get.mjs +6 -0
- package/.output/server/chunks/routes/api/resolve.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/studio/run.post.mjs +40 -0
- package/.output/server/chunks/routes/api/studio/run.post.mjs.map +1 -0
- package/.output/server/chunks/routes/renderer.mjs +1 -1
- package/.output/server/chunks/routes/renderer.mjs.map +1 -1
- package/.output/server/index.mjs +7 -1
- package/.output/server/index.mjs.map +1 -1
- package/.output/server/node_modules/acorn/dist/acorn.mjs +6266 -0
- package/.output/server/node_modules/acorn/package.json +50 -0
- package/.output/server/node_modules/ip-address/dist/common.js +112 -4
- package/.output/server/node_modules/ip-address/dist/ipv4.js +322 -87
- package/.output/server/node_modules/ip-address/dist/ipv6.js +654 -199
- package/.output/server/node_modules/ip-address/dist/v4/constants.js +45 -2
- package/.output/server/node_modules/ip-address/dist/v6/constants.js +55 -3
- package/.output/server/node_modules/ip-address/dist/v6/helpers.js +12 -3
- package/.output/server/node_modules/ip-address/package.json +49 -37
- package/.output/server/node_modules/ufo/dist/index.mjs +2 -2
- package/.output/server/node_modules/ufo/package.json +9 -9
- package/.output/server/package.json +4 -3
- package/README.md +65 -20
- package/app/assets/css/main.css +1 -0
- package/app/assets/css/studio.css +131 -0
- package/app/components/StudioConversion.vue +41 -0
- package/app/components/StudioDataPanel.vue +21 -0
- package/app/components/StudioEditor.vue +78 -102
- package/app/components/StudioExpandButton.vue +11 -0
- package/app/components/StudioOutput.vue +7 -1
- package/app/components/StudioSourcePanel.vue +16 -0
- package/app/components/StudioValidation.vue +47 -0
- package/app/components/ZodMongooseStudio.vue +216 -198
- package/app/composables/useStudioRunner.ts +142 -0
- package/app/pages/index.vue +1 -1
- package/app/plugins/monaco.client.ts +2 -0
- package/bin/index.mjs +5 -0
- package/nuxt.config.ts +4 -3
- package/package.json +6 -3
- package/server/api/editor-types.get.ts +7 -0
- package/server/api/parse.post.ts +17 -175
- package/server/api/studio/run.post.ts +29 -0
- package/server/middleware/security.ts +29 -7
- package/server/utils/editor-types.json +1 -0
- package/server/utils/example-data.ts +80 -0
- package/server/utils/parse-request.ts +28 -0
- package/server/utils/runner-client.ts +40 -0
- package/server/utils/studio-execution.ts +176 -0
- package/.output/public/_nuxt/BD40YYqZ.js +0 -33
- package/.output/public/_nuxt/BlQmLjV4.js +0 -1
- package/.output/public/_nuxt/CJ4Cv9fY.js +0 -61
- package/.output/public/_nuxt/builds/meta/495a8ebb-444d-4414-9b98-c181aa84d817.json +0 -1
- package/.output/public/_nuxt/error-404.64pZEiDn.css +0 -1
- package/.output/public/_nuxt/error-500.DHOpI0Ir.css +0 -1
- package/.output/public/_nuxt/rH_O4qXT.js +0 -1
- package/.output/server/chunks/build/error-404-BN-7rNE4.mjs.map +0 -1
- package/.output/server/chunks/build/error-404-styles.Byx46oLR.mjs +0 -8
- package/.output/server/chunks/build/error-404-styles.Byx46oLR.mjs.map +0 -1
- package/.output/server/chunks/build/error-500-DhIQ-2yt.mjs.map +0 -1
- package/.output/server/chunks/build/error-500-styles.BL3UT0Wa.mjs +0 -8
- package/.output/server/chunks/build/error-500-styles.BL3UT0Wa.mjs.map +0 -1
- package/.output/server/chunks/build/index-B4T6zHp0.mjs +0 -1904
- package/.output/server/chunks/build/index-B4T6zHp0.mjs.map +0 -1
- package/.output/server/node_modules/zod/index.js +0 -4
- /package/.output/public/_nuxt/{editor.CdBKkuhR.css → monaco.CdBKkuhR.css} +0 -0
|
@@ -34,6 +34,7 @@ const ipv4_1 = require("./ipv4");
|
|
|
34
34
|
const regular_expressions_1 = require("./v6/regular-expressions");
|
|
35
35
|
const address_error_1 = require("./address-error");
|
|
36
36
|
const common_1 = require("./common");
|
|
37
|
+
const isCorrect6 = common.isCorrect(constants6.BITS);
|
|
37
38
|
function assert(condition) {
|
|
38
39
|
if (!condition) {
|
|
39
40
|
throw new Error('Assertion failed.');
|
|
@@ -72,12 +73,10 @@ function paddedHex(octet) {
|
|
|
72
73
|
return parseInt(octet, 16).toString(16).padStart(4, '0');
|
|
73
74
|
}
|
|
74
75
|
function unsignByte(b) {
|
|
75
|
-
// eslint-disable-next-line no-bitwise
|
|
76
76
|
return b & 0xff;
|
|
77
77
|
}
|
|
78
78
|
/**
|
|
79
79
|
* Represents an IPv6 address
|
|
80
|
-
* @class Address6
|
|
81
80
|
* @param {string} address - An IPv6 address string
|
|
82
81
|
* @param {number} [groups=8] - How many octets to parse
|
|
83
82
|
* @example
|
|
@@ -93,19 +92,28 @@ class Address6 {
|
|
|
93
92
|
this.zone = '';
|
|
94
93
|
// #region Attributes
|
|
95
94
|
/**
|
|
96
|
-
* Returns true if the given address is in the subnet of the current address
|
|
97
|
-
*
|
|
98
|
-
*
|
|
95
|
+
* Returns true if the given address is in the subnet of the current address.
|
|
96
|
+
* An `Address4` is never in the subnet of an `Address6`; convert with
|
|
97
|
+
* `Address6.fromAddress4()` or `to4()` to compare across families.
|
|
99
98
|
* @returns {boolean}
|
|
100
99
|
*/
|
|
101
100
|
this.isInSubnet = common.isInSubnet;
|
|
101
|
+
/**
|
|
102
|
+
* Returns true if this address's host bits fall inside the given subnet,
|
|
103
|
+
* ignoring this address's own subnet mask. Prefer this over `isInSubnet`
|
|
104
|
+
* when classifying a single address, so the answer doesn't change with the
|
|
105
|
+
* CIDR suffix the caller happened to write — notably when the address came
|
|
106
|
+
* from untrusted input and the result backs a trust-boundary decision.
|
|
107
|
+
* An `Address4` is never in the subnet of an `Address6`; convert with
|
|
108
|
+
* `Address6.fromAddress4()` or `to4()` to compare across families.
|
|
109
|
+
* @returns {boolean}
|
|
110
|
+
*/
|
|
111
|
+
this.isHostInSubnet = common.isHostInSubnet;
|
|
102
112
|
/**
|
|
103
113
|
* Returns true if the address is correct, false otherwise
|
|
104
|
-
* @memberof Address6
|
|
105
|
-
* @instance
|
|
106
114
|
* @returns {boolean}
|
|
107
115
|
*/
|
|
108
|
-
this.isCorrect =
|
|
116
|
+
this.isCorrect = isCorrect6;
|
|
109
117
|
if (optionalGroups === undefined) {
|
|
110
118
|
this.groups = constants6.GROUPS;
|
|
111
119
|
}
|
|
@@ -125,7 +133,10 @@ class Address6 {
|
|
|
125
133
|
}
|
|
126
134
|
address = address.replace(constants6.RE_SUBNET_STRING, '');
|
|
127
135
|
}
|
|
128
|
-
|
|
136
|
+
// RE_SUBNET_STRING anchors on the end of the address, so it strips only
|
|
137
|
+
// the trailing suffix. A second one left behind (`::/0/1`) is malformed
|
|
138
|
+
// and must be rejected rather than parsed as an address group.
|
|
139
|
+
if (/\//.test(address)) {
|
|
129
140
|
throw new address_error_1.AddressError('Invalid subnet mask.');
|
|
130
141
|
}
|
|
131
142
|
const zone = constants6.RE_ZONE_STRING.exec(address);
|
|
@@ -133,23 +144,39 @@ class Address6 {
|
|
|
133
144
|
this.zone = zone[0];
|
|
134
145
|
address = address.replace(constants6.RE_ZONE_STRING, '');
|
|
135
146
|
}
|
|
147
|
+
// The longest well-formed address is all but the last two groups written
|
|
148
|
+
// as four hex digits with their colons, then a 15-character dotted quad:
|
|
149
|
+
// 5 * (groups - 2) + 15, which is 45 for eight groups, the same line
|
|
150
|
+
// CPython's ipaddress module draws. Rejecting longer input here keeps the
|
|
151
|
+
// parse diagnostics, which wrap every offending character in a span,
|
|
152
|
+
// proportional to an address rather than to whatever was passed in.
|
|
153
|
+
const longest = this.groups * 5 + 5;
|
|
154
|
+
if (address.length > longest) {
|
|
155
|
+
throw new address_error_1.AddressError(`IPv6 addresses are at most ${longest} characters.`);
|
|
156
|
+
}
|
|
136
157
|
this.addressMinusSuffix = address;
|
|
137
158
|
this.parsedAddress = this.parse(this.addressMinusSuffix);
|
|
138
159
|
}
|
|
160
|
+
/**
|
|
161
|
+
* Returns true if the given string is a valid IPv6 address (with optional
|
|
162
|
+
* CIDR subnet and zone identifier), false otherwise. Host bits in the
|
|
163
|
+
* subnet portion are allowed (e.g. `2001:db8::1/32` is valid); for strict
|
|
164
|
+
* network-address validation compare `correctForm()` to
|
|
165
|
+
* `startAddress().correctForm()`, or use `networkForm()`.
|
|
166
|
+
*/
|
|
139
167
|
static isValid(address) {
|
|
140
168
|
try {
|
|
141
169
|
// eslint-disable-next-line no-new
|
|
142
170
|
new Address6(address);
|
|
143
171
|
return true;
|
|
144
172
|
}
|
|
145
|
-
catch
|
|
173
|
+
catch {
|
|
146
174
|
return false;
|
|
147
175
|
}
|
|
148
176
|
}
|
|
149
177
|
/**
|
|
150
|
-
* Convert a BigInt to a v6 address object
|
|
151
|
-
*
|
|
152
|
-
* @static
|
|
178
|
+
* Convert a BigInt to a v6 address object. The value must be in the
|
|
179
|
+
* range `[0, 2**128 - 1]`; otherwise `AddressError` is thrown.
|
|
153
180
|
* @param {bigint} bigInt - a BigInt to convert
|
|
154
181
|
* @returns {Address6}
|
|
155
182
|
* @example
|
|
@@ -158,65 +185,57 @@ class Address6 {
|
|
|
158
185
|
* address.correctForm(); // '::e8:d4a5:1000'
|
|
159
186
|
*/
|
|
160
187
|
static fromBigInt(bigInt) {
|
|
188
|
+
if (bigInt < BigInt(0) || bigInt > (BigInt(1) << BigInt(constants6.BITS)) - BigInt(1)) {
|
|
189
|
+
throw new address_error_1.AddressError('IPv6 BigInt must be in the range 0 to 2**128 - 1');
|
|
190
|
+
}
|
|
161
191
|
const hex = bigInt.toString(16).padStart(32, '0');
|
|
162
192
|
const groups = [];
|
|
163
|
-
let i;
|
|
164
|
-
for (i = 0; i < constants6.GROUPS; i++) {
|
|
193
|
+
for (let i = 0; i < constants6.GROUPS; i++) {
|
|
165
194
|
groups.push(hex.slice(i * 4, (i + 1) * 4));
|
|
166
195
|
}
|
|
167
196
|
return new Address6(groups.join(':'));
|
|
168
197
|
}
|
|
169
198
|
/**
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
-
*
|
|
173
|
-
*
|
|
199
|
+
* Parse a URL (with optional bracketed host and port) into an address and
|
|
200
|
+
* port. Returns either `{ address, port }` on success or
|
|
201
|
+
* `{ error, address: null, port: null }` if the URL could not be parsed.
|
|
202
|
+
* Ports are returned as numbers (or `null` if absent or out of range).
|
|
174
203
|
* @example
|
|
175
204
|
* var addressAndPort = Address6.fromURL('http://[ffff::]:8080/foo/');
|
|
176
205
|
* addressAndPort.address.correctForm(); // 'ffff::'
|
|
177
206
|
* addressAndPort.port; // 8080
|
|
178
207
|
*/
|
|
179
208
|
static fromURL(url) {
|
|
209
|
+
var _a;
|
|
180
210
|
let host;
|
|
181
211
|
let port = null;
|
|
182
212
|
let result;
|
|
213
|
+
let error;
|
|
214
|
+
// Remove the protocol prefix, if any
|
|
215
|
+
const stripped = url.replace(/^[a-z][a-z0-9+.-]*:\/\//i, '');
|
|
183
216
|
// If we have brackets parse them and find a port
|
|
184
|
-
if (
|
|
185
|
-
|
|
217
|
+
if (stripped.indexOf('[') !== -1 && stripped.indexOf(']:') !== -1) {
|
|
218
|
+
error = 'failed to parse address with port';
|
|
219
|
+
result = constants6.RE_URL_WITH_PORT.exec(stripped);
|
|
186
220
|
if (result === null) {
|
|
187
|
-
return {
|
|
188
|
-
error: 'failed to parse address with port',
|
|
189
|
-
address: null,
|
|
190
|
-
port: null,
|
|
191
|
-
};
|
|
221
|
+
return { error, address: null, port: null };
|
|
192
222
|
}
|
|
193
223
|
host = result[1];
|
|
194
224
|
port = result[2];
|
|
195
|
-
// If there's a URL extract the address
|
|
196
225
|
}
|
|
197
|
-
else
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
// Parse the address
|
|
201
|
-
result = constants6.RE_URL.exec(url);
|
|
226
|
+
else {
|
|
227
|
+
error = 'failed to parse address from URL';
|
|
228
|
+
result = constants6.RE_URL.exec(stripped);
|
|
202
229
|
if (result === null) {
|
|
203
|
-
return {
|
|
204
|
-
error: 'failed to parse address from URL',
|
|
205
|
-
address: null,
|
|
206
|
-
port: null,
|
|
207
|
-
};
|
|
230
|
+
return { error, address: null, port: null };
|
|
208
231
|
}
|
|
209
|
-
host = result[1];
|
|
210
|
-
// Otherwise just assign the URL to the host and let the library parse it
|
|
211
|
-
}
|
|
212
|
-
else {
|
|
213
|
-
host = url;
|
|
232
|
+
host = (_a = result[1]) !== null && _a !== void 0 ? _a : result[2];
|
|
214
233
|
}
|
|
215
234
|
// If there's a port convert it to an integer
|
|
216
235
|
if (port) {
|
|
217
236
|
port = parseInt(port, 10);
|
|
218
|
-
// squelch out of range ports
|
|
219
|
-
if (port < 0 || port >
|
|
237
|
+
// squelch out of range ports (valid ports are 0-65535)
|
|
238
|
+
if (port < 0 || port > 65535) {
|
|
220
239
|
port = null;
|
|
221
240
|
}
|
|
222
241
|
}
|
|
@@ -224,15 +243,103 @@ class Address6 {
|
|
|
224
243
|
// Standardize `undefined` to `null`
|
|
225
244
|
port = null;
|
|
226
245
|
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
246
|
+
// The URL character class is a superset of valid IPv6, so a host the
|
|
247
|
+
// regex accepted (an IPv4 literal, bare punctuation, too many groups)
|
|
248
|
+
// can still be rejected by the parser
|
|
249
|
+
let address;
|
|
250
|
+
try {
|
|
251
|
+
address = new Address6(host);
|
|
252
|
+
}
|
|
253
|
+
catch {
|
|
254
|
+
return { error, address: null, port: null };
|
|
255
|
+
}
|
|
256
|
+
return { address, port };
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Construct an `Address6` from an address and a hex subnet mask given as
|
|
260
|
+
* separate strings (e.g. as returned by Node's `os.networkInterfaces()`).
|
|
261
|
+
* Throws `AddressError` if the mask is non-contiguous (e.g.
|
|
262
|
+
* `ffff::ffff`).
|
|
263
|
+
* @example
|
|
264
|
+
* var address = Address6.fromAddressAndMask('fe80::1', 'ffff:ffff:ffff:ffff::');
|
|
265
|
+
* address.subnetMask; // 64
|
|
266
|
+
*/
|
|
267
|
+
static fromAddressAndMask(address, mask) {
|
|
268
|
+
const bits = common.prefixLengthFromMask(new Address6(mask).bigInt(), constants6.BITS);
|
|
269
|
+
return new Address6(`${address}/${bits}`);
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Construct an `Address6` from an address and a Cisco-style wildcard mask
|
|
273
|
+
* given as separate strings (e.g. `::ffff:ffff:ffff:ffff` for a `/64`).
|
|
274
|
+
* The wildcard mask is the bitwise inverse of the subnet mask. Throws
|
|
275
|
+
* `AddressError` if the mask is non-contiguous.
|
|
276
|
+
* @example
|
|
277
|
+
* var address = Address6.fromAddressAndWildcardMask('fe80::1', '::ffff:ffff:ffff:ffff');
|
|
278
|
+
* address.subnetMask; // 64
|
|
279
|
+
*/
|
|
280
|
+
static fromAddressAndWildcardMask(address, wildcardMask) {
|
|
281
|
+
const wildcard = new Address6(wildcardMask).bigInt();
|
|
282
|
+
const allOnes = (BigInt(1) << BigInt(constants6.BITS)) - BigInt(1);
|
|
283
|
+
const mask = wildcard ^ allOnes;
|
|
284
|
+
const bits = common.prefixLengthFromMask(mask, constants6.BITS);
|
|
285
|
+
return new Address6(`${address}/${bits}`);
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Construct an `Address6` from a wildcard pattern with trailing `*`
|
|
289
|
+
* groups. The number of trailing wildcards determines the prefix
|
|
290
|
+
* length: each `*` represents 16 bits. `::` is expanded to zero groups
|
|
291
|
+
* (not wildcards) before evaluating trailing wildcards.
|
|
292
|
+
*
|
|
293
|
+
* Only trailing whole-group wildcards are supported. Partial-group
|
|
294
|
+
* wildcards (e.g. `2001:db8::0*`) and interior wildcards (e.g.
|
|
295
|
+
* `*::1`) throw `AddressError`.
|
|
296
|
+
* @example
|
|
297
|
+
* Address6.fromWildcard('2001:db8:*:*:*:*:*:*').subnet; // '/32'
|
|
298
|
+
* Address6.fromWildcard('2001:db8::*').subnet; // '/112'
|
|
299
|
+
* Address6.fromWildcard('*:*:*:*:*:*:*:*').subnet; // '/0'
|
|
300
|
+
*/
|
|
301
|
+
static fromWildcard(input) {
|
|
302
|
+
if (input.includes('%') || input.includes('/')) {
|
|
303
|
+
throw new address_error_1.AddressError('Wildcard pattern must not include a zone or CIDR suffix');
|
|
304
|
+
}
|
|
305
|
+
const halves = input.split('::');
|
|
306
|
+
if (halves.length > 2) {
|
|
307
|
+
throw new address_error_1.AddressError("Wildcard pattern cannot contain more than one '::'");
|
|
308
|
+
}
|
|
309
|
+
let groups;
|
|
310
|
+
if (halves.length === 2) {
|
|
311
|
+
const left = halves[0] === '' ? [] : halves[0].split(':');
|
|
312
|
+
const right = halves[1] === '' ? [] : halves[1].split(':');
|
|
313
|
+
const remaining = constants6.GROUPS - left.length - right.length;
|
|
314
|
+
if (remaining < 1) {
|
|
315
|
+
throw new address_error_1.AddressError("Wildcard pattern with '::' has too many groups");
|
|
316
|
+
}
|
|
317
|
+
groups = [...left, ...new Array(remaining).fill('0'), ...right];
|
|
318
|
+
}
|
|
319
|
+
else {
|
|
320
|
+
groups = input.split(':');
|
|
321
|
+
}
|
|
322
|
+
if (groups.length !== constants6.GROUPS) {
|
|
323
|
+
throw new address_error_1.AddressError('Wildcard pattern must have 8 groups');
|
|
324
|
+
}
|
|
325
|
+
let firstWildcard = -1;
|
|
326
|
+
for (let i = 0; i < groups.length; i++) {
|
|
327
|
+
if (groups[i] === '*') {
|
|
328
|
+
if (firstWildcard === -1) {
|
|
329
|
+
firstWildcard = i;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
else if (firstWildcard !== -1) {
|
|
333
|
+
throw new address_error_1.AddressError('Wildcard `*` must only appear in trailing groups (e.g. `2001:db8:*:*:*:*:*:*`)');
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
const trailing = firstWildcard === -1 ? 0 : groups.length - firstWildcard;
|
|
337
|
+
const replaced = groups.map((g) => (g === '*' ? '0' : g));
|
|
338
|
+
const subnetBits = constants6.BITS - trailing * 16;
|
|
339
|
+
return new Address6(`${replaced.join(':')}/${subnetBits}`);
|
|
231
340
|
}
|
|
232
341
|
/**
|
|
233
342
|
* Create an IPv6-mapped address given an IPv4 address
|
|
234
|
-
* @memberof Address6
|
|
235
|
-
* @static
|
|
236
343
|
* @param {string} address - An IPv4 address string
|
|
237
344
|
* @returns {Address6}
|
|
238
345
|
* @example
|
|
@@ -246,35 +353,35 @@ class Address6 {
|
|
|
246
353
|
return new Address6(`::ffff:${address4.correctForm()}/${mask6}`);
|
|
247
354
|
}
|
|
248
355
|
/**
|
|
249
|
-
* Return an address from ip6.arpa form
|
|
250
|
-
*
|
|
251
|
-
*
|
|
356
|
+
* Return an address from ip6.arpa form. A full 32-nibble name gives a /128
|
|
357
|
+
* address; a shorter name, as used for a delegated reverse zone, gives the
|
|
358
|
+
* network it covers, with a subnet mask of four bits per nibble, so
|
|
359
|
+
* `fromArpa(x.reverseForm())` round-trips {@link reverseForm} for any prefix.
|
|
252
360
|
* @param {string} arpaFormAddress - an 'ip6.arpa' form address
|
|
253
361
|
* @returns {Adress6}
|
|
254
362
|
* @example
|
|
255
363
|
* var address = Address6.fromArpa(e.f.f.f.3.c.2.6.f.f.f.e.6.6.8.e.1.0.6.7.9.4.e.c.0.0.0.0.1.0.0.2.ip6.arpa.)
|
|
256
364
|
* address.correctForm(); // '2001:0:ce49:7601:e866:efff:62c3:fffe'
|
|
365
|
+
* Address6.fromArpa('8.b.d.0.1.0.0.2.ip6.arpa.').networkForm(); // '2001:db8::/32'
|
|
257
366
|
*/
|
|
258
367
|
static fromArpa(arpaFormAddress) {
|
|
259
|
-
// remove ending ".ip6.arpa
|
|
260
|
-
|
|
261
|
-
const
|
|
262
|
-
|
|
263
|
-
if (address.length !== 63) {
|
|
368
|
+
// remove an ending ".ip6.arpa", in any case and with or without the root
|
|
369
|
+
// dot
|
|
370
|
+
const nibbles = arpaFormAddress.replace(/(\.ip6\.arpa)?\.?$/i, '');
|
|
371
|
+
if (!/^[0-9a-f](\.[0-9a-f]){0,31}$/i.test(nibbles)) {
|
|
264
372
|
throw new address_error_1.AddressError("Invalid 'ip6.arpa' form.");
|
|
265
373
|
}
|
|
266
|
-
const
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
374
|
+
const reversed = nibbles.split('.').reverse();
|
|
375
|
+
const subnetMask = reversed.length * 4;
|
|
376
|
+
const hex = reversed.join('').padEnd(32, '0');
|
|
377
|
+
const groups = [];
|
|
378
|
+
for (let i = 0; i < constants6.GROUPS; i++) {
|
|
379
|
+
groups.push(hex.slice(i * 4, (i + 1) * 4));
|
|
270
380
|
}
|
|
271
|
-
|
|
272
|
-
return new Address6(address);
|
|
381
|
+
return new Address6(`${groups.join(':')}/${subnetMask}`);
|
|
273
382
|
}
|
|
274
383
|
/**
|
|
275
384
|
* Return the Microsoft UNC transcription of the address
|
|
276
|
-
* @memberof Address6
|
|
277
|
-
* @instance
|
|
278
385
|
* @returns {String} the Microsoft UNC transcription of the address
|
|
279
386
|
*/
|
|
280
387
|
microsoftTranscription() {
|
|
@@ -282,8 +389,6 @@ class Address6 {
|
|
|
282
389
|
}
|
|
283
390
|
/**
|
|
284
391
|
* Return the first n bits of the address, defaulting to the subnet mask
|
|
285
|
-
* @memberof Address6
|
|
286
|
-
* @instance
|
|
287
392
|
* @param {number} [mask=subnet] - the number of bits to mask
|
|
288
393
|
* @returns {String} the first n bits of the address as a string
|
|
289
394
|
*/
|
|
@@ -292,8 +397,6 @@ class Address6 {
|
|
|
292
397
|
}
|
|
293
398
|
/**
|
|
294
399
|
* Return the number of possible subnets of a given size in the address
|
|
295
|
-
* @memberof Address6
|
|
296
|
-
* @instance
|
|
297
400
|
* @param {number} [subnetSize=128] - the subnet size
|
|
298
401
|
* @returns {String}
|
|
299
402
|
*/
|
|
@@ -309,8 +412,6 @@ class Address6 {
|
|
|
309
412
|
}
|
|
310
413
|
/**
|
|
311
414
|
* Helper function getting start address.
|
|
312
|
-
* @memberof Address6
|
|
313
|
-
* @instance
|
|
314
415
|
* @returns {bigint}
|
|
315
416
|
*/
|
|
316
417
|
_startAddress() {
|
|
@@ -319,8 +420,6 @@ class Address6 {
|
|
|
319
420
|
/**
|
|
320
421
|
* The first address in the range given by this address' subnet
|
|
321
422
|
* Often referred to as the Network Address.
|
|
322
|
-
* @memberof Address6
|
|
323
|
-
* @instance
|
|
324
423
|
* @returns {Address6}
|
|
325
424
|
*/
|
|
326
425
|
startAddress() {
|
|
@@ -329,8 +428,6 @@ class Address6 {
|
|
|
329
428
|
/**
|
|
330
429
|
* The first host address in the range given by this address's subnet ie
|
|
331
430
|
* the first address after the Network Address
|
|
332
|
-
* @memberof Address6
|
|
333
|
-
* @instance
|
|
334
431
|
* @returns {Address6}
|
|
335
432
|
*/
|
|
336
433
|
startAddressExclusive() {
|
|
@@ -339,28 +436,26 @@ class Address6 {
|
|
|
339
436
|
}
|
|
340
437
|
/**
|
|
341
438
|
* Helper function getting end address.
|
|
342
|
-
* @memberof Address6
|
|
343
|
-
* @instance
|
|
344
439
|
* @returns {bigint}
|
|
345
440
|
*/
|
|
346
441
|
_endAddress() {
|
|
347
442
|
return BigInt(`0b${this.mask() + '1'.repeat(constants6.BITS - this.subnetMask)}`);
|
|
348
443
|
}
|
|
349
444
|
/**
|
|
350
|
-
* The last address in the range given by this address' subnet
|
|
351
|
-
*
|
|
352
|
-
*
|
|
353
|
-
*
|
|
445
|
+
* The last address in the range given by this address's subnet. IPv6 has
|
|
446
|
+
* no broadcast address, so this is an ordinary assignable address (in a
|
|
447
|
+
* 64-bit-interface-identifier subnet it falls inside the reserved
|
|
448
|
+
* subnet-anycast block of [RFC 2526](https://datatracker.ietf.org/doc/html/rfc2526)).
|
|
354
449
|
* @returns {Address6}
|
|
355
450
|
*/
|
|
356
451
|
endAddress() {
|
|
357
452
|
return Address6.fromBigInt(this._endAddress());
|
|
358
453
|
}
|
|
359
454
|
/**
|
|
360
|
-
* The
|
|
361
|
-
* the
|
|
362
|
-
*
|
|
363
|
-
*
|
|
455
|
+
* The address one before {@link endAddress}. This is the IPv6 counterpart
|
|
456
|
+
* of the IPv4 method that skips the broadcast address; IPv6 has no broadcast,
|
|
457
|
+
* so it drops exactly one address and does not model the 128 reserved
|
|
458
|
+
* subnet-anycast identifiers of [RFC 2526](https://datatracker.ietf.org/doc/html/rfc2526).
|
|
364
459
|
* @returns {Address6}
|
|
365
460
|
*/
|
|
366
461
|
endAddressExclusive() {
|
|
@@ -368,36 +463,100 @@ class Address6 {
|
|
|
368
463
|
return Address6.fromBigInt(this._endAddress() - adjust);
|
|
369
464
|
}
|
|
370
465
|
/**
|
|
371
|
-
*
|
|
372
|
-
*
|
|
373
|
-
*
|
|
466
|
+
* Returns the address `n` addresses after this one (or before, when `n` is
|
|
467
|
+
* negative), keeping this address's subnet mask. Throws `AddressError` when
|
|
468
|
+
* the result would fall outside the IPv6 address space or `n` is not an
|
|
469
|
+
* integer.
|
|
470
|
+
* @param {number | bigint} n
|
|
471
|
+
* @returns {Address6}
|
|
472
|
+
* @example
|
|
473
|
+
* new Address6('2001:db8::/64').offset(1).correctForm(); // '2001:db8::1'
|
|
474
|
+
*/
|
|
475
|
+
offset(n) {
|
|
476
|
+
return Address6.fromBigInt(common.offsetBigInt(this.bigInt(), n, constants6.BITS, 'IPv6')).withSubnetMask(this.subnetMask);
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* Returns the network that follows this address's network: the address after
|
|
480
|
+
* {@link endAddress}, with the same subnet mask. Throws `AddressError` when
|
|
481
|
+
* this network is the last one in the address space.
|
|
482
|
+
* @returns {Address6}
|
|
483
|
+
* @example
|
|
484
|
+
* new Address6('2001:db8::/64').nextNetwork().networkForm(); // '2001:db8:0:1::/64'
|
|
485
|
+
*/
|
|
486
|
+
nextNetwork() {
|
|
487
|
+
return Address6.fromBigInt(common.offsetBigInt(this._endAddress(), 1, constants6.BITS, 'IPv6')).withSubnetMask(this.subnetMask);
|
|
488
|
+
}
|
|
489
|
+
withSubnetMask(subnetMask) {
|
|
490
|
+
return new Address6(`${this.correctForm()}/${subnetMask}`);
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* The hex form of the subnet mask, e.g. `ffff:ffff:ffff:ffff::` for a
|
|
494
|
+
* `/64`. Returns an `Address6`; call `.correctForm()` for the string.
|
|
495
|
+
* @returns {Address6}
|
|
496
|
+
*/
|
|
497
|
+
subnetMaskAddress() {
|
|
498
|
+
return Address6.fromBigInt(BigInt(`0b${'1'.repeat(this.subnetMask)}${'0'.repeat(constants6.BITS - this.subnetMask)}`));
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* The Cisco-style wildcard mask, e.g. `::ffff:ffff:ffff:ffff` for a
|
|
502
|
+
* `/64`. This is the bitwise inverse of `subnetMaskAddress()`. Returns
|
|
503
|
+
* an `Address6`; call `.correctForm()` for the string.
|
|
504
|
+
* @returns {Address6}
|
|
505
|
+
*/
|
|
506
|
+
wildcardMask() {
|
|
507
|
+
return Address6.fromBigInt(BigInt(`0b${'0'.repeat(this.subnetMask)}${'1'.repeat(constants6.BITS - this.subnetMask)}`));
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
510
|
+
* The network address in CIDR string form, e.g. `2001:db8::/32` for
|
|
511
|
+
* `2001:db8::1/32`. For an address with no explicit subnet the prefix
|
|
512
|
+
* is `/128`, e.g. `networkForm()` on `2001:db8::1` returns
|
|
513
|
+
* `2001:db8::1/128`.
|
|
514
|
+
* @returns {string}
|
|
515
|
+
*/
|
|
516
|
+
networkForm() {
|
|
517
|
+
return `${this.startAddress().correctForm()}/${this.subnetMask}`;
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* Return the scope of the address. The 4-bit scope field
|
|
521
|
+
* ([RFC 4291 §2.7](https://datatracker.ietf.org/doc/html/rfc4291#section-2.7))
|
|
522
|
+
* is only defined for multicast addresses; for unicast addresses the scope
|
|
523
|
+
* is derived from the address type per
|
|
524
|
+
* [RFC 4007 §6](https://datatracker.ietf.org/doc/html/rfc4007#section-6).
|
|
374
525
|
* @returns {String}
|
|
375
526
|
*/
|
|
376
527
|
getScope() {
|
|
377
|
-
|
|
378
|
-
if (
|
|
379
|
-
scope =
|
|
528
|
+
const type = this.getType();
|
|
529
|
+
if (type === 'Multicast' || type.startsWith('Multicast ')) {
|
|
530
|
+
const scope = constants6.SCOPES[parseInt(this.getBits(12, 16).toString(10), 10)];
|
|
531
|
+
return scope || 'Unknown';
|
|
532
|
+
}
|
|
533
|
+
// RFC 4291 §2.5.3: the loopback address is treated as having Link-Local
|
|
534
|
+
// scope. (Multicast scope 1, "Interface-Local", is a different concept
|
|
535
|
+
// used only for loopback transmission of multicast.)
|
|
536
|
+
if (type === 'Link-local unicast' || type === 'Loopback') {
|
|
537
|
+
return 'Link local';
|
|
538
|
+
}
|
|
539
|
+
// RFC 4007 §6: the unspecified address has no scope.
|
|
540
|
+
if (type === 'Unspecified') {
|
|
541
|
+
return 'Unknown';
|
|
380
542
|
}
|
|
381
|
-
return
|
|
543
|
+
return 'Global';
|
|
382
544
|
}
|
|
383
545
|
/**
|
|
384
546
|
* Return the type of the address
|
|
385
|
-
* @memberof Address6
|
|
386
|
-
* @instance
|
|
387
547
|
* @returns {String}
|
|
388
548
|
*/
|
|
389
549
|
getType() {
|
|
390
|
-
for (
|
|
391
|
-
|
|
392
|
-
|
|
550
|
+
for (let i = 0; i < TYPE_SUBNETS.length; i++) {
|
|
551
|
+
const entry = TYPE_SUBNETS[i];
|
|
552
|
+
if (this.isHostInSubnet(entry[0])) {
|
|
553
|
+
return entry[1];
|
|
393
554
|
}
|
|
394
555
|
}
|
|
395
556
|
return 'Global unicast';
|
|
396
557
|
}
|
|
397
558
|
/**
|
|
398
559
|
* Return the bits in the given range as a BigInt
|
|
399
|
-
* @memberof Address6
|
|
400
|
-
* @instance
|
|
401
560
|
* @returns {bigint}
|
|
402
561
|
*/
|
|
403
562
|
getBits(start, end) {
|
|
@@ -405,8 +564,6 @@ class Address6 {
|
|
|
405
564
|
}
|
|
406
565
|
/**
|
|
407
566
|
* Return the bits in the given range as a base-2 string
|
|
408
|
-
* @memberof Address6
|
|
409
|
-
* @instance
|
|
410
567
|
* @returns {String}
|
|
411
568
|
*/
|
|
412
569
|
getBitsBase2(start, end) {
|
|
@@ -414,8 +571,6 @@ class Address6 {
|
|
|
414
571
|
}
|
|
415
572
|
/**
|
|
416
573
|
* Return the bits in the given range as a base-16 string
|
|
417
|
-
* @memberof Address6
|
|
418
|
-
* @instance
|
|
419
574
|
* @returns {String}
|
|
420
575
|
*/
|
|
421
576
|
getBitsBase16(start, end) {
|
|
@@ -429,8 +584,6 @@ class Address6 {
|
|
|
429
584
|
}
|
|
430
585
|
/**
|
|
431
586
|
* Return the bits that are set past the subnet mask length
|
|
432
|
-
* @memberof Address6
|
|
433
|
-
* @instance
|
|
434
587
|
* @returns {String}
|
|
435
588
|
*/
|
|
436
589
|
getBitsPastSubnet() {
|
|
@@ -438,10 +591,8 @@ class Address6 {
|
|
|
438
591
|
}
|
|
439
592
|
/**
|
|
440
593
|
* Return the reversed ip6.arpa form of the address
|
|
441
|
-
* @memberof Address6
|
|
442
594
|
* @param {Object} options
|
|
443
595
|
* @param {boolean} options.omitSuffix - omit the "ip6.arpa" suffix
|
|
444
|
-
* @instance
|
|
445
596
|
* @returns {String}
|
|
446
597
|
*/
|
|
447
598
|
reverseForm(options) {
|
|
@@ -467,10 +618,10 @@ class Address6 {
|
|
|
467
618
|
return 'ip6.arpa.';
|
|
468
619
|
}
|
|
469
620
|
/**
|
|
470
|
-
*
|
|
471
|
-
*
|
|
472
|
-
*
|
|
473
|
-
*
|
|
621
|
+
* Returns the address in correct form, per
|
|
622
|
+
* [RFC 5952](https://datatracker.ietf.org/doc/html/rfc5952): leading zeros
|
|
623
|
+
* stripped, the longest run of zero groups collapsed to `::`, and hex digits
|
|
624
|
+
* lowercased (e.g. `2001:db8::1`). This is the recommended form for display.
|
|
474
625
|
*/
|
|
475
626
|
correctForm() {
|
|
476
627
|
let i;
|
|
@@ -514,8 +665,6 @@ class Address6 {
|
|
|
514
665
|
}
|
|
515
666
|
/**
|
|
516
667
|
* Return a zero-padded base-2 string representation of the address
|
|
517
|
-
* @memberof Address6
|
|
518
|
-
* @instance
|
|
519
668
|
* @returns {String}
|
|
520
669
|
* @example
|
|
521
670
|
* var address = new Address6('2001:4860:4001:803::1011');
|
|
@@ -524,27 +673,58 @@ class Address6 {
|
|
|
524
673
|
* // 0000000000000000000000000000000000000000000000000001000000010001'
|
|
525
674
|
*/
|
|
526
675
|
binaryZeroPad() {
|
|
527
|
-
|
|
676
|
+
if (this._binaryZeroPad === undefined) {
|
|
677
|
+
this._binaryZeroPad = this.bigInt().toString(2).padStart(constants6.BITS, '0');
|
|
678
|
+
}
|
|
679
|
+
return this._binaryZeroPad;
|
|
528
680
|
}
|
|
681
|
+
/**
|
|
682
|
+
* Parses a v4-in-v6 string (e.g. `::ffff:192.168.0.1`) by extracting the
|
|
683
|
+
* trailing IPv4 address into `this.address4` / `this.parsedAddress4` and
|
|
684
|
+
* returning the address with the v4 portion converted to two v6 groups.
|
|
685
|
+
* Used internally by `parse()`.
|
|
686
|
+
*/
|
|
529
687
|
// TODO: Improve the semantics of this helper function
|
|
530
688
|
parse4in6(address) {
|
|
689
|
+
if (address.indexOf('.') === -1) {
|
|
690
|
+
return address;
|
|
691
|
+
}
|
|
531
692
|
const groups = address.split(':');
|
|
532
693
|
const lastGroup = groups.slice(-1)[0];
|
|
694
|
+
// RE_ADDRESS rejects octets with a leading zero, so a dotted-quad tail is
|
|
695
|
+
// matched permissively first: that way this notation still gets its own
|
|
696
|
+
// message with the offending octet highlighted, rather than falling
|
|
697
|
+
// through as an unrecognized group.
|
|
698
|
+
const v4Octets = lastGroup.split('.');
|
|
699
|
+
if (v4Octets.length === constants4.GROUPS &&
|
|
700
|
+
v4Octets.every((octet) => /^\d{1,3}$/.test(octet))) {
|
|
701
|
+
if (v4Octets.some((octet) => /^0\d/.test(octet))) {
|
|
702
|
+
// The prefix groups haven't been through the bad-character check
|
|
703
|
+
// yet, so escape them before including in the error HTML.
|
|
704
|
+
const highlighted = v4Octets.map(spanLeadingZeroes4).join('.');
|
|
705
|
+
const prefix = groups.slice(0, -1).map(helpers.escapeHtml).join(':');
|
|
706
|
+
const separator = groups.length > 1 ? ':' : '';
|
|
707
|
+
throw new address_error_1.AddressError("IPv4 addresses can't have leading zeroes.", `${prefix}${separator}${highlighted}`);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
533
710
|
const address4 = lastGroup.match(constants4.RE_ADDRESS);
|
|
534
711
|
if (address4) {
|
|
535
712
|
this.parsedAddress4 = address4[0];
|
|
536
|
-
this.
|
|
537
|
-
|
|
538
|
-
if (/^0[0-9]+/.test(this.address4.parsedAddress[i])) {
|
|
539
|
-
throw new address_error_1.AddressError("IPv4 addresses can't have leading zeroes.", address.replace(constants4.RE_ADDRESS, this.address4.parsedAddress.map(spanLeadingZeroes4).join('.')));
|
|
540
|
-
}
|
|
541
|
-
}
|
|
713
|
+
const v4Suffix = this.subnetMask >= 96 ? `/${this.subnetMask - 96}` : '';
|
|
714
|
+
this.address4 = new ipv4_1.Address4(`${this.parsedAddress4}${v4Suffix}`);
|
|
542
715
|
this.v4 = true;
|
|
543
716
|
groups[groups.length - 1] = this.address4.toGroup6();
|
|
544
717
|
address = groups.join(':');
|
|
545
718
|
}
|
|
546
719
|
return address;
|
|
547
720
|
}
|
|
721
|
+
/**
|
|
722
|
+
* Parses an IPv6 address string into its 8 hexadecimal groups (expanding
|
|
723
|
+
* any `::` elision and any trailing v4-in-v6 portion) and stores the result
|
|
724
|
+
* on `this.parsedAddress`. Called automatically by the constructor; you
|
|
725
|
+
* typically don't need to call it directly. Throws `AddressError` if the
|
|
726
|
+
* input is malformed.
|
|
727
|
+
*/
|
|
548
728
|
// TODO: Make private?
|
|
549
729
|
parse(address) {
|
|
550
730
|
address = this.parse4in6(address);
|
|
@@ -594,18 +774,16 @@ class Address6 {
|
|
|
594
774
|
return groups;
|
|
595
775
|
}
|
|
596
776
|
/**
|
|
597
|
-
*
|
|
598
|
-
*
|
|
599
|
-
*
|
|
600
|
-
*
|
|
777
|
+
* Returns the canonical (fully expanded) form of the address: all 8 groups,
|
|
778
|
+
* each padded to 4 hex digits, with no `::` collapsing
|
|
779
|
+
* (e.g. `2001:0db8:0000:0000:0000:0000:0000:0001`). Useful for sorting and
|
|
780
|
+
* byte-exact comparison.
|
|
601
781
|
*/
|
|
602
782
|
canonicalForm() {
|
|
603
783
|
return this.parsedAddress.map(paddedHex).join(':');
|
|
604
784
|
}
|
|
605
785
|
/**
|
|
606
786
|
* Return the decimal form of the address
|
|
607
|
-
* @memberof Address6
|
|
608
|
-
* @instance
|
|
609
787
|
* @returns {String}
|
|
610
788
|
*/
|
|
611
789
|
decimal() {
|
|
@@ -613,17 +791,17 @@ class Address6 {
|
|
|
613
791
|
}
|
|
614
792
|
/**
|
|
615
793
|
* Return the address as a BigInt
|
|
616
|
-
* @memberof Address6
|
|
617
|
-
* @instance
|
|
618
794
|
* @returns {bigint}
|
|
619
795
|
*/
|
|
620
796
|
bigInt() {
|
|
621
797
|
return BigInt(`0x${this.parsedAddress.map(paddedHex).join('')}`);
|
|
622
798
|
}
|
|
623
799
|
/**
|
|
624
|
-
* Return the last two groups of this address as an IPv4 address string
|
|
625
|
-
*
|
|
626
|
-
*
|
|
800
|
+
* Return the last two groups of this address as an IPv4 address string.
|
|
801
|
+
* If this address carries a CIDR prefix that covers the trailing 32 bits
|
|
802
|
+
* (i.e. `subnetMask >= 96`), the resulting `Address4` inherits the
|
|
803
|
+
* corresponding v4 prefix (`subnetMask - 96`); otherwise it defaults to
|
|
804
|
+
* `/32`.
|
|
627
805
|
* @returns {Address4}
|
|
628
806
|
* @example
|
|
629
807
|
* var address = new Address6('2001:4860:4001::1825:bf11');
|
|
@@ -631,12 +809,21 @@ class Address6 {
|
|
|
631
809
|
*/
|
|
632
810
|
to4() {
|
|
633
811
|
const binary = this.binaryZeroPad().split('');
|
|
634
|
-
|
|
812
|
+
const hex = BigInt(`0b${binary.slice(96, 128).join('')}`)
|
|
813
|
+
.toString(16)
|
|
814
|
+
.padStart(8, '0');
|
|
815
|
+
if (this.subnetMask >= 96) {
|
|
816
|
+
const v4Mask = this.subnetMask - 96;
|
|
817
|
+
const groups = [];
|
|
818
|
+
for (let i = 0; i < 8; i += 2) {
|
|
819
|
+
groups.push(parseInt(hex.slice(i, i + 2), 16));
|
|
820
|
+
}
|
|
821
|
+
return new ipv4_1.Address4(`${groups.join('.')}/${v4Mask}`);
|
|
822
|
+
}
|
|
823
|
+
return ipv4_1.Address4.fromHex(hex);
|
|
635
824
|
}
|
|
636
825
|
/**
|
|
637
826
|
* Return the v4-in-v6 form of the address
|
|
638
|
-
* @memberof Address6
|
|
639
|
-
* @instance
|
|
640
827
|
* @returns {String}
|
|
641
828
|
*/
|
|
642
829
|
to4in6() {
|
|
@@ -647,13 +834,13 @@ class Address6 {
|
|
|
647
834
|
if (!/:$/.test(correct)) {
|
|
648
835
|
infix = ':';
|
|
649
836
|
}
|
|
650
|
-
return correct + infix + address4.
|
|
837
|
+
return correct + infix + address4.correctForm();
|
|
651
838
|
}
|
|
652
839
|
/**
|
|
653
|
-
*
|
|
654
|
-
*
|
|
655
|
-
*
|
|
656
|
-
*
|
|
840
|
+
* Decodes the Teredo tunneling fields embedded in this address. Returns the
|
|
841
|
+
* Teredo prefix, server IPv4, client IPv4, raw flag bits, cone-NAT flag,
|
|
842
|
+
* UDP port, and Microsoft-format flag breakdown (reserved, universal/local,
|
|
843
|
+
* group/individual, nonce). Only meaningful for addresses in `2001::/32`.
|
|
657
844
|
*/
|
|
658
845
|
inspectTeredo() {
|
|
659
846
|
/*
|
|
@@ -679,12 +866,10 @@ class Address6 {
|
|
|
679
866
|
*/
|
|
680
867
|
const prefix = this.getBitsBase16(0, 32);
|
|
681
868
|
const bitsForUdpPort = this.getBits(80, 96);
|
|
682
|
-
// eslint-disable-next-line no-bitwise
|
|
683
869
|
const udpPort = (bitsForUdpPort ^ BigInt('0xffff')).toString();
|
|
684
870
|
const server4 = ipv4_1.Address4.fromHex(this.getBitsBase16(32, 64));
|
|
685
871
|
const bitsForClient4 = this.getBits(96, 128);
|
|
686
|
-
|
|
687
|
-
const client4 = ipv4_1.Address4.fromHex((bitsForClient4 ^ BigInt('0xffffffff')).toString(16));
|
|
872
|
+
const client4 = ipv4_1.Address4.fromHex((bitsForClient4 ^ BigInt('0xffffffff')).toString(16).padStart(8, '0'));
|
|
688
873
|
const flagsBase2 = this.getBitsBase2(64, 80);
|
|
689
874
|
const coneNat = (0, common_1.testBit)(flagsBase2, 15);
|
|
690
875
|
const reserved = (0, common_1.testBit)(flagsBase2, 14);
|
|
@@ -707,10 +892,9 @@ class Address6 {
|
|
|
707
892
|
};
|
|
708
893
|
}
|
|
709
894
|
/**
|
|
710
|
-
*
|
|
711
|
-
*
|
|
712
|
-
*
|
|
713
|
-
* @returns {Object}
|
|
895
|
+
* Decodes the 6to4 tunneling fields embedded in this address. Returns the
|
|
896
|
+
* 6to4 prefix and the embedded IPv4 gateway address. Only meaningful for
|
|
897
|
+
* addresses in `2002::/16`.
|
|
714
898
|
*/
|
|
715
899
|
inspect6to4() {
|
|
716
900
|
/*
|
|
@@ -726,8 +910,6 @@ class Address6 {
|
|
|
726
910
|
}
|
|
727
911
|
/**
|
|
728
912
|
* Return a v6 6to4 address from a v6 v4inv6 address
|
|
729
|
-
* @memberof Address6
|
|
730
|
-
* @instance
|
|
731
913
|
* @returns {Address6}
|
|
732
914
|
*/
|
|
733
915
|
to6to4() {
|
|
@@ -744,15 +926,88 @@ class Address6 {
|
|
|
744
926
|
return new Address6(addr6to4);
|
|
745
927
|
}
|
|
746
928
|
/**
|
|
747
|
-
*
|
|
748
|
-
*
|
|
749
|
-
*
|
|
929
|
+
* Embed an IPv4 address into a NAT64 IPv6 address using the encoding
|
|
930
|
+
* defined by [RFC 6052](https://datatracker.ietf.org/doc/html/rfc6052).
|
|
931
|
+
* The default prefix is the well-known prefix `64:ff9b::/96`. The prefix
|
|
932
|
+
* length must be one of 32, 40, 48, 56, 64, or 96; for prefixes shorter
|
|
933
|
+
* than /64 the IPv4 octets are split around the reserved bits 64–71.
|
|
934
|
+
* @example
|
|
935
|
+
* Address6.fromAddress4Nat64('192.0.2.33').correctForm(); // '64:ff9b::c000:221'
|
|
936
|
+
* Address6.fromAddress4Nat64('192.0.2.33', '2001:db8::/32').correctForm(); // '2001:db8:c000:221::'
|
|
937
|
+
*/
|
|
938
|
+
static fromAddress4Nat64(address, prefix = '64:ff9b::/96') {
|
|
939
|
+
const v4 = new ipv4_1.Address4(address);
|
|
940
|
+
const prefix6 = new Address6(prefix);
|
|
941
|
+
const pl = prefix6.subnetMask;
|
|
942
|
+
if (pl !== 32 && pl !== 40 && pl !== 48 && pl !== 56 && pl !== 64 && pl !== 96) {
|
|
943
|
+
throw new address_error_1.AddressError('NAT64 prefix length must be 32, 40, 48, 56, 64, or 96');
|
|
944
|
+
}
|
|
945
|
+
const prefixBits = prefix6.binaryZeroPad();
|
|
946
|
+
const v4Bits = v4.binaryZeroPad();
|
|
947
|
+
let bits;
|
|
948
|
+
if (pl === 96) {
|
|
949
|
+
bits = prefixBits.slice(0, 96) + v4Bits;
|
|
950
|
+
}
|
|
951
|
+
else {
|
|
952
|
+
const beforeU = 64 - pl;
|
|
953
|
+
bits = [
|
|
954
|
+
prefixBits.slice(0, pl),
|
|
955
|
+
v4Bits.slice(0, beforeU),
|
|
956
|
+
// Bits 64 to 71 are the reserved u octet and are always zero.
|
|
957
|
+
'00000000',
|
|
958
|
+
v4Bits.slice(beforeU),
|
|
959
|
+
'0'.repeat(128 - 72 - (32 - beforeU)),
|
|
960
|
+
].join('');
|
|
961
|
+
}
|
|
962
|
+
const hex = BigInt(`0b${bits}`).toString(16).padStart(32, '0');
|
|
963
|
+
const groups = [];
|
|
964
|
+
for (let i = 0; i < 8; i++) {
|
|
965
|
+
groups.push(hex.slice(i * 4, (i + 1) * 4));
|
|
966
|
+
}
|
|
967
|
+
return new Address6(groups.join(':'));
|
|
968
|
+
}
|
|
969
|
+
/**
|
|
970
|
+
* Extract the embedded IPv4 address from a NAT64 IPv6 address using the
|
|
971
|
+
* encoding defined by [RFC 6052](https://datatracker.ietf.org/doc/html/rfc6052).
|
|
972
|
+
* The default prefix is the well-known prefix `64:ff9b::/96`. Returns
|
|
973
|
+
* `null` if this address is not contained within the given prefix.
|
|
974
|
+
* @example
|
|
975
|
+
* new Address6('64:ff9b::c000:221').toAddress4Nat64()!.correctForm(); // '192.0.2.33'
|
|
976
|
+
*/
|
|
977
|
+
toAddress4Nat64(prefix = '64:ff9b::/96') {
|
|
978
|
+
const prefix6 = new Address6(prefix);
|
|
979
|
+
const pl = prefix6.subnetMask;
|
|
980
|
+
if (pl !== 32 && pl !== 40 && pl !== 48 && pl !== 56 && pl !== 64 && pl !== 96) {
|
|
981
|
+
throw new address_error_1.AddressError('NAT64 prefix length must be 32, 40, 48, 56, 64, or 96');
|
|
982
|
+
}
|
|
983
|
+
if (!this.isHostInSubnet(prefix6)) {
|
|
984
|
+
return null;
|
|
985
|
+
}
|
|
986
|
+
const bits = this.binaryZeroPad();
|
|
987
|
+
let v4Bits;
|
|
988
|
+
if (pl === 96) {
|
|
989
|
+
v4Bits = bits.slice(96, 128);
|
|
990
|
+
}
|
|
991
|
+
else {
|
|
992
|
+
const beforeU = 64 - pl;
|
|
993
|
+
v4Bits = bits.slice(pl, pl + beforeU) + bits.slice(72, 72 + (32 - beforeU));
|
|
994
|
+
}
|
|
995
|
+
const octets = [];
|
|
996
|
+
for (let i = 0; i < 4; i++) {
|
|
997
|
+
octets.push(parseInt(v4Bits.slice(i * 8, (i + 1) * 8), 2).toString());
|
|
998
|
+
}
|
|
999
|
+
return new ipv4_1.Address4(octets.join('.'));
|
|
1000
|
+
}
|
|
1001
|
+
/**
|
|
1002
|
+
* Return a byte array.
|
|
1003
|
+
*
|
|
1004
|
+
* To get a Node.js `Buffer`, wrap the result: `Buffer.from(address.toByteArray())`.
|
|
750
1005
|
* @returns {Array}
|
|
751
1006
|
*/
|
|
752
1007
|
toByteArray() {
|
|
753
|
-
const
|
|
754
|
-
|
|
755
|
-
|
|
1008
|
+
const value = this.bigInt()
|
|
1009
|
+
.toString(16)
|
|
1010
|
+
.padStart(constants6.BITS / 4, '0');
|
|
756
1011
|
const bytes = [];
|
|
757
1012
|
for (let i = 0, length = value.length; i < length; i += 2) {
|
|
758
1013
|
bytes.push(parseInt(value.substring(i, i + 2), 16));
|
|
@@ -760,30 +1015,45 @@ class Address6 {
|
|
|
760
1015
|
return bytes;
|
|
761
1016
|
}
|
|
762
1017
|
/**
|
|
763
|
-
* Return an unsigned byte array
|
|
764
|
-
*
|
|
765
|
-
*
|
|
1018
|
+
* Return an unsigned byte array.
|
|
1019
|
+
*
|
|
1020
|
+
* To get a Node.js `Buffer`, wrap the result: `Buffer.from(address.toUnsignedByteArray())`.
|
|
766
1021
|
* @returns {Array}
|
|
767
1022
|
*/
|
|
768
1023
|
toUnsignedByteArray() {
|
|
1024
|
+
// toByteArray() emits 0 to 255, so unsigning it is an identity mapping and
|
|
1025
|
+
// the two methods return equal arrays. 11.0.0 keeps one of them and makes
|
|
1026
|
+
// this a deprecated alias; test/common-test.ts fails at that version.
|
|
769
1027
|
return this.toByteArray().map(unsignByte);
|
|
770
1028
|
}
|
|
771
1029
|
/**
|
|
772
|
-
* Convert a byte array to an Address6 object
|
|
773
|
-
*
|
|
774
|
-
*
|
|
1030
|
+
* Convert a byte array to an Address6 object.
|
|
1031
|
+
*
|
|
1032
|
+
* Accepts unsigned bytes (0 to 255) or signed bytes (-128 to 127, as an
|
|
1033
|
+
* `Int8Array` or a Java `byte[]` holds them), folding signed values to their
|
|
1034
|
+
* unsigned equivalent. Throws `AddressError` unless given exactly 16
|
|
1035
|
+
* integers from -128 to 255.
|
|
1036
|
+
*
|
|
1037
|
+
* To convert from a Node.js `Buffer`, spread it: `Address6.fromByteArray([...buf])`.
|
|
775
1038
|
* @returns {Address6}
|
|
776
1039
|
*/
|
|
777
1040
|
static fromByteArray(bytes) {
|
|
1041
|
+
// Address4.fromByteArray takes unsigned bytes only. 11.0.0 aligns this
|
|
1042
|
+
// method with it, at which point the -128 floor here, unsignByte, and the
|
|
1043
|
+
// mapping below all go; test/common-test.ts fails at that version.
|
|
1044
|
+
common.assertByteArray(bytes, 16, 'IPv6', -128);
|
|
778
1045
|
return this.fromUnsignedByteArray(bytes.map(unsignByte));
|
|
779
1046
|
}
|
|
780
1047
|
/**
|
|
781
|
-
* Convert an unsigned byte array to an Address6 object
|
|
782
|
-
*
|
|
783
|
-
*
|
|
1048
|
+
* Convert an unsigned byte array to an Address6 object.
|
|
1049
|
+
*
|
|
1050
|
+
* Throws `AddressError` unless given exactly 16 integers from 0 to 255.
|
|
1051
|
+
*
|
|
1052
|
+
* To convert from a Node.js `Buffer`, spread it: `Address6.fromUnsignedByteArray([...buf])`.
|
|
784
1053
|
* @returns {Address6}
|
|
785
1054
|
*/
|
|
786
1055
|
static fromUnsignedByteArray(bytes) {
|
|
1056
|
+
common.assertByteArray(bytes, 16, 'IPv6', 0);
|
|
787
1057
|
const BYTE_MAX = BigInt('256');
|
|
788
1058
|
let result = BigInt('0');
|
|
789
1059
|
let multiplier = BigInt('1');
|
|
@@ -795,76 +1065,234 @@ class Address6 {
|
|
|
795
1065
|
}
|
|
796
1066
|
/**
|
|
797
1067
|
* Returns true if the address is in the canonical form, false otherwise
|
|
798
|
-
* @memberof Address6
|
|
799
|
-
* @instance
|
|
800
1068
|
* @returns {boolean}
|
|
801
1069
|
*/
|
|
802
1070
|
isCanonical() {
|
|
803
1071
|
return this.addressMinusSuffix === this.canonicalForm();
|
|
804
1072
|
}
|
|
805
1073
|
/**
|
|
806
|
-
* Returns true if the address is a link
|
|
807
|
-
*
|
|
808
|
-
*
|
|
1074
|
+
* Returns true if the address is a link-local unicast address in `fe80::/10`
|
|
1075
|
+
* ([RFC 4291 §2.4](https://datatracker.ietf.org/doc/html/rfc4291#section-2.4))
|
|
1076
|
+
* or an IPv4-mapped / NAT64 address whose embedded IPv4 address is link-local
|
|
1077
|
+
* (`169.254.0.0/16`, e.g. `::ffff:169.254.169.254`), false otherwise.
|
|
809
1078
|
* @returns {boolean}
|
|
810
1079
|
*/
|
|
811
1080
|
isLinkLocal() {
|
|
812
|
-
|
|
813
|
-
if (
|
|
814
|
-
|
|
815
|
-
return true;
|
|
1081
|
+
const embedded = this.embeddedIPv4();
|
|
1082
|
+
if (embedded) {
|
|
1083
|
+
return embedded.isLinkLocal();
|
|
816
1084
|
}
|
|
817
|
-
return
|
|
1085
|
+
return this.isHostInSubnet(LINK_LOCAL_SUBNET);
|
|
818
1086
|
}
|
|
819
1087
|
/**
|
|
820
1088
|
* Returns true if the address is a multicast address, false otherwise
|
|
821
|
-
* @memberof Address6
|
|
822
|
-
* @instance
|
|
823
1089
|
* @returns {boolean}
|
|
824
1090
|
*/
|
|
825
1091
|
isMulticast() {
|
|
826
|
-
|
|
1092
|
+
const embedded = this.embeddedIPv4();
|
|
1093
|
+
if (embedded) {
|
|
1094
|
+
return embedded.isMulticast();
|
|
1095
|
+
}
|
|
1096
|
+
const type = this.getType();
|
|
1097
|
+
return type === 'Multicast' || type.startsWith('Multicast ');
|
|
827
1098
|
}
|
|
828
1099
|
/**
|
|
829
|
-
* Returns true if the address
|
|
830
|
-
*
|
|
831
|
-
*
|
|
1100
|
+
* Returns true if the address was written in v4-in-v6 dotted-quad notation
|
|
1101
|
+
* (e.g. `::ffff:127.0.0.1`), false otherwise. This is a notation-level flag
|
|
1102
|
+
* and does not reflect whether the address bits lie in the IPv4-mapped
|
|
1103
|
+
* (`::ffff:0:0/96`) subnet — for that, see {@link isMapped4}.
|
|
832
1104
|
* @returns {boolean}
|
|
833
1105
|
*/
|
|
834
1106
|
is4() {
|
|
835
1107
|
return this.v4;
|
|
836
1108
|
}
|
|
1109
|
+
/**
|
|
1110
|
+
* Returns true if the address is an IPv4-mapped IPv6 address in
|
|
1111
|
+
* `::ffff:0:0/96` ([RFC 4291 §2.5.5.2](https://datatracker.ietf.org/doc/html/rfc4291#section-2.5.5.2)),
|
|
1112
|
+
* false otherwise. Unlike {@link is4}, this checks the underlying address
|
|
1113
|
+
* bits rather than the textual notation, so `::ffff:127.0.0.1` and
|
|
1114
|
+
* `::ffff:7f00:1` both return true.
|
|
1115
|
+
* @returns {boolean}
|
|
1116
|
+
*/
|
|
1117
|
+
isMapped4() {
|
|
1118
|
+
return this.isHostInSubnet(IPV4_MAPPED_SUBNET);
|
|
1119
|
+
}
|
|
1120
|
+
/**
|
|
1121
|
+
* If this address embeds a routable IPv4 address — i.e. it is IPv4-mapped
|
|
1122
|
+
* (`::ffff:0:0/96`) or sits in the NAT64 well-known prefix (`64:ff9b::/96`,
|
|
1123
|
+
* [RFC 6052](https://datatracker.ietf.org/doc/html/rfc6052)) — return that
|
|
1124
|
+
* embedded address as an {@link Address4}; otherwise return null.
|
|
1125
|
+
*
|
|
1126
|
+
* The special-property checks (`isLoopback`, `isLinkLocal`, `isMulticast`,
|
|
1127
|
+
* `isUnspecified`, `isPrivate`, `isCGNAT`, `isBroadcast`) call this first and
|
|
1128
|
+
* delegate to the embedded {@link Address4} when present, so a literal such as
|
|
1129
|
+
* `::ffff:127.0.0.1` is classified by what it actually reaches (loopback)
|
|
1130
|
+
* rather than by its IPv6 wrapper (which `getType()` reports as IPv4-mapped).
|
|
1131
|
+
* This matters wherever the checks back a trust-boundary decision (e.g. an
|
|
1132
|
+
* SSRF allow/deny filter): without normalization, `::ffff:10.0.0.1`,
|
|
1133
|
+
* `::ffff:169.254.169.254`, `64:ff9b::7f00:1`, etc. would all read as
|
|
1134
|
+
* non-internal.
|
|
1135
|
+
* @returns {Address4 | null}
|
|
1136
|
+
*/
|
|
1137
|
+
embeddedIPv4() {
|
|
1138
|
+
if (this.isMapped4() || this.isHostInSubnet(NAT64_WELL_KNOWN_SUBNET)) {
|
|
1139
|
+
return this.to4();
|
|
1140
|
+
}
|
|
1141
|
+
return null;
|
|
1142
|
+
}
|
|
837
1143
|
/**
|
|
838
1144
|
* Returns true if the address is a Teredo address, false otherwise
|
|
839
|
-
* @memberof Address6
|
|
840
|
-
* @instance
|
|
841
1145
|
* @returns {boolean}
|
|
842
1146
|
*/
|
|
843
1147
|
isTeredo() {
|
|
844
|
-
return this.
|
|
1148
|
+
return this.isHostInSubnet(TEREDO_SUBNET);
|
|
845
1149
|
}
|
|
846
1150
|
/**
|
|
847
1151
|
* Returns true if the address is a 6to4 address, false otherwise
|
|
848
|
-
* @memberof Address6
|
|
849
|
-
* @instance
|
|
850
1152
|
* @returns {boolean}
|
|
851
1153
|
*/
|
|
852
1154
|
is6to4() {
|
|
853
|
-
return this.
|
|
1155
|
+
return this.isHostInSubnet(SIX_TO_FOUR_SUBNET);
|
|
854
1156
|
}
|
|
855
1157
|
/**
|
|
856
1158
|
* Returns true if the address is a loopback address, false otherwise
|
|
857
|
-
* @memberof Address6
|
|
858
|
-
* @instance
|
|
859
1159
|
* @returns {boolean}
|
|
860
1160
|
*/
|
|
861
1161
|
isLoopback() {
|
|
1162
|
+
const embedded = this.embeddedIPv4();
|
|
1163
|
+
if (embedded) {
|
|
1164
|
+
return embedded.isLoopback();
|
|
1165
|
+
}
|
|
862
1166
|
return this.getType() === 'Loopback';
|
|
863
1167
|
}
|
|
1168
|
+
/**
|
|
1169
|
+
* Returns true if the address is a Unique Local Address in `fc00::/7` ([RFC 4193](https://datatracker.ietf.org/doc/html/rfc4193)). ULAs are the IPv6 equivalent of IPv4 [RFC 1918](https://datatracker.ietf.org/doc/html/rfc1918) private addresses.
|
|
1170
|
+
* @returns {boolean}
|
|
1171
|
+
*/
|
|
1172
|
+
isULA() {
|
|
1173
|
+
return this.isHostInSubnet(ULA_SUBNET);
|
|
1174
|
+
}
|
|
1175
|
+
/**
|
|
1176
|
+
* Returns true if the address is private, i.e. a Unique Local Address in
|
|
1177
|
+
* `fc00::/7` ([RFC 4193](https://datatracker.ietf.org/doc/html/rfc4193)), an
|
|
1178
|
+
* address in the NAT64 local-use range `64:ff9b:1::/48`
|
|
1179
|
+
* ([RFC 8215](https://datatracker.ietf.org/doc/html/rfc8215)), or an
|
|
1180
|
+
* IPv4-mapped / NAT64 well-known address whose embedded IPv4 address is in
|
|
1181
|
+
* one of the [RFC 1918](https://datatracker.ietf.org/doc/html/rfc1918)
|
|
1182
|
+
* private ranges (e.g. `::ffff:10.0.0.1`). This is the IPv6 counterpart to
|
|
1183
|
+
* {@link Address4.isPrivate}; use it instead of {@link isULA} when you need to
|
|
1184
|
+
* catch mapped RFC 1918 addresses as well as native ULAs.
|
|
1185
|
+
*
|
|
1186
|
+
* The local-use NAT64 range is reported private as a whole rather than by
|
|
1187
|
+
* its embedded IPv4 address: an operator may carve a prefix of any RFC 6052
|
|
1188
|
+
* length out of `64:ff9b:1::/48`, so the same bits decode to different IPv4
|
|
1189
|
+
* addresses under different deployments and no single decoding is correct.
|
|
1190
|
+
* Use {@link toAddress4Nat64} with the deployment's prefix to decode one.
|
|
1191
|
+
* @returns {boolean}
|
|
1192
|
+
*/
|
|
1193
|
+
isPrivate() {
|
|
1194
|
+
const embedded = this.embeddedIPv4();
|
|
1195
|
+
if (embedded) {
|
|
1196
|
+
return embedded.isPrivate();
|
|
1197
|
+
}
|
|
1198
|
+
return this.isULA() || this.isHostInSubnet(NAT64_LOCAL_USE_SUBNET);
|
|
1199
|
+
}
|
|
1200
|
+
/**
|
|
1201
|
+
* Returns true if the address is an IPv4-mapped / NAT64 address whose embedded
|
|
1202
|
+
* IPv4 address is in the carrier-grade NAT range `100.64.0.0/10`
|
|
1203
|
+
* ([RFC 6598](https://datatracker.ietf.org/doc/html/rfc6598)), false
|
|
1204
|
+
* otherwise. There is no native IPv6 CGNAT range, so this only ever returns
|
|
1205
|
+
* true for an embedded IPv4 address (e.g. `::ffff:100.64.0.1`).
|
|
1206
|
+
* @returns {boolean}
|
|
1207
|
+
*/
|
|
1208
|
+
isCGNAT() {
|
|
1209
|
+
const embedded = this.embeddedIPv4();
|
|
1210
|
+
if (embedded) {
|
|
1211
|
+
return embedded.isCGNAT();
|
|
1212
|
+
}
|
|
1213
|
+
return false;
|
|
1214
|
+
}
|
|
1215
|
+
/**
|
|
1216
|
+
* Returns true if the address is an IPv4-mapped / NAT64 address whose embedded
|
|
1217
|
+
* IPv4 address is the limited broadcast address `255.255.255.255`
|
|
1218
|
+
* ([RFC 919](https://datatracker.ietf.org/doc/html/rfc919)), false otherwise.
|
|
1219
|
+
* There is no IPv6 broadcast, so this only ever returns true for an embedded
|
|
1220
|
+
* IPv4 address (e.g. `::ffff:255.255.255.255`).
|
|
1221
|
+
* @returns {boolean}
|
|
1222
|
+
*/
|
|
1223
|
+
isBroadcast() {
|
|
1224
|
+
const embedded = this.embeddedIPv4();
|
|
1225
|
+
if (embedded) {
|
|
1226
|
+
return embedded.isBroadcast();
|
|
1227
|
+
}
|
|
1228
|
+
return false;
|
|
1229
|
+
}
|
|
1230
|
+
/**
|
|
1231
|
+
* Returns true if the address is the unspecified address `::`.
|
|
1232
|
+
* @returns {boolean}
|
|
1233
|
+
*/
|
|
1234
|
+
isUnspecified() {
|
|
1235
|
+
const embedded = this.embeddedIPv4();
|
|
1236
|
+
if (embedded) {
|
|
1237
|
+
return embedded.isUnspecified();
|
|
1238
|
+
}
|
|
1239
|
+
return this.getType() === 'Unspecified';
|
|
1240
|
+
}
|
|
1241
|
+
/**
|
|
1242
|
+
* Returns true if the address is in the documentation prefix `2001:db8::/32` ([RFC 3849](https://datatracker.ietf.org/doc/html/rfc3849)).
|
|
1243
|
+
* @returns {boolean}
|
|
1244
|
+
*/
|
|
1245
|
+
isDocumentation() {
|
|
1246
|
+
return DOCUMENTATION_SUBNETS.some((subnet) => this.isHostInSubnet(subnet));
|
|
1247
|
+
}
|
|
1248
|
+
/**
|
|
1249
|
+
* Returns true if the address is in the benchmarking range `2001:2::/48`
|
|
1250
|
+
* ([RFC 5180](https://datatracker.ietf.org/doc/html/rfc5180)) or is an
|
|
1251
|
+
* IPv4-mapped / NAT64 address whose embedded IPv4 address is in
|
|
1252
|
+
* `198.18.0.0/15`, false otherwise.
|
|
1253
|
+
* @returns {boolean}
|
|
1254
|
+
*/
|
|
1255
|
+
isBenchmarking() {
|
|
1256
|
+
const embedded = this.embeddedIPv4();
|
|
1257
|
+
if (embedded) {
|
|
1258
|
+
return embedded.isBenchmarking();
|
|
1259
|
+
}
|
|
1260
|
+
return this.isHostInSubnet(BENCHMARKING_SUBNET);
|
|
1261
|
+
}
|
|
1262
|
+
/**
|
|
1263
|
+
* Returns true if the address is globally reachable: inside the global
|
|
1264
|
+
* unicast allocation `2000::/3` (the only range the [IANA IPv6 Address Space
|
|
1265
|
+
* Registry](https://www.iana.org/assignments/ipv6-address-space/) assigns
|
|
1266
|
+
* for global unicast; everything else is reserved, ULA, link-local, or
|
|
1267
|
+
* multicast) and not in any block the [IANA IPv6 Special-Purpose Address Registry](https://www.iana.org/assignments/iana-ipv6-special-registry/)
|
|
1268
|
+
* marks as not globally reachable. An IPv4-mapped or NAT64 well-known
|
|
1269
|
+
* address answers for its embedded IPv4 address, so `::ffff:10.0.0.1` and
|
|
1270
|
+
* `64:ff9b::7f00:1` are not global. Teredo (`2001::/32`) and 6to4
|
|
1271
|
+
* (`2002::/16`) are not global either: the registry lists them as N/A and a
|
|
1272
|
+
* packet to one needs a relay.
|
|
1273
|
+
*
|
|
1274
|
+
* This covers everything the individual classifiers name and the blocks they
|
|
1275
|
+
* do not: the discard-only prefix `100::/64`, the IETF protocol assignments
|
|
1276
|
+
* in `2001::/23`, the deprecated site-local `fec0::/10` and IPv4-compatible
|
|
1277
|
+
* `::/96` ranges, and unallocated space such as `4000::/3`. It is the single
|
|
1278
|
+
* predicate to use where a request must not reach an internal or
|
|
1279
|
+
* special-purpose destination; see SECURITY.md.
|
|
1280
|
+
* @returns {boolean}
|
|
1281
|
+
*/
|
|
1282
|
+
isGlobal() {
|
|
1283
|
+
const embedded = this.embeddedIPv4();
|
|
1284
|
+
if (embedded) {
|
|
1285
|
+
return embedded.isGlobal();
|
|
1286
|
+
}
|
|
1287
|
+
return (this.isHostInSubnet(GLOBAL_UNICAST_SUBNET) &&
|
|
1288
|
+
common.isGloballyReachable.call(this, SPECIAL_PURPOSE_V6));
|
|
1289
|
+
}
|
|
864
1290
|
// #endregion
|
|
865
1291
|
// #region HTML
|
|
866
1292
|
/**
|
|
867
|
-
*
|
|
1293
|
+
* Returns the address as an HTTP URL with the host bracketed, e.g.
|
|
1294
|
+
* `http://[2001:db8::1]/`. If `optionalPort` is provided it is appended,
|
|
1295
|
+
* e.g. `http://[2001:db8::1]:8080/`.
|
|
868
1296
|
*/
|
|
869
1297
|
href(optionalPort) {
|
|
870
1298
|
if (optionalPort === undefined) {
|
|
@@ -876,7 +1304,12 @@ class Address6 {
|
|
|
876
1304
|
return `http://[${this.correctForm()}]${optionalPort}/`;
|
|
877
1305
|
}
|
|
878
1306
|
/**
|
|
879
|
-
*
|
|
1307
|
+
* Returns an HTML `<a>` element whose `href` encodes the address in a URL
|
|
1308
|
+
* hash fragment (default prefix `/#address=`). Useful for linking between
|
|
1309
|
+
* pages of an address-inspector UI.
|
|
1310
|
+
* @param options.className - CSS class for the rendered `<a>` element
|
|
1311
|
+
* @param options.prefix - hash prefix prepended to the address (default `/#address=`)
|
|
1312
|
+
* @param options.v4 - when true, render the address in v4-in-v6 form
|
|
880
1313
|
*/
|
|
881
1314
|
link(options) {
|
|
882
1315
|
if (!options) {
|
|
@@ -896,25 +1329,33 @@ class Address6 {
|
|
|
896
1329
|
formFunction = this.to4in6;
|
|
897
1330
|
}
|
|
898
1331
|
const form = formFunction.call(this);
|
|
1332
|
+
const safeHref = helpers.escapeHtml(`${options.prefix}${form}`);
|
|
1333
|
+
const safeForm = helpers.escapeHtml(form);
|
|
899
1334
|
if (options.className) {
|
|
900
|
-
|
|
1335
|
+
const safeClass = helpers.escapeHtml(options.className);
|
|
1336
|
+
return `<a href="${safeHref}" class="${safeClass}">${safeForm}</a>`;
|
|
901
1337
|
}
|
|
902
|
-
return `<a href="${
|
|
1338
|
+
return `<a href="${safeHref}">${safeForm}</a>`;
|
|
903
1339
|
}
|
|
904
1340
|
/**
|
|
905
|
-
* Groups an address
|
|
1341
|
+
* Groups an address.
|
|
1342
|
+
*
|
|
1343
|
+
* Returns an HTML fragment: each group is wrapped in a `<span>` carrying
|
|
1344
|
+
* the group classes an address-inspector UI hovers on. The address content
|
|
1345
|
+
* is HTML-escaped; anything you concatenate around it is your
|
|
1346
|
+
* responsibility.
|
|
906
1347
|
* @returns {String}
|
|
907
1348
|
*/
|
|
908
1349
|
group() {
|
|
909
1350
|
if (this.elidedGroups === 0) {
|
|
910
1351
|
// The simple case
|
|
911
|
-
return helpers.simpleGroup(this.
|
|
1352
|
+
return helpers.simpleGroup(this.addressMinusSuffix).join(':');
|
|
912
1353
|
}
|
|
913
1354
|
assert(typeof this.elidedGroups === 'number');
|
|
914
1355
|
assert(typeof this.elisionBegin === 'number');
|
|
915
1356
|
// The elided case
|
|
916
1357
|
const output = [];
|
|
917
|
-
const [left, right] = this.
|
|
1358
|
+
const [left, right] = this.addressMinusSuffix.split('::');
|
|
918
1359
|
if (left.length) {
|
|
919
1360
|
output.push(...helpers.simpleGroup(left));
|
|
920
1361
|
}
|
|
@@ -944,8 +1385,6 @@ class Address6 {
|
|
|
944
1385
|
/**
|
|
945
1386
|
* Generate a regular expression string that can be used to find or validate
|
|
946
1387
|
* all variations of this address
|
|
947
|
-
* @memberof Address6
|
|
948
|
-
* @instance
|
|
949
1388
|
* @param {boolean} substringSearch
|
|
950
1389
|
* @returns {string}
|
|
951
1390
|
*/
|
|
@@ -990,8 +1429,6 @@ class Address6 {
|
|
|
990
1429
|
/**
|
|
991
1430
|
* Generate a regular expression that can be used to find or validate all
|
|
992
1431
|
* variations of this address.
|
|
993
|
-
* @memberof Address6
|
|
994
|
-
* @instance
|
|
995
1432
|
* @param {boolean} substringSearch
|
|
996
1433
|
* @returns {RegExp}
|
|
997
1434
|
*/
|
|
@@ -1000,4 +1437,22 @@ class Address6 {
|
|
|
1000
1437
|
}
|
|
1001
1438
|
}
|
|
1002
1439
|
exports.Address6 = Address6;
|
|
1440
|
+
const TYPE_SUBNETS = Object.keys(constants6.TYPES).map((subnet) => [
|
|
1441
|
+
new Address6(subnet),
|
|
1442
|
+
constants6.TYPES[subnet],
|
|
1443
|
+
]);
|
|
1444
|
+
const TEREDO_SUBNET = new Address6('2001::/32');
|
|
1445
|
+
const SIX_TO_FOUR_SUBNET = new Address6('2002::/16');
|
|
1446
|
+
const ULA_SUBNET = new Address6('fc00::/7');
|
|
1447
|
+
const LINK_LOCAL_SUBNET = new Address6('fe80::/10');
|
|
1448
|
+
const DOCUMENTATION_SUBNETS = [new Address6('2001:db8::/32'), new Address6('3fff::/20')];
|
|
1449
|
+
const BENCHMARKING_SUBNET = new Address6('2001:2::/48');
|
|
1450
|
+
const GLOBAL_UNICAST_SUBNET = new Address6('2000::/3');
|
|
1451
|
+
const SPECIAL_PURPOSE_V6 = constants6.SPECIAL_PURPOSE.map(([cidr, , reachable]) => ({
|
|
1452
|
+
subnet: new Address6(cidr),
|
|
1453
|
+
reachable,
|
|
1454
|
+
}));
|
|
1455
|
+
const IPV4_MAPPED_SUBNET = new Address6('::ffff:0:0/96');
|
|
1456
|
+
const NAT64_WELL_KNOWN_SUBNET = new Address6('64:ff9b::/96');
|
|
1457
|
+
const NAT64_LOCAL_USE_SUBNET = new Address6('64:ff9b:1::/48');
|
|
1003
1458
|
//# sourceMappingURL=ipv6.js.map
|