Skip to content

Commit b2e0a9a

Browse files
benjaminPariseljulienmege
authored andcommitted
fix(angular.merge): do not merge __proto__ property
Report from * angular@c0498d4 * angular@add78e6
1 parent 9ff305b commit b2e0a9a

File tree

3 files changed

+138
-14
lines changed

3 files changed

+138
-14
lines changed

src/Angular.js

+58-14
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
extend: true,
3131
int: true,
3232
inherit: true,
33+
merge: true,
3334
noop: true,
3435
identity: true,
3536
valueFn: true,
@@ -327,6 +328,41 @@ function setHashKey(obj, h) {
327328
}
328329
}
329330

331+
332+
function baseExtend(dst, objs, deep) {
333+
var h = dst.$$hashKey;
334+
for (var i = 0, ii = objs.length; i < ii; ++i) {
335+
var obj = objs[i];
336+
if (!isObject(obj) && !isFunction(obj)) continue;
337+
var keys = Object.keys(obj);
338+
for (var j = 0, jj = keys.length; j < jj; j++) {
339+
var key = keys[j];
340+
var src = obj[key];
341+
if (deep && isObject(src)) {
342+
if (isDate(src)) {
343+
dst[key] = new Date(src.valueOf());
344+
} else if (isRegExp(src)) {
345+
dst[key] = new RegExp(src);
346+
} else if (src.nodeName) {
347+
dst[key] = src.cloneNode(true);
348+
} else if (isElement(src)) {
349+
dst[key] = src.clone();
350+
} else {
351+
if (key !== '__proto__') {
352+
if (!isObject(dst[key])) dst[key] = isArray(src) ? [] : {};
353+
baseExtend(dst[key], [src], true);
354+
}
355+
}
356+
} else {
357+
dst[key] = src;
358+
}
359+
}
360+
}
361+
362+
setHashKey(dst, h);
363+
return dst;
364+
}
365+
330366
/**
331367
* @ngdoc function
332368
* @name angular.extend
@@ -344,21 +380,29 @@ function setHashKey(obj, h) {
344380
* @returns {Object} Reference to `dst`.
345381
*/
346382
function extend(dst) {
347-
var h = dst.$$hashKey;
348-
349-
for (var i = 1, ii = arguments.length; i < ii; i++) {
350-
var obj = arguments[i];
351-
if (obj) {
352-
var keys = Object.keys(obj);
353-
for (var j = 0, jj = keys.length; j < jj; j++) {
354-
var key = keys[j];
355-
dst[key] = obj[key];
356-
}
357-
}
358-
}
383+
return baseExtend(dst, slice.call(arguments, 1), false);
384+
}
359385

360-
setHashKey(dst, h);
361-
return dst;
386+
/**
387+
* @ngdoc function
388+
* @name angular.merge
389+
* @module ng
390+
* @kind function
391+
*
392+
* @description
393+
* Deeply extends the destination object `dst` by copying own enumerable properties from the `src` object(s)
394+
* to `dst`. You can specify multiple `src` objects. If you want to preserve original objects, you can do so
395+
* by passing an empty object as the target: `var object = angular.merge({}, object1, object2)`.
396+
*
397+
* Unlike {@link angular.extend extend()}, `merge()` recursively descends into object properties of source
398+
* objects, performing a deep copy.
399+
*
400+
* @param {Object} dst Destination object.
401+
* @param {...Object} src Source object(s).
402+
* @returns {Object} Reference to `dst`.
403+
*/
404+
function merge(dst) {
405+
return baseExtend(dst, slice.call(arguments, 1), true);
362406
}
363407

364408
function int(str) {

src/AngularPublic.js

+1
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ function publishExternalAPI(angular) {
116116
'bootstrap': bootstrap,
117117
'copy': copy,
118118
'extend': extend,
119+
'merge': merge,
119120
'equals': equals,
120121
'element': jqLite,
121122
'forEach': forEach,

test/AngularSpec.js

+79
Original file line numberDiff line numberDiff line change
@@ -1580,3 +1580,82 @@ describe('angular', function() {
15801580
});
15811581
});
15821582
});
1583+
describe('merge', function () {
1584+
it('should recursively copy objects into dst from left to right', function () {
1585+
var dst = {foo: {bar: 'foobar'}};
1586+
var src1 = {foo: {bazz: 'foobazz'}};
1587+
var src2 = {foo: {bozz: 'foobozz'}};
1588+
merge(dst, src1, src2);
1589+
expect(dst).toEqual({
1590+
foo: {
1591+
bar: 'foobar',
1592+
bazz: 'foobazz',
1593+
bozz: 'foobozz'
1594+
}
1595+
});
1596+
});
1597+
1598+
1599+
it('should replace primitives with objects', function () {
1600+
var dst = {foo: "bloop"};
1601+
var src = {foo: {bar: {baz: "bloop"}}};
1602+
merge(dst, src);
1603+
expect(dst).toEqual({
1604+
foo: {
1605+
bar: {
1606+
baz: "bloop"
1607+
}
1608+
}
1609+
});
1610+
});
1611+
1612+
1613+
it('should replace null values in destination with objects', function () {
1614+
var dst = {foo: null};
1615+
var src = {foo: {bar: {baz: "bloop"}}};
1616+
merge(dst, src);
1617+
expect(dst).toEqual({
1618+
foo: {
1619+
bar: {
1620+
baz: "bloop"
1621+
}
1622+
}
1623+
});
1624+
});
1625+
1626+
it('should copy references to functions by value rather than merging', function () {
1627+
function fn() {
1628+
}
1629+
1630+
var dst = {foo: 1};
1631+
var src = {foo: fn};
1632+
merge(dst, src);
1633+
expect(dst).toEqual({
1634+
foo: fn
1635+
});
1636+
});
1637+
1638+
1639+
it('should create a new array if destination property is a non-object and source property is an array', function () {
1640+
var dst = {foo: NaN};
1641+
var src = {foo: [1, 2, 3]};
1642+
merge(dst, src);
1643+
expect(dst).toEqual({
1644+
foo: [1, 2, 3]
1645+
});
1646+
expect(dst.foo).not.toBe(src.foo);
1647+
});
1648+
1649+
it('should not merge the __proto__ property', function() {
1650+
var src = JSON.parse('{ "__proto__": { "xxx": "polluted" } }');
1651+
var dst = {};
1652+
1653+
merge(dst, src);
1654+
1655+
if (typeof dst.__proto__ !== 'undefined') { // eslint-disable-line
1656+
// Should not overwrite the __proto__ property or pollute the Object prototype
1657+
expect(dst.__proto__).toBe(Object.prototype); // eslint-disable-line
1658+
}
1659+
expect(({}).xxx).toBeUndefined();
1660+
});
1661+
});

0 commit comments

Comments
 (0)