...**/!(*.map|*.min.js)Size
Gzip
Dependencies
Publish
Install
Publish
Install
Size
Gzip
Dependencies
@@ -397,6 +397,7 @@ | ||
| 397 | 397 | clarifyTimeoutError?: boolean; |
| 398 | 398 | legacyInterceptorReqResOrdering?: boolean; |
| 399 | 399 | advertiseZstdAcceptEncoding?: boolean; |
| 400 | validateStatusUndefinedResolves?: boolean; | |
| 400 | 401 | } |
| 401 | 402 | |
| 402 | 403 | interface GenericAbortSignal { |
@@ -559,6 +560,7 @@ | ||
| 559 | 560 | }; |
| 560 | 561 | formDataHeaderPolicy?: 'legacy' | 'content-only'; |
| 561 | 562 | redact?: string[]; |
| 563 | sensitiveHeaders?: string[]; | |
| 562 | 564 | } |
| 563 | 565 | |
| 564 | 566 | // Alias |
@@ -102,6 +102,7 @@ | ||
| 102 | 102 | clarifyTimeoutError: validators.transitional(validators.boolean), |
| 103 | 103 | legacyInterceptorReqResOrdering: validators.transitional(validators.boolean), |
| 104 | 104 | advertiseZstdAcceptEncoding: validators.transitional(validators.boolean), |
| 105 | validateStatusUndefinedResolves: validators.transitional(validators.boolean), | |
| 105 | 106 | }, |
| 106 | 107 | false |
| 107 | 108 | ); |
@@ -233,7 +234,7 @@ | ||
| 233 | 234 | |
| 234 | 235 | getUri(config) { |
| 235 | 236 | config = mergeConfig(this.defaults, config); |
| 236 | const fullPath = buildFullPath(config.baseURL, config.url, config.allowAbsoluteUrls); | |
| 237 | const fullPath = buildFullPath(config.baseURL, config.url, config.allowAbsoluteUrls, config); | |
| 237 | 238 | return buildURL(fullPath, config.params, config.paramsSerializer); |
| 238 | 239 | } |
| 239 | 240 | } |
@@ -246,7 +247,7 @@ | ||
| 246 | 247 | mergeConfig(config || {}, { |
| 247 | 248 | method, |
| 248 | 249 | url, |
| 249 | data: (config || {}).data, | |
| 250 | data: config && utils.hasOwnProp(config, 'data') ? config.data : undefined, | |
| 250 | 251 | }) |
| 251 | 252 | ); |
| 252 | 253 | }; |
@@ -111,8 +111,8 @@ | ||
| 111 | 111 | setHeaders(header, valueOrRewrite); |
| 112 | 112 | } else if (utils.isString(header) && (header = header.trim()) && !isValidHeaderName(header)) { |
| 113 | 113 | setHeaders(parseHeaders(header), valueOrRewrite); |
| 114 | } else if (utils.isObject(header) && utils.isIterable(header)) { | |
| 115 | let obj = {}, | |
| 114 | } else if (utils.isObject(header) && utils.isSafeIterable(header)) { | |
| 115 | let obj = Object.create(null), | |
| 116 | 116 | dest, |
| 117 | 117 | key; |
| 118 | 118 | for (const entry of header) { |
@@ -120,11 +120,14 @@ | ||
| 120 | 120 | throw new TypeError('Object iterator must return a key-value pair'); |
| 121 | 121 | } |
| 122 | 122 | |
| 123 | obj[(key = entry[0])] = (dest = obj[key]) | |
| 124 | ? utils.isArray(dest) | |
| 125 | ? [...dest, entry[1]] | |
| 126 | : [dest, entry[1]] | |
| 127 | : entry[1]; | |
| 123 | key = entry[0]; | |
| 124 | ||
| 125 | if (utils.hasOwnProp(obj, key)) { | |
| 126 | dest = obj[key]; | |
| 127 | obj[key] = utils.isArray(dest) ? [...dest, entry[1]] : [dest, entry[1]]; | |
| 128 | } else { | |
| 129 | obj[key] = entry[1]; | |
| 130 | } | |
| 128 | 131 | } |
| 129 | 132 | |
| 130 | 133 | setHeaders(obj, valueOrRewrite); |
@@ -1,8 +1,34 @@ | ||
| 1 | 1 | 'use strict'; |
| 2 | 2 | |
| 3 | import AxiosError from './AxiosError.js'; | |
| 3 | 4 | import isAbsoluteURL from '../helpers/isAbsoluteURL.js'; |
| 4 | 5 | import combineURLs from '../helpers/combineURLs.js'; |
| 5 | 6 | |
| 7 | const malformedHttpProtocol = /^https?:(?!\/\/)/i; | |
| 8 | const httpProtocolControlCharacters = /[\t\n\r]/g; | |
| 9 | ||
| 10 | function stripLeadingC0ControlOrSpace(url) { | |
| 11 | let i = 0; | |
| 12 | while (i < url.length && url.charCodeAt(i) <= 0x20) { | |
| 13 | i++; | |
| 14 | } | |
| 15 | return url.slice(i); | |
| 16 | } | |
| 17 | ||
| 18 | function normalizeURLForProtocolCheck(url) { | |
| 19 | return stripLeadingC0ControlOrSpace(url).replace(httpProtocolControlCharacters, ''); | |
| 20 | } | |
| 21 | ||
| 22 | function assertValidHttpProtocolURL(url, config) { | |
| 23 | if (typeof url === 'string' && malformedHttpProtocol.test(normalizeURLForProtocolCheck(url))) { | |
| 24 | throw new AxiosError( | |
| 25 | 'Invalid URL: missing "//" after protocol', | |
| 26 | AxiosError.ERR_INVALID_URL, | |
| 27 | config | |
| 28 | ); | |
| 29 | } | |
| 30 | } | |
| 31 | ||
| 6 | 32 | /** |
| 7 | 33 | * Creates a new URL by combining the baseURL with the requestedURL, |
| 8 | 34 | * only when the requestedURL is not already an absolute URL. |
@@ -13,9 +39,11 @@ | ||
| 13 | 39 | * |
| 14 | 40 | * @returns {string} The combined full path |
| 15 | 41 | */ |
| 16 | export default function buildFullPath(baseURL, requestedURL, allowAbsoluteUrls) { | |
| 42 | export default function buildFullPath(baseURL, requestedURL, allowAbsoluteUrls, config) { | |
| 43 | assertValidHttpProtocolURL(requestedURL, config); | |
| 17 | 44 | let isRelativeUrl = !isAbsoluteURL(requestedURL); |
| 18 | 45 | if (baseURL && (isRelativeUrl || allowAbsoluteUrls === false)) { |
| 46 | assertValidHttpProtocolURL(baseURL, config); | |
| 19 | 47 | return combineURLs(baseURL, requestedURL); |
| 20 | 48 | } |
| 21 | 49 | return requestedURL; |
@@ -33,15 +33,17 @@ | ||
| 33 | 33 | return url; |
| 34 | 34 | } |
| 35 | 35 | |
| 36 | const _encode = (options && options.encode) || encode; | |
| 37 | ||
| 38 | 36 | const _options = utils.isFunction(options) |
| 39 | 37 | ? { |
| 40 | 38 | serialize: options, |
| 41 | 39 | } |
| 42 | 40 | : options; |
| 43 | 41 | |
| 44 | const serializeFn = _options && _options.serialize; | |
| 42 | // Read serializer options pollution-safely: own properties and methods on a | |
| 43 | // class/template prototype are honored, but values injected onto a polluted | |
| 44 | // Object.prototype are ignored. | |
| 45 | const _encode = utils.getSafeProp(_options, 'encode') || encode; | |
| 46 | const serializeFn = utils.getSafeProp(_options, 'serialize'); | |
| 45 | 47 | |
| 46 | 48 | let serializedParams; |
| 47 | 49 | |
@@ -2,11 +2,19 @@ | ||
| 2 | 2 | * Estimate decoded byte length of a data:// URL *without* allocating large buffers. |
| 3 | 3 | * - For base64: compute exact decoded size using length and padding; |
| 4 | 4 | * handle %XX at the character-count level (no string allocation). |
| 5 | * - For non-base64: use UTF-8 byteLength of the encoded body as a safe upper bound. | |
| 5 | * - For non-base64: compute the exact percent-decoded UTF-8 byte length. | |
| 6 | 6 | * |
| 7 | 7 | * @param {string} url |
| 8 | 8 | * @returns {number} |
| 9 | 9 | */ |
| 10 | const isHexDigit = (charCode) => | |
| 11 | (charCode >= 48 && charCode <= 57) || | |
| 12 | (charCode >= 65 && charCode <= 70) || | |
| 13 | (charCode >= 97 && charCode <= 102); | |
| 14 | ||
| 15 | const isPercentEncodedByte = (str, i, len) => | |
| 16 | i + 2 < len && isHexDigit(str.charCodeAt(i + 1)) && isHexDigit(str.charCodeAt(i + 2)); | |
| 17 | ||
| 10 | 18 | export default function estimateDataURLDecodedBytes(url) { |
| 11 | 19 | if (!url || typeof url !== 'string') return 0; |
| 12 | 20 | if (!url.startsWith('data:')) return 0; |
@@ -26,9 +34,7 @@ | ||
| 26 | 34 | if (body.charCodeAt(i) === 37 /* '%' */ && i + 2 < len) { |
| 27 | 35 | const a = body.charCodeAt(i + 1); |
| 28 | 36 | const b = body.charCodeAt(i + 2); |
| 29 | const isHex = | |
| 30 | ((a >= 48 && a <= 57) || (a >= 65 && a <= 70) || (a >= 97 && a <= 102)) && | |
| 31 | ((b >= 48 && b <= 57) || (b >= 65 && b <= 70) || (b >= 97 && b <= 102)); | |
| 37 | const isHex = isHexDigit(a) && isHexDigit(b); | |
| 32 | 38 | |
| 33 | 39 | if (isHex) { |
| 34 | 40 | effectiveLen -= 2; |
@@ -69,19 +75,18 @@ | ||
| 69 | 75 | return bytes > 0 ? bytes : 0; |
| 70 | 76 | } |
| 71 | 77 | |
| 72 | if (typeof Buffer !== 'undefined' && typeof Buffer.byteLength === 'function') { | |
| 73 | return Buffer.byteLength(body, 'utf8'); | |
| 74 | } | |
| 75 | ||
| 76 | 78 | // Compute UTF-8 byte length directly from UTF-16 code units without allocating |
| 77 | 79 | // a byte buffer (TextEncoder.encode would defeat the DoS guard on large bodies). |
| 78 | // Using body.length here would undercount non-ASCII (e.g. '€' is 1 code unit | |
| 79 | // but 3 UTF-8 bytes). | |
| 80 | // Valid %XX triplets count as one decoded byte; this matches the bytes that | |
| 81 | // decodeURIComponent(body) would produce before Buffer re-encodes the string. | |
| 80 | 82 | let bytes = 0; |
| 81 | 83 | for (let i = 0, len = body.length; i < len; i++) { |
| 82 | 84 | const c = body.charCodeAt(i); |
| 83 | if (c < 0x80) { | |
| 85 | if (c === 37 /* '%' */ && isPercentEncodedByte(body, i, len)) { | |
| 84 | 86 | bytes += 1; |
| 87 | i += 2; | |
| 88 | } else if (c < 0x80) { | |
| 89 | bytes += 1; | |
| 85 | 90 | } else if (c < 0x800) { |
| 86 | 91 | bytes += 2; |
| 87 | 92 | } else if (c >= 0xd800 && c <= 0xdbff && i + 1 < len) { |
@@ -1,7 +1,20 @@ | ||
| 1 | 1 | 'use strict'; |
| 2 | 2 | |
| 3 | 3 | import utils from '../utils.js'; |
| 4 | import AxiosError from '../core/AxiosError.js'; | |
| 5 | import { DEFAULT_FORM_DATA_MAX_DEPTH } from './toFormData.js'; | |
| 4 | 6 | |
| 7 | const MAX_DEPTH = DEFAULT_FORM_DATA_MAX_DEPTH; | |
| 8 | ||
| 9 | function throwIfDepthExceeded(index) { | |
| 10 | if (index > MAX_DEPTH) { | |
| 11 | throw new AxiosError( | |
| 12 | 'FormData field is too deeply nested (' + index + ' levels). Max depth: ' + MAX_DEPTH, | |
| 13 | AxiosError.ERR_FORM_DATA_DEPTH_EXCEEDED | |
| 14 | ); | |
| 15 | } | |
| 16 | } | |
| 17 | ||
| 5 | 18 | /** |
| 6 | 19 | * It takes a string like `foo[x][y][z]` and returns an array like `['foo', 'x', 'y', 'z'] |
| 7 | 20 | * |
@@ -14,9 +27,16 @@ | ||
| 14 | 27 | // foo.x.y.z |
| 15 | 28 | // foo-x-y-z |
| 16 | 29 | // foo x y z |
| 17 | return utils.matchAll(/\w+|\[(\w*)]/g, name).map((match) => { | |
| 18 | return match[0] === '[]' ? '' : match[1] || match[0]; | |
| 19 | }); | |
| 30 | const path = []; | |
| 31 | const pattern = /\w+|\[(\w*)]/g; | |
| 32 | let match; | |
| 33 | ||
| 34 | while ((match = pattern.exec(name)) !== null) { | |
| 35 | throwIfDepthExceeded(path.length); | |
| 36 | path.push(match[0] === '[]' ? '' : match[1] || match[0]); | |
| 37 | } | |
| 38 | ||
| 39 | return path; | |
| 20 | 40 | } |
| 21 | 41 | |
| 22 | 42 | /** |
@@ -48,6 +68,8 @@ | ||
| 48 | 68 | */ |
| 49 | 69 | function formDataToJSON(formData) { |
| 50 | 70 | function buildPath(path, value, target, index) { |
| 71 | throwIfDepthExceeded(index); | |
| 72 | ||
| 51 | 73 | let name = path[index++]; |
| 52 | 74 | |
| 53 | 75 | if (name === '__proto__') return true; |
@@ -68,6 +68,28 @@ | ||
| 68 | 68 | } |
| 69 | 69 | } |
| 70 | 70 | |
| 71 | function getMergedTransitionalOption(prop) { | |
| 72 | const transitional2 = utils.hasOwnProp(config2, 'transitional') ? config2.transitional : undefined; | |
| 73 | ||
| 74 | if (!utils.isUndefined(transitional2)) { | |
| 75 | if (utils.isPlainObject(transitional2)) { | |
| 76 | if (utils.hasOwnProp(transitional2, prop)) { | |
| 77 | return transitional2[prop]; | |
| 78 | } | |
| 79 | } else { | |
| 80 | return undefined; | |
| 81 | } | |
| 82 | } | |
| 83 | ||
| 84 | const transitional1 = utils.hasOwnProp(config1, 'transitional') ? config1.transitional : undefined; | |
| 85 | ||
| 86 | if (utils.isPlainObject(transitional1) && utils.hasOwnProp(transitional1, prop)) { | |
| 87 | return transitional1[prop]; | |
| 88 | } | |
| 89 | ||
| 90 | return undefined; | |
| 91 | } | |
| 92 | ||
| 71 | 93 | // eslint-disable-next-line consistent-return |
| 72 | 94 | function mergeDirectKeys(a, b, prop) { |
| 73 | 95 | if (utils.hasOwnProp(config2, prop)) { |
@@ -120,5 +142,17 @@ | ||
| 120 | 142 | (utils.isUndefined(configValue) && merge !== mergeDirectKeys) || (config[prop] = configValue); |
| 121 | 143 | }); |
| 122 | 144 | |
| 145 | if ( | |
| 146 | utils.hasOwnProp(config2, 'validateStatus') && | |
| 147 | utils.isUndefined(config2.validateStatus) && | |
| 148 | getMergedTransitionalOption('validateStatusUndefinedResolves') === false | |
| 149 | ) { | |
| 150 | if (utils.hasOwnProp(config1, 'validateStatus')) { | |
| 151 | config.validateStatus = getMergedValue(undefined, config1.validateStatus); | |
| 152 | } else { | |
| 153 | delete config.validateStatus; | |
| 154 | } | |
| 155 | } | |
| 156 | ||
| 123 | 157 | return config; |
| 124 | 158 | } |
@@ -55,17 +55,19 @@ | ||
| 55 | 55 | newConfig.headers = headers = AxiosHeaders.from(headers); |
| 56 | 56 | |
| 57 | 57 | newConfig.url = buildURL( |
| 58 | buildFullPath(baseURL, url, allowAbsoluteUrls), | |
| 58 | buildFullPath(baseURL, url, allowAbsoluteUrls, newConfig), | |
| 59 | 59 | own('params'), |
| 60 | 60 | own('paramsSerializer') |
| 61 | 61 | ); |
| 62 | 62 | |
| 63 | 63 | // HTTP basic authentication |
| 64 | 64 | if (auth) { |
| 65 | const username = utils.getSafeProp(auth, 'username') || ''; | |
| 66 | const password = utils.getSafeProp(auth, 'password') || ''; | |
| 67 | ||
| 65 | 68 | headers.set( |
| 66 | 69 | 'Authorization', |
| 67 | 'Basic ' + | |
| 68 | btoa((auth.username || '') + ':' + (auth.password ? encodeUTF8(auth.password) : '')) | |
| 70 | 'Basic ' + btoa(username + ':' + (password ? encodeUTF8(password) : '')) | |
| 69 | 71 | ); |
| 70 | 72 | } |
| 71 | 73 | |
@@ -1,4 +1,4 @@ | ||
| 1 | const LOOPBACK_HOSTNAMES = new Set(['localhost']); | |
| 1 | const LOOPBACK_HOSTNAMES = new Set(['localhost', '0.0.0.0']); | |
| 2 | 2 | |
| 3 | 3 | const isIPv4Loopback = (host) => { |
| 4 | 4 | const parts = host.split('.'); |
@@ -7,6 +7,37 @@ | ||
| 7 | 7 | return parts.every((p) => /^\d+$/.test(p) && Number(p) >= 0 && Number(p) <= 255); |
| 8 | 8 | }; |
| 9 | 9 | |
| 10 | const isIPv6ZeroGroup = (group) => /^0{1,4}$/.test(group); | |
| 11 | ||
| 12 | // The unspecified address (IPv4 0.0.0.0 / IPv6 ::) resolves to the local host | |
| 13 | // for outbound connections, so treat it as loopback-equivalent for NO_PROXY | |
| 14 | // matching. 0.0.0.0 is covered by LOOPBACK_HOSTNAMES; this handles compressed | |
| 15 | // and full IPv6 all-zero forms so both families bypass symmetrically. | |
| 16 | const isIPv6Unspecified = (host) => { | |
| 17 | if (host === '::') return true; | |
| 18 | ||
| 19 | const compressionIndex = host.indexOf('::'); | |
| 20 | ||
| 21 | if (compressionIndex !== -1) { | |
| 22 | if (compressionIndex !== host.lastIndexOf('::')) return false; | |
| 23 | ||
| 24 | const left = host.slice(0, compressionIndex); | |
| 25 | const right = host.slice(compressionIndex + 2); | |
| 26 | const leftGroups = left ? left.split(':') : []; | |
| 27 | const rightGroups = right ? right.split(':') : []; | |
| 28 | const explicitGroups = leftGroups.length + rightGroups.length; | |
| 29 | ||
| 30 | return ( | |
| 31 | explicitGroups < 8 && | |
| 32 | leftGroups.every(isIPv6ZeroGroup) && | |
| 33 | rightGroups.every(isIPv6ZeroGroup) | |
| 34 | ); | |
| 35 | } | |
| 36 | ||
| 37 | const groups = host.split(':'); | |
| 38 | return groups.length === 8 && groups.every(isIPv6ZeroGroup); | |
| 39 | }; | |
| 40 | ||
| 10 | 41 | const isIPv6Loopback = (host) => { |
| 11 | 42 | // Collapse all-zero groups: any form of ::1 / 0:0:...:0:1 |
| 12 | 43 | // First, strip any leading "::" by normalising with Set lookup of common forms, |
@@ -42,6 +73,7 @@ | ||
| 42 | 73 | if (!host) return false; |
| 43 | 74 | if (LOOPBACK_HOSTNAMES.has(host)) return true; |
| 44 | 75 | if (isIPv4Loopback(host)) return true; |
| 76 | if (isIPv6Unspecified(host)) return true; | |
| 45 | 77 | return isIPv6Loopback(host); |
| 46 | 78 | }; |
| 47 | 79 | |
@@ -5,6 +5,10 @@ | ||
| 5 | 5 | // temporary hotfix to avoid circular references until AxiosURLSearchParams is refactored |
| 6 | 6 | import PlatformFormData from '../platform/node/classes/FormData.js'; |
| 7 | 7 | |
| 8 | // Default nesting limit shared with the inverse transform (formDataToJSON) so | |
| 9 | // the FormData <-> JSON round-trip stays symmetric. | |
| 10 | export const DEFAULT_FORM_DATA_MAX_DEPTH = 100; | |
| 11 | ||
| 8 | 12 | /** |
| 9 | 13 | * Determines if the given thing is a array or js object. |
| 10 | 14 | * |
@@ -115,8 +119,9 @@ | ||
| 115 | 119 | const dots = options.dots; |
| 116 | 120 | const indexes = options.indexes; |
| 117 | 121 | const _Blob = options.Blob || (typeof Blob !== 'undefined' && Blob); |
| 118 | const maxDepth = options.maxDepth === undefined ? 100 : options.maxDepth; | |
| 122 | const maxDepth = options.maxDepth === undefined ? DEFAULT_FORM_DATA_MAX_DEPTH : options.maxDepth; | |
| 119 | 123 | const useBlob = _Blob && utils.isSpecCompliantForm(formData); |
| 124 | const stack = []; | |
| 120 | 125 | |
| 121 | 126 | if (!utils.isFunction(visitor)) { |
| 122 | 127 | throw new TypeError('visitor must be a function'); |
@@ -144,6 +149,38 @@ | ||
| 144 | 149 | return value; |
| 145 | 150 | } |
| 146 | 151 | |
| 152 | function throwIfMaxDepthExceeded(depth) { | |
| 153 | if (depth > maxDepth) { | |
| 154 | throw new AxiosError( | |
| 155 | 'Object is too deeply nested (' + depth + ' levels). Max depth: ' + maxDepth, | |
| 156 | AxiosError.ERR_FORM_DATA_DEPTH_EXCEEDED | |
| 157 | ); | |
| 158 | } | |
| 159 | } | |
| 160 | ||
| 161 | function stringifyWithDepthLimit(value, depth) { | |
| 162 | if (maxDepth === Infinity) { | |
| 163 | return JSON.stringify(value); | |
| 164 | } | |
| 165 | ||
| 166 | const ancestors = []; | |
| 167 | ||
| 168 | return JSON.stringify(value, function limitDepth(_key, currentValue) { | |
| 169 | if (!utils.isObject(currentValue)) { | |
| 170 | return currentValue; | |
| 171 | } | |
| 172 | ||
| 173 | while (ancestors.length && ancestors[ancestors.length - 1] !== this) { | |
| 174 | ancestors.pop(); | |
| 175 | } | |
| 176 | ||
| 177 | ancestors.push(currentValue); | |
| 178 | throwIfMaxDepthExceeded(depth + ancestors.length - 1); | |
| 179 | ||
| 180 | return currentValue; | |
| 181 | }); | |
| 182 | } | |
| 183 | ||
| 147 | 184 | /** |
| 148 | 185 | * Default visitor. |
| 149 | 186 | * |
@@ -167,7 +204,7 @@ | ||
| 167 | 204 | // eslint-disable-next-line no-param-reassign |
| 168 | 205 | key = metaTokens ? key : key.slice(0, -2); |
| 169 | 206 | // eslint-disable-next-line no-param-reassign |
| 170 | value = JSON.stringify(value); | |
| 207 | value = stringifyWithDepthLimit(value, 1); | |
| 171 | 208 | } else if ( |
| 172 | 209 | (utils.isArray(value) && isFlatArray(value)) || |
| 173 | 210 | ((utils.isFileList(value) || utils.endsWith(key, '[]')) && (arr = utils.toArray(value))) |
@@ -200,8 +237,6 @@ | ||
| 200 | 237 | return false; |
| 201 | 238 | } |
| 202 | 239 | |
| 203 | const stack = []; | |
| 204 | ||
| 205 | 240 | const exposedHelpers = Object.assign(predicates, { |
| 206 | 241 | defaultVisitor, |
| 207 | 242 | convertValue, |
@@ -211,12 +246,7 @@ | ||
| 211 | 246 | function build(value, path, depth = 0) { |
| 212 | 247 | if (utils.isUndefined(value)) return; |
| 213 | 248 | |
| 214 | if (depth > maxDepth) { | |
| 215 | throw new AxiosError( | |
| 216 | 'Object is too deeply nested (' + depth + ' levels). Max depth: ' + maxDepth, | |
| 217 | AxiosError.ERR_FORM_DATA_DEPTH_EXCEEDED | |
| 218 | ); | |
| 219 | } | |
| 249 | throwIfMaxDepthExceeded(depth); | |
| 220 | 250 | |
| 221 | 251 | if (stack.indexOf(value) !== -1) { |
| 222 | 252 | throw new Error('Circular reference detected in ' + path.join('.')); |
@@ -8,6 +8,57 @@ | ||
| 8 | 8 | const { getPrototypeOf } = Object; |
| 9 | 9 | const { iterator, toStringTag } = Symbol; |
| 10 | 10 | |
| 11 | /* Creating a function that will check if an object has a property. */ | |
| 12 | const hasOwnProperty = ( | |
| 13 | ({ hasOwnProperty }) => | |
| 14 | (obj, prop) => | |
| 15 | hasOwnProperty.call(obj, prop) | |
| 16 | )(Object.prototype); | |
| 17 | ||
| 18 | /** | |
| 19 | * Walk the prototype chain (excluding the shared Object.prototype) looking for | |
| 20 | * an own `prop`. This distinguishes genuine own/inherited members — including | |
| 21 | * class accessors and template prototypes — from members injected via | |
| 22 | * Object.prototype pollution (e.g. `Object.prototype.username = '...'`), which | |
| 23 | * live on Object.prototype itself and are therefore never matched. | |
| 24 | * | |
| 25 | * @param {*} thing The value whose chain to inspect | |
| 26 | * @param {string|symbol} prop The property key to look for | |
| 27 | * | |
| 28 | * @returns {boolean} True when `prop` is owned below Object.prototype | |
| 29 | */ | |
| 30 | const hasOwnInPrototypeChain = (thing, prop) => { | |
| 31 | let obj = thing; | |
| 32 | const seen = []; | |
| 33 | ||
| 34 | while (obj != null && obj !== Object.prototype) { | |
| 35 | if (seen.indexOf(obj) !== -1) { | |
| 36 | return false; | |
| 37 | } | |
| 38 | seen.push(obj); | |
| 39 | ||
| 40 | if (hasOwnProperty(obj, prop)) { | |
| 41 | return true; | |
| 42 | } | |
| 43 | obj = getPrototypeOf(obj); | |
| 44 | } | |
| 45 | return false; | |
| 46 | }; | |
| 47 | ||
| 48 | /** | |
| 49 | * Read `obj[prop]` only when it is safe from Object.prototype pollution. Own | |
| 50 | * properties and members inherited from a non-Object.prototype source (a class | |
| 51 | * instance or template object) are honored; a value reachable only through a | |
| 52 | * polluted Object.prototype is ignored and `undefined` is returned. | |
| 53 | * | |
| 54 | * @param {*} obj The source object | |
| 55 | * @param {string|symbol} prop The property key to read | |
| 56 | * | |
| 57 | * @returns {*} The resolved value, or undefined when unsafe/absent | |
| 58 | */ | |
| 59 | const getSafeProp = (obj, prop) => | |
| 60 | obj != null && hasOwnInPrototypeChain(obj, prop) ? obj[prop] : undefined; | |
| 61 | ||
| 11 | 62 | const kindOf = ((cache) => (thing) => { |
| 12 | 63 | const str = toString.call(thing); |
| 13 | 64 | return cache[str] || (cache[str] = str.slice(8, -1).toLowerCase()); |
@@ -133,7 +184,7 @@ | ||
| 133 | 184 | * @returns {boolean} True if value is a plain Object, otherwise false |
| 134 | 185 | */ |
| 135 | 186 | const isPlainObject = (val) => { |
| 136 | if (kindOf(val) !== 'object') { | |
| 187 | if (!isObject(val)) { | |
| 137 | 188 | return false; |
| 138 | 189 | } |
| 139 | 190 | |
@@ -141,9 +192,12 @@ | ||
| 141 | 192 | return ( |
| 142 | 193 | (prototype === null || |
| 143 | 194 | prototype === Object.prototype || |
| 144 | Object.getPrototypeOf(prototype) === null) && | |
| 145 | !(toStringTag in val) && | |
| 146 | !(iterator in val) | |
| 195 | getPrototypeOf(prototype) === null) && | |
| 196 | // Treat any genuine (non-Object.prototype-polluted) Symbol.toStringTag or | |
| 197 | // Symbol.iterator as evidence the value is a tagged/iterable type rather | |
| 198 | // than a plain object, while ignoring keys injected onto Object.prototype. | |
| 199 | !hasOwnInPrototypeChain(val, toStringTag) && | |
| 200 | !hasOwnInPrototypeChain(val, iterator) | |
| 147 | 201 | ); |
| 148 | 202 | }; |
| 149 | 203 | |
@@ -670,13 +724,6 @@ | ||
| 670 | 724 | }); |
| 671 | 725 | }; |
| 672 | 726 | |
| 673 | /* Creating a function that will check if an object has a property. */ | |
| 674 | const hasOwnProperty = ( | |
| 675 | ({ hasOwnProperty }) => | |
| 676 | (obj, prop) => | |
| 677 | hasOwnProperty.call(obj, prop) | |
| 678 | )(Object.prototype); | |
| 679 | ||
| 680 | 727 | const { propertyIsEnumerable } = Object.prototype; |
| 681 | 728 | |
| 682 | 729 | /** |
@@ -890,6 +937,20 @@ | ||
| 890 | 937 | |
| 891 | 938 | const isIterable = (thing) => thing != null && isFunction(thing[iterator]); |
| 892 | 939 | |
| 940 | /** | |
| 941 | * Determine if a value is iterable via an iterator that is NOT sourced solely | |
| 942 | * from a polluted Object.prototype. Use this instead of `isIterable` whenever | |
| 943 | * the iterable comes from untrusted input (e.g. user-supplied header sources), | |
| 944 | * so `Object.prototype[Symbol.iterator] = ...` cannot turn an ordinary object | |
| 945 | * into an attacker-controlled entries iterator. | |
| 946 | * | |
| 947 | * @param {*} thing The value to test | |
| 948 | * | |
| 949 | * @returns {boolean} True if value has a non-polluted iterator | |
| 950 | */ | |
| 951 | const isSafeIterable = (thing) => | |
| 952 | thing != null && hasOwnInPrototypeChain(thing, iterator) && isIterable(thing); | |
| 953 | ||
| 893 | 954 | export default { |
| 894 | 955 | isArray, |
| 895 | 956 | isArrayBuffer, |
@@ -934,6 +995,8 @@ | ||
| 934 | 995 | isHTMLForm, |
| 935 | 996 | hasOwnProperty, |
| 936 | 997 | hasOwnProp: hasOwnProperty, // an alias to avoid ESLint no-prototype-builtins detection |
| 998 | hasOwnInPrototypeChain, | |
| 999 | getSafeProp, | |
| 937 | 1000 | reduceDescriptors, |
| 938 | 1001 | freezeMethods, |
| 939 | 1002 | toObjectSet, |
@@ -950,4 +1013,5 @@ | ||
| 950 | 1013 | setImmediate: _setImmediate, |
| 951 | 1014 | asap, |
| 952 | 1015 | isIterable, |
| 1016 | isSafeIterable, | |
| 953 | 1017 | }; |
@@ -1,5 +1,56 @@ | ||
| 1 | 1 | # Changelog |
| 2 | 2 | |
| 3 | ## v1.17.0 — June 1, 2026 | |
| 4 | ||
| 5 | This release adds Node HTTP zstd decompression, hardens config and release workflows, and fixes authentication, header, proxy, and type-handling regressions. | |
| 6 | ||
| 7 | ## 🔒 Security Fixes | |
| 8 | ||
| 9 | * **Config Hardening:** Guarded `socketPath`, `params`, and `paramsSerializer` reads with own-property checks to prevent inherited prototype values from affecting request behavior, including SSRF-sensitive paths. (__#10901__, __#10922__) | |
| 10 | * **Release Publishing:** Switched the publish workflow to npm staged publishing for safer, auditable package releases with provenance. (__#10926__) | |
| 11 | ||
| 12 | ## 🚀 New Features | |
| 13 | ||
| 14 | * **HTTP Compression:** Added Node HTTP adapter support for zstd response decompression, with `transitional.advertiseZstdAcceptEncoding` controlling whether `zstd` is advertised in `Accept-Encoding`. (__#6792__, __#10920__) | |
| 15 | ||
| 16 | ## 🐛 Bug Fixes | |
| 17 | ||
| 18 | * **Authentication Handling:** Restored Basic auth on same-origin Node redirects while continuing to strip credentials cross-origin, and aligned the fetch adapter with HTTP adapter behavior for URL-embedded Basic auth. (__#10929__, __#10896__) | |
| 19 | * **Proxy TLS:** Preserved user `httpsAgent` TLS options when tunneling HTTPS requests through HTTP CONNECT proxies. (__#10957__) | |
| 20 | * **React Native FormData:** Cleared default `Content-Type` for React Native `FormData` so multipart boundaries can be generated correctly. (__#10898__) | |
| 21 | * **Headers:** Silently skipped empty or whitespace-only header names instead of throwing, matching parsed-header behavior and avoiding React Native response crashes. (__#10875__) | |
| 22 | * **Request Data Merging:** Preserved enumerable symbol keys when cloning plain request data through axios merge logic. (__#10812__) | |
| 23 | * **Bundler Compatibility:** Converted `resolveConfig` from an arrow default export to a named function export to avoid webpack and Babel transform interop failures. (__#10891__) | |
| 24 | * **Types:** Corrected `AxiosHeaders.toJSON()` return types and updated CommonJS `isCancel` typings to narrow to `CanceledError<T>`. (__#10956__, __#10952__) | |
| 25 | * **Build Tooling:** Avoided emitting a null `Authorization` header from the GitHub build helper when `GITHUB_TOKEN` is unset. (__#10931__) | |
| 26 | ||
| 27 | ## 🔧 Maintenance & Chores | |
| 28 | ||
| 29 | * **HTTP/2 Internals:** Extracted `Http2Sessions` into its own helper module and added direct unit coverage for session pooling, timeout, and cleanup behavior. (__#10861__) | |
| 30 | * **Package Publishing:** Reduced published package size by switching to a `files` allowlist and dropping unneeded unminified bundle source maps. (__#10939__) | |
| 31 | * **CI and Release Automation:** Added bundle-size reporting, moved reports to the job summary, fixed bundle-size comparison coverage, added Node 26 to the matrix, pinned npm for staged publishing, and prepared the 1.17.0 release. (__#10907__, __#10911__, __#10916__, __#10927__, __#10935__, __#10983__) | |
| 32 | * **Developer Workflow:** Added a dev container and iterated on OpenSpec workflow files before removing them from the release branch. (__#10925__, __#10914__, __#10958__) | |
| 33 | * **Documentation and Policy:** Updated disclosure, contributor, collaboration, threat-model, advanced docs, README badges, release notes, moderator configuration, and project metadata. (__#10890__, __#10889__, __#10921__, __#10945__, __#10905__, __#10933__, __#10915__, __#10887__, __#10955__) | |
| 34 | * **Dependencies:** Bumped Babel tooling, Commitlint, ESLint, Rollup, Globals, Vitest, Playwright, `fs-extra`, `qs`, docs dependencies, and GitHub Actions dependencies including `actions/dependency-review-action` and `zizmorcore/zizmor-action`. (__#10871__, __#10879__, __#10918__, __#10919__, __#10934__, __#10947__, __#10954__, __#10960__) | |
| 35 | ||
| 36 | ## 🌟 New Contributors | |
| 37 | ||
| 38 | We are thrilled to welcome our new contributors. Thank you for helping improve axios: | |
| 39 | ||
| 40 | * __@BasixKOR__ (__#6792__) | |
| 41 | * __@carladams1299-lab__ (__#10861__) | |
| 42 | * __@LaplaceYoung__ (__#10812__) | |
| 43 | * __@JamieMagee__ (__#10939__) | |
| 44 | * __@RonGamzu__ (__#10905__) | |
| 45 | * __@sapirbaruch__ (__#10891__) | |
| 46 | * __@nezukoagent__ (__#10901__) | |
| 47 | * __@devareddy05__ (__#10929__) | |
| 48 | * __@Mohammad-Faiz-Cloud-Engineer__ (__#10922__) | |
| 49 | * __@azandabot__ (__#10931__) | |
| 50 | * __@niksy__ (__#10896__) | |
| 51 | ||
| 52 | [Full Changelog](https://github.com/axios/axios/compare/v1.16.1...v1.17.0) | |
| 53 | ||
| 3 | 54 | ## v1.16.1 — May 13, 2026 |
| 4 | 55 | |
| 5 | 56 | This release ships a defence-in-depth fix for prototype pollution in `formDataToJSON`, hardens proxy and CI workflows, restores Webpack 4 compatibility for the fetch adapter, and includes several small bug fixes and maintenance improvements. |
@@ -1432,7 +1483,7 @@ | ||
| 1432 | 1483 | |
| 1433 | 1484 | - fix: improve AxiosHeaders class [#5224](https://github.com/axios/axios/pull/5224) |
| 1434 | 1485 | - fix: TypeScript type definitions for commonjs [#5196](https://github.com/axios/axios/pull/5196) |
| 1435 | - fix: type definition of use method on AxiosInterceptorManager to match the the README [#5071](https://github.com/axios/axios/pull/5071) | |
| 1486 | - fix: type definition of use method on AxiosInterceptorManager to match the README [#5071](https://github.com/axios/axios/pull/5071) | |
| 1436 | 1487 | - fix: \_\_dirname is not defined in the sandbox [#5269](https://github.com/axios/axios/pull/5269) |
| 1437 | 1488 | - fix: AxiosError.toJSON method to avoid circular references [#5247](https://github.com/axios/axios/pull/5247) |
| 1438 | 1489 | - fix: Z_BUF_ERROR when content-encoding is set but the response body is empty [#5250](https://github.com/axios/axios/pull/5250) |
@@ -773,6 +773,7 @@ | ||
| 773 | 773 | // When no `transformRequest` is set, it must be of one of the following types: |
| 774 | 774 | // - string, plain object, ArrayBuffer, ArrayBufferView, URLSearchParams |
| 775 | 775 | // - Browser only: FormData, File, Blob |
| 776 | // - React Native: FormData | |
| 776 | 777 | // - Node only: Stream, Buffer, FormData (form-data package) |
| 777 | 778 | data: { |
| 778 | 779 | firstName: 'Fred' |
@@ -877,10 +878,12 @@ | ||
| 877 | 878 | // Do whatever you want with the Axios progress event |
| 878 | 879 | }, |
| 879 | 880 | |
| 880 | // `maxContentLength` defines the max size of the http response content in bytes allowed in node.js | |
| 881 | // `maxContentLength` defines the max size of the response content in bytes. | |
| 882 | // It is enforced by the Node.js HTTP adapter and the fetch adapter. | |
| 881 | 883 | maxContentLength: 2000, |
| 882 | 884 | |
| 883 | // `maxBodyLength` (Node only option) defines the max size of the http request content in bytes allowed | |
| 885 | // `maxBodyLength` defines the max size of the request content in bytes. | |
| 886 | // It is enforced by the Node.js HTTP adapter and the fetch adapter when the body length can be determined. | |
| 884 | 887 | maxBodyLength: 2000, |
| 885 | 888 | |
| 886 | 889 | // `redact` masks matching config keys when AxiosError#toJSON() is called. |
@@ -898,6 +901,12 @@ | ||
| 898 | 901 | // If set to 0, Axios follows no redirects. |
| 899 | 902 | maxRedirects: 21, // default |
| 900 | 903 | |
| 904 | // `sensitiveHeaders` (Node only option) lists custom secret-bearing headers | |
| 905 | // to remove from cross-origin redirects. Matching is case-insensitive. | |
| 906 | // Same-origin redirects keep these headers. If `maxRedirects` is 0, this | |
| 907 | // option is not used. | |
| 908 | sensitiveHeaders: ['X-API-Key'], | |
| 909 | ||
| 901 | 910 | // `beforeRedirect` defines a function that Axios calls before redirect. |
| 902 | 911 | // Use this to adjust the request options upon redirecting, |
| 903 | 912 | // to inspect the latest response headers, |
@@ -1640,6 +1649,7 @@ | ||
| 1640 | 1649 | |
| 1641 | 1650 | To send data as `multipart/form-data`, pass a FormData instance as the payload. |
| 1642 | 1651 | You do not need to set the `Content-Type` header. Axios detects it from the payload type. |
| 1652 | For browser, web worker, and React Native `FormData`, leave `Content-Type` unset so the runtime can add the multipart boundary. | |
| 1643 | 1653 | |
| 1644 | 1654 | ```js |
| 1645 | 1655 | const formData = new FormData(); |
@@ -2068,6 +2078,8 @@ | ||
| 2068 | 2078 | |
| 2069 | 2079 | The option can also accept a user-defined function that determines whether to overwrite the value. |
| 2070 | 2080 | |
| 2081 | Empty or whitespace-only header names are ignored. | |
| 2082 | ||
| 2071 | 2083 | Returns `this`. |
| 2072 | 2084 | |
| 2073 | 2085 | ### AxiosHeaders#get(header) |
@@ -2243,6 +2255,8 @@ | ||
| 2243 | 2255 | The adapter supports the same features as the `xhr` adapter, including upload and download progress capturing. |
| 2244 | 2256 | It also supports response types such as `stream` and `formdata` when the environment supports them. |
| 2245 | 2257 | |
| 2258 | When `auth` is omitted, the fetch adapter can read HTTP Basic auth credentials from the request URL, for example `https://user:pass@example.com`. Percent-encoded URL credentials are decoded before the `Authorization` header is generated, and `auth` takes precedence over URL-embedded credentials. | |
| 2259 | ||
| 2246 | 2260 | ### Custom fetch |
| 2247 | 2261 | |
| 2248 | 2262 | Since `v1.12.0`, you can configure the fetch adapter to use a custom fetch API instead of environment globals. |
@@ -2365,6 +2379,20 @@ | ||
| 2365 | 2379 | } |
| 2366 | 2380 | ``` |
| 2367 | 2381 | |
| 2382 | Use `axios.isCancel<T>()` to narrow cancellation errors to `CanceledError<T>`: | |
| 2383 | ||
| 2384 | ```typescript | |
| 2385 | const controller = new AbortController(); | |
| 2386 | ||
| 2387 | try { | |
| 2388 | await axios.get<User>('/user?ID=12345', { signal: controller.signal }); | |
| 2389 | } catch (error) { | |
| 2390 | if (axios.isCancel<User>(error)) { | |
| 2391 | handleCancellation(error); | |
| 2392 | } | |
| 2393 | } | |
| 2394 | ``` | |
| 2395 | ||
| 2368 | 2396 | Because axios publishes an ESM default export and a CJS `module.exports`, TypeScript has a few caveats. |
| 2369 | 2397 | The recommended setting is `"moduleResolution": "node16"`, which is implied by `"module": "node16"`. This requires TypeScript 4.7 or greater. |
| 2370 | 2398 | If you use ESM, your settings should be fine. |
@@ -284,6 +284,7 @@ | ||
| 284 | 284 | clarifyTimeoutError?: boolean; |
| 285 | 285 | legacyInterceptorReqResOrdering?: boolean; |
| 286 | 286 | advertiseZstdAcceptEncoding?: boolean; |
| 287 | validateStatusUndefinedResolves?: boolean; | |
| 287 | 288 | } |
| 288 | 289 | |
| 289 | 290 | export interface GenericAbortSignal { |
@@ -452,6 +453,7 @@ | ||
| 452 | 453 | }; |
| 453 | 454 | formDataHeaderPolicy?: 'legacy' | 'content-only'; |
| 454 | 455 | redact?: string[]; |
| 456 | sensitiveHeaders?: string[]; | |
| 455 | 457 | } |
| 456 | 458 | |
| 457 | 459 | // Alias |