refactor($parse): remove Angular expression sandbox
The angular expression parser (`$parse`) attempts to sandbox expressions to prevent unrestricted access to the global context. While the sandbox was not on the frontline of the security defense, developers kept relying upon it as a security feature even though it was always possible to access arbitrary JavaScript code if a malicious user could control the content of Angular templates in applications. This commit removes this sandbox, which has the following benefits: * it sends a clear message to developers that they should not rely on the sandbox to prevent XSS attacks; that they must prevent control of expression and templates instead. * it allows performance and size improvements in the core Angular 1 library. * it simplifies maintenance and provides opportunities to make the parser more capable. Please see the [Sandbox Removal Blog Post](http://angularjs.blogspot.com/2016/09/angular-16-expression-sandbox-removal.html) for more detail on what you should do to ensure that your application is secure. Closes #15094
This commit is contained in:
@@ -1,12 +0,0 @@
|
||||
@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.
|
||||
@@ -1,47 +0,0 @@
|
||||
@ngdoc error
|
||||
@name $parse:isecdom
|
||||
@fullName Referencing a DOM node in Expression
|
||||
@description
|
||||
|
||||
Occurs when an expression attempts to access a DOM node.
|
||||
|
||||
AngularJS restricts access to DOM nodes from within expressions since it's a known way to
|
||||
execute arbitrary Javascript code.
|
||||
|
||||
This check is only performed on object index and function calls in Angular expressions. These are
|
||||
places that are harder for the developer to guard. Dotted member access (such as a.b.c) does not
|
||||
perform this check - it's up to the developer to not expose such sensitive and powerful objects
|
||||
directly on the scope chain.
|
||||
|
||||
To resolve this error, avoid access to DOM nodes.
|
||||
|
||||
|
||||
# Event Handlers and Return Values
|
||||
|
||||
The `$parse:isecdom` error also occurs when an event handler invokes a function that returns a DOM
|
||||
node.
|
||||
|
||||
```html
|
||||
<button ng-click="iWillReturnDOM()">click me</button>
|
||||
```
|
||||
|
||||
```js
|
||||
$scope.iWillReturnDOM = function() {
|
||||
return someDomNode;
|
||||
}
|
||||
```
|
||||
|
||||
To fix this issue, avoid returning DOM nodes from event handlers.
|
||||
|
||||
*Note: This error often means that you are accessing DOM from your controllers, which is usually
|
||||
a sign of poor coding style that violates separation of concerns.*
|
||||
|
||||
|
||||
# Implicit Returns in CoffeeScript
|
||||
|
||||
This error can occur more frequently when using CoffeeScript, which has a feature called implicit
|
||||
returns. This language feature returns the last dereferenced object in the function when the
|
||||
function has no explicit return statement.
|
||||
|
||||
The solution in this scenario is to add an explicit return statement. For example `return false` to
|
||||
the function.
|
||||
@@ -1,17 +0,0 @@
|
||||
@ngdoc error
|
||||
@name $parse:isecff
|
||||
@fullName Referencing 'call', 'apply' and 'bind' Disallowed
|
||||
@description
|
||||
|
||||
Occurs when an expression attempts to invoke Function's 'call', 'apply' or 'bind'.
|
||||
|
||||
Angular bans the invocation of 'call', 'apply' and 'bind' from within expressions
|
||||
since access is a known way to modify the behaviour of existing functions.
|
||||
|
||||
To resolve this error, avoid using these methods in expressions.
|
||||
|
||||
Example expression that would result in this error:
|
||||
|
||||
```
|
||||
<div>{{user.sendInfo.call({}, true)}}</div>
|
||||
```
|
||||
@@ -1,27 +0,0 @@
|
||||
@ngdoc error
|
||||
@name $parse:isecfld
|
||||
@fullName Referencing Disallowed Field in Expression
|
||||
@description
|
||||
|
||||
Occurs when an expression attempts to access one of the following fields:
|
||||
|
||||
* __proto__
|
||||
* __defineGetter__
|
||||
* __defineSetter__
|
||||
* __lookupGetter__
|
||||
* __lookupSetter__
|
||||
|
||||
AngularJS bans access to these fields from within expressions since
|
||||
access is a known way to mess with native objects or
|
||||
to execute arbitrary Javascript code.
|
||||
|
||||
To resolve this error, avoid using these fields in expressions. As a last resort,
|
||||
alias their value and access them through the alias instead.
|
||||
|
||||
Example expressions that would result in this error:
|
||||
|
||||
```
|
||||
<div>{{user.__proto__.hasOwnProperty = $emit}}</div>
|
||||
|
||||
<div>{{user.__defineGetter__('name', noop)}}</div>
|
||||
```
|
||||
@@ -1,10 +0,0 @@
|
||||
@ngdoc error
|
||||
@name $parse:isecfn
|
||||
@fullName Referencing Function Disallowed
|
||||
@description
|
||||
|
||||
Occurs when an expression attempts to access the 'Function' object (constructor for all functions in JavaScript).
|
||||
|
||||
Angular bans access to Function from within expressions since constructor access is a known way to execute arbitrary Javascript code.
|
||||
|
||||
To resolve this error, avoid Function access.
|
||||
@@ -1,11 +0,0 @@
|
||||
@ngdoc error
|
||||
@name $parse:isecobj
|
||||
@fullName Referencing Object Disallowed
|
||||
@description
|
||||
|
||||
Occurs when an expression attempts to access the 'Object' object (Root object in JavaScript).
|
||||
|
||||
Angular bans access to Object from within expressions since access is a known way to modify
|
||||
the behaviour of existing objects.
|
||||
|
||||
To resolve this error, avoid Object access.
|
||||
@@ -1,45 +0,0 @@
|
||||
@ngdoc error
|
||||
@name $parse:isecwindow
|
||||
@fullName Referencing Window object in Expression
|
||||
@description
|
||||
|
||||
Occurs when an expression attempts to access a Window object.
|
||||
|
||||
AngularJS restricts access to the Window object from within expressions since it's a known way to
|
||||
execute arbitrary Javascript code.
|
||||
|
||||
This check is only performed on object index and function calls in Angular expressions. These are
|
||||
places that are harder for the developer to guard. Dotted member access (such as a.b.c) does not
|
||||
perform this check - it's up to the developer to not expose such sensitive and powerful objects
|
||||
directly on the scope chain.
|
||||
|
||||
To resolve this error, avoid Window access.
|
||||
|
||||
### Common CoffeeScript Issue
|
||||
|
||||
Be aware that if you are using CoffeeScript, it automatically returns the value of the last statement in a
|
||||
function. So for instance
|
||||
|
||||
```coffeescript
|
||||
scope.foo = ->
|
||||
window.open 'https://example.com'
|
||||
```
|
||||
|
||||
compiles to something like
|
||||
|
||||
```js
|
||||
scope.foo = function() {
|
||||
return window.open('https://example.com');
|
||||
};
|
||||
```
|
||||
|
||||
You can see that this function will return the result of calling `window.open`, which is a `Window`
|
||||
object.
|
||||
|
||||
You can avoid this by explicitly returning something else from the function:
|
||||
|
||||
```coffeescript
|
||||
scope.foo = ->
|
||||
window.open 'https://example.com'
|
||||
return true;
|
||||
```
|
||||
@@ -113,11 +113,11 @@ You can try evaluating different expressions here:
|
||||
Angular does not use JavaScript's `eval()` to evaluate expressions. Instead Angular's
|
||||
{@link ng.$parse $parse} service processes these expressions.
|
||||
|
||||
Angular expressions do not have access to global variables like `window`, `document` or `location`.
|
||||
Angular expressions do not have direct access to global variables like `window`, `document` or `location`.
|
||||
This restriction is intentional. It prevents accidental access to the global state – a common source of subtle bugs.
|
||||
|
||||
Instead use services like `$window` and `$location` in functions called from expressions. Such services
|
||||
provide mockable access to globals.
|
||||
Instead use services like `$window` and `$location` in functions on controllers, which are then called from expressions.
|
||||
Such services provide mockable access to globals.
|
||||
|
||||
It is possible to access the context object using the identifier `this` and the locals object using the
|
||||
identifier `$locals`.
|
||||
|
||||
@@ -30,42 +30,55 @@ so keeping to AngularJS standards is not just a functionality issue, it's also c
|
||||
facilitate rapid security updates.
|
||||
|
||||
|
||||
## Expression Sandboxing
|
||||
## Angular Templates and Expressions
|
||||
|
||||
AngularJS's expressions are sandboxed not for security reasons, but instead to maintain a proper
|
||||
separation of application responsibilities. For example, access to `window` is disallowed
|
||||
because it makes it easy to introduce brittle global state into your application.
|
||||
**If an attacker has access to control Angular templates or expressions, they can exploit an Angular application
|
||||
via an XSS attack, regardless of the version.**
|
||||
|
||||
However, this sandbox is not intended to stop attackers who can edit the template before it's
|
||||
processed by Angular. It may be possible to run arbitrary JavaScript inside double-curly bindings
|
||||
if an attacker can modify them.
|
||||
There are a number of ways that templates and expressions can be controlled:
|
||||
|
||||
But if an attacker can change arbitrary HTML templates, there's nothing stopping them from doing:
|
||||
* **Generating Angular templates on the server containing user-provided content**. This is the most common pitfall
|
||||
where you are generating HTML via some server-side engine such as PHP, Java or ASP.NET.
|
||||
* **Passing an expression generated from user-provided content in calls to the following methods on a {@link scope scope}**:
|
||||
* `$watch(userContent, ...)`
|
||||
* `$watchGroup(userContent, ...)`
|
||||
* `$watchCollection(userContent, ...)`
|
||||
* `$eval(userContent)`
|
||||
* `$evalAsync(userContent)`
|
||||
* `$apply(userContent)`
|
||||
* `$applyAsync(userContent)`
|
||||
* **Passing an expression generated from user-provided content in calls to services that parse expressions**:
|
||||
* `$compile(userContent)`
|
||||
* `$parse(userContent)`
|
||||
* `$interpolate(userContent)`
|
||||
* **Passing an expression generated from user provided content as a predicate to `orderBy` pipe**:
|
||||
`{{ value | orderBy : userContent }}`
|
||||
|
||||
```html
|
||||
<script>somethingEvil();</script>
|
||||
```
|
||||
### Sandbox removal
|
||||
Each version of Angular 1 up to, but not including 1.6, contained an expression sandbox, which reduced the surface area of
|
||||
the vulnerability but never removed it. **In Angular 1.6 we removed this sandbox as developers kept relying upon it as a security
|
||||
feature even though it was always possible to access arbitrary JavaScript code if one could control the Angular templates
|
||||
or expressions of applications.**
|
||||
|
||||
**It's better to design your application in such a way that users cannot change client-side templates.**
|
||||
Control of the Angular templates makes applications vulnerable even if there was a completely secure sandbox:
|
||||
* https://ryhanson.com/stealing-session-tokens-on-plunker-with-an-angular-expression-injection/ in this blog post the author shows
|
||||
a (now closed) vulnerability in the Plunker application due to server-side rendering inside an Angular template.
|
||||
* https://ryhanson.com/angular-expression-injection-walkthrough/ in this blog post the author describes an attack, which does not
|
||||
rely upon an expression sandbox bypass, that can be made because the sample application is rendering a template on the server that
|
||||
contains user entered content.
|
||||
|
||||
For instance:
|
||||
**It's best to design your application in such a way that users cannot change client-side templates.**
|
||||
|
||||
* Do not mix client and server templates
|
||||
* Do not use user input to generate templates dynamically
|
||||
* Do not run user input through `$scope.$eval`
|
||||
* Do not run user input through `$scope.$eval` (or any of the other expression parsing functions listed above)
|
||||
* Consider using {@link ng.directive:ngCsp CSP} (but don't rely only on CSP)
|
||||
|
||||
**You can use suitably sanitized server-side templating to dynamically generate CSS, URLs, etc, but not for generating templates that are
|
||||
bootstrapped/compiled by Angular.**
|
||||
|
||||
### Mixing client-side and server-side templates
|
||||
|
||||
In general, we recommend against this because it can create unintended XSS vectors.
|
||||
|
||||
However, it's ok to mix server-side templating in the bootstrap template (`index.html`) as long
|
||||
as user input cannot be used on the server to output html that would then be processed by Angular
|
||||
in a way that would allow for arbitrary code execution.
|
||||
|
||||
**For instance, you can use server-side templating to dynamically generate CSS, URLs, etc, but not
|
||||
for generating templates that are bootstrapped/compiled by Angular.**
|
||||
**If you must continue to allow user-provided content in an Angular template then the safest option is to ensure that it is only
|
||||
present in the part of the template that is made inert via the {@link ngNonBindable} directive.**
|
||||
|
||||
|
||||
## HTTP Requests
|
||||
|
||||
+19
-245
@@ -13,60 +13,23 @@
|
||||
|
||||
var $parseMinErr = minErr('$parse');
|
||||
|
||||
var ARRAY_CTOR = [].constructor;
|
||||
var BOOLEAN_CTOR = (false).constructor;
|
||||
var FUNCTION_CTOR = Function.constructor;
|
||||
var NUMBER_CTOR = (0).constructor;
|
||||
var OBJECT_CTOR = {}.constructor;
|
||||
var STRING_CTOR = ''.constructor;
|
||||
var ARRAY_CTOR_PROTO = ARRAY_CTOR.prototype;
|
||||
var BOOLEAN_CTOR_PROTO = BOOLEAN_CTOR.prototype;
|
||||
var FUNCTION_CTOR_PROTO = FUNCTION_CTOR.prototype;
|
||||
var NUMBER_CTOR_PROTO = NUMBER_CTOR.prototype;
|
||||
var OBJECT_CTOR_PROTO = OBJECT_CTOR.prototype;
|
||||
var STRING_CTOR_PROTO = STRING_CTOR.prototype;
|
||||
|
||||
var CALL = FUNCTION_CTOR_PROTO.call;
|
||||
var APPLY = FUNCTION_CTOR_PROTO.apply;
|
||||
var BIND = FUNCTION_CTOR_PROTO.bind;
|
||||
|
||||
var objectValueOf = OBJECT_CTOR_PROTO.valueOf;
|
||||
var objectValueOf = {}.constructor.prototype.valueOf;
|
||||
|
||||
// Sandboxing Angular Expressions
|
||||
// ------------------------------
|
||||
// Angular expressions are generally considered safe because these expressions only have direct
|
||||
// access to `$scope` and locals. However, one can obtain the ability to execute arbitrary JS code by
|
||||
// obtaining a reference to native JS functions such as the Function constructor.
|
||||
// Angular expressions are no longer sandboxed. So it is now even easier to access arbitary JS code by
|
||||
// various means such as obtaining a reference to native JS functions like the Function constructor.
|
||||
//
|
||||
// As an example, consider the following Angular expression:
|
||||
//
|
||||
// {}.toString.constructor('alert("evil JS code")')
|
||||
//
|
||||
// This sandboxing technique is not perfect and doesn't aim to be. The goal is to prevent exploits
|
||||
// against the expression language, but not to prevent exploits that were enabled by exposing
|
||||
// sensitive JavaScript or browser APIs on Scope. Exposing such objects on a Scope is never a good
|
||||
// practice and therefore we are not even trying to protect against interaction with an object
|
||||
// explicitly exposed in this way.
|
||||
//
|
||||
// In general, it is not possible to access a Window object from an angular expression unless a
|
||||
// window or some DOM object that has a reference to window is published onto a Scope.
|
||||
// Similarly we prevent invocations of function known to be dangerous, as well as assignments to
|
||||
// native objects.
|
||||
// It is important to realise that if you create an expression from a string that contains user provided
|
||||
// content then it is possible that your application contains a security vulnerability to an XSS style attack.
|
||||
//
|
||||
// See https://docs.angularjs.org/guide/security
|
||||
|
||||
|
||||
function ensureSafeMemberName(name, fullExpression) {
|
||||
if (name === '__defineGetter__' || name === '__defineSetter__'
|
||||
|| name === '__lookupGetter__' || name === '__lookupSetter__'
|
||||
|| name === '__proto__') {
|
||||
throw $parseMinErr('isecfld',
|
||||
'Attempting to access a disallowed field in Angular expressions! '
|
||||
+ 'Expression: {0}', fullExpression);
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
function getStringValue(name) {
|
||||
// Property names must be strings. This means that non-string objects cannot be used
|
||||
// as keys in an object. Any non-string object, including a number, is typecasted
|
||||
@@ -85,67 +48,6 @@ function getStringValue(name) {
|
||||
return name + '';
|
||||
}
|
||||
|
||||
function ensureSafeObject(obj, fullExpression) {
|
||||
// nifty check if obj is Function that is fast and works across iframes and other contexts
|
||||
if (obj) {
|
||||
if (obj.constructor === obj) {
|
||||
throw $parseMinErr('isecfn',
|
||||
'Referencing Function in Angular expressions is disallowed! Expression: {0}',
|
||||
fullExpression);
|
||||
} else if (// isWindow(obj)
|
||||
obj.window === obj) {
|
||||
throw $parseMinErr('isecwindow',
|
||||
'Referencing the Window in Angular expressions is disallowed! Expression: {0}',
|
||||
fullExpression);
|
||||
} else if (// isElement(obj)
|
||||
obj.children && (obj.nodeName || (obj.prop && obj.attr && obj.find))) {
|
||||
throw $parseMinErr('isecdom',
|
||||
'Referencing DOM nodes in Angular expressions is disallowed! Expression: {0}',
|
||||
fullExpression);
|
||||
} else if (// block Object so that we can't get hold of dangerous Object.* methods
|
||||
obj === Object) {
|
||||
throw $parseMinErr('isecobj',
|
||||
'Referencing Object in Angular expressions is disallowed! Expression: {0}',
|
||||
fullExpression);
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
function ensureSafeFunction(obj, fullExpression) {
|
||||
if (obj) {
|
||||
if (obj.constructor === obj) {
|
||||
throw $parseMinErr('isecfn',
|
||||
'Referencing Function in Angular expressions is disallowed! Expression: {0}',
|
||||
fullExpression);
|
||||
} else if (obj === CALL || obj === APPLY || obj === BIND) {
|
||||
throw $parseMinErr('isecff',
|
||||
'Referencing call, apply or bind in Angular expressions is disallowed! Expression: {0}',
|
||||
fullExpression);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function ensureSafeAssignContext(obj, fullExpression) {
|
||||
if (obj) {
|
||||
if (obj === ARRAY_CTOR ||
|
||||
obj === BOOLEAN_CTOR ||
|
||||
obj === FUNCTION_CTOR ||
|
||||
obj === NUMBER_CTOR ||
|
||||
obj === OBJECT_CTOR ||
|
||||
obj === STRING_CTOR ||
|
||||
obj === ARRAY_CTOR_PROTO ||
|
||||
obj === BOOLEAN_CTOR_PROTO ||
|
||||
obj === FUNCTION_CTOR_PROTO ||
|
||||
obj === NUMBER_CTOR_PROTO ||
|
||||
obj === OBJECT_CTOR_PROTO ||
|
||||
obj === STRING_CTOR_PROTO) {
|
||||
throw $parseMinErr('isecaf',
|
||||
'Assigning to a constructor or its prototype is disallowed! Expression: {0}',
|
||||
fullExpression);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var OPERATORS = createMap();
|
||||
forEach('+ - * / % === !== == != < > <= >= && || ! = |'.split(' '), function(operator) { OPERATORS[operator] = true; });
|
||||
@@ -862,13 +764,12 @@ function ASTCompiler(astBuilder, $filter) {
|
||||
}
|
||||
|
||||
ASTCompiler.prototype = {
|
||||
compile: function(expression, expensiveChecks) {
|
||||
compile: function(expression) {
|
||||
var self = this;
|
||||
var ast = this.astBuilder.ast(expression);
|
||||
this.state = {
|
||||
nextId: 0,
|
||||
filters: {},
|
||||
expensiveChecks: expensiveChecks,
|
||||
fn: {vars: [], body: [], own: {}},
|
||||
assign: {vars: [], body: [], own: {}},
|
||||
inputs: []
|
||||
@@ -911,21 +812,13 @@ ASTCompiler.prototype = {
|
||||
|
||||
// eslint-disable-next-line no-new-func
|
||||
var fn = (new Function('$filter',
|
||||
'ensureSafeMemberName',
|
||||
'ensureSafeObject',
|
||||
'ensureSafeFunction',
|
||||
'getStringValue',
|
||||
'ensureSafeAssignContext',
|
||||
'ifDefined',
|
||||
'plus',
|
||||
'text',
|
||||
fnString))(
|
||||
this.$filter,
|
||||
ensureSafeMemberName,
|
||||
ensureSafeObject,
|
||||
ensureSafeFunction,
|
||||
getStringValue,
|
||||
ensureSafeAssignContext,
|
||||
ifDefined,
|
||||
plusFn,
|
||||
expression);
|
||||
@@ -1042,7 +935,6 @@ ASTCompiler.prototype = {
|
||||
nameId.computed = false;
|
||||
nameId.name = ast.name;
|
||||
}
|
||||
ensureSafeMemberName(ast.name);
|
||||
self.if_(self.stage === 'inputs' || self.not(self.getHasOwnProperty('l', ast.name)),
|
||||
function() {
|
||||
self.if_(self.stage === 'inputs' || 's', function() {
|
||||
@@ -1055,9 +947,6 @@ ASTCompiler.prototype = {
|
||||
});
|
||||
}, intoId && self.lazyAssign(intoId, self.nonComputedMember('l', ast.name))
|
||||
);
|
||||
if (self.state.expensiveChecks || isPossiblyDangerousMemberName(ast.name)) {
|
||||
self.addEnsureSafeObject(intoId);
|
||||
}
|
||||
recursionFn(intoId);
|
||||
break;
|
||||
case AST.MemberExpression:
|
||||
@@ -1065,32 +954,24 @@ ASTCompiler.prototype = {
|
||||
intoId = intoId || this.nextId();
|
||||
self.recurse(ast.object, left, undefined, function() {
|
||||
self.if_(self.notNull(left), function() {
|
||||
if (create && create !== 1) {
|
||||
self.addEnsureSafeAssignContext(left);
|
||||
}
|
||||
if (ast.computed) {
|
||||
right = self.nextId();
|
||||
self.recurse(ast.property, right);
|
||||
self.getStringValue(right);
|
||||
self.addEnsureSafeMemberName(right);
|
||||
if (create && create !== 1) {
|
||||
self.if_(self.not(self.computedMember(left, right)), self.lazyAssign(self.computedMember(left, right), '{}'));
|
||||
}
|
||||
expression = self.ensureSafeObject(self.computedMember(left, right));
|
||||
expression = self.computedMember(left, right);
|
||||
self.assign(intoId, expression);
|
||||
if (nameId) {
|
||||
nameId.computed = true;
|
||||
nameId.name = right;
|
||||
}
|
||||
} else {
|
||||
ensureSafeMemberName(ast.property.name);
|
||||
if (create && create !== 1) {
|
||||
self.if_(self.not(self.nonComputedMember(left, ast.property.name)), self.lazyAssign(self.nonComputedMember(left, ast.property.name), '{}'));
|
||||
}
|
||||
expression = self.nonComputedMember(left, ast.property.name);
|
||||
if (self.state.expensiveChecks || isPossiblyDangerousMemberName(ast.property.name)) {
|
||||
expression = self.ensureSafeObject(expression);
|
||||
}
|
||||
self.assign(intoId, expression);
|
||||
if (nameId) {
|
||||
nameId.computed = false;
|
||||
@@ -1122,21 +1003,16 @@ ASTCompiler.prototype = {
|
||||
args = [];
|
||||
self.recurse(ast.callee, right, left, function() {
|
||||
self.if_(self.notNull(right), function() {
|
||||
self.addEnsureSafeFunction(right);
|
||||
forEach(ast.arguments, function(expr) {
|
||||
self.recurse(expr, ast.constant ? undefined : self.nextId(), undefined, function(argument) {
|
||||
args.push(self.ensureSafeObject(argument));
|
||||
args.push(argument);
|
||||
});
|
||||
});
|
||||
if (left.name) {
|
||||
if (!self.state.expensiveChecks) {
|
||||
self.addEnsureSafeObject(left.context);
|
||||
}
|
||||
expression = self.member(left.context, left.name, left.computed) + '(' + args.join(',') + ')';
|
||||
} else {
|
||||
expression = right + '(' + args.join(',') + ')';
|
||||
}
|
||||
expression = self.ensureSafeObject(expression);
|
||||
self.assign(intoId, expression);
|
||||
}, function() {
|
||||
self.assign(intoId, 'undefined');
|
||||
@@ -1154,8 +1030,6 @@ ASTCompiler.prototype = {
|
||||
this.recurse(ast.left, undefined, left, function() {
|
||||
self.if_(self.notNull(left.context), function() {
|
||||
self.recurse(ast.right, right);
|
||||
self.addEnsureSafeObject(self.member(left.context, left.name, left.computed));
|
||||
self.addEnsureSafeAssignContext(left.context);
|
||||
expression = self.member(left.context, left.name, left.computed) + ast.operator + right;
|
||||
self.assign(intoId, expression);
|
||||
recursionFn(intoId || expression);
|
||||
@@ -1303,42 +1177,10 @@ ASTCompiler.prototype = {
|
||||
return this.nonComputedMember(left, right);
|
||||
},
|
||||
|
||||
addEnsureSafeObject: function(item) {
|
||||
this.current().body.push(this.ensureSafeObject(item), ';');
|
||||
},
|
||||
|
||||
addEnsureSafeMemberName: function(item) {
|
||||
this.current().body.push(this.ensureSafeMemberName(item), ';');
|
||||
},
|
||||
|
||||
addEnsureSafeFunction: function(item) {
|
||||
this.current().body.push(this.ensureSafeFunction(item), ';');
|
||||
},
|
||||
|
||||
addEnsureSafeAssignContext: function(item) {
|
||||
this.current().body.push(this.ensureSafeAssignContext(item), ';');
|
||||
},
|
||||
|
||||
ensureSafeObject: function(item) {
|
||||
return 'ensureSafeObject(' + item + ',text)';
|
||||
},
|
||||
|
||||
ensureSafeMemberName: function(item) {
|
||||
return 'ensureSafeMemberName(' + item + ',text)';
|
||||
},
|
||||
|
||||
ensureSafeFunction: function(item) {
|
||||
return 'ensureSafeFunction(' + item + ',text)';
|
||||
},
|
||||
|
||||
getStringValue: function(item) {
|
||||
this.assign(item, 'getStringValue(' + item + ')');
|
||||
},
|
||||
|
||||
ensureSafeAssignContext: function(item) {
|
||||
return 'ensureSafeAssignContext(' + item + ',text)';
|
||||
},
|
||||
|
||||
lazyRecurse: function(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck) {
|
||||
var self = this;
|
||||
return function() {
|
||||
@@ -1390,11 +1232,10 @@ function ASTInterpreter(astBuilder, $filter) {
|
||||
}
|
||||
|
||||
ASTInterpreter.prototype = {
|
||||
compile: function(expression, expensiveChecks) {
|
||||
compile: function(expression) {
|
||||
var self = this;
|
||||
var ast = this.astBuilder.ast(expression);
|
||||
this.expression = expression;
|
||||
this.expensiveChecks = expensiveChecks;
|
||||
findConstantAndWatchExpressions(ast, self.$filter);
|
||||
var assignable;
|
||||
var assign;
|
||||
@@ -1465,20 +1306,17 @@ ASTInterpreter.prototype = {
|
||||
context
|
||||
);
|
||||
case AST.Identifier:
|
||||
ensureSafeMemberName(ast.name, self.expression);
|
||||
return self.identifier(ast.name,
|
||||
self.expensiveChecks || isPossiblyDangerousMemberName(ast.name),
|
||||
context, create, self.expression);
|
||||
case AST.MemberExpression:
|
||||
left = this.recurse(ast.object, false, !!create);
|
||||
if (!ast.computed) {
|
||||
ensureSafeMemberName(ast.property.name, self.expression);
|
||||
right = ast.property.name;
|
||||
}
|
||||
if (ast.computed) right = this.recurse(ast.property);
|
||||
return ast.computed ?
|
||||
this.computedMember(left, right, context, create, self.expression) :
|
||||
this.nonComputedMember(left, right, self.expensiveChecks, context, create, self.expression);
|
||||
this.nonComputedMember(left, right, context, create, self.expression);
|
||||
case AST.CallExpression:
|
||||
args = [];
|
||||
forEach(ast.arguments, function(expr) {
|
||||
@@ -1499,13 +1337,11 @@ ASTInterpreter.prototype = {
|
||||
var rhs = right(scope, locals, assign, inputs);
|
||||
var value;
|
||||
if (rhs.value != null) {
|
||||
ensureSafeObject(rhs.context, self.expression);
|
||||
ensureSafeFunction(rhs.value, self.expression);
|
||||
var values = [];
|
||||
for (var i = 0; i < args.length; ++i) {
|
||||
values.push(ensureSafeObject(args[i](scope, locals, assign, inputs), self.expression));
|
||||
values.push(args[i](scope, locals, assign, inputs));
|
||||
}
|
||||
value = ensureSafeObject(rhs.value.apply(rhs.context, values), self.expression);
|
||||
value = rhs.value.apply(rhs.context, values);
|
||||
}
|
||||
return context ? {value: value} : value;
|
||||
};
|
||||
@@ -1515,8 +1351,6 @@ ASTInterpreter.prototype = {
|
||||
return function(scope, locals, assign, inputs) {
|
||||
var lhs = left(scope, locals, assign, inputs);
|
||||
var rhs = right(scope, locals, assign, inputs);
|
||||
ensureSafeObject(lhs.value, self.expression);
|
||||
ensureSafeAssignContext(lhs.context);
|
||||
lhs.context[lhs.name] = rhs;
|
||||
return context ? {value: rhs} : rhs;
|
||||
};
|
||||
@@ -1708,16 +1542,13 @@ ASTInterpreter.prototype = {
|
||||
value: function(value, context) {
|
||||
return function() { return context ? {context: undefined, name: undefined, value: value} : value; };
|
||||
},
|
||||
identifier: function(name, expensiveChecks, context, create, expression) {
|
||||
identifier: function(name, context, create, expression) {
|
||||
return function(scope, locals, assign, inputs) {
|
||||
var base = locals && (name in locals) ? locals : scope;
|
||||
if (create && create !== 1 && base && !(base[name])) {
|
||||
base[name] = {};
|
||||
}
|
||||
var value = base ? base[name] : undefined;
|
||||
if (expensiveChecks) {
|
||||
ensureSafeObject(value, expression);
|
||||
}
|
||||
if (context) {
|
||||
return {context: base, name: name, value: value};
|
||||
} else {
|
||||
@@ -1733,15 +1564,12 @@ ASTInterpreter.prototype = {
|
||||
if (lhs != null) {
|
||||
rhs = right(scope, locals, assign, inputs);
|
||||
rhs = getStringValue(rhs);
|
||||
ensureSafeMemberName(rhs, expression);
|
||||
if (create && create !== 1) {
|
||||
ensureSafeAssignContext(lhs);
|
||||
if (lhs && !(lhs[rhs])) {
|
||||
lhs[rhs] = {};
|
||||
}
|
||||
}
|
||||
value = lhs[rhs];
|
||||
ensureSafeObject(value, expression);
|
||||
}
|
||||
if (context) {
|
||||
return {context: lhs, name: rhs, value: value};
|
||||
@@ -1750,19 +1578,15 @@ ASTInterpreter.prototype = {
|
||||
}
|
||||
};
|
||||
},
|
||||
nonComputedMember: function(left, right, expensiveChecks, context, create, expression) {
|
||||
nonComputedMember: function(left, right, context, create, expression) {
|
||||
return function(scope, locals, assign, inputs) {
|
||||
var lhs = left(scope, locals, assign, inputs);
|
||||
if (create && create !== 1) {
|
||||
ensureSafeAssignContext(lhs);
|
||||
if (lhs && !(lhs[right])) {
|
||||
lhs[right] = {};
|
||||
}
|
||||
}
|
||||
var value = lhs != null ? lhs[right] : undefined;
|
||||
if (expensiveChecks || isPossiblyDangerousMemberName(right)) {
|
||||
ensureSafeObject(value, expression);
|
||||
}
|
||||
if (context) {
|
||||
return {context: lhs, name: right, value: value};
|
||||
} else {
|
||||
@@ -1794,14 +1618,10 @@ Parser.prototype = {
|
||||
constructor: Parser,
|
||||
|
||||
parse: function(text) {
|
||||
return this.astCompiler.compile(text, this.options.expensiveChecks);
|
||||
return this.astCompiler.compile(text);
|
||||
}
|
||||
};
|
||||
|
||||
function isPossiblyDangerousMemberName(name) {
|
||||
return name === 'constructor';
|
||||
}
|
||||
|
||||
function getValueOf(value) {
|
||||
return isFunction(value.valueOf) ? value.valueOf() : objectValueOf.call(value);
|
||||
}
|
||||
@@ -1859,8 +1679,7 @@ function getValueOf(value) {
|
||||
* service.
|
||||
*/
|
||||
function $ParseProvider() {
|
||||
var cacheDefault = createMap();
|
||||
var cacheExpensive = createMap();
|
||||
var cache = createMap();
|
||||
var literals = {
|
||||
'true': true,
|
||||
'false': false,
|
||||
@@ -1918,37 +1737,20 @@ function $ParseProvider() {
|
||||
var noUnsafeEval = csp().noUnsafeEval;
|
||||
var $parseOptions = {
|
||||
csp: noUnsafeEval,
|
||||
expensiveChecks: false,
|
||||
literals: copy(literals),
|
||||
isIdentifierStart: isFunction(identStart) && identStart,
|
||||
isIdentifierContinue: isFunction(identContinue) && identContinue
|
||||
},
|
||||
$parseOptionsExpensive = {
|
||||
csp: noUnsafeEval,
|
||||
expensiveChecks: true,
|
||||
literals: copy(literals),
|
||||
isIdentifierStart: isFunction(identStart) && identStart,
|
||||
isIdentifierContinue: isFunction(identContinue) && identContinue
|
||||
};
|
||||
var runningChecksEnabled = false;
|
||||
|
||||
$parse.$$runningExpensiveChecks = function() {
|
||||
return runningChecksEnabled;
|
||||
};
|
||||
|
||||
return $parse;
|
||||
|
||||
function $parse(exp, interceptorFn, expensiveChecks) {
|
||||
function $parse(exp, interceptorFn) {
|
||||
var parsedExpression, oneTime, cacheKey;
|
||||
|
||||
expensiveChecks = expensiveChecks || runningChecksEnabled;
|
||||
|
||||
switch (typeof exp) {
|
||||
case 'string':
|
||||
exp = exp.trim();
|
||||
cacheKey = exp;
|
||||
|
||||
var cache = (expensiveChecks ? cacheExpensive : cacheDefault);
|
||||
parsedExpression = cache[cacheKey];
|
||||
|
||||
if (!parsedExpression) {
|
||||
@@ -1956,9 +1758,8 @@ function $ParseProvider() {
|
||||
oneTime = true;
|
||||
exp = exp.substring(2);
|
||||
}
|
||||
var parseOptions = expensiveChecks ? $parseOptionsExpensive : $parseOptions;
|
||||
var lexer = new Lexer(parseOptions);
|
||||
var parser = new Parser(lexer, $filter, parseOptions);
|
||||
var lexer = new Lexer($parseOptions);
|
||||
var parser = new Parser(lexer, $filter, $parseOptions);
|
||||
parsedExpression = parser.parse(exp);
|
||||
if (parsedExpression.constant) {
|
||||
parsedExpression.$$watchDelegate = constantWatchDelegate;
|
||||
@@ -1968,9 +1769,6 @@ function $ParseProvider() {
|
||||
} else if (parsedExpression.inputs) {
|
||||
parsedExpression.$$watchDelegate = inputsWatchDelegate;
|
||||
}
|
||||
if (expensiveChecks) {
|
||||
parsedExpression = expensiveChecksInterceptor(parsedExpression);
|
||||
}
|
||||
cache[cacheKey] = parsedExpression;
|
||||
}
|
||||
return addInterceptor(parsedExpression, interceptorFn);
|
||||
@@ -1983,30 +1781,6 @@ function $ParseProvider() {
|
||||
}
|
||||
}
|
||||
|
||||
function expensiveChecksInterceptor(fn) {
|
||||
if (!fn) return fn;
|
||||
expensiveCheckFn.$$watchDelegate = fn.$$watchDelegate;
|
||||
expensiveCheckFn.assign = expensiveChecksInterceptor(fn.assign);
|
||||
expensiveCheckFn.constant = fn.constant;
|
||||
expensiveCheckFn.literal = fn.literal;
|
||||
for (var i = 0; fn.inputs && i < fn.inputs.length; ++i) {
|
||||
fn.inputs[i] = expensiveChecksInterceptor(fn.inputs[i]);
|
||||
}
|
||||
expensiveCheckFn.inputs = fn.inputs;
|
||||
|
||||
return expensiveCheckFn;
|
||||
|
||||
function expensiveCheckFn(scope, locals, assign, inputs) {
|
||||
var expensiveCheckOldValue = runningChecksEnabled;
|
||||
runningChecksEnabled = true;
|
||||
try {
|
||||
return fn(scope, locals, assign, inputs);
|
||||
} finally {
|
||||
runningChecksEnabled = expensiveCheckOldValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function expressionInputDirtyCheck(newValue, oldValueOfValue) {
|
||||
|
||||
if (newValue == null || oldValueOfValue == null) { // null/undefined
|
||||
|
||||
@@ -90,23 +90,13 @@ describe('event directives', function() {
|
||||
|
||||
});
|
||||
|
||||
describe('security', function() {
|
||||
describe('DOM event object', function() {
|
||||
it('should allow access to the $event object', inject(function($rootScope, $compile) {
|
||||
var scope = $rootScope.$new();
|
||||
element = $compile('<button ng-click="e = $event">BTN</button>')(scope);
|
||||
element.triggerHandler('click');
|
||||
expect(scope.e.target).toBe(element[0]);
|
||||
}));
|
||||
|
||||
it('should block access to DOM nodes (e.g. exposed via $event)', inject(function($rootScope, $compile) {
|
||||
var scope = $rootScope.$new();
|
||||
element = $compile('<button ng-click="e = $event.target">BTN</button>')(scope);
|
||||
expect(function() {
|
||||
element.triggerHandler('click');
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecdom', 'Referencing DOM nodes in Angular expressions is disallowed! ' +
|
||||
'Expression: e = $event.target');
|
||||
}));
|
||||
});
|
||||
|
||||
describe('blur', function() {
|
||||
|
||||
@@ -2422,770 +2422,6 @@ describe('parser', function() {
|
||||
}));
|
||||
|
||||
|
||||
describe('sandboxing', function() {
|
||||
describe('Function constructor', function() {
|
||||
it('should not tranverse the Function constructor in the getter', function() {
|
||||
expect(function() {
|
||||
scope.$eval('{}.toString.constructor');
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
|
||||
'Expression: {}.toString.constructor');
|
||||
});
|
||||
|
||||
it('should not allow access to the Function prototype in the getter', function() {
|
||||
expect(function() {
|
||||
scope.$eval('toString.constructor.prototype');
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
|
||||
'Expression: toString.constructor.prototype');
|
||||
});
|
||||
|
||||
it('should NOT allow access to Function constructor in getter', function() {
|
||||
expect(function() {
|
||||
scope.$eval('{}.toString.constructor("alert(1)")');
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
|
||||
'Expression: {}.toString.constructor("alert(1)")');
|
||||
});
|
||||
|
||||
it('should NOT allow access to Function constructor in setter', function() {
|
||||
|
||||
expect(function() {
|
||||
scope.$eval('{}.toString.constructor.a = 1');
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecfn','Referencing Function in Angular expressions is disallowed! ' +
|
||||
'Expression: {}.toString.constructor.a = 1');
|
||||
|
||||
expect(function() {
|
||||
scope.$eval('{}.toString["constructor"]["constructor"] = 1');
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
|
||||
'Expression: {}.toString["constructor"]["constructor"] = 1');
|
||||
|
||||
scope.key1 = 'const';
|
||||
scope.key2 = 'ructor';
|
||||
expect(function() {
|
||||
scope.$eval('{}.toString[key1 + key2].foo = 1');
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
|
||||
'Expression: {}.toString[key1 + key2].foo = 1');
|
||||
|
||||
expect(function() {
|
||||
scope.$eval('{}.toString["constructor"]["a"] = 1');
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
|
||||
'Expression: {}.toString["constructor"]["a"] = 1');
|
||||
|
||||
scope.a = [];
|
||||
expect(function() {
|
||||
scope.$eval('a.toString.constructor = 1', scope);
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
|
||||
'Expression: a.toString.constructor');
|
||||
});
|
||||
|
||||
it('should disallow traversing the Function object in a setter: E02', function() {
|
||||
expect(function() {
|
||||
// This expression by itself isn't dangerous. However, one can use this to
|
||||
// automatically call an object (e.g. a Function object) when it is automatically
|
||||
// toString'd/valueOf'd by setting the RHS to Function.prototype.call.
|
||||
scope.$eval('hasOwnProperty.constructor.prototype.valueOf = 1');
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
|
||||
'Expression: hasOwnProperty.constructor.prototype.valueOf');
|
||||
});
|
||||
|
||||
it('should disallow passing the Function object as a parameter: E03', function() {
|
||||
expect(function() {
|
||||
// This expression constructs a function but does not execute it. It does lead the
|
||||
// way to execute it if one can get the toString/valueOf of it to call the function.
|
||||
scope.$eval('["a", "alert(1)"].sort(hasOwnProperty.constructor)');
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
it('should prevent exploit E01', function() {
|
||||
// This is a tracking exploit. The two individual tests, it('should … : E02') and
|
||||
// it('should … : E03') test for two parts to block this exploit. This exploit works
|
||||
// as follows:
|
||||
//
|
||||
// • Array.sort takes a comparison function and passes it 2 parameters to compare. If
|
||||
// the result is non-primitive, sort then invokes valueOf() on the result.
|
||||
// • The Function object conveniently accepts two string arguments so we can use this
|
||||
// to construct a function. However, this doesn't do much unless we can execute it.
|
||||
// • We set the valueOf property on Function.prototype to Function.prototype.call.
|
||||
// This causes the function that we constructed to be executed when sort calls
|
||||
// .valueOf() on the result of the comparison.
|
||||
expect(function() {
|
||||
scope.$eval('' +
|
||||
'hasOwnProperty.constructor.prototype.valueOf=valueOf.call;' +
|
||||
'["a","alert(1)"].sort(hasOwnProperty.constructor)');
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
it('should NOT allow access to Function constructor that has been aliased in getters', function() {
|
||||
scope.foo = { 'bar': Function };
|
||||
expect(function() {
|
||||
scope.$eval('foo["bar"]');
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
|
||||
'Expression: foo["bar"]');
|
||||
});
|
||||
|
||||
it('should NOT allow access to Function constructor that has been aliased in setters', function() {
|
||||
scope.foo = { 'bar': Function };
|
||||
expect(function() {
|
||||
scope.$eval('foo["bar"] = 1');
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
|
||||
'Expression: foo["bar"] = 1');
|
||||
});
|
||||
|
||||
describe('expensiveChecks', function() {
|
||||
it('should block access to window object even when aliased in getters', inject(function($parse, $window) {
|
||||
scope.foo = {w: $window};
|
||||
// This isn't blocked for performance.
|
||||
expect(scope.$eval($parse('foo.w'))).toBe($window);
|
||||
// Event handlers use the more expensive path for better protection since they expose
|
||||
// the $event object on the scope.
|
||||
expect(function() {
|
||||
scope.$eval($parse('foo.w', null, true));
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecwindow', 'Referencing the Window in Angular expressions is disallowed! ' +
|
||||
'Expression: foo.w');
|
||||
}));
|
||||
|
||||
it('should block access to window object even when aliased in setters', inject(function($parse, $window) {
|
||||
scope.foo = {w: $window};
|
||||
// This is blocked as it points to `window`.
|
||||
expect(function() {
|
||||
expect(scope.$eval($parse('foo.w = 1'))).toBe($window);
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecwindow', 'Referencing the Window in Angular expressions is disallowed! ' +
|
||||
'Expression: foo.w = 1');
|
||||
// Event handlers use the more expensive path for better protection since they expose
|
||||
// the $event object on the scope.
|
||||
expect(function() {
|
||||
scope.$eval($parse('foo.w = 1', null, true));
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecwindow', 'Referencing the Window in Angular expressions is disallowed! ' +
|
||||
'Expression: foo.w = 1');
|
||||
}));
|
||||
|
||||
they('should propagate expensive checks when calling $prop',
|
||||
['foo.w && true',
|
||||
'$eval("foo.w && true")',
|
||||
'this["$eval"]("foo.w && true")',
|
||||
'bar;$eval("foo.w && true")',
|
||||
'$eval("foo.w && true");bar',
|
||||
'$eval("foo.w && true", null, false)',
|
||||
'$eval("foo");$eval("foo.w && true")',
|
||||
'$eval("$eval(\\"foo.w && true\\")")',
|
||||
'$eval("foo.e()")',
|
||||
'$evalAsync("foo.w && true")',
|
||||
'this["$evalAsync"]("foo.w && true")',
|
||||
'bar;$evalAsync("foo.w && true")',
|
||||
'$evalAsync("foo.w && true");bar',
|
||||
'$evalAsync("foo.w && true", null, false)',
|
||||
'$evalAsync("foo");$evalAsync("foo.w && true")',
|
||||
'$evalAsync("$evalAsync(\\"foo.w && true\\")")',
|
||||
'$evalAsync("foo.e()")',
|
||||
'$evalAsync("$eval(\\"foo.w && true\\")")',
|
||||
'$eval("$evalAsync(\\"foo.w && true\\")")',
|
||||
'$watch("foo.w && true")',
|
||||
'$watchCollection("foo.w && true", foo.f)',
|
||||
'$watchGroup(["foo.w && true"])',
|
||||
'$applyAsync("foo.w && true")'], function(expression) {
|
||||
inject(function($parse, $window) {
|
||||
scope.foo = {
|
||||
w: $window,
|
||||
bar: 'bar',
|
||||
e: function() { scope.$eval('foo.w && true'); },
|
||||
f: function() {}
|
||||
};
|
||||
expect($parse.$$runningExpensiveChecks()).toEqual(false);
|
||||
expect(function() {
|
||||
scope.$eval($parse(expression, null, true));
|
||||
scope.$digest();
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecwindow', 'Referencing the Window in Angular expressions is disallowed! ' +
|
||||
'Expression: foo.w && true');
|
||||
expect($parse.$$runningExpensiveChecks()).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
they('should restore the state of $$runningExpensiveChecks when the expression $prop throws',
|
||||
['$eval("foo.t()")',
|
||||
'$evalAsync("foo.t()", {foo: foo})'], function(expression) {
|
||||
inject(function($parse, $window) {
|
||||
scope.foo = {
|
||||
t: function() { throw new Error(); }
|
||||
};
|
||||
expect($parse.$$runningExpensiveChecks()).toEqual(false);
|
||||
expect(function() {
|
||||
scope.$eval($parse(expression, null, true));
|
||||
scope.$digest();
|
||||
}).toThrow();
|
||||
expect($parse.$$runningExpensiveChecks()).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle `inputs` when running with expensive checks', inject(function($parse) {
|
||||
expect(function() {
|
||||
scope.$watch($parse('a + b', null, true), noop);
|
||||
scope.$digest();
|
||||
}).not.toThrow();
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
describe('Function prototype functions', function() {
|
||||
it('should NOT allow invocation to Function.call', function() {
|
||||
scope.fn = Function.prototype.call;
|
||||
|
||||
expect(function() {
|
||||
scope.$eval('$eval.call()');
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecff', 'Referencing call, apply or bind in Angular expressions is disallowed! ' +
|
||||
'Expression: $eval.call()');
|
||||
|
||||
expect(function() {
|
||||
scope.$eval('fn()');
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecff', 'Referencing call, apply or bind in Angular expressions is disallowed! ' +
|
||||
'Expression: fn()');
|
||||
});
|
||||
|
||||
it('should NOT allow invocation to Function.apply', function() {
|
||||
scope.apply = Function.prototype.apply;
|
||||
|
||||
expect(function() {
|
||||
scope.$eval('$eval.apply()');
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecff', 'Referencing call, apply or bind in Angular expressions is disallowed! ' +
|
||||
'Expression: $eval.apply()');
|
||||
|
||||
expect(function() {
|
||||
scope.$eval('apply()');
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecff', 'Referencing call, apply or bind in Angular expressions is disallowed! ' +
|
||||
'Expression: apply()');
|
||||
});
|
||||
|
||||
it('should NOT allow invocation to Function.bind', function() {
|
||||
scope.bind = Function.prototype.bind;
|
||||
|
||||
expect(function() {
|
||||
scope.$eval('$eval.bind()');
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecff', 'Referencing call, apply or bind in Angular expressions is disallowed! ' +
|
||||
'Expression: $eval.bind()');
|
||||
|
||||
expect(function() {
|
||||
scope.$eval('bind()');
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecff', 'Referencing call, apply or bind in Angular expressions is disallowed! ' +
|
||||
'Expression: bind()');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Object constructor', function() {
|
||||
|
||||
it('should NOT allow access to Object constructor that has been aliased in getters', function() {
|
||||
scope.foo = { 'bar': Object };
|
||||
|
||||
expect(function() {
|
||||
scope.$eval('foo.bar.keys(foo)');
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecobj', 'Referencing Object in Angular expressions is disallowed! ' +
|
||||
'Expression: foo.bar.keys(foo)');
|
||||
|
||||
expect(function() {
|
||||
scope.$eval('foo["bar"]["keys"](foo)');
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecobj', 'Referencing Object in Angular expressions is disallowed! ' +
|
||||
'Expression: foo["bar"]["keys"](foo)');
|
||||
});
|
||||
|
||||
it('should NOT allow access to Object constructor that has been aliased in setters', function() {
|
||||
scope.foo = { 'bar': Object };
|
||||
|
||||
expect(function() {
|
||||
scope.$eval('foo.bar.keys(foo).bar = 1');
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecobj', 'Referencing Object in Angular expressions is disallowed! ' +
|
||||
'Expression: foo.bar.keys(foo).bar = 1');
|
||||
|
||||
expect(function() {
|
||||
scope.$eval('foo["bar"]["keys"](foo).bar = 1');
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecobj', 'Referencing Object in Angular expressions is disallowed! ' +
|
||||
'Expression: foo["bar"]["keys"](foo).bar = 1');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Window and $element/node', function() {
|
||||
it('should NOT allow access to the Window or DOM when indexing', inject(function($window, $document) {
|
||||
scope.wrap = {w: $window, d: $document};
|
||||
|
||||
expect(function() {
|
||||
scope.$eval('wrap["w"]', scope);
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecwindow', 'Referencing the Window in Angular expressions is ' +
|
||||
'disallowed! Expression: wrap["w"]');
|
||||
expect(function() {
|
||||
scope.$eval('wrap["d"]', scope);
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecdom', 'Referencing DOM nodes in Angular expressions is ' +
|
||||
'disallowed! Expression: wrap["d"]');
|
||||
expect(function() {
|
||||
scope.$eval('wrap["w"] = 1', scope);
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecwindow', 'Referencing the Window in Angular expressions is ' +
|
||||
'disallowed! Expression: wrap["w"] = 1');
|
||||
expect(function() {
|
||||
scope.$eval('wrap["d"] = 1', scope);
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecdom', 'Referencing DOM nodes in Angular expressions is ' +
|
||||
'disallowed! Expression: wrap["d"] = 1');
|
||||
}));
|
||||
|
||||
it('should NOT allow access to the Window or DOM returned from a function', inject(function($window, $document) {
|
||||
scope.getWin = valueFn($window);
|
||||
scope.getDoc = valueFn($document);
|
||||
|
||||
expect(function() {
|
||||
scope.$eval('getWin()', scope);
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecwindow', 'Referencing the Window in Angular expressions is ' +
|
||||
'disallowed! Expression: getWin()');
|
||||
expect(function() {
|
||||
scope.$eval('getDoc()', scope);
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecdom', 'Referencing DOM nodes in Angular expressions is ' +
|
||||
'disallowed! Expression: getDoc()');
|
||||
}));
|
||||
|
||||
it('should NOT allow calling functions on Window or DOM', inject(function($window, $document) {
|
||||
scope.a = {b: { win: $window, doc: $document }};
|
||||
expect(function() {
|
||||
scope.$eval('a.b.win.alert(1)', scope);
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecwindow', 'Referencing the Window in Angular expressions is ' +
|
||||
'disallowed! Expression: a.b.win.alert(1)');
|
||||
expect(function() {
|
||||
scope.$eval('a.b.doc.on("click")', scope);
|
||||
}).toThrowMinErr(
|
||||
'$parse', 'isecdom', 'Referencing DOM nodes in Angular expressions is ' +
|
||||
'disallowed! Expression: a.b.doc.on("click")');
|
||||
}));
|
||||
|
||||
// Issue #4805
|
||||
it('should NOT throw isecdom when referencing a Backbone Collection', function() {
|
||||
// Backbone stuff is sort of hard to mock, if you have a better way of doing this,
|
||||
// please fix this.
|
||||
var fakeBackboneCollection = {
|
||||
children: [{}, {}, {}],
|
||||
find: function() {},
|
||||
on: function() {},
|
||||
off: function() {},
|
||||
bind: function() {}
|
||||
};
|
||||
scope.backbone = fakeBackboneCollection;
|
||||
expect(function() { scope.$eval('backbone'); }).not.toThrow();
|
||||
});
|
||||
|
||||
it('should NOT throw isecdom when referencing an array with node properties', function() {
|
||||
var array = [1,2,3];
|
||||
array.on = array.attr = array.prop = array.bind = true;
|
||||
scope.array = array;
|
||||
expect(function() { scope.$eval('array'); }).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Disallowed fields', function() {
|
||||
it('should NOT allow access or invocation of __defineGetter__', function() {
|
||||
expect(function() {
|
||||
scope.$eval('{}.__defineGetter__');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
expect(function() {
|
||||
scope.$eval('{}.__defineGetter__("a", "".charAt)');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
|
||||
expect(function() {
|
||||
scope.$eval('{}["__defineGetter__"]');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
expect(function() {
|
||||
scope.$eval('{}["__defineGetter__"]("a", "".charAt)');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
|
||||
scope.a = '__define';
|
||||
scope.b = 'Getter__';
|
||||
expect(function() {
|
||||
scope.$eval('{}[a + b]');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
expect(function() {
|
||||
scope.$eval('{}[a + b]("a", "".charAt)');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
});
|
||||
|
||||
it('should NOT allow access or invocation of __defineSetter__', function() {
|
||||
expect(function() {
|
||||
scope.$eval('{}.__defineSetter__');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
expect(function() {
|
||||
scope.$eval('{}.__defineSetter__("a", "".charAt)');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
|
||||
expect(function() {
|
||||
scope.$eval('{}["__defineSetter__"]');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
expect(function() {
|
||||
scope.$eval('{}["__defineSetter__"]("a", "".charAt)');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
|
||||
scope.a = '__define';
|
||||
scope.b = 'Setter__';
|
||||
expect(function() {
|
||||
scope.$eval('{}[a + b]');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
expect(function() {
|
||||
scope.$eval('{}[a + b]("a", "".charAt)');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
});
|
||||
|
||||
it('should NOT allow access or invocation of __lookupGetter__', function() {
|
||||
expect(function() {
|
||||
scope.$eval('{}.__lookupGetter__');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
expect(function() {
|
||||
scope.$eval('{}.__lookupGetter__("a")');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
|
||||
expect(function() {
|
||||
scope.$eval('{}["__lookupGetter__"]');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
expect(function() {
|
||||
scope.$eval('{}["__lookupGetter__"]("a")');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
|
||||
scope.a = '__lookup';
|
||||
scope.b = 'Getter__';
|
||||
expect(function() {
|
||||
scope.$eval('{}[a + b]');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
expect(function() {
|
||||
scope.$eval('{}[a + b]("a")');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
});
|
||||
|
||||
it('should NOT allow access or invocation of __lookupSetter__', function() {
|
||||
expect(function() {
|
||||
scope.$eval('{}.__lookupSetter__');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
expect(function() {
|
||||
scope.$eval('{}.__lookupSetter__("a")');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
|
||||
expect(function() {
|
||||
scope.$eval('{}["__lookupSetter__"]');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
expect(function() {
|
||||
scope.$eval('{}["__lookupSetter__"]("a")');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
|
||||
scope.a = '__lookup';
|
||||
scope.b = 'Setter__';
|
||||
expect(function() {
|
||||
scope.$eval('{}[a + b]');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
expect(function() {
|
||||
scope.$eval('{}[a + b]("a")');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
});
|
||||
|
||||
it('should NOT allow access to __proto__', function() {
|
||||
expect(function() {
|
||||
scope.$eval('__proto__');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
expect(function() {
|
||||
scope.$eval('{}.__proto__');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
expect(function() {
|
||||
scope.$eval('{}.__proto__.foo = 1');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
|
||||
expect(function() {
|
||||
scope.$eval('{}["__proto__"]');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
expect(function() {
|
||||
scope.$eval('{}["__proto__"].foo = 1');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
|
||||
expect(function() {
|
||||
scope.$eval('{}[["__proto__"]]');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
expect(function() {
|
||||
scope.$eval('{}[["__proto__"]].foo = 1');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
|
||||
expect(function() {
|
||||
scope.$eval('0[["__proto__"]]');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
expect(function() {
|
||||
scope.$eval('0[["__proto__"]].foo = 1');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
|
||||
scope.a = '__pro';
|
||||
scope.b = 'to__';
|
||||
expect(function() {
|
||||
scope.$eval('{}[a + b]');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
expect(function() {
|
||||
scope.$eval('{}[a + b].foo = 1');
|
||||
}).toThrowMinErr('$parse', 'isecfld');
|
||||
});
|
||||
});
|
||||
|
||||
it('should prevent the exploit', function() {
|
||||
expect(function() {
|
||||
scope.$eval('(1)[{0: "__proto__", 1: "__proto__", 2: "__proto__", 3: "safe", length: 4, toString: [].pop}].foo = 1');
|
||||
}).toThrow();
|
||||
if (!msie || msie > 10) {
|
||||
// eslint-disable-next-line no-proto
|
||||
expect((1)['__proto__'].foo).toBeUndefined();
|
||||
}
|
||||
});
|
||||
|
||||
it('should prevent the exploit', function() {
|
||||
expect(function() {
|
||||
scope.$eval('' +
|
||||
' "".sub.call.call(' +
|
||||
'({})["constructor"].getOwnPropertyDescriptor("".sub.__proto__, "constructor").value,' +
|
||||
'null,' +
|
||||
'"alert(1)"' +
|
||||
')()' +
|
||||
'');
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
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(constructorExpr + '.join');
|
||||
}).not.toThrow();
|
||||
expect(function() {
|
||||
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(constructorExpr + '[0] = ""');
|
||||
}).toThrow();
|
||||
expect(function() {
|
||||
delete scope.foo;
|
||||
scope.$eval('foo = ' + constructorExpr + '; foo.join = ""');
|
||||
}).toThrow();
|
||||
|
||||
expect(function() {
|
||||
scope.foo = thing;
|
||||
scope.$eval('foo.constructor[0] = ""');
|
||||
}).toThrow();
|
||||
expect(function() {
|
||||
delete scope.foo;
|
||||
scope.$eval('foo.constructor[0] = ""', {foo: thing});
|
||||
}).toThrow();
|
||||
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');
|
||||
});
|
||||
|
||||
it('should prevent assigning only in the context of an actual constructor', function() {
|
||||
// foo.constructor is not a constructor.
|
||||
expect(function() {
|
||||
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() {
|
||||
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(prototypeExpr + '[0] = ""');
|
||||
}).toThrow();
|
||||
expect(function() {
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
||||
it('should call the function from the received instance and not from a new one', function() {
|
||||
var n = 0;
|
||||
scope.fn = function() {
|
||||
|
||||
Reference in New Issue
Block a user