Skip to content

Commit 1192268

Browse files
committed
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 expressionsin 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 angular#14196
1 parent db281c1 commit 1192268

File tree

2 files changed

+64
-14
lines changed

2 files changed

+64
-14
lines changed

Diff for: src/ng/interpolate.js

+23-14
Original file line numberDiff line numberDiff line change
@@ -97,12 +97,19 @@ function $InterpolateProvider() {
9797

9898

9999
this.$get = ['$parse', '$exceptionHandler', '$sce', function($parse, $exceptionHandler, $sce) {
100-
var startSymbolLength = startSymbol.length,
100+
var EVERY_CHAR = /./g,
101+
startSymbolLength = startSymbol.length,
101102
endSymbolLength = endSymbol.length,
102-
escapedStartRegexp = new RegExp(startSymbol.replace(/./g, escape), 'g'),
103-
escapedEndRegexp = new RegExp(endSymbol.replace(/./g, escape), 'g');
103+
escapedStartSymbol = startSymbol.replace(EVERY_CHAR, escapeForString),
104+
escapedEndSymbol = endSymbol.replace(EVERY_CHAR, escapeForString),
105+
escapedStartRegexp = new RegExp(startSymbol.replace(EVERY_CHAR, escapeForRegex), 'g'),
106+
escapedEndRegexp = new RegExp(endSymbol.replace(EVERY_CHAR, escapeForRegex), 'g');
104107

105-
function escape(ch) {
108+
function escapeForString(ch) {
109+
return '\\' + ch;
110+
}
111+
112+
function escapeForRegex(ch) {
106113
return '\\\\\\' + ch;
107114
}
108115

@@ -194,13 +201,6 @@ function $InterpolateProvider() {
194201
* replacing angle brackets (<, >) with < and > respectively, and replacing all
195202
* interpolation start/end markers with their escaped counterparts.**
196203
*
197-
* Escaped interpolation markers are only replaced with the actual interpolation markers in rendered
198-
* output when the $interpolate service processes the text. So, for HTML elements interpolated
199-
* by {@link ng.$compile $compile}, or otherwise interpolated with the `mustHaveExpression` parameter
200-
* set to `true`, the interpolated text must contain an unescaped interpolation expression. As such,
201-
* this is typically useful only when user-data is used in rendering a template from the server, or
202-
* when otherwise untrusted data is used by a directive.
203-
*
204204
* <example>
205205
* <file name="index.html">
206206
* <div ng-init="username='A user'">
@@ -232,10 +232,13 @@ function $InterpolateProvider() {
232232
* - `context`: evaluation context for all expressions embedded in the interpolated text
233233
*/
234234
function $interpolate(text, mustHaveExpression, trustedContext, allOrNothing) {
235+
var hasEscapedMarkers = text.length &&
236+
(text.indexOf(escapedStartSymbol) !== -1 || text.indexOf(escapedEndSymbol) !== -1);
237+
235238
// Provide a quick exit and simplified result function for text with no interpolation
236239
if (!text.length || text.indexOf(startSymbol) === -1) {
237240
var constantInterp;
238-
if (!mustHaveExpression) {
241+
if (!mustHaveExpression || hasEscapedMarkers) {
239242
var unescapedText = unescapeText(text);
240243
constantInterp = valueFn(unescapedText);
241244
constantInterp.exp = text;
@@ -257,8 +260,8 @@ function $InterpolateProvider() {
257260
expressionPositions = [];
258261

259262
while (index < textLength) {
260-
if (((startIndex = text.indexOf(startSymbol, index)) != -1) &&
261-
((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) != -1)) {
263+
if (((startIndex = text.indexOf(startSymbol, index)) !== -1) &&
264+
((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) !== -1)) {
262265
if (index !== startIndex) {
263266
concat.push(unescapeText(text.substring(index, startIndex)));
264267
}
@@ -332,6 +335,12 @@ function $InterpolateProvider() {
332335
});
333336
}
334337
});
338+
} else if (hasEscapedMarkers) {
339+
return extend(valueFn(unescapeText(text)), {
340+
exp: text,
341+
expressions: [],
342+
$$watchDelegate: constantWatchDelegate
343+
});
335344
}
336345

337346
function parseStringifyInterceptor(value) {

Diff for: test/ng/interpolateSpec.js

+41
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,47 @@ describe('$interpolate', function() {
200200
}));
201201

202202

203+
it('should always unescape markers in uninterpolated strings', inject(function($interpolate) {
204+
// Exercise the "quick exit" path
205+
expect($interpolate('\\{\\{foo\\}\\}', false)(obj)).toBe('{{foo}}');
206+
expect($interpolate('\\{\\{foo\\}\\}', true)(obj)).toBe('{{foo}}');
207+
208+
// Exercise the "slow" path, where we can't immediately tell that there are no expressions
209+
expect($interpolate('}}{{\\{\\{foo\\}\\}', false)(obj)).toBe('}}{{{{foo}}');
210+
expect($interpolate('}}{{\\{\\{foo\\}\\}', true)(obj)).toBe('}}{{{{foo}}');
211+
})
212+
);
213+
214+
215+
it('should always unescape custom markers in uninterpolated strings', function() {
216+
module(function($interpolateProvider) {
217+
$interpolateProvider.startSymbol('[[');
218+
$interpolateProvider.endSymbol(']]');
219+
});
220+
inject(function($interpolate) {
221+
// Exercise the "quick exit" path
222+
expect($interpolate('\\[\\[foo\\]\\]', false)(obj)).toBe('[[foo]]');
223+
expect($interpolate('\\[\\[foo\\]\\]', true)(obj)).toBe('[[foo]]');
224+
225+
// Exercise the "slow" path, where we can't immediately tell that there are no expressions
226+
expect($interpolate(']][[\\[\\[foo\\]\\]', false)(obj)).toBe(']][[[[foo]]');
227+
expect($interpolate(']][[\\[\\[foo\\]\\]', true)(obj)).toBe(']][[[[foo]]');
228+
});
229+
});
230+
231+
232+
it('should not interpolate escaped expressions after unescaping',
233+
inject(function($compile, $rootScope) {
234+
var elem = $compile('<div>\\{\\{foo\\}\\}</div>')($rootScope);
235+
$rootScope.foo = 'bar';
236+
$rootScope.$digest();
237+
$rootScope.$digest();
238+
239+
expect(elem.text()).toBe('{{foo}}');
240+
})
241+
);
242+
243+
203244
// This test demonstrates that the web-server is responsible for escaping every single instance
204245
// of interpolation start/end markers in an expression which they do not wish to evaluate,
205246
// because AngularJS will not protect them from being evaluated (due to the added complexity

0 commit comments

Comments
 (0)