diff --git a/dist/index.js b/dist/index.js index a6e9fc5..d31f59f 100644 --- a/dist/index.js +++ b/dist/index.js @@ -14860,6 +14860,13 @@ catch (error) { useNativeURL = error.code === "ERR_INVALID_URL"; } +// HTTP headers to drop across HTTP/HTTPS and domain boundaries +var sensitiveHeaders = [ + "Authorization", + "Proxy-Authorization", + "Cookie", +]; + // URL fields to preserve in copy operations var preservedUrlFields = [ "auth", @@ -14941,6 +14948,11 @@ function RedirectableRequest(options, responseCallback) { } }; + // Create filter for sensitive HTTP headers + this._headerFilter = new RegExp("^(?:" + + sensitiveHeaders.concat(options.sensitiveHeaders).map(escapeRegex).join("|") + + ")$", "i"); + // Perform the first request this._performRequest(); } @@ -15124,6 +15136,9 @@ RedirectableRequest.prototype._sanitizeOptions = function (options) { if (!options.headers) { options.headers = {}; } + if (!isArray(options.sensitiveHeaders)) { + options.sensitiveHeaders = []; + } // Since http.request treats host as an alias of hostname, // but the url module interprets host as hostname plus port, @@ -15306,7 +15321,7 @@ RedirectableRequest.prototype._processResponse = function (response) { redirectUrl.protocol !== "https:" || redirectUrl.host !== currentHost && !isSubdomain(redirectUrl.host, currentHost)) { - removeMatchingHeaders(/^(?:(?:proxy-)?authorization|cookie)$/i, this._options.headers); + removeMatchingHeaders(this._headerFilter, this._options.headers); } // Evaluate the beforeRedirect callback @@ -15499,6 +15514,10 @@ function isSubdomain(subdomain, domain) { return dot > 0 && subdomain[dot] === "." && subdomain.endsWith(domain); } +function isArray(value) { + return value instanceof Array; +} + function isString(value) { return typeof value === "string" || value instanceof String; } @@ -15515,6 +15534,10 @@ function isURL(value) { return URL && value instanceof URL; } +function escapeRegex(regex) { + return regex.replace(/[\]\\/()*+?.$]/g, "\\$&"); +} + // Exports module.exports = wrap({ http: http, https: https }); module.exports.wrap = wrap; @@ -45166,7 +45189,7 @@ module.exports = require("zlib"); /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { "use strict"; -/*! Axios v1.15.0 Copyright (c) 2026 Matt Zabriskie and contributors */ +/*! Axios v1.16.0 Copyright (c) 2026 Matt Zabriskie and contributors */ var FormData$1 = __nccwpck_require__(6454); @@ -45176,6 +45199,7 @@ var http = __nccwpck_require__(8611); var https = __nccwpck_require__(5692); var http2 = __nccwpck_require__(5675); var util = __nccwpck_require__(9023); +var path = __nccwpck_require__(6928); var followRedirects = __nccwpck_require__(1573); var zlib = __nccwpck_require__(3106); var stream = __nccwpck_require__(2203); @@ -45375,9 +45399,9 @@ const isFile = kindOfTest('File'); * also have a `name` and `type` attribute to specify filename and content type * * @see https://github.com/facebook/react-native/blob/26684cf3adf4094eb6c405d345a75bf8c7c0bf88/Libraries/Network/FormData.js#L68-L71 - * + * * @param {*} value The value to test - * + * * @returns {boolean} True if value is a React Native Blob, otherwise false */ const isReactNativeBlob = value => { @@ -45387,9 +45411,9 @@ const isReactNativeBlob = value => { /** * Determine if environment is React Native * ReactNative `FormData` has a non-standard `getParts()` method - * + * * @param {*} formData The formData to test - * + * * @returns {boolean} True if environment is React Native, otherwise false */ const isReactNative = formData => formData && typeof formData.getParts !== 'undefined'; @@ -45408,7 +45432,7 @@ const isBlob = kindOfTest('Blob'); * * @param {*} val The value to test * - * @returns {boolean} True if value is a File, otherwise false + * @returns {boolean} True if value is a FileList, otherwise false */ const isFileList = kindOfTest('FileList'); @@ -45438,10 +45462,16 @@ function getGlobal() { const G = getGlobal(); const FormDataCtor = typeof G.FormData !== 'undefined' ? G.FormData : undefined; const isFormData = thing => { - let kind; - return thing && (FormDataCtor && thing instanceof FormDataCtor || isFunction$1(thing.append) && ((kind = kindOf(thing)) === 'formdata' || + if (!thing) return false; + if (FormDataCtor && thing instanceof FormDataCtor) return true; + // Reject plain objects inheriting directly from Object.prototype so prototype-pollution gadgets can't spoof FormData. + const proto = getPrototypeOf(thing); + if (!proto || proto === Object.prototype) return false; + if (!isFunction$1(thing.append)) return false; + const kind = kindOf(thing); + return kind === 'formdata' || // detect form-data instance - kind === 'object' && isFunction$1(thing.toString) && thing.toString() === '[object FormData]')); + kind === 'object' && isFunction$1(thing.toString) && thing.toString() === '[object FormData]'; }; /** @@ -45566,8 +45596,7 @@ const isContextDefined = context => !isUndefined(context) && context !== _global * * @returns {Object} Result of all merge properties */ -function merge(/* obj1, obj2, obj3, ... */ -) { +function merge(...objs) { const { caseless, skipUndefined @@ -45579,8 +45608,12 @@ function merge(/* obj1, obj2, obj3, ... */ return; } const targetKey = caseless && findKey(result, key) || key; - if (isPlainObject(result[targetKey]) && isPlainObject(val)) { - result[targetKey] = merge(result[targetKey], val); + // Read via own-prop only — a bare `result[targetKey]` walks the prototype + // chain, so a polluted Object.prototype value could surface here and get + // copied into the merged result. + const existing = hasOwnProperty(result, targetKey) ? result[targetKey] : undefined; + if (isPlainObject(existing) && isPlainObject(val)) { + result[targetKey] = merge(existing, val); } else if (isPlainObject(val)) { result[targetKey] = merge({}, val); } else if (isArray(val)) { @@ -45589,8 +45622,8 @@ function merge(/* obj1, obj2, obj3, ... */ result[targetKey] = val; } }; - for (let i = 0, l = arguments.length; i < l; i++) { - arguments[i] && forEach(arguments[i], assignValue); + for (let i = 0, l = objs.length; i < l; i++) { + objs[i] && forEach(objs[i], assignValue); } return result; } @@ -45612,6 +45645,9 @@ const extend = (a, b, thisArg, { forEach(b, (val, key) => { if (thisArg && isFunction$1(val)) { Object.defineProperty(a, key, { + // Null-proto descriptor so a polluted Object.prototype.get cannot + // hijack defineProperty's accessor-vs-data resolution. + __proto__: null, value: bind(val, thisArg), writable: true, enumerable: true, @@ -45619,6 +45655,7 @@ const extend = (a, b, thisArg, { }); } else { Object.defineProperty(a, key, { + __proto__: null, value: val, writable: true, enumerable: true, @@ -45657,12 +45694,14 @@ const stripBOM = content => { const inherits = (constructor, superConstructor, props, descriptors) => { constructor.prototype = Object.create(superConstructor.prototype, descriptors); Object.defineProperty(constructor.prototype, 'constructor', { + __proto__: null, value: constructor, writable: true, enumerable: false, configurable: true }); Object.defineProperty(constructor, 'super', { + __proto__: null, value: superConstructor.prototype }); props && Object.assign(constructor.prototype, props); @@ -45830,7 +45869,7 @@ const reduceDescriptors = (obj, reducer) => { const freezeMethods = obj => { reduceDescriptors(obj, (descriptor, name) => { // skip restricted props in strict mode - if (isFunction$1(obj) && ['arguments', 'caller', 'callee'].indexOf(name) !== -1) { + if (isFunction$1(obj) && ['arguments', 'caller', 'callee'].includes(name)) { return false; } const value = obj[name]; @@ -46037,6 +46076,364 @@ var utils$1 = { isIterable }; +// RawAxiosHeaders whose duplicates are ignored by node +// c.f. https://nodejs.org/api/http.html#http_message_headers +const ignoreDuplicateOf = utils$1.toObjectSet(['age', 'authorization', 'content-length', 'content-type', 'etag', 'expires', 'from', 'host', 'if-modified-since', 'if-unmodified-since', 'last-modified', 'location', 'max-forwards', 'proxy-authorization', 'referer', 'retry-after', 'user-agent']); + +/** + * Parse headers into an object + * + * ``` + * Date: Wed, 27 Aug 2014 08:58:49 GMT + * Content-Type: application/json + * Connection: keep-alive + * Transfer-Encoding: chunked + * ``` + * + * @param {String} rawHeaders Headers needing to be parsed + * + * @returns {Object} Headers parsed into an object + */ +var parseHeaders = rawHeaders => { + const parsed = {}; + let key; + let val; + let i; + rawHeaders && rawHeaders.split('\n').forEach(function parser(line) { + i = line.indexOf(':'); + key = line.substring(0, i).trim().toLowerCase(); + val = line.substring(i + 1).trim(); + if (!key || parsed[key] && ignoreDuplicateOf[key]) { + return; + } + if (key === 'set-cookie') { + if (parsed[key]) { + parsed[key].push(val); + } else { + parsed[key] = [val]; + } + } else { + parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val; + } + }); + return parsed; +}; + +const $internals = Symbol('internals'); +const INVALID_HEADER_VALUE_CHARS_RE = /[^\x09\x20-\x7E\x80-\xFF]/g; +function trimSPorHTAB(str) { + let start = 0; + let end = str.length; + while (start < end) { + const code = str.charCodeAt(start); + if (code !== 0x09 && code !== 0x20) { + break; + } + start += 1; + } + while (end > start) { + const code = str.charCodeAt(end - 1); + if (code !== 0x09 && code !== 0x20) { + break; + } + end -= 1; + } + return start === 0 && end === str.length ? str : str.slice(start, end); +} +function normalizeHeader(header) { + return header && String(header).trim().toLowerCase(); +} +function sanitizeHeaderValue(str) { + return trimSPorHTAB(str.replace(INVALID_HEADER_VALUE_CHARS_RE, '')); +} +function normalizeValue(value) { + if (value === false || value == null) { + return value; + } + return utils$1.isArray(value) ? value.map(normalizeValue) : sanitizeHeaderValue(String(value)); +} +function parseTokens(str) { + const tokens = Object.create(null); + const tokensRE = /([^\s,;=]+)\s*(?:=\s*([^,;]+))?/g; + let match; + while (match = tokensRE.exec(str)) { + tokens[match[1]] = match[2]; + } + return tokens; +} +const isValidHeaderName = str => /^[-_a-zA-Z0-9^`|~,!#$%&'*+.]+$/.test(str.trim()); +function matchHeaderValue(context, value, header, filter, isHeaderNameFilter) { + if (utils$1.isFunction(filter)) { + return filter.call(this, value, header); + } + if (isHeaderNameFilter) { + value = header; + } + if (!utils$1.isString(value)) return; + if (utils$1.isString(filter)) { + return value.indexOf(filter) !== -1; + } + if (utils$1.isRegExp(filter)) { + return filter.test(value); + } +} +function formatHeader(header) { + return header.trim().toLowerCase().replace(/([a-z\d])(\w*)/g, (w, char, str) => { + return char.toUpperCase() + str; + }); +} +function buildAccessors(obj, header) { + const accessorName = utils$1.toCamelCase(' ' + header); + ['get', 'set', 'has'].forEach(methodName => { + Object.defineProperty(obj, methodName + accessorName, { + // Null-proto descriptor so a polluted Object.prototype.get cannot turn + // this data descriptor into an accessor descriptor on the way in. + __proto__: null, + value: function (arg1, arg2, arg3) { + return this[methodName].call(this, header, arg1, arg2, arg3); + }, + configurable: true + }); + }); +} +class AxiosHeaders { + constructor(headers) { + headers && this.set(headers); + } + set(header, valueOrRewrite, rewrite) { + const self = this; + function setHeader(_value, _header, _rewrite) { + const lHeader = normalizeHeader(_header); + if (!lHeader) { + throw new Error('header name must be a non-empty string'); + } + const key = utils$1.findKey(self, lHeader); + if (!key || self[key] === undefined || _rewrite === true || _rewrite === undefined && self[key] !== false) { + self[key || _header] = normalizeValue(_value); + } + } + const setHeaders = (headers, _rewrite) => utils$1.forEach(headers, (_value, _header) => setHeader(_value, _header, _rewrite)); + if (utils$1.isPlainObject(header) || header instanceof this.constructor) { + setHeaders(header, valueOrRewrite); + } else if (utils$1.isString(header) && (header = header.trim()) && !isValidHeaderName(header)) { + setHeaders(parseHeaders(header), valueOrRewrite); + } else if (utils$1.isObject(header) && utils$1.isIterable(header)) { + let obj = {}, + dest, + key; + for (const entry of header) { + if (!utils$1.isArray(entry)) { + throw TypeError('Object iterator must return a key-value pair'); + } + obj[key = entry[0]] = (dest = obj[key]) ? utils$1.isArray(dest) ? [...dest, entry[1]] : [dest, entry[1]] : entry[1]; + } + setHeaders(obj, valueOrRewrite); + } else { + header != null && setHeader(valueOrRewrite, header, rewrite); + } + return this; + } + get(header, parser) { + header = normalizeHeader(header); + if (header) { + const key = utils$1.findKey(this, header); + if (key) { + const value = this[key]; + if (!parser) { + return value; + } + if (parser === true) { + return parseTokens(value); + } + if (utils$1.isFunction(parser)) { + return parser.call(this, value, key); + } + if (utils$1.isRegExp(parser)) { + return parser.exec(value); + } + throw new TypeError('parser must be boolean|regexp|function'); + } + } + } + has(header, matcher) { + header = normalizeHeader(header); + if (header) { + const key = utils$1.findKey(this, header); + return !!(key && this[key] !== undefined && (!matcher || matchHeaderValue(this, this[key], key, matcher))); + } + return false; + } + delete(header, matcher) { + const self = this; + let deleted = false; + function deleteHeader(_header) { + _header = normalizeHeader(_header); + if (_header) { + const key = utils$1.findKey(self, _header); + if (key && (!matcher || matchHeaderValue(self, self[key], key, matcher))) { + delete self[key]; + deleted = true; + } + } + } + if (utils$1.isArray(header)) { + header.forEach(deleteHeader); + } else { + deleteHeader(header); + } + return deleted; + } + clear(matcher) { + const keys = Object.keys(this); + let i = keys.length; + let deleted = false; + while (i--) { + const key = keys[i]; + if (!matcher || matchHeaderValue(this, this[key], key, matcher, true)) { + delete this[key]; + deleted = true; + } + } + return deleted; + } + normalize(format) { + const self = this; + const headers = {}; + utils$1.forEach(this, (value, header) => { + const key = utils$1.findKey(headers, header); + if (key) { + self[key] = normalizeValue(value); + delete self[header]; + return; + } + const normalized = format ? formatHeader(header) : String(header).trim(); + if (normalized !== header) { + delete self[header]; + } + self[normalized] = normalizeValue(value); + headers[normalized] = true; + }); + return this; + } + concat(...targets) { + return this.constructor.concat(this, ...targets); + } + toJSON(asStrings) { + const obj = Object.create(null); + utils$1.forEach(this, (value, header) => { + value != null && value !== false && (obj[header] = asStrings && utils$1.isArray(value) ? value.join(', ') : value); + }); + return obj; + } + [Symbol.iterator]() { + return Object.entries(this.toJSON())[Symbol.iterator](); + } + toString() { + return Object.entries(this.toJSON()).map(([header, value]) => header + ': ' + value).join('\n'); + } + getSetCookie() { + return this.get('set-cookie') || []; + } + get [Symbol.toStringTag]() { + return 'AxiosHeaders'; + } + static from(thing) { + return thing instanceof this ? thing : new this(thing); + } + static concat(first, ...targets) { + const computed = new this(first); + targets.forEach(target => computed.set(target)); + return computed; + } + static accessor(header) { + const internals = this[$internals] = this[$internals] = { + accessors: {} + }; + const accessors = internals.accessors; + const prototype = this.prototype; + function defineAccessor(_header) { + const lHeader = normalizeHeader(_header); + if (!accessors[lHeader]) { + buildAccessors(prototype, _header); + accessors[lHeader] = true; + } + } + utils$1.isArray(header) ? header.forEach(defineAccessor) : defineAccessor(header); + return this; + } +} +AxiosHeaders.accessor(['Content-Type', 'Content-Length', 'Accept', 'Accept-Encoding', 'User-Agent', 'Authorization']); + +// reserved names hotfix +utils$1.reduceDescriptors(AxiosHeaders.prototype, ({ + value +}, key) => { + let mapped = key[0].toUpperCase() + key.slice(1); // map `set` => `Set` + return { + get: () => value, + set(headerValue) { + this[mapped] = headerValue; + } + }; +}); +utils$1.freezeMethods(AxiosHeaders); + +const REDACTED = '[REDACTED ****]'; +function hasOwnOrPrototypeToJSON(source) { + if (utils$1.hasOwnProp(source, 'toJSON')) { + return true; + } + let prototype = Object.getPrototypeOf(source); + while (prototype && prototype !== Object.prototype) { + if (utils$1.hasOwnProp(prototype, 'toJSON')) { + return true; + } + prototype = Object.getPrototypeOf(prototype); + } + return false; +} + +// Build a plain-object snapshot of `config` and replace the value of any key +// (case-insensitive) listed in `redactKeys` with REDACTED. Walks through arrays +// and AxiosHeaders, and short-circuits on circular references. +function redactConfig(config, redactKeys) { + const lowerKeys = new Set(redactKeys.map(k => String(k).toLowerCase())); + const seen = []; + const visit = source => { + if (source === null || typeof source !== 'object') return source; + if (utils$1.isBuffer(source)) return source; + if (seen.indexOf(source) !== -1) return undefined; + if (source instanceof AxiosHeaders) { + source = source.toJSON(); + } + seen.push(source); + let result; + if (utils$1.isArray(source)) { + result = []; + source.forEach((v, i) => { + const reducedValue = visit(v); + if (!utils$1.isUndefined(reducedValue)) { + result[i] = reducedValue; + } + }); + } else { + if (!utils$1.isPlainObject(source) && hasOwnOrPrototypeToJSON(source)) { + seen.pop(); + return source; + } + result = Object.create(null); + for (const [key, value] of Object.entries(source)) { + const reducedValue = lowerKeys.has(key.toLowerCase()) ? REDACTED : visit(value); + if (!utils$1.isUndefined(reducedValue)) { + result[key] = reducedValue; + } + } + } + seen.pop(); + return result; + }; + return visit(config); +} class AxiosError extends Error { static from(error, code, config, request, response, customProps) { const axiosError = new AxiosError(error.message, code || error.code, config, request, response); @@ -46069,6 +46466,9 @@ class AxiosError extends Error { // The native Error constructor sets message as non-enumerable, // but axios < v1.13.3 had it as enumerable Object.defineProperty(this, 'message', { + // Null-proto descriptor so a polluted Object.prototype.get cannot turn + // this data descriptor into an accessor descriptor on the way in. + __proto__: null, value: message, enumerable: true, writable: true, @@ -46085,6 +46485,13 @@ class AxiosError extends Error { } } toJSON() { + // Opt-in redaction: when the request config carries a `redact` array, the + // value of any matching key (case-insensitive, at any depth) is replaced + // with REDACTED in the serialized snapshot. Undefined or empty leaves the + // existing serialization behavior unchanged. + const config = this.config; + const redactKeys = config && utils$1.hasOwnProp(config, 'redact') ? config.redact : undefined; + const serializedConfig = utils$1.isArray(redactKeys) && redactKeys.length > 0 ? redactConfig(config, redactKeys) : utils$1.toJSONObject(config); return { // Standard message: this.message, @@ -46098,7 +46505,7 @@ class AxiosError extends Error { columnNumber: this.columnNumber, stack: this.stack, // Axios - config: utils$1.toJSONObject(this.config), + config: serializedConfig, code: this.code, status: this.status }; @@ -46110,6 +46517,7 @@ AxiosError.ERR_BAD_OPTION_VALUE = 'ERR_BAD_OPTION_VALUE'; AxiosError.ERR_BAD_OPTION = 'ERR_BAD_OPTION'; AxiosError.ECONNABORTED = 'ECONNABORTED'; AxiosError.ETIMEDOUT = 'ETIMEDOUT'; +AxiosError.ECONNREFUSED = 'ECONNREFUSED'; AxiosError.ERR_NETWORK = 'ERR_NETWORK'; AxiosError.ERR_FR_TOO_MANY_REDIRECTS = 'ERR_FR_TOO_MANY_REDIRECTS'; AxiosError.ERR_DEPRECATED = 'ERR_DEPRECATED'; @@ -46118,6 +46526,7 @@ AxiosError.ERR_BAD_REQUEST = 'ERR_BAD_REQUEST'; AxiosError.ERR_CANCELED = 'ERR_CANCELED'; AxiosError.ERR_NOT_SUPPORT = 'ERR_NOT_SUPPORT'; AxiosError.ERR_INVALID_URL = 'ERR_INVALID_URL'; +AxiosError.ERR_FORM_DATA_DEPTH_EXCEEDED = 'ERR_FORM_DATA_DEPTH_EXCEEDED'; /** * Determines if the given thing is a array or js object. @@ -46219,6 +46628,7 @@ function toFormData(obj, formData, options) { const dots = options.dots; const indexes = options.indexes; const _Blob = options.Blob || typeof Blob !== 'undefined' && Blob; + const maxDepth = options.maxDepth === undefined ? 100 : options.maxDepth; const useBlob = _Blob && utils$1.isSpecCompliantForm(formData); if (!utils$1.isFunction(visitor)) { throw new TypeError('visitor must be a function'); @@ -46285,8 +46695,11 @@ function toFormData(obj, formData, options) { convertValue, isVisitable }); - function build(value, path) { + function build(value, path, depth = 0) { if (utils$1.isUndefined(value)) return; + if (depth > maxDepth) { + throw new AxiosError('Object is too deeply nested (' + depth + ' levels). Max depth: ' + maxDepth, AxiosError.ERR_FORM_DATA_DEPTH_EXCEEDED); + } if (stack.indexOf(value) !== -1) { throw Error('Circular reference detected in ' + path.join('.')); } @@ -46294,7 +46707,7 @@ function toFormData(obj, formData, options) { utils$1.forEach(value, function each(el, key) { const result = !(utils$1.isUndefined(el) || el === null) && visitor.call(formData, el, utils$1.isString(key) ? key.trim() : key, path, exposedHelpers); if (result === true) { - build(el, path ? path.concat(key) : [key]); + build(el, path ? path.concat(key) : [key], depth + 1); } }); stack.pop(); @@ -46321,10 +46734,9 @@ function encode$1(str) { '(': '%28', ')': '%29', '~': '%7E', - '%20': '+', - '%00': '\x00' + '%20': '+' }; - return encodeURIComponent(str).replace(/[!'()~]|%20|%00/g, function replacer(match) { + return encodeURIComponent(str).replace(/[!'()~]|%20/g, function replacer(match) { return charMap[match]; }); } @@ -46625,7 +47037,7 @@ function formDataToJSON(formData) { name = !name && utils$1.isArray(target) ? target.length : name; if (isLast) { if (utils$1.hasOwnProp(target, name)) { - target[name] = [target[name], value]; + target[name] = utils$1.isArray(target[name]) ? target[name].concat(value) : [target[name], value]; } else { target[name] = value; } @@ -46650,6 +47062,8 @@ function formDataToJSON(formData) { return null; } +const own = (obj, key) => obj != null && utils$1.hasOwnProp(obj, key) ? obj[key] : undefined; + /** * It takes a string, tries to parse it, and if it fails, it returns the stringified version * of the input @@ -46699,14 +47113,16 @@ const defaults = { } let isFileList; if (isObjectPayload) { + const formSerializer = own(this, 'formSerializer'); if (contentType.indexOf('application/x-www-form-urlencoded') > -1) { - return toURLEncodedForm(data, this.formSerializer).toString(); + return toURLEncodedForm(data, formSerializer).toString(); } if ((isFileList = utils$1.isFileList(data)) || contentType.indexOf('multipart/form-data') > -1) { - const _FormData = this.env && this.env.FormData; + const env = own(this, 'env'); + const _FormData = env && env.FormData; return toFormData(isFileList ? { 'files[]': data - } : data, _FormData && new _FormData(), this.formSerializer); + } : data, _FormData && new _FormData(), formSerializer); } } if (isObjectPayload || hasJSONContentType) { @@ -46716,21 +47132,22 @@ const defaults = { return data; }], transformResponse: [function transformResponse(data) { - const transitional = this.transitional || defaults.transitional; + const transitional = own(this, 'transitional') || defaults.transitional; const forcedJSONParsing = transitional && transitional.forcedJSONParsing; - const JSONRequested = this.responseType === 'json'; + const responseType = own(this, 'responseType'); + const JSONRequested = responseType === 'json'; if (utils$1.isResponse(data) || utils$1.isReadableStream(data)) { return data; } - if (data && utils$1.isString(data) && (forcedJSONParsing && !this.responseType || JSONRequested)) { + if (data && utils$1.isString(data) && (forcedJSONParsing && !responseType || JSONRequested)) { const silentJSONParsing = transitional && transitional.silentJSONParsing; const strictJSONParsing = !silentJSONParsing && JSONRequested; try { - return JSON.parse(data, this.parseReviver); + return JSON.parse(data, own(this, 'parseReviver')); } catch (e) { if (strictJSONParsing) { if (e.name === 'SyntaxError') { - throw AxiosError.from(e, AxiosError.ERR_BAD_RESPONSE, this, null, this.response); + throw AxiosError.from(e, AxiosError.ERR_BAD_RESPONSE, this, null, own(this, 'response')); } throw e; } @@ -46761,311 +47178,10 @@ const defaults = { } } }; -utils$1.forEach(['delete', 'get', 'head', 'post', 'put', 'patch'], method => { +utils$1.forEach(['delete', 'get', 'head', 'post', 'put', 'patch', 'query'], method => { defaults.headers[method] = {}; }); -// RawAxiosHeaders whose duplicates are ignored by node -// c.f. https://nodejs.org/api/http.html#http_message_headers -const ignoreDuplicateOf = utils$1.toObjectSet(['age', 'authorization', 'content-length', 'content-type', 'etag', 'expires', 'from', 'host', 'if-modified-since', 'if-unmodified-since', 'last-modified', 'location', 'max-forwards', 'proxy-authorization', 'referer', 'retry-after', 'user-agent']); - -/** - * Parse headers into an object - * - * ``` - * Date: Wed, 27 Aug 2014 08:58:49 GMT - * Content-Type: application/json - * Connection: keep-alive - * Transfer-Encoding: chunked - * ``` - * - * @param {String} rawHeaders Headers needing to be parsed - * - * @returns {Object} Headers parsed into an object - */ -var parseHeaders = rawHeaders => { - const parsed = {}; - let key; - let val; - let i; - rawHeaders && rawHeaders.split('\n').forEach(function parser(line) { - i = line.indexOf(':'); - key = line.substring(0, i).trim().toLowerCase(); - val = line.substring(i + 1).trim(); - if (!key || parsed[key] && ignoreDuplicateOf[key]) { - return; - } - if (key === 'set-cookie') { - if (parsed[key]) { - parsed[key].push(val); - } else { - parsed[key] = [val]; - } - } else { - parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val; - } - }); - return parsed; -}; - -const $internals = Symbol('internals'); -const isValidHeaderValue = value => !/[\r\n]/.test(value); -function assertValidHeaderValue(value, header) { - if (value === false || value == null) { - return; - } - if (utils$1.isArray(value)) { - value.forEach(v => assertValidHeaderValue(v, header)); - return; - } - if (!isValidHeaderValue(String(value))) { - throw new Error(`Invalid character in header content ["${header}"]`); - } -} -function normalizeHeader(header) { - return header && String(header).trim().toLowerCase(); -} -function stripTrailingCRLF(str) { - let end = str.length; - while (end > 0) { - const charCode = str.charCodeAt(end - 1); - if (charCode !== 10 && charCode !== 13) { - break; - } - end -= 1; - } - return end === str.length ? str : str.slice(0, end); -} -function normalizeValue(value) { - if (value === false || value == null) { - return value; - } - return utils$1.isArray(value) ? value.map(normalizeValue) : stripTrailingCRLF(String(value)); -} -function parseTokens(str) { - const tokens = Object.create(null); - const tokensRE = /([^\s,;=]+)\s*(?:=\s*([^,;]+))?/g; - let match; - while (match = tokensRE.exec(str)) { - tokens[match[1]] = match[2]; - } - return tokens; -} -const isValidHeaderName = str => /^[-_a-zA-Z0-9^`|~,!#$%&'*+.]+$/.test(str.trim()); -function matchHeaderValue(context, value, header, filter, isHeaderNameFilter) { - if (utils$1.isFunction(filter)) { - return filter.call(this, value, header); - } - if (isHeaderNameFilter) { - value = header; - } - if (!utils$1.isString(value)) return; - if (utils$1.isString(filter)) { - return value.indexOf(filter) !== -1; - } - if (utils$1.isRegExp(filter)) { - return filter.test(value); - } -} -function formatHeader(header) { - return header.trim().toLowerCase().replace(/([a-z\d])(\w*)/g, (w, char, str) => { - return char.toUpperCase() + str; - }); -} -function buildAccessors(obj, header) { - const accessorName = utils$1.toCamelCase(' ' + header); - ['get', 'set', 'has'].forEach(methodName => { - Object.defineProperty(obj, methodName + accessorName, { - value: function (arg1, arg2, arg3) { - return this[methodName].call(this, header, arg1, arg2, arg3); - }, - configurable: true - }); - }); -} -class AxiosHeaders { - constructor(headers) { - headers && this.set(headers); - } - set(header, valueOrRewrite, rewrite) { - const self = this; - function setHeader(_value, _header, _rewrite) { - const lHeader = normalizeHeader(_header); - if (!lHeader) { - throw new Error('header name must be a non-empty string'); - } - const key = utils$1.findKey(self, lHeader); - if (!key || self[key] === undefined || _rewrite === true || _rewrite === undefined && self[key] !== false) { - assertValidHeaderValue(_value, _header); - self[key || _header] = normalizeValue(_value); - } - } - const setHeaders = (headers, _rewrite) => utils$1.forEach(headers, (_value, _header) => setHeader(_value, _header, _rewrite)); - if (utils$1.isPlainObject(header) || header instanceof this.constructor) { - setHeaders(header, valueOrRewrite); - } else if (utils$1.isString(header) && (header = header.trim()) && !isValidHeaderName(header)) { - setHeaders(parseHeaders(header), valueOrRewrite); - } else if (utils$1.isObject(header) && utils$1.isIterable(header)) { - let obj = {}, - dest, - key; - for (const entry of header) { - if (!utils$1.isArray(entry)) { - throw TypeError('Object iterator must return a key-value pair'); - } - obj[key = entry[0]] = (dest = obj[key]) ? utils$1.isArray(dest) ? [...dest, entry[1]] : [dest, entry[1]] : entry[1]; - } - setHeaders(obj, valueOrRewrite); - } else { - header != null && setHeader(valueOrRewrite, header, rewrite); - } - return this; - } - get(header, parser) { - header = normalizeHeader(header); - if (header) { - const key = utils$1.findKey(this, header); - if (key) { - const value = this[key]; - if (!parser) { - return value; - } - if (parser === true) { - return parseTokens(value); - } - if (utils$1.isFunction(parser)) { - return parser.call(this, value, key); - } - if (utils$1.isRegExp(parser)) { - return parser.exec(value); - } - throw new TypeError('parser must be boolean|regexp|function'); - } - } - } - has(header, matcher) { - header = normalizeHeader(header); - if (header) { - const key = utils$1.findKey(this, header); - return !!(key && this[key] !== undefined && (!matcher || matchHeaderValue(this, this[key], key, matcher))); - } - return false; - } - delete(header, matcher) { - const self = this; - let deleted = false; - function deleteHeader(_header) { - _header = normalizeHeader(_header); - if (_header) { - const key = utils$1.findKey(self, _header); - if (key && (!matcher || matchHeaderValue(self, self[key], key, matcher))) { - delete self[key]; - deleted = true; - } - } - } - if (utils$1.isArray(header)) { - header.forEach(deleteHeader); - } else { - deleteHeader(header); - } - return deleted; - } - clear(matcher) { - const keys = Object.keys(this); - let i = keys.length; - let deleted = false; - while (i--) { - const key = keys[i]; - if (!matcher || matchHeaderValue(this, this[key], key, matcher, true)) { - delete this[key]; - deleted = true; - } - } - return deleted; - } - normalize(format) { - const self = this; - const headers = {}; - utils$1.forEach(this, (value, header) => { - const key = utils$1.findKey(headers, header); - if (key) { - self[key] = normalizeValue(value); - delete self[header]; - return; - } - const normalized = format ? formatHeader(header) : String(header).trim(); - if (normalized !== header) { - delete self[header]; - } - self[normalized] = normalizeValue(value); - headers[normalized] = true; - }); - return this; - } - concat(...targets) { - return this.constructor.concat(this, ...targets); - } - toJSON(asStrings) { - const obj = Object.create(null); - utils$1.forEach(this, (value, header) => { - value != null && value !== false && (obj[header] = asStrings && utils$1.isArray(value) ? value.join(', ') : value); - }); - return obj; - } - [Symbol.iterator]() { - return Object.entries(this.toJSON())[Symbol.iterator](); - } - toString() { - return Object.entries(this.toJSON()).map(([header, value]) => header + ': ' + value).join('\n'); - } - getSetCookie() { - return this.get('set-cookie') || []; - } - get [Symbol.toStringTag]() { - return 'AxiosHeaders'; - } - static from(thing) { - return thing instanceof this ? thing : new this(thing); - } - static concat(first, ...targets) { - const computed = new this(first); - targets.forEach(target => computed.set(target)); - return computed; - } - static accessor(header) { - const internals = this[$internals] = this[$internals] = { - accessors: {} - }; - const accessors = internals.accessors; - const prototype = this.prototype; - function defineAccessor(_header) { - const lHeader = normalizeHeader(_header); - if (!accessors[lHeader]) { - buildAccessors(prototype, _header); - accessors[lHeader] = true; - } - } - utils$1.isArray(header) ? header.forEach(defineAccessor) : defineAccessor(header); - return this; - } -} -AxiosHeaders.accessor(['Content-Type', 'Content-Length', 'Accept', 'Accept-Encoding', 'User-Agent', 'Authorization']); - -// reserved names hotfix -utils$1.reduceDescriptors(AxiosHeaders.prototype, ({ - value -}, key) => { - let mapped = key[0].toUpperCase() + key.slice(1); // map `set` => `Set` - return { - get: () => value, - set(headerValue) { - this[mapped] = headerValue; - } - }; -}); -utils$1.freezeMethods(AxiosHeaders); - /** * Transform the data for a request or a response * @@ -47121,7 +47237,7 @@ function settle(resolve, reject, response) { if (!response.status || !validateStatus || validateStatus(response.status)) { resolve(response); } else { - reject(new AxiosError('Request failed with status code ' + response.status, [AxiosError.ERR_BAD_REQUEST, AxiosError.ERR_BAD_RESPONSE][Math.floor(response.status / 100) - 4], response.config, response.request, response)); + reject(new AxiosError('Request failed with status code ' + response.status, response.status >= 400 && response.status < 500 ? AxiosError.ERR_BAD_REQUEST : AxiosError.ERR_BAD_RESPONSE, response.config, response.request, response)); } } @@ -47166,7 +47282,7 @@ function combineURLs(baseURL, relativeURL) { */ function buildFullPath(baseURL, requestedURL, allowAbsoluteUrls) { let isRelativeUrl = !isAbsoluteURL(requestedURL); - if (baseURL && (isRelativeUrl || allowAbsoluteUrls == false)) { + if (baseURL && (isRelativeUrl || allowAbsoluteUrls === false)) { return combineURLs(baseURL, requestedURL); } return requestedURL; @@ -47268,10 +47384,10 @@ function getEnv(key) { return process.env[key.toLowerCase()] || process.env[key.toUpperCase()] || ''; } -const VERSION = "1.15.0"; +const VERSION = "1.16.0"; function parseProtocol(url) { - const match = /^([-+\w]{1,25})(:?\/\/|:)/.exec(url); + const match = /^([-+\w]{1,25}):(?:\/\/)?/.exec(url); return match && match[1] || ''; } @@ -47459,7 +47575,8 @@ class FormDataPart { if (isStringValue) { value = textEncoder.encode(String(value).replace(/\r?\n|\r\n?/g, CRLF)); } else { - headers += `Content-Type: ${value.type || 'application/octet-stream'}${CRLF}`; + const safeType = String(value.type || 'application/octet-stream').replace(/[\r\n]/g, ''); + headers += `Content-Type: ${safeType}${CRLF}`; } this.headers = textEncoder.encode(headers + CRLF); this.contentLength = isStringValue ? value.byteLength : value.size; @@ -47497,7 +47614,7 @@ const formDataToStream = (form, headersHandler, options) => { throw TypeError('FormData instance required'); } if (boundary.length < 1 || boundary.length > 70) { - throw Error('boundary must be 10-70 characters long'); + throw Error('boundary must be 1-70 characters long'); } const boundaryBytes = textEncoder.encode('--' + boundary + CRLF); const footerBytes = textEncoder.encode('--' + boundary + '--' + CRLF); @@ -47560,6 +47677,47 @@ const callbackify = (fn, reducer) => { } : fn; }; +const LOOPBACK_HOSTNAMES = new Set(['localhost']); +const isIPv4Loopback = host => { + const parts = host.split('.'); + if (parts.length !== 4) return false; + if (parts[0] !== '127') return false; + return parts.every(p => /^\d+$/.test(p) && Number(p) >= 0 && Number(p) <= 255); +}; +const isIPv6Loopback = host => { + // Collapse all-zero groups: any form of ::1 / 0:0:...:0:1 + // First, strip any leading "::" by normalising with Set lookup of common forms, + // then fall back to structural check. + if (host === '::1') return true; + + // Check IPv4-mapped IPv6 loopback: ::ffff: or ::ffff: + // Node's URL parser normalises ::ffff:127.0.0.1 → ::ffff:7f00:1 + const v4MappedDotted = host.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/i); + if (v4MappedDotted) return isIPv4Loopback(v4MappedDotted[1]); + const v4MappedHex = host.match(/^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i); + if (v4MappedHex) { + const high = parseInt(v4MappedHex[1], 16); + // High 16 bits must start with 127 (0x7f) — i.e. 0x7f00..0x7fff + return high >= 0x7f00 && high <= 0x7fff; + } + + // Full-form ::1 variants: any number of zero groups followed by trailing 1 + // e.g. 0:0:0:0:0:0:0:1, 0000:...:0001 + const groups = host.split(':'); + if (groups.length === 8) { + for (let i = 0; i < 7; i++) { + if (!/^0+$/.test(groups[i])) return false; + } + return /^0*1$/.test(groups[7]); + } + return false; +}; +const isLoopback = host => { + if (!host) return false; + if (LOOPBACK_HOSTNAMES.has(host)) return true; + if (isIPv4Loopback(host)) return true; + return isIPv6Loopback(host); +}; const DEFAULT_PORTS = { http: 80, https: 443, @@ -47589,6 +47747,27 @@ const parseNoProxyEntry = entry => { } return [entryHost, entryPort]; }; + +// Convert IPv4-mapped IPv6 (::ffff:0:0/96 prefix) to IPv4 dotted form so both +// sides of a NO_PROXY comparison see the same canonical address. Without this, +// `NO_PROXY=192.168.1.5` would not match a request to `http://[::ffff:192.168.1.5]/` +// (Node's URL parser normalises that to `[::ffff:c0a8:105]`), and vice-versa, +// allowing the proxy-bypass policy to be circumvented by using the alternate +// representation. Returns the input unchanged when not IPv4-mapped. +const IPV4_MAPPED_DOTTED_RE = /^(?:::|(?:0{1,4}:){1,4}:|(?:0{1,4}:){5})ffff:(\d+\.\d+\.\d+\.\d+)$/i; +const IPV4_MAPPED_HEX_RE = /^(?:::|(?:0{1,4}:){1,4}:|(?:0{1,4}:){5})ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i; +const unmapIPv4MappedIPv6 = host => { + if (typeof host !== 'string' || host.indexOf(':') === -1) return host; + const dotted = host.match(IPV4_MAPPED_DOTTED_RE); + if (dotted) return dotted[1]; + const hex = host.match(IPV4_MAPPED_HEX_RE); + if (hex) { + const high = parseInt(hex[1], 16); + const low = parseInt(hex[2], 16); + return `${high >> 8}.${high & 0xff}.${low >> 8}.${low & 0xff}`; + } + return host; +}; const normalizeNoProxyHost = hostname => { if (!hostname) { return hostname; @@ -47596,7 +47775,7 @@ const normalizeNoProxyHost = hostname => { if (hostname.charAt(0) === '[' && hostname.charAt(hostname.length - 1) === ']') { hostname = hostname.slice(1, -1); } - return hostname.replace(/\.+$/, ''); + return unmapIPv4MappedIPv6(hostname.replace(/\.+$/, '')); }; function shouldBypassProxy(location) { let parsed; @@ -47632,7 +47811,7 @@ function shouldBypassProxy(location) { if (entryHost.charAt(0) === '.') { return hostname.endsWith(entryHost); } - return hostname === entryHost; + return hostname === entryHost || isLoopback(hostname) && isLoopback(entryHost); }); } @@ -47719,19 +47898,19 @@ const progressEventReducer = (listener, isDownloadStream, freq = 3) => { let bytesNotified = 0; const _speedometer = speedometer(50, 250); return throttle(e => { - const loaded = e.loaded; + const rawLoaded = e.loaded; const total = e.lengthComputable ? e.total : undefined; - const progressBytes = loaded - bytesNotified; + const loaded = total != null ? Math.min(rawLoaded, total) : rawLoaded; + const progressBytes = Math.max(0, loaded - bytesNotified); const rate = _speedometer(progressBytes); - const inRange = loaded <= total; - bytesNotified = loaded; + bytesNotified = Math.max(bytesNotified, loaded); const data = { loaded, total, progress: total ? loaded / total : undefined, bytes: progressBytes, rate: rate ? rate : undefined, - estimated: rate && total && inRange ? (total - loaded) / rate : undefined, + estimated: rate && total ? (total - loaded) / rate : undefined, event: e, lengthComputable: total != null, [isDownloadStream ? 'download' : 'upload']: true @@ -47809,7 +47988,34 @@ function estimateDataURLDecodedBytes(url) { const bytes = groups * 3 - (pad || 0); return bytes > 0 ? bytes : 0; } - return Buffer.byteLength(body, 'utf8'); + if (typeof Buffer !== 'undefined' && typeof Buffer.byteLength === 'function') { + return Buffer.byteLength(body, 'utf8'); + } + + // Compute UTF-8 byte length directly from UTF-16 code units without allocating + // a byte buffer (TextEncoder.encode would defeat the DoS guard on large bodies). + // Using body.length here would undercount non-ASCII (e.g. '€' is 1 code unit + // but 3 UTF-8 bytes). + let bytes = 0; + for (let i = 0, len = body.length; i < len; i++) { + const c = body.charCodeAt(i); + if (c < 0x80) { + bytes += 1; + } else if (c < 0x800) { + bytes += 2; + } else if (c >= 0xd800 && c <= 0xdbff && i + 1 < len) { + const next = body.charCodeAt(i + 1); + if (next >= 0xdc00 && next <= 0xdfff) { + bytes += 4; + i++; + } else { + bytes += 3; + } + } else { + bytes += 3; + } + } + return bytes; } const zlibOptions = { @@ -47826,9 +48032,41 @@ const { https: httpsFollow } = followRedirects; const isHttps = /https:?/; +const FORM_DATA_CONTENT_HEADERS$1 = ['content-type', 'content-length']; +function setFormDataHeaders$1(headers, formHeaders, policy) { + if (policy !== 'content-only') { + headers.set(formHeaders); + return; + } + Object.entries(formHeaders).forEach(([key, val]) => { + if (FORM_DATA_CONTENT_HEADERS$1.includes(key.toLowerCase())) { + headers.set(key, val); + } + }); +} + +// Symbols used to bind a single 'error' listener to a pooled socket and track +// the request currently owning that socket across keep-alive reuse (issue #10780). +const kAxiosSocketListener = Symbol('axios.http.socketListener'); +const kAxiosCurrentReq = Symbol('axios.http.currentReq'); const supportedProtocols = platform.protocols.map(protocol => { return protocol + ':'; }); + +// Node's WHATWG URL parser returns `username` and `password` percent-encoded. +// Decode before composing the `auth` option so credentials such as +// `my%40email.com:pass` are sent as `my@email.com:pass`. Falls back to the +// original value for malformed input so a bad encoding never throws. +const decodeURIComponentSafe = value => { + if (!utils$1.isString(value)) { + return value; + } + try { + return decodeURIComponent(value); + } catch (error) { + return value; + } +}; const flushOnFinish = (stream, [throttled, flush]) => { stream.on('end', flush).on('error', flush); return throttled; @@ -47916,12 +48154,12 @@ const http2Sessions = new Http2Sessions(); * * @returns {Object} */ -function dispatchBeforeRedirect(options, responseDetails) { +function dispatchBeforeRedirect(options, responseDetails, requestDetails) { if (options.beforeRedirects.proxy) { options.beforeRedirects.proxy(options); } if (options.beforeRedirects.config) { - options.beforeRedirects.config(options, responseDetails); + options.beforeRedirects.config(options, responseDetails, requestDetails); } } @@ -47934,7 +48172,7 @@ function dispatchBeforeRedirect(options, responseDetails) { * * @returns {http.ClientRequestArgs} */ -function setProxy(options, configProxy, location) { +function setProxy(options, configProxy, location, isRedirect) { let proxy = configProxy; if (!proxy && proxy !== false) { const proxyUrl = getProxyForUrl(location); @@ -47944,39 +48182,80 @@ function setProxy(options, configProxy, location) { } } } + // On redirect re-invocation, strip any stale Proxy-Authorization header carried + // over from the prior request (e.g. new target no longer uses a proxy, or uses + // a different proxy). Skip on the initial request so user-supplied headers are + // preserved. Header names are case-insensitive, so remove every case variant. + if (isRedirect && options.headers) { + for (const name of Object.keys(options.headers)) { + if (name.toLowerCase() === 'proxy-authorization') { + delete options.headers[name]; + } + } + } if (proxy) { + // Read proxy fields without traversing the prototype chain. URL instances expose + // username/password/hostname/host/port/protocol via getters on URL.prototype (so + // direct reads are shielded), but plain object proxies — and the `auth` field + // (which URL does not expose) — must be guarded so a polluted Object.prototype + // (e.g. Object.prototype.auth = { username, password }) cannot inject + // attacker-controlled credentials into the Proxy-Authorization header or + // redirect proxying to an attacker-controlled host. + const isProxyURL = proxy instanceof URL; + const readProxyField = key => isProxyURL || utils$1.hasOwnProp(proxy, key) ? proxy[key] : undefined; + const proxyUsername = readProxyField('username'); + const proxyPassword = readProxyField('password'); + let proxyAuth = utils$1.hasOwnProp(proxy, 'auth') ? proxy.auth : undefined; + // Basic proxy authorization - if (proxy.username) { - proxy.auth = (proxy.username || '') + ':' + (proxy.password || ''); - } - if (proxy.auth) { - // Support proxy auth object form - const validProxyAuth = Boolean(proxy.auth.username || proxy.auth.password); + if (proxyUsername) { + proxyAuth = (proxyUsername || '') + ':' + (proxyPassword || ''); + } + if (proxyAuth) { + // Support proxy auth object form. Read sub-fields via own-prop checks so a + // plain object inheriting from polluted Object.prototype cannot leak creds. + const authIsObject = typeof proxyAuth === 'object'; + const authUsername = authIsObject && utils$1.hasOwnProp(proxyAuth, 'username') ? proxyAuth.username : undefined; + const authPassword = authIsObject && utils$1.hasOwnProp(proxyAuth, 'password') ? proxyAuth.password : undefined; + const validProxyAuth = Boolean(authUsername || authPassword); if (validProxyAuth) { - proxy.auth = (proxy.auth.username || '') + ':' + (proxy.auth.password || ''); - } else if (typeof proxy.auth === 'object') { + proxyAuth = (authUsername || '') + ':' + (authPassword || ''); + } else if (authIsObject) { throw new AxiosError('Invalid proxy authorization', AxiosError.ERR_BAD_OPTION, { proxy }); } - const base64 = Buffer.from(proxy.auth, 'utf8').toString('base64'); + const base64 = Buffer.from(proxyAuth, 'utf8').toString('base64'); options.headers['Proxy-Authorization'] = 'Basic ' + base64; } - options.headers.host = options.hostname + (options.port ? ':' + options.port : ''); - const proxyHost = proxy.hostname || proxy.host; + + // Preserve a user-supplied Host header (case-insensitive) so callers can override + // the value forwarded to the proxy; otherwise default to the request URL's host. + let hasUserHostHeader = false; + for (const name of Object.keys(options.headers)) { + if (name.toLowerCase() === 'host') { + hasUserHostHeader = true; + break; + } + } + if (!hasUserHostHeader) { + options.headers.host = options.hostname + (options.port ? ':' + options.port : ''); + } + const proxyHost = readProxyField('hostname') || readProxyField('host'); options.hostname = proxyHost; // Replace 'host' since options is not a URL object options.host = proxyHost; - options.port = proxy.port; + options.port = readProxyField('port'); options.path = location; - if (proxy.protocol) { - options.protocol = proxy.protocol.includes(':') ? proxy.protocol : `${proxy.protocol}:`; + const proxyProtocol = readProxyField('protocol'); + if (proxyProtocol) { + options.protocol = proxyProtocol.includes(':') ? proxyProtocol : `${proxyProtocol}:`; } } options.beforeRedirects.proxy = function beforeRedirect(redirectOptions) { // Configure proxy for redirected request, passing the original config proxy to apply // the exact same logic as if the redirected request was performed by axios directly. - setProxy(redirectOptions, configProxy, redirectOptions.href); + setProxy(redirectOptions, configProxy, redirectOptions.href, true); }; } const isHttpAdapterSupported = typeof process !== 'undefined' && utils$1.kindOf(process) === 'process'; @@ -48059,21 +48338,20 @@ const http2Transport = { /*eslint consistent-return:0*/ var httpAdapter = isHttpAdapterSupported && function httpAdapter(config) { return wrapAsync(async function dispatchHttpRequest(resolve, reject, onDone) { - let { - data, - lookup, - family, - httpVersion = 1, - http2Options - } = config; - const { - responseType, - responseEncoding - } = config; + const own = key => utils$1.hasOwnProp(config, key) ? config[key] : undefined; + let data = own('data'); + let lookup = own('lookup'); + let family = own('family'); + let httpVersion = own('httpVersion'); + if (httpVersion === undefined) httpVersion = 1; + let http2Options = own('http2Options'); + const responseType = own('responseType'); + const responseEncoding = own('responseEncoding'); const method = config.method.toUpperCase(); let isDone; let rejected = false; let req; + let connectPhaseTimer; httpVersion = +httpVersion; if (Number.isNaN(httpVersion)) { throw TypeError(`Invalid protocol version: '${config.httpVersion}' is not a number`); @@ -48103,8 +48381,23 @@ var httpAdapter = isHttpAdapterSupported && function httpAdapter(config) { console.warn('emit error', err); } } + function clearConnectPhaseTimer() { + if (connectPhaseTimer) { + clearTimeout(connectPhaseTimer); + connectPhaseTimer = null; + } + } + function createTimeoutError() { + let timeoutErrorMessage = config.timeout ? 'timeout of ' + config.timeout + 'ms exceeded' : 'timeout exceeded'; + const transitional = config.transitional || transitionalDefaults; + if (config.timeoutErrorMessage) { + timeoutErrorMessage = config.timeoutErrorMessage; + } + return new AxiosError(timeoutErrorMessage, transitional.clarifyTimeoutError ? AxiosError.ETIMEDOUT : AxiosError.ECONNABORTED, config, req); + } abortEmitter.once('abort', reject); const onFinished = () => { + clearConnectPhaseTimer(); if (config.cancelToken) { config.cancelToken.unsubscribe(abort); } @@ -48121,6 +48414,7 @@ var httpAdapter = isHttpAdapterSupported && function httpAdapter(config) { } onDone((response, isRejected) => { isDone = true; + clearConnectPhaseTimer(); if (isRejected) { rejected = true; onFinished(); @@ -48213,8 +48507,8 @@ var httpAdapter = isHttpAdapterSupported && function httpAdapter(config) { boundary: userBoundary && userBoundary[1] || undefined }); // support for https://www.npmjs.com/package/form-data api - } else if (utils$1.isFormData(data) && utils$1.isFunction(data.getHeaders)) { - headers.set(data.getHeaders()); + } else if (utils$1.isFormData(data) && utils$1.isFunction(data.getHeaders) && data.getHeaders !== Object.prototype.getHeaders) { + setFormDataHeaders$1(headers, data.getHeaders(), own('formDataHeaderPolicy')); if (!headers.hasContentLength()) { try { const knownLength = await util.promisify(data.getLength).call(data); @@ -48262,20 +48556,21 @@ var httpAdapter = isHttpAdapterSupported && function httpAdapter(config) { // HTTP basic authentication let auth = undefined; - if (config.auth) { - const username = config.auth.username || ''; - const password = config.auth.password || ''; + const configAuth = own('auth'); + if (configAuth) { + const username = configAuth.username || ''; + const password = configAuth.password || ''; auth = username + ':' + password; } if (!auth && parsed.username) { - const urlUsername = parsed.username; - const urlPassword = parsed.password; + const urlUsername = decodeURIComponentSafe(parsed.username); + const urlPassword = decodeURIComponentSafe(parsed.password); auth = urlUsername + ':' + urlPassword; } auth && headers.delete('authorization'); - let path; + let path$1; try { - path = buildURL(parsed.pathname + parsed.search, config.params, config.paramsSerializer).replace(/^\?/, ''); + path$1 = buildURL(parsed.pathname + parsed.search, config.params, config.paramsSerializer).replace(/^\?/, ''); } catch (err) { const customErr = new Error(err.message); customErr.config = config; @@ -48284,8 +48579,11 @@ var httpAdapter = isHttpAdapterSupported && function httpAdapter(config) { return reject(customErr); } headers.set('Accept-Encoding', 'gzip, compress, deflate' + (isBrotliSupported ? ', br' : ''), false); - const options = { - path, + + // Null-prototype to block prototype pollution gadgets on properties read + // directly by Node's http.request (e.g. insecureHTTPParser, lookup). + const options = Object.assign(Object.create(null), { + path: path$1, method: method, headers: headers.toJSON(), agents: { @@ -48296,13 +48594,24 @@ var httpAdapter = isHttpAdapterSupported && function httpAdapter(config) { protocol, family, beforeRedirect: dispatchBeforeRedirect, - beforeRedirects: {}, + beforeRedirects: Object.create(null), http2Options - }; + }); // cacheable-lookup integration hotfix !utils$1.isUndefined(lookup) && (options.lookup = lookup); if (config.socketPath) { + if (typeof config.socketPath !== 'string') { + return reject(new AxiosError('socketPath must be a string', AxiosError.ERR_BAD_OPTION_VALUE, config)); + } + if (config.allowedSocketPaths != null) { + const allowed = Array.isArray(config.allowedSocketPaths) ? config.allowedSocketPaths : [config.allowedSocketPaths]; + const resolvedSocket = path.resolve(config.socketPath); + const isAllowed = allowed.some(entry => typeof entry === 'string' && path.resolve(entry) === resolvedSocket); + if (!isAllowed) { + return reject(new AxiosError(`socketPath "${config.socketPath}" is not permitted by allowedSocketPaths`, AxiosError.ERR_BAD_OPTION_VALUE, config)); + } + } options.socketPath = config.socketPath; } else { options.hostname = parsed.hostname.startsWith('[') ? parsed.hostname.slice(1, -1) : parsed.hostname; @@ -48310,21 +48619,25 @@ var httpAdapter = isHttpAdapterSupported && function httpAdapter(config) { setProxy(options, config.proxy, protocol + '//' + parsed.hostname + (parsed.port ? ':' + parsed.port : '') + options.path); } let transport; + let isNativeTransport = false; const isHttpsRequest = isHttps.test(options.protocol); options.agent = isHttpsRequest ? config.httpsAgent : config.httpAgent; if (isHttp2) { transport = http2Transport; } else { - if (config.transport) { - transport = config.transport; + const configTransport = own('transport'); + if (configTransport) { + transport = configTransport; } else if (config.maxRedirects === 0) { transport = isHttpsRequest ? https : http; + isNativeTransport = true; } else { if (config.maxRedirects) { options.maxRedirects = config.maxRedirects; } - if (config.beforeRedirect) { - options.beforeRedirects.config = config.beforeRedirect; + const configBeforeRedirect = own('beforeRedirect'); + if (configBeforeRedirect) { + options.beforeRedirects.config = configBeforeRedirect; } transport = isHttpsRequest ? httpsFollow : httpFollow; } @@ -48335,12 +48648,15 @@ var httpAdapter = isHttpAdapterSupported && function httpAdapter(config) { // follow-redirects does not skip comparison, so it should always succeed for axios -1 unlimited options.maxBodyLength = Infinity; } - if (config.insecureHTTPParser) { - options.insecureHTTPParser = config.insecureHTTPParser; - } + + // Always set an explicit own value so a polluted + // Object.prototype.insecureHTTPParser cannot enable the lenient parser + // through Node's internal options copy + options.insecureHTTPParser = Boolean(own('insecureHTTPParser')); // Create the request req = transport.request(options, function handleResponse(res) { + clearConnectPhaseTimer(); if (req.destroyed) return; const streams = [res]; const responseLength = utils$1.toFiniteNumber(res.headers['content-length']); @@ -48402,6 +48718,25 @@ var httpAdapter = isHttpAdapterSupported && function httpAdapter(config) { request: lastRequest }; if (responseType === 'stream') { + // Enforce maxContentLength on streamed responses; previously this + // was applied only to buffered responses. + if (config.maxContentLength > -1) { + const limit = config.maxContentLength; + const source = responseStream; + async function* enforceMaxContentLength() { + let totalResponseBytes = 0; + for await (const chunk of source) { + totalResponseBytes += chunk.length; + if (totalResponseBytes > limit) { + throw new AxiosError('maxContentLength size of ' + limit + ' exceeded', AxiosError.ERR_BAD_RESPONSE, config, lastRequest); + } + yield chunk; + } + } + responseStream = stream.Readable.from(enforceMaxContentLength(), { + objectMode: false + }); + } response.data = responseStream; settle(resolve, reject, response); } else { @@ -48423,13 +48758,13 @@ var httpAdapter = isHttpAdapterSupported && function httpAdapter(config) { if (rejected) { return; } - const err = new AxiosError('stream has been aborted', AxiosError.ERR_BAD_RESPONSE, config, lastRequest); + const err = new AxiosError('stream has been aborted', AxiosError.ERR_BAD_RESPONSE, config, lastRequest, response); responseStream.destroy(err); reject(err); }); responseStream.on('error', function handleStreamError(err) { - if (req.destroyed) return; - reject(AxiosError.from(err, null, config, lastRequest)); + if (rejected) return; + reject(AxiosError.from(err, null, config, lastRequest, response)); }); responseStream.on('end', function handleStreamEnd() { try { @@ -48468,9 +48803,44 @@ var httpAdapter = isHttpAdapterSupported && function httpAdapter(config) { }); // set tcp keep alive to prevent drop connection by peer + // Track every socket bound to this outer RedirectableRequest so a single + // 'close' listener can release ownership on all of them. follow-redirects + // re-emits the 'socket' event for each hop's native request onto the same + // outer request, so attaching per-request listeners inside this handler + // would accumulate across hops and trigger MaxListenersExceededWarning at + // >= 11 redirects. Clearing only the last-bound socket would leave stale + // kAxiosCurrentReq refs on earlier hop sockets returned to the keep-alive + // pool, causing an idle-pool 'error' to be attributed to a closed req. + const boundSockets = new Set(); req.on('socket', function handleRequestSocket(socket) { // default interval of sending ack packet is 1 minute socket.setKeepAlive(true, 1000 * 60); + + // Install a single 'error' listener per socket (not per request) to avoid + // accumulating listeners on pooled keep-alive sockets that get reassigned + // to new requests before the previous request's 'close' fires (issue #10780). + // The listener is bound to the socket's currently-active request via a + // symbol, which is swapped as the socket is reassigned. + if (!socket[kAxiosSocketListener]) { + socket.on('error', function handleSocketError(err) { + const current = socket[kAxiosCurrentReq]; + if (current && !current.destroyed) { + current.destroy(err); + } + }); + socket[kAxiosSocketListener] = true; + } + socket[kAxiosCurrentReq] = req; + boundSockets.add(socket); + }); + req.once('close', function clearCurrentReq() { + clearConnectPhaseTimer(); + for (const socket of boundSockets) { + if (socket[kAxiosCurrentReq] === req) { + socket[kAxiosCurrentReq] = null; + } + } + boundSockets.clear(); }); // Handle request timeout @@ -48481,21 +48851,23 @@ var httpAdapter = isHttpAdapterSupported && function httpAdapter(config) { abort(new AxiosError('error trying to parse `config.timeout` to int', AxiosError.ERR_BAD_OPTION_VALUE, config, req)); return; } + const handleTimeout = function handleTimeout() { + if (isDone) return; + abort(createTimeoutError()); + }; + if (isNativeTransport && timeout > 0) { + // Native ClientRequest#setTimeout starts from the socket lifecycle and + // may not fire while TCP connect is still pending. Mirror the + // follow-redirects wall-clock timer for the maxRedirects === 0 path. + connectPhaseTimer = setTimeout(handleTimeout, timeout); + } // Sometime, the response will be very slow, and does not respond, the connect event will be block by event loop system. // And timer callback will be fired, and abort() will be invoked before connection, then get "socket hang up" and code ECONNRESET. // At this time, if we have a large number of request, nodejs will hang up some socket on background. and the number will up and up. // And then these socket which be hang up will devouring CPU little by little. // ClientRequest.setTimeout will be fired on the specify milliseconds, and can make sure that abort() will be fired after connect. - req.setTimeout(timeout, function handleRequestTimeout() { - if (isDone) return; - let timeoutErrorMessage = config.timeout ? 'timeout of ' + config.timeout + 'ms exceeded' : 'timeout exceeded'; - const transitional = config.transitional || transitionalDefaults; - if (config.timeoutErrorMessage) { - timeoutErrorMessage = config.timeoutErrorMessage; - } - abort(new AxiosError(timeoutErrorMessage, transitional.clarifyTimeoutError ? AxiosError.ETIMEDOUT : AxiosError.ECONNABORTED, config, req)); - }); + req.setTimeout(timeout, handleTimeout); } else { // explicitly reset the socket timeout value for a possible `keep-alive` request req.setTimeout(0); @@ -48517,7 +48889,28 @@ var httpAdapter = isHttpAdapterSupported && function httpAdapter(config) { abort(new CanceledError('Request stream has been aborted', config, req)); } }); - data.pipe(req); + + // Enforce maxBodyLength for streamed uploads on the native http/https + // transport (maxRedirects === 0); follow-redirects enforces it on the + // other path. + let uploadStream = data; + if (config.maxBodyLength > -1 && config.maxRedirects === 0) { + const limit = config.maxBodyLength; + let bytesSent = 0; + uploadStream = stream.pipeline([data, new stream.Transform({ + transform(chunk, _enc, cb) { + bytesSent += chunk.length; + if (bytesSent > limit) { + return cb(new AxiosError('Request body larger than maxBodyLength limit', AxiosError.ERR_BAD_REQUEST, config, req)); + } + cb(null, chunk); + } + })], utils$1.noop); + uploadStream.on('error', err => { + if (!req.destroyed) req.destroy(err); + }); + } + uploadStream.pipe(req); } else { data && req.write(data); req.end(); @@ -48555,8 +48948,20 @@ var cookies = platform.hasStandardBrowserEnv ? }, read(name) { if (typeof document === 'undefined') return null; - const match = document.cookie.match(new RegExp('(?:^|; )' + name + '=([^;]*)')); - return match ? decodeURIComponent(match[1]) : null; + // Match name=value by splitting on the semicolon separator instead of building a + // RegExp from `name` — interpolating an unescaped string into a RegExp would let + // metacharacters (e.g. `.+?` in an attacker-influenced cookie name) cause ReDoS or + // match the wrong cookie. Browsers may serialize cookie pairs as either ";" or + // "; ", so ignore optional whitespace before each cookie name. + const cookies = document.cookie.split(';'); + for (let i = 0; i < cookies.length; i++) { + const cookie = cookies[i].replace(/^\s+/, ''); + const eq = cookie.indexOf('='); + if (eq !== -1 && cookie.slice(0, eq) === name) { + return decodeURIComponent(cookie.slice(eq + 1)); + } + } + return null; }, remove(name) { this.write(name, '', Date.now() - 86400000, '/'); @@ -48587,7 +48992,21 @@ const headersToObject = thing => thing instanceof AxiosHeaders ? { function mergeConfig(config1, config2) { // eslint-disable-next-line no-param-reassign config2 = config2 || {}; - const config = {}; + + // Use a null-prototype object so that downstream reads such as `config.auth` + // or `config.baseURL` cannot inherit polluted values from Object.prototype. + // `hasOwnProperty` is restored as a non-enumerable own slot to preserve + // ergonomics for user code that relies on it. + const config = Object.create(null); + Object.defineProperty(config, 'hasOwnProperty', { + // Null-proto descriptor so a polluted Object.prototype.get cannot turn + // this data descriptor into an accessor descriptor on the way in. + __proto__: null, + value: Object.prototype.hasOwnProperty, + enumerable: false, + writable: true, + configurable: true + }); function getMergedValue(target, source, prop, caseless) { if (utils$1.isPlainObject(target) && utils$1.isPlainObject(source)) { return utils$1.merge.call({ @@ -48626,9 +49045,9 @@ function mergeConfig(config1, config2) { // eslint-disable-next-line consistent-return function mergeDirectKeys(a, b, prop) { - if (prop in config2) { + if (utils$1.hasOwnProp(config2, prop)) { return getMergedValue(a, b); - } else if (prop in config1) { + } else if (utils$1.hasOwnProp(config1, prop)) { return getMergedValue(undefined, a); } } @@ -48659,6 +49078,7 @@ function mergeConfig(config1, config2) { httpsAgent: defaultToConfig2, cancelToken: defaultToConfig2, socketPath: defaultToConfig2, + allowedSocketPaths: defaultToConfig2, responseEncoding: defaultToConfig2, validateStatus: mergeDirectKeys, headers: (a, b, prop) => mergeDeepProperties(headersToObject(a), headersToObject(b), prop, true) @@ -48669,42 +49089,64 @@ function mergeConfig(config1, config2) { }), function computeConfigValue(prop) { if (prop === '__proto__' || prop === 'constructor' || prop === 'prototype') return; const merge = utils$1.hasOwnProp(mergeMap, prop) ? mergeMap[prop] : mergeDeepProperties; - const configValue = merge(config1[prop], config2[prop], prop); + const a = utils$1.hasOwnProp(config1, prop) ? config1[prop] : undefined; + const b = utils$1.hasOwnProp(config2, prop) ? config2[prop] : undefined; + const configValue = merge(a, b, prop); utils$1.isUndefined(configValue) && merge !== mergeDirectKeys || (config[prop] = configValue); }); return config; } +const FORM_DATA_CONTENT_HEADERS = ['content-type', 'content-length']; +function setFormDataHeaders(headers, formHeaders, policy) { + if (policy !== 'content-only') { + headers.set(formHeaders); + return; + } + Object.entries(formHeaders).forEach(([key, val]) => { + if (FORM_DATA_CONTENT_HEADERS.includes(key.toLowerCase())) { + headers.set(key, val); + } + }); +} + +/** + * Encode a UTF-8 string to a Latin-1 byte string for use with btoa(). + * This is a modern replacement for the deprecated unescape(encodeURIComponent(str)) pattern. + * + * @param {string} str The string to encode + * + * @returns {string} UTF-8 bytes as a Latin-1 string + */ +const encodeUTF8 = str => encodeURIComponent(str).replace(/%([0-9A-F]{2})/gi, (_, hex) => String.fromCharCode(parseInt(hex, 16))); var resolveConfig = config => { const newConfig = mergeConfig({}, config); - let { - data, - withXSRFToken, - xsrfHeaderName, - xsrfCookieName, - headers, - auth - } = newConfig; + + // Read only own properties to prevent prototype pollution gadgets + // (e.g. Object.prototype.baseURL = 'https://evil.com'). + const own = key => utils$1.hasOwnProp(newConfig, key) ? newConfig[key] : undefined; + const data = own('data'); + let withXSRFToken = own('withXSRFToken'); + const xsrfHeaderName = own('xsrfHeaderName'); + const xsrfCookieName = own('xsrfCookieName'); + let headers = own('headers'); + const auth = own('auth'); + const baseURL = own('baseURL'); + const allowAbsoluteUrls = own('allowAbsoluteUrls'); + const url = own('url'); newConfig.headers = headers = AxiosHeaders.from(headers); - newConfig.url = buildURL(buildFullPath(newConfig.baseURL, newConfig.url, newConfig.allowAbsoluteUrls), config.params, config.paramsSerializer); + newConfig.url = buildURL(buildFullPath(baseURL, url, allowAbsoluteUrls), config.params, config.paramsSerializer); // HTTP basic authentication if (auth) { - headers.set('Authorization', 'Basic ' + btoa((auth.username || '') + ':' + (auth.password ? unescape(encodeURIComponent(auth.password)) : ''))); + headers.set('Authorization', 'Basic ' + btoa((auth.username || '') + ':' + (auth.password ? encodeUTF8(auth.password) : ''))); } if (utils$1.isFormData(data)) { if (platform.hasStandardBrowserEnv || platform.hasStandardBrowserWebWorkerEnv) { headers.setContentType(undefined); // browser handles it } else if (utils$1.isFunction(data.getHeaders)) { // Node.js FormData (like form-data package) - const formHeaders = data.getHeaders(); - // Only set safe headers to avoid overwriting security headers - const allowedHeaders = ['content-type', 'content-length']; - Object.entries(formHeaders).forEach(([key, val]) => { - if (allowedHeaders.includes(key.toLowerCase())) { - headers.set(key, val); - } - }); + setFormDataHeaders(headers, data.getHeaders(), own('formDataHeaderPolicy')); } } @@ -48713,9 +49155,15 @@ var resolveConfig = config => { // Specifically not if we're in a web worker, or react-native. if (platform.hasStandardBrowserEnv) { - withXSRFToken && utils$1.isFunction(withXSRFToken) && (withXSRFToken = withXSRFToken(newConfig)); - if (withXSRFToken || withXSRFToken !== false && isURLSameOrigin(newConfig.url)) { - // Add xsrf header + if (utils$1.isFunction(withXSRFToken)) { + withXSRFToken = withXSRFToken(newConfig); + } + + // Strict boolean check — prevents proto-pollution gadgets (e.g. Object.prototype.withXSRFToken = 1) + // and misconfigurations (e.g. "false") from short-circuiting the same-origin check and leaking + // the XSRF token cross-origin. + const shouldSendXSRF = withXSRFToken === true || withXSRFToken == null && isURLSameOrigin(newConfig.url); + if (shouldSendXSRF) { const xsrfValue = xsrfHeaderName && xsrfCookieName && cookies.read(xsrfCookieName); if (xsrfValue) { headers.set(xsrfHeaderName, xsrfValue); @@ -48791,7 +49239,7 @@ var xhrAdapter = isXHRAdapterSupported && function (config) { // handled by onerror instead // With one exception: request that using file: protocol, most browsers // will return status as 0 even though it's a successful request - if (request.status === 0 && !(request.responseURL && request.responseURL.indexOf('file:') === 0)) { + if (request.status === 0 && !(request.responseURL && request.responseURL.startsWith('file:'))) { return; } // readystate handler is calling before onerror or ontimeout handlers, @@ -48806,6 +49254,7 @@ var xhrAdapter = isXHRAdapterSupported && function (config) { return; } reject(new AxiosError('Request aborted', AxiosError.ECONNABORTED, config, request)); + done(); // Clean up request request = null; @@ -48821,6 +49270,7 @@ var xhrAdapter = isXHRAdapterSupported && function (config) { // attach the underlying event for consumers who want details err.event = event || null; reject(err); + done(); request = null; }; @@ -48832,6 +49282,7 @@ var xhrAdapter = isXHRAdapterSupported && function (config) { timeoutErrorMessage = _config.timeoutErrorMessage; } reject(new AxiosError(timeoutErrorMessage, transitional.clarifyTimeoutError ? AxiosError.ETIMEDOUT : AxiosError.ECONNABORTED, config, request)); + done(); // Clean up request request = null; @@ -48878,6 +49329,7 @@ var xhrAdapter = isXHRAdapterSupported && function (config) { } reject(!cancel || cancel.type ? new CanceledError(null, config, request) : cancel); request.abort(); + done(); request = null; }; _config.cancelToken && _config.cancelToken.subscribe(onCanceled); @@ -48886,7 +49338,7 @@ var xhrAdapter = isXHRAdapterSupported && function (config) { } } const protocol = parseProtocol(_config.url); - if (protocol && platform.protocols.indexOf(protocol) === -1) { + if (protocol && !platform.protocols.includes(protocol)) { reject(new AxiosError('Unsupported protocol ' + protocol + ':', AxiosError.ERR_BAD_REQUEST, config)); return; } @@ -49020,17 +49472,6 @@ const DEFAULT_CHUNK_SIZE = 64 * 1024; const { isFunction } = utils$1; -const globalFetchAPI = (({ - Request, - Response -}) => ({ - Request, - Response -}))(utils$1.global); -const { - ReadableStream: ReadableStream$1, - TextEncoder: TextEncoder$1 -} = utils$1.global; const test = (fn, ...args) => { try { return !!fn(...args); @@ -49039,9 +49480,18 @@ const test = (fn, ...args) => { } }; const factory = env => { + var _utils$global; + const globalObject = (_utils$global = utils$1.global) !== null && _utils$global !== void 0 ? _utils$global : globalThis; + const { + ReadableStream, + TextEncoder + } = globalObject; env = utils$1.merge.call({ skipUndefined: true - }, globalFetchAPI, env); + }, { + Request: globalObject.Request, + Response: globalObject.Response + }, env); const { fetch: envFetch, Request, @@ -49053,20 +49503,22 @@ const factory = env => { if (!isFetchSupported) { return false; } - const isReadableStreamSupported = isFetchSupported && isFunction(ReadableStream$1); - const encodeText = isFetchSupported && (typeof TextEncoder$1 === 'function' ? (encoder => str => encoder.encode(str))(new TextEncoder$1()) : async str => new Uint8Array(await new Request(str).arrayBuffer())); + const isReadableStreamSupported = isFetchSupported && isFunction(ReadableStream); + const encodeText = isFetchSupported && (typeof TextEncoder === 'function' ? (encoder => str => encoder.encode(str))(new TextEncoder()) : async str => new Uint8Array(await new Request(str).arrayBuffer())); const supportsRequestStream = isRequestSupported && isReadableStreamSupported && test(() => { let duplexAccessed = false; - const body = new ReadableStream$1(); - const hasContentType = new Request(platform.origin, { - body, + const request = new Request(platform.origin, { + body: new ReadableStream(), method: 'POST', get duplex() { duplexAccessed = true; return 'half'; } - }).headers.has('Content-Type'); - body.cancel(); + }); + const hasContentType = request.headers.has('Content-Type'); + if (request.body != null) { + request.body.cancel(); + } return duplexAccessed && !hasContentType; }); const supportsResponseStream = isResponseSupported && isReadableStreamSupported && test(() => utils$1.isReadableStream(new Response('').body)); @@ -49125,8 +49577,12 @@ const factory = env => { responseType, headers, withCredentials = 'same-origin', - fetchOptions + fetchOptions, + maxContentLength, + maxBodyLength } = resolveConfig(config); + const hasMaxContentLength = utils$1.isNumber(maxContentLength) && maxContentLength > -1; + const hasMaxBodyLength = utils$1.isNumber(maxBodyLength) && maxBodyLength > -1; let _fetch = envFetch || fetch; responseType = responseType ? (responseType + '').toLowerCase() : 'text'; let composedSignal = composeSignals([signal, cancelToken && cancelToken.toAbortSignal()], timeout); @@ -49136,6 +49592,26 @@ const factory = env => { }); let requestContentLength; try { + // Enforce maxContentLength for data: URLs up-front so we never materialize + // an oversized payload. The HTTP adapter applies the same check (see http.js + // "if (protocol === 'data:')" branch). + if (hasMaxContentLength && typeof url === 'string' && url.startsWith('data:')) { + const estimated = estimateDataURLDecodedBytes(url); + if (estimated > maxContentLength) { + throw new AxiosError('maxContentLength size of ' + maxContentLength + ' exceeded', AxiosError.ERR_BAD_RESPONSE, config, request); + } + } + + // Enforce maxBodyLength against the outbound request body before dispatch. + // Mirrors http.js behavior (ERR_BAD_REQUEST / 'Request body larger than + // maxBodyLength limit'). Skip when the body length cannot be determined + // (e.g. a live ReadableStream supplied by the caller). + if (hasMaxBodyLength && method !== 'get' && method !== 'head') { + const outboundLength = await resolveBodyLength(headers, data); + if (typeof outboundLength === 'number' && isFinite(outboundLength) && outboundLength > maxBodyLength) { + throw new AxiosError('Request body larger than maxBodyLength limit', AxiosError.ERR_BAD_REQUEST, config, request); + } + } if (onUploadProgress && supportsRequestStream && method !== 'get' && method !== 'head' && (requestContentLength = await resolveBodyLength(headers, data)) !== 0) { let _request = new Request(url, { method: 'POST', @@ -49158,6 +49634,18 @@ const factory = env => { // Cloudflare Workers throws when credentials are defined // see https://github.com/cloudflare/workerd/issues/902 const isCredentialsSupported = isRequestSupported && 'credentials' in Request.prototype; + + // If data is FormData and Content-Type is multipart/form-data without boundary, + // delete it so fetch can set it correctly with the boundary + if (utils$1.isFormData(data)) { + const contentType = headers.getContentType(); + if (contentType && /^multipart\/form-data/i.test(contentType) && !/boundary=/i.test(contentType)) { + headers.delete('content-type'); + } + } + + // Set User-Agent header if not already set (fetch defaults to 'node' in Node.js) + headers.set('User-Agent', 'axios/' + VERSION, false); const resolvedOptions = { ...fetchOptions, signal: composedSignal, @@ -49169,21 +49657,59 @@ const factory = env => { }; request = isRequestSupported && new Request(url, resolvedOptions); let response = await (isRequestSupported ? _fetch(request, fetchOptions) : _fetch(url, resolvedOptions)); + + // Cheap pre-check: if the server honestly declares a content-length that + // already exceeds the cap, reject before we start streaming. + if (hasMaxContentLength) { + const declaredLength = utils$1.toFiniteNumber(response.headers.get('content-length')); + if (declaredLength != null && declaredLength > maxContentLength) { + throw new AxiosError('maxContentLength size of ' + maxContentLength + ' exceeded', AxiosError.ERR_BAD_RESPONSE, config, request); + } + } const isStreamResponse = supportsResponseStream && (responseType === 'stream' || responseType === 'response'); - if (supportsResponseStream && (onDownloadProgress || isStreamResponse && unsubscribe)) { + if (supportsResponseStream && response.body && (onDownloadProgress || hasMaxContentLength || isStreamResponse && unsubscribe)) { const options = {}; ['status', 'statusText', 'headers'].forEach(prop => { options[prop] = response[prop]; }); const responseContentLength = utils$1.toFiniteNumber(response.headers.get('content-length')); const [onProgress, flush] = onDownloadProgress && progressEventDecorator(responseContentLength, progressEventReducer(asyncDecorator(onDownloadProgress), true)) || []; - response = new Response(trackStream(response.body, DEFAULT_CHUNK_SIZE, onProgress, () => { + let bytesRead = 0; + const onChunkProgress = loadedBytes => { + if (hasMaxContentLength) { + bytesRead = loadedBytes; + if (bytesRead > maxContentLength) { + throw new AxiosError('maxContentLength size of ' + maxContentLength + ' exceeded', AxiosError.ERR_BAD_RESPONSE, config, request); + } + } + onProgress && onProgress(loadedBytes); + }; + response = new Response(trackStream(response.body, DEFAULT_CHUNK_SIZE, onChunkProgress, () => { flush && flush(); unsubscribe && unsubscribe(); }), options); } responseType = responseType || 'text'; let responseData = await resolvers[utils$1.findKey(resolvers, responseType) || 'text'](response, config); + + // Fallback enforcement for environments without ReadableStream support + // (legacy runtimes). Detect materialized size from typed output; skip + // streams/Response passthrough since the user will read those themselves. + if (hasMaxContentLength && !supportsResponseStream && !isStreamResponse) { + let materializedSize; + if (responseData != null) { + if (typeof responseData.byteLength === 'number') { + materializedSize = responseData.byteLength; + } else if (typeof responseData.size === 'number') { + materializedSize = responseData.size; + } else if (typeof responseData === 'string') { + materializedSize = typeof TextEncoder === 'function' ? new TextEncoder().encode(responseData).byteLength : responseData.length; + } + } + if (typeof materializedSize === 'number' && materializedSize > maxContentLength) { + throw new AxiosError('maxContentLength size of ' + maxContentLength + ' exceeded', AxiosError.ERR_BAD_RESPONSE, config, request); + } + } !isStreamResponse && unsubscribe && unsubscribe(); return await new Promise((resolve, reject) => { settle(resolve, reject, { @@ -49197,6 +49723,17 @@ const factory = env => { }); } catch (err) { unsubscribe && unsubscribe(); + + // Safari can surface fetch aborts as a DOMException-like object whose + // branded getters throw. Prefer our composed signal reason before reading + // the caught error, preserving timeout vs cancellation semantics. + if (composedSignal && composedSignal.aborted && composedSignal.reason instanceof AxiosError) { + const canceledError = composedSignal.reason; + canceledError.config = config; + request && (canceledError.request = request); + err !== canceledError && (canceledError.cause = err); + throw canceledError; + } if (err && err.name === 'TypeError' && /Load failed|fetch/i.test(err.message)) { throw Object.assign(new AxiosError('Network Error', AxiosError.ERR_NETWORK, config, request, err && err.response), { cause: err.cause || err @@ -49251,13 +49788,17 @@ const knownAdapters = { utils$1.forEach(knownAdapters, (fn, value) => { if (fn) { try { + // Null-proto descriptors so a polluted Object.prototype.get cannot turn + // these data descriptors into accessor descriptors on the way in. Object.defineProperty(fn, 'name', { + __proto__: null, value }); } catch (e) { // eslint-disable-next-line no-empty } Object.defineProperty(fn, 'adapterName', { + __proto__: null, value }); } @@ -49372,8 +49913,15 @@ function dispatchRequest(config) { return adapter(config).then(function onAdapterResolution(response) { throwIfCancellationRequested(config); - // Transform response data - response.data = transformData.call(config, config.transformResponse, response); + // Expose the current response on config so that transformResponse can + // attach it to any AxiosError it throws (e.g. on JSON parse failure). + // We clean it up afterwards to avoid polluting the config object. + config.response = response; + try { + response.data = transformData.call(config, config.transformResponse, response); + } finally { + delete config.response; + } response.headers = AxiosHeaders.from(response.headers); return response; }, function onAdapterRejection(reason) { @@ -49382,7 +49930,12 @@ function dispatchRequest(config) { // Transform response data if (reason && reason.response) { - reason.response.data = transformData.call(config, config.transformResponse, reason.response); + config.response = reason.response; + try { + reason.response.data = transformData.call(config, config.transformResponse, reason.response); + } finally { + delete config.response; + } reason.response.headers = AxiosHeaders.from(reason.response.headers); } } @@ -49453,7 +50006,9 @@ function assertOptions(options, schema, allowUnknown) { let i = keys.length; while (i-- > 0) { const opt = keys[i]; - const validator = schema[opt]; + // Use hasOwnProperty so a polluted Object.prototype. cannot supply + // a non-function validator and cause a TypeError. + const validator = Object.prototype.hasOwnProperty.call(schema, opt) ? schema[opt] : undefined; if (validator) { const value = options[opt]; const result = value === undefined || validator(value, opt, options); @@ -49585,7 +50140,7 @@ class Axios { // Flatten headers let contextHeaders = headers && utils$1.merge(headers.common, headers[config.method]); - headers && utils$1.forEach(['delete', 'get', 'head', 'post', 'put', 'patch', 'common'], method => { + headers && utils$1.forEach(['delete', 'get', 'head', 'post', 'put', 'patch', 'query', 'common'], method => { delete headers[method]; }); config.headers = AxiosHeaders.concat(contextHeaders, headers); @@ -49666,7 +50221,7 @@ utils$1.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoDa })); }; }); -utils$1.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) { +utils$1.forEach(['post', 'put', 'patch', 'query'], function forEachMethodWithData(method) { function generateHTTPMethod(isForm) { return function httpMethod(url, data, config) { return this.request(mergeConfig(config || {}, { @@ -49680,7 +50235,12 @@ utils$1.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) }; } Axios.prototype[method] = generateHTTPMethod(); - Axios.prototype[method + 'Form'] = generateHTTPMethod(true); + + // QUERY is a safe/idempotent read method; multipart form bodies don't fit + // its semantics, so no queryForm shorthand is generated. + if (method !== 'query') { + Axios.prototype[method + 'Form'] = generateHTTPMethod(true); + } }); /** diff --git a/package-lock.json b/package-lock.json index 5138ab9..1d794c7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -122,6 +122,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.12.tgz", "integrity": "sha512-XsMfHovsUYHFMdrIHkZphTN/2Hzzi78R08NuHfDBehym2VsPDL6Zn/JAD/JQdnRvbSsbQc4mVaU1m6JgtTEElg==", "dev": true, + "peer": true, "dependencies": { "@ampproject/remapping": "^2.1.0", "@babel/code-frame": "^7.18.6", @@ -1626,6 +1627,7 @@ "integrity": "sha512-plR3pp6D+SSUn1HM7xvSkx12/DhoHInI2YF35KAcVFNZvlC0gtrWqx7Qq1oH2Ssgi0vlFRCTbP+DZc7B9+TtsQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.59.2", "@typescript-eslint/types": "8.59.2", @@ -1878,6 +1880,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2171,11 +2174,12 @@ } }, "node_modules/axios": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.15.0.tgz", - "integrity": "sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.16.0.tgz", + "integrity": "sha512-6hp5CwvTPlN2A31g5dxnwAX0orzM7pmCRDLnZSX772mv8WDqICwFjowHuPs04Mc8deIld1+ejhtaMn5vp6b+1w==", + "license": "MIT", "dependencies": { - "follow-redirects": "^1.15.11", + "follow-redirects": "^1.16.0", "form-data": "^4.0.5", "proxy-from-env": "^2.1.0" } @@ -2339,6 +2343,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -3020,6 +3025,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", "dev": true, + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -3080,6 +3086,7 @@ "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", "dev": true, "license": "MIT", + "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -3271,6 +3278,7 @@ "integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==", "dev": true, "license": "MIT", + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -3714,9 +3722,9 @@ "license": "ISC" }, "node_modules/follow-redirects": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", - "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", "funding": [ { "type": "individual", @@ -4747,6 +4755,7 @@ "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", "dev": true, + "peer": true, "dependencies": { "@jest/core": "^29.7.0", "@jest/types": "^29.6.3", @@ -6114,6 +6123,7 @@ "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", "dev": true, + "peer": true, "bin": { "prettier": "bin-prettier.js" }, @@ -6895,6 +6905,7 @@ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -7157,6 +7168,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -7224,6 +7236,7 @@ "integrity": "sha512-N9lBGA9o9aqb1hVMc9hzySbhKibHmB+N3IpoShyV6HyQYRGIhlrO5rQgttypi+yEeKsKI4idxC8Jw6gXKD4THA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.49.0", "@typescript-eslint/types": "8.49.0", @@ -7848,6 +7861,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.12.tgz", "integrity": "sha512-XsMfHovsUYHFMdrIHkZphTN/2Hzzi78R08NuHfDBehym2VsPDL6Zn/JAD/JQdnRvbSsbQc4mVaU1m6JgtTEElg==", "dev": true, + "peer": true, "requires": { "@ampproject/remapping": "^2.1.0", "@babel/code-frame": "^7.18.6", @@ -8993,6 +9007,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.59.2.tgz", "integrity": "sha512-plR3pp6D+SSUn1HM7xvSkx12/DhoHInI2YF35KAcVFNZvlC0gtrWqx7Qq1oH2Ssgi0vlFRCTbP+DZc7B9+TtsQ==", "dev": true, + "peer": true, "requires": { "@typescript-eslint/scope-manager": "8.59.2", "@typescript-eslint/types": "8.59.2", @@ -9131,7 +9146,8 @@ "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "dev": true + "dev": true, + "peer": true }, "acorn-jsx": { "version": "5.3.2", @@ -9324,11 +9340,11 @@ "dev": true }, "axios": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.15.0.tgz", - "integrity": "sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.16.0.tgz", + "integrity": "sha512-6hp5CwvTPlN2A31g5dxnwAX0orzM7pmCRDLnZSX772mv8WDqICwFjowHuPs04Mc8deIld1+ejhtaMn5vp6b+1w==", "requires": { - "follow-redirects": "^1.15.11", + "follow-redirects": "^1.16.0", "form-data": "^4.0.5", "proxy-from-env": "^2.1.0" } @@ -9444,6 +9460,7 @@ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", "dev": true, + "peer": true, "requires": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -9917,6 +9934,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", "dev": true, + "peer": true, "requires": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -9967,6 +9985,7 @@ "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", "dev": true, + "peer": true, "requires": {} }, "eslint-import-resolver-node": { @@ -10095,7 +10114,8 @@ "version": "3.7.4", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.4.tgz", "integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==", - "dev": true + "dev": true, + "peer": true } } }, @@ -10388,9 +10408,9 @@ "dev": true }, "follow-redirects": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", - "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==" + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==" }, "for-each": { "version": "0.3.5", @@ -11042,6 +11062,7 @@ "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", "dev": true, + "peer": true, "requires": { "@jest/core": "^29.7.0", "@jest/types": "^29.6.3", @@ -12065,7 +12086,8 @@ "version": "2.8.8", "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", - "dev": true + "dev": true, + "peer": true }, "prettier-linter-helpers": { "version": "1.0.0", @@ -12593,7 +12615,8 @@ "version": "4.0.4", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "dev": true + "dev": true, + "peer": true } } }, @@ -12754,7 +12777,8 @@ "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true + "dev": true, + "peer": true }, "typescript-eslint": { "version": "8.49.0", @@ -12789,6 +12813,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.49.0.tgz", "integrity": "sha512-N9lBGA9o9aqb1hVMc9hzySbhKibHmB+N3IpoShyV6HyQYRGIhlrO5rQgttypi+yEeKsKI4idxC8Jw6gXKD4THA==", "dev": true, + "peer": true, "requires": { "@typescript-eslint/scope-manager": "8.49.0", "@typescript-eslint/types": "8.49.0",