From 70ddb61fe1803d81235af035617ae4089d38d9d3 Mon Sep 17 00:00:00 2001 From: Christopher Hiller Date: Fri, 8 Nov 2013 18:49:28 -0800 Subject: [PATCH] feat($parse): toggle evaluation of private fields via $parseProvider.allowPrivateFields() Disallowing evaluation of private fields in Angular expressions should be toggleable through a `config()` block. Usage is shown in ngdoc comments. --- src/ng/parse.js | 51 +++++++++++++++++++++++++++++++++----------- test/ng/parseSpec.js | 22 +++++++++++++++++-- 2 files changed, 58 insertions(+), 15 deletions(-) diff --git a/src/ng/parse.js b/src/ng/parse.js index eeb60c4e4e1f..4000bf73de31 100644 --- a/src/ng/parse.js +++ b/src/ng/parse.js @@ -38,7 +38,7 @@ var promiseWarning; // 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. -function ensureSafeMemberName(name, fullExpression, allowConstructor) { +function ensureSafeMemberName(name, fullExpression, allowConstructor, allowPrivate) { if (typeof name !== 'string' && toString.apply(name) !== "[object String]") { return name; } @@ -47,7 +47,7 @@ function ensureSafeMemberName(name, fullExpression, allowConstructor) { 'Referencing "constructor" field in Angular expressions is disallowed! Expression: {0}', fullExpression); } - if (name.charAt(0) === '_' || name.charAt(name.length-1) === '_') { + if (!allowPrivate && (name.charAt(0) === '_' || name.charAt(name.length-1) === '_')) { throw $parseMinErr('isecprv', 'Referencing private fields in Angular expressions is disallowed! Expression: {0}', fullExpression); @@ -738,7 +738,7 @@ Parser.prototype = { // In the getter, we will not block looking up "constructor" by name in order to support user defined // constructors. However, if value looked up is the Function constructor, we will still block it in the // ensureSafeObject call right after we look up o[i] (a few lines below.) - i = ensureSafeMemberName(indexFn(self, locals), parser.text, true /* allowConstructor */), + i = ensureSafeMemberName(indexFn(self, locals), parser.text, true /* allowConstructor */, parser.options.allowPrivateFields), v, p; if (!o) return undefined; @@ -754,7 +754,7 @@ Parser.prototype = { return v; }, { assign: function(self, value, locals) { - var key = ensureSafeMemberName(indexFn(self, locals), parser.text); + var key = ensureSafeMemberName(indexFn(self, locals), parser.text, false, parser.options.allowPrivateFields); // prevent overwriting of Function.constructor which would break ensureSafeObject check var safe = ensureSafeObject(obj(self, locals), parser.text); return safe[key] = value; @@ -863,7 +863,7 @@ function setter(obj, path, setValue, fullExp, options) { var element = path.split('.'), key; for (var i = 0; element.length > 1; i++) { - key = ensureSafeMemberName(element.shift(), fullExp); + key = ensureSafeMemberName(element.shift(), fullExp, false, options.allowPrivateFields); var propertyObj = obj[key]; if (!propertyObj) { propertyObj = {}; @@ -883,7 +883,7 @@ function setter(obj, path, setValue, fullExp, options) { obj = obj.$$v; } } - key = ensureSafeMemberName(element.shift(), fullExp); + key = ensureSafeMemberName(element.shift(), fullExp, false, options.allowPrivateFields); obj[key] = setValue; return setValue; } @@ -896,11 +896,12 @@ var getterFnCache = {}; * - http://jsperf.com/path-evaluation-simplified/7 */ function cspSafeGetterFn(key0, key1, key2, key3, key4, fullExp, options) { - ensureSafeMemberName(key0, fullExp); - ensureSafeMemberName(key1, fullExp); - ensureSafeMemberName(key2, fullExp); - ensureSafeMemberName(key3, fullExp); - ensureSafeMemberName(key4, fullExp); + + ensureSafeMemberName(key0, fullExp, false, options.allowPrivateFields); + ensureSafeMemberName(key1, fullExp, false, options.allowPrivateFields); + ensureSafeMemberName(key2, fullExp, false, options.allowPrivateFields); + ensureSafeMemberName(key3, fullExp, false, options.allowPrivateFields); + ensureSafeMemberName(key4, fullExp, false, options.allowPrivateFields); return !options.unwrapPromises ? function cspSafeGetter(scope, locals) { @@ -1023,7 +1024,7 @@ function getterFn(path, options, fullExp) { } else { var code = 'var l, fn, p;\n'; forEach(pathKeys, function(key, index) { - ensureSafeMemberName(key, fullExp); + ensureSafeMemberName(key, fullExp, false, options.allowPrivateFields); code += 'if(s === null || s === undefined) return s;\n' + 'l=s;\n' + 's='+ (index @@ -1120,7 +1121,8 @@ function $ParseProvider() { var $parseOptions = { csp: false, unwrapPromises: false, - logPromiseWarnings: true + logPromiseWarnings: true, + allowPrivateFields: false }; @@ -1206,6 +1208,29 @@ function $ParseProvider() { } }; + /** + * @ngdoc method + * @name ng.$parseProvider#allowPrivateFields + * @methodOf ng.$parseProvider + * @description + * + * Controls whether Angular should allow private fields (fields beginning with "_") within + * expressions. + * + * The default is set to `false`. + * + * @param {boolean=} value New value. + * @returns {boolean|self} Returns the current setting when used as a getter and self is used as + * setter. + */ + this.allowPrivateFields = function(value) { + if (isDefined(value)) { + $parseOptions.allowPrivateFields = value; + return this; + } else { + return $parseOptions.allowPrivateFields; + } + }; this.$get = ['$filter', '$sniffer', '$log', function($filter, $sniffer, $log) { $parseOptions.csp = $sniffer.csp; diff --git a/test/ng/parseSpec.js b/test/ng/parseSpec.js index c72b7e818749..855738ad562c 100644 --- a/test/ng/parseSpec.js +++ b/test/ng/parseSpec.js @@ -192,10 +192,11 @@ describe('parser', function() { }); }); - var $filterProvider, scope; + var $filterProvider, $parseProvider, scope; - beforeEach(module(['$filterProvider', function (filterProvider) { + beforeEach(module(['$filterProvider', '$parseProvider', function (filterProvider, parseProvider) { $filterProvider = filterProvider; + $parseProvider = parseProvider; }])); @@ -640,6 +641,23 @@ describe('parser', function() { testExpression('a["b"]["NAME"]'); testExpression('a["b"]["NAME"] = 1'); }); }); + + it('should allow access to private members if allowPrivateFields is true', function() { + scope._name = 'foo'; + expect(function() { + scope.$eval('_name'); + }).toThrowMinErr( + '$parse', 'isecprv', 'Referencing private fields in Angular expressions is disallowed! ' + + 'Expression: _name'); + $parseProvider.allowPrivateFields(true); + expect($parseProvider.allowPrivateFields()).toBe(true); + expect(function() { + scope.$eval('_name'); + }).not.toThrow(); + $parseProvider.allowPrivateFields(false); + expect($parseProvider.allowPrivateFields()).toBe(false); + }); + }); describe('Function constructor', function() {