set-object-value 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/cjs/src/index.js +63 -26
- package/build/es/src/__tests__/IndexTest.mjs +8 -8
- package/build/es/src/index.mjs +60 -25
- package/package.json +14 -9
- package/types/index.d.ts +35 -12
package/build/cjs/src/index.js
CHANGED
|
@@ -1,39 +1,72 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
var _interopRequireDefault = require("reshow-runtime/helpers/interopRequireDefault");
|
|
4
|
+
Object.defineProperty(exports, "__esModule", {
|
|
5
|
+
value: true
|
|
6
|
+
});
|
|
4
7
|
exports.unsafeSet = exports["default"] = void 0;
|
|
8
|
+
var _typeof2 = _interopRequireDefault(require("reshow-runtime/helpers/typeof"));
|
|
5
9
|
// @ts-check
|
|
6
10
|
|
|
7
11
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
12
|
+
* Check whether the given path contains a prototype-polluting key.
|
|
13
|
+
*
|
|
14
|
+
* @param {readonly (string | number)[]} arrPath - The path segments to inspect.
|
|
15
|
+
* @returns {boolean} `true` when the path contains `__proto__`, `constructor`, or `prototype`.
|
|
10
16
|
*/
|
|
17
|
+
var DANGEROUS_KEYS = new Set(["__proto__", "constructor", "prototype"]);
|
|
11
18
|
var isPrototypePolluted = function isPrototypePolluted(arrPath) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
return -1 !== joinPath.indexOf("|" + key + "|");
|
|
19
|
+
return arrPath.some(function (key) {
|
|
20
|
+
return DANGEROUS_KEYS.has(String(key));
|
|
15
21
|
});
|
|
16
22
|
};
|
|
17
23
|
|
|
18
24
|
/**
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
25
|
+
* Set (or replace) a nested value on `obj` following `arrPath`.
|
|
26
|
+
*
|
|
27
|
+
* Intermediate objects are created as needed using `Object.create(null)`
|
|
28
|
+
* (null-prototype objects) to avoid prototype pollution.
|
|
29
|
+
*
|
|
30
|
+
* @template {object} T
|
|
31
|
+
* @param {T} obj - The target object to mutate.
|
|
32
|
+
* @param {readonly (string | number)[]} arrPath - Path segments leading to the value to set.
|
|
33
|
+
* @param {any} val - The value to assign (or append when `isAppend` is true).
|
|
34
|
+
* @param {boolean} [isAppend=false] - When true, append `val` to an existing array
|
|
35
|
+
* (or wrap the existing value into an array) instead of overwriting.
|
|
36
|
+
* @param {boolean} [unSafe=false] - When true, skip the prototype-pollution guard.
|
|
37
|
+
* @returns {void}
|
|
38
|
+
* @example
|
|
39
|
+
* const obj = {};
|
|
40
|
+
* replaceValue(obj, ["a", "b"], "c");
|
|
41
|
+
* // obj => { a: { b: "c" } }
|
|
42
|
+
* @throws {Error} Throws when the path contains a prototype-polluting key and
|
|
43
|
+
* `unSafe` is not enabled, or when `arrPath` is empty.
|
|
44
|
+
* @throws {TypeError} Throws when `obj` is not an object, `arrPath` is not an
|
|
45
|
+
* array, or an intermediate path segment is not an object.
|
|
24
46
|
*/
|
|
25
47
|
var replaceValue = function replaceValue(obj, arrPath, val, isAppend, unSafe) {
|
|
48
|
+
if (obj == null || (0, _typeof2["default"])(obj) !== "object") {
|
|
49
|
+
throw new TypeError("Target must be an object.");
|
|
50
|
+
}
|
|
51
|
+
if (!Array.isArray(arrPath)) {
|
|
52
|
+
throw new TypeError("Path must be an array.");
|
|
53
|
+
}
|
|
54
|
+
if (arrPath.length === 0) {
|
|
55
|
+
throw new Error("Path must not be empty.");
|
|
56
|
+
}
|
|
26
57
|
if (!unSafe && isPrototypePolluted(arrPath)) {
|
|
27
|
-
throw "Contain un-safe key.";
|
|
58
|
+
throw new Error("Contain un-safe key.");
|
|
28
59
|
}
|
|
29
|
-
/**
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
var last = /** @type string */arrPath.pop();
|
|
60
|
+
var last = /** @type {string | number} */arrPath[arrPath.length - 1];
|
|
61
|
+
var parents = arrPath.slice(0, -1);
|
|
62
|
+
/** @type {any} */
|
|
33
63
|
var linkObj = obj;
|
|
34
|
-
|
|
64
|
+
parents.forEach(function (k) {
|
|
35
65
|
var _linkObj$k;
|
|
36
|
-
|
|
66
|
+
if (linkObj[k] != null && (0, _typeof2["default"])(linkObj[k]) !== "object") {
|
|
67
|
+
throw new TypeError("Cannot set property along path: \"" + k + "\" is not an object.");
|
|
68
|
+
}
|
|
69
|
+
linkObj[k] = (_linkObj$k = linkObj[k]) != null ? _linkObj$k : Object.create(null);
|
|
37
70
|
linkObj = linkObj[k];
|
|
38
71
|
});
|
|
39
72
|
if (isAppend && (!linkObj[last] || !linkObj[last].push)) {
|
|
@@ -50,14 +83,18 @@ var replaceValue = function replaceValue(obj, arrPath, val, isAppend, unSafe) {
|
|
|
50
83
|
};
|
|
51
84
|
|
|
52
85
|
/**
|
|
53
|
-
* @
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
86
|
+
* Unsafe variant of {@link replaceValue} that bypasses the prototype-pollution guard.
|
|
87
|
+
*
|
|
88
|
+
* Use only when the path is trusted or the target is a null-prototype object.
|
|
89
|
+
*
|
|
90
|
+
* @template {object} T
|
|
91
|
+
* @param {T} obj - The target object to mutate.
|
|
92
|
+
* @param {readonly (string | number)[]} arrPath - Path segments leading to the value to set.
|
|
93
|
+
* @param {any} val - The value to assign (or append when `isAppend` is true).
|
|
94
|
+
* @param {boolean} [isAppend=false] - When true, append `val` to an existing array.
|
|
95
|
+
* @returns {void}
|
|
57
96
|
*/
|
|
58
|
-
var unsafeSet = function unsafeSet(obj, arrPath, val, isAppend) {
|
|
97
|
+
var unsafeSet = exports.unsafeSet = function unsafeSet(obj, arrPath, val, isAppend) {
|
|
59
98
|
return replaceValue(obj, arrPath, val, isAppend, true);
|
|
60
99
|
};
|
|
61
|
-
exports
|
|
62
|
-
var _default = replaceValue;
|
|
63
|
-
exports["default"] = _default;
|
|
100
|
+
var _default = exports["default"] = replaceValue;
|
|
@@ -2,7 +2,7 @@ import { expect } from "chai";
|
|
|
2
2
|
import set, { unsafeSet } from "../index.mjs";
|
|
3
3
|
describe("Test set", function () {
|
|
4
4
|
it("check set result", function () {
|
|
5
|
-
|
|
5
|
+
const obj = {};
|
|
6
6
|
set(obj, ["a", "b"], "c");
|
|
7
7
|
expect(obj).to.deep.equal({
|
|
8
8
|
a: {
|
|
@@ -13,7 +13,7 @@ describe("Test set", function () {
|
|
|
13
13
|
});
|
|
14
14
|
describe("CVE-2020-28281", function () {
|
|
15
15
|
it("should not overwrite prototype", function () {
|
|
16
|
-
|
|
16
|
+
const obj = {};
|
|
17
17
|
expect(obj.isAdmin).to.be.undefined;
|
|
18
18
|
expect(function () {
|
|
19
19
|
return set(obj, ["__proto__", "isAdmin"], true);
|
|
@@ -30,23 +30,23 @@ describe("CVE-2020-28281", function () {
|
|
|
30
30
|
expect(obj.isAdmin).to.be.true;
|
|
31
31
|
});
|
|
32
32
|
it("should force overwrite prototype with unsafe mode", function () {
|
|
33
|
-
|
|
33
|
+
const obj = {};
|
|
34
34
|
unsafeSet(obj, ["__proto__", "fake"], true);
|
|
35
35
|
expect(obj.fake).to.be.true;
|
|
36
36
|
});
|
|
37
37
|
it("safe way with unsafe set", function () {
|
|
38
|
-
|
|
38
|
+
const obj = Object.create(null);
|
|
39
39
|
unsafeSet(obj, ["__proto__", "fake"], true);
|
|
40
40
|
expect(obj.fake).to.be.undefined;
|
|
41
41
|
expect(obj.__proto__.fake).to.be.true;
|
|
42
|
-
|
|
42
|
+
const obj2 = Object.create(null);
|
|
43
43
|
expect({}.fake).to.be.true; // bad case
|
|
44
44
|
expect(obj2.fake).to.be.undefined;
|
|
45
45
|
});
|
|
46
46
|
});
|
|
47
47
|
describe("Test append", function () {
|
|
48
48
|
it("test simple append", function () {
|
|
49
|
-
|
|
49
|
+
const obj = {};
|
|
50
50
|
set(obj, ["a"], "a1", true);
|
|
51
51
|
set(obj, ["a"], "a2", true);
|
|
52
52
|
expect(obj).to.deep.equal({
|
|
@@ -54,7 +54,7 @@ describe("Test append", function () {
|
|
|
54
54
|
});
|
|
55
55
|
});
|
|
56
56
|
it("should keep origin value", function () {
|
|
57
|
-
|
|
57
|
+
const obj = {};
|
|
58
58
|
set(obj, ["a"], "a3");
|
|
59
59
|
set(obj, ["a"], "a4", true);
|
|
60
60
|
expect(obj).to.deep.equal({
|
|
@@ -62,7 +62,7 @@ describe("Test append", function () {
|
|
|
62
62
|
});
|
|
63
63
|
});
|
|
64
64
|
it("last is not array", function () {
|
|
65
|
-
|
|
65
|
+
const obj = {
|
|
66
66
|
a: {
|
|
67
67
|
b: "foo"
|
|
68
68
|
}
|
package/build/es/src/index.mjs
CHANGED
|
@@ -1,35 +1,64 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Check whether the given path contains a prototype-polluting key.
|
|
5
|
+
*
|
|
6
|
+
* @param {readonly (string | number)[]} arrPath - The path segments to inspect.
|
|
7
|
+
* @returns {boolean} `true` when the path contains `__proto__`, `constructor`, or `prototype`.
|
|
6
8
|
*/
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
return
|
|
10
|
-
return
|
|
9
|
+
const DANGEROUS_KEYS = new Set(["__proto__", "constructor", "prototype"]);
|
|
10
|
+
const isPrototypePolluted = function isPrototypePolluted(arrPath) {
|
|
11
|
+
return arrPath.some(function (key) {
|
|
12
|
+
return DANGEROUS_KEYS.has(String(key));
|
|
11
13
|
});
|
|
12
14
|
};
|
|
13
15
|
|
|
14
16
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
17
|
+
* Set (or replace) a nested value on `obj` following `arrPath`.
|
|
18
|
+
*
|
|
19
|
+
* Intermediate objects are created as needed using `Object.create(null)`
|
|
20
|
+
* (null-prototype objects) to avoid prototype pollution.
|
|
21
|
+
*
|
|
22
|
+
* @template {object} T
|
|
23
|
+
* @param {T} obj - The target object to mutate.
|
|
24
|
+
* @param {readonly (string | number)[]} arrPath - Path segments leading to the value to set.
|
|
25
|
+
* @param {any} val - The value to assign (or append when `isAppend` is true).
|
|
26
|
+
* @param {boolean} [isAppend=false] - When true, append `val` to an existing array
|
|
27
|
+
* (or wrap the existing value into an array) instead of overwriting.
|
|
28
|
+
* @param {boolean} [unSafe=false] - When true, skip the prototype-pollution guard.
|
|
29
|
+
* @returns {void}
|
|
30
|
+
* @example
|
|
31
|
+
* const obj = {};
|
|
32
|
+
* replaceValue(obj, ["a", "b"], "c");
|
|
33
|
+
* // obj => { a: { b: "c" } }
|
|
34
|
+
* @throws {Error} Throws when the path contains a prototype-polluting key and
|
|
35
|
+
* `unSafe` is not enabled, or when `arrPath` is empty.
|
|
36
|
+
* @throws {TypeError} Throws when `obj` is not an object, `arrPath` is not an
|
|
37
|
+
* array, or an intermediate path segment is not an object.
|
|
20
38
|
*/
|
|
21
|
-
|
|
39
|
+
const replaceValue = function replaceValue(obj, arrPath, val, isAppend, unSafe) {
|
|
40
|
+
if (obj == null || typeof obj !== "object") {
|
|
41
|
+
throw new TypeError("Target must be an object.");
|
|
42
|
+
}
|
|
43
|
+
if (!Array.isArray(arrPath)) {
|
|
44
|
+
throw new TypeError("Path must be an array.");
|
|
45
|
+
}
|
|
46
|
+
if (arrPath.length === 0) {
|
|
47
|
+
throw new Error("Path must not be empty.");
|
|
48
|
+
}
|
|
22
49
|
if (!unSafe && isPrototypePolluted(arrPath)) {
|
|
23
|
-
throw "Contain un-safe key.";
|
|
50
|
+
throw new Error("Contain un-safe key.");
|
|
24
51
|
}
|
|
25
|
-
/**
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
arrPath.forEach(function (k) {
|
|
52
|
+
const last = /** @type {string | number} */arrPath[arrPath.length - 1];
|
|
53
|
+
const parents = arrPath.slice(0, -1);
|
|
54
|
+
/** @type {any} */
|
|
55
|
+
let linkObj = obj;
|
|
56
|
+
parents.forEach(function (k) {
|
|
31
57
|
var _linkObj$k;
|
|
32
|
-
|
|
58
|
+
if (linkObj[k] != null && typeof linkObj[k] !== "object") {
|
|
59
|
+
throw new TypeError(`Cannot set property along path: "${k}" is not an object.`);
|
|
60
|
+
}
|
|
61
|
+
linkObj[k] = (_linkObj$k = linkObj[k]) != null ? _linkObj$k : Object.create(null);
|
|
33
62
|
linkObj = linkObj[k];
|
|
34
63
|
});
|
|
35
64
|
if (isAppend && (!linkObj[last] || !linkObj[last].push)) {
|
|
@@ -46,12 +75,18 @@ var replaceValue = function replaceValue(obj, arrPath, val, isAppend, unSafe) {
|
|
|
46
75
|
};
|
|
47
76
|
|
|
48
77
|
/**
|
|
49
|
-
* @
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
78
|
+
* Unsafe variant of {@link replaceValue} that bypasses the prototype-pollution guard.
|
|
79
|
+
*
|
|
80
|
+
* Use only when the path is trusted or the target is a null-prototype object.
|
|
81
|
+
*
|
|
82
|
+
* @template {object} T
|
|
83
|
+
* @param {T} obj - The target object to mutate.
|
|
84
|
+
* @param {readonly (string | number)[]} arrPath - Path segments leading to the value to set.
|
|
85
|
+
* @param {any} val - The value to assign (or append when `isAppend` is true).
|
|
86
|
+
* @param {boolean} [isAppend=false] - When true, append `val` to an existing array.
|
|
87
|
+
* @returns {void}
|
|
53
88
|
*/
|
|
54
|
-
|
|
89
|
+
const unsafeSet = function unsafeSet(obj, arrPath, val, isAppend) {
|
|
55
90
|
return replaceValue(obj, arrPath, val, isAppend, true);
|
|
56
91
|
};
|
|
57
92
|
export default replaceValue;
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.
|
|
2
|
+
"version": "0.3.0",
|
|
3
3
|
"name": "set-object-value",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
@@ -8,26 +8,32 @@
|
|
|
8
8
|
},
|
|
9
9
|
"homepage": "https://github.com/react-atomic/react-atomic-organism/tree/main/packages/lib/set-object-value",
|
|
10
10
|
"description": "set object nested value",
|
|
11
|
-
"main": "./build/cjs/src/index.js",
|
|
12
|
-
"module": "./build/es/src/index.mjs",
|
|
13
11
|
"exports": {
|
|
14
|
-
"
|
|
15
|
-
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./types/index.d.ts",
|
|
14
|
+
"require": "./build/cjs/src/index.js",
|
|
15
|
+
"import": "./build/es/src/index.mjs"
|
|
16
|
+
}
|
|
16
17
|
},
|
|
18
|
+
"types": "./types/index.d.ts",
|
|
19
|
+
"main": "./build/cjs/src/index.js",
|
|
20
|
+
"module": "./build/es/src/index.mjs",
|
|
17
21
|
"keywords": [],
|
|
18
22
|
"author": "Hill <hill@kimo.com>",
|
|
19
23
|
"license": "MIT",
|
|
20
24
|
"dependencies": {},
|
|
21
25
|
"devDependencies": {
|
|
22
|
-
"@babel/cli": "^
|
|
26
|
+
"@babel/cli": "^8.1.0",
|
|
27
|
+
"@babel/core": "^8.1.2",
|
|
23
28
|
"chai": "*",
|
|
24
|
-
"mocha": "*"
|
|
29
|
+
"mocha": "*",
|
|
30
|
+
"typescript": "^7.0.2"
|
|
25
31
|
},
|
|
26
32
|
"scripts": {
|
|
27
33
|
"clean": "find ./build ./types -name '*.*' | xargs rm -rf",
|
|
28
34
|
"build:cjs": "BABEL_ENV=cjs babel src -d build/cjs/src --ignore /**/__tests__ --root-mode upward",
|
|
29
35
|
"build:es": "BABEL_ENV=es babel src -d build/es/src --out-file-extension .mjs --root-mode upward",
|
|
30
|
-
"build:type": "
|
|
36
|
+
"build:type": "tsc src/*.js --declaration --allowJs --emitDeclarationOnly --skipLibCheck --declarationDir types --resolvePackageJsonExports false --strict false",
|
|
31
37
|
"build": "npm run clean && npm run build:cjs && npm run build:es && npm run build:type",
|
|
32
38
|
"mochaFor": "mocha",
|
|
33
39
|
"mocha": "npm run mochaFor -- 'build/es/**/__tests__/*.mjs'",
|
|
@@ -35,7 +41,6 @@
|
|
|
35
41
|
"test": "npm run build && npm run mocha",
|
|
36
42
|
"prepublishOnly": "npm run test"
|
|
37
43
|
},
|
|
38
|
-
"types": "./types/index.d.ts",
|
|
39
44
|
"files": [
|
|
40
45
|
"types",
|
|
41
46
|
"build",
|
package/types/index.d.ts
CHANGED
|
@@ -1,16 +1,39 @@
|
|
|
1
|
-
export default replaceValue;
|
|
2
1
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
2
|
+
* Set (or replace) a nested value on `obj` following `arrPath`.
|
|
3
|
+
*
|
|
4
|
+
* Intermediate objects are created as needed using `Object.create(null)`
|
|
5
|
+
* (null-prototype objects) to avoid prototype pollution.
|
|
6
|
+
*
|
|
7
|
+
* @template {object} T
|
|
8
|
+
* @param {T} obj - The target object to mutate.
|
|
9
|
+
* @param {readonly (string | number)[]} arrPath - Path segments leading to the value to set.
|
|
10
|
+
* @param {any} val - The value to assign (or append when `isAppend` is true).
|
|
11
|
+
* @param {boolean} [isAppend=false] - When true, append `val` to an existing array
|
|
12
|
+
* (or wrap the existing value into an array) instead of overwriting.
|
|
13
|
+
* @param {boolean} [unSafe=false] - When true, skip the prototype-pollution guard.
|
|
14
|
+
* @returns {void}
|
|
15
|
+
* @example
|
|
16
|
+
* const obj = {};
|
|
17
|
+
* replaceValue(obj, ["a", "b"], "c");
|
|
18
|
+
* // obj => { a: { b: "c" } }
|
|
19
|
+
* @throws {Error} Throws when the path contains a prototype-polluting key and
|
|
20
|
+
* `unSafe` is not enabled, or when `arrPath` is empty.
|
|
21
|
+
* @throws {TypeError} Throws when `obj` is not an object, `arrPath` is not an
|
|
22
|
+
* array, or an intermediate path segment is not an object.
|
|
8
23
|
*/
|
|
9
|
-
declare
|
|
24
|
+
declare const replaceValue: <T extends object>(obj: T, arrPath: readonly (string | number)[], val: any, isAppend?: boolean, unSafe?: boolean) => void;
|
|
10
25
|
/**
|
|
11
|
-
* @
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
26
|
+
* Unsafe variant of {@link replaceValue} that bypasses the prototype-pollution guard.
|
|
27
|
+
*
|
|
28
|
+
* Use only when the path is trusted or the target is a null-prototype object.
|
|
29
|
+
*
|
|
30
|
+
* @template {object} T
|
|
31
|
+
* @param {T} obj - The target object to mutate.
|
|
32
|
+
* @param {readonly (string | number)[]} arrPath - Path segments leading to the value to set.
|
|
33
|
+
* @param {any} val - The value to assign (or append when `isAppend` is true).
|
|
34
|
+
* @param {boolean} [isAppend=false] - When true, append `val` to an existing array.
|
|
35
|
+
* @returns {void}
|
|
15
36
|
*/
|
|
16
|
-
|
|
37
|
+
declare const unsafeSet: <T extends object>(obj: T, arrPath: readonly (string | number)[], val: any, isAppend?: boolean) => void;
|
|
38
|
+
export default replaceValue;
|
|
39
|
+
export { unsafeSet };
|