From 83f8b20ea531f85368545434149eda35fda80d2b Mon Sep 17 00:00:00 2001 From: Nathan Weizenbaum Date: Fri, 16 Oct 2009 18:31:04 -0700 Subject: [PATCH] [Haml] Fix a bug with interpolation and HTML escaping. Closes gh-44 --- doc-src/HAML_CHANGELOG.md | 13 ++++++++++++ lib/haml/precompiler.rb | 38 ++++++++++++++++++++------------- test/haml/engine_test.rb | 44 +++++++++++++++++++++------------------ 3 files changed, 61 insertions(+), 34 deletions(-) diff --git a/doc-src/HAML_CHANGELOG.md b/doc-src/HAML_CHANGELOG.md index 4d592cf1..1c3a9b23 100644 --- a/doc-src/HAML_CHANGELOG.md +++ b/doc-src/HAML_CHANGELOG.md @@ -9,6 +9,19 @@ under certain circumstances. This was mostly an issue under Rails when using methods like `capture`. +* Fixed a bug where template text was escaped when there was interpolation in a line + and the `:escape_html` option was enabled. For example: + + Foo < Bar #{"<"} Baz + + with `:escape_html` used to render as + + Foo &lt; Bar < Baz + + but now renders as + + Foo < Bar < Baz + ## [2.2.8](http://github.com/nex3/haml/commit/2.2.8) * Fixed a potential XSS issue with HTML escaping and wacky Unicode nonsense. diff --git a/lib/haml/precompiler.rb b/lib/haml/precompiler.rb index 3dd1e8a3..0aadd865 100644 --- a/lib/haml/precompiler.rb +++ b/lib/haml/precompiler.rb @@ -329,7 +329,10 @@ END end if contains_interpolation?(text) - push_script unescape_interpolation(text), :escape_html => options[:escape_html] + options[:escape_html] = self.options[:escape_html] if options[:escape_html].nil? + push_script( + unescape_interpolation(text, :escape_html => options[:escape_html]), + :escape_html => false) else push_text text end @@ -665,30 +668,37 @@ END nuke_inner_whitespace ||= preserve_tag preserve_tag &&= !options[:ugly] + escape_html = (action == '&' || (action != '!' && @options[:escape_html])) + case action when '/'; self_closing = true when '~'; parse = preserve_script = true when '=' parse = true - value = unescape_interpolation(value[1..-1].strip) if value[0] == ?= + if value[0] == ?= + value = unescape_interpolation(value[1..-1].strip, :escape_html => escape_html) + escape_html = false + end when '&', '!' if value[0] == ?= || value[0] == ?~ parse = true preserve_script = (value[0] == ?~) - value = - if value[1] == ?= - unescape_interpolation(value[2..-1].strip) - else - value[1..-1].strip - end + if value[1] == ?= + value = unescape_interpolation(value[2..-1].strip, :escape_html => escape_html) + escape_html = false + else + value = value[1..-1].strip + end elsif contains_interpolation?(value) + value = unescape_interpolation(value, :escape_html => escape_html) parse = true - value = unescape_interpolation(value) + escape_html = false end else if contains_interpolation?(value) + value = unescape_interpolation(value, :escape_html => escape_html) parse = true - value = unescape_interpolation(value) + escape_html = false end end @@ -697,8 +707,6 @@ END value = '' end - escape_html = (action == '&' || (action != '!' && @options[:escape_html])) - object_ref = "nil" if object_ref.nil? || @options[:suppress_eval] attributes = parse_class_and_id(attributes) @@ -936,7 +944,7 @@ END str.include?('#{') end - def unescape_interpolation(str) + def unescape_interpolation(str, opts = {}) res = '' rest = Haml::Shared.handle_interpolation str.dump do |scan| escapes = (scan[2].size - 1) / 2 @@ -944,7 +952,9 @@ END if escapes % 2 == 1 res << '#{' else - res << '#{' + eval('"' + balance(scan, ?{, ?}, 1)[0][0...-1] + '"') + "}"# Use eval to get rid of string escapes + content = eval('"' + balance(scan, ?{, ?}, 1)[0][0...-1] + '"') + content = "Haml::Helpers.html_escape(#{content})" if opts[:escape_html] + res << '#{' + content + "}"# Use eval to get rid of string escapes end end res + rest diff --git a/test/haml/engine_test.rb b/test/haml/engine_test.rb index 0e78a98f..d14688a9 100644 --- a/test/haml/engine_test.rb +++ b/test/haml/engine_test.rb @@ -163,6 +163,10 @@ class EngineTest < Test::Unit::TestCase assert_equal("

\n 2\n

\n", render("%p\n \#{1 + 1}")) end + def test_escaped_interpolation + assert_equal("

Foo & Bar & Baz

\n", render('%p& Foo #{"&"} Bar & Baz')) + end + def test_nil_tag_value_should_render_as_empty assert_equal("

\n", render("%p= nil")) end @@ -598,53 +602,53 @@ HAML end def test_string_double_equals_should_be_esaped - assert_equal("

4&3

\n", render("%p== \#{2+2}&\#{2+1}", :escape_html => true)) - assert_equal("

