File Explorer

/proc/2/root/var/runtime/node_modules/@aws-sdk/node_modules/brace-expansion/dist/commonjs

This explorer reads the filesystem of the server it runs on, so /workspace/user isn't present here. Browsing and the terminal still work against this server's own disk from /.

index.js9.8 KB · 263 lines
"use strict";Object.defineProperty(exports, "__esModule", { value: true });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';const escOpen = '\0OPEN' + Math.random() + '\0';const escClose = '\0CLOSE' + Math.random() + '\0';const escComma = '\0COMMA' + Math.random() + '\0';const escPeriod = '\0PERIOD' + Math.random() + '\0';const escSlashPattern = new RegExp(escSlash, 'g');const escOpenPattern = new RegExp(escOpen, 'g');const escClosePattern = new RegExp(escClose, 'g');const escCommaPattern = new RegExp(escComma, 'g');const escPeriodPattern = new RegExp(escPeriod, 'g');const slashPattern = /\\\\/g;const openPattern = /\\{/g;const closePattern = /\\}/g;const commaPattern = /\\,/g;const periodPattern = /\\\./g;exports.EXPANSION_MAX = 100_000;// `EXPANSION_MAX` caps the *number* of expansions, but not their length. An// input like `'{a,b}'.repeat(1500)` stays under that count - its output is// truncated to 100k results - while making every result ~1500 characters// long. The result set, and the intermediate arrays built while combining// brace sets, then grow large enough to exhaust memory and crash the process// (CVE-2026-14257). `EXPANSION_MAX_LENGTH` bounds the total number of// characters the accumulator may hold at any point, so memory stays flat no// matter how many brace groups are chained. The limit sits well above any// realistic expansion (100k results hitting `EXPANSION_MAX` measure ~1M// characters) so legitimate input is unaffected.exports.EXPANSION_MAX_LENGTH = 4_000_000;function numeric(str) {    return !isNaN(str) ? parseInt(str, 10) : str.charCodeAt(0);}function escapeBraces(str) {    return str        .replace(slashPattern, escSlash)        .replace(openPattern, escOpen)        .replace(closePattern, escClose)        .replace(commaPattern, escComma)        .replace(periodPattern, escPeriod);}function unescapeBraces(str) {    return str        .replace(escSlashPattern, '\\')        .replace(escOpenPattern, '{')        .replace(escClosePattern, '}')        .replace(escCommaPattern, ',')        .replace(escPeriodPattern, '.');}/** * 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);    }    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;    // 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,    // but a{},b}c will be expanded to [a}c,abc].    // One could argue that this is a bug in Bash, but since the goal of    // this module is to match Bash's rules, we escape a leading {}    if (str.slice(0, 2) === '{}') {        str = '\\{\\}' + str.slice(2);    }    return expand_(escapeBraces(str), max, maxLength, true).map(unescapeBraces);}function embrace(str) {    return '{' + str + '}';}function isPadded(el) {    return /^-?0\d/.test(el);}function lte(i, y) {    return i <= y;}function gte(i, y) {    return i >= y;}// Build `{ acc[a] + pre + values[v] }` for every combination, capping the// number of results at `max` and the total number of characters at `maxLength`.// This is the one place output grows, so bounding it here keeps the single// accumulator - and therefore memory - flat regardless of how many brace groups// are combined (CVE-2026-14257).function combine(acc, pre, values, max, maxLength, dropEmpties) {    const out = [];    let length = 0;    for (let a = 0; a < acc.length; a++) {        for (let v = 0; v < values.length; v++) {            if (out.length >= max)                return out;            const expansion = acc[a] + pre + values[v];            // Bash drops empty results at the top level. Skip them before they count            // against `max`, so `max` bounds the number of *kept* results.            if (dropEmpties && !expansion)                continue;            if (length + expansion.length > maxLength)                return out;            out.push(expansion);            length += expansion.length;        }    }    return out;}// The expansion values of a single numeric (`1..5`) or alphabetic (`a..e..2`)// sequence body.function expandSequence(body, isAlphaSequence, max) {    const n = body.split(/\.\./);    const N = [];    // A sequence body always splits into two or three parts, but the compiler    // can't know that.    /* c8 ignore start */    if (n[0] === undefined || n[1] === undefined) {        return N;    }    /* c8 ignore stop */    const x = numeric(n[0]);    const y = numeric(n[1]);    const width = Math.max(n[0].length, n[1].length);    let incr = n.length === 3 && n[2] !== undefined ?        Math.max(Math.abs(numeric(n[2])), 1)        : 1;    let test = lte;    const reverse = y < x;    if (reverse) {        incr *= -1;        test = gte;    }    const pad = n.some(isPadded);    for (let i = x; test(i, y) && N.length < max; i += incr) {        let c;        if (isAlphaSequence) {            c = String.fromCharCode(i);            if (c === '\\') {                c = '';            }        }        else {            c = String(i);            if (pad) {                const need = width - c.length;                if (need > 0) {                    const z = new Array(need + 1).join('0');                    if (i < 0) {                        c = '-' + z + c.slice(1);                    }                    else {                        c = z + c;                    }                }            }        }        N.push(c);    }    return N;}function expand_(str, max, maxLength, isTop) {    // 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    // depth constant, so deeply chained input (`'{a,b}'.repeat(3000)`) can no    // longer overflow the stack, and leaves a single accumulator whose size    // `maxLength` bounds directly (CVE-2026-14257).    let acc = [''];    // Bash drops empty results, but only when the *first* top-level group is a    // 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).    let dropEmpties = false;    let firstGroup = true;    for (;;) {        const m = (0, balanced_match_1.balanced)('{', '}', str);        // No brace set left: the rest of the string is literal.        if (!m) {            return combine(acc, str, [''], max, maxLength, dropEmpties);        }        // no need to expand pre, since it is guaranteed to be free of brace-sets        const pre = m.pre;        if (/\$$/.test(pre)) {            acc = combine(acc, pre + '{' + m.body + '}', [''], max, maxLength, dropEmpties && !m.post.length);            firstGroup = false;            if (!m.post.length)                break;            str = m.post;            continue;        }        const isNumericSequence = /^-?\d+\.\.-?\d+(?:\.\.-?\d+)?$/.test(m.body);        const isAlphaSequence = /^[a-zA-Z]\.\.[a-zA-Z](?:\.\.-?\d+)?$/.test(m.body);        const isSequence = isNumericSequence || isAlphaSequence;        const isOptions = m.body.indexOf(',') >= 0;        if (!isSequence && !isOptions) {            // {a},b}            if (m.post.match(/,(?!,).*\}/)) {                str = m.pre + '{' + m.body + escClose + m.post;                isTop = true;                continue;            }            // Nothing here expands, so the whole remaining string is literal.            return combine(acc, pre + '{' + m.body + '}' + m.post, [''], max, maxLength, dropEmpties);        }        if (firstGroup) {            dropEmpties = isTop && !isSequence;            firstGroup = false;        }        let values;        if (isSequence) {            values = expandSequence(m.body, isAlphaSequence, max);        }        else {            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);                //XXX is this necessary? Can't seem to hit it in tests.                /* c8 ignore start */                if (n.length === 1) {                    acc = combine(acc, pre + n[0], [''], max, maxLength, dropEmpties && !m.post.length);                    if (!m.post.length)                        break;                    str = m.post;                    continue;                }                /* c8 ignore stop */            }            values = [];            for (let j = 0; j < n.length; j++) {                values.push.apply(values, expand_(n[j], max, maxLength, false));            }        }        acc = combine(acc, pre, values, max, maxLength, dropEmpties && !m.post.length);        if (!m.post.length)            break;        str = m.post;    }    return acc;}//# sourceMappingURL=index.js.map