1
0
Fork 0
mirror of https://github.com/haml/haml.git synced 2022-11-09 12:33:31 -05:00

Escape tag attributes and interpolated strings when the :escape_html flag is set. Add &== and !==.

This commit is contained in:
Andre Arko 2008-03-18 02:19:41 -07:00
parent e817beea3c
commit e1028be61a
6 changed files with 79 additions and 27 deletions

View file

@ -696,8 +696,8 @@ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir)
#
# ==== &=
#
# An ampersand followed by an equals character
# evaluates Ruby code just like the single equals,
# An ampersand followed by one or two equals characters
# evaluates Ruby code just like the equals without the ampersand,
# but sanitizes any HTML-sensitive characters in the result of the code.
# For example:
#
@ -712,8 +712,8 @@ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir)
#
# ==== !=
#
# An exclamation mark followed by an equals character
# evaluates Ruby code just like the single equals,
# An exclamation mark followed by one or two equals characters
# evaluates Ruby code just like the equals would,
# but never sanitizes the HTML.
#
# By default, the single equals doesn't sanitize HTML either.
@ -835,7 +835,7 @@ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir)
# [<tt>:escape_html</tt>] Sets whether or not to escape HTML-sensitive characters in script.
# If this is true, = behaves like &=;
# otherwise, it behaves like !=.
# <b>Note that this doesn't affect attributes or == interpolation.</b>
# <b>Note that this escapes tag attributes.</b>
# Defaults to false.
#
# [<tt>:suppress_eval</tt>] Whether or not attribute hashes and Ruby scripts

View file

@ -117,7 +117,7 @@ module Haml
# Takes the various information about the opening tag for an
# element, formats it, and adds it to the buffer.
def open_tag(name, atomic, try_one_line, preserve_tag, class_id, obj_ref, content, *attributes_hashes)
def open_tag(name, atomic, try_one_line, preserve_tag, escape_html, class_id, obj_ref, content, *attributes_hashes)
tabulation = @real_tabs
attributes = class_id
@ -135,7 +135,7 @@ module Haml
str = ">\n"
end
attributes = Precompiler.build_attributes(html?, @options[:attr_wrapper], attributes)
attributes = Precompiler.build_attributes(html?, @options[:attr_wrapper], escape_html, attributes)
@buffer << "#{@options[:ugly] ? '' : tabs(tabulation)}<#{name}#{attributes}#{str}"
if content

View file

@ -293,7 +293,9 @@ module Haml
end
attributes = Haml::Precompiler.build_attributes(haml_buffer.html?,
haml_buffer.options[:attr_wrapper], attributes)
haml_buffer.options[:attr_wrapper],
haml_buffer.options[:escape_html],
attributes)
if text.nil? && block.nil?
puts "<#{name}#{attributes} />"
return nil

View file

@ -202,6 +202,7 @@ END
when ELEMENT; render_tag(text)
when COMMENT; render_comment(text)
when SANITIZE
return push_script(unescape_interpolation(text[3..-1].strip), false, nil, false, true) if text[1..2] == "=="
return push_script(text[2..-1].strip, false, nil, false, true) if text[1] == SCRIPT
push_plain text
when SCRIPT
@ -220,6 +221,7 @@ END
when FILTER; start_filtered(text[1..-1].downcase)
when DOCTYPE
return render_doctype(text) if text[0...3] == '!!!'
return push_script(unescape_interpolation(text[3..-1].strip), false) if text[1..2] == "=="
return push_script(text[2..-1].strip, false) if text[1] == SCRIPT
push_plain text
when ESCAPE; push_plain text[1..-1]
@ -461,7 +463,7 @@ END
end
# This is a class method so it can be accessed from Buffer.
def self.build_attributes(is_html, attr_wrapper, attributes = {})
def self.build_attributes(is_html, attr_wrapper, escape_html, attributes = {})
quote_escape = attr_wrapper == '"' ? "&quot;" : "&apos;"
other_quote_char = attr_wrapper == '"' ? "'" : '"'
@ -485,12 +487,13 @@ END
end
end
" #{attr}=#{this_attr_wrapper}#{value}#{this_attr_wrapper}"
end
result.compact.sort.join
end.compact.sort.join
escape_html ? Haml::Helpers.html_escape(result) : result
end
def prerender_tag(name, self_close, attributes)
attributes_string = Precompiler.build_attributes(html?, @options[:attr_wrapper], attributes)
def prerender_tag(name, self_close, escape_html, attributes)
attributes_string = Precompiler.build_attributes(html?, @options[:attr_wrapper], escape_html, attributes)
"<#{name}#{attributes_string}#{self_close && xhtml? ? ' /' : ''}>"
end
@ -529,7 +532,7 @@ END
when '&', '!'
if value[0] == ?=
parse = true
value = value[1..-1].strip
value = (value[1] == ?= ? unescape_interpolation(value[2..-1].strip) : value[1..-1].strip)
end
end
@ -559,7 +562,7 @@ END
# This means that we can render the tag directly to text and not process it in the buffer
tag_closed = !value.empty? && one_liner && !parse
open_tag = prerender_tag(tag_name, atomic, attributes)
open_tag = prerender_tag(tag_name, atomic, escape_html, attributes)
open_tag << "#{value}</#{tag_name}>" if tag_closed
open_tag << "\n" unless parse
@ -569,7 +572,7 @@ END
flush_merged_text
content = value.empty? || parse ? 'nil' : value.dump
attributes_hash = ', ' + attributes_hash if attributes_hash
push_silent "_hamlout.open_tag(#{tag_name.inspect}, #{atomic.inspect}, #{(!value.empty?).inspect}, #{preserve_tag.inspect}, #{attributes.inspect}, #{object_ref}, #{content}#{attributes_hash})"
push_silent "_hamlout.open_tag(#{tag_name.inspect}, #{atomic.inspect}, #{(!value.empty?).inspect}, #{preserve_tag.inspect}, #{escape_html.inspect}, #{attributes.inspect}, #{object_ref}, #{content}#{attributes_hash})"
end
return if atomic

