Skip to content

Commit 2b1b152

Browse files
committed
fix($interpolate): always unescape escaped interpolation markers
Previously, whenever `mustHaveExpression` was true (e.g. when compiling a text nodes), `$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 2b1b152

File tree

2 files changed

+64
-7
lines changed

2 files changed

+64
-7
lines changed

Diff for: src/ng/interpolate.js

+23-7
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

@@ -232,10 +239,13 @@ function $InterpolateProvider() {
232239
* - `context`: evaluation context for all expressions embedded in the interpolated text
233240
*/
234241
function $interpolate(text, mustHaveExpression, trustedContext, allOrNothing) {
242+
var hasEscapedMarkers = text.length &&
243+
(text.indexOf(escapedStartSymbol) !== -1 || text.indexOf(escapedEndSymbol) !== -1);
244+
235245
// Provide a quick exit and simplified result function for text with no interpolation
236246
if (!text.length || text.indexOf(startSymbol) === -1) {
237247
var constantInterp;
238-
if (!mustHaveExpression) {
248+
if (!mustHaveExpression || hasEscapedMarkers) {
239249
var unescapedText = unescapeText(text);
240250
constantInterp = valueFn(unescapedText);
241251
constantInterp.exp = text;
@@ -257,8 +267,8 @@ function $InterpolateProvider() {
257267
expressionPositions = [];
258268

259269
while (index < textLength) {
260-
if (((startIndex = text.indexOf(startSymbol, index)) != -1) &&
261-
((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) != -1)) {
270+
if (((startIndex = text.indexOf(startSymbol, index)) !== -1) &&
271+
((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) !== -1)) {
262272
if (index !== startIndex) {
263273
concat.push(unescapeText(text.substring(index, startIndex)));
264274
}
@@ -332,6 +342,12 @@ function $InterpolateProvider() {
332342
});
333343
}
334344
});
345+
} else if (hasEscapedMarkers) {
346+
return extend(valueFn(unescapeText(text)), {
347+
exp: text,
348+
expressions: [],
349+
$$watchDelegate: constantWatchDelegate
350+
});
335351
}
336352

337353
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)