4&3

\n", render("%p== \#{2+2}&\#{2+1}", :escape_html => false)) + assert_equal("

4&<

\n", render("%p== \#{2+2}&\#{'<'}", :escape_html => true)) + assert_equal("

4&<

\n", render("%p== \#{2+2}&\#{'<'}", :escape_html => false)) end def test_escaped_inline_string_double_equals - assert_equal("

4&3

\n", render("%p&== \#{2+2}&\#{2+1}", :escape_html => true)) - assert_equal("

4&3

\n", render("%p&== \#{2+2}&\#{2+1}", :escape_html => false)) + assert_equal("

4&<

\n", render("%p&== \#{2+2}&\#{'<'}", :escape_html => true)) + assert_equal("

4&<

\n", render("%p&== \#{2+2}&\#{'<'}", :escape_html => false)) end def test_unescaped_inline_string_double_equals - assert_equal("

4&3

\n", render("%p!== \#{2+2}&\#{2+1}", :escape_html => true)) - assert_equal("

4&3

\n", render("%p!== \#{2+2}&\#{2+1}", :escape_html => false)) + assert_equal("

4&<

\n", render("%p!== \#{2+2}&\#{'<'}", :escape_html => true)) + assert_equal("

4&<

\n", render("%p!== \#{2+2}&\#{'<'}", :escape_html => false)) end def test_escaped_string_double_equals - assert_equal("

\n 4&3\n

\n", render("%p\n &== \#{2+2}&\#{2+1}", :escape_html => true)) - assert_equal("

\n 4&3\n

\n", render("%p\n &== \#{2+2}&\#{2+1}", :escape_html => false)) + assert_equal("

\n 4&<\n

\n", render("%p\n &== \#{2+2}&\#{'<'}", :escape_html => true)) + assert_equal("

\n 4&<\n

\n", render("%p\n &== \#{2+2}&\#{'<'}", :escape_html => false)) end def test_unescaped_string_double_equals - assert_equal("

\n 4&3\n

\n", render("%p\n !== \#{2+2}&\#{2+1}", :escape_html => true)) - assert_equal("

\n 4&3\n

\n", render("%p\n !== \#{2+2}&\#{2+1}", :escape_html => false)) + assert_equal("

\n 4&<\n

\n", render("%p\n !== \#{2+2}&\#{'<'}", :escape_html => true)) + assert_equal("

\n 4&<\n

\n", render("%p\n !== \#{2+2}&\#{'<'}", :escape_html => false)) end def test_string_interpolation_should_be_esaped - assert_equal("

4&3

\n", render("%p \#{2+2}&\#{2+1}", :escape_html => true)) - assert_equal("

4&3

\n", render("%p \#{2+2}&\#{2+1}", :escape_html => false)) + assert_equal("

4&<

\n", render("%p \#{2+2}&\#{'<'}", :escape_html => true)) + assert_equal("

4&<

\n", render("%p \#{2+2}&\#{'<'}", :escape_html => false)) end def test_escaped_inline_string_interpolation - assert_equal("

4&3

\n", render("%p& \#{2+2}&\#{2+1}", :escape_html => true)) - assert_equal("

4&3

\n", render("%p& \#{2+2}&\#{2+1}", :escape_html => false)) + assert_equal("

4&<

\n", render("%p& \#{2+2}&\#{'<'}", :escape_html => true)) + assert_equal("

4&<

\n", render("%p& \#{2+2}&\#{'<'}", :escape_html => false)) end def test_unescaped_inline_string_interpolation - assert_equal("

4&3

\n", render("%p! \#{2+2}&\#{2+1}", :escape_html => true)) - assert_equal("

4&3

\n", render("%p! \#{2+2}&\#{2+1}", :escape_html => false)) + assert_equal("

4&<

\n", render("%p! \#{2+2}&\#{'<'}", :escape_html => true)) + assert_equal("

4&<

\n", render("%p! \#{2+2}&\#{'<'}", :escape_html => false)) end def test_escaped_string_interpolation - assert_equal("

\n 4&3\n

\n", render("%p\n & \#{2+2}&\#{2+1}", :escape_html => true)) - assert_equal("

\n 4&3\n

\n", render("%p\n & \#{2+2}&\#{2+1}", :escape_html => false)) + assert_equal("

\n 4&<\n

\n", render("%p\n & \#{2+2}&\#{'<'}", :escape_html => true)) + assert_equal("

\n 4&<\n

\n", render("%p\n & \#{2+2}&\#{'<'}", :escape_html => false)) end def test_unescaped_string_interpolation - assert_equal("

\n 4&3\n

\n", render("%p\n ! \#{2+2}&\#{2+1}", :escape_html => true)) - assert_equal("

\n 4&3\n

\n", render("%p\n ! \#{2+2}&\#{2+1}", :escape_html => false)) + assert_equal("

\n 4&<\n

\n", render("%p\n ! \#{2+2}&\#{'<'}", :escape_html => true)) + assert_equal("

\n 4&<\n

\n", render("%p\n ! \#{2+2}&\#{'<'}", :escape_html => false)) end def test_scripts_should_respect_escape_html_option