From a4c333941d14eb4b1856aa835baf176d504f2287 Mon Sep 17 00:00:00 2001 From: Georgios Kalpakas Date: Tue, 8 Mar 2016 23:42:02 +0200 Subject: [PATCH] fix($interpolate): always unescape escaped interpolation markers Previously, whenever `mustHaveExpression` was true (e.g. when compiling a text node), `$interpolate` would not unescape the escaped interpolation markers if there were no actual interpolation expressions in the same string. This commit fixes the problem, by always unescaping the escaped markers (if any). Due to always checking for the presence of escaped interpolation markers, there is a slight performance hit. Fixes #14196 --- src/ng/interpolate.js | 37 +++++++++++++++++++++------------- test/ng/interpolateSpec.js | 41 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 14 deletions(-) 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