diff --git a/packages/turf-clone/index.ts b/packages/turf-clone/index.ts index 72aa3db1c..bbda31b29 100644 --- a/packages/turf-clone/index.ts +++ b/packages/turf-clone/index.ts @@ -53,7 +53,7 @@ function cloneFeature(geojson: any) { case "geometry": return; default: - cloned[key] = geojson[key]; + assignKey(cloned, key, geojson[key]); } }); // Add properties & geometry last @@ -66,6 +66,32 @@ function cloneFeature(geojson: any) { return cloned; } +/** + * Assign a member to a clone as an own property. + * + * A key named "__proto__" (which JSON.parse produces as a regular own property) + * cannot be copied with a plain assignment: that invokes the + * Object.prototype.__proto__ setter and replaces the prototype of the clone + * with the input value instead of copying the member. + * + * @private + * @param {Object} target object being cloned into + * @param {string} key member name + * @param {*} value member value + */ +function assignKey(target: any, key: string, value: any) { + if (key === "__proto__") { + Object.defineProperty(target, key, { + configurable: true, + enumerable: true, + value, + writable: true, + }); + } else { + target[key] = value; + } +} + /** * Clone Properties * @@ -83,18 +109,22 @@ function cloneProperties(properties: GeoJsonProperties) { if (typeof value === "object") { if (value === null) { // handle null - cloned[key] = null; + assignKey(cloned, key, null); } else if (Array.isArray(value)) { // handle Array - cloned[key] = value.map((item) => { - return item; - }); + assignKey( + cloned, + key, + value.map((item) => { + return item; + }) + ); } else { // handle generic Object - cloned[key] = cloneProperties(value); + assignKey(cloned, key, cloneProperties(value)); } } else { - cloned[key] = value; + assignKey(cloned, key, value); } }); return cloned; @@ -117,7 +147,7 @@ function cloneFeatureCollection(geojson: any) { case "features": return; default: - cloned[key] = geojson[key]; + assignKey(cloned, key, geojson[key]); } }); // Add features diff --git a/packages/turf-clone/test.ts b/packages/turf-clone/test.ts index 4564e794d..8e2fb976c 100644 --- a/packages/turf-clone/test.ts +++ b/packages/turf-clone/test.ts @@ -346,3 +346,30 @@ test("turf-clone -- Feature with property called 'length', issue #1621", (t) => t.end(); }); + +test("turf-clone -- '__proto__' member is copied, not used as prototype", (t) => { + // JSON.parse yields "__proto__" as an ordinary own property + const f = JSON.parse( + '{"type":"Feature","geometry":null,"properties":{"__proto__":{"inherited":true}},"__proto__":{"foreign":true}}' + ); + const fc = JSON.parse( + '{"type":"FeatureCollection","features":[],"__proto__":{"foreign":true}}' + ); + + const cloned = clone(f); + const fcCloned = clone(fc); + + t.deepEqual(cloned, f, "cloned feature should be deeply equal"); + t.deepEqual(fcCloned, fc, "cloned feature collection should be deeply equal"); + t.equal(Object.getPrototypeOf(cloned), Object.prototype); + t.equal(Object.getPrototypeOf(cloned.properties), Object.prototype); + t.equal(Object.getPrototypeOf(fcCloned), Object.prototype); + t.equal(cloned.foreign, undefined, "feature should not inherit members"); + t.equal( + cloned.properties.inherited, + undefined, + "properties should not inherit members" + ); + t.equal(fcCloned.foreign, undefined, "fc should not inherit members"); + t.end(); +});