fix(ngRepeat): make allowed aliasAs expressions more strict

Ensure that aliasAs expressions are valid simple identifiers. These are still assigned to $scope in the same way
that they were previously, however now you won't accidentally create a property named "filtered.collection".

This change additionally restricts identifiers to prevent the use of certain ECMAScript reserved words ("null",
"undefined", "this" --- should probably add "super", "try", "catch" and "finally" there too), as well as certain
properties used by $scope or ngRepeat, including $parent, $index, $even, $odd, $first, $middle, or $last.

Closes #8438
Closes #8440
This commit is contained in:
Caitlin Potter
2014-07-31 21:34:47 -04:00
parent b674003f41
commit 09b298705f
3 changed files with 91 additions and 0 deletions

View File

@@ -0,0 +1,39 @@
@ngdoc error
@name ngRepeat:badident
@fullName Invalid identifier expression
@description
Occurs when an invalid identifier is specified in an {@link ng.directive:ngRepeat ngRepeat} expression.
The {@link ng.directive:ngRepeat ngRepeat} directive's `alias as` syntax is used to assign an alias for the processed collection in scope.
If the expression is not a simple identifier (such that you could declare it with `var {name}`, or if the expression is a reserved name,
this error is thrown.
Reserved names include:
- `null`
- `this`
- `undefined`
- `$parent`
- `$even`
- `$odd`
- `$first`
- `$last`
- `$middle`
Invalid expressions might look like this:
```html
<li ng-repeat="item in items | filter:searchString as this">{{item}}</li>
<li ng-repeat="item in items | filter:searchString as some.objects["property"]">{{item}}</li>
<li ng-repeat="item in items | filter:searchString as resultOfSomeMethod()">{{item}}</li>
<li ng-repeat="item in items | filter:searchString as foo=6">{{item}}</li>
```
Valid expressions might look like this:
```html
<li ng-repeat="item in items | filter:searchString as collections">{{item}}</li>
<li ng-repeat="item in items | filter:searchString as filteredCollection">{{item}}</li>
```

View File

@@ -266,6 +266,12 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
var valueIdentifier = match[3] || match[1];
var keyIdentifier = match[2];
if (aliasAs && (!/^[$a-zA-Z_][$a-zA-Z0-9_]*$/.test(aliasAs) ||
/^null|undefined|this|\$index|\$first|\$middle|\$last|\$even|\$odd|\$parent$/.test(aliasAs))) {
throw ngRepeatMinErr('badident', "alias '{0}' is invalid --- must be a valid JS identifier which is not a reserved name.",
aliasAs);
}
var trackByExpGetter, trackByIdExpFn, trackByIdArrayFn, trackByIdObjFn;
var hashFnLocals = {$id: hashKey};

View File

@@ -416,6 +416,52 @@ describe('ngRepeat', function() {
expect(trim(element.text())).toEqual('No results found...');
});
it('should throw if alias identifier is not a simple identifier', inject(function($exceptionHandler) {
scope.x = 'bl';
scope.items = [
{ name : 'red' },
{ name : 'blue' },
{ name : 'green' },
{ name : 'black' },
{ name : 'orange' },
{ name : 'blonde' }
];
forEach([
'null',
'this',
'undefined',
'$parent',
'$index',
'$first',
'$middle',
'$last',
'$even',
'$odd',
'obj[key]',
'obj["key"]',
'obj[\'key\']',
'obj.property',
'foo=6'
], function(expr) {
var expression = ('item in items | filter:x as ' + expr + ' track by $index').replace(/"/g, '&quot;');
element = $compile(
'<div>' +
' <div ng-repeat="' + expression + '">{{item}}</div>' +
'</div>')(scope);
var expected = new RegExp('^\\[ngRepeat:badident\\] alias \'' + escape(expr) + '\' is invalid --- must be a valid JS identifier which is not a reserved name');
expect($exceptionHandler.errors.shift()[0].message).
toMatch(expected);
dealoc(element);
});
function escape(text) {
return text.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
}
}));
});