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.
@@ -1,39 +1,72 @@
1
1
  "use strict";
2
2
 
3
- exports.__esModule = true;
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
- * @param {string[]} arrPath
9
- * @returns {boolean}
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
- var joinPath = "|" + arrPath.join("|") + "|";
13
- return ["__proto__", "constructor", "prototype"].some(function (key) {
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
- * @param {any} obj
20
- * @param {string[]} arrPath
21
- * @param {any} val
22
- * @param {boolean} [isAppend]
23
- * @param {boolean} [unSafe]
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
- * @type string
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
- arrPath.forEach(function (k) {
64
+ parents.forEach(function (k) {
35
65
  var _linkObj$k;
36
- linkObj[k] = (_linkObj$k = linkObj[k]) !== null && _linkObj$k !== void 0 ? _linkObj$k : Object.create(null);
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
- * @param {any} obj
54
- * @param {string[]} arrPath
55
- * @param {any} val
56
- * @param {boolean} [isAppend]
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.unsafeSet = unsafeSet;
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
- var obj = {};
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
- var obj = {};
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
- var obj = {};
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
- var obj = Object.create(null);
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
- var obj2 = Object.create(null);
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
- var obj = {};
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
- var obj = {};
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
- var obj = {
65
+ const obj = {
66
66
  a: {
67
67
  b: "foo"
68
68
  }
@@ -1,35 +1,64 @@
1
1
  // @ts-check
2
2
 
3
3
  /**
4
- * @param {string[]} arrPath
5
- * @returns {boolean}
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
- var isPrototypePolluted = function isPrototypePolluted(arrPath) {
8
- var joinPath = "|" + arrPath.join("|") + "|";
9
- return ["__proto__", "constructor", "prototype"].some(function (key) {
10
- return -1 !== joinPath.indexOf("|" + key + "|");
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
- * @param {any} obj
16
- * @param {string[]} arrPath
17
- * @param {any} val
18
- * @param {boolean} [isAppend]
19
- * @param {boolean} [unSafe]
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
- var replaceValue = function replaceValue(obj, arrPath, val, isAppend, unSafe) {
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
- * @type string
27
- */
28
- var last = /** @type string */arrPath.pop();
29
- var linkObj = obj;
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
- linkObj[k] = (_linkObj$k = linkObj[k]) !== null && _linkObj$k !== void 0 ? _linkObj$k : Object.create(null);
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
- * @param {any} obj
50
- * @param {string[]} arrPath
51
- * @param {any} val
52
- * @param {boolean} [isAppend]
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
- var unsafeSet = function unsafeSet(obj, arrPath, val, isAppend) {
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.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
- "require": "./build/cjs/src/index.js",
15
- "import": "./build/es/src/index.mjs"
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": "^7.x",
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": "npx -p typescript tsc src/*.js --declaration --allowJs --emitDeclarationOnly --skipLibCheck --declarationDir types",
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
- * @param {any} obj
4
- * @param {string[]} arrPath
5
- * @param {any} val
6
- * @param {boolean} [isAppend]
7
- * @param {boolean} [unSafe]
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 function replaceValue(obj: any, arrPath: string[], val: any, isAppend?: boolean, unSafe?: boolean): void;
24
+ declare const replaceValue: <T extends object>(obj: T, arrPath: readonly (string | number)[], val: any, isAppend?: boolean, unSafe?: boolean) => void;
10
25
  /**
11
- * @param {any} obj
12
- * @param {string[]} arrPath
13
- * @param {any} val
14
- * @param {boolean} [isAppend]
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
- export function unsafeSet(obj: any, arrPath: string[], val: any, isAppend?: boolean): void;
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 };