View file

@ -118,36 +118,79 @@ class EngineTest < Test::Unit::TestCase
# HTML escaping tests
def test_script_ending_in_comment_should_render_when_html_is_escaped
assert_equal("foo&amp;bar\n", render("= 'foo&bar' #comment", :escape_html => true))
end
def test_ampersand_equals
def test_ampersand_equals_should_escape
assert_equal("<p>\n foo &amp; bar\n</p>\n", render("%p\n &= 'foo & bar'", :escape_html => false))
end
def test_ampersand_equals_inline
def test_ampersand_equals_inline_should_escape
assert_equal("<p>foo &amp; bar</p>\n", render("%p&= 'foo & bar'", :escape_html => false))
end
def test_bang_equals
def test_bang_equals_should_not_escape
assert_equal("<p>\n foo & bar\n</p>\n", render("%p\n != 'foo & bar'", :escape_html => true))
end
def test_bang_equals_inline
def test_bang_equals_inline_should_not_escape
assert_equal("<p>foo & bar</p>\n", render("%p!= 'foo & bar'", :escape_html => true))
end
def test_escape_html_option_for_scripts
def test_static_attributes_should_be_escaped
assert_equal("<img class='atlantis' style='ugly&amp;stupid' />\n",
render("%img.atlantis{:style => 'ugly&stupid'}", :escape_html => true))
assert_equal("<div class='atlantis' style='ugly&amp;stupid'>foo</div>\n",
render(".atlantis{:style => 'ugly&stupid'} foo", :escape_html => true))
assert_equal("<p class='atlantis' style='ugly&amp;stupid'>foo</p>\n",
render("%p.atlantis{:style => 'ugly&stupid'}= 'foo'", :escape_html => true))
end
def test_dynamic_attributes_should_be_escaped
assert_equal("<img alt='' src='/foo.png' />\n",
render("%img{:width => nil, :src => '/foo.png', :alt => String.new}", :escape_html => true))
assert_equal("<p alt='' src='/foo.png'>foo</p>\n",
render("%p{:width => nil, :src => '/foo.png', :alt => String.new} foo", :escape_html => true))
assert_equal("<div alt='' src='/foo.png'>foo</div>\n",
render("%div{:width => nil, :src => '/foo.png', :alt => String.new}= 'foo'", :escape_html => true))
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))
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))
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))
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))
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))
end
def test_scripts_should_respect_escape_html_option
assert_equal("<p>\n foo &amp; bar\n</p>\n", render("%p\n = 'foo & bar'", :escape_html => true))
assert_equal("<p>\n foo & bar\n</p>\n", render("%p\n = 'foo & bar'", :escape_html => false))
end
def test_escape_html_option_for_inline_scripts
def test_inline_scripts_should_respect_escape_html_option
assert_equal("<p>foo &amp; bar</p>\n", render("%p= 'foo & bar'", :escape_html => true))
assert_equal("<p>foo & bar</p>\n", render("%p= 'foo & bar'", :escape_html => false))
end
def test_script_ending_in_comment_should_render_when_html_is_escaped
assert_equal("foo&amp;bar\n", render("= 'foo&bar' #comment", :escape_html => true))
end
# Options tests
def test_stop_eval

View file

@ -96,6 +96,10 @@ class HelperTest < Test::Unit::TestCase
assert_equal("\"<p>13</p>\\n\"\n", render("- foo = capture_haml(13) do |a|\n %p= a\n= foo.dump"))
end
def test_haml_tag_attribute_html_escaping
assert_equal("<p id='foo&amp;bar'>baz</p>\n", render("%p{:id => 'foo&bar'} baz", :escape_html => true))
end
def test_is_haml
assert(!ActionView::Base.new.is_haml?)
assert_equal("true\n", render("= is_haml?"))