diff --git a/.licenses/npm/balanced-match.dep.yml b/.licenses/npm/balanced-match.dep.yml index cdc7f86c1..36095592b 100644 --- a/.licenses/npm/balanced-match.dep.yml +++ b/.licenses/npm/balanced-match.dep.yml @@ -1,18 +1,39 @@ --- name: balanced-match -version: 4.0.4 +version: 1.0.2 type: npm summary: Match balanced character pairs, like "{" and "}" -homepage: -license: other +homepage: https://github.com/juliangruber/balanced-match +license: mit licenses: - sources: LICENSE.md text: | (MIT) - Original code Copyright Julian Gruber + Copyright (c) 2013 Julian Gruber <julian@juliangruber.com> - Port to TypeScript Copyright Isaac Z. Schlueter + Permission is hereby granted, free of charge, to any person obtaining a copy of + this software and associated documentation files (the "Software"), to deal in + the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do + so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +- sources: README.md + text: |- + (MIT) + + Copyright (c) 2013 Julian Gruber <julian@juliangruber.com> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/.licenses/npm/brace-expansion.dep.yml b/.licenses/npm/brace-expansion.dep.yml index f40e8707d..32de44bd8 100644 --- a/.licenses/npm/brace-expansion.dep.yml +++ b/.licenses/npm/brace-expansion.dep.yml @@ -1,18 +1,16 @@ --- name: brace-expansion -version: 5.0.8 +version: 1.1.18 type: npm summary: Brace expansion as known from sh/bash -homepage: -license: other +homepage: https://github.com/juliangruber/brace-expansion +license: mit licenses: - sources: LICENSE text: | MIT License - Copyright Julian Gruber - - TypeScript port Copyright Isaac Z. Schlueter + Copyright (c) 2013 Julian Gruber Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -24,6 +22,29 @@ licenses: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +- sources: README.md + text: |- + (MIT) + + Copyright (c) 2013 Julian Gruber <julian@juliangruber.com> + + Permission is hereby granted, free of charge, to any person obtaining a copy of + this software and associated documentation files (the "Software"), to deal in + the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do + so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE diff --git a/.licenses/npm/concat-map.dep.yml b/.licenses/npm/concat-map.dep.yml new file mode 100644 index 000000000..3b736f5b3 --- /dev/null +++ b/.licenses/npm/concat-map.dep.yml @@ -0,0 +1,31 @@ +--- +name: concat-map +version: 0.0.1 +type: npm +summary: concatenative mapdashery +homepage: +license: other +licenses: +- sources: LICENSE + text: | + This software is released under the MIT license: + + Permission is hereby granted, free of charge, to any person obtaining a copy of + this software and associated documentation files (the "Software"), to deal in + the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +- sources: README.markdown + text: MIT +notices: [] diff --git a/.licenses/npm/undici.dep.yml b/.licenses/npm/undici.dep.yml index c46a5c7bf..b339a4487 100644 --- a/.licenses/npm/undici.dep.yml +++ b/.licenses/npm/undici.dep.yml @@ -1,6 +1,6 @@ --- name: undici -version: 6.27.0 +version: 6.28.0 type: npm summary: An HTTP/1.1 client, written from scratch for Node.js homepage: https://undici.nodejs.org diff --git a/dist/index.js b/dist/index.js index 3171f8fb5..ca69f1540 100644 --- a/dist/index.js +++ b/dist/index.js @@ -7525,6 +7525,26 @@ exports.Agent = Agent; }))); +/***/ }), + +/***/ 7087: +/***/ ((module) => { + +module.exports = function (xs, fn) { + var res = []; + for (var i = 0; i < xs.length; i++) { + var x = fn(xs[i], i); + if (isArray(x)) res.push.apply(res, x); + else res.push(x); + } + return res; +}; + +var isArray = Array.isArray || function (xs) { + return Object.prototype.toString.call(xs) === '[object Array]'; +}; + + /***/ }), /***/ 6110: @@ -9130,7 +9150,7 @@ var path = (function () { try { return __nccwpck_require__(6928) } catch (e) {}} minimatch.sep = path.sep var GLOBSTAR = minimatch.GLOBSTAR = Minimatch.GLOBSTAR = {} -var expand = __nccwpck_require__(8968) +var expand = __nccwpck_require__(3783) var plTypes = { '!': { open: '(?:(?!(?:', close: '))[^/]*?)'}, @@ -10128,6 +10148,464 @@ function regExpEscape (s) { } +/***/ }), + +/***/ 1880: +/***/ ((module) => { + + +module.exports = balanced; +function balanced(a, b, str) { + if (a instanceof RegExp) a = maybeMatch(a, str); + if (b instanceof RegExp) b = maybeMatch(b, str); + + var r = range(a, b, str); + + return r && { + start: r[0], + end: r[1], + pre: str.slice(0, r[0]), + body: str.slice(r[0] + a.length, r[1]), + post: str.slice(r[1] + b.length) + }; +} + +function maybeMatch(reg, str) { + var m = str.match(reg); + return m ? m[0] : null; +} + +balanced.range = range; +function range(a, b, str) { + var begs, beg, left, right, result; + var ai = str.indexOf(a); + var bi = str.indexOf(b, ai + 1); + var i = ai; + + if (ai >= 0 && bi > 0) { + if(a===b) { + return [ai, bi]; + } + begs = []; + left = str.length; + + while (i >= 0 && !result) { + if (i == ai) { + begs.push(i); + ai = str.indexOf(a, i + 1); + } else if (begs.length == 1) { + result = [ begs.pop(), bi ]; + } else { + beg = begs.pop(); + if (beg < left) { + left = beg; + right = bi; + } + + bi = str.indexOf(b, i + 1); + } + + i = ai < bi && ai >= 0 ? ai : bi; + } + + if (begs.length) { + result = [ left, right ]; + } + } + + return result; +} + + +/***/ }), + +/***/ 3783: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +var concatMap = __nccwpck_require__(7087); +var balanced = __nccwpck_require__(1880); + +module.exports = expandTop; + +var escSlash = '\0SLASH'+Math.random()+'\0'; +var escOpen = '\0OPEN'+Math.random()+'\0'; +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) + : str.charCodeAt(0); +} + +function escapeBraces(str) { + return str.split('\\\\').join(escSlash) + .split('\\{').join(escOpen) + .split('\\}').join(escClose) + .split('\\,').join(escComma) + .split('\\.').join(escPeriod); +} + +function unescapeBraces(str) { + return str.split(escSlash).join('\\') + .split(escOpen).join('{') + .split(escClose).join('}') + .split(escComma).join(',') + .split(escPeriod).join('.'); +} + + +// 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 ['']; + + var parts = []; + var m = balanced('{', '}', str); + + if (!m) + return str.split(','); + + var pre = m.pre; + var body = m.body; + var post = m.post; + var p = pre.split(','); + + p[p.length-1] += '{' + body + '}'; + var 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 expandTop(str, options) { + if (!str) + return []; + + options = options || {}; + var max = options.max == null ? EXPANSION_MAX : options.max; + var maxLength = options.maxLength == null ? EXPANSION_MAX_LENGTH : options.maxLength; + + // 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.substr(0, 2) === '{}') { + str = '\\{\\}' + str.substr(2); + } + + return expand(escapeBraces(str), max, maxLength, true).map(unescapeBraces); +} + +function identity(e) { + return e; +} + +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). +// +// `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 + } + } + 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) + + 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 + } + } + } + } + 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 + } + + return acc +} + + /***/ }), /***/ 744: @@ -16534,7 +17012,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 @@ -16545,7 +17029,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') { @@ -17917,6 +18406,7 @@ const { RequestContentLengthMismatchError, ResponseContentLengthMismatchError, RequestAbortedError, + InvalidArgumentError, HeadersTimeoutError, HeadersOverflowError, SocketError, @@ -18900,8 +19390,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') { @@ -22374,6 +22872,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 @@ -22588,6 +23108,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') @@ -22611,6 +23137,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), @@ -26855,7 +27387,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') @@ -26864,16 +27396,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". + * + * ::= | " " + * ::=