Compare commits

..

2 Commits

Author SHA1 Message Date
HarithaVattikuti
bb4b67e4f6 Update License 2026-10-08 11:46:25 -05:00
dependabot[bot]
f4c8249567 Bump brace-expansion from 5.0.9 to 5.0.12
Bumps [brace-expansion](https://github.com/juliangruber/brace-expansion) from 5.0.9 to 5.0.12.
- [Release notes](https://github.com/juliangruber/brace-expansion/releases)
- [Commits](https://github.com/juliangruber/brace-expansion/compare/v5.0.9...v5.0.12)

---
updated-dependencies:
- dependency-name: brace-expansion
  dependency-version: 5.0.12
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-10-05 12:48:21 +00:00
4 changed files with 210 additions and 78 deletions

View File

@@ -1,6 +1,6 @@
--- ---
name: brace-expansion name: brace-expansion
version: 5.0.9 version: 5.0.12
type: npm type: npm
summary: Brace expansion as known from sh/bash summary: Brace expansion as known from sh/bash
homepage: homepage:

View File

@@ -41078,7 +41078,7 @@ exports.range = range;
Object.defineProperty(exports, "__esModule", ({ value: true })); 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; exports.expand = expand;
const balanced_match_1 = __nccwpck_require__(2649); const balanced_match_1 = __nccwpck_require__(2649);
const escSlash = '\0SLASH' + Math.random() + '\0'; const escSlash = '\0SLASH' + Math.random() + '\0';
@@ -41108,6 +41108,24 @@ exports.EXPANSION_MAX = 100_000;
// realistic expansion (100k results hitting `EXPANSION_MAX` measure ~1M // realistic expansion (100k results hitting `EXPANSION_MAX` measure ~1M
// characters) so legitimate input is unaffected. // characters) so legitimate input is unaffected.
exports.EXPANSION_MAX_LENGTH = 4_000_000; 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) { function numeric(str) {
return !isNaN(str) ? parseInt(str, 10) : str.charCodeAt(0); return !isNaN(str) ? parseInt(str, 10) : str.charCodeAt(0);
} }
@@ -41127,37 +41145,52 @@ function unescapeBraces(str) {
.replace(escCommaPattern, ',') .replace(escCommaPattern, ',')
.replace(escPeriodPattern, '.'); .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 * Basically just str.split(","), but handling cases
* where we have nested braced sections, which should be * where we have nested braced sections, which should be
* treated as individual members, like {a,{b,c},d} * treated as individual members, like {a,{b,c},d}
*/ */
function parseCommaParts(str) { function parseCommaParts(str) {
if (!str) {
return [''];
}
const parts = []; const parts = [];
// 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); const m = (0, balanced_match_1.balanced)('{', '}', str);
if (!m) { if (!m) {
return str.split(','); const tail = str.split(',');
tail[0] = carry + tail[0];
pushAll(parts, tail);
return parts;
} }
const { pre, body, post } = m; const { pre, body, post } = m;
const p = pre.split(','); const p = pre.split(',');
p[0] = carry + p[0];
p[p.length - 1] += '{' + body + '}'; p[p.length - 1] += '{' + body + '}';
const postParts = parseCommaParts(post); if (!post.length) {
if (post.length) { pushAll(parts, p);
;
p[p.length - 1] += postParts.shift();
p.push.apply(p, postParts);
}
parts.push.apply(parts, p);
return parts; return parts;
} }
carry = p.pop();
pushAll(parts, p);
str = post;
}
}
function expand(str, options = {}) { function expand(str, options = {}) {
if (!str) { if (!str) {
return []; 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. // I don't know why Bash 4.3 does this, but it does.
// Anything starting with {} will have the first two bytes preserved // Anything starting with {} will have the first two bytes preserved
// but *only* at the top level, so {},a}b will not expand to anything, // but *only* at the top level, so {},a}b will not expand to anything,
@@ -41167,7 +41200,7 @@ function expand(str, options = {}) {
if (str.slice(0, 2) === '{}') { if (str.slice(0, 2) === '{}') {
str = '\\{\\}' + str.slice(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) { function embrace(str) {
return '{' + str + '}'; return '{' + str + '}';
@@ -41262,7 +41295,13 @@ function expandSequence(body, isAlphaSequence, max, maxLength) {
} }
return N; 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 // Consume the string's top-level brace groups left to right, threading a
// running set of combined prefixes (`acc`). Expanding the tail iteratively - // running set of combined prefixes (`acc`). Expanding the tail iteratively -
// rather than recursing on `m.post` once per group - keeps the native stack // rather than recursing on `m.post` once per group - keeps the native stack
@@ -41274,6 +41313,9 @@ function expand_(str, max, maxLength, isTop) {
// comma set - a sequence like `{a..\}` may legitimately yield ''. The drop // comma set - a sequence like `{a..\}` may legitimately yield ''. The drop
// is on the final strings, so it is applied to whichever `combine` produces // is on the final strings, so it is applied to whichever `combine` produces
// them (the one with no brace set left in the tail). // 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 dropEmpties = false;
let firstGroup = true; let firstGroup = true;
for (;;) { for (;;) {
@@ -41298,7 +41340,8 @@ function expand_(str, max, maxLength, isTop) {
const isOptions = m.body.indexOf(',') >= 0; const isOptions = m.body.indexOf(',') >= 0;
if (!isSequence && !isOptions) { if (!isSequence && !isOptions) {
// {a},b} // {a},b}
if (m.post.match(/,(?!,).*\}/)) { if (rewrites < maxRewrites && m.post.match(/,(?!,).*\}/)) {
rewrites++;
str = m.pre + '{' + m.body + escClose + m.post; str = m.pre + '{' + m.body + escClose + m.post;
isTop = true; isTop = true;
continue; continue;
@@ -41318,7 +41361,7 @@ function expand_(str, max, maxLength, isTop) {
let n = parseCommaParts(m.body); let n = parseCommaParts(m.body);
if (n.length === 1 && n[0] !== undefined) { if (n.length === 1 && n[0] !== undefined) {
// x{{a,b}}y ==> x{a}y x{b}y // 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. //XXX is this necessary? Can't seem to hit it in tests.
/* c8 ignore start */ /* c8 ignore start */
if (n.length === 1) { if (n.length === 1) {
@@ -41344,12 +41387,13 @@ function expand_(str, max, maxLength, isTop) {
values = []; values = [];
let valuesLength = 0; let valuesLength = 0;
outer: for (let j = 0; j < n.length; j++) { 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++) { for (let k = 0; k < expanded.length; k++) {
const v = expanded[k]; const v = expanded[k];
if (dropsEmpties && !v) if (dropsEmpties && !v)
continue; continue;
if (values.length >= max || valuesLength + v.length > maxLength) { if (values.length >= max ||
valuesLength + v.length > maxLength) {
break outer; break outer;
} }
values.push(v); values.push(v);

162
dist/setup/index.js vendored
View File

@@ -41078,7 +41078,7 @@ exports.range = range;
Object.defineProperty(exports, "__esModule", ({ value: true })); 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; exports.expand = expand;
const balanced_match_1 = __nccwpck_require__(2649); const balanced_match_1 = __nccwpck_require__(2649);
const escSlash = '\0SLASH' + Math.random() + '\0'; const escSlash = '\0SLASH' + Math.random() + '\0';
@@ -41108,6 +41108,24 @@ exports.EXPANSION_MAX = 100_000;
// realistic expansion (100k results hitting `EXPANSION_MAX` measure ~1M // realistic expansion (100k results hitting `EXPANSION_MAX` measure ~1M
// characters) so legitimate input is unaffected. // characters) so legitimate input is unaffected.
exports.EXPANSION_MAX_LENGTH = 4_000_000; 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) { function numeric(str) {
return !isNaN(str) ? parseInt(str, 10) : str.charCodeAt(0); return !isNaN(str) ? parseInt(str, 10) : str.charCodeAt(0);
} }
@@ -41127,37 +41145,52 @@ function unescapeBraces(str) {
.replace(escCommaPattern, ',') .replace(escCommaPattern, ',')
.replace(escPeriodPattern, '.'); .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 * Basically just str.split(","), but handling cases
* where we have nested braced sections, which should be * where we have nested braced sections, which should be
* treated as individual members, like {a,{b,c},d} * treated as individual members, like {a,{b,c},d}
*/ */
function parseCommaParts(str) { function parseCommaParts(str) {
if (!str) {
return [''];
}
const parts = []; const parts = [];
// 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); const m = (0, balanced_match_1.balanced)('{', '}', str);
if (!m) { if (!m) {
return str.split(','); const tail = str.split(',');
tail[0] = carry + tail[0];
pushAll(parts, tail);
return parts;
} }
const { pre, body, post } = m; const { pre, body, post } = m;
const p = pre.split(','); const p = pre.split(',');
p[0] = carry + p[0];
p[p.length - 1] += '{' + body + '}'; p[p.length - 1] += '{' + body + '}';
const postParts = parseCommaParts(post); if (!post.length) {
if (post.length) { pushAll(parts, p);
;
p[p.length - 1] += postParts.shift();
p.push.apply(p, postParts);
}
parts.push.apply(parts, p);
return parts; return parts;
} }
carry = p.pop();
pushAll(parts, p);
str = post;
}
}
function expand(str, options = {}) { function expand(str, options = {}) {
if (!str) { if (!str) {
return []; 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. // I don't know why Bash 4.3 does this, but it does.
// Anything starting with {} will have the first two bytes preserved // Anything starting with {} will have the first two bytes preserved
// but *only* at the top level, so {},a}b will not expand to anything, // but *only* at the top level, so {},a}b will not expand to anything,
@@ -41167,7 +41200,7 @@ function expand(str, options = {}) {
if (str.slice(0, 2) === '{}') { if (str.slice(0, 2) === '{}') {
str = '\\{\\}' + str.slice(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) { function embrace(str) {
return '{' + str + '}'; return '{' + str + '}';
@@ -41262,7 +41295,13 @@ function expandSequence(body, isAlphaSequence, max, maxLength) {
} }
return N; 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 // Consume the string's top-level brace groups left to right, threading a
// running set of combined prefixes (`acc`). Expanding the tail iteratively - // running set of combined prefixes (`acc`). Expanding the tail iteratively -
// rather than recursing on `m.post` once per group - keeps the native stack // rather than recursing on `m.post` once per group - keeps the native stack
@@ -41274,6 +41313,9 @@ function expand_(str, max, maxLength, isTop) {
// comma set - a sequence like `{a..\}` may legitimately yield ''. The drop // comma set - a sequence like `{a..\}` may legitimately yield ''. The drop
// is on the final strings, so it is applied to whichever `combine` produces // is on the final strings, so it is applied to whichever `combine` produces
// them (the one with no brace set left in the tail). // 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 dropEmpties = false;
let firstGroup = true; let firstGroup = true;
for (;;) { for (;;) {
@@ -41298,7 +41340,8 @@ function expand_(str, max, maxLength, isTop) {
const isOptions = m.body.indexOf(',') >= 0; const isOptions = m.body.indexOf(',') >= 0;
if (!isSequence && !isOptions) { if (!isSequence && !isOptions) {
// {a},b} // {a},b}
if (m.post.match(/,(?!,).*\}/)) { if (rewrites < maxRewrites && m.post.match(/,(?!,).*\}/)) {
rewrites++;
str = m.pre + '{' + m.body + escClose + m.post; str = m.pre + '{' + m.body + escClose + m.post;
isTop = true; isTop = true;
continue; continue;
@@ -41318,7 +41361,7 @@ function expand_(str, max, maxLength, isTop) {
let n = parseCommaParts(m.body); let n = parseCommaParts(m.body);
if (n.length === 1 && n[0] !== undefined) { if (n.length === 1 && n[0] !== undefined) {
// x{{a,b}}y ==> x{a}y x{b}y // 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. //XXX is this necessary? Can't seem to hit it in tests.
/* c8 ignore start */ /* c8 ignore start */
if (n.length === 1) { if (n.length === 1) {
@@ -41344,12 +41387,13 @@ function expand_(str, max, maxLength, isTop) {
values = []; values = [];
let valuesLength = 0; let valuesLength = 0;
outer: for (let j = 0; j < n.length; j++) { 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++) { for (let k = 0; k < expanded.length; k++) {
const v = expanded[k]; const v = expanded[k];
if (dropsEmpties && !v) if (dropsEmpties && !v)
continue; continue;
if (values.length >= max || valuesLength + v.length > maxLength) { if (values.length >= max ||
valuesLength + v.length > maxLength) {
break outer; break outer;
} }
values.push(v); values.push(v);
@@ -100326,6 +100370,24 @@ const EXPANSION_MAX = 100_000;
// realistic expansion (100k results hitting `EXPANSION_MAX` measure ~1M // realistic expansion (100k results hitting `EXPANSION_MAX` measure ~1M
// characters) so legitimate input is unaffected. // characters) so legitimate input is unaffected.
const EXPANSION_MAX_LENGTH = 4_000_000; 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.
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.
const EXPANSION_MAX_REWRITES = 1_000;
function numeric(str) { function numeric(str) {
return !isNaN(str) ? parseInt(str, 10) : str.charCodeAt(0); return !isNaN(str) ? parseInt(str, 10) : str.charCodeAt(0);
} }
@@ -100345,37 +100407,52 @@ function unescapeBraces(str) {
.replace(escCommaPattern, ',') .replace(escCommaPattern, ',')
.replace(escPeriodPattern, '.'); .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 * Basically just str.split(","), but handling cases
* where we have nested braced sections, which should be * where we have nested braced sections, which should be
* treated as individual members, like {a,{b,c},d} * treated as individual members, like {a,{b,c},d}
*/ */
function parseCommaParts(str) { function parseCommaParts(str) {
if (!str) {
return [''];
}
const parts = []; const parts = [];
// 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); const m = balanced('{', '}', str);
if (!m) { if (!m) {
return str.split(','); const tail = str.split(',');
tail[0] = carry + tail[0];
pushAll(parts, tail);
return parts;
} }
const { pre, body, post } = m; const { pre, body, post } = m;
const p = pre.split(','); const p = pre.split(',');
p[0] = carry + p[0];
p[p.length - 1] += '{' + body + '}'; p[p.length - 1] += '{' + body + '}';
const postParts = parseCommaParts(post); if (!post.length) {
if (post.length) { pushAll(parts, p);
;
p[p.length - 1] += postParts.shift();
p.push.apply(p, postParts);
}
parts.push.apply(parts, p);
return parts; return parts;
} }
carry = p.pop();
pushAll(parts, p);
str = post;
}
}
function expand(str, options = {}) { function expand(str, options = {}) {
if (!str) { if (!str) {
return []; 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. // I don't know why Bash 4.3 does this, but it does.
// Anything starting with {} will have the first two bytes preserved // Anything starting with {} will have the first two bytes preserved
// but *only* at the top level, so {},a}b will not expand to anything, // but *only* at the top level, so {},a}b will not expand to anything,
@@ -100385,7 +100462,7 @@ function expand(str, options = {}) {
if (str.slice(0, 2) === '{}') { if (str.slice(0, 2) === '{}') {
str = '\\{\\}' + str.slice(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) { function embrace(str) {
return '{' + str + '}'; return '{' + str + '}';
@@ -100480,7 +100557,13 @@ function expandSequence(body, isAlphaSequence, max, maxLength) {
} }
return N; 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 // Consume the string's top-level brace groups left to right, threading a
// running set of combined prefixes (`acc`). Expanding the tail iteratively - // running set of combined prefixes (`acc`). Expanding the tail iteratively -
// rather than recursing on `m.post` once per group - keeps the native stack // rather than recursing on `m.post` once per group - keeps the native stack
@@ -100492,6 +100575,9 @@ function expand_(str, max, maxLength, isTop) {
// comma set - a sequence like `{a..\}` may legitimately yield ''. The drop // comma set - a sequence like `{a..\}` may legitimately yield ''. The drop
// is on the final strings, so it is applied to whichever `combine` produces // is on the final strings, so it is applied to whichever `combine` produces
// them (the one with no brace set left in the tail). // 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 dropEmpties = false;
let firstGroup = true; let firstGroup = true;
for (;;) { for (;;) {
@@ -100516,7 +100602,8 @@ function expand_(str, max, maxLength, isTop) {
const isOptions = m.body.indexOf(',') >= 0; const isOptions = m.body.indexOf(',') >= 0;
if (!isSequence && !isOptions) { if (!isSequence && !isOptions) {
// {a},b} // {a},b}
if (m.post.match(/,(?!,).*\}/)) { if (rewrites < maxRewrites && m.post.match(/,(?!,).*\}/)) {
rewrites++;
str = m.pre + '{' + m.body + escClose + m.post; str = m.pre + '{' + m.body + escClose + m.post;
isTop = true; isTop = true;
continue; continue;
@@ -100536,7 +100623,7 @@ function expand_(str, max, maxLength, isTop) {
let n = parseCommaParts(m.body); let n = parseCommaParts(m.body);
if (n.length === 1 && n[0] !== undefined) { if (n.length === 1 && n[0] !== undefined) {
// x{{a,b}}y ==> x{a}y x{b}y // 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. //XXX is this necessary? Can't seem to hit it in tests.
/* c8 ignore start */ /* c8 ignore start */
if (n.length === 1) { if (n.length === 1) {
@@ -100562,12 +100649,13 @@ function expand_(str, max, maxLength, isTop) {
values = []; values = [];
let valuesLength = 0; let valuesLength = 0;
outer: for (let j = 0; j < n.length; j++) { 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++) { for (let k = 0; k < expanded.length; k++) {
const v = expanded[k]; const v = expanded[k];
if (dropsEmpties && !v) if (dropsEmpties && !v)
continue; continue;
if (values.length >= max || valuesLength + v.length > maxLength) { if (values.length >= max ||
valuesLength + v.length > maxLength) {
break outer; break outer;
} }
values.push(v); values.push(v);

6
package-lock.json generated
View File

@@ -2793,9 +2793,9 @@
} }
}, },
"node_modules/brace-expansion": { "node_modules/brace-expansion": {
"version": "5.0.9", "version": "5.0.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz",
"integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"balanced-match": "^4.0.2" "balanced-match": "^4.0.2"