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

Commit 4fa214c

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 8ddfa2a commit 4fa214c

File tree

3 files changed

+227
-15
lines changed

3 files changed

+227
-15
lines changed
Lines changed: 12 additions & 0 deletions
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

Lines changed: 21 additions & 3 deletions
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

Lines changed: 194 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2963,39 +2963,221 @@ describe('parser', function() {
29632963
}).toThrow();
29642964
});
29652965

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

0 commit comments

Comments
 (0)