mirror of
https://github.com/tenrok/axios.git
synced 2026-05-15 11:59:42 +03:00
fix: Denial of Service via __proto__ Key in mergeConfig (#7369)
* fix: sec issue as per advisory * chore: expand and add tests
This commit is contained in:
+22
-8
@@ -1,9 +1,10 @@
|
|||||||
'use strict';
|
"use strict";
|
||||||
|
|
||||||
import utils from '../utils.js';
|
import utils from "../utils.js";
|
||||||
import AxiosHeaders from "./AxiosHeaders.js";
|
import AxiosHeaders from "./AxiosHeaders.js";
|
||||||
|
|
||||||
const headersToObject = (thing) => thing instanceof AxiosHeaders ? { ...thing } : thing;
|
const headersToObject = (thing) =>
|
||||||
|
thing instanceof AxiosHeaders ? { ...thing } : thing;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Config-specific merge-function which creates a new config-object
|
* Config-specific merge-function which creates a new config-object
|
||||||
@@ -92,14 +93,27 @@ export default function mergeConfig(config1, config2) {
|
|||||||
socketPath: defaultToConfig2,
|
socketPath: defaultToConfig2,
|
||||||
responseEncoding: defaultToConfig2,
|
responseEncoding: defaultToConfig2,
|
||||||
validateStatus: mergeDirectKeys,
|
validateStatus: mergeDirectKeys,
|
||||||
headers: (a, b, prop) => mergeDeepProperties(headersToObject(a), headersToObject(b), prop, true)
|
headers: (a, b, prop) =>
|
||||||
|
mergeDeepProperties(headersToObject(a), headersToObject(b), prop, true),
|
||||||
};
|
};
|
||||||
|
|
||||||
utils.forEach(Object.keys({ ...config1, ...config2 }), function computeConfigValue(prop) {
|
utils.forEach(
|
||||||
const merge = mergeMap[prop] || mergeDeepProperties;
|
Object.keys({ ...config1, ...config2 }),
|
||||||
|
function computeConfigValue(prop) {
|
||||||
|
if (
|
||||||
|
prop === "__proto__" ||
|
||||||
|
prop === "constructor" ||
|
||||||
|
prop === "prototype"
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
const merge = utils.hasOwnProp(mergeMap, prop)
|
||||||
|
? mergeMap[prop]
|
||||||
|
: mergeDeepProperties;
|
||||||
const configValue = merge(config1[prop], config2[prop], prop);
|
const configValue = merge(config1[prop], config2[prop], prop);
|
||||||
(utils.isUndefined(configValue) && merge !== mergeDirectKeys) || (config[prop] = configValue);
|
(utils.isUndefined(configValue) && merge !== mergeDirectKeys) ||
|
||||||
});
|
(config[prop] = configValue);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|||||||
+179
-115
@@ -1,6 +1,6 @@
|
|||||||
'use strict';
|
"use strict";
|
||||||
|
|
||||||
import bind from './helpers/bind.js';
|
import bind from "./helpers/bind.js";
|
||||||
|
|
||||||
// utils is a library of generic helper functions non-specific to axios
|
// utils is a library of generic helper functions non-specific to axios
|
||||||
|
|
||||||
@@ -8,17 +8,17 @@ const {toString} = Object.prototype;
|
|||||||
const { getPrototypeOf } = Object;
|
const { getPrototypeOf } = Object;
|
||||||
const { iterator, toStringTag } = Symbol;
|
const { iterator, toStringTag } = Symbol;
|
||||||
|
|
||||||
const kindOf = (cache => thing => {
|
const kindOf = ((cache) => (thing) => {
|
||||||
const str = toString.call(thing);
|
const str = toString.call(thing);
|
||||||
return cache[str] || (cache[str] = str.slice(8, -1).toLowerCase());
|
return cache[str] || (cache[str] = str.slice(8, -1).toLowerCase());
|
||||||
})(Object.create(null));
|
})(Object.create(null));
|
||||||
|
|
||||||
const kindOfTest = (type) => {
|
const kindOfTest = (type) => {
|
||||||
type = type.toLowerCase();
|
type = type.toLowerCase();
|
||||||
return (thing) => kindOf(thing) === type
|
return (thing) => kindOf(thing) === type;
|
||||||
}
|
};
|
||||||
|
|
||||||
const typeOfTest = type => thing => typeof thing === type;
|
const typeOfTest = (type) => (thing) => typeof thing === type;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine if a value is a non-null object
|
* Determine if a value is a non-null object
|
||||||
@@ -36,7 +36,7 @@ const {isArray} = Array;
|
|||||||
*
|
*
|
||||||
* @returns {boolean} True if the value is undefined, otherwise false
|
* @returns {boolean} True if the value is undefined, otherwise false
|
||||||
*/
|
*/
|
||||||
const isUndefined = typeOfTest('undefined');
|
const isUndefined = typeOfTest("undefined");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine if a value is a Buffer
|
* Determine if a value is a Buffer
|
||||||
@@ -46,8 +46,14 @@ const isUndefined = typeOfTest('undefined');
|
|||||||
* @returns {boolean} True if value is a Buffer, otherwise false
|
* @returns {boolean} True if value is a Buffer, otherwise false
|
||||||
*/
|
*/
|
||||||
function isBuffer(val) {
|
function isBuffer(val) {
|
||||||
return val !== null && !isUndefined(val) && val.constructor !== null && !isUndefined(val.constructor)
|
return (
|
||||||
&& isFunction(val.constructor.isBuffer) && val.constructor.isBuffer(val);
|
val !== null &&
|
||||||
|
!isUndefined(val) &&
|
||||||
|
val.constructor !== null &&
|
||||||
|
!isUndefined(val.constructor) &&
|
||||||
|
isFunction(val.constructor.isBuffer) &&
|
||||||
|
val.constructor.isBuffer(val)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -57,8 +63,7 @@ function isBuffer(val) {
|
|||||||
*
|
*
|
||||||
* @returns {boolean} True if value is an ArrayBuffer, otherwise false
|
* @returns {boolean} True if value is an ArrayBuffer, otherwise false
|
||||||
*/
|
*/
|
||||||
const isArrayBuffer = kindOfTest('ArrayBuffer');
|
const isArrayBuffer = kindOfTest("ArrayBuffer");
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine if a value is a view on an ArrayBuffer
|
* Determine if a value is a view on an ArrayBuffer
|
||||||
@@ -69,10 +74,10 @@ const isArrayBuffer = kindOfTest('ArrayBuffer');
|
|||||||
*/
|
*/
|
||||||
function isArrayBufferView(val) {
|
function isArrayBufferView(val) {
|
||||||
let result;
|
let result;
|
||||||
if ((typeof ArrayBuffer !== 'undefined') && (ArrayBuffer.isView)) {
|
if (typeof ArrayBuffer !== "undefined" && ArrayBuffer.isView) {
|
||||||
result = ArrayBuffer.isView(val);
|
result = ArrayBuffer.isView(val);
|
||||||
} else {
|
} else {
|
||||||
result = (val) && (val.buffer) && (isArrayBuffer(val.buffer));
|
result = val && val.buffer && isArrayBuffer(val.buffer);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -84,7 +89,7 @@ function isArrayBufferView(val) {
|
|||||||
*
|
*
|
||||||
* @returns {boolean} True if value is a String, otherwise false
|
* @returns {boolean} True if value is a String, otherwise false
|
||||||
*/
|
*/
|
||||||
const isString = typeOfTest('string');
|
const isString = typeOfTest("string");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine if a value is a Function
|
* Determine if a value is a Function
|
||||||
@@ -92,7 +97,7 @@ const isString = typeOfTest('string');
|
|||||||
* @param {*} val The value to test
|
* @param {*} val The value to test
|
||||||
* @returns {boolean} True if value is a Function, otherwise false
|
* @returns {boolean} True if value is a Function, otherwise false
|
||||||
*/
|
*/
|
||||||
const isFunction = typeOfTest('function');
|
const isFunction = typeOfTest("function");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine if a value is a Number
|
* Determine if a value is a Number
|
||||||
@@ -101,7 +106,7 @@ const isFunction = typeOfTest('function');
|
|||||||
*
|
*
|
||||||
* @returns {boolean} True if value is a Number, otherwise false
|
* @returns {boolean} True if value is a Number, otherwise false
|
||||||
*/
|
*/
|
||||||
const isNumber = typeOfTest('number');
|
const isNumber = typeOfTest("number");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine if a value is an Object
|
* Determine if a value is an Object
|
||||||
@@ -110,7 +115,7 @@ const isNumber = typeOfTest('number');
|
|||||||
*
|
*
|
||||||
* @returns {boolean} True if value is an Object, otherwise false
|
* @returns {boolean} True if value is an Object, otherwise false
|
||||||
*/
|
*/
|
||||||
const isObject = (thing) => thing !== null && typeof thing === 'object';
|
const isObject = (thing) => thing !== null && typeof thing === "object";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine if a value is a Boolean
|
* Determine if a value is a Boolean
|
||||||
@@ -118,7 +123,7 @@ const isObject = (thing) => thing !== null && typeof thing === 'object';
|
|||||||
* @param {*} thing The value to test
|
* @param {*} thing The value to test
|
||||||
* @returns {boolean} True if value is a Boolean, otherwise false
|
* @returns {boolean} True if value is a Boolean, otherwise false
|
||||||
*/
|
*/
|
||||||
const isBoolean = thing => thing === true || thing === false;
|
const isBoolean = (thing) => thing === true || thing === false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine if a value is a plain Object
|
* Determine if a value is a plain Object
|
||||||
@@ -128,13 +133,19 @@ const isBoolean = thing => thing === true || thing === false;
|
|||||||
* @returns {boolean} True if value is a plain Object, otherwise false
|
* @returns {boolean} True if value is a plain Object, otherwise false
|
||||||
*/
|
*/
|
||||||
const isPlainObject = (val) => {
|
const isPlainObject = (val) => {
|
||||||
if (kindOf(val) !== 'object') {
|
if (kindOf(val) !== "object") {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const prototype = getPrototypeOf(val);
|
const prototype = getPrototypeOf(val);
|
||||||
return (prototype === null || prototype === Object.prototype || Object.getPrototypeOf(prototype) === null) && !(toStringTag in val) && !(iterator in val);
|
return (
|
||||||
}
|
(prototype === null ||
|
||||||
|
prototype === Object.prototype ||
|
||||||
|
Object.getPrototypeOf(prototype) === null) &&
|
||||||
|
!(toStringTag in val) &&
|
||||||
|
!(iterator in val)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine if a value is an empty object (safely handles Buffers)
|
* Determine if a value is an empty object (safely handles Buffers)
|
||||||
@@ -150,12 +161,15 @@ const isEmptyObject = (val) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return Object.keys(val).length === 0 && Object.getPrototypeOf(val) === Object.prototype;
|
return (
|
||||||
|
Object.keys(val).length === 0 &&
|
||||||
|
Object.getPrototypeOf(val) === Object.prototype
|
||||||
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Fallback for any other objects that might cause RangeError with Object.keys()
|
// Fallback for any other objects that might cause RangeError with Object.keys()
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine if a value is a Date
|
* Determine if a value is a Date
|
||||||
@@ -164,7 +178,7 @@ const isEmptyObject = (val) => {
|
|||||||
*
|
*
|
||||||
* @returns {boolean} True if value is a Date, otherwise false
|
* @returns {boolean} True if value is a Date, otherwise false
|
||||||
*/
|
*/
|
||||||
const isDate = kindOfTest('Date');
|
const isDate = kindOfTest("Date");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine if a value is a File
|
* Determine if a value is a File
|
||||||
@@ -173,7 +187,7 @@ const isDate = kindOfTest('Date');
|
|||||||
*
|
*
|
||||||
* @returns {boolean} True if value is a File, otherwise false
|
* @returns {boolean} True if value is a File, otherwise false
|
||||||
*/
|
*/
|
||||||
const isFile = kindOfTest('File');
|
const isFile = kindOfTest("File");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine if a value is a Blob
|
* Determine if a value is a Blob
|
||||||
@@ -182,7 +196,7 @@ const isFile = kindOfTest('File');
|
|||||||
*
|
*
|
||||||
* @returns {boolean} True if value is a Blob, otherwise false
|
* @returns {boolean} True if value is a Blob, otherwise false
|
||||||
*/
|
*/
|
||||||
const isBlob = kindOfTest('Blob');
|
const isBlob = kindOfTest("Blob");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine if a value is a FileList
|
* Determine if a value is a FileList
|
||||||
@@ -191,7 +205,7 @@ const isBlob = kindOfTest('Blob');
|
|||||||
*
|
*
|
||||||
* @returns {boolean} True if value is a File, otherwise false
|
* @returns {boolean} True if value is a File, otherwise false
|
||||||
*/
|
*/
|
||||||
const isFileList = kindOfTest('FileList');
|
const isFileList = kindOfTest("FileList");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine if a value is a Stream
|
* Determine if a value is a Stream
|
||||||
@@ -211,16 +225,17 @@ const isStream = (val) => isObject(val) && isFunction(val.pipe);
|
|||||||
*/
|
*/
|
||||||
const isFormData = (thing) => {
|
const isFormData = (thing) => {
|
||||||
let kind;
|
let kind;
|
||||||
return thing && (
|
return (
|
||||||
(typeof FormData === 'function' && thing instanceof FormData) || (
|
thing &&
|
||||||
isFunction(thing.append) && (
|
((typeof FormData === "function" && thing instanceof FormData) ||
|
||||||
(kind = kindOf(thing)) === 'formdata' ||
|
(isFunction(thing.append) &&
|
||||||
|
((kind = kindOf(thing)) === "formdata" ||
|
||||||
// detect form-data instance
|
// detect form-data instance
|
||||||
(kind === 'object' && isFunction(thing.toString) && thing.toString() === '[object FormData]')
|
(kind === "object" &&
|
||||||
)
|
isFunction(thing.toString) &&
|
||||||
)
|
thing.toString() === "[object FormData]"))))
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine if a value is a URLSearchParams object
|
* Determine if a value is a URLSearchParams object
|
||||||
@@ -229,9 +244,14 @@ const isFormData = (thing) => {
|
|||||||
*
|
*
|
||||||
* @returns {boolean} True if value is a URLSearchParams object, otherwise false
|
* @returns {boolean} True if value is a URLSearchParams object, otherwise false
|
||||||
*/
|
*/
|
||||||
const isURLSearchParams = kindOfTest('URLSearchParams');
|
const isURLSearchParams = kindOfTest("URLSearchParams");
|
||||||
|
|
||||||
const [isReadableStream, isRequest, isResponse, isHeaders] = ['ReadableStream', 'Request', 'Response', 'Headers'].map(kindOfTest);
|
const [isReadableStream, isRequest, isResponse, isHeaders] = [
|
||||||
|
"ReadableStream",
|
||||||
|
"Request",
|
||||||
|
"Response",
|
||||||
|
"Headers",
|
||||||
|
].map(kindOfTest);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Trim excess whitespace off the beginning and end of a string
|
* Trim excess whitespace off the beginning and end of a string
|
||||||
@@ -240,8 +260,8 @@ const [isReadableStream, isRequest, isResponse, isHeaders] = ['ReadableStream',
|
|||||||
*
|
*
|
||||||
* @returns {String} The String freed of excess whitespace
|
* @returns {String} The String freed of excess whitespace
|
||||||
*/
|
*/
|
||||||
const trim = (str) => str.trim ?
|
const trim = (str) =>
|
||||||
str.trim() : str.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
|
str.trim ? str.trim() : str.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, "");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Iterate over an Array or an Object invoking a function for each item.
|
* Iterate over an Array or an Object invoking a function for each item.
|
||||||
@@ -261,7 +281,7 @@ const trim = (str) => str.trim ?
|
|||||||
*/
|
*/
|
||||||
function forEach(obj, fn, { allOwnKeys = false } = {}) {
|
function forEach(obj, fn, { allOwnKeys = false } = {}) {
|
||||||
// Don't bother if no value provided
|
// Don't bother if no value provided
|
||||||
if (obj === null || typeof obj === 'undefined') {
|
if (obj === null || typeof obj === "undefined") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -269,7 +289,7 @@ function forEach(obj, fn, {allOwnKeys = false} = {}) {
|
|||||||
let l;
|
let l;
|
||||||
|
|
||||||
// Force an array if not already something iterable
|
// Force an array if not already something iterable
|
||||||
if (typeof obj !== 'object') {
|
if (typeof obj !== "object") {
|
||||||
/*eslint no-param-reassign:0*/
|
/*eslint no-param-reassign:0*/
|
||||||
obj = [obj];
|
obj = [obj];
|
||||||
}
|
}
|
||||||
@@ -286,7 +306,9 @@ function forEach(obj, fn, {allOwnKeys = false} = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Iterate over object keys
|
// Iterate over object keys
|
||||||
const keys = allOwnKeys ? Object.getOwnPropertyNames(obj) : Object.keys(obj);
|
const keys = allOwnKeys
|
||||||
|
? Object.getOwnPropertyNames(obj)
|
||||||
|
: Object.keys(obj);
|
||||||
const len = keys.length;
|
const len = keys.length;
|
||||||
let key;
|
let key;
|
||||||
|
|
||||||
@@ -318,10 +340,15 @@ function findKey(obj, key) {
|
|||||||
const _global = (() => {
|
const _global = (() => {
|
||||||
/*eslint no-undef:0*/
|
/*eslint no-undef:0*/
|
||||||
if (typeof globalThis !== "undefined") return globalThis;
|
if (typeof globalThis !== "undefined") return globalThis;
|
||||||
return typeof self !== "undefined" ? self : (typeof window !== 'undefined' ? window : global)
|
return typeof self !== "undefined"
|
||||||
|
? self
|
||||||
|
: typeof window !== "undefined"
|
||||||
|
? window
|
||||||
|
: global;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
const isContextDefined = (context) => !isUndefined(context) && context !== _global;
|
const isContextDefined = (context) =>
|
||||||
|
!isUndefined(context) && context !== _global;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Accepts varargs expecting each argument to be an object, then
|
* Accepts varargs expecting each argument to be an object, then
|
||||||
@@ -342,10 +369,15 @@ const isContextDefined = (context) => !isUndefined(context) && context !== _glob
|
|||||||
* @returns {Object} Result of all merge properties
|
* @returns {Object} Result of all merge properties
|
||||||
*/
|
*/
|
||||||
function merge(/* obj1, obj2, obj3, ... */) {
|
function merge(/* obj1, obj2, obj3, ... */) {
|
||||||
const {caseless, skipUndefined} = isContextDefined(this) && this || {};
|
const { caseless, skipUndefined } = (isContextDefined(this) && this) || {};
|
||||||
const result = {};
|
const result = {};
|
||||||
const assignValue = (val, key) => {
|
const assignValue = (val, key) => {
|
||||||
const targetKey = caseless && findKey(result, key) || key;
|
// Skip dangerous property names to prevent prototype pollution
|
||||||
|
if (key === "__proto__" || key === "constructor" || key === "prototype") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetKey = (caseless && findKey(result, key)) || key;
|
||||||
if (isPlainObject(result[targetKey]) && isPlainObject(val)) {
|
if (isPlainObject(result[targetKey]) && isPlainObject(val)) {
|
||||||
result[targetKey] = merge(result[targetKey], val);
|
result[targetKey] = merge(result[targetKey], val);
|
||||||
} else if (isPlainObject(val)) {
|
} else if (isPlainObject(val)) {
|
||||||
@@ -355,7 +387,7 @@ function merge(/* obj1, obj2, obj3, ... */) {
|
|||||||
} else if (!skipUndefined || !isUndefined(val)) {
|
} else if (!skipUndefined || !isUndefined(val)) {
|
||||||
result[targetKey] = val;
|
result[targetKey] = val;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
for (let i = 0, l = arguments.length; i < l; i++) {
|
for (let i = 0, l = arguments.length; i < l; i++) {
|
||||||
arguments[i] && forEach(arguments[i], assignValue);
|
arguments[i] && forEach(arguments[i], assignValue);
|
||||||
@@ -375,25 +407,29 @@ function merge(/* obj1, obj2, obj3, ... */) {
|
|||||||
* @returns {Object} The resulting value of object a
|
* @returns {Object} The resulting value of object a
|
||||||
*/
|
*/
|
||||||
const extend = (a, b, thisArg, { allOwnKeys } = {}) => {
|
const extend = (a, b, thisArg, { allOwnKeys } = {}) => {
|
||||||
forEach(b, (val, key) => {
|
forEach(
|
||||||
|
b,
|
||||||
|
(val, key) => {
|
||||||
if (thisArg && isFunction(val)) {
|
if (thisArg && isFunction(val)) {
|
||||||
Object.defineProperty(a, key, {
|
Object.defineProperty(a, key, {
|
||||||
value: bind(val, thisArg),
|
value: bind(val, thisArg),
|
||||||
writable: true,
|
writable: true,
|
||||||
enumerable: true,
|
enumerable: true,
|
||||||
configurable: true
|
configurable: true,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Object.defineProperty(a, key, {
|
Object.defineProperty(a, key, {
|
||||||
value: val,
|
value: val,
|
||||||
writable: true,
|
writable: true,
|
||||||
enumerable: true,
|
enumerable: true,
|
||||||
configurable: true
|
configurable: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, {allOwnKeys});
|
},
|
||||||
|
{ allOwnKeys },
|
||||||
|
);
|
||||||
return a;
|
return a;
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove byte order marker. This catches EF BB BF (the UTF-8 BOM)
|
* Remove byte order marker. This catches EF BB BF (the UTF-8 BOM)
|
||||||
@@ -403,11 +439,11 @@ const extend = (a, b, thisArg, {allOwnKeys}= {}) => {
|
|||||||
* @returns {string} content value without BOM
|
* @returns {string} content value without BOM
|
||||||
*/
|
*/
|
||||||
const stripBOM = (content) => {
|
const stripBOM = (content) => {
|
||||||
if (content.charCodeAt(0) === 0xFEFF) {
|
if (content.charCodeAt(0) === 0xfeff) {
|
||||||
content = content.slice(1);
|
content = content.slice(1);
|
||||||
}
|
}
|
||||||
return content;
|
return content;
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inherit the prototype methods from one constructor into another
|
* Inherit the prototype methods from one constructor into another
|
||||||
@@ -419,18 +455,21 @@ const stripBOM = (content) => {
|
|||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
const inherits = (constructor, superConstructor, props, descriptors) => {
|
const inherits = (constructor, superConstructor, props, descriptors) => {
|
||||||
constructor.prototype = Object.create(superConstructor.prototype, descriptors);
|
constructor.prototype = Object.create(
|
||||||
Object.defineProperty(constructor.prototype, 'constructor', {
|
superConstructor.prototype,
|
||||||
|
descriptors,
|
||||||
|
);
|
||||||
|
Object.defineProperty(constructor.prototype, "constructor", {
|
||||||
value: constructor,
|
value: constructor,
|
||||||
writable: true,
|
writable: true,
|
||||||
enumerable: false,
|
enumerable: false,
|
||||||
configurable: true
|
configurable: true,
|
||||||
});
|
});
|
||||||
Object.defineProperty(constructor, 'super', {
|
Object.defineProperty(constructor, "super", {
|
||||||
value: superConstructor.prototype
|
value: superConstructor.prototype,
|
||||||
});
|
});
|
||||||
props && Object.assign(constructor.prototype, props);
|
props && Object.assign(constructor.prototype, props);
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolve object with deep prototype chain to a flat object
|
* Resolve object with deep prototype chain to a flat object
|
||||||
@@ -456,16 +495,23 @@ const toFlatObject = (sourceObj, destObj, filter, propFilter) => {
|
|||||||
i = props.length;
|
i = props.length;
|
||||||
while (i-- > 0) {
|
while (i-- > 0) {
|
||||||
prop = props[i];
|
prop = props[i];
|
||||||
if ((!propFilter || propFilter(prop, sourceObj, destObj)) && !merged[prop]) {
|
if (
|
||||||
|
(!propFilter || propFilter(prop, sourceObj, destObj)) &&
|
||||||
|
!merged[prop]
|
||||||
|
) {
|
||||||
destObj[prop] = sourceObj[prop];
|
destObj[prop] = sourceObj[prop];
|
||||||
merged[prop] = true;
|
merged[prop] = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sourceObj = filter !== false && getPrototypeOf(sourceObj);
|
sourceObj = filter !== false && getPrototypeOf(sourceObj);
|
||||||
} while (sourceObj && (!filter || filter(sourceObj, destObj)) && sourceObj !== Object.prototype);
|
} while (
|
||||||
|
sourceObj &&
|
||||||
|
(!filter || filter(sourceObj, destObj)) &&
|
||||||
|
sourceObj !== Object.prototype
|
||||||
|
);
|
||||||
|
|
||||||
return destObj;
|
return destObj;
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines whether a string ends with the characters of a specified string
|
* Determines whether a string ends with the characters of a specified string
|
||||||
@@ -484,8 +530,7 @@ const endsWith = (str, searchString, position) => {
|
|||||||
position -= searchString.length;
|
position -= searchString.length;
|
||||||
const lastIndex = str.indexOf(searchString, position);
|
const lastIndex = str.indexOf(searchString, position);
|
||||||
return lastIndex !== -1 && lastIndex === position;
|
return lastIndex !== -1 && lastIndex === position;
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns new array from array like object or null if failed
|
* Returns new array from array like object or null if failed
|
||||||
@@ -504,7 +549,7 @@ const toArray = (thing) => {
|
|||||||
arr[i] = thing[i];
|
arr[i] = thing[i];
|
||||||
}
|
}
|
||||||
return arr;
|
return arr;
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checking if the Uint8Array exists and if it does, it returns a function that checks if the
|
* Checking if the Uint8Array exists and if it does, it returns a function that checks if the
|
||||||
@@ -515,12 +560,12 @@ const toArray = (thing) => {
|
|||||||
* @returns {Array}
|
* @returns {Array}
|
||||||
*/
|
*/
|
||||||
// eslint-disable-next-line func-names
|
// eslint-disable-next-line func-names
|
||||||
const isTypedArray = (TypedArray => {
|
const isTypedArray = ((TypedArray) => {
|
||||||
// eslint-disable-next-line func-names
|
// eslint-disable-next-line func-names
|
||||||
return thing => {
|
return (thing) => {
|
||||||
return TypedArray && thing instanceof TypedArray;
|
return TypedArray && thing instanceof TypedArray;
|
||||||
};
|
};
|
||||||
})(typeof Uint8Array !== 'undefined' && getPrototypeOf(Uint8Array));
|
})(typeof Uint8Array !== "undefined" && getPrototypeOf(Uint8Array));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For each entry in the object, call the function with the key and value.
|
* For each entry in the object, call the function with the key and value.
|
||||||
@@ -541,7 +586,7 @@ const forEachEntry = (obj, fn) => {
|
|||||||
const pair = result.value;
|
const pair = result.value;
|
||||||
fn.call(obj, pair[0], pair[1]);
|
fn.call(obj, pair[0], pair[1]);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* It takes a regular expression and a string, and returns an array of all the matches
|
* It takes a regular expression and a string, and returns an array of all the matches
|
||||||
@@ -560,21 +605,25 @@ const matchAll = (regExp, str) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return arr;
|
return arr;
|
||||||
}
|
};
|
||||||
|
|
||||||
/* Checking if the kindOfTest function returns true when passed an HTMLFormElement. */
|
/* Checking if the kindOfTest function returns true when passed an HTMLFormElement. */
|
||||||
const isHTMLForm = kindOfTest('HTMLFormElement');
|
const isHTMLForm = kindOfTest("HTMLFormElement");
|
||||||
|
|
||||||
const toCamelCase = str => {
|
const toCamelCase = (str) => {
|
||||||
return str.toLowerCase().replace(/[-_\s]([a-z\d])(\w*)/g,
|
return str
|
||||||
function replacer(m, p1, p2) {
|
.toLowerCase()
|
||||||
|
.replace(/[-_\s]([a-z\d])(\w*)/g, function replacer(m, p1, p2) {
|
||||||
return p1.toUpperCase() + p2;
|
return p1.toUpperCase() + p2;
|
||||||
}
|
});
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Creating a function that will check if an object has a property. */
|
/* Creating a function that will check if an object has a property. */
|
||||||
const hasOwnProperty = (({hasOwnProperty}) => (obj, prop) => hasOwnProperty.call(obj, prop))(Object.prototype);
|
const hasOwnProperty = (
|
||||||
|
({ hasOwnProperty }) =>
|
||||||
|
(obj, prop) =>
|
||||||
|
hasOwnProperty.call(obj, prop)
|
||||||
|
)(Object.prototype);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine if a value is a RegExp object
|
* Determine if a value is a RegExp object
|
||||||
@@ -583,7 +632,7 @@ const hasOwnProperty = (({hasOwnProperty}) => (obj, prop) => hasOwnProperty.call
|
|||||||
*
|
*
|
||||||
* @returns {boolean} True if value is a RegExp object, otherwise false
|
* @returns {boolean} True if value is a RegExp object, otherwise false
|
||||||
*/
|
*/
|
||||||
const isRegExp = kindOfTest('RegExp');
|
const isRegExp = kindOfTest("RegExp");
|
||||||
|
|
||||||
const reduceDescriptors = (obj, reducer) => {
|
const reduceDescriptors = (obj, reducer) => {
|
||||||
const descriptors = Object.getOwnPropertyDescriptors(obj);
|
const descriptors = Object.getOwnPropertyDescriptors(obj);
|
||||||
@@ -597,7 +646,7 @@ const reduceDescriptors = (obj, reducer) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
Object.defineProperties(obj, reducedDescriptors);
|
Object.defineProperties(obj, reducedDescriptors);
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Makes all methods read-only
|
* Makes all methods read-only
|
||||||
@@ -607,7 +656,10 @@ const reduceDescriptors = (obj, reducer) => {
|
|||||||
const freezeMethods = (obj) => {
|
const freezeMethods = (obj) => {
|
||||||
reduceDescriptors(obj, (descriptor, name) => {
|
reduceDescriptors(obj, (descriptor, name) => {
|
||||||
// skip restricted props in strict mode
|
// skip restricted props in strict mode
|
||||||
if (isFunction(obj) && ['arguments', 'caller', 'callee'].indexOf(name) !== -1) {
|
if (
|
||||||
|
isFunction(obj) &&
|
||||||
|
["arguments", "caller", "callee"].indexOf(name) !== -1
|
||||||
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -617,40 +669,42 @@ const freezeMethods = (obj) => {
|
|||||||
|
|
||||||
descriptor.enumerable = false;
|
descriptor.enumerable = false;
|
||||||
|
|
||||||
if ('writable' in descriptor) {
|
if ("writable" in descriptor) {
|
||||||
descriptor.writable = false;
|
descriptor.writable = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!descriptor.set) {
|
if (!descriptor.set) {
|
||||||
descriptor.set = () => {
|
descriptor.set = () => {
|
||||||
throw Error('Can not rewrite read-only method \'' + name + '\'');
|
throw Error("Can not rewrite read-only method '" + name + "'");
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
const toObjectSet = (arrayOrString, delimiter) => {
|
const toObjectSet = (arrayOrString, delimiter) => {
|
||||||
const obj = {};
|
const obj = {};
|
||||||
|
|
||||||
const define = (arr) => {
|
const define = (arr) => {
|
||||||
arr.forEach(value => {
|
arr.forEach((value) => {
|
||||||
obj[value] = true;
|
obj[value] = true;
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
isArray(arrayOrString) ? define(arrayOrString) : define(String(arrayOrString).split(delimiter));
|
isArray(arrayOrString)
|
||||||
|
? define(arrayOrString)
|
||||||
|
: define(String(arrayOrString).split(delimiter));
|
||||||
|
|
||||||
return obj;
|
return obj;
|
||||||
}
|
};
|
||||||
|
|
||||||
const noop = () => {}
|
const noop = () => {};
|
||||||
|
|
||||||
const toFiniteNumber = (value, defaultValue) => {
|
const toFiniteNumber = (value, defaultValue) => {
|
||||||
return value != null && Number.isFinite(value = +value) ? value : defaultValue;
|
return value != null && Number.isFinite((value = +value))
|
||||||
}
|
? value
|
||||||
|
: defaultValue;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If the thing is a FormData object, return true, otherwise return false.
|
* If the thing is a FormData object, return true, otherwise return false.
|
||||||
@@ -660,14 +714,18 @@ const toFiniteNumber = (value, defaultValue) => {
|
|||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
function isSpecCompliantForm(thing) {
|
function isSpecCompliantForm(thing) {
|
||||||
return !!(thing && isFunction(thing.append) && thing[toStringTag] === 'FormData' && thing[iterator]);
|
return !!(
|
||||||
|
thing &&
|
||||||
|
isFunction(thing.append) &&
|
||||||
|
thing[toStringTag] === "FormData" &&
|
||||||
|
thing[iterator]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const toJSONObject = (obj) => {
|
const toJSONObject = (obj) => {
|
||||||
const stack = new Array(10);
|
const stack = new Array(10);
|
||||||
|
|
||||||
const visit = (source, i) => {
|
const visit = (source, i) => {
|
||||||
|
|
||||||
if (isObject(source)) {
|
if (isObject(source)) {
|
||||||
if (stack.indexOf(source) >= 0) {
|
if (stack.indexOf(source) >= 0) {
|
||||||
return;
|
return;
|
||||||
@@ -678,7 +736,7 @@ const toJSONObject = (obj) => {
|
|||||||
return source;
|
return source;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!('toJSON' in source)) {
|
if (!("toJSON" in source)) {
|
||||||
stack[i] = source;
|
stack[i] = source;
|
||||||
const target = isArray(source) ? [] : {};
|
const target = isArray(source) ? [] : {};
|
||||||
|
|
||||||
@@ -694,15 +752,18 @@ const toJSONObject = (obj) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return source;
|
return source;
|
||||||
}
|
};
|
||||||
|
|
||||||
return visit(obj, 0);
|
return visit(obj, 0);
|
||||||
}
|
};
|
||||||
|
|
||||||
const isAsyncFn = kindOfTest('AsyncFunction');
|
const isAsyncFn = kindOfTest("AsyncFunction");
|
||||||
|
|
||||||
const isThenable = (thing) =>
|
const isThenable = (thing) =>
|
||||||
thing && (isObject(thing) || isFunction(thing)) && isFunction(thing.then) && isFunction(thing.catch);
|
thing &&
|
||||||
|
(isObject(thing) || isFunction(thing)) &&
|
||||||
|
isFunction(thing.then) &&
|
||||||
|
isFunction(thing.catch);
|
||||||
|
|
||||||
// original code
|
// original code
|
||||||
// https://github.com/DigitalBrainJS/AxiosPromise/blob/16deab13710ec09779922131f3fa5954320f83ab/lib/utils.js#L11-L34
|
// https://github.com/DigitalBrainJS/AxiosPromise/blob/16deab13710ec09779922131f3fa5954320f83ab/lib/utils.js#L11-L34
|
||||||
@@ -712,32 +773,35 @@ const _setImmediate = ((setImmediateSupported, postMessageSupported) => {
|
|||||||
return setImmediate;
|
return setImmediate;
|
||||||
}
|
}
|
||||||
|
|
||||||
return postMessageSupported ? ((token, callbacks) => {
|
return postMessageSupported
|
||||||
_global.addEventListener("message", ({source, data}) => {
|
? ((token, callbacks) => {
|
||||||
|
_global.addEventListener(
|
||||||
|
"message",
|
||||||
|
({ source, data }) => {
|
||||||
if (source === _global && data === token) {
|
if (source === _global && data === token) {
|
||||||
callbacks.length && callbacks.shift()();
|
callbacks.length && callbacks.shift()();
|
||||||
}
|
}
|
||||||
}, false);
|
},
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
return (cb) => {
|
return (cb) => {
|
||||||
callbacks.push(cb);
|
callbacks.push(cb);
|
||||||
_global.postMessage(token, "*");
|
_global.postMessage(token, "*");
|
||||||
}
|
};
|
||||||
})(`axios@${Math.random()}`, []) : (cb) => setTimeout(cb);
|
})(`axios@${Math.random()}`, [])
|
||||||
})(
|
: (cb) => setTimeout(cb);
|
||||||
typeof setImmediate === 'function',
|
})(typeof setImmediate === "function", isFunction(_global.postMessage));
|
||||||
isFunction(_global.postMessage)
|
|
||||||
);
|
|
||||||
|
|
||||||
const asap = typeof queueMicrotask !== 'undefined' ?
|
const asap =
|
||||||
queueMicrotask.bind(_global) : ( typeof process !== 'undefined' && process.nextTick || _setImmediate);
|
typeof queueMicrotask !== "undefined"
|
||||||
|
? queueMicrotask.bind(_global)
|
||||||
|
: (typeof process !== "undefined" && process.nextTick) || _setImmediate;
|
||||||
|
|
||||||
// *********************
|
// *********************
|
||||||
|
|
||||||
|
|
||||||
const isIterable = (thing) => thing != null && isFunction(thing[iterator]);
|
const isIterable = (thing) => thing != null && isFunction(thing[iterator]);
|
||||||
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
isArray,
|
isArray,
|
||||||
isArrayBuffer,
|
isArrayBuffer,
|
||||||
@@ -795,5 +859,5 @@ export default {
|
|||||||
isThenable,
|
isThenable,
|
||||||
setImmediate: _setImmediate,
|
setImmediate: _setImmediate,
|
||||||
asap,
|
asap,
|
||||||
isIterable
|
isIterable,
|
||||||
};
|
};
|
||||||
|
|||||||
Vendored
+226
@@ -0,0 +1,226 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
import assert from "assert";
|
||||||
|
import utils from "../../../lib/utils.js";
|
||||||
|
import mergeConfig from "../../../lib/core/mergeConfig.js";
|
||||||
|
|
||||||
|
describe("Prototype Pollution Protection", function () {
|
||||||
|
afterEach(function () {
|
||||||
|
// Clean up any pollution that might have occurred
|
||||||
|
delete Object.prototype.polluted;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("utils.merge", function () {
|
||||||
|
it("should filter __proto__ key at top level", function () {
|
||||||
|
const result = utils.merge(
|
||||||
|
{},
|
||||||
|
{ __proto__: { polluted: "yes" }, safe: "value" },
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.strictEqual(Object.prototype.polluted, undefined);
|
||||||
|
assert.strictEqual(result.safe, "value");
|
||||||
|
assert.strictEqual(result.hasOwnProperty("__proto__"), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should filter constructor key at top level", function () {
|
||||||
|
const result = utils.merge(
|
||||||
|
{},
|
||||||
|
{ constructor: { polluted: "yes" }, safe: "value" },
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.strictEqual(result.safe, "value");
|
||||||
|
assert.strictEqual(result.hasOwnProperty("constructor"), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should filter prototype key at top level", function () {
|
||||||
|
const result = utils.merge(
|
||||||
|
{},
|
||||||
|
{ prototype: { polluted: "yes" }, safe: "value" },
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.strictEqual(result.safe, "value");
|
||||||
|
assert.strictEqual(result.hasOwnProperty("prototype"), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should filter __proto__ key in nested objects", function () {
|
||||||
|
const result = utils.merge(
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
__proto__: { polluted: "nested" },
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.strictEqual(Object.prototype.polluted, undefined);
|
||||||
|
assert.strictEqual(result.headers["Content-Type"], "application/json");
|
||||||
|
assert.strictEqual(result.headers.hasOwnProperty("__proto__"), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should filter constructor key in nested objects", function () {
|
||||||
|
const result = utils.merge(
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
constructor: { prototype: { polluted: "nested" } },
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.strictEqual(Object.prototype.polluted, undefined);
|
||||||
|
assert.strictEqual(result.headers["Content-Type"], "application/json");
|
||||||
|
assert.strictEqual(result.headers.hasOwnProperty("constructor"), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should filter prototype key in nested objects", function () {
|
||||||
|
const result = utils.merge(
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
prototype: { polluted: "nested" },
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.strictEqual(result.headers["Content-Type"], "application/json");
|
||||||
|
assert.strictEqual(result.headers.hasOwnProperty("prototype"), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should filter dangerous keys in deeply nested objects", function () {
|
||||||
|
const result = utils.merge(
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
level1: {
|
||||||
|
level2: {
|
||||||
|
__proto__: { polluted: "deep" },
|
||||||
|
prototype: { polluted: "deep" },
|
||||||
|
safe: "value",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.strictEqual(Object.prototype.polluted, undefined);
|
||||||
|
assert.strictEqual(result.level1.level2.safe, "value");
|
||||||
|
assert.strictEqual(
|
||||||
|
result.level1.level2.hasOwnProperty("__proto__"),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should still merge regular properties correctly", function () {
|
||||||
|
const result = utils.merge({ a: 1, b: { c: 2 } }, { b: { d: 3 }, e: 4 });
|
||||||
|
|
||||||
|
assert.strictEqual(result.a, 1);
|
||||||
|
assert.strictEqual(result.b.c, 2);
|
||||||
|
assert.strictEqual(result.b.d, 3);
|
||||||
|
assert.strictEqual(result.e, 4);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle JSON.parse payloads safely", function () {
|
||||||
|
const malicious = JSON.parse('{"__proto__": {"polluted": "yes"}}');
|
||||||
|
const result = utils.merge({}, malicious);
|
||||||
|
|
||||||
|
assert.strictEqual(Object.prototype.polluted, undefined);
|
||||||
|
assert.strictEqual(result.hasOwnProperty("__proto__"), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle nested JSON.parse payloads safely", function () {
|
||||||
|
const malicious = JSON.parse(
|
||||||
|
'{"headers": {"constructor": {"prototype": {"polluted": "yes"}}}}',
|
||||||
|
);
|
||||||
|
const result = utils.merge({}, malicious);
|
||||||
|
|
||||||
|
assert.strictEqual(Object.prototype.polluted, undefined);
|
||||||
|
assert.strictEqual(result.headers.hasOwnProperty("constructor"), false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("mergeConfig", function () {
|
||||||
|
it("should filter dangerous keys at top level", function () {
|
||||||
|
const result = mergeConfig(
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
__proto__: { polluted: "yes" },
|
||||||
|
constructor: { polluted: "yes" },
|
||||||
|
prototype: { polluted: "yes" },
|
||||||
|
url: "/api/test",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.strictEqual(Object.prototype.polluted, undefined);
|
||||||
|
assert.strictEqual(result.url, "/api/test");
|
||||||
|
assert.strictEqual(result.hasOwnProperty("__proto__"), false);
|
||||||
|
assert.strictEqual(result.hasOwnProperty("constructor"), false);
|
||||||
|
assert.strictEqual(result.hasOwnProperty("prototype"), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should filter dangerous keys in headers", function () {
|
||||||
|
const result = mergeConfig(
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
__proto__: { polluted: "yes" },
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.strictEqual(Object.prototype.polluted, undefined);
|
||||||
|
assert.strictEqual(result.headers["Content-Type"], "application/json");
|
||||||
|
assert.strictEqual(result.headers.hasOwnProperty("__proto__"), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should filter dangerous keys in custom config properties", function () {
|
||||||
|
const result = mergeConfig(
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
customProp: {
|
||||||
|
__proto__: { polluted: "yes" },
|
||||||
|
safe: "value",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.strictEqual(Object.prototype.polluted, undefined);
|
||||||
|
assert.strictEqual(result.customProp.safe, "value");
|
||||||
|
assert.strictEqual(result.customProp.hasOwnProperty("__proto__"), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should still merge configs correctly", function () {
|
||||||
|
const config1 = {
|
||||||
|
baseURL: "https://api.example.com",
|
||||||
|
timeout: 1000,
|
||||||
|
headers: {
|
||||||
|
common: {
|
||||||
|
Accept: "application/json",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const config2 = {
|
||||||
|
url: "/users",
|
||||||
|
timeout: 5000,
|
||||||
|
headers: {
|
||||||
|
common: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = mergeConfig(config1, config2);
|
||||||
|
|
||||||
|
assert.strictEqual(result.baseURL, "https://api.example.com");
|
||||||
|
assert.strictEqual(result.url, "/users");
|
||||||
|
assert.strictEqual(result.timeout, 5000);
|
||||||
|
assert.strictEqual(result.headers.common.Accept, "application/json");
|
||||||
|
assert.strictEqual(
|
||||||
|
result.headers.common["Content-Type"],
|
||||||
|
"application/json",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user