Permalink
Browse files

Implemented escaping

  • Loading branch information...
1 parent a59f589 commit ee90dee41e226ee925f6dffb4ff8d6a2c0b5d85f Anders Hellerup Madsen committed Jan 17, 2010
Showing with 190 additions and 42 deletions.
  1. +22 −5 template/template.js
  2. +26 −0 template/template.test.js
  3. +62 −30 template/template_defaults.js
  4. +80 −7 template/template_defaults.test.js
View
@@ -165,9 +165,10 @@ function normalize(value) {
/*************** Context *********************************/
-function Context(o, blockmark) {
- this.scope = [ o ];
+function Context(o) {
+ this.scope = [ o || {} ];
this.blocks = {};
+ this.autoescaping = true;
}
process.mixin(Context.prototype, {
@@ -277,16 +278,32 @@ process.mixin(FilterExpression.prototype, {
value = context.get(this.variable);
}
- return this.filter_list.reduce( function (p,c) {
+ var safety = {
+ is_safe: false,
+ must_escape: context.autoescaping,
+ };
+
+ var out = this.filter_list.reduce( function (p,c) {
+
var filter = template_defaults.filters[c.name];
+
if ( filter && typeof filter === 'function') {
- return filter(p, c.arg);
+ return filter(p, c.arg, safety);
} else {
// throw 'Cannot find filter';
sys.debug('Cannot find filter ' + c.name);
- return value;
+ return p;
}
}, value);
+
+ if (safety.must_escape && !safety.is_safe) {
+ if (typeof out === 'string') {
+ return utils.html.escape(out)
+ } else if (out instanceof Array) {
+ return out.map( utils.html.escape );
+ }
+ }
+ return out;
}
});
View
@@ -68,6 +68,32 @@ testcase('Filter Expression tests');
shouldThrow(attempt, 'item|add:2 |sub');
});
+ test('output (without filters) should be escaped if autoescaping is on', function () {
+ var context = new Context({test: '<script>'});
+ context.autoescaping = true;
+ var expr = new FilterExpression("test");
+ assertEquals('&lt;script&gt;', expr.resolve(context));
+ });
+
+ test('output (without filters) should not be escaped if autoescaping is off', function () {
+ var context = new Context({test: '<script>'});
+ context.autoescaping = false;
+ var expr = new FilterExpression("test");
+ assertEquals('<script>', expr.resolve(context));
+ });
+ test('safe filter should prevent escaping', function () {
+ var context = new Context({test: '<script>'});
+ context.autoescaping = true;
+ var expr = new FilterExpression("test|safe|upper");
+ assertEquals('<SCRIPT>', expr.resolve(context));
+ });
+ test('escape filter should force escaping', function () {
+ var context = new Context({test: '<script>'});
+ context.autoescaping = false;
+ var expr = new FilterExpression("test|escape|upper");
+ assertEquals('&lt;SCRIPT&gt;', expr.resolve(context));
+ });
+
testcase('Context test');
setup( function () {
var tc = {
@@ -85,9 +85,9 @@ var filters = exports.filters = {
},
divisibleby: function (value, arg) { return value % arg === 0; },
- escape: function (value, arg) {
- // TODO: Implement escaping/autoescaping correctly
- throw "escape() filter is not implemented";
+ escape: function (value, arg, safety) {
+ safety.must_escape = true;
+ return value;
},
escapejs: function (value, arg) { return escape(value || ''); },
filesizeformat: function (value, arg) {
@@ -100,8 +100,10 @@ var filters = exports.filters = {
return (bytes / (1024 * 1024 * 1024)).toFixed(1) + 'GB';
},
first: function (value, arg) { return (value instanceof Array) ? value[0] : ""; },
- fix_ampersands: function (value, arg) { return ("" + value).replace('&', '&amp;'); },
-
+ fix_ampersands: function (value, arg, safety) {
+ safety.is_safe = true;
+ return ("" + value).replace('&', '&amp;');
+ },
floatformat: function (value, arg) {
arg = arg || -1;
var num = value - 0,
@@ -116,7 +118,10 @@ var filters = exports.filters = {
}
return s;
},
- force_escape: function (value, arg) { return utils.html.escape("" + value); },
+ force_escape: function (value, arg, safety) {
+ safety.is_safe = true;
+ return utils.html.escape("" + value);
+ },
get_digit: function (value, arg) {
if (typeof value !== 'number' || typeof arg !== 'number' || typeof arg < 1) { return value; }
var s = "" + value;
@@ -128,16 +133,36 @@ var filters = exports.filters = {
},
join: function (value, arg) { return (value instanceof Array) ? value.join(arg) : ''; },
last: function (value, arg) { return ((value instanceof Array) && value.length) ? value[value.length - 1] : ''; },
- length: function (value, arg) { return value.hasOwnProperty('length') ? value.length : 0; },
- length_is: function (value, arg) { return value.hasOwnProperty('length') && value.length === arg; },
- linebreaks: function (value, arg) { return utils.html.linebreaks("" + value); },
- linebreaksbr: function (value, arg) { return "" + value.replace(/\n/g, '<br />'); },
- linenumbers: function (value, arg) {
+ length: function (value, arg) { return value.length ? value.length : 0; },
+ length_is: function (value, arg) { return value.length === arg; },
+ linebreaks: function (value, arg, safety) {
+ if (!safety.is_safe && safety.must_escape) {
+ value = utils.html.escape("" + value);
+ }
+ safety.is_safe = true;
+ return utils.html.linebreaks("" + value);
+ },
+ linebreaksbr: function (value, arg, safety) {
+ if (!safety.is_safe && safety.must_escape) {
+ value = utils.html.escape("" + value);
+ }
+ safety.is_safe = true;
+ return "" + value.replace(/\n/g, '<br />');
+ },
+ linenumbers: function (value, arg, safety) {
var lines = String(value).split('\n');
var len = String(lines.length).length;
- return lines
- .map(function (s, idx) { return utils.string.sprintf('%0' + len + 'd. %s', idx + 1, s); })
+
+ // TODO: escape if string is not safe, and autoescaping is active
+ var out = lines
+ .map(function (s, idx) {
+ if (!safety.is_safe && safety.must_escape) {
+ s = utils.html.escape("" + s);
+ }
+ return utils.string.sprintf('%0' + len + 'd. %s', idx + 1, s); })
.join('\n');
+ safety.is_safe = true;
+ return out;
},
ljust: function (value, arg) {
try {
@@ -165,11 +190,12 @@ var filters = exports.filters = {
},
pprint: function (value, arg) { return JSON.stringify(value); },
random: function (value, arg) {
- return (value instanceof Array) ? value[ Math.floor( Math.random() * 4 ) ] : '';
+ return (value instanceof Array) ? value[ Math.floor( Math.random() * value.length ) ] : '';
},
- removetags: function (value, arg) {
+ removetags: function (value, arg, safety) {
arg = String(arg).replace(/\s+/g, '|');
var re = new RegExp( '</?\\s*(' + arg + ')\\b[^>]*/?>', 'ig');
+ safety.is_safe = true;
return String(value).replace(re, '');
},
rjust: function (value, arg) {
@@ -179,13 +205,13 @@ var filters = exports.filters = {
return '';
}
},
- safe: function (value, arg) {
- // TODO: implement autoescaping
- throw "safe is not implemented";
+ safe: function (value, arg, safety) {
+ safety.is_safe = true;
+ return value;
},
safeseq: function (value, arg) {
- // TODO: implement autoescaping
- throw "safeseq is not implemented";
+ safety.is_safe = true;
+ return value;
},
slice: function (value, arg) {
if (!(value instanceof Array)) { return []; }
@@ -212,7 +238,8 @@ var filters = exports.filters = {
stringformat: function (value, arg) {
try { return utils.string.sprintf('%' + arg, value); } catch (e) { return ''; }
},
- striptags: function (value, arg) {
+ striptags: function (value, arg, safety) {
+ safety.is_safe = true;
return String(value).replace(/<(.|\n)*?>/g, '');
},
title: function (value, arg) {
@@ -236,7 +263,6 @@ var nodes = exports.nodes = {
return function () { return text; };
},
-
VariableNode: function (filterexpression) {
return function (context) {
return filterexpression.resolve(context);
@@ -334,15 +360,17 @@ var nodes = exports.nodes = {
// put this block in front of list
context.blocks[name].unshift( node_list );
- context.push();
+ // if this is a root template descend through templates and evaluate blocks for overrides
+ if (!context.extends) {
+ context.push();
- // descend through templates and evaluate for overrides
- context.blocks[name].forEach( function (list) {
- out = list.evaluate( context );
- context.set('block', { super: out });
- });
+ context.blocks[name].forEach( function (list) {
+ out = list.evaluate( context );
+ context.set('block', { super: out });
+ });
- context.pop();
+ context.pop();
+ }
return out;
};
@@ -490,10 +518,14 @@ var callbacks = exports.callbacks = {
'extends': function (parser, token) {
var parts = template.split_token(token.contents);
- if (parts[0] !== 'extends' || parts.length !== 2) { throw 'unexpected syntax in "block" tag'; }
+ if (parts[0] !== 'extends' || parts.length !== 2) { throw 'unexpected syntax in "extends" tag'; }
var name = parts[1];
return nodes.ExtendsNode(name);
}
};
+
+
+
+
Oops, something went wrong.

0 comments on commit ee90dee

Please sign in to comment.