diff --git a/dist/index.js b/dist/index.js index da065a2..ca84899 100644 --- a/dist/index.js +++ b/dist/index.js @@ -7750,7 +7750,7 @@ module.exports = require("zlib"); /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { "use strict"; -/*! Axios v1.18.1 Copyright (c) 2026 Matt Zabriskie and contributors */ +/*! Axios v1.20.0 Copyright (c) 2026 Matt Zabriskie and contributors */ var FormData$1 = __nccwpck_require__(6454); @@ -7797,13 +7797,57 @@ const { const hasOwnProperty = (({ hasOwnProperty }) => (obj, prop) => hasOwnProperty.call(obj, prop))(Object.prototype); +const isUnsafeObjectKey = prop => typeof prop === 'string' && (prop === '__proto__' || prop === 'constructor' || prop === 'prototype'); /** - * Walk the prototype chain (excluding the shared Object.prototype) looking for - * an own `prop`. This distinguishes genuine own/inherited members — including - * class accessors and template prototypes — from members injected via - * Object.prototype pollution (e.g. `Object.prototype.username = '...'`), which - * live on Object.prototype itself and are therefore never matched. + * Determine whether an inherited object must be treated as a shared-prototype + * boundary. Cross-realm Object.prototype objects cannot be distinguished + * reliably from application-created null-prototype objects because their + * properties are mutable, so all inherited terminal prototypes are excluded + * as a fail-closed boundary. A null-prototype source still keeps its own + * properties, as produced by mergeConfig and other safe materialization paths. + * + * @param {*} obj The object to inspect + * @param {*} prototype The object's prototype + * @param {boolean} source Whether obj is the original traversal source + * + * @returns {boolean} True when obj is a safe prototype traversal boundary + */ +const isPrototypeBoundary = (obj, prototype, source) => obj === Object.prototype || !source && prototype === null; + +/** + * Determine whether an object can retain its identity through code paths that + * add, replace, and remove config properties without bypassing unsafe-key + * filtering. Immutable objects, unsafe-key-bearing objects, and objects with + * accessor or restricted data properties must be materialized instead. + * + * @param {*} obj The object to inspect + * + * @returns {boolean} True when every own property is safe and fully mutable + */ +const isSafeAndFullyMutable = obj => { + if (!Object.isExtensible(obj)) { + return false; + } + const props = Object.getOwnPropertyNames(obj); + if (Object.getOwnPropertySymbols) { + props.push(...Object.getOwnPropertySymbols(obj)); + } + return props.every(prop => { + if (isUnsafeObjectKey(prop)) { + return false; + } + const descriptor = Object.getOwnPropertyDescriptor(obj, prop); + return !!descriptor && descriptor.configurable && descriptor.writable === true; + }); +}; + +/** + * Walk the prototype chain (excluding the source realm's Object.prototype) + * looking for an own `prop`. This distinguishes genuine own/inherited members + * — including class accessors and template prototypes — from members injected + * via Object.prototype pollution (e.g. `Object.prototype.username = '...'`), + * which live on Object.prototype itself and are therefore never matched. * * @param {*} thing The value whose chain to inspect * @param {string|symbol} prop The property key to look for @@ -7813,15 +7857,19 @@ const hasOwnProperty = (({ const hasOwnInPrototypeChain = (thing, prop) => { let obj = thing; const seen = []; - while (obj != null && obj !== Object.prototype) { + while (obj != null) { if (seen.indexOf(obj) !== -1) { return false; } seen.push(obj); + const prototype = getPrototypeOf(obj); + if (isPrototypeBoundary(obj, prototype, obj === thing)) { + return false; + } if (hasOwnProperty(obj, prop)) { return true; } - obj = getPrototypeOf(obj); + obj = prototype; } return false; }; @@ -7838,6 +7886,56 @@ const hasOwnInPrototypeChain = (thing, prop) => { * @returns {*} The resolved value, or undefined when unsafe/absent */ const getSafeProp = (obj, prop) => obj != null && hasOwnInPrototypeChain(obj, prop) ? obj[prop] : undefined; + +/** + * Flatten an object and its application-defined prototype chain into a + * null-prototype object. Members inherited only from the source realm's + * Object.prototype are deliberately excluded, while class/template members + * below that boundary are preserved. + * + * @param {*} thing The value to flatten + * + * @returns {*} A null-prototype copy, or the original value when it is already + * structurally safe or is not an object + */ +const toSafeFlatObject = thing => { + if (thing == null || typeof thing !== 'object' && typeof thing !== 'function') { + return thing; + } + const sourcePrototype = getPrototypeOf(thing); + if (sourcePrototype === null && isSafeAndFullyMutable(thing)) { + return thing; + } + const result = Object.create(null); + const merged = Object.create(null); + const seen = []; + let current = thing; + while (current != null) { + if (seen.indexOf(current) !== -1) { + break; + } + seen.push(current); + const prototype = current === thing ? sourcePrototype : getPrototypeOf(current); + if (isPrototypeBoundary(current, prototype, current === thing)) { + break; + } + const props = Object.getOwnPropertyNames(current); + if (Object.getOwnPropertySymbols) { + props.push(...Object.getOwnPropertySymbols(current)); + } + for (const prop of props) { + if (isUnsafeObjectKey(prop)) { + continue; + } + if (!hasOwnProperty(merged, prop)) { + result[prop] = thing[prop]; + merged[prop] = true; + } + } + current = prototype; + } + return result; +}; const kindOf = (cache => thing => { const str = toString.call(thing); return cache[str] || (cache[str] = str.slice(8, -1).toLowerCase()); @@ -7961,9 +8059,9 @@ const isPlainObject = val => { } const prototype = getPrototypeOf(val); return (prototype === null || prototype === Object.prototype || getPrototypeOf(prototype) === null) && - // Treat any genuine (non-Object.prototype-polluted) Symbol.toStringTag or - // Symbol.iterator as evidence the value is a tagged/iterable type rather - // than a plain object, while ignoring keys injected onto Object.prototype. + // Treat safe own/inherited Symbol.toStringTag or Symbol.iterator members as + // evidence the value is tagged/iterable, while ignoring members reachable + // only through shared or terminal prototype boundaries. !hasOwnInPrototypeChain(val, toStringTag) && !hasOwnInPrototypeChain(val, iterator); }; @@ -8047,6 +8145,7 @@ const isBlob = kindOfTest('Blob'); * @returns {boolean} True if value is a FileList, otherwise false */ const isFileList = kindOfTest('FileList'); +const isSet = kindOfTest('Set'); /** * Determine if a value is a Stream @@ -8569,11 +8668,20 @@ const toJSONObject = obj => { if (!('toJSON' in source)) { // add-on descent / delete-on-ascent: preserves path semantics, so DAG nodes serialise at every occurrence (see #7230). visited.add(source); - const target = isArray(source) ? [] : {}; - forEach(source, (value, key) => { - const reducedValue = visit(value); - !isUndefined(reducedValue) && (target[key] = reducedValue); - }); + let target; + if (isSet(source)) { + target = []; + for (const value of source) { + const reducedValue = visit(value); + !isUndefined(reducedValue) && target.push(reducedValue); + } + } else { + target = isArray(source) ? [] : {}; + forEach(source, (value, key) => { + const reducedValue = visit(value); + !isUndefined(reducedValue) && (target[key] = reducedValue); + }); + } visited.delete(source); return target; } @@ -8701,6 +8809,7 @@ var utils$1 = { // an alias to avoid ESLint no-prototype-builtins detection hasOwnInPrototypeChain, getSafeProp, + toSafeFlatObject, reduceDescriptors, freezeMethods, toObjectSet, @@ -8747,17 +8856,18 @@ var parseHeaders = rawHeaders => { i = line.indexOf(':'); key = line.substring(0, i).trim().toLowerCase(); val = line.substring(i + 1).trim(); - if (!key || parsed[key] && ignoreDuplicateOf[key]) { + const hasKey = utils$1.hasOwnProp(parsed, key); + if (!key || hasKey && utils$1.hasOwnProp(ignoreDuplicateOf, key)) { return; } if (key === 'set-cookie') { - if (parsed[key]) { + if (hasKey) { parsed[key].push(val); } else { parsed[key] = [val]; } } else { - parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val; + parsed[key] = hasKey ? parsed[key] + ', ' + val : val; } }); return parsed; @@ -8804,7 +8914,7 @@ function toByteStringHeaderObject(headers) { return byteStringHeaders; } -const $internals = Symbol('internals'); +const $internals$1 = Symbol('internals'); function normalizeHeader(header) { return header && String(header).trim().toLowerCase(); } @@ -8823,6 +8933,90 @@ function parseTokens(str) { } return tokens; } +const parameterNameRE = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/; +function trimOWS(value) { + let start = 0; + let end = value.length; + while (start < end) { + const code = value.charCodeAt(start); + if (code !== 0x09 && code !== 0x20) { + break; + } + start += 1; + } + while (end > start) { + const code = value.charCodeAt(end - 1); + if (code !== 0x09 && code !== 0x20) { + break; + } + end -= 1; + } + return start === 0 && end === value.length ? value : value.slice(start, end); +} +function decodeQuotedString(value) { + const last = value.length - 1; + if (last < 1 || value.charCodeAt(0) !== 0x22 || value.charCodeAt(last) !== 0x22) { + return value; + } + let decoded = ''; + for (let i = 1; i < last; i++) { + const code = value.charCodeAt(i); + if (code === 0x22) { + return value; + } + if (code === 0x5c) { + i += 1; + if (i >= last) { + return value; + } + } + decoded += value[i]; + } + return decoded; +} +function parseParameters(value) { + const parameters = Object.create(null); + const str = String(value); + let start = 0; + let quoted = false; + let escaped = false; + function parseParameter(end) { + const part = trimOWS(str.slice(start, end)); + const equals = part.indexOf('='); + if (equals < 1) { + return; + } + const name = trimOWS(part.slice(0, equals)); + if (!parameterNameRE.test(name)) { + return; + } + const normalizedName = name.toLowerCase(); + if (normalizedName === '__proto__' || normalizedName === 'constructor' || normalizedName === 'prototype') { + return; + } + const parameterValue = trimOWS(part.slice(equals + 1)); + parameters[normalizedName] = decodeQuotedString(parameterValue); + } + for (let i = 0; i < str.length; i++) { + const code = str.charCodeAt(i); + if (quoted) { + if (escaped) { + escaped = false; + } else if (code === 0x5c) { + escaped = true; + } else if (code === 0x22) { + quoted = false; + } + } else if (code === 0x22) { + quoted = true; + } else if (code === 0x2c || code === 0x3b) { + parseParameter(i); + start = i + 1; + } + } + parseParameter(str.length); + return parameters; +} const isValidHeaderName = str => /^[-_a-zA-Z0-9^`|~,!#$%&'*+.]+$/.test(str.trim()); function matchHeaderValue(context, value, header, filter, isHeaderNameFilter) { if (utils$1.isFunction(filter)) { @@ -9000,7 +9194,8 @@ class AxiosHeaders { return Object.entries(this.toJSON()).map(([header, value]) => header + ': ' + value).join('\n'); } getSetCookie() { - return this.get('set-cookie') || []; + const value = this.get('set-cookie'); + return utils$1.isArray(value) ? value : value == null || value === false ? [] : [value]; } get [Symbol.toStringTag]() { return 'AxiosHeaders'; @@ -9008,13 +9203,16 @@ class AxiosHeaders { static from(thing) { return thing instanceof this ? thing : new this(thing); } + static parseParameters(value) { + return parseParameters(value); + } 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] = { + const internals = this[$internals$1] = this[$internals$1] = { accessors: {} }; const accessors = internals.accessors; @@ -9102,9 +9300,33 @@ function redactConfig(config, redactKeys) { }; return visit(config); } +function stringifySafely$1(value) { + try { + return String(value); + } catch (err) { + return ''; + } +} +function aggregateErrorMessage(error) { + const message = error.errors.map(entry => { + try { + return entry && entry.message ? stringifySafely$1(entry.message) : stringifySafely$1(entry); + } catch (err) { + return ''; + } + }).filter(Boolean).join('; '); + return message || error.name || 'AggregateError'; +} class AxiosError extends Error { static from(error, code, config, request, response, customProps) { - const axiosError = new AxiosError(error.message, code || error.code, config, request, response); + // `AggregateError` (thrown by Node on dual-stack/Happy-Eyeballs connection + // failures) has an empty `message`; its detail lives in `errors[]`. Without + // this, the wrapped error surfaces with a blank message (see #6721). + let message = error.message; + if (!message && utils$1.isArray(error.errors) && error.errors.length) { + message = aggregateErrorMessage(error); + } + const axiosError = new AxiosError(message, code || error.code, config, request, response); // Match native `Error` `cause` semantics: non-enumerable. The wrapped // error often carries circular internals (sockets, requests, agents), so // an enumerable `cause` makes structured loggers (pino/winston) and any @@ -9208,6 +9430,15 @@ 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'; +var PlatformBuffer = { + isBufferAvailable() { + return typeof Buffer !== 'undefined'; + }, + from(value) { + return Buffer.from(value); + } +}; + // Default nesting limit shared with the inverse transform (formDataToJSON) so // the FormData <-> JSON round-trip stays symmetric. const DEFAULT_FORM_DATA_MAX_DEPTH = 100; @@ -9296,23 +9527,17 @@ function toFormData(obj, formData, options) { // eslint-disable-next-line no-param-reassign formData = formData || new (FormData$1 || FormData)(); - - // eslint-disable-next-line no-param-reassign - options = utils$1.toFlatObject(options, { - metaTokens: true, - dots: false, - indexes: false - }, false, function defined(option, source) { - // eslint-disable-next-line no-eq-null,eqeqeq - return !utils$1.isUndefined(source[option]); - }); - const metaTokens = options.metaTokens; + const option = (name, fallback) => { + const value = utils$1.getSafeProp(options, name); + return utils$1.isUndefined(value) ? fallback : value; + }; + const metaTokens = option('metaTokens', true); // eslint-disable-next-line no-use-before-define - const visitor = options.visitor || defaultVisitor; - const dots = options.dots; - const indexes = options.indexes; - const _Blob = options.Blob || typeof Blob !== 'undefined' && Blob; - const maxDepth = options.maxDepth === undefined ? DEFAULT_FORM_DATA_MAX_DEPTH : options.maxDepth; + const visitor = option('visitor') || defaultVisitor; + const dots = option('dots', false); + const indexes = option('indexes', false); + const _Blob = option('Blob') || typeof Blob !== 'undefined' && Blob; + const maxDepth = option('maxDepth', DEFAULT_FORM_DATA_MAX_DEPTH); const useBlob = _Blob && utils$1.isSpecCompliantForm(formData); const stack = []; if (!utils$1.isFunction(visitor)) { @@ -9333,8 +9558,8 @@ function toFormData(obj, formData, options) { if (useBlob && typeof _Blob === 'function') { return new _Blob([value]); } - if (typeof Buffer !== 'undefined') { - return Buffer.from(value); + if (PlatformBuffer && PlatformBuffer.isBufferAvailable()) { + return PlatformBuffer.from(value); } throw new AxiosError('Blob is not supported. Use a Buffer instead.', AxiosError.ERR_NOT_SUPPORT); } @@ -9525,9 +9750,51 @@ function buildURL(url, params, options) { return url; } +const $internals = Symbol('internals'); + +// `handlers` is public and may be replaced with a nullish value by user code; +// `clear()` has always tolerated that. Treat it as an empty stack rather than +// dereferencing it. +function countHandlers(handlers) { + return handlers ? handlers.length : 0; +} +function trimHandlers(handlers) { + if (!handlers) { + return; + } + while (handlers.length && handlers[handlers.length - 1] === null) { + handlers.pop(); + } +} +function syncHandlerEntries(manager, internals) { + const handlers = manager.handlers; + const length = countHandlers(handlers); + if (handlers !== internals.handlersRef) { + internals.handlersRef = handlers; + internals.handlerEntries.clear(); + } else if (length !== internals.handlersLength) { + if (!length) { + internals.handlerEntries.clear(); + } else { + internals.handlerEntries.forEach(function removeStaleEntry(entry, id) { + if (handlers[entry.index] !== entry.handler) { + internals.handlerEntries.delete(id); + } + }); + } + } + internals.handlersLength = length; +} class InterceptorManager { constructor() { this.handlers = []; + this[$internals] = { + handlersRef: this.handlers, + handlersLength: this.handlers.length, + handlerEntries: new Map(), + iterationDepth: 0, + nextId: 0 + }; } /** @@ -9540,13 +9807,25 @@ class InterceptorManager { * @return {Number} An ID used to remove interceptor later */ use(fulfilled, rejected, options) { - this.handlers.push({ + const handler = { fulfilled, rejected, synchronous: options ? options.synchronous : false, runWhen: options ? options.runWhen : null + }; + const internals = this[$internals]; + if (this.handlers == null) { + this.handlers = []; + } + syncHandlerEntries(this, internals); + const id = internals.nextId++; + this.handlers.push(handler); + internals.handlerEntries.set(id, { + handler, + index: this.handlers.length - 1 }); - return this.handlers.length - 1; + internals.handlersLength = this.handlers.length; + return id; } /** @@ -9557,8 +9836,23 @@ class InterceptorManager { * @returns {void} */ eject(id) { - if (this.handlers[id]) { - this.handlers[id] = null; + const internals = this[$internals]; + syncHandlerEntries(this, internals); + const entry = internals.handlerEntries.get(id); + if (entry) { + internals.handlerEntries.delete(id); + + // Ignore IDs invalidated by clear or direct replacement of handlers. + if (this.handlers[entry.index] !== entry.handler) { + return; + } + this.handlers[entry.index] = null; + + // Do not reuse an index while forEach is walking its length snapshot. + if (!internals.iterationDepth) { + trimHandlers(this.handlers); + internals.handlersLength = this.handlers.length; + } } } @@ -9570,6 +9864,7 @@ class InterceptorManager { clear() { if (this.handlers) { this.handlers = []; + syncHandlerEntries(this, this[$internals]); } } @@ -9584,11 +9879,22 @@ class InterceptorManager { * @returns {void} */ forEach(fn) { - utils$1.forEach(this.handlers, function forEachHandler(h) { - if (h !== null) { - fn(h); + const internals = this[$internals]; + syncHandlerEntries(this, internals); + internals.iterationDepth++; + try { + utils$1.forEach(this.handlers, function forEachHandler(h) { + if (h !== null) { + fn(h); + } + }); + } finally { + if (! --internals.iterationDepth) { + syncHandlerEntries(this, internals); + trimHandlers(this.handlers); + internals.handlersLength = countHandlers(this.handlers); } - }); + } } } @@ -9714,12 +10020,18 @@ function throwIfDepthExceeded(index) { * @returns An array of strings. */ function parsePropPath(name) { - // foo[x][y][z] - // foo.x.y.z - // foo-x-y-z - // foo x y z + // foo[x][y][z] -> ['foo', 'x', 'y', 'z'] + // foo.x.y.z -> ['foo', 'x', 'y', 'z'] + // A path is split on `.` and on `[...]` groups. A segment — whether written + // in dot notation or captured inside brackets — may contain any character + // except `.`, `[` and `]`, so a key like `user-name` or `user name` is kept + // literal instead of being split (#5402). `.`, `[` and `]` keep their existing + // meaning, e.g. `foo[bar.baz]` -> ['foo', 'bar', 'baz'] and `[]` is an array push. + // Excluding `[` from the bracket group also makes the match fail fast at the + // next `[`, so a malformed name cannot rescan to the end of the string from + // every unmatched `[` — parsing stays linear in the length of the name. const path = []; - const pattern = /\w+|\[(\w*)]/g; + const pattern = /[^.[\]]+|\[([^.[\]]*)]/g; let match; while ((match = pattern.exec(name)) !== null) { throwIfDepthExceeded(path.length); @@ -9790,6 +10102,8 @@ function formDataToJSON(formData) { return null; } +const methodList = Object.freeze(['get', 'delete', 'head', 'options', 'post', 'put', 'patch', 'purge', 'link', 'unlink', 'query']); + const own = (obj, key) => obj != null && utils$1.hasOwnProp(obj, key) ? obj[key] : undefined; /** @@ -9906,7 +10220,7 @@ const defaults = { } } }; -utils$1.forEach(['delete', 'get', 'head', 'post', 'put', 'patch', 'query'], method => { +utils$1.forEach(methodList, method => { defaults.headers[method] = {}; }); @@ -9995,24 +10309,70 @@ function isAbsoluteURL(url) { * @returns {string} The combined URL */ function combineURLs(baseURL, relativeURL) { - return relativeURL ? baseURL.replace(/\/?\/$/, '') + '/' + relativeURL.replace(/^\/+/, '') : baseURL; + if (!relativeURL) { + return baseURL; + } + let end = baseURL.length; + while (end > 0 && baseURL.charCodeAt(end - 1) === 47) { + end--; + } + return baseURL.slice(0, end) + '/' + relativeURL.replace(/^\/+/, ''); } -const malformedHttpProtocol = /^https?:(?!\/\/)/i; -const httpProtocolControlCharacters = /[\t\n\r]/g; -function stripLeadingC0ControlOrSpace(url) { - let i = 0; - while (i < url.length && url.charCodeAt(i) <= 0x20) { - i++; +const urlParserControlCharacters = /[\t\n\r]/g; + +/** + * Match WHATWG URL preprocessing before checking a URL's protocol. + * + * @param {string} url + * + * @returns {string} + */ +function normalizeURLForProtocolCheck(url) { + if (typeof url !== 'string') { + return url; } - return url.slice(i); + let start = 0; + while (start < url.length && url.charCodeAt(start) <= 0x20) { + start++; + } + return url.slice(start).replace(urlParserControlCharacters, ''); } -function normalizeURLForProtocolCheck(url) { - return stripLeadingC0ControlOrSpace(url).replace(httpProtocolControlCharacters, ''); + +const malformedHttpProtocol = /^https?:(?!\/\/)/i; + +// Redact the parts of a URL that can carry secrets before it is embedded in an +// error message. AxiosError.toJSON() serializes `message` verbatim and errors +// are commonly logged, while the opt-in `config.redact` model only cleans +// config keys — it cannot reach the message. Redact only the genuinely +// sensitive substrings — userinfo (credentials), query parameter values and +// fragment contents — with the same REDACTED marker the config redaction uses, +// while keeping the scheme, host, path and parameter names so the offending +// request stays accurately identifiable. +function redactFragment(fragment) { + if (!fragment) { + return fragment; + } + return fragment.replace(/(^|&)([^=&]*=)?[^&]+/g, (match, separator, parameterName = '') => { + return `${separator}${parameterName}${REDACTED}`; + }); +} +function redactSensitiveURLParts(url) { + const redactedURL = url.replace(/^(https?:\/{0,2})[^/?#]*@/i, `$1${REDACTED}@`); + const fragmentIndex = redactedURL.indexOf('#'); + const urlWithoutFragment = fragmentIndex === -1 ? redactedURL : redactedURL.slice(0, fragmentIndex); + const redactedURLWithoutFragment = urlWithoutFragment.replace(/([?&][^=&#]*=)[^&#]*/g, `$1${REDACTED}`); + if (fragmentIndex === -1) { + return redactedURLWithoutFragment; + } + return `${redactedURLWithoutFragment}#${redactFragment(redactedURL.slice(fragmentIndex + 1))}`; } function assertValidHttpProtocolURL(url, config) { - if (typeof url === 'string' && malformedHttpProtocol.test(normalizeURLForProtocolCheck(url))) { - throw new AxiosError('Invalid URL: missing "//" after protocol', AxiosError.ERR_INVALID_URL, config); + if (typeof url === 'string') { + const normalizedURL = normalizeURLForProtocolCheck(url); + if (malformedHttpProtocol.test(normalizedURL)) { + throw new AxiosError(`Invalid URL ${JSON.stringify(redactSensitiveURLParts(normalizedURL))}: missing "//" after protocol`, AxiosError.ERR_INVALID_URL, config); + } } } @@ -10132,7 +10492,7 @@ function getEnv(key) { return process.env[key.toLowerCase()] || process.env[key.toUpperCase()] || ''; } -const VERSION = "1.18.1"; +const VERSION = "1.20.0"; function parseProtocol(url) { const match = /^([-+\w]{1,25}):(?:\/\/)?/.exec(url); @@ -10141,7 +10501,7 @@ function parseProtocol(url) { // RFC 2397: data:[][;base64], // mediatype = type/subtype followed by optional ;name=value parameters -const DATA_URL_PATTERN = /^([^,;]+\/[^,;]+)?((?:;[^,;=]+=[^,;]+)*)(;base64)?,([\s\S]*)$/; +const DATA_URL_PATTERN = /^([^,;/]+\/[^,;/]+)?((?:;[^,;=]+=[^,;]+)*)(;base64)?,([\s\S]*)$/; /** * Parse data uri to a Buffer or Blob @@ -10192,6 +10552,31 @@ function fromDataURI(uri, asBlob, options) { throw new AxiosError('Unsupported protocol ' + protocol, AxiosError.ERR_NOT_SUPPORT); } +const FORM_DATA_CONTENT_HEADERS = ['content-type', 'content-length']; + +/** + * Apply the headers generated by a FormData implementation to the request headers, + * honoring the `formDataHeaderPolicy` option: with 'content-only', copy only the + * content-* headers; otherwise merge all of them. + * + * @param {AxiosHeaders} headers - the request headers to mutate + * @param {Object | null | undefined} formHeaders - headers produced by the FormData implementation + * @param {String} [policy] - the resolved `formDataHeaderPolicy` config value + * + * @returns {void} + */ +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); + } + }); +} + const kInternals = Symbol('internals'); class AxiosTransformStream extends stream.Transform { constructor(options) { @@ -10429,7 +10814,7 @@ class Http2Sessions { this.sessions = Object.create(null); } getSession(authority, options) { - options = Object.assign({ + options = Object.assign(Object.create(null), { sessionTimeout: 1000 }, options); let authoritySessions = this.sessions[authority]; @@ -10496,6 +10881,7 @@ class Http2Sessions { }; } session.once('close', removeSession); + session.once('error', removeSession); let entry = [session, options]; authoritySessions ? authoritySessions.push(entry) : authoritySessions = this.sessions[authority] = [entry]; return session; @@ -10516,12 +10902,117 @@ const callbackify = (fn, reducer) => { }; const LOOPBACK_HOSTNAMES = new Set(['localhost', '0.0.0.0']); +const trimTrailingDots = value => { + let end = value.length; + while (end && value.charCodeAt(end - 1) === 46) { + end--; + } + return end === value.length ? value : value.slice(0, end); +}; 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); }; + +/** + * Canonicalize an IPv4 address written in shorthand, octal, or hex form into + * dotted-decimal. IPv6 addresses and non-IP strings are returned unchanged so + * the existing IPv4-mapped IPv6 unmap path and the isLoopback path can still + * see them. + * + * Shorthand expansion mirrors Node's URL parser: literal parts fill from the + * left, the final part fills the remaining octets from the right with + * zero-padding on the left. + * 127.1 -> 127.0.0.1 + * 127.0.1 -> 127.0.0.1 + * 1.2.3 -> 1.2.0.3 + * + * Each octet is parsed with an explicit base: 16 for `0x`/`0X` prefix, 8 for + * zero-prefixed multi-digit all-`0-7` parts, 10 otherwise. Zero-prefixed + * decimal-looking parts that contain `8` or `9` are rejected to match Node's + * URL parser, and the comparison layer falls through to non-bypass if either + * side rejects the form (fail-safe). + * + * Returns the input unchanged on any parse failure, out-of-range octet, or + * unusual shape (1-part, 5+ parts) so the comparison layer fails closed. + */ +const parseIPv4Octet = text => { + if (/^0[xX][0-9a-fA-F]+$/.test(text)) { + const n = parseInt(text.slice(2), 16); + return Number.isFinite(n) ? n : null; + } + if (text.length > 1 && /^0[0-7]+$/.test(text)) { + const n = parseInt(text, 8); + return Number.isFinite(n) ? n : null; + } + if (text.length > 1 && /^0[0-9]+$/.test(text)) { + return null; + } + if (/^[0-9]+$/.test(text)) { + const n = parseInt(text, 10); + return Number.isFinite(n) ? n : null; + } + return null; +}; +const normalizeIPAddress = host => { + if (typeof host !== 'string' || !host || host.indexOf(':') !== -1) { + return host; + } + let h = host; + if (h.charAt(0) === '[' && h.charAt(h.length - 1) === ']') { + h = h.slice(1, -1); + } + h = trimTrailingDots(h); + + // Allowed characters for any IPv4 shape: digits, dot, 'x', 'X', hex digits. + if (!/^[0-9.xXa-fA-F]+$/.test(h)) return host; + const parts = h.split('.'); + + // No part may be empty (e.g. "127..0.1" or "127.0.0."). Trailing dots are + // already stripped above; this guards against the empty-middle case. + if (parts.some(p => p === '')) return host; + if (parts.length === 4) { + // Full IPv4 form: each part is an octet. + const octets = parts.map(parseIPv4Octet); + if (octets.some(n => n === null || n < 0 || n > 255)) return host; + return octets.join('.'); + } + if (parts.length > 4) { + return host; + } + + // Shorthand: 1..3 parts. Node's URL parser treats a 1-part input as a 32-bit + // integer split into octets, which has surprising semantics (e.g. "127" -> + // "0.0.0.127"). Reject 1-part inputs to keep the helper predictable: the + // fail-safe returns the input unchanged and the comparison layer falls + // through to non-bypass. + if (parts.length === 1) return host; + + // 2..3 parts: literal parts fill from the left, tail fills remaining octets + // from the right with zero-padding. + const literalOctets = parts.slice(0, -1); + const tail = parts[parts.length - 1]; + const tailSlots = 4 - literalOctets.length; + + // Tail is parsed as a full IPv4 number (hex/octal/decimal) and packed + // low-byte-right into the remaining octets, matching Node's URL parser. + // e.g. 127.65535 (tail 0xFFFF into 3 slots) -> 127.0.255.255; + // 127.0x00ff (tail 0xFF into 3 slots) -> 127.0.0.255; + // 127.0.65535 (tail 0xFFFF into 2 slots) -> 127.0.255.255. + const tailValue = parseIPv4Octet(tail); + if (tailValue === null) return host; + const maxTail = (1 << 8 * tailSlots) - 1; + if (tailValue < 0 || tailValue > maxTail) return host; + const tailOctets = new Array(tailSlots).fill(0); + for (let i = tailSlots - 1, v = tailValue; i >= 0; i--, v >>= 8) { + tailOctets[i] = v & 0xff; + } + const literal = literalOctets.map(parseIPv4Octet); + if (literal.some(n => n === null || n < 0 || n > 255)) return host; + return [...literal, ...tailOctets].join('.'); +}; const isIPv6ZeroGroup = group => /^0{1,4}$/.test(group); // The unspecified address (IPv4 0.0.0.0 / IPv6 ::) resolves to the local host @@ -10628,6 +11119,40 @@ const unmapIPv4MappedIPv6 = host => { } return host; }; +const IPV4_OCTET_RE = /^(?:0|[1-9]\d{0,2})$/; +const ipv4ToBytes = host => { + const parts = host.split('.'); + return parts.length === 4 && parts.every(part => IPV4_OCTET_RE.test(part) && Number(part) <= 255) ? parts.map(Number) : null; +}; +const IPV6_GROUP_RE = /^[0-9a-f]{1,4}$/i; +const ipv6ToBytes = host => { + const halves = host.split('::'); + if (halves.length > 2) { + return null; + } + const groups = halves[0] ? halves[0].split(':') : []; + if (halves.length === 2) { + const rear = halves[1] ? halves[1].split(':') : []; + const missing = 8 - groups.length - rear.length; + if (missing < 1) { + return null; + } + groups.push(...new Array(missing).fill('0'), ...rear); + } + if (groups.length !== 8 || groups.some(group => !IPV6_GROUP_RE.test(group))) { + return null; + } + return groups.flatMap(group => { + const value = Number.parseInt(group, 16); + return [value >> 8 & 0xff, value & 0xff]; + }); +}; +const ipToBytes = host => { + if (typeof host !== 'string' || !host) { + return null; + } + return host.indexOf(':') !== -1 ? ipv6ToBytes(host) : ipv4ToBytes(host); +}; const normalizeNoProxyHost = hostname => { if (!hostname) { return hostname; @@ -10635,7 +11160,101 @@ const normalizeNoProxyHost = hostname => { if (hostname.charAt(0) === '[' && hostname.charAt(hostname.length - 1) === ']') { hostname = hostname.slice(1, -1); } - return unmapIPv4MappedIPv6(hostname.replace(/\.+$/, '')); + const trimmed = trimTrailingDots(hostname); + + // IPv4 shorthand/octal/hex → dotted-decimal; helper is a no-op for inputs + // containing ':' (IPv6 and IPv4-mapped IPv6) so we fall through to unmap. + const ipv4 = normalizeIPAddress(trimmed); + if (ipv4 !== trimmed) { + return ipv4; + } + return unmapIPv4MappedIPv6(trimmed); +}; +const normalizeCidrBase = input => { + let base = input; + const startsBracket = base.charAt(0) === '['; + const endsBracket = base.charAt(base.length - 1) === ']'; + const hasBracket = base.includes('[') || base.includes(']'); + if (startsBracket || endsBracket) { + if (!startsBracket || !endsBracket) { + return null; + } + base = base.slice(1, -1); + if (base.indexOf(':') === -1 || base.includes('[') || base.includes(']')) { + return null; + } + } else if (hasBracket) { + return null; + } + if (!base || base.charAt(base.length - 1) === '.') { + return null; + } + const wasIPv6 = base.indexOf(':') !== -1; + if (wasIPv6) { + try { + base = new URL(`http://[${base}]/`).hostname.slice(1, -1); + } catch (_err) { + return null; + } + } else { + base = normalizeIPAddress(base); + if (!ipv4ToBytes(base)) { + return null; + } + } + return { + normalized: unmapIPv4MappedIPv6(base), + wasIPv6 + }; +}; +const CIDR_ENTRY_RE = /^(.+)\/(0|[1-9]\d{0,2})$/; +const parseCidrEntry = entry => { + if (entry.indexOf('/') === -1) { + return undefined; + } + const match = CIDR_ENTRY_RE.exec(entry); + if (!match) { + return null; + } + let prefix = Number(match[2]); + const parsedBase = normalizeCidrBase(match[1]); + if (!parsedBase) { + return null; + } + const { + normalized, + wasIPv6 + } = parsedBase; + if (wasIPv6 && normalized.indexOf(':') === -1) { + if (prefix < 96) { + return null; + } + prefix -= 96; + } + const bytes = ipToBytes(normalized); + if (!bytes || prefix > bytes.length * 8) { + return null; + } + return { + bytes, + prefix + }; +}; +const isInSubnet = (addressBytes, networkBytes, prefix) => { + const fullBytes = prefix >> 3; + for (let i = 0; i < fullBytes; i++) { + if (addressBytes[i] !== networkBytes[i]) { + return false; + } + } + const remainingBits = prefix & 7; + if (remainingBits) { + const mask = 0xff << 8 - remainingBits & 0xff; + if ((addressBytes[fullBytes] & mask) !== (networkBytes[fullBytes] & mask)) { + return false; + } + } + return true; }; function shouldBypassProxy(location) { let parsed; @@ -10653,10 +11272,18 @@ function shouldBypassProxy(location) { } const port = Number.parseInt(parsed.port, 10) || DEFAULT_PORTS[parsed.protocol.split(':', 1)[0]] || 0; const hostname = normalizeNoProxyHost(parsed.hostname.toLowerCase()); + const hostnameBytes = ipToBytes(hostname); return noProxy.split(/[\s,]+/).some(entry => { if (!entry) { return false; } + if (entry === '*') { + return true; + } + const cidr = parseCidrEntry(entry); + if (cidr !== undefined) { + return cidr !== null && !!hostnameBytes && hostnameBytes.length === cidr.bytes.length && isInSubnet(hostnameBytes, cidr.bytes, cidr.prefix); + } let [entryHost, entryPort] = parseNoProxyEntry(entry); entryHost = normalizeNoProxyHost(entryHost); if (!entryHost) { @@ -10719,7 +11346,7 @@ function speedometer(samplesCount, min) { * Throttle decorator * @param {Function} fn * @param {Number} freq - * @return {Function} + * @return {Array} */ function throttle(fn, freq) { let timestamp = 0; @@ -10751,19 +11378,20 @@ function throttle(fn, freq) { } }; const flush = () => lastArgs && invoke(lastArgs); - return [throttled, flush]; + const flushWith = (...args) => invoke(args); + return [throttled, flush, flushWith]; } const progressEventReducer = (listener, isDownloadStream, freq = 3) => { let bytesNotified = 0; const _speedometer = speedometer(50, 250); return throttle(e => { - if (!e || typeof e.loaded !== 'number') { + if (!e || !utils$1.isNumber(e.loaded)) { return; } const rawLoaded = e.loaded; const total = e.lengthComputable ? e.total : undefined; - const loaded = total != null ? Math.min(rawLoaded, total) : rawLoaded; + const loaded = Math.max(0, total != null ? Math.min(rawLoaded, total) : rawLoaded); const progressBytes = Math.max(0, loaded - bytesNotified); const rate = _speedometer(progressBytes); bytesNotified = Math.max(bytesNotified, loaded); @@ -10789,20 +11417,85 @@ const progressEventDecorator = (total, throttled) => { loaded }), throttled[1]]; }; -const asyncDecorator = fn => (...args) => utils$1.asap(() => fn(...args)); +const asyncDecorator = (fn, scheduler = utils$1.asap) => (...args) => scheduler(() => fn(...args)); /** - * Estimate decoded byte length of a data:// URL *without* allocating large buffers. - * - For base64: compute exact decoded size using length and padding; - * handle %XX at the character-count level (no string allocation). - * - For non-base64: compute the exact percent-decoded UTF-8 byte length. - * - * @param {string} url - * @returns {number} + * Estimate data: URL byte lengths *without* allocating large buffers. + * - Fetch percent-decodes a base64 body before decoding it. + * - Node's Buffer.from(body, 'base64') sizes its backing allocation from the + * raw body, including ignored characters and content after padding. + * - Non-base64 data is percent-decoded and then encoded as UTF-8. */ const isHexDigit = charCode => charCode >= 48 && charCode <= 57 || charCode >= 65 && charCode <= 70 || charCode >= 97 && charCode <= 102; const isPercentEncodedByte = (str, i, len) => i + 2 < len && isHexDigit(str.charCodeAt(i + 1)) && isHexDigit(str.charCodeAt(i + 2)); -function estimateDataURLDecodedBytes(url) { +const hexValue = charCode => charCode <= 57 ? charCode - 48 : (charCode & 0xdf) - 55; +const isBase64Char = charCode => charCode >= 65 && charCode <= 90 || +// A-Z +charCode >= 97 && charCode <= 122 || +// a-z +charCode >= 48 && charCode <= 57 || +// 0-9 +charCode === 43 || +// + +charCode === 47 || +// / +charCode === 45 || +// - (base64url) +charCode === 95; // _ (base64url) + +const isBase64Whitespace = charCode => charCode === 9 || charCode === 10 || charCode === 12 || charCode === 13 || charCode === 32; +const base64Bytes = significant => { + const groups = Math.floor(significant / 4); + const remainder = significant % 4; + return groups * 3 + (remainder === 2 ? 1 : remainder === 3 ? 2 : 0); +}; + +// Buffer.byteLength(body, 'base64') uses the raw string length as an allocation +// upper bound even when Buffer.from later ignores characters or stops at '='. +const estimateBase64BufferAllocation = body => { + const len = body.length; + let padding = 0; + if (len > 0 && body.charCodeAt(len - 1) === 61 /* '=' */) { + padding++; + if (len > 1 && body.charCodeAt(len - 2) === 61 /* '=' */) { + padding++; + } + } + return Math.floor((len - padding) * 3 / 4); +}; +const estimatePercentDecodedBase64Bytes = body => { + const len = body.length; + let significant = 0; + let padding = 0; + let invalid = false; + for (let i = 0; i < len; i++) { + let code = body.charCodeAt(i); + if (code === 37 /* '%' */ && isPercentEncodedByte(body, i, len)) { + code = hexValue(body.charCodeAt(i + 1)) * 16 + hexValue(body.charCodeAt(i + 2)); + i += 2; + } + if (isBase64Whitespace(code)) { + continue; + } + if (code === 61 /* '=' */) { + padding++; + continue; + } + if (!isBase64Char(code) || padding > 0) { + invalid = true; + continue; + } + significant++; + } + + // Fetch rejects malformed forgiving-base64 input. Returning the raw-size + // allocation bound keeps that invalid input from becoming a pre-check bypass. + if (invalid || padding > 2 || padding > 0 && (significant + padding) % 4 !== 0 || significant % 4 === 1) { + return estimateBase64BufferAllocation(body); + } + return base64Bytes(significant); +}; +const estimateDataURLBytes = (url, estimateBase64) => { if (!url || typeof url !== 'string') return 0; if (!url.startsWith('data:')) return 0; const comma = url.indexOf(','); @@ -10811,47 +11504,7 @@ function estimateDataURLDecodedBytes(url) { const body = url.slice(comma + 1); const isBase64 = /;base64/i.test(meta); if (isBase64) { - let effectiveLen = body.length; - const len = body.length; // cache length - - for (let i = 0; i < len; i++) { - if (body.charCodeAt(i) === 37 /* '%' */ && i + 2 < len) { - const a = body.charCodeAt(i + 1); - const b = body.charCodeAt(i + 2); - const isHex = isHexDigit(a) && isHexDigit(b); - if (isHex) { - effectiveLen -= 2; - i += 2; - } - } - } - let pad = 0; - let idx = len - 1; - const tailIsPct3D = j => j >= 2 && body.charCodeAt(j - 2) === 37 && - // '%' - body.charCodeAt(j - 1) === 51 && ( - // '3' - body.charCodeAt(j) === 68 || body.charCodeAt(j) === 100); // 'D' or 'd' - - if (idx >= 0) { - if (body.charCodeAt(idx) === 61 /* '=' */) { - pad++; - idx--; - } else if (tailIsPct3D(idx)) { - pad++; - idx -= 3; - } - } - if (pad === 1 && idx >= 0) { - if (body.charCodeAt(idx) === 61 /* '=' */) { - pad++; - } else if (tailIsPct3D(idx)) { - pad++; - } - } - const groups = Math.floor(effectiveLen / 4); - const bytes = groups * 3 - (pad || 0); - return bytes > 0 ? bytes : 0; + return estimateBase64(body); } // Compute UTF-8 byte length directly from UTF-16 code units without allocating @@ -10881,6 +11534,28 @@ function estimateDataURLDecodedBytes(url) { } } return bytes; +}; + +/** + * Estimate the percent-decoded payload size used by Fetch data: URLs. + * + * @param {string} url + * @returns {number} + */ +function estimateDataURLDecodedBytes(url) { + // Fetch removes URL fragments before processing a data: URL. + const fragmentIndex = typeof url === 'string' ? url.indexOf('#') : -1; + return estimateDataURLBytes(fragmentIndex === -1 ? url : url.slice(0, fragmentIndex), estimatePercentDecodedBase64Bytes); +} + +/** + * Estimate the Buffer backing allocation used by Node's raw base64 decoder. + * + * @param {string} url + * @returns {number} + */ +function estimateDataURLBufferAllocation(url) { + return estimateDataURLBytes(url, estimateBase64BufferAllocation); } const zlibOptions = { @@ -10899,29 +11574,27 @@ const isBrotliSupported = utils$1.isFunction(zlib.createBrotliDecompress); const isZstdSupported = utils$1.isFunction(zlib.createZstdDecompress); const ACCEPT_ENCODING = 'gzip, compress, deflate' + (isBrotliSupported ? ', br' : ''); const ACCEPT_ENCODING_WITH_ZSTD = ACCEPT_ENCODING + (isZstdSupported ? ', zstd' : ''); +const scheduleProgress = typeof process !== 'undefined' && process.nextTick ? process.nextTick.bind(process) : utils$1.asap; const { http: httpFollow, 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'); +// A shared listener avoids retaining an adapter context for the lifetime of a +// pooled socket. EventEmitter invokes listeners with `this` set to the emitter. +function handleSocketError(err) { + const current = this[kAxiosCurrentReq]; + if (current && !current.destroyed) { + current.destroy(err); + } +} + // Tags HttpsProxyAgent instances installed by setProxy() so the redirect path // can strip them without clobbering a user-supplied agent that happens to be // an HttpsProxyAgent. @@ -11067,13 +11740,14 @@ function isSameOriginRedirect(redirectOptions, requestDetails) { * @param {http.ClientRequestArgs} options * @param {AxiosProxyConfig} configProxy configuration from Axios options object * @param {string} location + * @param {boolean} [allowEnvProxy=true] whether environment proxy configuration can be used * - * @returns {http.ClientRequestArgs} + * @returns {boolean} whether a proxy applies to the selected transport */ -function setProxy(options, configProxy, location, isRedirect, configHttpsAgent, configHttpAgent) { +function setProxy(options, configProxy, location, isRedirect, configHttpsAgent, configHttpAgent, allowEnvProxy = true) { let proxy = configProxy; const proxyEnvAgent = getProxyEnvAgent(options, configHttpAgent, configHttpsAgent); - if (!proxy && proxy !== false && !isNodeEnvProxyEnabled(proxyEnvAgent)) { + if (!proxy && proxy !== false && allowEnvProxy && !isNodeEnvProxyEnabled(proxyEnvAgent)) { const proxyUrl = getProxyForUrl(location); if (proxyUrl) { if (!shouldBypassProxy(location)) { @@ -11208,8 +11882,9 @@ function setProxy(options, configProxy, location, isRedirect, configHttpsAgent, 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, true, configHttpsAgent, configHttpAgent); + setProxy(redirectOptions, configProxy, redirectOptions.href, true, configHttpsAgent, configHttpAgent, allowEnvProxy); }; + return Boolean(proxy || configProxy !== false && allowEnvProxy && isNodeEnvProxyEnabled(proxyEnvAgent)); } const isHttpAdapterSupported = typeof process !== 'undefined' && utils$1.kindOf(process) === 'process'; @@ -11240,7 +11915,7 @@ const resolveFamily = ({ family }) => { if (!utils$1.isString(address)) { - throw TypeError('address must be a string'); + throw new AxiosError('address must be a string', AxiosError.ERR_BAD_OPTION_VALUE); } return { address, @@ -11251,6 +11926,32 @@ const buildAddressEntry = (address, family) => resolveFamily(utils$1.isObject(ad address, family }); +const normalizedLookupCache = new WeakMap(); +const normalizeLookup = lookup => { + let normalized = normalizedLookupCache.get(lookup); + if (normalized) { + return normalized; + } + const callbackLookup = callbackify(lookup, value => utils$1.isArray(value) ? value : [value]); + + // Support opt.all, which is required by current Node.js releases. + normalized = (hostname, opt, cb) => { + callbackLookup(hostname, opt, (err, arg0, arg1) => { + if (err) { + return cb(err); + } + let addresses; + try { + addresses = utils$1.isArray(arg0) ? arg0.map(addr => buildAddressEntry(addr)) : [buildAddressEntry(arg0, arg1)]; + } catch (error) { + return cb(error); + } + opt.all ? cb(err, addresses) : cb(err, addresses[0].address, addresses[0].family); + }); + }; + normalizedLookupCache.set(lookup, normalized); + return normalized; +}; const http2Transport = { request(options, cb) { const authority = options.protocol + '//' + options.hostname + ':' + (options.port || (options.protocol === 'https:' ? 443 : 80)); @@ -11303,6 +12004,7 @@ var httpAdapter = isHttpAdapterSupported && function httpAdapter(config) { let family = own('family'); let httpVersion = own('httpVersion'); if (httpVersion === undefined) httpVersion = 1; + const rawHttpVersion = httpVersion; let http2Options = own('http2Options'); const httpAgent = own('httpAgent'); const httpsAgent = own('httpsAgent'); @@ -11319,26 +12021,20 @@ var httpAdapter = isHttpAdapterSupported && function httpAdapter(config) { let rejected = false; let req; let connectPhaseTimer; - httpVersion = +httpVersion; + try { + httpVersion = +httpVersion; + } catch (err) { + throw new AxiosError('Invalid protocol version: value is not a number', AxiosError.ERR_BAD_OPTION_VALUE, config); + } if (Number.isNaN(httpVersion)) { - throw TypeError(`Invalid protocol version: '${config.httpVersion}' is not a number`); + throw new AxiosError(`Invalid protocol version: '${rawHttpVersion}' is not a number`, AxiosError.ERR_BAD_OPTION_VALUE, config); } if (httpVersion !== 1 && httpVersion !== 2) { - throw TypeError(`Unsupported protocol version '${httpVersion}'`); + throw new AxiosError(`Unsupported protocol version '${httpVersion}'`, AxiosError.ERR_BAD_OPTION_VALUE, config); } const isHttp2 = httpVersion === 2; if (lookup) { - const _lookup = callbackify(lookup, value => utils$1.isArray(value) ? value : [value]); - // hotfix to support opt.all option which is required for node 20.x - lookup = (hostname, opt, cb) => { - _lookup(hostname, opt, (err, arg0, arg1) => { - if (err) { - return cb(err); - } - const addresses = utils$1.isArray(arg0) ? arg0.map(addr => buildAddressEntry(addr)) : [buildAddressEntry(arg0, arg1)]; - opt.all ? cb(err, addresses) : cb(err, addresses[0].address, addresses[0].family); - }); - }; + lookup = normalizeLookup(lookup); } const abortEmitter = new events.EventEmitter(); function abort(reason) { @@ -11415,7 +12111,7 @@ var httpAdapter = isHttpAdapterSupported && function httpAdapter(config) { if (maxContentLength > -1) { // Use the exact string passed to fromDataURI (the configured url); fall back to fullPath if needed. const dataUrl = String(own('url') || fullPath || ''); - const estimated = estimateDataURLDecodedBytes(dataUrl); + const estimated = estimateDataURLBufferAllocation(dataUrl); if (estimated > maxContentLength) { return reject(new AxiosError('maxContentLength size of ' + maxContentLength + ' exceeded', AxiosError.ERR_BAD_RESPONSE, config)); } @@ -11481,7 +12177,7 @@ var httpAdapter = isHttpAdapterSupported && function httpAdapter(config) { }); // support for https://www.npmjs.com/package/form-data api } else if (utils$1.isFormData(data) && utils$1.isFunction(data.getHeaders) && data.getHeaders !== Object.prototype.getHeaders) { - setFormDataHeaders$1(headers, data.getHeaders(), own('formDataHeaderPolicy')); + setFormDataHeaders(headers, data.getHeaders(), own('formDataHeaderPolicy')); if (!headers.hasContentLength()) { try { const knownLength = await util.promisify(data.getLength).call(data); @@ -11524,7 +12220,7 @@ var httpAdapter = isHttpAdapterSupported && function httpAdapter(config) { data = stream.pipeline([data, new AxiosTransformStream({ maxRate: utils$1.toFiniteNumber(maxUploadRate) })], utils$1.noop); - onUploadProgress && data.on('progress', flushOnFinish(data, progressEventDecorator(contentLength, progressEventReducer(asyncDecorator(onUploadProgress), false, 3)))); + onUploadProgress && data.on('progress', flushOnFinish(data, progressEventDecorator(contentLength, progressEventReducer(asyncDecorator(onUploadProgress, scheduleProgress), false, 3)))); } // HTTP basic authentication @@ -11551,6 +12247,11 @@ var httpAdapter = isHttpAdapterSupported && function httpAdapter(config) { })); } headers.set('Accept-Encoding', utils$1.hasOwnProp(transitional, 'advertiseZstdAcceptEncoding') && transitional.advertiseZstdAcceptEncoding === true ? ACCEPT_ENCODING_WITH_ZSTD : ACCEPT_ENCODING, false); + if (isHttp2 && lookup) { + http2Options = Object.assign(Object.create(null), http2Options, { + lookup + }); + } // Null-prototype to block prototype pollution gadgets on properties read // directly by Node's http.request (e.g. insecureHTTPParser, lookup). @@ -11567,11 +12268,13 @@ var httpAdapter = isHttpAdapterSupported && function httpAdapter(config) { family, beforeRedirect: dispatchBeforeRedirect, beforeRedirects: Object.create(null), - http2Options + http2Options, + createConnection: undefined }); // cacheable-lookup integration hotfix !utils$1.isUndefined(lookup) && (options.lookup = lookup); + let proxyApplied = false; if (socketPath) { if (typeof socketPath !== 'string') { return reject(new AxiosError('socketPath must be a string', AxiosError.ERR_BAD_OPTION_VALUE, config)); @@ -11589,7 +12292,11 @@ var httpAdapter = isHttpAdapterSupported && function httpAdapter(config) { } else { options.hostname = parsed.hostname.startsWith('[') ? parsed.hostname.slice(1, -1) : parsed.hostname; options.port = parsed.port; - setProxy(options, configProxy, protocol + '//' + parsed.hostname + (parsed.port ? ':' + parsed.port : '') + options.path, false, httpsAgent, httpAgent); + proxyApplied = setProxy(options, configProxy, protocol + '//' + parsed.hostname + (parsed.port ? ':' + parsed.port : '') + options.path, false, httpsAgent, httpAgent, + // The HTTP/2 transport connects independently of HTTP/1 agents, so it + // cannot apply either axios-resolved or agent-local environment proxies. + // Explicit proxy config is still processed and rejected below. + !isHttp2); } let transport; let isNativeTransport = false; @@ -11605,6 +12312,9 @@ var httpAdapter = isHttpAdapterSupported && function httpAdapter(config) { options.agent = isHttpsRequest ? httpsAgent : httpAgent; } if (isHttp2) { + if (proxyApplied) { + return reject(new AxiosError('HTTP/2 requests with a proxy are not supported', AxiosError.ERR_NOT_SUPPORT, config)); + } transport = http2Transport; } else { const configTransport = own('transport'); @@ -11689,7 +12399,7 @@ var httpAdapter = isHttpAdapterSupported && function httpAdapter(config) { const transformStream = new AxiosTransformStream({ maxRate: utils$1.toFiniteNumber(maxDownloadRate) }); - onDownloadProgress && transformStream.on('progress', flushOnFinish(transformStream, progressEventDecorator(responseLength, progressEventReducer(asyncDecorator(onDownloadProgress), true, 3)))); + onDownloadProgress && transformStream.on('progress', flushOnFinish(transformStream, progressEventDecorator(responseLength, progressEventReducer(asyncDecorator(onDownloadProgress, scheduleProgress), true, 3)))); streams.push(transformStream); } @@ -11852,18 +12562,10 @@ var httpAdapter = isHttpAdapterSupported && function httpAdapter(config) { 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. + // Install one shared 'error' listener per socket. The symbol follows the + // currently-active request as pooled sockets are reassigned (issue #10780). if (!socket[kAxiosSocketListener]) { - socket.on('error', function handleSocketError(err) { - const current = socket[kAxiosCurrentReq]; - if (current && !current.destroyed) { - current.destroy(err); - } - }); + socket.on('error', handleSocketError); socket[kAxiosSocketListener] = true; } socket[kAxiosCurrentReq] = req; @@ -12020,6 +12722,12 @@ var cookies = platform.hasStandardBrowserEnv ? const headersToObject = thing => thing instanceof AxiosHeaders ? { ...thing } : thing; +const ownEnumerableKeys = thing => { + if (Object.getOwnPropertySymbols && Object.getOwnPropertyDescriptor) { + return Object.keys(thing).concat(Object.getOwnPropertySymbols(thing).filter(symbol => Object.getOwnPropertyDescriptor(thing, symbol).enumerable)); + } + return Object.keys(thing); +}; /** * Config-specific merge-function which creates a new config-object @@ -12119,7 +12827,7 @@ function mergeConfig(config1, config2) { transformResponse: defaultToConfig2, paramsSerializer: defaultToConfig2, timeout: defaultToConfig2, - timeoutMessage: defaultToConfig2, + timeoutErrorMessage: defaultToConfig2, withCredentials: defaultToConfig2, withXSRFToken: defaultToConfig2, adapter: defaultToConfig2, @@ -12142,7 +12850,7 @@ function mergeConfig(config1, config2) { validateStatus: mergeDirectKeys, headers: (a, b, prop) => mergeDeepProperties(headersToObject(a), headersToObject(b), prop, true) }; - utils$1.forEach(Object.keys({ + utils$1.forEach(ownEnumerableKeys({ ...config1, ...config2 }), function computeConfigValue(prop) { @@ -12163,19 +12871,6 @@ function mergeConfig(config1, config2) { 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. @@ -12214,11 +12909,12 @@ function resolveConfig(config) { } } if (utils$1.isFormData(data)) { + const getHeaders = utils$1.getSafeProp(data, 'getHeaders'); if (platform.hasStandardBrowserEnv || platform.hasStandardBrowserWebWorkerEnv || utils$1.isReactNative(data)) { headers.setContentType(undefined); // browser/web worker/RN handles it - } else if (utils$1.isFunction(data.getHeaders)) { + } else if (utils$1.isFunction(getHeaders)) { // Node.js FormData (like form-data package) - setFormDataHeaders(headers, data.getHeaders(), own('formDataHeaderPolicy')); + setFormDataHeaders(headers, getHeaders.call(data), own('formDataHeaderPolicy')); } } @@ -12258,7 +12954,7 @@ var xhrAdapter = isXHRAdapterSupported && function (config) { } = _config; let onCanceled; let uploadThrottled, downloadThrottled; - let flushUpload, flushDownload; + let flushUpload, flushDownload, flushDownloadWithEvent; function done() { flushUpload && flushUpload(); // flush events flushDownload && flushDownload(); // flush events @@ -12271,10 +12967,50 @@ var xhrAdapter = isXHRAdapterSupported && function (config) { // Set the request timeout in MS request.timeout = _config.timeout; - function onloadend() { + function onloadend(event) { if (!request) { return; } + + // Status 0 means no response was received, which onerror and onabort normally + // reject before this runs. Firefox 152 fires only readystatechange and loadend for + // navigation-canceled requests (https://bugzilla.mozilla.org/show_bug.cgi?id=1505389), + // leaving settle() to resolve them as an empty success. ECONNABORTED is the error + // onabort raised on Firefox 151. Reads over file:, which some environments report as + // status 0 on success, are excluded by the request URL's scheme after browser-style + // preprocessing, by the page origin's scheme for relative URLs (which inherit it), or + // by responseURL where implemented. + if (request.status === 0 && (parseProtocol(normalizeURLForProtocolCheck(_config.url)) || parseProtocol(platform.origin)) !== 'file' && !(request.responseURL && request.responseURL.startsWith('file:'))) { + reject(new AxiosError('Request aborted', AxiosError.ECONNABORTED, config, request)); + done(); + + // Clean up request + request = null; + return; + } + + // When loadend is still dispatching, flushing with it gives progress + // listeners a final delivery whose event has a live target. The legacy + // ready-state fallback has no event, so replay its pending progress. + // A throwing listener must not block settlement; rethrow asynchronously, + // matching how listener errors surface on the throttle timer path. + try { + if (event) { + flushDownloadWithEvent && flushDownloadWithEvent(event); + } else { + flushDownload && flushDownload(); + } + } catch (err) { + setTimeout(() => { + throw err; + }); + } + + // A final progress callback can cancel the request synchronously. + if (!request) { + return; + } + // Prepare the response const responseHeaders = AxiosHeaders.from('getAllResponseHeaders' in request && request.getAllResponseHeaders()); const responseData = !responseType || responseType === 'text' || responseType === 'json' ? request.responseText : request.response; @@ -12382,7 +13118,7 @@ var xhrAdapter = isXHRAdapterSupported && function (config) { // Handle progress if needed if (onDownloadProgress) { - [downloadThrottled, flushDownload] = progressEventReducer(onDownloadProgress, true); + [downloadThrottled, flushDownload, flushDownloadWithEvent] = progressEventReducer(onDownloadProgress, true); request.addEventListener('progress', downloadThrottled); } @@ -12451,9 +13187,18 @@ const composeSignals = (signals, timeout) => { }); signals = null; }; - signals.forEach(signal => signal.addEventListener('abort', onabort, { - once: true - })); + signals.forEach(signal => { + if (aborted) { + return; + } + if (signal.aborted) { + onabort.call(signal); + return; + } + signal.addEventListener('abort', onabort, { + once: true + }); + }); const { signal } = controller; @@ -12544,6 +13289,17 @@ const trackStream = (stream, chunkSize, onProgress, onFinish) => { }; const DEFAULT_CHUNK_SIZE = 64 * 1024; +const DEFAULT_REQUEST_OPTIONS = { + cache: 'default', + redirect: 'follow', + referrer: 'about:client', + referrerPolicy: '', + mode: 'cors', + integrity: '', + keepalive: false, + priority: 'auto', + window: null +}; const { isFunction } = utils$1; @@ -12686,7 +13442,8 @@ const factory = env => { withCredentials = 'same-origin', fetchOptions, maxContentLength, - maxBodyLength + maxBodyLength, + maxRedirects } = resolveConfig(config); const hasMaxContentLength = utils$1.isNumber(maxContentLength) && maxContentLength > -1; const hasMaxBodyLength = utils$1.isNumber(maxBodyLength) && maxBodyLength > -1; @@ -12817,17 +13574,46 @@ const factory = env => { // 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, + const safeFetchOptions = fetchOptions == null ? fetchOptions : Object.assign(Object.create(null), fetchOptions); + if (safeFetchOptions) { + // These options are owned by Axios and are already reflected in the + // resolved Request passed to fetch. + delete safeFetchOptions.body; + delete safeFetchOptions.headers; + delete safeFetchOptions.method; + delete safeFetchOptions.signal; + delete safeFetchOptions.duplex; + delete safeFetchOptions.credentials; + } + const resolvedOptions = Object.assign(Object.create(null), safeFetchOptions, { signal: composedSignal, method: method.toUpperCase(), headers: toByteStringHeaderObject(headers.normalize()), body: data, duplex: 'half', credentials: isCredentialsSupported ? withCredentials : undefined - }; + }); + if (isRequestSupported) { + utils$1.forEach(DEFAULT_REQUEST_OPTIONS, (value, key) => { + if (resolvedOptions[key] === undefined) { + resolvedOptions[key] = value; + } + }); + if (resolvedOptions.signal === undefined) { + resolvedOptions.signal = null; + } + if (resolvedOptions.body === undefined) { + resolvedOptions.body = null; + } + } + if (maxRedirects === 0) { + resolvedOptions.redirect = 'manual'; + if (safeFetchOptions) { + safeFetchOptions.redirect = 'manual'; + } + } request = isRequestSupported && new Request(url, resolvedOptions); - let response = await (isRequestSupported ? _fetch(request, fetchOptions) : _fetch(url, resolvedOptions)); + let response = await (isRequestSupported ? _fetch(request, safeFetchOptions) : _fetch(url, resolvedOptions)); const responseHeaders = AxiosHeaders.from(response.headers); // Cheap pre-check: if the server honestly declares a content-length that @@ -13107,9 +13893,13 @@ function throwIfCancellationRequested(config) { * * @returns {Promise} The Promise to be fulfilled */ -function dispatchRequest(config) { +function dispatchRequest(_config) { + // Interceptors may replace the merged config with an ordinary object. Flatten + // it at the dispatch boundary so shared prototype members cannot become + // request behavior, while preserving intentional template/class members. + const config = utils$1.toSafeFlatObject(_config); throwIfCancellationRequested(config); - config.headers = AxiosHeaders.from(config.headers); + config.headers = AxiosHeaders.from(utils$1.getSafeProp(config, 'headers')); // Transform request data config.data = transformData.call(config, config.transformRequest); @@ -13265,18 +14055,17 @@ class Axios { return await this._request(configOrUrl, config); } catch (err) { if (err instanceof Error) { - let dummy = {}; - Error.captureStackTrace ? Error.captureStackTrace(dummy) : dummy = new Error(); - - // slice off the Error: ... line - const stack = (() => { - if (!dummy.stack) { - return ''; - } - const firstNewlineIndex = dummy.stack.indexOf('\n'); - return firstNewlineIndex === -1 ? '' : dummy.stack.slice(firstNewlineIndex + 1); - })(); try { + let dummy = {}; + Error.captureStackTrace ? Error.captureStackTrace(dummy) : dummy = new Error(); + const dummyStack = dummy.stack; + let stack = ''; + + // slice off the Error: ... line + if (typeof dummyStack === 'string') { + const firstNewlineIndex = dummyStack.indexOf('\n'); + stack = firstNewlineIndex === -1 ? '' : dummyStack.slice(firstNewlineIndex + 1); + } if (!err.stack) { err.stack = stack; // match without the 2 top stack lines @@ -13289,7 +14078,7 @@ class Axios { } } } catch (e) { - // ignore the case where "stack" is an un-writable property + // Ignore failures from custom stack hooks or un-writable stack properties. } } throw err; @@ -13345,11 +14134,11 @@ class Axios { }, true); // Set config.method - config.method = (config.method || this.defaults.method || 'get').toLowerCase(); + config.method = (utils$1.getSafeProp(config, 'method') || utils$1.getSafeProp(this.defaults, 'method') || 'get').toLowerCase(); // Flatten headers let contextHeaders = headers && utils$1.merge(headers.common, headers[config.method]); - headers && utils$1.forEach(['delete', 'get', 'head', 'post', 'put', 'patch', 'query', 'common'], method => { + headers && utils$1.forEach(methodList.concat('common'), method => { delete headers[method]; }); config.headers = AxiosHeaders.concat(contextHeaders, headers); @@ -13394,16 +14183,29 @@ class Axios { const onFulfilled = requestInterceptorChain[i++]; const onRejected = requestInterceptorChain[i++]; try { - newConfig = onFulfilled(newConfig); + newConfig = onFulfilled ? onFulfilled(newConfig) : newConfig; } catch (error) { - onRejected.call(this, error); + if (!onRejected) { + promise = Promise.reject(error); + break; + } + try { + const rejectedResult = onRejected.call(this, error); + if (utils$1.isThenable(rejectedResult)) { + promise = Promise.resolve(rejectedResult).then(() => dispatchRequest.call(this, newConfig)); + } + } catch (rejectedError) { + promise = Promise.reject(rejectedError); + } break; } } - try { - promise = dispatchRequest.call(this, newConfig); - } catch (error) { - return Promise.reject(error); + if (!promise) { + try { + promise = dispatchRequest.call(this, newConfig); + } catch (error) { + promise = Promise.reject(error); + } } i = 0; len = responseInterceptorChain.length; @@ -13642,14 +14444,22 @@ const HttpStatusCode = { Gone: 410, LengthRequired: 411, PreconditionFailed: 412, + /** + * @deprecated Use `ContentTooLarge` instead. + */ PayloadTooLarge: 413, + ContentTooLarge: 413, UriTooLong: 414, UnsupportedMediaType: 415, RangeNotSatisfiable: 416, ExpectationFailed: 417, ImATeapot: 418, MisdirectedRequest: 421, + /** + * @deprecated Use `UnprocessableContent` instead. + */ UnprocessableEntity: 422, + UnprocessableContent: 422, Locked: 423, FailedDependency: 424, TooEarly: 425, @@ -13669,6 +14479,7 @@ const HttpStatusCode = { LoopDetected: 508, NotExtended: 510, NetworkAuthenticationRequired: 511, + WebServerReturnsAnUnknownError: 520, WebServerIsDown: 521, ConnectionTimedOut: 522, OriginIsUnreachable: 523, @@ -13677,7 +14488,9 @@ const HttpStatusCode = { InvalidSslCertificate: 526 }; Object.entries(HttpStatusCode).forEach(([key, value]) => { - HttpStatusCode[value] = key; + if (HttpStatusCode[value] === undefined) { + HttpStatusCode[value] = key; + } }); /** @@ -13745,7 +14558,6 @@ axios.HttpStatusCode = HttpStatusCode; axios.default = axios; module.exports = axios; -//# sourceMappingURL=axios.cjs.map /***/ }),