From 7791272dd31f4930a8306fda5d9d7d810bee4a87 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Tue, 11 Aug 2026 11:54:20 +0200 Subject: [PATCH] chore: generate --- dist/post_run/index.js | 511 ++++++++++++++++++----------------------- dist/run/index.js | 511 ++++++++++++++++++----------------------- 2 files changed, 456 insertions(+), 566 deletions(-) diff --git a/dist/post_run/index.js b/dist/post_run/index.js index 3ca7040..d83a980 100644 --- a/dist/post_run/index.js +++ b/dist/post_run/index.js @@ -24514,20 +24514,6 @@ var escClose = '\0CLOSE'+Math.random()+'\0'; var escComma = '\0COMMA'+Math.random()+'\0'; var escPeriod = '\0PERIOD'+Math.random()+'\0'; -var EXPANSION_MAX = 100000 - -// `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. -var EXPANSION_MAX_LENGTH = 4000000 - function numeric(str) { return parseInt(str, 10) == str ? parseInt(str, 10) @@ -24586,8 +24572,7 @@ function expandTop(str, options) { return []; options = options || {}; - var max = options.max == null ? EXPANSION_MAX : options.max; - var maxLength = options.maxLength == null ? EXPANSION_MAX_LENGTH : options.maxLength; + var max = options.max == null ? Infinity : options.max; // I don't know why Bash 4.3 does this, but it does. // Anything starting with {} will have the first two bytes preserved @@ -24599,7 +24584,7 @@ function expandTop(str, options) { str = '\\{\\}' + str.substr(2); } - return expand(escapeBraces(str), max, maxLength, true).map(unescapeBraces); + return expand(escapeBraces(str), max, true).map(unescapeBraces); } function identity(e) { @@ -24620,270 +24605,106 @@ 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). -// -// `base[a]` is the length of the part of `acc[a]` that predates the current -// empty-drop baseline (see `expand`). The matching baselines for the results -// are appended to `outBase`, which the caller carries forward alongside them. -function combine( - acc, - base, - pre, - values, - max, - maxLength, - dropEmpties, - outBase -) { - var out = [] - var length = 0 - for (var a = 0; a < acc.length; a++) { - for (var v = 0; v < values.length; v++) { - if (out.length >= max) return out - var 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. "Empty" - // means "adds nothing past the baseline", not "empty overall". - if (dropEmpties && expansion.length === base[a]) continue - if (length + expansion.length > maxLength) return out - out.push(expansion) - outBase.push(base[a]) - length += expansion.length +function expand(str, max, isTop) { + var expansions = []; + + var m = balanced('{', '}', str); + if (!m || /\$$/.test(m.pre)) return [str]; + + var isNumericSequence = /^-?\d+\.\.-?\d+(?:\.\.-?\d+)?$/.test(m.body); + var isAlphaSequence = /^[a-zA-Z]\.\.[a-zA-Z](?:\.\.-?\d+)?$/.test(m.body); + var isSequence = isNumericSequence || isAlphaSequence; + var isOptions = m.body.indexOf(',') >= 0; + if (!isSequence && !isOptions) { + // {a},b} + if (m.post.match(/,(?!,).*\}/)) { + str = m.pre + '{' + m.body + escClose + m.post; + return expand(str, max, true); + } + return [str]; + } + + var n; + if (isSequence) { + n = m.body.split(/\.\./); + } else { + n = parseCommaParts(m.body); + if (n.length === 1) { + // x{{a,b}}y ==> x{a}y x{b}y + n = expand(n[0], max, false).map(embrace); + if (n.length === 1) { + var post = m.post.length + ? expand(m.post, max, false) + : ['']; + return post.map(function(p) { + return m.pre + n[0] + p; + }); + } } } - return out -} -// The expansion values of a single numeric (`1..5`) or alphabetic (`a..e..2`) -// sequence body. -function expandSequence( - body, - isAlphaSequence, - max, - maxLength -) { - var n = body.split(/\.\./) - var 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 */ - var x = numeric(n[0]) - var y = numeric(n[1]) - var width = Math.max(n[0].length, n[1].length) - var incr = - n.length === 3 && n[2] !== undefined ? - Math.max(Math.abs(numeric(n[2])), 1) - : 1 - var test = lte - var reverse = y < x - if (reverse) { - incr *= -1 - test = gte - } - var pad = n.some(isPadded) + // at this point, n is the parts, and we know it's not a comma set + // with a single entry. - var length = 0 - for (var i = x; test(i, y) && N.length < max; i += incr) { - var c - if (isAlphaSequence) { - c = String.fromCharCode(i) - if (c === '\\') { - c = '' - } - } else { - c = String(i) - if (pad) { - var need = width - c.length - if (need > 0) { - var z = new Array(need + 1).join('0') - if (i < 0) { - c = '-' + z + c.slice(1) - } else { - c = z + c + // no need to expand pre, since it is guaranteed to be free of brace-sets + var pre = m.pre; + var post = m.post.length + ? expand(m.post, max, false) + : ['']; + + var N; + + if (isSequence) { + var x = numeric(n[0]); + var y = numeric(n[1]); + var width = Math.max(n[0].length, n[1].length) + var incr = n.length == 3 + ? Math.max(Math.abs(numeric(n[2])), 1) + : 1; + var test = lte; + var reverse = y < x; + if (reverse) { + incr *= -1; + test = gte; + } + var pad = n.some(isPadded); + + N = []; + + for (var i = x; test(i, y); i += incr) { + var c; + if (isAlphaSequence) { + c = String.fromCharCode(i); + if (c === '\\') + c = ''; + } else { + c = String(i); + if (pad) { + var need = width - c.length; + if (need > 0) { + var z = new Array(need + 1).join('0'); + if (i < 0) + c = '-' + z + c.slice(1); + else + c = z + c; } } } + N.push(c); } - if (length + c.length > maxLength) break - N.push(c) - length += c.length - } - 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). - var acc = [''] - - // Bash drops empty results, but only when the *first* group of the run 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). - // - // The old implementation recursed on `m.post`, so the drop tested only the - // expansion of the current call's substring. The `{a},b}` rewrite below turns - // `isTop` back on part-way through a string, starting a fresh such run, so - // the drop must ignore whatever `acc` already holds from earlier groups. - // `accBase[a]` records how much of `acc[a]` predates the current run; - // `combine` treats an expansion as empty when it adds nothing past that. - var accBase = [0] - var dropEmpties = false - var firstGroup = true - var nextBase - - for (;;) { - var m = balanced('{', '}', str); - - // No brace set left: the rest of the string is literal. - if (!m) { - return combine(acc, accBase, str, [''], max, maxLength, dropEmpties, []) - } - - // no need to expand pre, since it is guaranteed to be free of brace-sets - var pre = m.pre; - - // For compatibility reasons, `${` is not eligible for brace expansion, and - // on the 1.x line it suppresses expansion of the rest of the string too: - // the whole remainder is literal. The 2.x and 5.x lines instead keep - // expanding the tail, which is what bash does, but changing that here would - // be a breaking change for 1.x consumers. Routed through `combine` so the - // result is still bounded by `max` and `maxLength`. - if (/\$$/.test(pre)) { - return combine(acc, accBase, str, [''], max, maxLength, dropEmpties, []) - } - - var isNumericSequence = /^-?\d+\.\.-?\d+(?:\.\.-?\d+)?$/.test(m.body); - var isAlphaSequence = /^[a-zA-Z]\.\.[a-zA-Z](?:\.\.-?\d+)?$/.test(m.body); - var isSequence = isNumericSequence || isAlphaSequence; - var isOptions = m.body.indexOf(',') >= 0; - if (!isSequence && !isOptions) { - // {a},b} - if (m.post.match(/,(?!,).*\}/)) { - str = m.pre + '{' + m.body + escClose + m.post; - // The rewritten string is expanded as if it were a fresh top-level one, - // so start a new empty-drop run: anchor the baseline at what `acc` - // holds now, and let the next expanding group decide whether to drop. - isTop = true - firstGroup = true - dropEmpties = false - accBase = [] - for (var b = 0; b < acc.length; b++) { - accBase.push(acc[b].length) - } - continue - } - // Nothing here expands, so the whole remaining string is literal. - return combine( - acc, - accBase, - pre + '{' + m.body + '}' + m.post, - [''], - max, - maxLength, - dropEmpties, - [] - ) - } - - if (firstGroup) { - dropEmpties = isTop && !isSequence - firstGroup = false - } - - var values; - if (isSequence) { - values = expandSequence(m.body, isAlphaSequence, max, maxLength); - } else { - var 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) { - nextBase = [] - acc = combine( - acc, - accBase, - pre + n[0], - [''], - max, - maxLength, - dropEmpties && !m.post.length, - nextBase - ) - accBase = nextBase - if (!m.post.length) break - str = m.post - continue - } - /* c8 ignore stop */ - } - - // Values that `combine` is going to drop as empty produce no result, so - // they must not count against `max` - otherwise `{a,,b}` with `max: 2` - // would stop at `['a', '']` and yield one result instead of two. Skipping - // them outright keeps `values` bounded while leaving `max` a bound on - // *kept* results. A value is dropped when it adds nothing past the - // baseline, which is what `combine` tests. - var dropsEmpties = dropEmpties && !m.post.length && !pre - for (var d = 0; dropsEmpties && d < acc.length; d++) { - if (acc[d].length !== accBase[d]) { - dropsEmpties = false - } - } - - values = [] - var valuesLength = 0 - outer: for (var j = 0; j < n.length; j++) { - var expanded = expand(n[j], max, maxLength, false) - for (var k = 0; k < expanded.length; k++) { - var v = expanded[k] - if (dropsEmpties && !v) continue - if (values.length >= max || valuesLength + v.length > maxLength) { - break outer - } - values.push(v) - valuesLength += v.length - } - } - } - - nextBase = [] - acc = combine( - acc, - accBase, - pre, - values, - max, - maxLength, - dropEmpties && !m.post.length, - nextBase - ) - accBase = nextBase - if (!m.post.length) break - str = m.post + } else { + N = concatMap(n, function(el) { return expand(el, max, false) }); } - return acc + for (var j = 0; j < N.length; j++) { + for (var k = 0; k < post.length && expansions.length < max; k++) { + var expansion = pre + N[j] + post[k]; + if (!isTop || isSequence || expansion) + expansions.push(expansion); + } + } + + return expansions; } @@ -33971,7 +33792,13 @@ function processHeader (request, key, val) { } else if (typeof val[i] === 'object') { throw new InvalidArgumentError(`invalid ${key} header`) } else { - arr.push(`${val[i]}`) + // Coerce primitives (and reject unsafe coercions such as functions + // with a crafted toString/Symbol.toPrimitive). + const str = `${val[i]}` + if (!isValidHeaderValue(str)) { + throw new InvalidArgumentError(`invalid ${key} header`) + } + arr.push(str) } } val = arr @@ -33982,7 +33809,12 @@ function processHeader (request, key, val) { } else if (val === null) { val = '' } else { + // Coerce primitives (and reject unsafe coercions such as functions + // with a crafted toString/Symbol.toPrimitive). val = `${val}` + if (!isValidHeaderValue(val)) { + throw new InvalidArgumentError(`invalid ${key} header`) + } } if (headerName === 'host') { @@ -35359,6 +35191,7 @@ const { RequestContentLengthMismatchError, ResponseContentLengthMismatchError, RequestAbortedError, + InvalidArgumentError, HeadersTimeoutError, HeadersOverflowError, SocketError, @@ -36342,8 +36175,16 @@ function writeH1 (client, request) { } body = bodyStream.stream contentLength = bodyStream.length - } else if (util.isBlobLike(body) && request.contentType == null && body.type) { - headers.push('content-type', body.type) + } else if (util.isBlobLike(body) && request.contentType == null) { + const contentType = body.type + if (contentType) { + const contentTypeValue = `${contentType}` + if (!util.isValidHeaderValue(contentTypeValue)) { + util.errorRequest(client, request, new InvalidArgumentError('invalid content-type header')) + return false + } + headers.push('content-type', contentTypeValue) + } } if (body && typeof body.read === 'function') { @@ -39830,6 +39671,28 @@ function calculateRetryAfterHeader (retryAfter) { return new Date(retryAfter).getTime() - current } +function validatePartialResponseContentLength (headers, range, statusCode, retryCount) { + const contentLength = headers['content-length'] + if (contentLength == null) { + return null + } + + if (!Number.isFinite(range.start) || !Number.isFinite(range.end)) { + return null + } + + const length = Number(contentLength) + const expectedLength = range.end - range.start + 1 + if (!Number.isFinite(length) || length !== expectedLength) { + return new RequestRetryError('Content-Length mismatch', statusCode, { + headers, + data: { count: retryCount } + }) + } + + return null +} + class RetryHandler { constructor (opts, handlers) { const { retryOptions, ...dispatchOpts } = opts @@ -40044,6 +39907,12 @@ class RetryHandler { return false } + const contentLengthError = validatePartialResponseContentLength(headers, contentRange, statusCode, this.retryCount) + if (contentLengthError != null) { + this.abort(contentLengthError) + return false + } + const { start, size, end = size - 1 } = contentRange assert(this.start === start, 'content-range mismatch') @@ -40067,6 +39936,12 @@ class RetryHandler { ) } + const contentLengthError = validatePartialResponseContentLength(headers, range, statusCode, this.retryCount) + if (contentLengthError != null) { + this.abort(contentLengthError) + return false + } + const { start, size, end = size - 1 } = range assert( start != null && Number.isFinite(start), @@ -44338,7 +44213,7 @@ function validateCookiePath (path) { if ( code < 0x20 || // exclude CTLs (0-31) - code === 0x7F || // DEL + code > 0x7E || // exclude DEL and non-ascii code === 0x3B // ; ) { throw new Error('Invalid cookie path') @@ -44347,16 +44222,80 @@ function validateCookiePath (path) { } /** - * I have no idea why these values aren't allowed to be honest, - * but Deno tests these. - Khafra + * ::= | + * + * ::= any one of the 52 alphabetic characters A through Z in + * upper case and a through z in lower case + * + * ::= any one of the ten digits 0 through 9r + * + * @see https://www.rfc-editor.org/rfc/rfc1034#section-3.5 + * @param {number} code + */ +function isLetterOrDigit (code) { + return ( + (code >= 0x30 && code <= 0x39) || // 0-9 + (code >= 0x41 && code <= 0x5A) || // A-Z + (code >= 0x61 && code <= 0x7A) // a-z + ) +} + +/** + * Validates a cookie domain against the "preferred name syntax". + * + * ::= | " " + * ::=