Axios is vulnerable to DoS attack through lack of data size check
Description
Axios is a promise based HTTP client for the browser and Node.js. When Axios starting in version 0.28.0 and prior to versions 0.30.2 and 1.12.0 runs on Node.js and is given a URL with the data: scheme, it does not perform HTTP. Instead, its Node http adapter decodes the entire payload into memory (Buffer/Blob) and returns a synthetic 200 response. This path ignores maxContentLength / maxBodyLength (which only protect HTTP responses), so an attacker can supply a very large data: URI and cause the process to allocate unbounded memory and crash (DoS), even if the caller requested responseType: 'stream'. Versions 0.30.2 and 1.12.0 contain a patch for the issue.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
axiosnpm | >= 1.0.0, < 1.12.0 | 1.12.0 |
axiosnpm | >= 0.28.0, < 0.30.2 | 0.30.2 |
Affected products
1Patches
3a1b1d3f073a9fix: backport `maxContentLength` vulnerability fix to v0.x (#7034)
3 files changed · +122 −0
lib/adapters/http.js+16 −0 modified@@ -18,6 +18,7 @@ var CanceledError = require('../cancel/CanceledError'); var platform = require('../platform'); var fromDataURI = require('../helpers/fromDataURI'); var stream = require('stream'); +var estimateDataURLDecodedBytes = require('../helpers/estimateDataURLDecodedBytes.js'); var isHttps = /https:?/; @@ -114,6 +115,21 @@ module.exports = function httpAdapter(config) { var protocol = parsed.protocol || supportedProtocols[0]; if (protocol === 'data:') { + // Apply the same semantics as HTTP: only enforce if a finite, non-negative cap is set. + if (config.maxContentLength > -1) { + // Use the exact string passed to fromDataURI (config.url); fall back to fullPath if needed. + var dataUrl = String(config.url || fullPath || ''); + var estimated = estimateDataURLDecodedBytes(dataUrl); + + if (estimated > config.maxContentLength) { + return reject(new AxiosError( + 'maxContentLength size of ' + config.maxContentLength + ' exceeded', + AxiosError.ERR_BAD_RESPONSE, + config + )); + } + } + var convertedData; if (method !== 'GET') {
lib/helpers/estimateDataURLDecodedBytes.js+76 −0 added@@ -0,0 +1,76 @@ +/** + * 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: use UTF-8 byteLength of the encoded body as a safe upper bound. + * + * @param {string} url + * @returns {number} + */ +function estimateDataURLDecodedBytes(url) { + if (!url || typeof url !== 'string') return 0; + if (!url.startsWith('data:')) return 0; + + var comma = url.indexOf(','); + if (comma < 0) return 0; + + var meta = url.slice(5, comma); + var body = url.slice(comma + 1); + var isBase64 = /;base64/i.test(meta); + + if (isBase64) { + var effectiveLen = body.length; + var len = body.length; // cache length + + for (var i = 0; i < len; i++) { + if (body.charCodeAt(i) === 37 /* '%' */ && i + 2 < len) { + var a = body.charCodeAt(i + 1); + var b = body.charCodeAt(i + 2); + var isHex = + ((a >= 48 && a <= 57) || (a >= 65 && a <= 70) || (a >= 97 && a <= 102)) && + ((b >= 48 && b <= 57) || (b >= 65 && b <= 70) || (b >= 97 && b <= 102)); + + if (isHex) { + effectiveLen -= 2; + i += 2; + } + } + } + + var pad = 0; + var idx = len - 1; + + function tailIsPct3D(j) { + return 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++; + } + } + + var groups = Math.floor(effectiveLen / 4); + var bytes = groups * 3 - (pad || 0); + return bytes > 0 ? bytes : 0; + } + + return Buffer.byteLength(body, 'utf8'); +} + +module.exports = estimateDataURLDecodedBytes;
test/unit/helpers/estimateDataURLDecodedBytes.spec.js+30 −0 added@@ -0,0 +1,30 @@ +var assert = require('assert'); +var estimateDataURLDecodedBytes = require('../../../lib/helpers/estimateDataURLDecodedBytes'); + +describe('estimateDataURLDecodedBytes', () => { + it('should return 0 for non-data URLs', () => { + assert.strictEqual(estimateDataURLDecodedBytes('http://example.com'), 0); + }); + + it('should calculate length for simple non-base64 data URL', () => { + const url = 'data:,Hello'; + assert.strictEqual(estimateDataURLDecodedBytes(url), Buffer.byteLength('Hello', 'utf8')); + }); + + it('should calculate decoded length for base64 data URL', () => { + const str = 'Hello'; + const b64 = Buffer.from(str, 'utf8').toString('base64'); + const url = `data:text/plain;base64,${b64}`; + assert.strictEqual(estimateDataURLDecodedBytes(url), str.length); + }); + + it('should handle base64 with = padding', () => { + const url = 'data:text/plain;base64,TQ=='; // "M" + assert.strictEqual(estimateDataURLDecodedBytes(url), 1); + }); + + it('should handle base64 with %3D padding', () => { + const url = 'data:text/plain;base64,TQ%3D%3D'; // "M" + assert.strictEqual(estimateDataURLDecodedBytes(url), 1); + }); +});
945435fc5146fix(node): enforce maxContentLength for data: URLs (#7011)
4 files changed · +123 −0
lib/adapters/http.js+18 −0 modified@@ -25,6 +25,7 @@ import readBlob from "../helpers/readBlob.js"; import ZlibHeaderTransformStream from '../helpers/ZlibHeaderTransformStream.js'; import callbackify from "../helpers/callbackify.js"; import {progressEventReducer, progressEventDecorator, asyncDecorator} from "../helpers/progressEventReducer.js"; +import estimateDataURLDecodedBytes from '../helpers/estimateDataURLDecodedBytes.js'; const zlibOptions = { flush: zlib.constants.Z_SYNC_FLUSH, @@ -46,6 +47,7 @@ const supportedProtocols = platform.protocols.map(protocol => { return protocol + ':'; }); + const flushOnFinish = (stream, [throttled, flush]) => { stream .on('end', flush) @@ -54,6 +56,7 @@ const flushOnFinish = (stream, [throttled, flush]) => { return throttled; } + /** * If the proxy or config beforeRedirects functions are defined, call them with the options * object. @@ -233,6 +236,21 @@ export default isHttpAdapterSupported && function httpAdapter(config) { const protocol = parsed.protocol || supportedProtocols[0]; if (protocol === 'data:') { + // Apply the same semantics as HTTP: only enforce if a finite, non-negative cap is set. + if (config.maxContentLength > -1) { + // Use the exact string passed to fromDataURI (config.url); fall back to fullPath if needed. + const dataUrl = String(config.url || fullPath || ''); + const estimated = estimateDataURLDecodedBytes(dataUrl); + + if (estimated > config.maxContentLength) { + return reject(new AxiosError( + 'maxContentLength size of ' + config.maxContentLength + ' exceeded', + AxiosError.ERR_BAD_RESPONSE, + config + )); + } + } + let convertedData; if (method !== 'GET') {
lib/helpers/estimateDataURLDecodedBytes.js+73 −0 added@@ -0,0 +1,73 @@ +/** + * 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: use UTF-8 byteLength of the encoded body as a safe upper bound. + * + * @param {string} url + * @returns {number} + */ +export default function estimateDataURLDecodedBytes(url) { + if (!url || typeof url !== 'string') return 0; + if (!url.startsWith('data:')) return 0; + + const comma = url.indexOf(','); + if (comma < 0) return 0; + + const meta = url.slice(5, comma); + 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 = + ((a >= 48 && a <= 57) || (a >= 65 && a <= 70) || (a >= 97 && a <= 102)) && + ((b >= 48 && b <= 57) || (b >= 65 && b <= 70) || (b >= 97 && b <= 102)); + + 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 Buffer.byteLength(body, 'utf8'); +}
lib/utils.js+2 −0 modified@@ -635,6 +635,8 @@ const toFiniteNumber = (value, defaultValue) => { return value != null && Number.isFinite(value = +value) ? value : defaultValue; } + + /** * If the thing is a FormData object, return true, otherwise return false. *
test/unit/helpers/estimateDataURLDecodedBytes.spec.js+30 −0 added@@ -0,0 +1,30 @@ +import assert from 'assert'; +import estimateDataURLDecodedBytes from '../../../lib/helpers/estimateDataURLDecodedBytes.js'; + +describe('estimateDataURLDecodedBytes', () => { + it('should return 0 for non-data URLs', () => { + assert.strictEqual(estimateDataURLDecodedBytes('http://example.com'), 0); + }); + + it('should calculate length for simple non-base64 data URL', () => { + const url = 'data:,Hello'; + assert.strictEqual(estimateDataURLDecodedBytes(url), Buffer.byteLength('Hello', 'utf8')); + }); + + it('should calculate decoded length for base64 data URL', () => { + const str = 'Hello'; + const b64 = Buffer.from(str, 'utf8').toString('base64'); + const url = `data:text/plain;base64,${b64}`; + assert.strictEqual(estimateDataURLDecodedBytes(url), str.length); + }); + + it('should handle base64 with = padding', () => { + const url = 'data:text/plain;base64,TQ=='; // "M" + assert.strictEqual(estimateDataURLDecodedBytes(url), 1); + }); + + it('should handle base64 with %3D padding', () => { + const url = 'data:text/plain;base64,TQ%3D%3D'; // "M" + assert.strictEqual(estimateDataURLDecodedBytes(url), 1); + }); +});
c30252f685e8Added data URL support for node.js; (#4725)
17 files changed · +226 −48
.eslintrc.js+1 −1 modified@@ -80,7 +80,7 @@ module.exports = { 'no-new-wrappers': 2, // http://eslint.org/docs/rules/no-new-wrappers 'no-octal': 2, // http://eslint.org/docs/rules/no-octal 'no-octal-escape': 2, // http://eslint.org/docs/rules/no-octal-escape - 'no-param-reassign': 2, // http://eslint.org/docs/rules/no-param-reassign + 'no-param-reassign': 0, // http://eslint.org/docs/rules/no-param-reassign 'no-proto': 2, // http://eslint.org/docs/rules/no-proto 'no-redeclare': 2, // http://eslint.org/docs/rules/no-redeclare 'no-return-assign': 2, // http://eslint.org/docs/rules/no-return-assign
index.d.ts+3 −0 modified@@ -194,13 +194,16 @@ export class AxiosError<T = unknown, D = any> extends Error { isAxiosError: boolean; status?: number; toJSON: () => object; + cause?: Error; static readonly ERR_FR_TOO_MANY_REDIRECTS = "ERR_FR_TOO_MANY_REDIRECTS"; static readonly ERR_BAD_OPTION_VALUE = "ERR_BAD_OPTION_VALUE"; static readonly ERR_BAD_OPTION = "ERR_BAD_OPTION"; static readonly ERR_NETWORK = "ERR_NETWORK"; static readonly ERR_DEPRECATED = "ERR_DEPRECATED"; static readonly ERR_BAD_RESPONSE = "ERR_BAD_RESPONSE"; static readonly ERR_BAD_REQUEST = "ERR_BAD_REQUEST"; + static readonly ERR_NOT_SUPPORT = "ERR_NOT_SUPPORT"; + static readonly ERR_INVALID_URL = "ERR_INVALID_URL"; static readonly ERR_CANCELED = "ERR_CANCELED"; static readonly ECONNABORTED = "ECONNABORTED"; static readonly ETIMEDOUT = "ETIMEDOUT";
lib/adapters/http.js+76 −28 modified@@ -15,10 +15,15 @@ var VERSION = require('./../env/data').version; var transitionalDefaults = require('../defaults/transitional'); var AxiosError = require('../core/AxiosError'); var CanceledError = require('../cancel/CanceledError'); +var platform = require('../platform'); +var fromDataURI = require('../helpers/fromDataURI'); +var stream = require('stream'); var isHttps = /https:?/; -var supportedProtocols = [ 'http:', 'https:', 'file:' ]; +var supportedProtocols = platform.protocols.map(function(protocol) { + return protocol + ':'; +}); function dispatchBeforeRedirect(options) { if (options.beforeRedirects.proxy) { @@ -99,6 +104,62 @@ module.exports = function httpAdapter(config) { rejectPromise(value); }; var data = config.data; + var responseType = config.responseType; + var responseEncoding = config.responseEncoding; + var method = config.method.toUpperCase(); + + // Parse url + var fullPath = buildFullPath(config.baseURL, config.url); + var parsed = url.parse(fullPath); + var protocol = parsed.protocol || supportedProtocols[0]; + + if (protocol === 'data:') { + var convertedData; + + if (method !== 'GET') { + return settle(resolve, reject, { + status: 405, + statusText: 'method not allowed', + headers: {}, + config: config + }); + } + + try { + convertedData = fromDataURI(config.url, responseType === 'blob', { + Blob: config.env && config.env.Blob + }); + } catch (err) { + throw AxiosError.from(err, AxiosError.ERR_BAD_REQUEST, config); + } + + if (responseType === 'text') { + convertedData = convertedData.toString(responseEncoding); + + if (!responseEncoding || responseEncoding === 'utf8') { + data = utils.stripBOM(convertedData); + } + } else if (responseType === 'stream') { + convertedData = stream.Readable.from(convertedData); + } + + return settle(resolve, reject, { + data: convertedData, + status: 200, + statusText: 'OK', + headers: {}, + config: config + }); + } + + if (supportedProtocols.indexOf(protocol) === -1) { + return reject(new AxiosError( + 'Unsupported protocol ' + protocol, + AxiosError.ERR_BAD_REQUEST, + config + )); + } + var headers = config.headers; var headerNames = {}; @@ -159,19 +220,6 @@ module.exports = function httpAdapter(config) { auth = username + ':' + password; } - // Parse url - var fullPath = buildFullPath(config.baseURL, config.url); - var parsed = url.parse(fullPath); - var protocol = parsed.protocol || supportedProtocols[0]; - - if (supportedProtocols.indexOf(protocol) === -1) { - return reject(new AxiosError( - 'Unsupported protocol ' + protocol, - AxiosError.ERR_BAD_REQUEST, - config - )); - } - if (!auth && parsed.auth) { var urlAuth = parsed.auth.split(':'); var urlUsername = urlAuth[0] || ''; @@ -195,7 +243,7 @@ module.exports = function httpAdapter(config) { var options = { path: buildURL(parsed.path, config.params, config.paramsSerializer).replace(/^\?/, ''), - method: config.method.toUpperCase(), + method: method, headers: headers, agents: { http: config.httpAgent, https: config.httpsAgent }, auth: auth, @@ -242,7 +290,7 @@ module.exports = function httpAdapter(config) { if (req.aborted) return; // uncompress the response body transparently if required - var stream = res; + var responseStream = res; // return the last request in case of redirects var lastRequest = res.req || req; @@ -261,7 +309,7 @@ module.exports = function httpAdapter(config) { case 'compress': case 'deflate': // add the unzipper to the body stream processing pipeline - stream = stream.pipe(zlib.createUnzip()); + responseStream = responseStream.pipe(zlib.createUnzip()); // remove the content-encoding in order to not confuse downstream operations delete res.headers['content-encoding']; @@ -277,31 +325,31 @@ module.exports = function httpAdapter(config) { request: lastRequest }; - if (config.responseType === 'stream') { - response.data = stream; + if (responseType === 'stream') { + response.data = responseStream; settle(resolve, reject, response); } else { var responseBuffer = []; var totalResponseBytes = 0; - stream.on('data', function handleStreamData(chunk) { + responseStream.on('data', function handleStreamData(chunk) { responseBuffer.push(chunk); totalResponseBytes += chunk.length; // make sure the content length is not over the maxContentLength if specified if (config.maxContentLength > -1 && totalResponseBytes > config.maxContentLength) { // stream.destroy() emit aborted event before calling reject() on Node.js v16 rejected = true; - stream.destroy(); + responseStream.destroy(); reject(new AxiosError('maxContentLength size of ' + config.maxContentLength + ' exceeded', AxiosError.ERR_BAD_RESPONSE, config, lastRequest)); } }); - stream.on('aborted', function handlerStreamAborted() { + responseStream.on('aborted', function handlerStreamAborted() { if (rejected) { return; } - stream.destroy(); + responseStream.destroy(); reject(new AxiosError( 'maxContentLength size of ' + config.maxContentLength + ' exceeded', AxiosError.ERR_BAD_RESPONSE, @@ -310,17 +358,17 @@ module.exports = function httpAdapter(config) { )); }); - stream.on('error', function handleStreamError(err) { + responseStream.on('error', function handleStreamError(err) { if (req.aborted) return; reject(AxiosError.from(err, null, config, lastRequest)); }); - stream.on('end', function handleStreamEnd() { + responseStream.on('end', function handleStreamEnd() { try { var responseData = responseBuffer.length === 1 ? responseBuffer[0] : Buffer.concat(responseBuffer); - if (config.responseType !== 'arraybuffer') { - responseData = responseData.toString(config.responseEncoding); - if (!config.responseEncoding || config.responseEncoding === 'utf8') { + if (responseType !== 'arraybuffer') { + responseData = responseData.toString(responseEncoding); + if (!responseEncoding || responseEncoding === 'utf8') { responseData = utils.stripBOM(responseData); } }
lib/adapters/xhr.js+2 −1 modified@@ -11,6 +11,7 @@ var transitionalDefaults = require('../defaults/transitional'); var AxiosError = require('../core/AxiosError'); var CanceledError = require('../cancel/CanceledError'); var parseProtocol = require('../helpers/parseProtocol'); +var platform = require('../platform'); module.exports = function xhrAdapter(config) { return new Promise(function dispatchXhrRequest(resolve, reject) { @@ -210,7 +211,7 @@ module.exports = function xhrAdapter(config) { var protocol = parseProtocol(fullPath); - if (protocol && [ 'http', 'https', 'file', 'blob' ].indexOf(protocol) === -1) { + if (protocol && platform.protocols.indexOf(protocol) === -1) { reject(new AxiosError('Unsupported protocol ' + protocol + ':', AxiosError.ERR_BAD_REQUEST, config)); return; }
lib/core/AxiosError.js+5 −1 modified@@ -64,7 +64,9 @@ var descriptors = {}; 'ERR_DEPRECATED', 'ERR_BAD_RESPONSE', 'ERR_BAD_REQUEST', - 'ERR_CANCELED' + 'ERR_CANCELED', + 'ERR_NOT_SUPPORT', + 'ERR_INVALID_URL' // eslint-disable-next-line func-names ].forEach(function(code) { descriptors[code] = {value: code}; @@ -83,6 +85,8 @@ AxiosError.from = function(error, code, config, request, response, customProps) AxiosError.call(axiosError, error.message, code, config, request, response); + axiosError.cause = error; + axiosError.name = error.name; customProps && Object.assign(axiosError, customProps);
lib/defaults/index.js+8 −4 modified@@ -6,6 +6,7 @@ var AxiosError = require('../core/AxiosError'); var transitionalDefaults = require('./transitional'); var toFormData = require('../helpers/toFormData'); var toURLEncodedForm = require('../helpers/toURLEncodedForm'); +var platform = require('../platform'); var DEFAULT_CONTENT_TYPE = { 'Content-Type': 'application/x-www-form-urlencoded' @@ -101,11 +102,13 @@ var defaults = { transformResponse: [function transformResponse(data) { var transitional = this.transitional || defaults.transitional; - var silentJSONParsing = transitional && transitional.silentJSONParsing; var forcedJSONParsing = transitional && transitional.forcedJSONParsing; - var strictJSONParsing = !silentJSONParsing && this.responseType === 'json'; + var JSONRequested = this.responseType === 'json'; + + if (data && utils.isString(data) && ((forcedJSONParsing && !this.responseType) || JSONRequested)) { + var silentJSONParsing = transitional && transitional.silentJSONParsing; + var strictJSONParsing = !silentJSONParsing && JSONRequested; - if (strictJSONParsing || (forcedJSONParsing && utils.isString(data) && data.length)) { try { return JSON.parse(data); } catch (e) { @@ -134,7 +137,8 @@ var defaults = { maxBodyLength: -1, env: { - FormData: require('./env/FormData') + FormData: platform.classes.FormData, + Blob: platform.classes.Blob }, validateStatus: function validateStatus(status) {
lib/env/classes/FormData.js+0 −0 renamedlib/helpers/fromDataURI.js+51 −0 added@@ -0,0 +1,51 @@ +'use strict'; + +var AxiosError = require('../core/AxiosError'); +var parseProtocol = require('./parseProtocol'); +var platform = require('../platform'); + +var DATA_URL_PATTERN = /^(?:([^;]+);)?(?:[^;]+;)?(base64|),([\s\S]*)$/; + +/** + * Parse data uri to a Buffer or Blob + * @param {String} uri + * @param {?Boolean} asBlob + * @param {?Object} options + * @param {?Function} options.Blob + * @returns {Buffer|Blob} + */ +module.exports = function fromDataURI(uri, asBlob, options) { + var _Blob = options && options.Blob || platform.classes.Blob; + var protocol = parseProtocol(uri); + + if (asBlob === undefined && _Blob) { + asBlob = true; + } + + if (protocol === 'data') { + uri = uri.slice(protocol.length); + + var match = DATA_URL_PATTERN.exec(uri); + + if (!match) { + throw new AxiosError('Invalid URL', AxiosError.ERR_INVALID_URL); + } + + var mime = match[1]; + var isBase64 = match[2]; + var body = match[3]; + var buffer = Buffer.from(decodeURIComponent(body), isBase64 ? 'base64' : 'utf8'); + + if (asBlob) { + if (!_Blob) { + throw new AxiosError('Blob is not supported', AxiosError.ERR_NOT_SUPPORT); + } + + return new _Blob([buffer], {type: mime}); + } + + return buffer; + } + + throw new AxiosError('Unsupported protocol ' + protocol, AxiosError.ERR_NOT_SUPPORT); +};
lib/helpers/toFormData.js+1 −1 modified@@ -1,7 +1,7 @@ 'use strict'; var utils = require('../utils'); -var envFormData = require('../defaults/env/FormData'); +var envFormData = require('../env/classes/FormData'); function isVisitable(thing) { return utils.isPlainObject(thing) || utils.isArray(thing);
lib/platform/browser/classes/FormData.js+3 −0 added@@ -0,0 +1,3 @@ +'use strict'; + +module.exports = FormData;
lib/platform/browser/index.js+5 −2 modified@@ -3,6 +3,9 @@ module.exports = { isBrowser: true, classes: { - URLSearchParams: require('./classes/URLSearchParams') - } + URLSearchParams: require('./classes/URLSearchParams'), + FormData: require('./classes/FormData'), + Blob: Blob + }, + protocols: ['http', 'https', 'file', 'blob', 'url'] };
lib/platform/node/classes/FormData.js+3 −0 added@@ -0,0 +1,3 @@ +'use strict'; + +module.exports = require('form-data');
lib/platform/node/index.js+5 −2 modified@@ -3,6 +3,9 @@ module.exports = { isNode: true, classes: { - URLSearchParams: require('./classes/URLSearchParams') - } + URLSearchParams: require('./classes/URLSearchParams'), + FormData: require('./classes/FormData'), + Blob: typeof Blob !== 'undefined' && Blob || null + }, + protocols: [ 'http', 'https', 'file', 'data' ] };
lib/utils.js+8 −7 modified@@ -229,15 +229,16 @@ function trim(str) { * navigator.product -> 'NativeScript' or 'NS' */ function isStandardBrowserEnv() { - if (typeof navigator !== 'undefined' && (navigator.product === 'ReactNative' || - navigator.product === 'NativeScript' || - navigator.product === 'NS')) { + var product; + if (typeof navigator !== 'undefined' && ( + (product = navigator.product) === 'ReactNative' || + product === 'NativeScript' || + product === 'NS') + ) { return false; } - return ( - typeof window !== 'undefined' && - typeof document !== 'undefined' - ); + + return typeof window !== 'undefined' && typeof document !== 'undefined'; } /**
package.json+0 −1 modified@@ -80,7 +80,6 @@ }, "browser": { "./lib/adapters/http.js": "./lib/adapters/xhr.js", - "./lib/defaults/env/FormData.js": "./lib/helpers/null.js", "./lib/platform/node/index.js": "./lib/platform/browser/index.js" }, "jsdelivr": "dist/axios.min.js",
test/unit/adapters/http.js+43 −0 modified@@ -1442,4 +1442,47 @@ describe('supports http with nodejs', function () { }).catch(done); }); }); + + describe('Data URL', function () { + it('should support requesting data URL as a Buffer', function (done) { + const buffer = Buffer.from('123'); + + const dataURI = 'data:application/octet-stream;base64,' + buffer.toString('base64'); + + axios.get(dataURI).then(({data})=> { + assert.deepStrictEqual(data, buffer); + done(); + }).catch(done); + }); + + it('should support requesting data URL as a String (text)', function (done) { + const buffer = Buffer.from('123', 'utf-8'); + + const dataURI = 'data:application/octet-stream;base64,' + buffer.toString('base64'); + + axios.get(dataURI, {responseType: "text"}).then(({data})=> { + assert.deepStrictEqual(data, '123'); + done(); + }).catch(done); + }); + + it('should support requesting data URL as a Stream', function (done) { + const buffer = Buffer.from('123', 'utf-8'); + + const dataURI = 'data:application/octet-stream;base64,' + buffer.toString('base64'); + + axios.get(dataURI, {responseType: "stream"}).then(({data})=> { + var str = ''; + + data.on('data', function(response){ + str += response.toString(); + }); + + data.on('end', function(){ + assert.strictEqual(str, '123'); + done(); + }); + }).catch(done); + }); + }); });
test/unit/helpers/fromDataURI.js+12 −0 added@@ -0,0 +1,12 @@ +var assert = require('assert'); +var fromDataURI = require('../../../lib/helpers/fromDataURI'); + +describe('helpers::fromDataURI', function () { + it('should return buffer from data uri', function () { + const buffer= Buffer.from('123'); + + const dataURI = 'data:application/octet-stream;base64,' + buffer.toString('base64'); + + assert.deepStrictEqual(fromDataURI(dataURI), buffer); + }); +});
Vulnerability mechanics
Synthesis attempt was rejected by the grounding validator. Re-run pending.
References
10- github.com/advisories/GHSA-4hjh-wcwx-xvwjghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2025-58754ghsaADVISORY
- github.com/axios/axios/commit/945435fc51467303768202250debb8d4ae892593ghsax_refsource_MISCWEB
- github.com/axios/axios/commit/a1b1d3f073a988601583a604f5f9f5d05a3d0b67ghsax_refsource_MISCWEB
- github.com/axios/axios/commit/c30252f685e8f4326722de84923fcbc8cf557f06ghsax_refsource_MISCWEB
- github.com/axios/axios/pull/7011ghsax_refsource_MISCWEB
- github.com/axios/axios/pull/7034ghsax_refsource_MISCWEB
- github.com/axios/axios/releases/tag/v0.30.2ghsax_refsource_MISCWEB
- github.com/axios/axios/releases/tag/v1.12.0ghsax_refsource_MISCWEB
- github.com/axios/axios/security/advisories/GHSA-4hjh-wcwx-xvwjghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.