Skip to content
This repository has been archived by the owner on Apr 12, 2024. It is now read-only.

feat($interpolate): escape interpolated expressions #7496

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 31 additions & 7 deletions src/ng/interpolate.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ var $interpolateMinErr = minErr('$interpolate');
function $InterpolateProvider() {
var startSymbol = '{{';
var endSymbol = '}}';
var escapedStartSymbol = '{{{{';
var escapedEndSymbol = '}}}}';

/**
* @ngdoc method
Expand All @@ -49,11 +51,15 @@ function $InterpolateProvider() {
* Symbol to denote start of expression in the interpolated string. Defaults to `{{`.
*
* @param {string=} value new value to set the starting symbol to.
* @param {string=} escaped new value to set the escaped starting symbol to.
* @returns {string|self} Returns the symbol when used as getter and self if used as setter.
*/
this.startSymbol = function(value){
this.startSymbol = function(value, escaped) {
if (value) {
startSymbol = value;
if (escaped) {
escapedStartSymbol = escaped;
}
return this;
} else {
return startSymbol;
Expand All @@ -67,11 +73,15 @@ function $InterpolateProvider() {
* Symbol to denote the end of expression in the interpolated string. Defaults to `}}`.
*
* @param {string=} value new value to set the ending symbol to.
* @param {string=} escaped new value to set the escaped ending symbol to.
* @returns {string|self} Returns the symbol when used as getter and self if used as setter.
*/
this.endSymbol = function(value){
this.endSymbol = function(value, escaped) {
if (value) {
endSymbol = value;
if (escaped) {
escapedEndSymbol = escaped;
}
return this;
} else {
return endSymbol;
Expand All @@ -81,7 +91,9 @@ function $InterpolateProvider() {

this.$get = ['$parse', '$exceptionHandler', '$sce', function($parse, $exceptionHandler, $sce) {
var startSymbolLength = startSymbol.length,
endSymbolLength = endSymbol.length;
endSymbolLength = endSymbol.length,
escapedStartLength = escapedStartSymbol.length,
escapedStartOffset = escapedStartSymbol.indexOf(startSymbol);

/**
* @ngdoc service
Expand Down Expand Up @@ -157,10 +169,17 @@ function $InterpolateProvider() {
lastValuesCache = { values: {}, results: {}};

while(index < textLength) {
if ( ((startIndex = text.indexOf(startSymbol, index)) != -1) &&
((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) != -1) ) {
var i = index;
do {
startIndex = text.indexOf(startSymbol, i);
i = startIndex - escapedStartOffset + escapedStartLength;
} while (escapedStartOffset !== -1 && startIndex !== -1 &&
text.slice(startIndex - escapedStartOffset, i) === escapedStartSymbol);

if (startIndex !== -1 &&
(endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) != -1) {
if (index !== startIndex) hasText = true;
separators.push(text.substring(index, startIndex));
separators.push(unescape(text.substring(index, startIndex)));
exp = text.substring(startIndex + startSymbolLength, endIndex);
expressions.push(exp);
parseFns.push($parse(exp));
Expand All @@ -170,7 +189,7 @@ function $InterpolateProvider() {
// we did not find an interpolation, so we have to add the remainder to the separators array
if (index !== textLength) {
hasText = true;
separators.push(text.substring(index));
separators.push(unescape(text.substring(index)));
}
break;
}
Expand Down Expand Up @@ -316,6 +335,11 @@ function $InterpolateProvider() {
return endSymbol;
};

function unescape(text) {
return text.split(escapedStartSymbol).join(startSymbol)
.split(escapedEndSymbol).join(endSymbol);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm pretty frustrated with the split/join thing. The alternative is to either write a simple replaceAll function or use some ugly regexp escaping function so that we can replace(new RegExp(escapedStartRegexp, 'g'), startSymbol)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, I think escapedEndSymbol is pretty useless, maybe we can just remove it... (we can have a escapedStartSymbol method instead of adding the escaped param to the startSymbol method, seems more elegant in my opinion since currently startSymbol method is a kind of weird "set two things or get one thing")

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

then you'd render the wrong thing, anyways whatever, there's enough comments, take a break

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean we could say that the server needs to escape only the start symbol. What do you mean by there's enough comments?

}

return $interpolate;
}];
}
Expand Down
40 changes: 40 additions & 0 deletions test/ng/interpolateSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,46 @@ describe('$interpolate', function() {
expect($interpolate("Hello, world!{{bloop}}")()).toBe("Hello, world!");
}));

describe('interpolation escaping', function() {
var obj;

beforeEach(function() {
obj = {foo: 'Hello', bar: 'World'};
});

it('should support escaping interpolation signs', inject(function($interpolate) {
expect($interpolate('{{foo}} {{{{bar}}}}')(obj)).toBe('Hello {{bar}}');
expect($interpolate('{{{{foo}}}} {{bar}}')(obj)).toBe('{{foo}} World');
}));

it('should unescape multiple expressions', inject(function($interpolate) {
expect($interpolate('{{{{foo}}}}{{{{bar}}}} {{foo}}')(obj)).toBe('{{foo}}{{bar}} Hello');
}));

it('should not really care about end symbols when escaping', inject(function($interpolate) {
expect($interpolate('{{{{foo{{foo}}')(obj)).toBe('{{fooHello');
}));

it('should support customizing escape signs', function() {
module(function($interpolateProvider) {
$interpolateProvider.startSymbol('{{', '[[');
$interpolateProvider.endSymbol('}}', ']]');
});
inject(function($interpolate) {
expect($interpolate('{{foo}} [[bar]]')(obj)).toBe('Hello {{bar}}');
});
});

it('should support customizing escape signs which contain interpolation signs', function() {
module(function($interpolateProvider) {
$interpolateProvider.startSymbol('{{', '-{{-');
$interpolateProvider.endSymbol('}}', '-}}-');
});
inject(function($interpolate) {
expect($interpolate('{{foo}} -{{-bar-}}-')(obj)).toBe('Hello {{bar}}');
});
});
});

describe('interpolating in a trusted context', function() {
var sce;
Expand Down