Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit 8690081

Browse files
jbedardpetebacondarwin
authored andcommitted
fix($parse): allow use of locals in assignments Fixes #4664
1 parent 26ee32e commit 8690081

File tree

3 files changed

+82
-11
lines changed

3 files changed

+82
-11
lines changed

src/ng/directive/form.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -489,19 +489,19 @@ var formDirectiveFactory = function(isNgForm) {
489489
alias = controller.$name;
490490

491491
if (alias) {
492-
setter(scope, alias, controller, alias);
492+
setter(scope, null, alias, controller, alias);
493493
attr.$observe(attr.name ? 'name' : 'ngForm', function(newValue) {
494494
if (alias === newValue) return;
495-
setter(scope, alias, undefined, alias);
495+
setter(scope, null, alias, undefined, alias);
496496
alias = newValue;
497-
setter(scope, alias, controller, alias);
497+
setter(scope, null, alias, controller, alias);
498498
parentFormCtrl.$$renameControl(controller, alias);
499499
});
500500
}
501501
formElement.on('$destroy', function() {
502502
parentFormCtrl.$removeControl(controller);
503503
if (alias) {
504-
setter(scope, alias, undefined, alias);
504+
setter(scope, null, alias, undefined, alias);
505505
}
506506
extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards
507507
});

src/ng/parse.js

+8-7
Original file line numberDiff line numberDiff line change
@@ -664,7 +664,7 @@ Parser.prototype = {
664664
}, {
665665
assign: function(scope, value, locals) {
666666
var o = object(scope, locals);
667-
if (!o) object.assign(scope, o = {});
667+
if (!o) object.assign(scope, o = {}, locals);
668668
return getter.assign(o, value);
669669
}
670670
});
@@ -690,7 +690,7 @@ Parser.prototype = {
690690
var key = ensureSafeMemberName(indexFn(self, locals), expression);
691691
// prevent overwriting of Function.constructor which would break ensureSafeObject check
692692
var o = ensureSafeObject(obj(self, locals), expression);
693-
if (!o) obj.assign(self, o = {});
693+
if (!o) obj.assign(self, o = {}, locals);
694694
return o[key] = value;
695695
}
696696
});
@@ -800,18 +800,19 @@ Parser.prototype = {
800800
// Parser helper functions
801801
//////////////////////////////////////////////////
802802

803-
function setter(obj, path, setValue, fullExp) {
803+
function setter(obj, locals, path, setValue, fullExp) {
804804
ensureSafeObject(obj, fullExp);
805+
ensureSafeObject(locals, fullExp);
805806

806807
var element = path.split('.'), key;
807808
for (var i = 0; element.length > 1; i++) {
808809
key = ensureSafeMemberName(element.shift(), fullExp);
809-
var propertyObj = ensureSafeObject(obj[key], fullExp);
810+
var propertyObj = (i === 0 && locals && locals[key]) || obj[key];
810811
if (!propertyObj) {
811812
propertyObj = {};
812813
obj[key] = propertyObj;
813814
}
814-
obj = propertyObj;
815+
obj = ensureSafeObject(propertyObj, fullExp);
815816
}
816817
key = ensureSafeMemberName(element.shift(), fullExp);
817818
ensureSafeObject(obj[key], fullExp);
@@ -938,8 +939,8 @@ function getterFn(path, options, fullExp) {
938939
}
939940

940941
fn.sharedGetter = true;
941-
fn.assign = function(self, value) {
942-
return setter(self, path, value, path);
942+
fn.assign = function(self, value, locals) {
943+
return setter(self, locals, path, value, path);
943944
};
944945
getterFnCache[path] = fn;
945946
return fn;

test/ng/parseSpec.js

+70
Original file line numberDiff line numberDiff line change
@@ -488,6 +488,62 @@ describe('parser', function() {
488488
expect(scope.b).toEqual(234);
489489
});
490490

491+
it('should allow use of locals in the left side of an assignment', inject(function($rootScope) {
492+
$rootScope.a = {};
493+
$rootScope.key = "value";
494+
var localA = {};
495+
496+
//getterFn
497+
$rootScope.$eval('a.value = 1', {a: localA});
498+
expect(localA.value).toBe(1);
499+
500+
$rootScope.$eval('w.a.value = 2', {w: {a: localA}});
501+
expect(localA.value).toBe(2);
502+
503+
//field access
504+
$rootScope.$eval('(a).value = 3', {a: localA});
505+
expect(localA.value).toBe(3);
506+
507+
$rootScope.$eval('{c: {b: a}}.c.b.value = 4', {a: localA});
508+
expect(localA.value).toBe(4);
509+
510+
//object index
511+
$rootScope.$eval('a[key] = 5', {a: localA});
512+
expect(localA.value).toBe(5);
513+
514+
$rootScope.$eval('w.a[key] = 6', {w: {a: localA}});
515+
expect(localA.value).toBe(6);
516+
517+
$rootScope.$eval('{c: {b: a}}.c.b[key] = 7', {a: localA});
518+
expect(localA.value).toBe(7);
519+
520+
//Nothing should have touched the $rootScope.a
521+
expect($rootScope.a.value).toBeUndefined();
522+
}));
523+
524+
it('should allow use of locals in sub expressions of the left side of an assignment', inject(function($rootScope, $parse) {
525+
delete $rootScope.x;
526+
$rootScope.$eval('x[a][b] = true', {a: 'foo', b: 'bar'});
527+
expect($rootScope.x.foo.bar).toBe(true);
528+
529+
delete $rootScope.x;
530+
$rootScope.$eval('x.foo[b] = true', {b: 'bar'});
531+
expect($rootScope.x.foo.bar).toBe(true);
532+
533+
delete $rootScope.x;
534+
$rootScope.$eval('x[a].bar = true', {a: 'foo'});
535+
expect($rootScope.x.foo.bar).toBe(true);
536+
}));
537+
538+
it('should ignore locals beyond the root object of an assignment expression', inject(function($rootScope) {
539+
var a = {};
540+
var locals = {a: a};
541+
$rootScope.b = {a: {value: 123}};
542+
$rootScope.$eval('b.a.value = 1', locals);
543+
expect(a.value).toBeUndefined();
544+
expect($rootScope.b.a.value).toBe(1);
545+
}));
546+
491547
it('should evaluate assignments in ternary operator', function() {
492548
scope.$eval('a = 1 ? 2 : 3');
493549
expect(scope.a).toBe(2);
@@ -799,6 +855,12 @@ describe('parser', function() {
799855
}).toThrowMinErr(
800856
'$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
801857
'Expression: a.toString.constructor');
858+
859+
expect(function() {
860+
scope.$eval("c.a = 1", {c: Function.prototype.constructor});
861+
}).toThrowMinErr(
862+
'$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
863+
'Expression: c.a');
802864
});
803865

804866
it('should disallow traversing the Function object in a setter: E02', function() {
@@ -933,6 +995,14 @@ describe('parser', function() {
933995
'$parse', 'isecobj', 'Referencing Object in Angular expressions is disallowed! ' +
934996
'Expression: foo["bar"]["keys"](foo)');
935997
});
998+
999+
it('should NOT allow access to Object constructor in assignment locals', function() {
1000+
expect(function() {
1001+
scope.$eval("O.constructor.a = 1", {O: Object});
1002+
}).toThrowMinErr(
1003+
'$parse', 'isecobj', 'Referencing Object in Angular expressions is disallowed! ' +
1004+
'Expression: O.constructor.a');
1005+
});
9361006
});
9371007

9381008
describe('Window and $element/node', function() {

0 commit comments

Comments
 (0)