diff --git a/src/ng/interpolate.js b/src/ng/interpolate.js index d119fe0175e9..ae837b14070e 100644 --- a/src/ng/interpolate.js +++ b/src/ng/interpolate.js @@ -97,12 +97,19 @@ function $InterpolateProvider() { this.$get = ['$parse', '$exceptionHandler', '$sce', function($parse, $exceptionHandler, $sce) { - var startSymbolLength = startSymbol.length, + var EVERY_CHAR = /./g, + startSymbolLength = startSymbol.length, endSymbolLength = endSymbol.length, - escapedStartRegexp = new RegExp(startSymbol.replace(/./g, escape), 'g'), - escapedEndRegexp = new RegExp(endSymbol.replace(/./g, escape), 'g'); + escapedStartSymbol = startSymbol.replace(EVERY_CHAR, escapeForString), + escapedEndSymbol = endSymbol.replace(EVERY_CHAR, escapeForString), + escapedStartRegexp = new RegExp(startSymbol.replace(EVERY_CHAR, escapeForRegex), 'g'), + escapedEndRegexp = new RegExp(endSymbol.replace(EVERY_CHAR, escapeForRegex), 'g'); - function escape(ch) { + function escapeForString(ch) { + return '\\' + ch; + } + + function escapeForRegex(ch) { return '\\\\\\' + ch; } @@ -194,13 +201,6 @@ function $InterpolateProvider() { * replacing angle brackets (<, >) with &lt; and &gt; respectively, and replacing all * interpolation start/end markers with their escaped counterparts.** * - * Escaped interpolation markers are only replaced with the actual interpolation markers in rendered - * output when the $interpolate service processes the text. So, for HTML elements interpolated - * by {@link ng.$compile $compile}, or otherwise interpolated with the `mustHaveExpression` parameter - * set to `true`, the interpolated text must contain an unescaped interpolation expression. As such, - * this is typically useful only when user-data is used in rendering a template from the server, or - * when otherwise untrusted data is used by a directive. - * * * *
@@ -232,10 +232,13 @@ function $InterpolateProvider() { * - `context`: evaluation context for all expressions embedded in the interpolated text */ function $interpolate(text, mustHaveExpression, trustedContext, allOrNothing) { + var hasEscapedMarkers = text.length && + (text.indexOf(escapedStartSymbol) !== -1 || text.indexOf(escapedEndSymbol) !== -1); + // Provide a quick exit and simplified result function for text with no interpolation if (!text.length || text.indexOf(startSymbol) === -1) { var constantInterp; - if (!mustHaveExpression) { + if (!mustHaveExpression || hasEscapedMarkers) { var unescapedText = unescapeText(text); constantInterp = valueFn(unescapedText); constantInterp.exp = text; @@ -257,8 +260,8 @@ function $InterpolateProvider() { expressionPositions = []; while (index < textLength) { - if (((startIndex = text.indexOf(startSymbol, index)) != -1) && - ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) != -1)) { + if (((startIndex = text.indexOf(startSymbol, index)) !== -1) && + ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) !== -1)) { if (index !== startIndex) { concat.push(unescapeText(text.substring(index, startIndex))); } @@ -332,6 +335,12 @@ function $InterpolateProvider() { }); } }); + } else if (hasEscapedMarkers) { + return extend(valueFn(unescapeText(text)), { + exp: text, + expressions: [], + $$watchDelegate: constantWatchDelegate + }); } function parseStringifyInterceptor(value) { diff --git a/test/ng/interpolateSpec.js b/test/ng/interpolateSpec.js index 2c23536ef4eb..91f4fa158d8f 100644 --- a/test/ng/interpolateSpec.js +++ b/test/ng/interpolateSpec.js @@ -200,6 +200,47 @@ describe('$interpolate', function() { })); + it('should always unescape markers in uninterpolated strings', inject(function($interpolate) { + // Exercise the "quick exit" path + expect($interpolate('\\{\\{foo\\}\\}', false)(obj)).toBe('{{foo}}'); + expect($interpolate('\\{\\{foo\\}\\}', true)(obj)).toBe('{{foo}}'); + + // Exercise the "slow" path, where we can't immediately tell that there are no expressions + expect($interpolate('}}{{\\{\\{foo\\}\\}', false)(obj)).toBe('}}{{{{foo}}'); + expect($interpolate('}}{{\\{\\{foo\\}\\}', true)(obj)).toBe('}}{{{{foo}}'); + }) + ); + + + it('should always unescape custom markers in uninterpolated strings', function() { + module(function($interpolateProvider) { + $interpolateProvider.startSymbol('[['); + $interpolateProvider.endSymbol(']]'); + }); + inject(function($interpolate) { + // Exercise the "quick exit" path + expect($interpolate('\\[\\[foo\\]\\]', false)(obj)).toBe('[[foo]]'); + expect($interpolate('\\[\\[foo\\]\\]', true)(obj)).toBe('[[foo]]'); + + // Exercise the "slow" path, where we can't immediately tell that there are no expressions + expect($interpolate(']][[\\[\\[foo\\]\\]', false)(obj)).toBe(']][[[[foo]]'); + expect($interpolate(']][[\\[\\[foo\\]\\]', true)(obj)).toBe(']][[[[foo]]'); + }); + }); + + + it('should not interpolate escaped expressions after unescaping', + inject(function($compile, $rootScope) { + var elem = $compile('
\\{\\{foo\\}\\}
')($rootScope); + $rootScope.foo = 'bar'; + $rootScope.$digest(); + $rootScope.$digest(); + + expect(elem.text()).toBe('{{foo}}'); + }) + ); + + // This test demonstrates that the web-server is responsible for escaping every single instance // of interpolation start/end markers in an expression which they do not wish to evaluate, // because AngularJS will not protect them from being evaluated (due to the added complexity