Skip to content

Commit

Permalink
fix(angular.merge): do not merge __proto__ property
Browse files Browse the repository at this point in the history
  • Loading branch information
benjaminParisel committed Dec 21, 2022
1 parent e90923a commit 2475f57
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 14 deletions.
72 changes: 58 additions & 14 deletions src/Angular.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
extend: true,
int: true,
inherit: true,
merge: true,
noop: true,
identity: true,
valueFn: true,
Expand Down Expand Up @@ -327,6 +328,41 @@ function setHashKey(obj, h) {
}
}


function baseExtend(dst, objs, deep) {
var h = dst.$$hashKey;
for (var i = 0, ii = objs.length; i < ii; ++i) {
var obj = objs[i];
if (!isObject(obj) && !isFunction(obj)) continue;
var keys = Object.keys(obj);
for (var j = 0, jj = keys.length; j < jj; j++) {
var key = keys[j];
var src = obj[key];
if (deep && isObject(src)) {
if (isDate(src)) {
dst[key] = new Date(src.valueOf());
} else if (isRegExp(src)) {
dst[key] = new RegExp(src);
} else if (src.nodeName) {
dst[key] = src.cloneNode(true);
} else if (isElement(src)) {
dst[key] = src.clone();
} else {
if (key !== '__proto__') {
if (!isObject(dst[key])) dst[key] = isArray(src) ? [] : {};
baseExtend(dst[key], [src], true);
}
}
} else {
dst[key] = src;
}
}
}

setHashKey(dst, h);
return dst;
}

/**
* @ngdoc function
* @name angular.extend
Expand All @@ -344,21 +380,29 @@ function setHashKey(obj, h) {
* @returns {Object} Reference to `dst`.
*/
function extend(dst) {
var h = dst.$$hashKey;

for (var i = 1, ii = arguments.length; i < ii; i++) {
var obj = arguments[i];
if (obj) {
var keys = Object.keys(obj);
for (var j = 0, jj = keys.length; j < jj; j++) {
var key = keys[j];
dst[key] = obj[key];
}
}
}
return baseExtend(dst, slice.call(arguments, 1), false);
}

setHashKey(dst, h);
return dst;
/**
* @ngdoc function
* @name angular.merge
* @module ng
* @kind function
*
* @description
* Deeply extends the destination object `dst` by copying own enumerable properties from the `src` object(s)
* to `dst`. You can specify multiple `src` objects. If you want to preserve original objects, you can do so
* by passing an empty object as the target: `var object = angular.merge({}, object1, object2)`.
*
* Unlike {@link angular.extend extend()}, `merge()` recursively descends into object properties of source
* objects, performing a deep copy.
*
* @param {Object} dst Destination object.
* @param {...Object} src Source object(s).
* @returns {Object} Reference to `dst`.
*/
function merge(dst) {
return baseExtend(dst, slice.call(arguments, 1), true);
}

function int(str) {
Expand Down
1 change: 1 addition & 0 deletions src/AngularPublic.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ function publishExternalAPI(angular) {
'bootstrap': bootstrap,
'copy': copy,
'extend': extend,
'merge': merge,
'equals': equals,
'element': jqLite,
'forEach': forEach,
Expand Down
79 changes: 79 additions & 0 deletions test/AngularSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1580,3 +1580,82 @@ describe('angular', function() {
});
});
});
describe('merge', function () {
it('should recursively copy objects into dst from left to right', function () {
var dst = {foo: {bar: 'foobar'}};
var src1 = {foo: {bazz: 'foobazz'}};
var src2 = {foo: {bozz: 'foobozz'}};
merge(dst, src1, src2);
expect(dst).toEqual({
foo: {
bar: 'foobar',
bazz: 'foobazz',
bozz: 'foobozz'
}
});
});


it('should replace primitives with objects', function () {
var dst = {foo: "bloop"};
var src = {foo: {bar: {baz: "bloop"}}};
merge(dst, src);
expect(dst).toEqual({
foo: {
bar: {
baz: "bloop"
}
}
});
});


it('should replace null values in destination with objects', function () {
var dst = {foo: null};
var src = {foo: {bar: {baz: "bloop"}}};
merge(dst, src);
expect(dst).toEqual({
foo: {
bar: {
baz: "bloop"
}
}
});
});

it('should copy references to functions by value rather than merging', function () {
function fn() {
}

var dst = {foo: 1};
var src = {foo: fn};
merge(dst, src);
expect(dst).toEqual({
foo: fn
});
});


it('should create a new array if destination property is a non-object and source property is an array', function () {
var dst = {foo: NaN};
var src = {foo: [1, 2, 3]};
merge(dst, src);
expect(dst).toEqual({
foo: [1, 2, 3]
});
expect(dst.foo).not.toBe(src.foo);
});

it('should not merge the __proto__ property', function() {
var src = JSON.parse('{ "__proto__": { "xxx": "polluted" } }');
var dst = {};

merge(dst, src);

if (typeof dst.__proto__ !== 'undefined') { // eslint-disable-line
// Should not overwrite the __proto__ property or pollute the Object prototype
expect(dst.__proto__).toBe(Object.prototype); // eslint-disable-line
}
expect(({}).xxx).toBeUndefined();
});
});

0 comments on commit 2475f57

Please sign in to comment.