source: node_modules/brace-expansion/dist/commonjs/index.js@ 33517cc

finki-main main
Last change on this file since 33517cc was 33517cc, checked in by Klimentina Efremova <klimentina08642@…>, 10 days ago

Turned database from SQLite to PostgressSQL, updated database changes from Phase 1 and 2

  • Property mode set to 100644
File size: 13.7 KB
RevLine 
[81bc7da]1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
[33517cc]3exports.EXPANSION_MAX_REWRITES = exports.EXPANSION_MAX_DEPTH = exports.EXPANSION_MAX_LENGTH = exports.EXPANSION_MAX = void 0;
[81bc7da]4exports.expand = expand;
5const balanced_match_1 = require("balanced-match");
6const escSlash = '\0SLASH' + Math.random() + '\0';
7const escOpen = '\0OPEN' + Math.random() + '\0';
8const escClose = '\0CLOSE' + Math.random() + '\0';
9const escComma = '\0COMMA' + Math.random() + '\0';
10const escPeriod = '\0PERIOD' + Math.random() + '\0';
11const escSlashPattern = new RegExp(escSlash, 'g');
12const escOpenPattern = new RegExp(escOpen, 'g');
13const escClosePattern = new RegExp(escClose, 'g');
14const escCommaPattern = new RegExp(escComma, 'g');
15const escPeriodPattern = new RegExp(escPeriod, 'g');
16const slashPattern = /\\\\/g;
17const openPattern = /\\{/g;
18const closePattern = /\\}/g;
19const commaPattern = /\\,/g;
[33517cc]20const periodPattern = /\\\./g;
[81bc7da]21exports.EXPANSION_MAX = 100_000;
[33517cc]22// `EXPANSION_MAX` caps the *number* of expansions, but not their length. An
23// input like `'{a,b}'.repeat(1500)` stays under that count - its output is
24// truncated to 100k results - while making every result ~1500 characters
25// long. The result set, and the intermediate arrays built while combining
26// brace sets, then grow large enough to exhaust memory and crash the process
27// (CVE-2026-14257). `EXPANSION_MAX_LENGTH` bounds the total number of
28// characters the accumulator may hold at any point, so memory stays flat no
29// matter how many brace groups are chained. The limit sits well above any
30// realistic expansion (100k results hitting `EXPANSION_MAX` measure ~1M
31// characters) so legitimate input is unaffected.
32exports.EXPANSION_MAX_LENGTH = 4_000_000;
33// `expand_` recurses once per level of brace *nesting* - both when expanding a
34// set's comma members and when re-wrapping a set whose body is a single part.
35// The CVE-2026-14257 fix made the *tail* iterative (recursion on `m.post`, one
36// level per chained group), which left nesting depth unbounded: about 3,100
37// levels of `{{{...a,b...}}}` - only ~6KB of input - exhausted the native stack
38// and crashed the process. `EXPANSION_MAX_DEPTH` bounds how deep the parser
39// will follow nesting. It sits far above any realistic pattern and well below
40// the depth at which the stack runs out.
41exports.EXPANSION_MAX_DEPTH = 1_000;
42// Bash keeps a quirk where a brace group followed by a comma set still expands
43// (`{a},b}`). The parser implements it by rewriting the string and restarting
44// the scan, absorbing one `}` per pass. `n` trailing braces therefore cost `n`
45// full passes over a string that itself grows by one `escClose` sentinel each
46// time - quadratic in `n`, with a ~26x constant from the sentinel's length.
47// 128KB of `'{a}' + '}'.repeat(n) + ',z}'` blocked the event loop for 27
48// seconds to produce two results. `EXPANSION_MAX_REWRITES` bounds how many
49// times the scan may restart. Real `{a},b}` input needs a handful.
50exports.EXPANSION_MAX_REWRITES = 1_000;
[81bc7da]51function numeric(str) {
52 return !isNaN(str) ? parseInt(str, 10) : str.charCodeAt(0);
53}
54function escapeBraces(str) {
55 return str
56 .replace(slashPattern, escSlash)
57 .replace(openPattern, escOpen)
58 .replace(closePattern, escClose)
59 .replace(commaPattern, escComma)
60 .replace(periodPattern, escPeriod);
61}
62function unescapeBraces(str) {
63 return str
64 .replace(escSlashPattern, '\\')
65 .replace(escOpenPattern, '{')
66 .replace(escClosePattern, '}')
67 .replace(escCommaPattern, ',')
68 .replace(escPeriodPattern, '.');
69}
[33517cc]70// Like `target.push(...items)` but doesn't overflow the stack
71function pushAll(target, items) {
72 for (let i = 0; i < items.length; i++) {
73 target.push(items[i]);
74 }
75}
[81bc7da]76/**
77 * Basically just str.split(","), but handling cases
78 * where we have nested braced sections, which should be
79 * treated as individual members, like {a,{b,c},d}
80 */
81function parseCommaParts(str) {
82 const parts = [];
[33517cc]83 // Walk the brace groups iteratively. Recursing on `post` once per group let a
84 // chain of them exhaust the stack - the parsing-side counterpart to
85 // the `expand_` overflow fixed for CVE-2026-14257, and not something `max` or
86 // `maxLength` can bound, since it happens before expansion.
87 //
88 // The part the next chunk continues
89 let carry = '';
90 for (;;) {
91 const m = (0, balanced_match_1.balanced)('{', '}', str);
92 if (!m) {
93 const tail = str.split(',');
94 tail[0] = carry + tail[0];
95 pushAll(parts, tail);
96 return parts;
97 }
98 const { pre, body, post } = m;
99 const p = pre.split(',');
100 p[0] = carry + p[0];
101 p[p.length - 1] += '{' + body + '}';
102 if (!post.length) {
103 pushAll(parts, p);
104 return parts;
105 }
106 carry = p.pop();
107 pushAll(parts, p);
108 str = post;
[81bc7da]109 }
110}
111function expand(str, options = {}) {
112 if (!str) {
113 return [];
114 }
[33517cc]115 const { max = exports.EXPANSION_MAX, maxLength = exports.EXPANSION_MAX_LENGTH, maxDepth = exports.EXPANSION_MAX_DEPTH, maxRewrites = exports.EXPANSION_MAX_REWRITES, } = options;
[81bc7da]116 // I don't know why Bash 4.3 does this, but it does.
117 // Anything starting with {} will have the first two bytes preserved
118 // but *only* at the top level, so {},a}b will not expand to anything,
119 // but a{},b}c will be expanded to [a}c,abc].
120 // One could argue that this is a bug in Bash, but since the goal of
121 // this module is to match Bash's rules, we escape a leading {}
122 if (str.slice(0, 2) === '{}') {
123 str = '\\{\\}' + str.slice(2);
124 }
[33517cc]125 return expand_(escapeBraces(str), max, maxLength, maxDepth, 0, maxRewrites, true).map(unescapeBraces);
[81bc7da]126}
127function embrace(str) {
128 return '{' + str + '}';
129}
130function isPadded(el) {
131 return /^-?0\d/.test(el);
132}
133function lte(i, y) {
134 return i <= y;
135}
136function gte(i, y) {
137 return i >= y;
138}
[33517cc]139// Build `{ acc[a] + pre + values[v] }` for every combination, capping the
140// number of results at `max` and the total number of characters at `maxLength`.
141// This is the one place output grows, so bounding it here keeps the single
142// accumulator - and therefore memory - flat regardless of how many brace groups
143// are combined (CVE-2026-14257).
144function combine(acc, pre, values, max, maxLength, dropEmpties) {
145 const out = [];
146 let length = 0;
147 for (let a = 0; a < acc.length; a++) {
148 for (let v = 0; v < values.length; v++) {
149 if (out.length >= max)
150 return out;
151 const expansion = acc[a] + pre + values[v];
152 // Bash drops empty results at the top level. Skip them before they count
153 // against `max`, so `max` bounds the number of *kept* results.
154 if (dropEmpties && !expansion)
155 continue;
156 if (length + expansion.length > maxLength)
157 return out;
158 out.push(expansion);
159 length += expansion.length;
160 }
161 }
162 return out;
163}
164// The expansion values of a single numeric (`1..5`) or alphabetic (`a..e..2`)
165// sequence body.
166function expandSequence(body, isAlphaSequence, max, maxLength) {
167 const n = body.split(/\.\./);
168 const N = [];
169 // A sequence body always splits into two or three parts, but the compiler
170 // can't know that.
171 /* c8 ignore start */
172 if (n[0] === undefined || n[1] === undefined) {
173 return N;
174 }
175 /* c8 ignore stop */
176 const x = numeric(n[0]);
177 const y = numeric(n[1]);
178 const width = Math.max(n[0].length, n[1].length);
179 let incr = n.length === 3 && n[2] !== undefined ?
180 Math.max(Math.abs(numeric(n[2])), 1)
181 : 1;
182 let test = lte;
183 const reverse = y < x;
184 if (reverse) {
185 incr *= -1;
186 test = gte;
187 }
188 const pad = n.some(isPadded);
189 let length = 0;
190 for (let i = x; test(i, y) && N.length < max; i += incr) {
191 let c;
192 if (isAlphaSequence) {
193 c = String.fromCharCode(i);
194 if (c === '\\') {
195 c = '';
196 }
197 }
198 else {
199 c = String(i);
200 if (pad) {
201 const need = width - c.length;
202 if (need > 0) {
203 const z = new Array(need + 1).join('0');
204 if (i < 0) {
205 c = '-' + z + c.slice(1);
206 }
207 else {
208 c = z + c;
209 }
210 }
211 }
[81bc7da]212 }
[33517cc]213 if (length + c.length > maxLength)
214 break;
215 N.push(c);
216 length += c.length;
217 }
218 return N;
219}
220function expand_(str, max, maxLength, maxDepth, depth, maxRewrites, isTop) {
221 // Too deeply nested to keep following: treat the rest as literal, the same
222 // way a group that cannot expand is already handled. Truncating rather than
223 // throwing keeps `expand` total, matching `max` and `maxLength`.
224 if (depth > maxDepth) {
225 return [str];
[81bc7da]226 }
[33517cc]227 // Consume the string's top-level brace groups left to right, threading a
228 // running set of combined prefixes (`acc`). Expanding the tail iteratively -
229 // rather than recursing on `m.post` once per group - keeps the native stack
230 // depth constant, so deeply chained input (`'{a,b}'.repeat(3000)`) can no
231 // longer overflow the stack, and leaves a single accumulator whose size
232 // `maxLength` bounds directly (CVE-2026-14257).
233 let acc = [''];
234 // Bash drops empty results, but only when the *first* top-level group is a
235 // comma set - a sequence like `{a..\}` may legitimately yield ''. The drop
236 // is on the final strings, so it is applied to whichever `combine` produces
237 // them (the one with no brace set left in the tail).
238 // How many times the `{a},b}` rewrite below has restarted the scan. Each pass
239 // re-reads the whole string, so leaving this unbounded is quadratic.
240 let rewrites = 0;
241 let dropEmpties = false;
242 let firstGroup = true;
243 for (;;) {
244 const m = (0, balanced_match_1.balanced)('{', '}', str);
245 // No brace set left: the rest of the string is literal.
246 if (!m) {
247 return combine(acc, str, [''], max, maxLength, dropEmpties);
248 }
249 // no need to expand pre, since it is guaranteed to be free of brace-sets
250 const pre = m.pre;
251 if (/\$$/.test(pre)) {
252 acc = combine(acc, pre + '{' + m.body + '}', [''], max, maxLength, dropEmpties && !m.post.length);
253 firstGroup = false;
254 if (!m.post.length)
255 break;
256 str = m.post;
257 continue;
258 }
[81bc7da]259 const isNumericSequence = /^-?\d+\.\.-?\d+(?:\.\.-?\d+)?$/.test(m.body);
260 const isAlphaSequence = /^[a-zA-Z]\.\.[a-zA-Z](?:\.\.-?\d+)?$/.test(m.body);
261 const isSequence = isNumericSequence || isAlphaSequence;
262 const isOptions = m.body.indexOf(',') >= 0;
263 if (!isSequence && !isOptions) {
264 // {a},b}
[33517cc]265 if (rewrites < maxRewrites && m.post.match(/,(?!,).*\}/)) {
266 rewrites++;
[81bc7da]267 str = m.pre + '{' + m.body + escClose + m.post;
[33517cc]268 isTop = true;
269 continue;
[81bc7da]270 }
[33517cc]271 // Nothing here expands, so the whole remaining string is literal.
272 return combine(acc, pre + '{' + m.body + '}' + m.post, [''], max, maxLength, dropEmpties);
[81bc7da]273 }
[33517cc]274 if (firstGroup) {
275 dropEmpties = isTop && !isSequence;
276 firstGroup = false;
277 }
278 let values;
[81bc7da]279 if (isSequence) {
[33517cc]280 values = expandSequence(m.body, isAlphaSequence, max, maxLength);
[81bc7da]281 }
282 else {
[33517cc]283 let n = parseCommaParts(m.body);
[81bc7da]284 if (n.length === 1 && n[0] !== undefined) {
285 // x{{a,b}}y ==> x{a}y x{b}y
[33517cc]286 n = expand_(n[0], max, maxLength, maxDepth, depth + 1, maxRewrites, false).map(embrace);
[81bc7da]287 //XXX is this necessary? Can't seem to hit it in tests.
288 /* c8 ignore start */
289 if (n.length === 1) {
[33517cc]290 acc = combine(acc, pre + n[0], [''], max, maxLength, dropEmpties && !m.post.length);
291 if (!m.post.length)
292 break;
293 str = m.post;
294 continue;
[81bc7da]295 }
296 /* c8 ignore stop */
297 }
[33517cc]298 // Values that `combine` is going to drop as empty produce no result, so
299 // they must not count against `max` - otherwise `{a,,b}` with `max: 2`
300 // would stop at `['a', '']` and yield one result instead of two. Skipping
301 // them outright keeps `values` bounded while leaving `max` a bound on
302 // *kept* results.
303 let dropsEmpties = dropEmpties && !m.post.length && !pre;
304 for (let d = 0; dropsEmpties && d < acc.length; d++) {
305 if (acc[d]) {
306 dropsEmpties = false;
[81bc7da]307 }
308 }
[33517cc]309 values = [];
310 let valuesLength = 0;
311 outer: for (let j = 0; j < n.length; j++) {
312 const expanded = expand_(n[j], max, maxLength, maxDepth, depth + 1, maxRewrites, false);
313 for (let k = 0; k < expanded.length; k++) {
314 const v = expanded[k];
315 if (dropsEmpties && !v)
316 continue;
317 if (values.length >= max ||
318 valuesLength + v.length > maxLength) {
319 break outer;
320 }
321 values.push(v);
322 valuesLength += v.length;
[81bc7da]323 }
324 }
325 }
[33517cc]326 acc = combine(acc, pre, values, max, maxLength, dropEmpties && !m.post.length);
327 if (!m.post.length)
328 break;
329 str = m.post;
[81bc7da]330 }
[33517cc]331 return acc;
[81bc7da]332}
333//# sourceMappingURL=index.js.map
Note: See TracBrowser for help on using the repository browser.