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

Commit d7e31b5

Browse files
committed
fix($parse): block assigning to fields of a constructor prototype
This commit also adds the missing `isecaf` error page and more tests for assignment to constructors. Fixes #14939 Closes #14951
1 parent b014607 commit d7e31b5

File tree

3 files changed

+227
-15
lines changed

3 files changed

+227
-15
lines changed
+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
@ngdoc error
2+
@name $parse:isecaf
3+
@fullName Assigning to Fields of Disallowed Context
4+
@description
5+
6+
Occurs when an expression attempts to assign a value on a field of any of the `Boolean`, `Number`,
7+
`String`, `Array`, `Object`, or `Function` constructors or the corresponding prototypes.
8+
9+
Angular bans the modification of these constructors or their prototypes from within expressions,
10+
since it is a known way to modify the behaviour of existing functions/operations.
11+
12+
To resolve this error, avoid assigning to fields of constructors or their prototypes in expressions.

src/ng/parse.js

+21-3
Original file line numberDiff line numberDiff line change
@@ -113,10 +113,28 @@ function ensureSafeFunction(obj, fullExpression) {
113113

114114
function ensureSafeAssignContext(obj, fullExpression) {
115115
if (obj) {
116-
if (obj === (0).constructor || obj === (false).constructor || obj === ''.constructor ||
117-
obj === {}.constructor || obj === [].constructor || obj === Function.constructor) {
116+
var booleanConstructor = (false).constructor;
117+
var numberConstructor = (0).constructor;
118+
var stringConstructor = ''.constructor;
119+
var objectConstructor = {}.constructor;
120+
var arrayConstructor = [].constructor;
121+
var functionConstructor = Function.constructor;
122+
123+
if (obj === booleanConstructor ||
124+
obj === numberConstructor ||
125+
obj === stringConstructor ||
126+
obj === objectConstructor ||
127+
obj === arrayConstructor ||
128+
obj === functionConstructor ||
129+
obj === booleanConstructor.prototype ||
130+
obj === numberConstructor.prototype ||
131+
obj === stringConstructor.prototype ||
132+
obj === objectConstructor.prototype ||
133+
obj === arrayConstructor.prototype ||
134+
obj === functionConstructor.prototype) {
118135
throw $parseMinErr('isecaf',
119-
'Assigning to a constructor is disallowed! Expression: {0}', fullExpression);
136+
'Assigning to a constructor or its prototype is disallowed! Expression: {0}',
137+
fullExpression);
120138
}
121139
}
122140
}

test/ng/parseSpec.js

+194-12
Original file line numberDiff line numberDiff line change
@@ -2962,39 +2962,221 @@ describe('parser', function() {
29622962
}).toThrow();
29632963
});
29642964

