Skip to content

Commit f6f9cda

Browse files
committed
fix($parse): block assigning to fields of a constructor prototype
Fixes angular#14939
1 parent c207bff commit f6f9cda

File tree

2 files changed

+216
-84
lines changed

2 files changed

+216
-84
lines changed

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 it\'s prototype is disallowed! Expression: {0}',
137+
fullExpression);
120138
}
121139
}
122140
}

test/ng/parseSpec.js

+195-81
Original file line numberDiff line numberDiff line change
@@ -2957,95 +2957,99 @@ describe('parser', function() {
29572957
}).toThrow();
29582958
});
29592959

2960-
it('should prevent assigning in the context of a constructor', function() {
2961-
forEach({
2962-
'(true)': true,
2963-
'(1)': 1,
2964-
'"string"': 'string',
2965-
'[]': []
2966-
}, function(thing, expr) {
2967-
var constructorExpr = expr + '.constructor';
2960+
they('should prevent assigning in the context of the $prop constructor', {
2961+
Array: [[], '[]'],
2962+
Boolean: [true, '(true)'],
2963+
Number: [1, '(1)'],
2964+
String: ['string', '"string"']
2965+
}, function(values) {
2966+
var thing = values[0];
2967+
var expr = values[1];
2968+
var constructorExpr = expr + '.constructor';
29682969

2969-
expect(function() {
2970-
scope.$eval(constructorExpr + '.join');
2971-
}).not.toThrow();
2972-
expect(function() {
2973-
delete scope.foo;
2974-
scope.$eval('foo = ' + constructorExpr + '.join');
2975-
}).not.toThrow();
2976-
expect(function() {
2977-
scope.$eval(constructorExpr + '.join = ""');
2978-
}).toThrowMinErr('$parse', 'isecaf');
2979-
expect(function() {
2980-
scope.$eval(constructorExpr + '[0] = ""');
2981-
}).toThrowMinErr('$parse', 'isecaf');
2982-
expect(function() {
2983-
delete scope.foo;
2984-
scope.$eval('foo = ' + constructorExpr + '; foo.join = ""');
2985-
}).toThrowMinErr('$parse', 'isecaf');
2970+
expect(function() {
2971+
scope.$eval(constructorExpr + '.join');
2972+
}).not.toThrow();
2973+
expect(function() {
2974+
delete scope.foo;
2975+
scope.$eval('foo = ' + constructorExpr + '.join');
2976+
}).not.toThrow();
2977+
expect(function() {
2978+
scope.$eval(constructorExpr + '.join = ""');
2979+
}).toThrowMinErr('$parse', 'isecaf');
2980+
expect(function() {
2981+
scope.$eval(constructorExpr + '[0] = ""');
2982+
}).toThrowMinErr('$parse', 'isecaf');
2983+
expect(function() {
2984+
delete scope.foo;
2985+
scope.$eval('foo = ' + constructorExpr + '; foo.join = ""');
2986+
}).toThrowMinErr('$parse', 'isecaf');
29862987

2987-
expect(function() {
2988-
scope.foo = thing;
2989-
scope.$eval('foo.constructor[0] = ""');
2990-
}).toThrowMinErr('$parse', 'isecaf');
2991-
expect(function() {
2992-
delete scope.foo;
2993-
scope.$eval('foo.constructor[0] = ""', {foo: thing});
2994-
}).toThrowMinErr('$parse', 'isecaf');
2995-
expect(function() {
2996-
scope.foo = thing.constructor;
2997-
scope.$eval('foo[0] = ""');
2998-
}).toThrowMinErr('$parse', 'isecaf');
2999-
expect(function() {
3000-
delete scope.foo;
3001-
scope.$eval('foo[0] = ""', {foo: thing.constructor});
3002-
}).toThrowMinErr('$parse', 'isecaf');
3003-
});
2988+
expect(function() {
2989+
scope.foo = thing;
2990+
scope.$eval('foo.constructor[0] = ""');
2991+
}).toThrowMinErr('$parse', 'isecaf');
2992+
expect(function() {
2993+
delete scope.foo;
2994+
scope.$eval('foo.constructor[0] = ""', {foo: thing});
2995+
}).toThrowMinErr('$parse', 'isecaf');
2996+
expect(function() {
2997+
scope.foo = thing.constructor;
2998+
scope.$eval('foo[0] = ""');
2999+
}).toThrowMinErr('$parse', 'isecaf');
3000+
expect(function() {
3001+
delete scope.foo;
3002+
scope.$eval('foo[0] = ""', {foo: thing.constructor});
3003+
}).toThrowMinErr('$parse', 'isecaf');
3004+
});
30043005

3006+
they('should prevent assigning in the context of the $prop constructor', {
30053007
// These might throw different error (e.g. isecobj, isecfn),
30063008
// but still having them here for good measure
3007-
forEach({
3008-
'{}': {},
3009-
'$eval': scope.$eval
3010-
}, function(thing, expr) {
3011-
var constructorExpr = expr + '.constructor';
3009+
Function: [noop, '$eval'],
3010+
Object: [{}, '{}']
3011+
}, function(values) {
3012+
var thing = values[0];
3013+
var expr = values[1];
3014+
var constructorExpr = expr + '.constructor';
30123015

3013-
expect(function() {
3014-
scope.$eval(constructorExpr + '.join');
3015-
}).not.toThrowMinErr('$parse', 'isecaf');
3016-
expect(function() {
3017-
delete scope.foo;
3018-
scope.$eval('foo = ' + constructorExpr + '.join');
3019-
}).not.toThrowMinErr('$parse', 'isecaf');
3020-
expect(function() {
3021-
scope.$eval(constructorExpr + '.join = ""');
3022-
}).toThrow();
3023-
expect(function() {
3024-
scope.$eval(constructorExpr + '[0] = ""');
3025-
}).toThrow();
3026-
expect(function() {
3027-
delete scope.foo;
3028-
scope.$eval('foo = ' + constructorExpr + '; foo.join = ""');
3029-
}).toThrow();
3016+
expect(function() {
3017+
scope.$eval(constructorExpr + '.join');
3018+
}).not.toThrowMinErr('$parse', 'isecaf');
3019+
expect(function() {
3020+
delete scope.foo;
3021+
scope.$eval('foo = ' + constructorExpr + '.join');
3022+
}).not.toThrowMinErr('$parse', 'isecaf');
3023+
expect(function() {
3024+
scope.$eval(constructorExpr + '.join = ""');
3025+
}).toThrow();
3026+
expect(function() {
3027+
scope.$eval(constructorExpr + '[0] = ""');
3028+
}).toThrow();
3029+
expect(function() {
3030+
delete scope.foo;
3031+
scope.$eval('foo = ' + constructorExpr + '; foo.join = ""');
3032+
}).toThrow();
30303033

3031-
expect(function() {
3032-
scope.foo = thing;
3033-
scope.$eval('foo.constructor[0] = ""');
3034-
}).toThrow();
3035-
expect(function() {
3036-
delete scope.foo;
3037-
scope.$eval('foo.constructor[0] = ""', {foo: thing});
3038-
}).toThrow();
3039-
expect(function() {
3040-
scope.foo = thing.constructor;
3041-
scope.$eval('foo[0] = ""');
3042-
}).toThrowMinErr('$parse', 'isecaf');
3043-
expect(function() {
3044-
delete scope.foo;
3045-
scope.$eval('foo[0] = ""', {foo: thing.constructor});
3046-
}).toThrowMinErr('$parse', 'isecaf');
3047-
});
3034+
expect(function() {
3035+
scope.foo = thing;
3036+
scope.$eval('foo.constructor[0] = ""');
3037+
}).toThrow();
3038+
expect(function() {
3039+
delete scope.foo;
3040+
scope.$eval('foo.constructor[0] = ""', {foo: thing});
3041+
}).toThrow();
3042+
expect(function() {
3043+
scope.foo = thing.constructor;
3044+
scope.$eval('foo[0] = ""');
3045+
}).toThrowMinErr('$parse', 'isecaf');
3046+
expect(function() {
3047+
delete scope.foo;
3048+
scope.$eval('foo[0] = ""', {foo: thing.constructor});
3049+
}).toThrowMinErr('$parse', 'isecaf');
3050+
});
30483051

3052+
it('should prevent assigning only in the context of an actual constructor', function() {
30493053
// foo.constructor is not a constructor.
30503054
expect(function() {
30513055
delete scope.foo;
@@ -3059,6 +3063,116 @@ describe('parser', function() {
30593063
scope.$eval('"a".constructor.prototype.charCodeAt = [].concat');
30603064
}).toThrowMinErr('$parse', 'isecaf');
30613065
});
3066+
3067+
they('should prevent assigning in the context of the $prop constructor prototype', {
3068+
Array: [[], '[]'],
3069+
Boolean: [true, '(true)'],
3070+
Number: [1, '(1)'],
3071+
String: ['string', '"string"']
3072+
}, function(values) {
3073+
var thing = values[0];
3074+
var expr = values[1];
3075+
var constructorExpr = expr + '.constructor';
3076+
var prototypeExpr = constructorExpr + '.prototype';
3077+
3078+
expect(function() {
3079+
scope.$eval(prototypeExpr + '.boin');
3080+
}).not.toThrow();
3081+
expect(function() {
3082+
delete scope.foo;
3083+
scope.$eval('foo = ' + prototypeExpr + '.boin');
3084+
}).not.toThrow();
3085+
expect(function() {
3086+
scope.$eval(prototypeExpr + '.boin = ""');
3087+
}).toThrowMinErr('$parse', 'isecaf');
3088+
expect(function() {
3089+
scope.$eval(prototypeExpr + '[0] = ""');
3090+
}).toThrowMinErr('$parse', 'isecaf');
3091+
expect(function() {
3092+
delete scope.foo;
3093+
scope.$eval('foo = ' + constructorExpr + '; foo.prototype.boin = ""');
3094+
}).toThrowMinErr('$parse', 'isecaf');
3095+
expect(function() {
3096+
delete scope.foo;
3097+
scope.$eval('foo = ' + prototypeExpr + '; foo.boin = ""');
3098+
}).toThrowMinErr('$parse', 'isecaf');
3099+
3100+
expect(function() {
3101+
scope.foo = thing.constructor;
3102+
scope.$eval('foo.prototype[0] = ""');
3103+
}).toThrowMinErr('$parse', 'isecaf');
3104+
expect(function() {
3105+
delete scope.foo;
3106+
scope.$eval('foo.prototype[0] = ""', {foo: thing.constructor});
3107+
}).toThrowMinErr('$parse', 'isecaf');
3108+
expect(function() {
3109+
scope.foo = thing.constructor.prototype;
3110+
scope.$eval('foo[0] = ""');
3111+
}).toThrowMinErr('$parse', 'isecaf');
3112+
expect(function() {
3113+
delete scope.foo;
3114+
scope.$eval('foo[0] = ""', {foo: thing.constructor.prototype});
3115+
}).toThrowMinErr('$parse', 'isecaf');
3116+
});
3117+
3118+
they('should prevent assigning in the context of a constructor prototype', {
3119+
// These might throw different error (e.g. isecobj, isecfn),
3120+
// but still having them here for good measure
3121+
Function: [noop, '$eval'],
3122+
Object: [{}, '{}']
3123+
}, function(values) {
3124+
var thing = values[0];
3125+
var expr = values[1];
3126+
var constructorExpr = expr + '.constructor';
3127+
var prototypeExpr = constructorExpr + '.prototype';
3128+
3129+
expect(function() {
3130+
scope.$eval(prototypeExpr + '.boin');
3131+
}).not.toThrowMinErr('$parse', 'isecaf');
3132+
expect(function() {
3133+
delete scope.foo;
3134+
scope.$eval('foo = ' + prototypeExpr + '.boin');
3135+
}).not.toThrowMinErr('$parse', 'isecaf');
3136+
expect(function() {
3137+
scope.$eval(prototypeExpr + '.boin = ""');
3138+
}).toThrow();
3139+
expect(function() {
3140+
scope.$eval(prototypeExpr + '[0] = ""');
3141+
}).toThrow();
3142+
expect(function() {
3143+
delete scope.foo;
3144+
scope.$eval('foo = ' + constructorExpr + '; foo.prototype.boin = ""');
3145+
}).toThrow();
3146+
expect(function() {
3147+
delete scope.foo;
3148+
scope.$eval('foo = ' + prototypeExpr + '; foo.boin = ""');
3149+
}).toThrow();
3150+
3151+
expect(function() {
3152+
scope.foo = thing.constructor;
3153+
scope.$eval('foo.prototype[0] = ""');
3154+
}).toThrowMinErr('$parse', 'isecaf');
3155+
expect(function() {
3156+
delete scope.foo;
3157+
scope.$eval('foo.prototype[0] = ""', {foo: thing.constructor});
3158+
}).toThrowMinErr('$parse', 'isecaf');
3159+
expect(function() {
3160+
scope.foo = thing.constructor.prototype;
3161+
scope.$eval('foo[0] = ""');
3162+
}).toThrowMinErr('$parse', 'isecaf');
3163+
expect(function() {
3164+
delete scope.foo;
3165+
scope.$eval('foo[0] = ""', {foo: thing.constructor.prototype});
3166+
}).toThrowMinErr('$parse', 'isecaf');
3167+
});
3168+
3169+
it('should prevent assigning only in the context of an actual prototype', function() {
3170+
// foo.constructor.prototype is not a constructor prototype.
3171+
expect(function() {
3172+
delete scope.foo;
3173+
scope.$eval('foo.constructor.prototype[0] = ""', {foo: {constructor: {prototype: ''}}});
3174+
}).not.toThrow();
3175+
});
30623176
});
30633177

30643178
it('should call the function from the received instance and not from a new one', function() {

0 commit comments

Comments
 (0)