Skip to content

Commit

Permalink
feat($interpolate): escaped interpolation expressions
Browse files Browse the repository at this point in the history
This CL enables interpolation expressions to be escaped, by prefixing each character of their
start/end markers with a REVERSE SOLIDUS U+005C, and to render the escaped expression as a
regular interpolation expression.

Example:

`<span ng-init="foo='Hello'">{{foo}}, \\{\\{World!\\}\\}</span>` would be rendered as:
`<span ng-init="foo='Hello'">Hello, {{World!}}</span>`

This will also work with custom interpolation markers, for example:

     module.
       config(function($interpolateProvider) {
         $interpolateProvider.startSymbol('\\\\');
         $interpolateProvider.endSymbol('//');
       }).
       run(function($interpolate) {
         // Will alert with "hello\\bar//":
         alert($interpolate('\\\\foo//\\\\\\\\bar\\/\\/')({foo: "hello", bar: "world"}));
       });

This change effectively only changes the rendering of these escaped markers, because they are
not context-aware, and are incapable of preventing nested expressions within those escaped
markers from being evaluated.

Therefore, backends are encouraged to ensure that when escaping expressions for security
reasons, every single instance of a start or end marker have each of its characters prefixed
with a backslash (REVERSE SOLIDUS, U+005C)

Closes angular#5601
  • Loading branch information
caitp committed May 20, 2014
1 parent 95cdb53 commit f124c04
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 1 deletion.
16 changes: 15 additions & 1 deletion src/ng/interpolate.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,15 @@ function $InterpolateProvider() {

this.$get = ['$parse', '$exceptionHandler', '$sce', function($parse, $exceptionHandler, $sce) {
var startSymbolLength = startSymbol.length,
endSymbolLength = endSymbol.length;
endSymbolLength = endSymbol.length,
escapedStartSymbol = startSymbol.replace(/./g, escape),
escapedEndSymbol = endSymbol.replace(/./g, escape),
escapedStartRegexp = new RegExp(escapedStartSymbol.replace(/./g, escape), 'g'),
escapedEndRegexp = new RegExp(escapedEndSymbol.replace(/./g, escape), 'g');

function escape(ch) {
return '\\' + ch;
}

/**
* @ngdoc service
Expand Down Expand Up @@ -180,6 +188,12 @@ function $InterpolateProvider() {
separators.push('');
}

forEach(separators, function(key, i) {
separators[i] = separators[i].
replace(escapedStartRegexp, startSymbol).
replace(escapedEndRegexp, endSymbol);
});

// Concatenating expressions makes it hard to reason about whether some combination of
// concatenated values are unsafe to use and could easily lead to XSS. By requiring that a
// single expression be used for iframe[src], object[src], etc., we ensure that the value
Expand Down
41 changes: 41 additions & 0 deletions test/ng/interpolateSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,47 @@ describe('$interpolate', function() {
}));


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');
expect($interpolate('{{foo}}\\{\\{foo\\}\\}\\{\\{bar\\}\\}')(obj)).toBe('Hello{{foo}}{{bar}}');
expect($interpolate('\\{\\{foo\\}\\}{{foo}}\\{\\{bar\\}\\}')(obj)).toBe('{{foo}}Hello{{bar}}');
expect($interpolate('{{foo}}\\{\\{foo\\}\\}{{bar}}\\{\\{bar\\}\\}{{foo}}')(obj)).toBe('Hello{{foo}}World{{bar}}Hello');
}));


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 unescape incomplete escaped expressions', inject(function($interpolate) {
expect($interpolate('\\{\\{foo{{foo}}')(obj)).toBe('{{fooHello');
expect($interpolate('\\}\\}foo{{foo}}')(obj)).toBe('}}fooHello');
expect($interpolate('foo{{foo}}\\{\\{')(obj)).toBe('fooHello{{');
expect($interpolate('foo{{foo}}\\}\\}')(obj)).toBe('fooHello}}');
}));
});


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

0 comments on commit f124c04

Please sign in to comment.