Permalink
Browse files

Now supporting custom escapers

  • Loading branch information...
1 parent 5c2e226 commit 7eb12cb10f32654f3c1a510fb01d08b06c49413e @aaronblohowiak aaronblohowiak committed May 11, 2011
Showing with 143 additions and 30 deletions.
  1. +11 −0 README.markdown
  2. +28 −10 lib/haml.js
  3. +7 −0 test/escaping.haml
  4. +1 −0 test/escaping.html
  5. +13 −0 test/escaping.js
  6. +12 −0 test/other/custom_escape.haml
  7. +1 −0 test/other/custom_escape.html
  8. +70 −20 test/test.js
View
@@ -196,6 +196,8 @@ Please see `test/whitespace.haml` for more examples.
As of version 0.2.0 there is string interpolation throughout. This means that the body of regular text areas can have embedded code. This is true for attributes and the contents of plugins like javascript and markdown also. If you notice an area that doesn't support interpolation and it should then send me a note and I'll add it.
+For interpolation, you may use `#{}` for unescaped interpolation or `!{}` for escaped interpolation.
+
## Plugins
There are plugins in the parser for things like inline script tags, css blocks, and support for if statements and for loops.
@@ -262,6 +264,15 @@ This compiles to the following HTML:
</head><body onload="greet(&quot;I'm Pink&quot;)"> COLOR ME PINK
</body>
+
+## Custom Escaper
+
+By default, Haml(src) returns a completely self-sufficient function, including a nested `html_escape` function. However, repeating the html_escape function definition in each of your templates is going to use more size than necessary. So, you may pass the name of a custom escaper in an optional config variable.
+
+ Haml(src, {customEscaper: "MyApp.esc"})
+
+Then, the output template function definition will call `MyApp.esc(string)` and will omit the `html_escape` function definition. Haml.html_escape exposes the default escape function. If you are going to render your templates in the same context where you compile them (for instance, if you are only rendering them on the server side,) it might make sense to use `Haml(src, {customEscaper: "Haml.html_escape"})`
+
## Get Involved
If you want to use this project and something is missing then send me a message. I'm very busy and have several open source projects I manage. I'll contribute to this project as I have time, but if there is more interest for some particular aspect, I'll work on it a lot faster. Also you're welcome to fork this project and send me patches/pull-requests.
View
@@ -2,7 +2,7 @@ var Haml;
(function () {
- var matchers, self_close_tags, embedder, forceXML;
+ var matchers, self_close_tags, embedder, forceXML, escaperName;
function html_escape(text) {
return (text + "").
@@ -34,7 +34,7 @@ var Haml;
}
result.push(" " + key + '=\\"' + value + '\\"');
} catch (e) {
- result.push(" " + key + '=\\"" + html_escape(' + attribs[key] + ') + "\\"');
+ result.push(" " + key + '=\\"" + '+escaperName+'(' + attribs[key] + ') + "\\"');
}
}
}
@@ -154,6 +154,8 @@ var Haml;
next = match[0].length;
if (next < 0) { break; }
items.push(match[1] || match[2]);
+ //items.push(escaperName+"("+(match[1] || match[2])+")");
+
pos += next;
}
return items.filter(function (part) { return part && part.length > 0}).join(" +\n");
@@ -490,9 +492,9 @@ var Haml;
if (line.substr(0, 2) === "&=") {
line = line.substr(2, line.length).trim();
try {
- return JSON.stringify(html_escape(JSON.parse(line)));
+ return ''+escaperName+'('+JSON.stringify(JSON.parse(line)) +')';
} catch (e2) {
- return 'html_escape(' + line + ')';
+ return ''+escaperName+'(' + line + ')';
}
}
@@ -567,25 +569,41 @@ var Haml;
}).call(self);
};
- Haml = function Haml(haml, xml) {
- forceXML = xml;
- var js = optimize(compile(haml));
+ Haml = function Haml(haml, config) {
+ if(typeof(config) != "object"){
+ forceXML = config;
+ config = {};
+ }
+
+ var escaper;
+ if(config.customEscape){
+ escaper = "";
+ escaperName = config.customEscape;
+ }else{
+ escaper = html_escape.toString() + "\n";
+ escaperName = "html_escape";
+ }
+
+ var js = optimize(compile(haml, config));
var str = "with(locals || {}) {\n" +
" try {\n" +
" var _$output=" + js + ";\n return _$output;" +
" } catch (e) {\n" +
- " return \"\\n<pre class='error'>\" + html_escape(e.stack) + \"</pre>\\n\";\n" +
+ " return \"\\n<pre class='error'>\" + "+escaperName+"(e.stack) + \"</pre>\\n\";\n" +
" }\n" +
"}"
- return new Function("locals", html_escape + "\n" + str );
+
+
+ return new Function("locals", escaper + str );
}
+
Haml.compile = compile;
Haml.optimize = optimize;
Haml.render = render;
Haml.execute = execute;
-
+ Haml.html_escape = html_escape;
}());
// Hook into module system
View
@@ -0,0 +1,7 @@
+%a{apos:apos, amp:amp, carrots:carrots, quo:quo, sol:sol}
+%a{apos:apos, amp:amp, carrots:carrots, quo:quo, sol:sol}<>
+%a{apos:apos, amp:amp, carrots:carrots, quo:quo, sol:sol}&= "<br>"
+%p{apos:apos, amp:amp, carrots:carrots, quo:quo, sol:sol}= "<br>"
+%p<>
+%p<>
+%p(attr0=attr[0] attr1=attr[1])
View
@@ -0,0 +1 @@
+<a apos="'" amp="&amp;" carrots="&lt;&gt;" quo="&quot;" sol="/"></a> <a apos="'" amp="&amp;" carrots="&lt;&gt;" quo="&quot;" sol="/"> </a> <a apos="'" amp="&amp;" carrots="&lt;&gt;" quo="&quot;" sol="/">&lt;br&gt;</a><p apos="'" amp="&amp;" carrots="&lt;&gt;" quo="&quot;" sol="/"><br></p> <p> </p> <p> </p> <p attr0="&quot;&quot;&gt;&lt;SCRIPT&gt;alert(&quot;XSS&quot;)&lt;/SCRIPT&gt;" attr1="javascript:alert(String.fromCharCode(88,83,83)"></p>
View
@@ -0,0 +1,13 @@
+{
+ locals: {
+ apos: "'",
+ amp: '&',
+ carrots: '<>',
+ quo: '"',
+ sol: '/',
+ attr:[
+ '""><SCRIPT>alert("XSS")</SCRIPT>',
+ 'javascript:alert(String.fromCharCode(88,83,83)'
+ ]
+ }
+}
@@ -0,0 +1,12 @@
+%a{apos:apos, amp:amp, carrots:carrots, quo:quo, sol:sol}
+%a{apos:apos, amp:amp, carrots:carrots, quo:quo, sol:sol}<>
+%a{apos:apos, amp:amp, carrots:carrots, quo:quo, sol:sol}&= "<br>"
+%p{apos:apos, amp:amp, carrots:carrots, quo:quo, sol:sol}= "<br>"
+%p<>
+%p<>
+%p(attr0=attr[0] attr1=attr[1])
+
+
+
+%h1(id="#{carrots} blah") How#{apos}s it going #{amp} how are you?
+
@@ -0,0 +1 @@
+<a apos="moo" amp="moo" carrots="moo" quo="moo" sol="moo"></a> <a apos="moo" amp="moo" carrots="moo" quo="moo" sol="moo"> </a> <a apos="moo" amp="moo" carrots="moo" quo="moo" sol="moo">moo</a><p apos="moo" amp="moo" carrots="moo" quo="moo" sol="moo"><br></p> <p> </p> <p> </p> <p attr0="moo" attr1="moo"></p><h1 id="<> blah">How's it going & how are you?</h1>
View
@@ -4,6 +4,38 @@ var sys = require('sys');
var Haml = require("../lib/haml");
+
+
+function compare(haml_file, haml, expected, scope, options){
+ options || (options = {});
+ try {
+ sys.puts(haml_file + " Begun")
+ var js = Haml.compile(haml);
+ var js_opt = Haml.optimize(js);
+ var jsFn = Haml(haml, options);
+ var actual = jsFn.call(scope.context, scope.locals);
+ assert.equal(actual, expected);
+ sys.puts(haml_file + " Passed")
+
+ } catch (e) {
+ var message = e.name;
+ if (e.message) { message += ": " + e.message; }
+ sys.error(haml_file + " FAILED")
+ sys.error(message);
+ sys.error("\nJS:\n\n" + js);
+ sys.error("\nOptimized JS:\n\n" + js_opt);
+ sys.error("\nJS fn:\n\n"+jsFn.toString());
+ sys.error("\nStack:\n\n"+e.stack);
+ try{
+ sys.error("\nActual["+actual.length+"]:\n\n" + actual);
+ sys.error("\nExpected["+expected.length+"]:\n\n" + expected);
+ }catch(e2){}
+
+ process.exit();
+ }
+}
+
+
fs.readdir('.', function (err, files) {
files.forEach(function (haml_file) {
var m = haml_file.match(/^(.*)\.haml/),
@@ -16,26 +48,7 @@ fs.readdir('.', function (err, files) {
function load_haml(scope) {
fs.readFile(haml_file, "utf8", function (err, haml) {
fs.readFile(base + ".html", "utf8", function (err, expected) {
- try {
- sys.puts(haml_file + " Begun")
- var js = Haml.compile(haml);
- var js_opt = Haml.optimize(js);
- var actual = Haml(haml).call(scope.context, scope.locals);
- assert.equal(actual, expected);
-
- sys.puts(haml_file + " Passed")
- } catch (e) {
- var message = e.name;
- if (e.message) { message += ": " + e.message; }
- sys.error(haml_file + " FAILED")
- sys.error(message);
- sys.error(e.stack);
- sys.error("\nJS:\n\n" + js);
- sys.error("\nOptimized JS:\n\n" + js_opt);
- sys.error("\nActual:\n\n" + actual);
- sys.error("\nExpected:\n\n" + expected);
- process.exit();
- }
+ compare(haml_file, haml, expected, scope)
});
});
}
@@ -51,4 +64,41 @@ fs.readdir('.', function (err, files) {
});
});
+(function(){
+ var hamlSrc = fs.readFileSync("alt_attribs.haml", "utf8");
+ var includeEscape = Haml(hamlSrc).toString();
+ var customEscape = Haml(hamlSrc, {customEscape:"$esc"}).toString();
+ try{
+ assert.ok(customEscape.length < includeEscape.length);
+ }catch(e){
+ sys.error(e.stack);
+ sys.error(customEscape);
+ process.exit();
+ }
+})();
+
+
+(function(){
+ var hamlSrc = fs.readFileSync("./other/custom_escape.haml", "utf8");
+ var expected = fs.readFileSync("./other/custom_escape.html", "utf8");
+ var scope = eval("(" + fs.readFileSync("escaping.js") + ")");
+
+ sys.puts("custom_escape" + " Begun")
+ var jsFn = Haml(hamlSrc, {customEscape:"$esc"});
+
+ this.$esc = function(){
+ return "moo"
+ };
+
+ var actual = jsFn.call(scope.context, scope.locals);
+ try{
+ assert.equal(actual, expected);
+ }catch(e){
+ sys.error("\nActual["+actual.length+"]:\n\n" + actual);
+ sys.error("\nExpected["+expected.length+"]:\n\n" + expected);
+ process.exit();
+ }
+ sys.puts("custom_escape" + " Passed")
+
+})();

0 comments on commit 7eb12cb

Please sign in to comment.