2965-
it('should prevent assigning in the context of a constructor', function() {
2965+
they('should prevent assigning in the context of the $prop constructor', {
2966+
Array: [[], '[]'],
2967+
Boolean: [true, '(true)'],
2968+
Number: [1, '(1)'],
2969+
String: ['string', '"string"']
2970+
}, function(values) {
2971+
var thing = values[0];
2972+
var expr = values[1];
2973+
var constructorExpr = expr + '.constructor';
2974+
2975+
expect(function() {
2976+
scope.$eval(constructorExpr + '.join');
2977+
}).not.toThrow();
29662978
expect(function() {
2967-
scope.$eval("''.constructor.join");
2979+
delete scope.foo;
2980+
scope.$eval('foo = ' + constructorExpr + '.join');
29682981
}).not.toThrow();
29692982
expect(function() {
2970-
scope.$eval("''.constructor.join = ''.constructor.join");
2983+
scope.$eval(constructorExpr + '.join = ""');
2984+
}).toThrowMinErr('$parse', 'isecaf');
2985+
expect(function() {
2986+
scope.$eval(constructorExpr + '[0] = ""');
2987+
}).toThrowMinErr('$parse', 'isecaf');
2988+
expect(function() {
2989+
delete scope.foo;
2990+
scope.$eval('foo = ' + constructorExpr + '; foo.join = ""');
2991+
}).toThrowMinErr('$parse', 'isecaf');
2992+
2993+
expect(function() {
2994+
scope.foo = thing;
2995+
scope.$eval('foo.constructor[0] = ""');
2996+
}).toThrowMinErr('$parse', 'isecaf');
2997+
expect(function() {
2998+
delete scope.foo;
2999+
scope.$eval('foo.constructor[0] = ""', {foo: thing});
3000+
}).toThrowMinErr('$parse', 'isecaf');
3001+
expect(function() {
3002+
scope.foo = thing.constructor;
3003+
scope.$eval('foo[0] = ""');
3004+
}).toThrowMinErr('$parse', 'isecaf');
3005+
expect(function() {
3006+
delete scope.foo;
3007+
scope.$eval('foo[0] = ""', {foo: thing.constructor});
3008+
}).toThrowMinErr('$parse', 'isecaf');
3009+
});
3010+
3011+
they('should prevent assigning in the context of the $prop constructor', {
3012+
// These might throw different error (e.g. isecobj, isecfn),
3013+
// but still having them here for good measure
3014+
Function: [noop, '$eval'],
3015+
Object: [{}, '{}']
3016+
}, function(values) {
3017+
var thing = values[0];
3018+
var expr = values[1];
3019+
var constructorExpr = expr + '.constructor';
3020+
3021+
expect(function() {
3022+
scope.$eval(constructorExpr + '.join');
3023+
}).not.toThrowMinErr('$parse', 'isecaf');
3024+
expect(function() {
3025+
delete scope.foo;
3026+
scope.$eval('foo = ' + constructorExpr + '.join');
3027+
}).not.toThrowMinErr('$parse', 'isecaf');
3028+
expect(function() {
3029+
scope.$eval(constructorExpr + '.join = ""');
29713030
}).toThrow();
29723031
expect(function() {
2973-
scope.$eval("''.constructor[0] = ''");
3032+
scope.$eval(constructorExpr + '[0] = ""');
29743033
}).toThrow();
29753034
expect(function() {
2976-
scope.$eval("(0).constructor[0] = ''");
3035+
delete scope.foo;
3036+
scope.$eval('foo = ' + constructorExpr + '; foo.join = ""');
29773037
}).toThrow();
3038+
29783039
expect(function() {
2979-
scope.$eval("{}.constructor[0] = ''");
3040+
scope.foo = thing;
3041+
scope.$eval('foo.constructor[0] = ""');
29803042
}).toThrow();
2981-
// foo.constructor is the object constructor.
29823043
expect(function() {
2983-
scope.$eval("foo.constructor[0] = ''", {foo: {}});
3044+
delete scope.foo;
3045+
scope.$eval('foo.constructor[0] = ""', {foo: thing});
29843046
}).toThrow();
3047+
expect(function() {
3048+
scope.foo = thing.constructor;
3049+
scope.$eval('foo[0] = ""');
3050+
}).toThrowMinErr('$parse', 'isecaf');
3051+
expect(function() {
3052+
delete scope.foo;
3053+
scope.$eval('foo[0] = ""', {foo: thing.constructor});
3054+
}).toThrowMinErr('$parse', 'isecaf');
3055+
});
3056+
3057+
it('should prevent assigning only in the context of an actual constructor', function() {
29853058
// foo.constructor is not a constructor.
29863059
expect(function() {
2987-
scope.$eval("foo.constructor[0] = ''", {foo: {constructor: ''}});
3060+
delete scope.foo;
3061+
scope.$eval('foo.constructor[0] = ""', {foo: {constructor: ''}});
3062+
}).not.toThrow();
3063+
3064+
expect(function() {
3065+
scope.$eval('"a".constructor.prototype.charAt = [].join');
3066+
}).toThrowMinErr('$parse', 'isecaf');
3067+
expect(function() {
3068+
scope.$eval('"a".constructor.prototype.charCodeAt = [].concat');
3069+
}).toThrowMinErr('$parse', 'isecaf');
3070+
});
3071+
3072+
they('should prevent assigning in the context of the $prop constructor prototype', {
3073+
Array: [[], '[]'],
3074+
Boolean: [true, '(true)'],
3075+
Number: [1, '(1)'],
3076+
String: ['string', '"string"']
3077+
}, function(values) {
3078+
var thing = values[0];
3079+
var expr = values[1];
3080+
var constructorExpr = expr + '.constructor';
3081+
var prototypeExpr = constructorExpr + '.prototype';
3082+
3083+
expect(function() {
3084+
scope.$eval(prototypeExpr + '.boin');
3085+
}).not.toThrow();
3086+
expect(function() {
3087+
delete scope.foo;
3088+
scope.$eval('foo = ' + prototypeExpr + '.boin');
29883089
}).not.toThrow();
29893090
expect(function() {
2990-
scope.$eval("objConstructor = {}.constructor; objConstructor.join = ''");
3091+
scope.$eval(prototypeExpr + '.boin = ""');
3092+
}).toThrowMinErr('$parse', 'isecaf');
3093+
expect(function() {
3094+
scope.$eval(prototypeExpr + '[0] = ""');
3095+
}).toThrowMinErr('$parse', 'isecaf');
3096+
expect(function() {
3097+
delete scope.foo;
3098+
scope.$eval('foo = ' + constructorExpr + '; foo.prototype.boin = ""');
3099+
}).toThrowMinErr('$parse', 'isecaf');
3100+
expect(function() {
3101+
delete scope.foo;
3102+
scope.$eval('foo = ' + prototypeExpr + '; foo.boin = ""');
3103+
}).toThrowMinErr('$parse', 'isecaf');
3104+
3105+
expect(function() {
3106+
scope.foo = thing.constructor;
3107+
scope.$eval('foo.prototype[0] = ""');
3108+
}).toThrowMinErr('$parse', 'isecaf');
3109+
expect(function() {
3110+
delete scope.foo;
3111+
scope.$eval('foo.prototype[0] = ""', {foo: thing.constructor});
3112+
}).toThrowMinErr('$parse', 'isecaf');
3113+
expect(function() {
3114+
scope.foo = thing.constructor.prototype;
3115+
scope.$eval('foo[0] = ""');
3116+
}).toThrowMinErr('$parse', 'isecaf');
3117+
expect(function() {
3118+
delete scope.foo;
3119+
scope.$eval('foo[0] = ""', {foo: thing.constructor.prototype});
3120+
}).toThrowMinErr('$parse', 'isecaf');
3121+
});
3122+
3123+
they('should prevent assigning in the context of a constructor prototype', {
3124+
// These might throw different error (e.g. isecobj, isecfn),
3125+
// but still having them here for good measure
3126+
Function: [noop, '$eval'],
3127+
Object: [{}, '{}']
3128+
}, function(values) {
3129+
var thing = values[0];
3130+
var expr = values[1];
3131+
var constructorExpr = expr + '.constructor';
3132+
var prototypeExpr = constructorExpr + '.prototype';
3133+
3134+
expect(function() {
3135+
scope.$eval(prototypeExpr + '.boin');
3136+
}).not.toThrowMinErr('$parse', 'isecaf');
3137+
expect(function() {
3138+
delete scope.foo;
3139+
scope.$eval('foo = ' + prototypeExpr + '.boin');
3140+
}).not.toThrowMinErr('$parse', 'isecaf');
3141+
expect(function() {
3142+
scope.$eval(prototypeExpr + '.boin = ""');
29913143
}).toThrow();
29923144
expect(function() {
2993-
scope.$eval("'a'.constructor.prototype.charAt=[].join");
3145+
scope.$eval(prototypeExpr + '[0] = ""');
29943146
}).toThrow();
29953147
expect(function() {
2996-
scope.$eval("'a'.constructor.prototype.charCodeAt=[].concat");
3148+
delete scope.foo;
3149+
scope.$eval('foo = ' + constructorExpr + '; foo.prototype.boin = ""');
29973150
}).toThrow();
3151+
expect(function() {
3152+
delete scope.foo;
3153+
scope.$eval('foo = ' + prototypeExpr + '; foo.boin = ""');
3154+
}).toThrow();
3155+
3156+
expect(function() {
3157+
scope.foo = thing.constructor;
3158+
scope.$eval('foo.prototype[0] = ""');
3159+
}).toThrowMinErr('$parse', 'isecaf');
3160+
expect(function() {
3161+
delete scope.foo;
3162+
scope.$eval('foo.prototype[0] = ""', {foo: thing.constructor});
3163+
}).toThrowMinErr('$parse', 'isecaf');
3164+
expect(function() {
3165+
scope.foo = thing.constructor.prototype;
3166+
scope.$eval('foo[0] = ""');
3167+
}).toThrowMinErr('$parse', 'isecaf');
3168+
expect(function() {
3169+
delete scope.foo;
3170+
scope.$eval('foo[0] = ""', {foo: thing.constructor.prototype});
3171+
}).toThrowMinErr('$parse', 'isecaf');
3172+
});
3173+
3174+
it('should prevent assigning only in the context of an actual prototype', function() {
3175+
// foo.constructor.prototype is not a constructor prototype.
3176+
expect(function() {
3177+
delete scope.foo;
3178+
scope.$eval('foo.constructor.prototype[0] = ""', {foo: {constructor: {prototype: ''}}});
3179+
}).not.toThrow();
29983180
});
29993181
});
30003182

0 commit comments

Comments
 (0)