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
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
@ngdoc error
|
||||
@name $parse:isecaf
|
||||
@fullName Assigning to Fields of Disallowed Context
|
||||
@description
|
||||
|
||||
Occurs when an expression attempts to assign a value on a field of any of the `Boolean`, `Number`,
|
||||
`String`, `Array`, `Object`, or `Function` constructors or the corresponding prototypes.
|
||||
|
||||
Angular bans the modification of these constructors or their prototypes from within expressions,
|
||||
since it is a known way to modify the behaviour of existing functions/operations.
|
||||
|
||||
To resolve this error, avoid assigning to fields of constructors or their prototypes in expressions.
|
||||
+21
-3
@@ -113,10 +113,28 @@ function ensureSafeFunction(obj, fullExpression) {
|
||||
|
||||
function ensureSafeAssignContext(obj, fullExpression) {
|
||||
if (obj) {
|
||||
if (obj === (0).constructor || obj === (false).constructor || obj === ''.constructor ||
|
||||
obj === {}.constructor || obj === [].constructor || obj === Function.constructor) {
|
||||
var booleanConstructor = (false).constructor;
|
||||
var numberConstructor = (0).constructor;
|
||||
var stringConstructor = ''.constructor;
|
||||
var objectConstructor = {}.constructor;
|
||||
var arrayConstructor = [].constructor;
|
||||
var functionConstructor = Function.constructor;
|
||||
|
||||
if (obj === booleanConstructor ||
|
||||
obj === numberConstructor ||
|
||||
obj === stringConstructor ||
|
||||
obj === objectConstructor ||
|
||||
obj === arrayConstructor ||
|
||||
obj === functionConstructor ||
|
||||
obj === booleanConstructor.prototype ||
|
||||
obj === numberConstructor.prototype ||
|
||||
obj === stringConstructor.prototype ||
|
||||
obj === objectConstructor.prototype ||
|
||||
obj === arrayConstructor.prototype ||
|
||||
obj === functionConstructor.prototype) {
|
||||
throw $parseMinErr('isecaf',
|
||||
'Assigning to a constructor is disallowed! Expression: {0}', fullExpression);
|
||||
'Assigning to a constructor or its prototype is disallowed! Expression: {0}',
|
||||
fullExpression);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+195
-13
@@ -2963,39 +2963,221 @@ describe('parser', function() {
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
it('should prevent assigning in the context of a constructor', function() {
|
||||
they('should prevent assigning in the context of the $prop constructor', {
|
||||
Array: [[], '[]'],
|
||||
Boolean: [true, '(true)'],
|
||||
Number: [1, '(1)'],
|
||||
String: ['string', '"string"']
|
||||
}, function(values) {
|
||||
var thing = values[0];
|
||||
var expr = values[1];
|
||||
var constructorExpr = expr + '.constructor';
|
||||
|
||||
expect(function() {
|
||||
scope.$eval("''.constructor.join");
|
||||
scope.$eval(constructorExpr + '.join');
|
||||
}).not.toThrow();
|
||||
expect(function() {
|
||||
scope.$eval("''.constructor.join = ''.constructor.join");
|
||||
delete scope.foo;
|
||||
scope.$eval('foo = ' + constructorExpr + '.join');
|
||||
}).not.toThrow();
|
||||
expect(function() {
|
||||
scope.$eval(constructorExpr + '.join = ""');
|
||||
}).toThrowMinErr('$parse', 'isecaf');
|
||||
expect(function() {
|
||||
scope.$eval(constructorExpr + '[0] = ""');
|
||||
}).toThrowMinErr('$parse', 'isecaf');
|
||||
expect(function() {
|
||||
delete scope.foo;
|
||||
scope.$eval('foo = ' + constructorExpr + '; foo.join = ""');
|
||||
}).toThrowMinErr('$parse', 'isecaf');
|
||||
|
||||
expect(function() {
|
||||
scope.foo = thing;
|
||||
scope.$eval('foo.constructor[0] = ""');
|
||||
}).toThrowMinErr('$parse', 'isecaf');
|
||||
expect(function() {
|
||||
delete scope.foo;
|
||||
scope.$eval('foo.constructor[0] = ""', {foo: thing});
|
||||
}).toThrowMinErr('$parse', 'isecaf');
|
||||
expect(function() {
|
||||
scope.foo = thing.constructor;
|
||||
scope.$eval('foo[0] = ""');
|
||||
}).toThrowMinErr('$parse', 'isecaf');
|
||||
expect(function() {
|
||||
delete scope.foo;
|
||||
scope.$eval('foo[0] = ""', {foo: thing.constructor});
|
||||
}).toThrowMinErr('$parse', 'isecaf');
|
||||
});
|
||||
|
||||
they('should prevent assigning in the context of the $prop constructor', {
|
||||
// These might throw different error (e.g. isecobj, isecfn),
|
||||
// but still having them here for good measure
|
||||
Function: [noop, '$eval'],
|
||||
Object: [{}, '{}']
|
||||
}, function(values) {
|
||||
var thing = values[0];
|
||||
var expr = values[1];
|
||||
var constructorExpr = expr + '.constructor';
|
||||
|
||||
expect(function() {
|
||||
scope.$eval(constructorExpr + '.join');
|
||||
}).not.toThrowMinErr('$parse', 'isecaf');
|
||||
expect(function() {
|
||||
delete scope.foo;
|
||||
scope.$eval('foo = ' + constructorExpr + '.join');
|
||||
}).not.toThrowMinErr('$parse', 'isecaf');
|
||||
expect(function() {
|
||||
scope.$eval(constructorExpr + '.join = ""');
|
||||
}).toThrow();
|
||||
expect(function() {
|
||||
scope.$eval("''.constructor[0] = ''");
|
||||
scope.$eval(constructorExpr + '[0] = ""');
|
||||
}).toThrow();
|
||||
expect(function() {
|
||||
scope.$eval("(0).constructor[0] = ''");
|
||||
delete scope.foo;
|
||||
scope.$eval('foo = ' + constructorExpr + '; foo.join = ""');
|
||||
}).toThrow();
|
||||
|
||||
expect(function() {
|
||||
scope.foo = thing;
|
||||
scope.$eval('foo.constructor[0] = ""');
|
||||
}).toThrow();
|
||||
expect(function() {
|
||||
scope.$eval("{}.constructor[0] = ''");
|
||||
delete scope.foo;
|
||||
scope.$eval('foo.constructor[0] = ""', {foo: thing});
|
||||
}).toThrow();
|
||||
// foo.constructor is the object constructor.
|
||||
expect(function() {
|
||||
scope.$eval("foo.constructor[0] = ''", {foo: {}});
|
||||
}).toThrow();
|
||||
scope.foo = thing.constructor;
|
||||
scope.$eval('foo[0] = ""');
|
||||
}).toThrowMinErr('$parse', 'isecaf');
|
||||
expect(function() {
|
||||
delete scope.foo;
|
||||
scope.$eval('foo[0] = ""', {foo: thing.constructor});
|
||||
}).toThrowMinErr('$parse', 'isecaf');
|
||||
});
|
||||
|
||||
it('should prevent assigning only in the context of an actual constructor', function() {
|
||||
// foo.constructor is not a constructor.
|
||||
expect(function() {
|
||||
scope.$eval("foo.constructor[0] = ''", {foo: {constructor: ''}});
|
||||
delete scope.foo;
|
||||
scope.$eval('foo.constructor[0] = ""', {foo: {constructor: ''}});
|
||||
}).not.toThrow();
|
||||
|
||||
expect(function() {
|
||||
scope.$eval('"a".constructor.prototype.charAt = [].join');
|
||||
}).toThrowMinErr('$parse', 'isecaf');
|
||||
expect(function() {
|
||||
scope.$eval('"a".constructor.prototype.charCodeAt = [].concat');
|
||||
}).toThrowMinErr('$parse', 'isecaf');
|
||||
});
|
||||
|
||||
they('should prevent assigning in the context of the $prop constructor prototype', {
|
||||
Array: [[], '[]'],
|
||||
Boolean: [true, '(true)'],
|
||||
Number: [1, '(1)'],
|
||||
String: ['string', '"string"']
|
||||
}, function(values) {
|
||||
var thing = values[0];
|
||||
var expr = values[1];
|
||||
var constructorExpr = expr + '.constructor';
|
||||
var prototypeExpr = constructorExpr + '.prototype';
|
||||
|
||||
expect(function() {
|
||||
scope.$eval(prototypeExpr + '.boin');
|
||||
}).not.toThrow();
|
||||
expect(function() {
|
||||
scope.$eval("objConstructor = {}.constructor; objConstructor.join = ''");
|
||||
delete scope.foo;
|
||||
scope.$eval('foo = ' + prototypeExpr + '.boin');
|
||||
}).not.toThrow();
|
||||
expect(function() {
|
||||
scope.$eval(prototypeExpr + '.boin = ""');
|
||||
}).toThrowMinErr('$parse', 'isecaf');
|
||||
expect(function() {
|
||||
scope.$eval(prototypeExpr + '[0] = ""');
|
||||
}).toThrowMinErr('$parse', 'isecaf');
|
||||
expect(function() {
|
||||
delete scope.foo;
|
||||
scope.$eval('foo = ' + constructorExpr + '; foo.prototype.boin = ""');
|
||||
}).toThrowMinErr('$parse', 'isecaf');
|
||||
expect(function() {
|
||||
delete scope.foo;
|
||||
scope.$eval('foo = ' + prototypeExpr + '; foo.boin = ""');
|
||||
}).toThrowMinErr('$parse', 'isecaf');
|
||||
|
||||
expect(function() {
|
||||
scope.foo = thing.constructor;
|
||||
scope.$eval('foo.prototype[0] = ""');
|
||||
}).toThrowMinErr('$parse', 'isecaf');
|
||||
expect(function() {
|
||||
delete scope.foo;
|
||||
scope.$eval('foo.prototype[0] = ""', {foo: thing.constructor});
|
||||
}).toThrowMinErr('$parse', 'isecaf');
|
||||
expect(function() {
|
||||
scope.foo = thing.constructor.prototype;
|
||||
scope.$eval('foo[0] = ""');
|
||||
}).toThrowMinErr('$parse', 'isecaf');
|
||||
expect(function() {
|
||||
delete scope.foo;
|
||||
scope.$eval('foo[0] = ""', {foo: thing.constructor.prototype});
|
||||
}).toThrowMinErr('$parse', 'isecaf');
|
||||
});
|
||||
|
||||
they('should prevent assigning in the context of a constructor prototype', {
|
||||
// These might throw different error (e.g. isecobj, isecfn),
|
||||
// but still having them here for good measure
|
||||
Function: [noop, '$eval'],
|
||||
Object: [{}, '{}']
|
||||
}, function(values) {
|
||||
var thing = values[0];
|
||||
var expr = values[1];
|
||||
var constructorExpr = expr + '.constructor';
|
||||
var prototypeExpr = constructorExpr + '.prototype';
|
||||
|
||||
expect(function() {
|
||||
scope.$eval(prototypeExpr + '.boin');
|
||||
}).not.toThrowMinErr('$parse', 'isecaf');
|
||||
expect(function() {
|
||||
delete scope.foo;
|
||||
scope.$eval('foo = ' + prototypeExpr + '.boin');
|
||||
}).not.toThrowMinErr('$parse', 'isecaf');
|
||||
expect(function() {
|
||||
scope.$eval(prototypeExpr + '.boin = ""');
|
||||
}).toThrow();
|
||||
expect(function() {
|
||||
scope.$eval("'a'.constructor.prototype.charAt=[].join");
|
||||
scope.$eval(prototypeExpr + '[0] = ""');
|
||||
}).toThrow();
|
||||
expect(function() {
|
||||
scope.$eval("'a'.constructor.prototype.charCodeAt=[].concat");
|
||||
delete scope.foo;
|
||||
scope.$eval('foo = ' + constructorExpr + '; foo.prototype.boin = ""');
|
||||
}).toThrow();
|
||||
expect(function() {
|
||||
delete scope.foo;
|
||||
scope.$eval('foo = ' + prototypeExpr + '; foo.boin = ""');
|
||||
}).toThrow();
|
||||
|
||||
expect(function() {
|
||||
scope.foo = thing.constructor;
|
||||
scope.$eval('foo.prototype[0] = ""');
|
||||
}).toThrowMinErr('$parse', 'isecaf');
|
||||
expect(function() {
|
||||
delete scope.foo;
|
||||
scope.$eval('foo.prototype[0] = ""', {foo: thing.constructor});
|
||||
}).toThrowMinErr('$parse', 'isecaf');
|
||||
expect(function() {
|
||||
scope.foo = thing.constructor.prototype;
|
||||
scope.$eval('foo[0] = ""');
|
||||
}).toThrowMinErr('$parse', 'isecaf');
|
||||
expect(function() {
|
||||
delete scope.foo;
|
||||
scope.$eval('foo[0] = ""', {foo: thing.constructor.prototype});
|
||||
}).toThrowMinErr('$parse', 'isecaf');
|
||||
});
|
||||
|
||||
it('should prevent assigning only in the context of an actual prototype', function() {
|
||||
// foo.constructor.prototype is not a constructor prototype.
|
||||
expect(function() {
|
||||
delete scope.foo;
|
||||
scope.$eval('foo.constructor.prototype[0] = ""', {foo: {constructor: {prototype: ''}}});
|
||||
}).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user