[Haml] Fix a bug with interpolation and HTML escaping.

Closes gh-44
This commit is contained in:
Nathan Weizenbaum 2009-10-16 18:31:04 -07:00
parent c83b358f56
commit 83f8b20ea5
3 changed files with 61 additions and 34 deletions

View File

@ -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 &lt; Bar #{"<"} Baz
with `:escape_html` used to render as
Foo &amp;lt; Bar &lt; Baz
but now renders as
Foo &lt; Bar &lt; 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.

View File

@ -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

View File

@ -163,6 +163,10 @@ class EngineTest < Test::Unit::TestCase
assert_equal("<p>\n 2\n</p>\n", render("%p\n \#{1 + 1}"))
end
def test_escaped_interpolation
assert_equal("<p>Foo &amp; Bar & Baz</p>\n", render('%p& Foo #{"&"} Bar & Baz'))
end
def test_nil_tag_value_should_render_as_empty
assert_equal("<p></p>\n", render("%p= nil"))
end
@ -598,53 +602,53 @@ HAML
end
def test_string_double_equals_should_be_esaped
assert_equal("<p>4&amp;3</p>\n", render("%p== \#{2+2}&\#{2+1}", :escape_html => true))
assert_equal("<p>4&3</p>\n", render("%p== \#{2+2}&\#{2+1}", :escape_html => false))
assert_equal("<p>4&&lt;</p>\n", render("%p== \#{2+2}&\#{'<'}", :escape_html => true))
assert_equal("<p>4&<</p>\n", render("%p== \#{2+2}&\#{'<'}", :escape_html => false))
end
def test_escaped_inline_string_double_equals
assert_equal("<p>4&amp;3</p>\n", render("%p&== \#{2+2}&\#{2+1}", :escape_html => true))
assert_equal("<p>4&amp;3</p>\n", render("%p&== \#{2+2}&\#{2+1}", :escape_html => false))
assert_equal("<p>4&&lt;</p>\n", render("%p&== \#{2+2}&\#{'<'}", :escape_html => true))
assert_equal("<p>4&&lt;</p>\n", render("%p&== \#{2+2}&\#{'<'}", :escape_html => false))
end
def test_unescaped_inline_string_double_equals
assert_equal("<p>4&3</p>\n", render("%p!== \#{2+2}&\#{2+1}", :escape_html => true))
assert_equal("<p>4&3</p>\n", render("%p!== \#{2+2}&\#{2+1}", :escape_html => false))
assert_equal("<p>4&<</p>\n", render("%p!== \#{2+2}&\#{'<'}", :escape_html => true))
assert_equal("<p>4&<</p>\n", render("%p!== \#{2+2}&\#{'<'}", :escape_html => false))
end
def test_escaped_string_double_equals
assert_equal("<p>\n 4&amp;3\n</p>\n", render("%p\n &== \#{2+2}&\#{2+1}", :escape_html => true))
assert_equal("<p>\n 4&amp;3\n</p>\n", render("%p\n &== \#{2+2}&\#{2+1}", :escape_html => false))
assert_equal("<p>\n 4&&lt;\n</p>\n", render("%p\n &== \#{2+2}&\#{'<'}", :escape_html => true))
assert_equal("<p>\n 4&&lt;\n</p>\n", render("%p\n &== \#{2+2}&\#{'<'}", :escape_html => false))
end
def test_unescaped_string_double_equals
assert_equal("<p>\n 4&3\n</p>\n", render("%p\n !== \#{2+2}&\#{2+1}", :escape_html => true))
assert_equal("<p>\n 4&3\n</p>\n", render("%p\n !== \#{2+2}&\#{2+1}", :escape_html => false))
assert_equal("<p>\n 4&<\n</p>\n", render("%p\n !== \#{2+2}&\#{'<'}", :escape_html => true))
assert_equal("<p>\n 4&<\n</p>\n", render("%p\n !== \#{2+2}&\#{'<'}", :escape_html => false))
end
def test_string_interpolation_should_be_esaped
assert_equal("<p>4&amp;3</p>\n", render("%p \#{2+2}&\#{2+1}", :escape_html => true))
assert_equal("<p>4&3</p>\n", render("%p \#{2+2}&\#{2+1}", :escape_html => false))
assert_equal("<p>4&&lt;</p>\n", render("%p \#{2+2}&\#{'<'}", :escape_html => true))
assert_equal("<p>4&<</p>\n", render("%p \#{2+2}&\#{'<'}", :escape_html => false))
end
def test_escaped_inline_string_interpolation
assert_equal("<p>4&amp;3</p>\n", render("%p& \#{2+2}&\#{2+1}", :escape_html => true))
assert_equal("<p>4&amp;3</p>\n", render("%p& \#{2+2}&\#{2+1}", :escape_html => false))
assert_equal("<p>4&&lt;</p>\n", render("%p& \#{2+2}&\#{'<'}", :escape_html => true))
assert_equal("<p>4&&lt;</p>\n", render("%p& \#{2+2}&\#{'<'}", :escape_html => false))
end
def test_unescaped_inline_string_interpolation
assert_equal("<p>4&3</p>\n", render("%p! \#{2+2}&\#{2+1}", :escape_html => true))
assert_equal("<p>4&3</p>\n", render("%p! \#{2+2}&\#{2+1}", :escape_html => false))
assert_equal("<p>4&<</p>\n", render("%p! \#{2+2}&\#{'<'}", :escape_html => true))
assert_equal("<p>4&<</p>\n", render("%p! \#{2+2}&\#{'<'}", :escape_html => false))
end
def test_escaped_string_interpolation
assert_equal("<p>\n 4&amp;3\n</p>\n", render("%p\n & \#{2+2}&\#{2+1}", :escape_html => true))
assert_equal("<p>\n 4&amp;3\n</p>\n", render("%p\n & \#{2+2}&\#{2+1}", :escape_html => false))
assert_equal("<p>\n 4&&lt;\n</p>\n", render("%p\n & \#{2+2}&\#{'<'}", :escape_html => true))
assert_equal("<p>\n 4&&lt;\n</p>\n", render("%p\n & \#{2+2}&\#{'<'}", :escape_html => false))
end
def test_unescaped_string_interpolation
assert_equal("<p>\n 4&3\n</p>\n", render("%p\n ! \#{2+2}&\#{2+1}", :escape_html => true))
assert_equal("<p>\n 4&3\n</p>\n", render("%p\n ! \#{2+2}&\#{2+1}", :escape_html => false))
assert_equal("<p>\n 4&<\n</p>\n", render("%p\n ! \#{2+2}&\#{'<'}", :escape_html => true))
assert_equal("<p>\n 4&<\n</p>\n", render("%p\n ! \#{2+2}&\#{'<'}", :escape_html => false))
end
def test_scripts_should_respect_escape_html_option