diff --git a/node_modules/brace-expansion/dist/commonjs/index.js b/node_modules/brace-expansion/dist/commonjs/index.js index 869a6bee23807..48cf0d3dabc88 100644 --- a/node_modules/brace-expansion/dist/commonjs/index.js +++ b/node_modules/brace-expansion/dist/commonjs/index.js @@ -1,6 +1,6 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.EXPANSION_MAX_LENGTH = exports.EXPANSION_MAX = void 0; +exports.EXPANSION_MAX_REWRITES = exports.EXPANSION_MAX_DEPTH = exports.EXPANSION_MAX_LENGTH = exports.EXPANSION_MAX = void 0; exports.expand = expand; const balanced_match_1 = require("balanced-match"); const escSlash = '\0SLASH' + Math.random() + '\0'; @@ -30,6 +30,24 @@ exports.EXPANSION_MAX = 100_000; // realistic expansion (100k results hitting `EXPANSION_MAX` measure ~1M // characters) so legitimate input is unaffected. exports.EXPANSION_MAX_LENGTH = 4_000_000; +// `expand_` recurses once per level of brace *nesting* - both when expanding a +// set's comma members and when re-wrapping a set whose body is a single part. +// The CVE-2026-14257 fix made the *tail* iterative (recursion on `m.post`, one +// level per chained group), which left nesting depth unbounded: about 3,100 +// levels of `{{{...a,b...}}}` - only ~6KB of input - exhausted the native stack +// and crashed the process. `EXPANSION_MAX_DEPTH` bounds how deep the parser +// will follow nesting. It sits far above any realistic pattern and well below +// the depth at which the stack runs out. +exports.EXPANSION_MAX_DEPTH = 1_000; +// Bash keeps a quirk where a brace group followed by a comma set still expands +// (`{a},b}`). The parser implements it by rewriting the string and restarting +// the scan, absorbing one `}` per pass. `n` trailing braces therefore cost `n` +// full passes over a string that itself grows by one `escClose` sentinel each +// time - quadratic in `n`, with a ~26x constant from the sentinel's length. +// 128KB of `'{a}' + '}'.repeat(n) + ',z}'` blocked the event loop for 27 +// seconds to produce two results. `EXPANSION_MAX_REWRITES` bounds how many +// times the scan may restart. Real `{a},b}` input needs a handful. +exports.EXPANSION_MAX_REWRITES = 1_000; function numeric(str) { return !isNaN(str) ? parseInt(str, 10) : str.charCodeAt(0); } @@ -49,37 +67,52 @@ function unescapeBraces(str) { .replace(escCommaPattern, ',') .replace(escPeriodPattern, '.'); } +// Like `target.push(...items)` but doesn't overflow the stack +function pushAll(target, items) { + for (let i = 0; i < items.length; i++) { + target.push(items[i]); + } +} /** * Basically just str.split(","), but handling cases * where we have nested braced sections, which should be * treated as individual members, like {a,{b,c},d} */ function parseCommaParts(str) { - if (!str) { - return ['']; - } const parts = []; - const m = (0, balanced_match_1.balanced)('{', '}', str); - if (!m) { - return str.split(','); - } - const { pre, body, post } = m; - const p = pre.split(','); - p[p.length - 1] += '{' + body + '}'; - const postParts = parseCommaParts(post); - if (post.length) { - ; - p[p.length - 1] += postParts.shift(); - p.push.apply(p, postParts); + // Walk the brace groups iteratively. Recursing on `post` once per group let a + // chain of them exhaust the stack - the parsing-side counterpart to + // the `expand_` overflow fixed for CVE-2026-14257, and not something `max` or + // `maxLength` can bound, since it happens before expansion. + // + // The part the next chunk continues + let carry = ''; + for (;;) { + const m = (0, balanced_match_1.balanced)('{', '}', str); + if (!m) { + const tail = str.split(','); + tail[0] = carry + tail[0]; + pushAll(parts, tail); + return parts; + } + const { pre, body, post } = m; + const p = pre.split(','); + p[0] = carry + p[0]; + p[p.length - 1] += '{' + body + '}'; + if (!post.length) { + pushAll(parts, p); + return parts; + } + carry = p.pop(); + pushAll(parts, p); + str = post; } - parts.push.apply(parts, p); - return parts; } function expand(str, options = {}) { if (!str) { return []; } - const { max = exports.EXPANSION_MAX, maxLength = exports.EXPANSION_MAX_LENGTH } = options; + const { max = exports.EXPANSION_MAX, maxLength = exports.EXPANSION_MAX_LENGTH, maxDepth = exports.EXPANSION_MAX_DEPTH, maxRewrites = exports.EXPANSION_MAX_REWRITES, } = options; // I don't know why Bash 4.3 does this, but it does. // Anything starting with {} will have the first two bytes preserved // but *only* at the top level, so {},a}b will not expand to anything, @@ -89,7 +122,7 @@ function expand(str, options = {}) { if (str.slice(0, 2) === '{}') { str = '\\{\\}' + str.slice(2); } - return expand_(escapeBraces(str), max, maxLength, true).map(unescapeBraces); + return expand_(escapeBraces(str), max, maxLength, maxDepth, 0, maxRewrites, true).map(unescapeBraces); } function embrace(str) { return '{' + str + '}'; @@ -184,7 +217,13 @@ function expandSequence(body, isAlphaSequence, max, maxLength) { } return N; } -function expand_(str, max, maxLength, isTop) { +function expand_(str, max, maxLength, maxDepth, depth, maxRewrites, isTop) { + // Too deeply nested to keep following: treat the rest as literal, the same + // way a group that cannot expand is already handled. Truncating rather than + // throwing keeps `expand` total, matching `max` and `maxLength`. + if (depth > maxDepth) { + return [str]; + } // Consume the string's top-level brace groups left to right, threading a // running set of combined prefixes (`acc`). Expanding the tail iteratively - // rather than recursing on `m.post` once per group - keeps the native stack @@ -196,6 +235,9 @@ function expand_(str, max, maxLength, isTop) { // comma set - a sequence like `{a..\}` may legitimately yield ''. The drop // is on the final strings, so it is applied to whichever `combine` produces // them (the one with no brace set left in the tail). + // How many times the `{a},b}` rewrite below has restarted the scan. Each pass + // re-reads the whole string, so leaving this unbounded is quadratic. + let rewrites = 0; let dropEmpties = false; let firstGroup = true; for (;;) { @@ -220,7 +262,8 @@ function expand_(str, max, maxLength, isTop) { const isOptions = m.body.indexOf(',') >= 0; if (!isSequence && !isOptions) { // {a},b} - if (m.post.match(/,(?!,).*\}/)) { + if (rewrites < maxRewrites && m.post.match(/,(?!,).*\}/)) { + rewrites++; str = m.pre + '{' + m.body + escClose + m.post; isTop = true; continue; @@ -240,7 +283,7 @@ function expand_(str, max, maxLength, isTop) { let n = parseCommaParts(m.body); if (n.length === 1 && n[0] !== undefined) { // x{{a,b}}y ==> x{a}y x{b}y - n = expand_(n[0], max, maxLength, false).map(embrace); + n = expand_(n[0], max, maxLength, maxDepth, depth + 1, maxRewrites, false).map(embrace); //XXX is this necessary? Can't seem to hit it in tests. /* c8 ignore start */ if (n.length === 1) { @@ -266,12 +309,13 @@ function expand_(str, max, maxLength, isTop) { values = []; let valuesLength = 0; outer: for (let j = 0; j < n.length; j++) { - const expanded = expand_(n[j], max, maxLength, false); + const expanded = expand_(n[j], max, maxLength, maxDepth, depth + 1, maxRewrites, false); for (let k = 0; k < expanded.length; k++) { const v = expanded[k]; if (dropsEmpties && !v) continue; - if (values.length >= max || valuesLength + v.length > maxLength) { + if (values.length >= max || + valuesLength + v.length > maxLength) { break outer; } values.push(v); diff --git a/node_modules/brace-expansion/dist/esm/index.js b/node_modules/brace-expansion/dist/esm/index.js index fd68f57029207..0e0cc962307eb 100644 --- a/node_modules/brace-expansion/dist/esm/index.js +++ b/node_modules/brace-expansion/dist/esm/index.js @@ -26,6 +26,24 @@ export const EXPANSION_MAX = 100_000; // realistic expansion (100k results hitting `EXPANSION_MAX` measure ~1M // characters) so legitimate input is unaffected. export const EXPANSION_MAX_LENGTH = 4_000_000; +// `expand_` recurses once per level of brace *nesting* - both when expanding a +// set's comma members and when re-wrapping a set whose body is a single part. +// The CVE-2026-14257 fix made the *tail* iterative (recursion on `m.post`, one +// level per chained group), which left nesting depth unbounded: about 3,100 +// levels of `{{{...a,b...}}}` - only ~6KB of input - exhausted the native stack +// and crashed the process. `EXPANSION_MAX_DEPTH` bounds how deep the parser +// will follow nesting. It sits far above any realistic pattern and well below +// the depth at which the stack runs out. +export const EXPANSION_MAX_DEPTH = 1_000; +// Bash keeps a quirk where a brace group followed by a comma set still expands +// (`{a},b}`). The parser implements it by rewriting the string and restarting +// the scan, absorbing one `}` per pass. `n` trailing braces therefore cost `n` +// full passes over a string that itself grows by one `escClose` sentinel each +// time - quadratic in `n`, with a ~26x constant from the sentinel's length. +// 128KB of `'{a}' + '}'.repeat(n) + ',z}'` blocked the event loop for 27 +// seconds to produce two results. `EXPANSION_MAX_REWRITES` bounds how many +// times the scan may restart. Real `{a},b}` input needs a handful. +export const EXPANSION_MAX_REWRITES = 1_000; function numeric(str) { return !isNaN(str) ? parseInt(str, 10) : str.charCodeAt(0); } @@ -45,37 +63,52 @@ function unescapeBraces(str) { .replace(escCommaPattern, ',') .replace(escPeriodPattern, '.'); } +// Like `target.push(...items)` but doesn't overflow the stack +function pushAll(target, items) { + for (let i = 0; i < items.length; i++) { + target.push(items[i]); + } +} /** * Basically just str.split(","), but handling cases * where we have nested braced sections, which should be * treated as individual members, like {a,{b,c},d} */ function parseCommaParts(str) { - if (!str) { - return ['']; - } const parts = []; - const m = balanced('{', '}', str); - if (!m) { - return str.split(','); - } - const { pre, body, post } = m; - const p = pre.split(','); - p[p.length - 1] += '{' + body + '}'; - const postParts = parseCommaParts(post); - if (post.length) { - ; - p[p.length - 1] += postParts.shift(); - p.push.apply(p, postParts); + // Walk the brace groups iteratively. Recursing on `post` once per group let a + // chain of them exhaust the stack - the parsing-side counterpart to + // the `expand_` overflow fixed for CVE-2026-14257, and not something `max` or + // `maxLength` can bound, since it happens before expansion. + // + // The part the next chunk continues + let carry = ''; + for (;;) { + const m = balanced('{', '}', str); + if (!m) { + const tail = str.split(','); + tail[0] = carry + tail[0]; + pushAll(parts, tail); + return parts; + } + const { pre, body, post } = m; + const p = pre.split(','); + p[0] = carry + p[0]; + p[p.length - 1] += '{' + body + '}'; + if (!post.length) { + pushAll(parts, p); + return parts; + } + carry = p.pop(); + pushAll(parts, p); + str = post; } - parts.push.apply(parts, p); - return parts; } export function expand(str, options = {}) { if (!str) { return []; } - const { max = EXPANSION_MAX, maxLength = EXPANSION_MAX_LENGTH } = options; + const { max = EXPANSION_MAX, maxLength = EXPANSION_MAX_LENGTH, maxDepth = EXPANSION_MAX_DEPTH, maxRewrites = EXPANSION_MAX_REWRITES, } = options; // I don't know why Bash 4.3 does this, but it does. // Anything starting with {} will have the first two bytes preserved // but *only* at the top level, so {},a}b will not expand to anything, @@ -85,7 +118,7 @@ export function expand(str, options = {}) { if (str.slice(0, 2) === '{}') { str = '\\{\\}' + str.slice(2); } - return expand_(escapeBraces(str), max, maxLength, true).map(unescapeBraces); + return expand_(escapeBraces(str), max, maxLength, maxDepth, 0, maxRewrites, true).map(unescapeBraces); } function embrace(str) { return '{' + str + '}'; @@ -180,7 +213,13 @@ function expandSequence(body, isAlphaSequence, max, maxLength) { } return N; } -function expand_(str, max, maxLength, isTop) { +function expand_(str, max, maxLength, maxDepth, depth, maxRewrites, isTop) { + // Too deeply nested to keep following: treat the rest as literal, the same + // way a group that cannot expand is already handled. Truncating rather than + // throwing keeps `expand` total, matching `max` and `maxLength`. + if (depth > maxDepth) { + return [str]; + } // Consume the string's top-level brace groups left to right, threading a // running set of combined prefixes (`acc`). Expanding the tail iteratively - // rather than recursing on `m.post` once per group - keeps the native stack @@ -192,6 +231,9 @@ function expand_(str, max, maxLength, isTop) { // comma set - a sequence like `{a..\}` may legitimately yield ''. The drop // is on the final strings, so it is applied to whichever `combine` produces // them (the one with no brace set left in the tail). + // How many times the `{a},b}` rewrite below has restarted the scan. Each pass + // re-reads the whole string, so leaving this unbounded is quadratic. + let rewrites = 0; let dropEmpties = false; let firstGroup = true; for (;;) { @@ -216,7 +258,8 @@ function expand_(str, max, maxLength, isTop) { const isOptions = m.body.indexOf(',') >= 0; if (!isSequence && !isOptions) { // {a},b} - if (m.post.match(/,(?!,).*\}/)) { + if (rewrites < maxRewrites && m.post.match(/,(?!,).*\}/)) { + rewrites++; str = m.pre + '{' + m.body + escClose + m.post; isTop = true; continue; @@ -236,7 +279,7 @@ function expand_(str, max, maxLength, isTop) { let n = parseCommaParts(m.body); if (n.length === 1 && n[0] !== undefined) { // x{{a,b}}y ==> x{a}y x{b}y - n = expand_(n[0], max, maxLength, false).map(embrace); + n = expand_(n[0], max, maxLength, maxDepth, depth + 1, maxRewrites, false).map(embrace); //XXX is this necessary? Can't seem to hit it in tests. /* c8 ignore start */ if (n.length === 1) { @@ -262,12 +305,13 @@ function expand_(str, max, maxLength, isTop) { values = []; let valuesLength = 0; outer: for (let j = 0; j < n.length; j++) { - const expanded = expand_(n[j], max, maxLength, false); + const expanded = expand_(n[j], max, maxLength, maxDepth, depth + 1, maxRewrites, false); for (let k = 0; k < expanded.length; k++) { const v = expanded[k]; if (dropsEmpties && !v) continue; - if (values.length >= max || valuesLength + v.length > maxLength) { + if (values.length >= max || + valuesLength + v.length > maxLength) { break outer; } values.push(v); diff --git a/node_modules/brace-expansion/package.json b/node_modules/brace-expansion/package.json index 4376400796c95..a5c96ebe02f5f 100644 --- a/node_modules/brace-expansion/package.json +++ b/node_modules/brace-expansion/package.json @@ -1,7 +1,7 @@ { "name": "brace-expansion", "description": "Brace expansion as known from sh/bash", - "version": "5.0.9", + "version": "5.0.12", "files": [ "dist" ], @@ -29,6 +29,7 @@ "test": "tap", "snap": "tap", "format": "prettier --write .", + "format:check": "prettier --check .", "benchmark": "node benchmark/index.js", "typedoc": "typedoc --tsconfig .tshy/esm.json ./src/*.ts" }, diff --git a/node_modules/ip-address/dist/common.js b/node_modules/ip-address/dist/common.js index 0c15d21e3a39e..b91948281d37b 100644 --- a/node_modules/ip-address/dist/common.js +++ b/node_modules/ip-address/dist/common.js @@ -2,6 +2,8 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.isInSubnet = isInSubnet; exports.isHostInSubnet = isHostInSubnet; +exports.isGloballyReachable = isGloballyReachable; +exports.offsetBigInt = offsetBigInt; exports.isCorrect = isCorrect; exports.prefixLengthFromMask = prefixLengthFromMask; exports.assertByteArray = assertByteArray; @@ -36,10 +38,56 @@ function isInSubnet(address) { * {@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. + * + * An address of one family is never inside a network of the other, so an + * `Address4` against an `Address6` (or the reverse) is `false`. To compare + * across families, convert first: `Address6.fromAddress4()`, `to4()`, or + * `toAddress4Nat64()`. */ function isHostInSubnet(address) { + // mask() is a bit string of the family's width, and the leading bits of a + // 32-bit string can coincide with those of a 128-bit one (a00::1 and + // 10.0.0.0/8 both mask to 00001010), so the widths must agree before the + // strings are compared. + if (this.binaryZeroPad().length !== address.binaryZeroPad().length) { + return false; + } return this.mask(address.subnetMask) === address.mask(); } +/** + * Returns whether the registry marks this address globally reachable: the + * answer of the most specific entry containing it that has one, or `true` + * when no entry contains it. + */ +function isGloballyReachable(entries) { + let best = null; + for (let i = 0; i < entries.length; i++) { + const entry = entries[i]; + if (entry.reachable !== null && + isHostInSubnet.call(this, entry.subnet) && + (best === null || entry.subnet.subnetMask > best.subnet.subnetMask)) { + best = entry; + } + } + return best === null ? true : best.reachable; +} +/** + * Adds `n` to `value` and returns the result, throwing `AddressError` unless + * `n` is an integer and the result stays within `[0, 2**bits - 1]`. + */ +function offsetBigInt(value, n, bits, family) { + if (typeof n === 'number' && !Number.isSafeInteger(n)) { + throw new address_error_1.AddressError(`${family} offset must be an integer`); + } + if (typeof n !== 'number' && typeof n !== 'bigint') { + throw new address_error_1.AddressError(`${family} offset must be an integer`); + } + const result = value + BigInt(n); + if (result < BigInt(0) || result > (BigInt(1) << BigInt(bits)) - BigInt(1)) { + throw new address_error_1.AddressError(`${family} offset leaves the address space`); + } + return result; +} function isCorrect(defaultBits) { return function isCorrectForm() { if (this.addressMinusSuffix !== this.correctForm()) { diff --git a/node_modules/ip-address/dist/ipv4.js b/node_modules/ip-address/dist/ipv4.js index 1360e1836a0d3..4b9cf98b60849 100644 --- a/node_modules/ip-address/dist/ipv4.js +++ b/node_modules/ip-address/dist/ipv4.js @@ -48,7 +48,9 @@ class Address4 { */ this.isCorrect = isCorrect4; /** - * Returns true if the given address is in the subnet of the current address + * Returns true if the given address is in the subnet of the current address. + * An `Address6` is never in the subnet of an `Address4`; convert with + * `to4()` or `Address6.fromAddress4()` to compare across families. * @returns {boolean} */ this.isInSubnet = common.isInSubnet; @@ -58,6 +60,8 @@ class Address4 { * 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. + * An `Address6` is never in the subnet of an `Address4`; convert with + * `to4()` or `Address6.fromAddress4()` to compare across families. * @returns {boolean} */ this.isHostInSubnet = common.isHostInSubnet; @@ -72,6 +76,13 @@ class Address4 { } address = address.replace(constants.RE_SUBNET_STRING, ''); } + // Four three-digit octets and three dots: the longest well-formed address + // is 15 characters. Longer input is rejected before parsing, as Address6 + // does at its own limit. + const longest = constants.GROUPS * 4 - 1; + if (address.length > longest) { + throw new address_error_1.AddressError(`IPv4 addresses are at most ${longest} characters.`); + } this.addressMinusSuffix = address; this.parsedAddress = this.parse(address); } @@ -213,16 +224,27 @@ class Address4 { return Address4.fromHex(integer.toString(16).padStart(8, '0')); } /** - * Return an address from in-addr.arpa form + * Return an address from in-addr.arpa form: the four octets reversed, with + * or without the `.in-addr.arpa` suffix and root dot, in any case. Throws + * `AddressError` unless the reversed labels form a valid IPv4 address, so + * `fromArpa(x.reverseForm())` round-trips {@link reverseForm}. * @param {string} arpaFormAddress - an 'in-addr.arpa' form ipv4 address * @returns {Adress4} * @example - * var address = Address4.fromArpa(42.2.0.192.in-addr.arpa.) + * var address = Address4.fromArpa('42.2.0.192.in-addr.arpa.') * address.correctForm(); // '192.0.2.42' */ static fromArpa(arpaFormAddress) { - // remove ending ".in-addr.arpa." or just "." - const leader = arpaFormAddress.replace(/(\.in-addr\.arpa)?\.$/, ''); + // A 15-character address, a "/32" prefix length and ".in-addr.arpa.": the + // longest name is 32 characters. Longer input is rejected before its + // labels are split, as the constructor does at its own limit. + const longest = constants.GROUPS * 4 - 1 + '/32'.length + '.in-addr.arpa.'.length; + if (arpaFormAddress.length > longest) { + throw new address_error_1.AddressError(`in-addr.arpa names are at most ${longest} characters.`); + } + // remove an ending ".in-addr.arpa", in any case and with or without the + // root dot, as Address6.fromArpa does for ".ip6.arpa" + const leader = arpaFormAddress.replace(/(\.in-addr\.arpa)?\.?$/i, ''); const address = leader.split('.').reverse().join('.'); return new Address4(address); } @@ -285,6 +307,33 @@ class Address4 { const adjust = BigInt('1'); return Address4.fromBigInt(this._startAddress() + adjust); } + /** + * Returns the address `n` addresses after this one (or before, when `n` is + * negative), keeping this address's subnet mask. Throws `AddressError` when + * the result would fall outside the IPv4 address space or `n` is not an + * integer. + * @param {number | bigint} n + * @returns {Address4} + * @example + * new Address4('10.0.0.0/24').offset(1).correctForm(); // '10.0.0.1' + */ + offset(n) { + return Address4.fromBigInt(common.offsetBigInt(this.bigInt(), n, constants.BITS, 'IPv4')).withSubnetMask(this.subnetMask); + } + /** + * Returns the network that follows this address's network: the address after + * {@link endAddress}, with the same subnet mask. Throws `AddressError` when + * this network is the last one in the address space. + * @returns {Address4} + * @example + * new Address4('10.0.0.0/24').nextNetwork().networkForm(); // '10.0.1.0/24' + */ + nextNetwork() { + return Address4.fromBigInt(common.offsetBigInt(this._endAddress(), 1, constants.BITS, 'IPv4')).withSubnetMask(this.subnetMask); + } + withSubnetMask(subnetMask) { + return new Address4(`${this.correctForm()}/${subnetMask}`); + } /** * Helper function getting end address. * @returns {bigint} @@ -462,6 +511,43 @@ class Address4 { isCGNAT() { return this.isHostInSubnet(CGNAT_V4); } + /** + * Returns true if the address is in one of the documentation ranges + * `192.0.2.0/24`, `198.51.100.0/24`, or `203.0.113.0/24` ([RFC 5737](https://datatracker.ietf.org/doc/html/rfc5737)). + * @returns {boolean} + */ + isDocumentation() { + return DOCUMENTATION_V4.some((subnet) => this.isHostInSubnet(subnet)); + } + /** + * Returns true if the address is in the benchmarking range `198.18.0.0/15` ([RFC 2544](https://datatracker.ietf.org/doc/html/rfc2544)). + * @returns {boolean} + */ + isBenchmarking() { + return this.isHostInSubnet(BENCHMARKING_V4); + } + /** + * Returns true if the address is in the reserved range `240.0.0.0/4` ([RFC 1112](https://datatracker.ietf.org/doc/html/rfc1112)), + * which includes the limited broadcast address. + * @returns {boolean} + */ + isReserved() { + return this.isHostInSubnet(RESERVED_V4); + } + /** + * Returns true if the address is globally reachable: not multicast, and not + * in any block the [IANA IPv4 Special-Purpose Address Registry](https://www.iana.org/assignments/iana-ipv4-special-registry/) + * marks as not globally reachable. That covers everything the individual + * classifiers name (private, loopback, link-local, CGNAT, unspecified, + * broadcast, documentation, benchmarking, reserved) and the blocks they do + * not, such as `0.0.0.0/8` and the IETF protocol assignments in + * `192.0.0.0/24`. This is the single predicate to use where a request must + * not reach an internal or special-purpose destination; see SECURITY.md. + * @returns {boolean} + */ + isGlobal() { + return !this.isMulticast() && common.isGloballyReachable.call(this, SPECIAL_PURPOSE_V4); + } /** * Returns a zero-padded base-2 string representation of the address * @returns {string} @@ -502,4 +588,15 @@ const LINK_LOCAL_V4 = new Address4('169.254.0.0/16'); const UNSPECIFIED_V4 = new Address4('0.0.0.0/32'); const BROADCAST_V4 = new Address4('255.255.255.255/32'); const CGNAT_V4 = new Address4('100.64.0.0/10'); +const DOCUMENTATION_V4 = [ + new Address4('192.0.2.0/24'), + new Address4('198.51.100.0/24'), + new Address4('203.0.113.0/24'), +]; +const BENCHMARKING_V4 = new Address4('198.18.0.0/15'); +const RESERVED_V4 = new Address4('240.0.0.0/4'); +const SPECIAL_PURPOSE_V4 = constants.SPECIAL_PURPOSE.map(([cidr, , reachable]) => ({ + subnet: new Address4(cidr), + reachable, +})); //# sourceMappingURL=ipv4.js.map \ No newline at end of file diff --git a/node_modules/ip-address/dist/ipv6.js b/node_modules/ip-address/dist/ipv6.js index d5f4fdb9c87a3..b1f16e2970e93 100644 --- a/node_modules/ip-address/dist/ipv6.js +++ b/node_modules/ip-address/dist/ipv6.js @@ -92,7 +92,9 @@ class Address6 { this.zone = ''; // #region Attributes /** - * Returns true if the given address is in the subnet of the current address + * Returns true if the given address is in the subnet of the current address. + * An `Address4` is never in the subnet of an `Address6`; convert with + * `Address6.fromAddress4()` or `to4()` to compare across families. * @returns {boolean} */ this.isInSubnet = common.isInSubnet; @@ -102,6 +104,8 @@ class Address6 { * 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. + * An `Address4` is never in the subnet of an `Address6`; convert with + * `Address6.fromAddress4()` or `to4()` to compare across families. * @returns {boolean} */ this.isHostInSubnet = common.isHostInSubnet; @@ -140,6 +144,16 @@ class Address6 { this.zone = zone[0]; address = address.replace(constants6.RE_ZONE_STRING, ''); } + // The longest well-formed address is all but the last two groups written + // as four hex digits with their colons, then a 15-character dotted quad: + // 5 * (groups - 2) + 15, which is 45 for eight groups, the same line + // CPython's ipaddress module draws. Rejecting longer input here keeps the + // parse diagnostics, which wrap every offending character in a span, + // proportional to an address rather than to whatever was passed in. + const longest = this.groups * 5 + 5; + if (address.length > longest) { + throw new address_error_1.AddressError(`IPv6 addresses are at most ${longest} characters.`); + } this.addressMinusSuffix = address; this.parsedAddress = this.parse(this.addressMinusSuffix); } @@ -339,28 +353,32 @@ class Address6 { return new Address6(`::ffff:${address4.correctForm()}/${mask6}`); } /** - * Return an address from ip6.arpa form + * Return an address from ip6.arpa form. A full 32-nibble name gives a /128 + * address; a shorter name, as used for a delegated reverse zone, gives the + * network it covers, with a subnet mask of four bits per nibble, so + * `fromArpa(x.reverseForm())` round-trips {@link reverseForm} for any prefix. * @param {string} arpaFormAddress - an 'ip6.arpa' form address * @returns {Adress6} * @example * 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.) * address.correctForm(); // '2001:0:ce49:7601:e866:efff:62c3:fffe' + * Address6.fromArpa('8.b.d.0.1.0.0.2.ip6.arpa.').networkForm(); // '2001:db8::/32' */ static fromArpa(arpaFormAddress) { - // remove ending ".ip6.arpa." or just "." - let address = arpaFormAddress.replace(/(\.ip6\.arpa)?\.$/, ''); - const semicolonAmount = 7; - // correct ip6.arpa form with ending removed will be 63 characters - if (address.length !== 63) { + // remove an ending ".ip6.arpa", in any case and with or without the root + // dot + const nibbles = arpaFormAddress.replace(/(\.ip6\.arpa)?\.?$/i, ''); + if (!/^[0-9a-f](\.[0-9a-f]){0,31}$/i.test(nibbles)) { throw new address_error_1.AddressError("Invalid 'ip6.arpa' form."); } - const parts = address.split('.').reverse(); - for (let i = semicolonAmount; i > 0; i--) { - const insertIndex = i * 4; - parts.splice(insertIndex, 0, ':'); + const reversed = nibbles.split('.').reverse(); + const subnetMask = reversed.length * 4; + const hex = reversed.join('').padEnd(32, '0'); + const groups = []; + for (let i = 0; i < constants6.GROUPS; i++) { + groups.push(hex.slice(i * 4, (i + 1) * 4)); } - address = parts.join(''); - return new Address6(address); + return new Address6(`${groups.join(':')}/${subnetMask}`); } /** * Return the Microsoft UNC transcription of the address @@ -424,22 +442,53 @@ class Address6 { return BigInt(`0b${this.mask() + '1'.repeat(constants6.BITS - this.subnetMask)}`); } /** - * The last address in the range given by this address' subnet - * Often referred to as the Broadcast + * The last address in the range given by this address's subnet. IPv6 has + * no broadcast address, so this is an ordinary assignable address (in a + * 64-bit-interface-identifier subnet it falls inside the reserved + * subnet-anycast block of [RFC 2526](https://datatracker.ietf.org/doc/html/rfc2526)). * @returns {Address6} */ endAddress() { return Address6.fromBigInt(this._endAddress()); } /** - * The last host address in the range given by this address's subnet ie - * the last address prior to the Broadcast Address + * The address one before {@link endAddress}. This is the IPv6 counterpart + * of the IPv4 method that skips the broadcast address; IPv6 has no broadcast, + * so it drops exactly one address and does not model the 128 reserved + * subnet-anycast identifiers of [RFC 2526](https://datatracker.ietf.org/doc/html/rfc2526). * @returns {Address6} */ endAddressExclusive() { const adjust = BigInt('1'); return Address6.fromBigInt(this._endAddress() - adjust); } + /** + * Returns the address `n` addresses after this one (or before, when `n` is + * negative), keeping this address's subnet mask. Throws `AddressError` when + * the result would fall outside the IPv6 address space or `n` is not an + * integer. + * @param {number | bigint} n + * @returns {Address6} + * @example + * new Address6('2001:db8::/64').offset(1).correctForm(); // '2001:db8::1' + */ + offset(n) { + return Address6.fromBigInt(common.offsetBigInt(this.bigInt(), n, constants6.BITS, 'IPv6')).withSubnetMask(this.subnetMask); + } + /** + * Returns the network that follows this address's network: the address after + * {@link endAddress}, with the same subnet mask. Throws `AddressError` when + * this network is the last one in the address space. + * @returns {Address6} + * @example + * new Address6('2001:db8::/64').nextNetwork().networkForm(); // '2001:db8:0:1::/64' + */ + nextNetwork() { + return Address6.fromBigInt(common.offsetBigInt(this._endAddress(), 1, constants6.BITS, 'IPv6')).withSubnetMask(this.subnetMask); + } + withSubnetMask(subnetMask) { + return new Address6(`${this.correctForm()}/${subnetMask}`); + } /** * The hex form of the subnet mask, e.g. `ffff:ffff:ffff:ffff::` for a * `/64`. Returns an `Address6`; call `.correctForm()` for the string. @@ -1022,7 +1071,10 @@ class Address6 { return this.addressMinusSuffix === this.canonicalForm(); } /** - * Returns true if the address is a link local address, false otherwise + * Returns true if the address is a link-local unicast address in `fe80::/10` + * ([RFC 4291 ยง2.4](https://datatracker.ietf.org/doc/html/rfc4291#section-2.4)) + * or an IPv4-mapped / NAT64 address whose embedded IPv4 address is link-local + * (`169.254.0.0/16`, e.g. `::ffff:169.254.169.254`), false otherwise. * @returns {boolean} */ isLinkLocal() { @@ -1030,12 +1082,7 @@ class Address6 { 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; - } - return false; + return this.isHostInSubnet(LINK_LOCAL_SUBNET); } /** * Returns true if the address is a multicast address, false otherwise @@ -1127,12 +1174,20 @@ class Address6 { } /** * 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 + * `fc00::/7` ([RFC 4193](https://datatracker.ietf.org/doc/html/rfc4193)), an + * address in the NAT64 local-use range `64:ff9b:1::/48` + * ([RFC 8215](https://datatracker.ietf.org/doc/html/rfc8215)), or an + * IPv4-mapped / NAT64 well-known 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. + * + * The local-use NAT64 range is reported private as a whole rather than by + * its embedded IPv4 address: an operator may carve a prefix of any RFC 6052 + * length out of `64:ff9b:1::/48`, so the same bits decode to different IPv4 + * addresses under different deployments and no single decoding is correct. + * Use {@link toAddress4Nat64} with the deployment's prefix to decode one. * @returns {boolean} */ isPrivate() { @@ -1140,7 +1195,7 @@ class Address6 { if (embedded) { return embedded.isPrivate(); } - return this.isULA(); + return this.isULA() || this.isHostInSubnet(NAT64_LOCAL_USE_SUBNET); } /** * Returns true if the address is an IPv4-mapped / NAT64 address whose embedded @@ -1188,7 +1243,49 @@ class Address6 { * @returns {boolean} */ isDocumentation() { - return this.isHostInSubnet(DOCUMENTATION_SUBNET); + return DOCUMENTATION_SUBNETS.some((subnet) => this.isHostInSubnet(subnet)); + } + /** + * Returns true if the address is in the benchmarking range `2001:2::/48` + * ([RFC 5180](https://datatracker.ietf.org/doc/html/rfc5180)) or is an + * IPv4-mapped / NAT64 address whose embedded IPv4 address is in + * `198.18.0.0/15`, false otherwise. + * @returns {boolean} + */ + isBenchmarking() { + const embedded = this.embeddedIPv4(); + if (embedded) { + return embedded.isBenchmarking(); + } + return this.isHostInSubnet(BENCHMARKING_SUBNET); + } + /** + * Returns true if the address is globally reachable: inside the global + * unicast allocation `2000::/3` (the only range the [IANA IPv6 Address Space + * Registry](https://www.iana.org/assignments/ipv6-address-space/) assigns + * for global unicast; everything else is reserved, ULA, link-local, or + * multicast) and not in any block the [IANA IPv6 Special-Purpose Address Registry](https://www.iana.org/assignments/iana-ipv6-special-registry/) + * marks as not globally reachable. An IPv4-mapped or NAT64 well-known + * address answers for its embedded IPv4 address, so `::ffff:10.0.0.1` and + * `64:ff9b::7f00:1` are not global. Teredo (`2001::/32`) and 6to4 + * (`2002::/16`) are not global either: the registry lists them as N/A and a + * packet to one needs a relay. + * + * This covers everything the individual classifiers name and the blocks they + * do not: the discard-only prefix `100::/64`, the IETF protocol assignments + * in `2001::/23`, the deprecated site-local `fec0::/10` and IPv4-compatible + * `::/96` ranges, and unallocated space such as `4000::/3`. It is the single + * predicate to use where a request must not reach an internal or + * special-purpose destination; see SECURITY.md. + * @returns {boolean} + */ + isGlobal() { + const embedded = this.embeddedIPv4(); + if (embedded) { + return embedded.isGlobal(); + } + return (this.isHostInSubnet(GLOBAL_UNICAST_SUBNET) && + common.isGloballyReachable.call(this, SPECIAL_PURPOSE_V6)); } // #endregion // #region HTML @@ -1347,7 +1444,15 @@ const TYPE_SUBNETS = Object.keys(constants6.TYPES).map((subnet) => [ const TEREDO_SUBNET = new Address6('2001::/32'); 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 LINK_LOCAL_SUBNET = new Address6('fe80::/10'); +const DOCUMENTATION_SUBNETS = [new Address6('2001:db8::/32'), new Address6('3fff::/20')]; +const BENCHMARKING_SUBNET = new Address6('2001:2::/48'); +const GLOBAL_UNICAST_SUBNET = new Address6('2000::/3'); +const SPECIAL_PURPOSE_V6 = constants6.SPECIAL_PURPOSE.map(([cidr, , reachable]) => ({ + subnet: new Address6(cidr), + reachable, +})); const IPV4_MAPPED_SUBNET = new Address6('::ffff:0:0/96'); const NAT64_WELL_KNOWN_SUBNET = new Address6('64:ff9b::/96'); +const NAT64_LOCAL_USE_SUBNET = new Address6('64:ff9b:1::/48'); //# sourceMappingURL=ipv6.js.map \ No newline at end of file diff --git a/node_modules/ip-address/dist/v4/constants.js b/node_modules/ip-address/dist/v4/constants.js index 158288b7de656..f25120d60687a 100644 --- a/node_modules/ip-address/dist/v4/constants.js +++ b/node_modules/ip-address/dist/v4/constants.js @@ -1,6 +1,6 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.RE_SUBNET_STRING = exports.RE_ADDRESS = exports.GROUPS = exports.BITS = void 0; +exports.SPECIAL_PURPOSE = exports.RE_SUBNET_STRING = exports.RE_ADDRESS = exports.GROUPS = exports.BITS = void 0; exports.BITS = 32; exports.GROUPS = 4; // Each octet is 0-255 written without a leading zero. A leading zero is @@ -9,4 +9,43 @@ exports.GROUPS = 4; // 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}$/; +/** + * The IANA IPv4 Special-Purpose Address Registry + * (https://www.iana.org/assignments/iana-ipv4-special-registry/), one entry + * per block: `[cidr, name, globallyReachable]`. A `null` reachability means + * the registry leaves the column blank and the block inherits the answer of + * the block containing it (or is global when nothing contains it). + * + * `Address4.isGlobal()` answers from the most specific entry containing the + * address. `test/data/iana-corpus.json` is generated from the registry's CSV + * and pins this table to it. + */ +exports.SPECIAL_PURPOSE = [ + ['0.0.0.0/8', 'This network', false], + ['0.0.0.0/32', 'This host on this network', false], + ['10.0.0.0/8', 'Private-Use', false], + ['100.64.0.0/10', 'Shared Address Space', false], + ['127.0.0.0/8', 'Loopback', false], + ['169.254.0.0/16', 'Link Local', false], + ['172.16.0.0/12', 'Private-Use', false], + ['192.0.0.0/24', 'IETF Protocol Assignments', false], + ['192.0.0.0/29', 'IPv4 Service Continuity Prefix', false], + ['192.0.0.8/32', 'IPv4 dummy address', false], + ['192.0.0.9/32', 'Port Control Protocol Anycast', true], + ['192.0.0.10/32', 'Traversal Using Relays around NAT Anycast', true], + ['192.0.0.170/32', 'NAT64/DNS64 Discovery', false], + ['192.0.0.171/32', 'NAT64/DNS64 Discovery', false], + ['192.0.2.0/24', 'Documentation (TEST-NET-1)', false], + ['192.31.196.0/24', 'AS112-v4', true], + ['192.52.193.0/24', 'AMT', true], + ['192.88.99.0/24', 'Deprecated (6to4 Relay Anycast)', null], + ['192.88.99.2/32', '6a44-relay anycast address', false], + ['192.168.0.0/16', 'Private-Use', false], + ['192.175.48.0/24', 'Direct Delegation AS112 Service', true], + ['198.18.0.0/15', 'Benchmarking', false], + ['198.51.100.0/24', 'Documentation (TEST-NET-2)', false], + ['203.0.113.0/24', 'Documentation (TEST-NET-3)', false], + ['240.0.0.0/4', 'Reserved', false], + ['255.255.255.255/32', 'Limited Broadcast', false], +]; //# sourceMappingURL=constants.js.map \ No newline at end of file diff --git a/node_modules/ip-address/dist/v6/constants.js b/node_modules/ip-address/dist/v6/constants.js index 4616cad66b6a6..84e121300a676 100644 --- a/node_modules/ip-address/dist/v6/constants.js +++ b/node_modules/ip-address/dist/v6/constants.js @@ -1,6 +1,6 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.RE_URL_WITH_PORT = exports.RE_URL = exports.RE_ZONE_STRING = exports.RE_SUBNET_STRING = exports.RE_BAD_ADDRESS = exports.RE_BAD_CHARACTERS = exports.TYPES = exports.SCOPES = exports.GROUPS = exports.BITS = void 0; +exports.SPECIAL_PURPOSE = exports.RE_URL_WITH_PORT = exports.RE_URL = exports.RE_ZONE_STRING = exports.RE_SUBNET_STRING = exports.RE_BAD_ADDRESS = exports.RE_BAD_CHARACTERS = exports.TYPES = exports.SCOPES = exports.GROUPS = exports.BITS = void 0; exports.BITS = 128; exports.GROUPS = 8; /** @@ -48,8 +48,14 @@ exports.TYPES = { 'ff00::/8': 'Multicast', 'fe80::/10': 'Link-local unicast', 'fc00::/7': 'Unique local', + '2001::/32': 'Teredo', + '2001:2::/48': 'Benchmarking', '2002::/16': '6to4', '2001:db8::/32': 'Documentation', + '3fff::/20': 'Documentation', + '100::/64': 'Discard-only', + 'fec0::/10': 'Site-local unicast (deprecated)', + '::/96': 'IPv4-compatible (deprecated)', '64:ff9b::/96': 'NAT64 (well-known)', '64:ff9b:1::/48': 'NAT64 (local-use)', }; @@ -79,4 +85,44 @@ exports.RE_SUBNET_STRING = /\/\d{1,3}(?=%|$)/; exports.RE_ZONE_STRING = /%.*$/; exports.RE_URL = /^(?:\[([0-9a-f:.]+)\]|([0-9a-f:.]+))(?:[/?#].*)?$/i; exports.RE_URL_WITH_PORT = /^\[([0-9a-f:.]+)\]:([0-9]{1,5})(?:[/?#].*)?$/i; +/** + * The IANA IPv6 Special-Purpose Address Registry + * (https://www.iana.org/assignments/iana-ipv6-special-registry/), one entry + * per block: `[cidr, name, globallyReachable]`. A `null` reachability means + * the registry says N/A or leaves the column blank; N/A blocks (Teredo, 6to4) + * are treated as not globally reachable, since a packet to one needs a relay, + * and blank blocks inherit the answer of the block containing them. + * + * `Address6.isGlobal()` answers from the most specific entry containing the + * address, after delegating IPv4-mapped and NAT64 well-known addresses to the + * embedded IPv4 address. `test/data/iana-corpus.json` is generated from the + * registry's CSV and pins this table to it. + */ +exports.SPECIAL_PURPOSE = [ + ['::1/128', 'Loopback Address', false], + ['::/128', 'Unspecified Address', false], + ['::ffff:0:0/96', 'IPv4-mapped Address', false], + ['64:ff9b::/96', 'IPv4-IPv6 Translat.', true], + ['64:ff9b:1::/48', 'IPv4-IPv6 Translat.', false], + ['100::/64', 'Discard-Only Address Block', false], + ['100:0:0:1::/64', 'Dummy IPv6 Prefix', false], + ['2001::/23', 'IETF Protocol Assignments', false], + ['2001::/32', 'TEREDO', false], + ['2001:1::1/128', 'Port Control Protocol Anycast', true], + ['2001:1::2/128', 'Traversal Using Relays around NAT Anycast', true], + ['2001:1::3/128', 'DNS-SD Service Registration Protocol Anycast', true], + ['2001:2::/48', 'Benchmarking', false], + ['2001:3::/32', 'AMT', true], + ['2001:4:112::/48', 'AS112-v6', true], + ['2001:10::/28', 'Deprecated (previously ORCHID)', null], + ['2001:20::/28', 'ORCHIDv2', true], + ['2001:30::/28', 'Drone Remote ID Protocol Entity Tags (DETs) Prefix', true], + ['2001:db8::/32', 'Documentation', false], + ['2002::/16', '6to4', false], + ['2620:4f:8000::/48', 'Direct Delegation AS112 Service', true], + ['3fff::/20', 'Documentation', false], + ['5f00::/16', 'Segment Routing (SRv6) SIDs', false], + ['fc00::/7', 'Unique-Local', false], + ['fe80::/10', 'Link-Local Unicast', false], +]; //# sourceMappingURL=constants.js.map \ No newline at end of file diff --git a/node_modules/ip-address/package.json b/node_modules/ip-address/package.json index 6ea2d24bd62bb..a4e50d2d2cff7 100644 --- a/node_modules/ip-address/package.json +++ b/node_modules/ip-address/package.json @@ -16,7 +16,7 @@ "bigint", "browser" ], - "version": "10.5.0", + "version": "10.7.3", "author": "Beau Gunderson (https://beaugunderson.com/)", "license": "MIT", "main": "dist/ip-address.js", diff --git a/node_modules/postcss-selector-parser/dist/parser.js b/node_modules/postcss-selector-parser/dist/parser.js index d462e71aad7a3..50cf16a86feae 100644 --- a/node_modules/postcss-selector-parser/dist/parser.js +++ b/node_modules/postcss-selector-parser/dist/parser.js @@ -154,8 +154,9 @@ function indexesOf(array, item) { return indexes; } function uniqs() { - var list = Array.prototype.concat.apply([], arguments); - return list.filter(function (item, i) { return i === list.indexOf(item); }); + // A Set keeps the first-occurrence order the previous filter/indexOf pass + // produced, without its quadratic cost on long index lists. + return Array.from(new Set(Array.prototype.concat.apply([], arguments))); } var Parser = /** @class */ (function () { function Parser(rule, options) { @@ -200,6 +201,11 @@ var Parser = /** @class */ (function () { attr.push(this.currToken); this.position++; } + if (!this.currToken) { + // Ran off the end of the token stream: the attribute was never closed. + // Point at the opening bracket, which is where the author needs to look. + return this.expected("closing square bracket", startingToken[tokenize_1.FIELDS.START_POS]); + } if (this.currToken[tokenize_1.FIELDS.TYPE] !== tokens.closeSquare) { return this.expected("closing square bracket", this.currToken[tokenize_1.FIELDS.START_POS]); } @@ -260,7 +266,7 @@ var Parser = /** @class */ (function () { } if (commentBefore) { (0, util_1.ensureObject)(node, "raws", "spaces", "attribute"); - node.raws.spaces.attribute.before = spaceBefore; + node.raws.spaces.attribute.before = commentBefore; commentBefore = ""; } node.namespace = (node.namespace || "") + content; @@ -686,7 +692,21 @@ var Parser = /** @class */ (function () { return this.error("Unexpected '|'.", this.currToken[tokenize_1.FIELDS.START_POS]); }; Parser.prototype.namespace = function () { - var before = (this.prevToken && this.content(this.prevToken)) || true; + var prev = this.prevToken; + // Only treat the previous token as a namespace prefix when it can actually + // be one (a type/word or the universal `*`). A comment, comma, combinator + // or whitespace before `|` means an empty namespace, not a prefix. + var before = prev && + (prev[tokenize_1.FIELDS.TYPE] === tokens.word || + prev[tokenize_1.FIELDS.TYPE] === tokens.asterisk || + prev[tokenize_1.FIELDS.TYPE] === tokens.ampersand) + ? this.content(prev) + : true; + if (!this.nextToken) { + // A trailing `|` with nothing after it. `unexpectedPipe` reports against + // `currToken`, which is the pipe itself and always present here. + return this.unexpectedPipe(); + } if (this.nextToken[tokenize_1.FIELDS.TYPE] === tokens.word) { this.position++; return this.word(before); @@ -716,6 +736,7 @@ var Parser = /** @class */ (function () { Parser.prototype.parentheses = function () { var last = this.current.last; var unbalanced = 1; + var openingToken = this.currToken; this.position++; if (last && last.type === types.PSEUDO) { var selector = new selector_1.default({ @@ -785,7 +806,9 @@ var Parser = /** @class */ (function () { } } if (unbalanced) { - return this.expected("closing parenthesis", this.currToken[tokenize_1.FIELDS.START_POS]); + // `currToken` is undefined when the token stream ran out before the + // parenthesis was closed; fall back to the opening one. + return this.expected("closing parenthesis", (this.currToken || openingToken)[tokenize_1.FIELDS.START_POS]); } }; Parser.prototype.pseudo = function () { @@ -890,8 +913,14 @@ var Parser = /** @class */ (function () { // Eliminate Sass interpolations from the list of id indexes var interpolations = indexesOf(word, "#{"); if (interpolations.length) { - hasId = hasId.filter(function (hashIndex) { return !~interpolations.indexOf(hashIndex); }); + var interpolationIndexes_1 = new Set(interpolations); + hasId = hasId.filter(function (hashIndex) { return !interpolationIndexes_1.has(hashIndex); }); } + // Membership tests are built once. Scanning the arrays per index would make + // a flat selector such as `.a.a.a...` quadratic in its number of class/id + // indexes, which is enough to pin a CPU core on attacker-supplied input. + var classIndexes = new Set(hasClass); + var idIndexes = new Set(hasId); var indices = (0, sortAscending_1.default)(uniqs(__spreadArray(__spreadArray([0], __read(hasClass), false), __read(hasId), false))); indices.forEach(function (ind, i) { var index = indices[i + 1] || word.length; @@ -903,7 +932,7 @@ var Parser = /** @class */ (function () { var current = _this.currToken; var sourceIndex = current[tokenize_1.FIELDS.START_POS] + indices[i]; var source = getSource(current[1], current[2] + ind, current[3], current[2] + (index - 1)); - if (~hasClass.indexOf(ind)) { + if (classIndexes.has(ind)) { var classNameOpts = { value: value.slice(1), source: source, @@ -911,7 +940,7 @@ var Parser = /** @class */ (function () { }; node = new className_1.default(unescapeProp(classNameOpts, "value")); } - else if (~hasId.indexOf(ind)) { + else if (idIndexes.has(ind)) { var idOpts = { value: value.slice(1), source: source, diff --git a/node_modules/postcss-selector-parser/package.json b/node_modules/postcss-selector-parser/package.json index 12b12199b4365..5a7f19963ffd5 100644 --- a/node_modules/postcss-selector-parser/package.json +++ b/node_modules/postcss-selector-parser/package.json @@ -1,7 +1,7 @@ { "name": "postcss-selector-parser", "description": "Selector parser with built in methods for working with selector strings.", - "version": "7.1.4", + "version": "7.1.6", "devDependencies": { "oxfmt": "^0.54.0", "oxlint": "^1.69.0", @@ -45,6 +45,10 @@ }, "homepage": "https://github.com/postcss/postcss-selector-parser", "contributors": [ + { + "name": "Maxime Thirouin", + "url": "https://moox.io" + }, { "name": "Ben Briggs", "email": "beneb.info@gmail.com", @@ -52,8 +56,7 @@ }, { "name": "Chris Eppstein", - "email": "chris@eppsteins.net", - "url": "http://twitter.com/chriseppstein" + "email": "chris@eppsteins.net" } ], "repository": { diff --git a/package-lock.json b/package-lock.json index ec6ec30dd0654..01332e607ab23 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1161,9 +1161,9 @@ "peer": true }, "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.18", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", - "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", "dev": true, "license": "MIT", "peer": true, @@ -1403,9 +1403,9 @@ "peer": true }, "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { - "version": "1.1.18", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", - "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", "dev": true, "license": "MIT", "peer": true, @@ -3675,9 +3675,9 @@ "license": "ISC" }, "node_modules/brace-expansion": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", - "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", "inBundle": true, "license": "MIT", "dependencies": { @@ -5227,9 +5227,9 @@ "peer": true }, "node_modules/eslint-plugin-import/node_modules/brace-expansion": { - "version": "1.1.18", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", - "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", "dev": true, "license": "MIT", "peer": true, @@ -5311,9 +5311,9 @@ "peer": true }, "node_modules/eslint-plugin-node/node_modules/brace-expansion": { - "version": "1.1.18", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", - "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", "dev": true, "license": "MIT", "peer": true, @@ -5439,9 +5439,9 @@ "peer": true }, "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.18", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", - "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", "dev": true, "license": "MIT", "peer": true, @@ -5832,9 +5832,9 @@ "peer": true }, "node_modules/flat-cache/node_modules/brace-expansion": { - "version": "1.1.18", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", - "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", "dev": true, "license": "MIT", "peer": true, @@ -6710,9 +6710,9 @@ } }, "node_modules/ip-address": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.5.0.tgz", - "integrity": "sha512-R5SnVLJmgYYvf2F2ZgwSBnelz5G4q5AxIC277GDfUaNbrZKNANcBC7RHqYYePlszf4kBolVkJauG0ZjHHFh55g==", + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.3.tgz", + "integrity": "sha512-A1kdq/tSb5QjvKvAMgIoEvDBIgL7qaqVP/jkvSwYYRZ9iEzvPpopxp2wQfu3SuZRHtpHNxMn8Fs0bS+gf5Xmwg==", "inBundle": true, "license": "MIT", "engines": { @@ -7296,9 +7296,9 @@ "license": "MIT" }, "node_modules/istanbul-lib-processinfo/node_modules/brace-expansion": { - "version": "1.1.18", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", - "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", "dev": true, "license": "MIT", "dependencies": { @@ -9321,9 +9321,9 @@ "license": "MIT" }, "node_modules/nyc/node_modules/brace-expansion": { - "version": "1.1.18", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", - "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", "dev": true, "license": "MIT", "dependencies": { @@ -9880,9 +9880,9 @@ } }, "node_modules/postcss-selector-parser": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.4.tgz", - "integrity": "sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg==", + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz", + "integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -11216,9 +11216,9 @@ "license": "MIT" }, "node_modules/spawn-wrap/node_modules/brace-expansion": { - "version": "1.1.18", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", - "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", "dev": true, "license": "MIT", "dependencies": { @@ -11669,9 +11669,9 @@ "license": "MIT" }, "node_modules/tap-mocha-reporter/node_modules/brace-expansion": { - "version": "1.1.18", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", - "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", "dev": true, "license": "MIT", "dependencies": { @@ -13860,9 +13860,9 @@ "license": "MIT" }, "node_modules/test-exclude/node_modules/brace-expansion": { - "version": "1.1.18", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", - "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", "dev": true, "license": "MIT", "dependencies": {