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

Commit 9577702

Browse files
committed
fix($resource): don't use $parse for @dotted.member
params and paramDefaults support looking up the parameter value from the data object. The syntax for that is `@nested.property.name`. Currently, $resource uses $parse to do this. This is too liberal (you can use values like `@a=b` or `@a | filter` and have it work - which doesn't really make sense). It also puts up a dependency on $parse which is has restrictions to secure expressions used in templates. The value here, though a string, is specified in Javascript code and shouldn't have those restrictions.
1 parent a61b65d commit 9577702

File tree

3 files changed

+108
-6
lines changed

3 files changed

+108
-6
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
@ngdoc error
2+
@name $resource:badmember
3+
@fullName Syntax error in param value using @member lookup
4+
@description
5+
6+
Occurs when there is a syntax error when attempting to extract a param
7+
value from the data object.
8+
9+
Here's an example of valid syntax for `params` or `paramsDefault`:
10+
11+
````javascript
12+
{
13+
bar: '@foo.bar'
14+
}
15+
````
16+
17+
The part following the `@`, `foo.bar` in this case, should be a simple
18+
dotted member lookup using only ASCII identifiers. This error occurs
19+
when there is an error in that expression. The following are all syntax
20+
errors
21+
22+
| Value | Error |
23+
|---------|----------------|
24+
| `@` | Empty expression following `@`. |
25+
| `@1.a` | `1` is an invalid javascript identifier. |
26+
| `@.a` | Leading `.` is invalid. |
27+
| `@a[1]` | Only dotted lookups are supported (no index operator) |

src/ngResource/resource.js

+26-6
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,28 @@
22

33
var $resourceMinErr = angular.$$minErr('$resource');
44

5+
// Helper functions and regex to lookup a dotted path on an object
6+
// stopping at undefined/null. The path must be composed of ASCII
7+
// identifiers (just like $parse)
8+
var MEMBER_NAME_REGEX = /^(\.[a-zA-Z_$][0-9a-zA-Z_$]*)+$/;
9+
10+
function isValidDottedPath(path) {
11+
return (path != null && path !== '' && path !== 'hasOwnProperty' &&
12+
MEMBER_NAME_REGEX.test('.' + path));
13+
}
14+
15+
function lookupDottedPath(obj, path) {
16+
if (!isValidDottedPath(path)) {
17+
throw $resourceMinErr('badmember', 'Dotted member path "@{0}" is invalid.', path);
18+
}
19+
var keys = path.split('.');
20+
for (var i = 0, ii = keys.length; i < ii && obj !== undefined; i++) {
21+
var key = keys[i];
22+
obj = (obj !== null) ? obj[key] : undefined;
23+
}
24+
return obj;
25+
}
26+
527
/**
628
* @ngdoc overview
729
* @name ngResource
@@ -285,7 +307,8 @@ var $resourceMinErr = angular.$$minErr('$resource');
285307
</doc:example>
286308
*/
287309
angular.module('ngResource', ['ng']).
288-
factory('$resource', ['$http', '$parse', '$q', function($http, $parse, $q) {
310+
factory('$resource', ['$http', '$q', function($http, $q) {
311+
289312
var DEFAULT_ACTIONS = {
290313
'get': {method:'GET'},
291314
'save': {method:'POST'},
@@ -297,10 +320,7 @@ angular.module('ngResource', ['ng']).
297320
forEach = angular.forEach,
298321
extend = angular.extend,
299322
copy = angular.copy,
300-
isFunction = angular.isFunction,
301-
getter = function(obj, path) {
302-
return $parse(path)(obj);
303-
};
323+
isFunction = angular.isFunction;
304324

305325
/**
306326
* We need our custom method because encodeURIComponent is too aggressive and doesn't follow
@@ -415,7 +435,7 @@ angular.module('ngResource', ['ng']).
415435
forEach(actionParams, function(value, key){
416436
if (isFunction(value)) { value = value(); }
417437
ids[key] = value && value.charAt && value.charAt(0) == '@' ?
418-
getter(data, value.substr(1)) : value;
438+
lookupDottedPath(data, value.substr(1)) : value;
419439
});
420440
return ids;
421441
}

test/ngResource/resourceSpec.js

+55
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,48 @@ describe("resource", function() {
3131
$httpBackend.verifyNoOutstandingExpectation();
3232
});
3333

34+
describe('isValidDottedPath', function() {
35+
it('should support arbitrary dotted names', function() {
36+
expect(isValidDottedPath('')).toBe(false);
37+
expect(isValidDottedPath('1')).toBe(false);
38+
expect(isValidDottedPath('1abc')).toBe(false);
39+
expect(isValidDottedPath('.')).toBe(false);
40+
expect(isValidDottedPath('$')).toBe(true);
41+
expect(isValidDottedPath('a')).toBe(true);
42+
expect(isValidDottedPath('A')).toBe(true);
43+
expect(isValidDottedPath('a1')).toBe(true);
44+
expect(isValidDottedPath('$a')).toBe(true);
45+
expect(isValidDottedPath('$1')).toBe(true);
46+
expect(isValidDottedPath('$$')).toBe(true);
47+
expect(isValidDottedPath('$.$')).toBe(true);
48+
expect(isValidDottedPath('.$')).toBe(false);
49+
expect(isValidDottedPath('$.')).toBe(false);
50+
});
51+
});
52+
53+
describe('lookupDottedPath', function() {
54+
var data = {a: {b: 'foo', c: null}};
55+
56+
it('should throw for invalid path', function() {
57+
expect(function() {
58+
lookupDottedPath(data, '.ckck')
59+
}).toThrowMinErr('$resource', 'badmember',
60+
'Dotted member path "@.ckck" is invalid.');
61+
});
62+
63+
it('should get dotted paths', function() {
64+
expect(lookupDottedPath(data, 'a')).toEqual({b: 'foo', c: null});
65+
expect(lookupDottedPath(data, 'a.b')).toBe('foo');
66+
expect(lookupDottedPath(data, 'a.c')).toBeNull();
67+
});
68+
69+
it('should skip over null/undefined members', function() {
70+
expect(lookupDottedPath(data, 'a.b.c')).toBe(undefined);
71+
expect(lookupDottedPath(data, 'a.c.c')).toBe(undefined);
72+
expect(lookupDottedPath(data, 'a.b.c.d')).toBe(undefined);
73+
expect(lookupDottedPath(data, 'NOT_EXIST')).toBe(undefined);
74+
});
75+
});
3476

3577
it('should not include a request body when calling $delete', function() {
3678
$httpBackend.expect('DELETE', '/fooresource', null).respond({});
@@ -189,6 +231,19 @@ describe("resource", function() {
189231
});
190232

191233

234+
it('should support @_property lookups with underscores', function() {
235+
$httpBackend.expect('GET', '/Order/123').respond({_id: {_key:'123'}, count: 0});
236+
var LineItem = $resource('/Order/:_id', {_id: '@_id._key'});
237+
var item = LineItem.get({_id: 123});
238+
$httpBackend.flush();
239+
expect(item).toEqualData({_id: {_key: '123'}, count: 0});
240+
$httpBackend.expect('POST', '/Order/123').respond({_id: {_key:'123'}, count: 1});
241+
item.$save();
242+
$httpBackend.flush();
243+
expect(item).toEqualData({_id: {_key: '123'}, count: 1});
244+
});
245+
246+
192247
it('should not pass default params between actions', function() {
193248
var R = $resource('/Path', {}, {get: {method: 'GET', params: {objId: '1'}}, perform: {method: 'GET'}});
194249

0 commit comments

Comments
 (0)