Ignore:
Timestamp:
09/19/26 10:30:30 (10 days ago)
Author:
Klimentina Efremova <klimentina08642@…>
Branches:
finki-main, main
Children:
06ebe74
Parents:
62b2964
Message:

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

File:
1 edited

Legend:

Unmodified
Added
Removed
  • node_modules/brace-expansion/dist/esm/index.js

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