Skip to content
Browse files

BREAKING: default interpolation behavior is now to escape!

Added ability to have = escape output by default, added != unescaped output indicator.
  • Loading branch information...
1 parent 063907e commit 0a168dbaed770a3b193f87c9c86e2d5e80c56af7 @aaronblohowiak aaronblohowiak committed May 10, 2011
Showing with 99 additions and 15 deletions.
  1. +25 −0 README.markdown
  2. +34 −13 lib/haml.js
  3. +1 −1 test/embedded_code.haml
  4. +1 −1 test/interpolation.html
  5. +8 −0 test/other/escape_by_default.haml
  6. +1 −0 test/other/escape_by_default.html
  7. +29 −0 test/test.js
View
25 README.markdown
@@ -198,6 +198,31 @@ As of version 0.2.0 there is string interpolation throughout. This means that t
For interpolation, you may use `#{}` for escaped interpolation or `!{}` for unsafe interpolation.
+## Html Escaping / Santizer
+
+You probably don't want to put unescaped user input right into your html. http://xkcd.com/327/ HTML/XSS sanitization is the new "Bobby Tables."
+
+Let's assume we have a malicious username: `name = "<script>...</script>"`
+
+Always unsafe:
+
+ %span!= name
+
+ <span><script>...</script></span>
+
+Always safe:
+
+ %span&= name
+ <span>&lt;script&gt;...&lt;/script&gt;</span>
+
+Sometimes safe:
+
+ %span= name
+
+The behavior of `=` depends on the setting of escapeHtml configuration variable. To make `=` safe, call Haml like this:
+
+ Haml(src, {escapeHtmlByDefault: true})
+
## Plugins
There are plugins in the parser for things like inline script tags, css blocks, and support for if statements and for loops.
View
47 lib/haml.js
@@ -2,7 +2,7 @@ var Haml;
(function () {
- var matchers, self_close_tags, embedder, forceXML, escaperName;
+ var matchers, self_close_tags, embedder, forceXML, escaperName, escapeHtmlByDefault;
function html_escape(text) {
return (text + "").
@@ -482,23 +482,42 @@ var Haml;
return parse_interpol(line.substr(1, line.length));
}
- // Plain variable data
- if (line[0] === '=') {
- line = line.substr(1, line.length).trim();
+
+ function escapedLine(){
+ try {
+ return escaperName+'('+JSON.stringify(JSON.parse(line)) +')';
+ } catch (e2) {
+ return escaperName+'(' + line + ')';
+ }
+ }
+
+ function unescapedLine(){
try {
return parse_interpol(JSON.parse(line));
} catch (e) {
return line;
}
}
-
- // HTML escape variable data
- if (line.substr(0, 2) === "&=") {
+
+ // always escaped
+ if((line.substr(0, 2) === "&=")) {
line = line.substr(2, line.length).trim();
- try {
- return ''+escaperName+'('+JSON.stringify(JSON.parse(line)) +')';
- } catch (e2) {
- return ''+escaperName+'(' + line + ')';
+ return escapedLine();
+ }
+
+ //never escaped
+ if((line.substr(0, 2) === "!=")) {
+ line = line.substr(2, line.length).trim();
+ return unescapedLine();
+ }
+
+ // sometimes escaped
+ if ( (line[0] === '=')) {
+ line = line.substr(1, line.length).trim();
+ if(escapeHtmlByDefault){
+ return escapedLine();
+ }else{
+ return unescapedLine();
}
}
@@ -551,7 +570,7 @@ var Haml;
function render(text, options) {
options = options || {};
text = text || "";
- var js = compile(text);
+ var js = compile(text, options);
if (options.optimize) {
js = Haml.optimize(js);
}
@@ -588,7 +607,9 @@ var Haml;
escaperName = "html_escape";
}
- var js = optimize(compile(haml, config));
+ escapeHtmlByDefault = (config.escapeHtmlByDefault || config.escapeHTML || config.escape_html);
+
+ var js = optimize(compile(haml));
var str = "with(locals || {}) {\n" +
" try {\n" +
View
2 test/embedded_code.haml
@@ -1,6 +1,6 @@
%head
:javascript
- Page.chapter = #{JSON.stringify(chapter)};
+ Page.chapter = !{JSON.stringify(chapter)};
%body
%h1 Welcome #{name}
%div{class: "div_#{id}"}
View
2 test/interpolation.html
@@ -1 +1 @@
-<p id="p-&amp;">Well then &lt;&gt;</p><p id="p-&">Well then <></p>This is some text &amp; it is cool. <br/> i am doing fun stuff!<p src=""<br/>""></p>
+<p id="p-&amp;">Well then &lt;&gt;</p><p id="p-&">Well then <></p>This is some text &amp; it is cool. <br/> i am doing fun stuff!<p src=""<br/>""></p>
View
8 test/other/escape_by_default.haml
@@ -0,0 +1,8 @@
+- tag = "<script> bad things</script>";
+
+.safe<>= tag
+.safe>&= tag
+.safe<&= tag
+.unsafe<!= tag
+.unsafe<>!= tag
+.unsafe!= tag
View
1 test/other/escape_by_default.html
@@ -0,0 +1 @@
+ <div class="safe"> &lt;script&gt; bad things&lt;/script&gt; </div> <div class="safe">&lt;script&gt; bad things&lt;/script&gt;</div> <div class="safe"> &lt;script&gt; bad things&lt;/script&gt; </div><div class="unsafe"> <script> bad things</script> </div> <div class="unsafe"> <script> bad things</script> </div> <div class="unsafe"><script> bad things</script></div>
View
29 test/test.js
@@ -102,3 +102,32 @@ fs.readdir('.', function (err, files) {
})();
+
+(function(){
+ var hamlSrc = fs.readFileSync("./other/escape_by_default.haml", "utf8");
+ var expected = fs.readFileSync("./other/escape_by_default.html", "utf8");
+ var scope = {};
+
+ sys.puts("escape_by_default" + " Begun")
+ var js = Haml.compile(hamlSrc);
+ sys.error(js);
+
+
+ var jsFn = Haml(hamlSrc, {escapeHtmlByDefault:true});
+
+ 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("escape_by_default" + " Passed")
+
+})();
+

0 comments on commit 0a168db

Please sign in to comment.
Something went wrong with that request. Please try again.