diff --git a/deps/npm/node_modules/ip-address/dist/common.js b/deps/npm/node_modules/ip-address/dist/common.js index 6b76e051b44..0c15d21e3a3 100644 --- a/deps/npm/node_modules/ip-address/dist/common.js +++ b/deps/npm/node_modules/ip-address/dist/common.js @@ -1,23 +1,47 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.isInSubnet = isInSubnet; +exports.isHostInSubnet = isHostInSubnet; exports.isCorrect = isCorrect; exports.prefixLengthFromMask = prefixLengthFromMask; +exports.assertByteArray = assertByteArray; exports.numberToPaddedHex = numberToPaddedHex; exports.stringToPaddedHex = stringToPaddedHex; exports.testBit = testBit; const address_error_1 = require("./address-error"); +/** + * Returns whether this address's *network* is contained within `address`, + * i.e. whether every address this one can represent also falls inside + * `address`. A network wider than `address` is not contained in it, so + * `10.0.0.0/8` is not in `10.0.0.0/16`. + * + * To ask whether the address itself falls inside a range, ignoring any CIDR + * suffix it was written with, use {@link isHostInSubnet} instead. That is the + * question the special-use classifiers ask. + */ function isInSubnet(address) { if (this.subnetMask < address.subnetMask) { return false; } - if (this.mask(address.subnetMask) === address.mask()) { - return true; - } - return false; + return isHostInSubnet.call(this, address); +} +/** + * Returns whether this address's host bits fall inside `address`, ignoring + * this address's own subnet mask. + * + * This is the primitive the special-use classifiers (`isLoopback`, + * `isPrivate`, `isLinkLocal`, `getType`, …) are built on: they answer a + * question about the address, so the answer must not change with the CIDR + * suffix the caller happened to write. Use this rather than + * {@link isInSubnet} when classifying a single address — notably when the + * address came from untrusted input and the result backs a trust-boundary + * decision such as an SSRF allow/deny filter. + */ +function isHostInSubnet(address) { + return this.mask(address.subnetMask) === address.mask(); } function isCorrect(defaultBits) { - return function () { + return function isCorrectForm() { if (this.addressMinusSuffix !== this.correctForm()) { return false; } @@ -46,6 +70,21 @@ function prefixLengthFromMask(value, totalBits) { } return firstZero; } +/** + * Throws `AddressError` unless `bytes` holds exactly `byteCount` integers, + * each from `minimum` to 255. Pass a `minimum` of `-128` where signed bytes + * are accepted and folded to unsigned, and `0` where they are not. + */ +function assertByteArray(bytes, byteCount, family, minimum) { + if (bytes.length !== byteCount) { + throw new address_error_1.AddressError(`${family} addresses require exactly ${byteCount} bytes`); + } + for (let i = 0; i < bytes.length; i++) { + if (!Number.isInteger(bytes[i]) || bytes[i] < minimum || bytes[i] > 255) { + throw new address_error_1.AddressError(`All bytes must be integers between ${minimum} and 255`); + } + } +} function numberToPaddedHex(number) { return number.toString(16).padStart(2, '0'); } diff --git a/deps/npm/node_modules/ip-address/dist/ipv4.js b/deps/npm/node_modules/ip-address/dist/ipv4.js index 2c0fd182086..1360e1836a0 100644 --- a/deps/npm/node_modules/ip-address/dist/ipv4.js +++ b/deps/npm/node_modules/ip-address/dist/ipv4.js @@ -35,6 +35,7 @@ const isCorrect4 = common.isCorrect(constants.BITS); */ class Address4 { constructor(address) { + this.addressMinusSuffix = ''; this.groups = constants.GROUPS; this.parsedAddress = []; this.parsedSubnet = ''; @@ -51,6 +52,15 @@ class Address4 { * @returns {boolean} */ this.isInSubnet = common.isInSubnet; + /** + * Returns true if this address's host bits fall inside the given subnet, + * ignoring this address's own subnet mask. Prefer this over `isInSubnet` + * when classifying a single address, so the answer doesn't change with the + * CIDR suffix the caller happened to write — notably when the address came + * from untrusted input and the result backs a trust-boundary decision. + * @returns {boolean} + */ + this.isHostInSubnet = common.isHostInSubnet; this.address = address; const subnet = constants.RE_SUBNET_STRING.exec(address); if (subnet) { @@ -78,7 +88,7 @@ class Address4 { new Address4(address); return true; } - catch (e) { + catch { return false; } } @@ -90,6 +100,11 @@ class Address4 { */ parse(address) { const groups = address.split('.'); + // Checked before the general match so the error names the actual problem. + // Address6 rejects the same notation on its v4-in-v6 path. + if (groups.some((group) => /^0\d/.test(group))) { + throw new address_error_1.AddressError("IPv4 addresses can't have leading zeroes."); + } if (!address.match(constants.RE_ADDRESS)) { throw new address_error_1.AddressError('Invalid IPv4 address.'); } @@ -128,7 +143,6 @@ class Address4 { static fromAddressAndWildcardMask(address, wildcardMask) { const wildcard = new Address4(wildcardMask).bigInt(); const allOnes = (BigInt(1) << BigInt(constants.BITS)) - BigInt(1); - // eslint-disable-next-line no-bitwise const mask = wildcard ^ allOnes; const bits = common.prefixLengthFromMask(mask, constants.BITS); return new Address4(`${address}/${bits}`); @@ -328,32 +342,32 @@ class Address4 { * @returns {Address4} */ static fromBigInt(bigInt) { - if (bigInt < 0n || bigInt > 0xffffffffn) { + if (bigInt < BigInt(0) || bigInt > BigInt(0xffffffff)) { throw new address_error_1.AddressError('IPv4 BigInt must be in the range 0 to 2**32 - 1'); } return Address4.fromHex(bigInt.toString(16).padStart(8, '0')); } /** - * Convert a byte array to an Address4 object. + * Convert a byte array to an Address4 object. Throws `AddressError` unless + * given exactly 4 integers from 0 to 255. Signed bytes are rejected, so + * this differs from `Address6.fromByteArray`, which folds them; the two + * contracts converge on this stricter form in the next major version. * * To convert from a Node.js `Buffer`, spread it: `Address4.fromByteArray([...buf])`. * @param {Array} bytes - an array of 4 bytes (0-255) * @returns {Address4} */ static fromByteArray(bytes) { - if (bytes.length !== 4) { - throw new address_error_1.AddressError('IPv4 addresses require exactly 4 bytes'); - } - // Validate that all bytes are within valid range (0-255) - for (let i = 0; i < bytes.length; i++) { - if (!Number.isInteger(bytes[i]) || bytes[i] < 0 || bytes[i] > 255) { - throw new address_error_1.AddressError('All bytes must be integers between 0 and 255'); - } - } + common.assertByteArray(bytes, 4, 'IPv4', 0); return this.fromUnsignedByteArray(bytes); } /** - * Convert an unsigned byte array to an Address4 object + * Convert an unsigned byte array to an Address4 object. Throws + * `AddressError` unless given exactly 4 bytes, and rejects values outside + * 0 to 255 when parsing the resulting address. + * + * To convert from a Node.js `Buffer`, spread it: + * `Address4.fromUnsignedByteArray([...buf])`. * @param {Array} bytes - an array of 4 unsigned bytes (0-255) * @returns {Address4} */ @@ -383,7 +397,8 @@ class Address4 { return this.binaryZeroPad().slice(start, end); } /** - * Return the reversed ip6.arpa form of the address + * Return the reversed in-addr.arpa form of the address, e.g. + * `42.2.0.192.in-addr.arpa.` for `192.0.2.42`. * @param {Object} options * @param {boolean} options.omitSuffix - omit the "in-addr.arpa" suffix * @returns {String} @@ -403,49 +418,49 @@ class Address4 { * @returns {boolean} */ isMulticast() { - return this.isInSubnet(MULTICAST_V4); + return this.isHostInSubnet(MULTICAST_V4); } /** * Returns true if the address is in one of the [RFC 1918](https://datatracker.ietf.org/doc/html/rfc1918) private address ranges (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`). * @returns {boolean} */ isPrivate() { - return PRIVATE_V4.some((subnet) => this.isInSubnet(subnet)); + return PRIVATE_V4.some((subnet) => this.isHostInSubnet(subnet)); } /** * Returns true if the address is in the loopback range `127.0.0.0/8` ([RFC 1122](https://datatracker.ietf.org/doc/html/rfc1122)). * @returns {boolean} */ isLoopback() { - return this.isInSubnet(LOOPBACK_V4); + return this.isHostInSubnet(LOOPBACK_V4); } /** * Returns true if the address is in the link-local range `169.254.0.0/16` ([RFC 3927](https://datatracker.ietf.org/doc/html/rfc3927)). * @returns {boolean} */ isLinkLocal() { - return this.isInSubnet(LINK_LOCAL_V4); + return this.isHostInSubnet(LINK_LOCAL_V4); } /** * Returns true if the address is the unspecified address `0.0.0.0`. * @returns {boolean} */ isUnspecified() { - return this.isInSubnet(UNSPECIFIED_V4); + return this.isHostInSubnet(UNSPECIFIED_V4); } /** * Returns true if the address is the limited broadcast address `255.255.255.255` ([RFC 919](https://datatracker.ietf.org/doc/html/rfc919)). * @returns {boolean} */ isBroadcast() { - return this.isInSubnet(BROADCAST_V4); + return this.isHostInSubnet(BROADCAST_V4); } /** * Returns true if the address is in the carrier-grade NAT range `100.64.0.0/10` ([RFC 6598](https://datatracker.ietf.org/doc/html/rfc6598)). * @returns {boolean} */ isCGNAT() { - return this.isInSubnet(CGNAT_V4); + return this.isHostInSubnet(CGNAT_V4); } /** * Returns a zero-padded base-2 string representation of the address @@ -458,12 +473,17 @@ class Address4 { return this._binaryZeroPad; } /** - * Groups an IPv4 address for inclusion at the end of an IPv6 address + * Groups an IPv4 address for inclusion at the end of an IPv6 address. + * + * Returns an HTML fragment: each half of the address is wrapped in a + * `` carrying the group classes an address-inspector UI hovers on. + * The address content is HTML-escaped; anything you concatenate around it + * is your responsibility. * @returns {String} */ groupForV6() { const segments = this.parsedAddress; - return this.address.replace(constants.RE_ADDRESS, `${segments + return this.correctForm().replace(constants.RE_ADDRESS, `${segments .slice(0, 2) .join('.')}.${segments .slice(2, 4) diff --git a/deps/npm/node_modules/ip-address/dist/ipv6.js b/deps/npm/node_modules/ip-address/dist/ipv6.js index a78020ee788..d5f4fdb9c87 100644 --- a/deps/npm/node_modules/ip-address/dist/ipv6.js +++ b/deps/npm/node_modules/ip-address/dist/ipv6.js @@ -73,7 +73,6 @@ function paddedHex(octet) { return parseInt(octet, 16).toString(16).padStart(4, '0'); } function unsignByte(b) { - // eslint-disable-next-line no-bitwise return b & 0xff; } /** @@ -97,6 +96,15 @@ class Address6 { * @returns {boolean} */ this.isInSubnet = common.isInSubnet; + /** + * Returns true if this address's host bits fall inside the given subnet, + * ignoring this address's own subnet mask. Prefer this over `isInSubnet` + * when classifying a single address, so the answer doesn't change with the + * CIDR suffix the caller happened to write — notably when the address came + * from untrusted input and the result backs a trust-boundary decision. + * @returns {boolean} + */ + this.isHostInSubnet = common.isHostInSubnet; /** * Returns true if the address is correct, false otherwise * @returns {boolean} @@ -121,7 +129,10 @@ class Address6 { } address = address.replace(constants6.RE_SUBNET_STRING, ''); } - else if (/\//.test(address)) { + // RE_SUBNET_STRING anchors on the end of the address, so it strips only + // the trailing suffix. A second one left behind (`::/0/1`) is malformed + // and must be rejected rather than parsed as an address group. + if (/\//.test(address)) { throw new address_error_1.AddressError('Invalid subnet mask.'); } const zone = constants6.RE_ZONE_STRING.exec(address); @@ -145,7 +156,7 @@ class Address6 { new Address6(address); return true; } - catch (e) { + catch { return false; } } @@ -160,7 +171,7 @@ class Address6 { * address.correctForm(); // '::e8:d4a5:1000' */ static fromBigInt(bigInt) { - if (bigInt < 0n || bigInt > (1n << BigInt(constants6.BITS)) - 1n) { + if (bigInt < BigInt(0) || bigInt > (BigInt(1) << BigInt(constants6.BITS)) - BigInt(1)) { throw new address_error_1.AddressError('IPv6 BigInt must be in the range 0 to 2**128 - 1'); } const hex = bigInt.toString(16).padStart(32, '0'); @@ -181,46 +192,36 @@ class Address6 { * addressAndPort.port; // 8080 */ static fromURL(url) { + var _a; let host; let port = null; let result; + let error; + // Remove the protocol prefix, if any + const stripped = url.replace(/^[a-z][a-z0-9+.-]*:\/\//i, ''); // If we have brackets parse them and find a port - if (url.indexOf('[') !== -1 && url.indexOf(']:') !== -1) { - result = constants6.RE_URL_WITH_PORT.exec(url); + if (stripped.indexOf('[') !== -1 && stripped.indexOf(']:') !== -1) { + error = 'failed to parse address with port'; + result = constants6.RE_URL_WITH_PORT.exec(stripped); if (result === null) { - return { - error: 'failed to parse address with port', - address: null, - port: null, - }; + return { error, address: null, port: null }; } host = result[1]; port = result[2]; - // If there's a URL extract the address } - else if (url.indexOf('/') !== -1) { - // Remove the protocol prefix - url = url.replace(/^[a-z0-9]+:\/\//, ''); - // Parse the address - result = constants6.RE_URL.exec(url); + else { + error = 'failed to parse address from URL'; + result = constants6.RE_URL.exec(stripped); if (result === null) { - return { - error: 'failed to parse address from URL', - address: null, - port: null, - }; + return { error, address: null, port: null }; } - host = result[1]; - // Otherwise just assign the URL to the host and let the library parse it - } - else { - host = url; + host = (_a = result[1]) !== null && _a !== void 0 ? _a : result[2]; } // If there's a port convert it to an integer if (port) { port = parseInt(port, 10); - // squelch out of range ports - if (port < 0 || port > 65536) { + // squelch out of range ports (valid ports are 0-65535) + if (port < 0 || port > 65535) { port = null; } } @@ -228,10 +229,17 @@ class Address6 { // Standardize `undefined` to `null` port = null; } - return { - address: new Address6(host), - port, - }; + // The URL character class is a superset of valid IPv6, so a host the + // regex accepted (an IPv4 literal, bare punctuation, too many groups) + // can still be rejected by the parser + let address; + try { + address = new Address6(host); + } + catch { + return { error, address: null, port: null }; + } + return { address, port }; } /** * Construct an `Address6` from an address and a hex subnet mask given as @@ -258,7 +266,6 @@ class Address6 { static fromAddressAndWildcardMask(address, wildcardMask) { const wildcard = new Address6(wildcardMask).bigInt(); const allOnes = (BigInt(1) << BigInt(constants6.BITS)) - BigInt(1); - // eslint-disable-next-line no-bitwise const mask = wildcard ^ allOnes; const bits = common.prefixLengthFromMask(mask, constants6.BITS); return new Address6(`${address}/${bits}`); @@ -493,7 +500,7 @@ class Address6 { getType() { for (let i = 0; i < TYPE_SUBNETS.length; i++) { const entry = TYPE_SUBNETS[i]; - if (this.isInSubnet(entry[0])) { + if (this.isHostInSubnet(entry[0])) { return entry[1]; } } @@ -635,20 +642,27 @@ class Address6 { } const groups = address.split(':'); const lastGroup = groups.slice(-1)[0]; + // RE_ADDRESS rejects octets with a leading zero, so a dotted-quad tail is + // matched permissively first: that way this notation still gets its own + // message with the offending octet highlighted, rather than falling + // through as an unrecognized group. + const v4Octets = lastGroup.split('.'); + if (v4Octets.length === constants4.GROUPS && + v4Octets.every((octet) => /^\d{1,3}$/.test(octet))) { + if (v4Octets.some((octet) => /^0\d/.test(octet))) { + // The prefix groups haven't been through the bad-character check + // yet, so escape them before including in the error HTML. + const highlighted = v4Octets.map(spanLeadingZeroes4).join('.'); + const prefix = groups.slice(0, -1).map(helpers.escapeHtml).join(':'); + const separator = groups.length > 1 ? ':' : ''; + throw new address_error_1.AddressError("IPv4 addresses can't have leading zeroes.", `${prefix}${separator}${highlighted}`); + } + } const address4 = lastGroup.match(constants4.RE_ADDRESS); if (address4) { this.parsedAddress4 = address4[0]; - this.address4 = new ipv4_1.Address4(this.parsedAddress4); - for (let i = 0; i < this.address4.groups; i++) { - if (/^0[0-9]+/.test(this.address4.parsedAddress[i])) { - // The prefix groups haven't been through the bad-character check - // yet, so escape them before including in the error HTML. - const highlighted = this.address4.parsedAddress.map(spanLeadingZeroes4).join('.'); - const prefix = groups.slice(0, -1).map(helpers.escapeHtml).join(':'); - const separator = groups.length > 1 ? ':' : ''; - throw new address_error_1.AddressError("IPv4 addresses can't have leading zeroes.", `${prefix}${separator}${highlighted}`); - } - } + const v4Suffix = this.subnetMask >= 96 ? `/${this.subnetMask - 96}` : ''; + this.address4 = new ipv4_1.Address4(`${this.parsedAddress4}${v4Suffix}`); this.v4 = true; groups[groups.length - 1] = this.address4.toGroup6(); address = groups.join(':'); @@ -734,7 +748,11 @@ class Address6 { return BigInt(`0x${this.parsedAddress.map(paddedHex).join('')}`); } /** - * Return the last two groups of this address as an IPv4 address string + * Return the last two groups of this address as an IPv4 address string. + * If this address carries a CIDR prefix that covers the trailing 32 bits + * (i.e. `subnetMask >= 96`), the resulting `Address4` inherits the + * corresponding v4 prefix (`subnetMask - 96`); otherwise it defaults to + * `/32`. * @returns {Address4} * @example * var address = new Address6('2001:4860:4001::1825:bf11'); @@ -742,7 +760,18 @@ class Address6 { */ to4() { const binary = this.binaryZeroPad().split(''); - return ipv4_1.Address4.fromHex(BigInt(`0b${binary.slice(96, 128).join('')}`).toString(16).padStart(8, '0')); + const hex = BigInt(`0b${binary.slice(96, 128).join('')}`) + .toString(16) + .padStart(8, '0'); + if (this.subnetMask >= 96) { + const v4Mask = this.subnetMask - 96; + const groups = []; + for (let i = 0; i < 8; i += 2) { + groups.push(parseInt(hex.slice(i, i + 2), 16)); + } + return new ipv4_1.Address4(`${groups.join('.')}/${v4Mask}`); + } + return ipv4_1.Address4.fromHex(hex); } /** * Return the v4-in-v6 form of the address @@ -756,7 +785,7 @@ class Address6 { if (!/:$/.test(correct)) { infix = ':'; } - return correct + infix + address4.address; + return correct + infix + address4.correctForm(); } /** * Decodes the Teredo tunneling fields embedded in this address. Returns the @@ -788,11 +817,9 @@ class Address6 { */ const prefix = this.getBitsBase16(0, 32); const bitsForUdpPort = this.getBits(80, 96); - // eslint-disable-next-line no-bitwise const udpPort = (bitsForUdpPort ^ BigInt('0xffff')).toString(); const server4 = ipv4_1.Address4.fromHex(this.getBitsBase16(32, 64)); const bitsForClient4 = this.getBits(96, 128); - // eslint-disable-next-line no-bitwise const client4 = ipv4_1.Address4.fromHex((bitsForClient4 ^ BigInt('0xffffffff')).toString(16).padStart(8, '0')); const flagsBase2 = this.getBitsBase2(64, 80); const coneNat = (0, common_1.testBit)(flagsBase2, 15); @@ -874,12 +901,14 @@ class Address6 { } else { const beforeU = 64 - pl; - bits = - prefixBits.slice(0, pl) + - v4Bits.slice(0, beforeU) + - '00000000' + - v4Bits.slice(beforeU) + - '0'.repeat(128 - 72 - (32 - beforeU)); + bits = [ + prefixBits.slice(0, pl), + v4Bits.slice(0, beforeU), + // Bits 64 to 71 are the reserved u octet and are always zero. + '00000000', + v4Bits.slice(beforeU), + '0'.repeat(128 - 72 - (32 - beforeU)), + ].join(''); } const hex = BigInt(`0b${bits}`).toString(16).padStart(32, '0'); const groups = []; @@ -902,7 +931,7 @@ class Address6 { if (pl !== 32 && pl !== 40 && pl !== 48 && pl !== 56 && pl !== 64 && pl !== 96) { throw new address_error_1.AddressError('NAT64 prefix length must be 32, 40, 48, 56, 64, or 96'); } - if (!this.isInSubnet(prefix6)) { + if (!this.isHostInSubnet(prefix6)) { return null; } const bits = this.binaryZeroPad(); @@ -927,9 +956,9 @@ class Address6 { * @returns {Array} */ toByteArray() { - const valueWithoutPadding = this.bigInt().toString(16); - const leadingPad = '0'.repeat(valueWithoutPadding.length % 2); - const value = `${leadingPad}${valueWithoutPadding}`; + const value = this.bigInt() + .toString(16) + .padStart(constants6.BITS / 4, '0'); const bytes = []; for (let i = 0, length = value.length; i < length; i += 2) { bytes.push(parseInt(value.substring(i, i + 2), 16)); @@ -943,24 +972,39 @@ class Address6 { * @returns {Array} */ toUnsignedByteArray() { + // toByteArray() emits 0 to 255, so unsigning it is an identity mapping and + // the two methods return equal arrays. 11.0.0 keeps one of them and makes + // this a deprecated alias; test/common-test.ts fails at that version. return this.toByteArray().map(unsignByte); } /** * Convert a byte array to an Address6 object. * + * Accepts unsigned bytes (0 to 255) or signed bytes (-128 to 127, as an + * `Int8Array` or a Java `byte[]` holds them), folding signed values to their + * unsigned equivalent. Throws `AddressError` unless given exactly 16 + * integers from -128 to 255. + * * To convert from a Node.js `Buffer`, spread it: `Address6.fromByteArray([...buf])`. * @returns {Address6} */ static fromByteArray(bytes) { + // Address4.fromByteArray takes unsigned bytes only. 11.0.0 aligns this + // method with it, at which point the -128 floor here, unsignByte, and the + // mapping below all go; test/common-test.ts fails at that version. + common.assertByteArray(bytes, 16, 'IPv6', -128); return this.fromUnsignedByteArray(bytes.map(unsignByte)); } /** * Convert an unsigned byte array to an Address6 object. * + * Throws `AddressError` unless given exactly 16 integers from 0 to 255. + * * To convert from a Node.js `Buffer`, spread it: `Address6.fromUnsignedByteArray([...buf])`. * @returns {Address6} */ static fromUnsignedByteArray(bytes) { + common.assertByteArray(bytes, 16, 'IPv6', 0); const BYTE_MAX = BigInt('256'); let result = BigInt('0'); let multiplier = BigInt('1'); @@ -982,7 +1026,11 @@ class Address6 { * @returns {boolean} */ isLinkLocal() { - // Zeroes are required, i.e. we can't check isInSubnet with 'fe80::/10' + const embedded = this.embeddedIPv4(); + if (embedded) { + return embedded.isLinkLocal(); + } + // Zeroes are required, i.e. we can't check isHostInSubnet with 'fe80::/10' if (this.getBitsBase2(0, 64) === '1111111010000000000000000000000000000000000000000000000000000000') { return true; @@ -994,6 +1042,10 @@ class Address6 { * @returns {boolean} */ isMulticast() { + const embedded = this.embeddedIPv4(); + if (embedded) { + return embedded.isMulticast(); + } const type = this.getType(); return type === 'Multicast' || type.startsWith('Multicast '); } @@ -1016,27 +1068,54 @@ class Address6 { * @returns {boolean} */ isMapped4() { - return this.isInSubnet(IPV4_MAPPED_SUBNET); + return this.isHostInSubnet(IPV4_MAPPED_SUBNET); + } + /** + * If this address embeds a routable IPv4 address — i.e. it is IPv4-mapped + * (`::ffff:0:0/96`) or sits in the NAT64 well-known prefix (`64:ff9b::/96`, + * [RFC 6052](https://datatracker.ietf.org/doc/html/rfc6052)) — return that + * embedded address as an {@link Address4}; otherwise return null. + * + * The special-property checks (`isLoopback`, `isLinkLocal`, `isMulticast`, + * `isUnspecified`, `isPrivate`, `isCGNAT`, `isBroadcast`) call this first and + * delegate to the embedded {@link Address4} when present, so a literal such as + * `::ffff:127.0.0.1` is classified by what it actually reaches (loopback) + * rather than by its IPv6 wrapper (which `getType()` reports as IPv4-mapped). + * This matters wherever the checks back a trust-boundary decision (e.g. an + * SSRF allow/deny filter): without normalization, `::ffff:10.0.0.1`, + * `::ffff:169.254.169.254`, `64:ff9b::7f00:1`, etc. would all read as + * non-internal. + * @returns {Address4 | null} + */ + embeddedIPv4() { + if (this.isMapped4() || this.isHostInSubnet(NAT64_WELL_KNOWN_SUBNET)) { + return this.to4(); + } + return null; } /** * Returns true if the address is a Teredo address, false otherwise * @returns {boolean} */ isTeredo() { - return this.isInSubnet(TEREDO_SUBNET); + return this.isHostInSubnet(TEREDO_SUBNET); } /** * Returns true if the address is a 6to4 address, false otherwise * @returns {boolean} */ is6to4() { - return this.isInSubnet(SIX_TO_FOUR_SUBNET); + return this.isHostInSubnet(SIX_TO_FOUR_SUBNET); } /** * Returns true if the address is a loopback address, false otherwise * @returns {boolean} */ isLoopback() { + const embedded = this.embeddedIPv4(); + if (embedded) { + return embedded.isLoopback(); + } return this.getType() === 'Loopback'; } /** @@ -1044,13 +1123,64 @@ class Address6 { * @returns {boolean} */ isULA() { - return this.isInSubnet(ULA_SUBNET); + return this.isHostInSubnet(ULA_SUBNET); + } + /** + * Returns true if the address is private, i.e. a Unique Local Address in + * `fc00::/7` ([RFC 4193](https://datatracker.ietf.org/doc/html/rfc4193)) or an + * IPv4-mapped / NAT64 address whose embedded IPv4 address is in one of the + * [RFC 1918](https://datatracker.ietf.org/doc/html/rfc1918) private ranges + * (e.g. `::ffff:10.0.0.1`). This is the IPv6 counterpart to + * {@link Address4.isPrivate}; use it instead of {@link isULA} when you need to + * catch mapped RFC 1918 addresses as well as native ULAs. + * @returns {boolean} + */ + isPrivate() { + const embedded = this.embeddedIPv4(); + if (embedded) { + return embedded.isPrivate(); + } + return this.isULA(); + } + /** + * Returns true if the address is an IPv4-mapped / NAT64 address whose embedded + * IPv4 address is in the carrier-grade NAT range `100.64.0.0/10` + * ([RFC 6598](https://datatracker.ietf.org/doc/html/rfc6598)), false + * otherwise. There is no native IPv6 CGNAT range, so this only ever returns + * true for an embedded IPv4 address (e.g. `::ffff:100.64.0.1`). + * @returns {boolean} + */ + isCGNAT() { + const embedded = this.embeddedIPv4(); + if (embedded) { + return embedded.isCGNAT(); + } + return false; + } + /** + * Returns true if the address is an IPv4-mapped / NAT64 address whose embedded + * IPv4 address is the limited broadcast address `255.255.255.255` + * ([RFC 919](https://datatracker.ietf.org/doc/html/rfc919)), false otherwise. + * There is no IPv6 broadcast, so this only ever returns true for an embedded + * IPv4 address (e.g. `::ffff:255.255.255.255`). + * @returns {boolean} + */ + isBroadcast() { + const embedded = this.embeddedIPv4(); + if (embedded) { + return embedded.isBroadcast(); + } + return false; } /** * Returns true if the address is the unspecified address `::`. * @returns {boolean} */ isUnspecified() { + const embedded = this.embeddedIPv4(); + if (embedded) { + return embedded.isUnspecified(); + } return this.getType() === 'Unspecified'; } /** @@ -1058,7 +1188,7 @@ class Address6 { * @returns {boolean} */ isDocumentation() { - return this.isInSubnet(DOCUMENTATION_SUBNET); + return this.isHostInSubnet(DOCUMENTATION_SUBNET); } // #endregion // #region HTML @@ -1111,7 +1241,12 @@ class Address6 { return `${safeForm}`; } /** - * Groups an address + * Groups an address. + * + * Returns an HTML fragment: each group is wrapped in a `` carrying + * the group classes an address-inspector UI hovers on. The address content + * is HTML-escaped; anything you concatenate around it is your + * responsibility. * @returns {String} */ group() { @@ -1214,4 +1349,5 @@ const SIX_TO_FOUR_SUBNET = new Address6('2002::/16'); const ULA_SUBNET = new Address6('fc00::/7'); const DOCUMENTATION_SUBNET = new Address6('2001:db8::/32'); const IPV4_MAPPED_SUBNET = new Address6('::ffff:0:0/96'); +const NAT64_WELL_KNOWN_SUBNET = new Address6('64:ff9b::/96'); //# sourceMappingURL=ipv6.js.map \ No newline at end of file diff --git a/deps/npm/node_modules/ip-address/dist/v4/constants.js b/deps/npm/node_modules/ip-address/dist/v4/constants.js index 6fa2518f964..158288b7de6 100644 --- a/deps/npm/node_modules/ip-address/dist/v4/constants.js +++ b/deps/npm/node_modules/ip-address/dist/v4/constants.js @@ -3,6 +3,10 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.RE_SUBNET_STRING = exports.RE_ADDRESS = exports.GROUPS = exports.BITS = void 0; exports.BITS = 32; exports.GROUPS = 4; -exports.RE_ADDRESS = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/g; +// Each octet is 0-255 written without a leading zero. A leading zero is +// octal to the WHATWG URL parser, inet_aton, and getaddrinfo, but decimal to +// parseInt(part, 10), so accepting the notation would make this library +// disagree with the network stack about which host a string names. +exports.RE_ADDRESS = /^(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])$/g; exports.RE_SUBNET_STRING = /\/\d{1,2}$/; //# sourceMappingURL=constants.js.map \ No newline at end of file diff --git a/deps/npm/node_modules/ip-address/dist/v6/constants.js b/deps/npm/node_modules/ip-address/dist/v6/constants.js index 1a8cd1dd616..4616cad66b6 100644 --- a/deps/npm/node_modules/ip-address/dist/v6/constants.js +++ b/deps/npm/node_modules/ip-address/dist/v6/constants.js @@ -44,6 +44,7 @@ exports.TYPES = { 'ff05::1:3/128': 'Multicast (All DHCP servers in this site)', '::/128': 'Unspecified', '::1/128': 'Loopback', + '::ffff:0:0/96': 'IPv4-mapped', 'ff00::/8': 'Multicast', 'fe80::/10': 'Link-local unicast', 'fc00::/7': 'Unique local', @@ -76,6 +77,6 @@ exports.RE_SUBNET_STRING = /\/\d{1,3}(?=%|$)/; * @static */ exports.RE_ZONE_STRING = /%.*$/; -exports.RE_URL = /^\[{0,1}([0-9a-f:]+)\]{0,1}/; -exports.RE_URL_WITH_PORT = /\[([0-9a-f:]+)\]:([0-9]{1,5})/; +exports.RE_URL = /^(?:\[([0-9a-f:.]+)\]|([0-9a-f:.]+))(?:[/?#].*)?$/i; +exports.RE_URL_WITH_PORT = /^\[([0-9a-f:.]+)\]:([0-9]{1,5})(?:[/?#].*)?$/i; //# sourceMappingURL=constants.js.map \ No newline at end of file diff --git a/deps/npm/node_modules/ip-address/package.json b/deps/npm/node_modules/ip-address/package.json index 47d109ec6f3..6ea2d24bd62 100644 --- a/deps/npm/node_modules/ip-address/package.json +++ b/deps/npm/node_modules/ip-address/package.json @@ -16,7 +16,7 @@ "bigint", "browser" ], - "version": "10.2.0", + "version": "10.5.0", "author": "Beau Gunderson (https://beaugunderson.com/)", "license": "MIT", "main": "dist/ip-address.js", @@ -25,6 +25,9 @@ "docs": "tsx scripts/build-readme.ts", "build": "rm -rf dist; mkdir dist; tsc", "prepack": "npm run docs && npm run build", + "prepare": "git config core.hooksPath hooks || true", + "lint": "prettier --check . && eslint . --ext .ts,.js --max-warnings 0", + "lint:fix": "prettier --write . && eslint . --ext .ts,.js --max-warnings 0 --fix", "test-ci": "c8 --experimental-monocart mocha", "test": "mocha", "watch": "mocha --watch" @@ -54,7 +57,7 @@ ], "repository": { "type": "git", - "url": "git://github.com/beaugunderson/ip-address.git" + "url": "https://github.com/beaugunderson/ip-address.git" }, "overrides": { "diff": "^8.0.3", @@ -70,11 +73,10 @@ "chai": "^6.2.2", "eslint": "^8.57.1", "eslint_d": "^14.0.4", - "eslint-config-airbnb": "^19.0.4", + "eslint-config-airbnb-base": "^15.0.0", "eslint-config-prettier": "^10.1.8", "eslint-plugin-filenames": "^1.3.2", "eslint-plugin-import": "^2.32.0", - "eslint-plugin-jsx-a11y": "^6.10.2", "eslint-plugin-prettier": "^5.5.5", "eslint-plugin-sort-imports-es6-autofix": "^0.6.0", "mocha": "^11.7.5", diff --git a/deps/npm/package.json b/deps/npm/package.json index 01c352496cb..d1ad802bef6 100644 --- a/deps/npm/package.json +++ b/deps/npm/package.json @@ -75,7 +75,7 @@ "hosted-git-info": "^8.1.0", "ini": "^5.0.0", "init-package-json": "^7.0.2", - "ip-address": "^10.2.0", + "ip-address": "^10.5.0", "is-cidr": "^5.1.1", "json-parse-even-better-errors": "^4.0.0", "libnpmaccess": "^9.0.0", diff --git a/doc/changelogs/NSOLID_CHANGELOG_V6_NODE_V22.md b/doc/changelogs/NSOLID_CHANGELOG_V6_NODE_V22.md index 836e09c282d..8c7ffddd425 100644 --- a/doc/changelogs/NSOLID_CHANGELOG_V6_NODE_V22.md +++ b/doc/changelogs/NSOLID_CHANGELOG_V6_NODE_V22.md @@ -2,6 +2,12 @@ +## 2026-08-10, Version 22.23.2-nsolid-v6.3.6 'Jod' + +### Commits + +* \[[`eb8adbc501`](https://github.com/nodesource/nsolid/commit/eb8adbc501)] - **deps**: update npm ip-address\@10.5.0 (Santiago Gimeno) + ## 2026-07-31, Version 22.23.2-nsolid-v6.3.5 'Jod' ### Commits diff --git a/src/node_version.h b/src/node_version.h index 6677b464186..80934e0d2ea 100644 --- a/src/node_version.h +++ b/src/node_version.h @@ -36,7 +36,7 @@ #define NSOLID_MINOR_VERSION 3 #define NSOLID_PATCH_VERSION 6 -#define NSOLID_VERSION_IS_RELEASE 0 +#define NSOLID_VERSION_IS_RELEASE 1 #ifndef NODE_STRINGIFY #define NODE_STRINGIFY(n) NODE_STRINGIFY_HELPER(n)