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

Merge branch 'master' into html2haml

Conflicts:
	doc-src/HAML_CHANGELOG.md
This commit is contained in:
Nathan Weizenbaum 2009-10-20 01:07:37 -07:00
commit 8b45b30ef3
21 changed files with 450 additions and 82 deletions

View file

@ -22,6 +22,7 @@ Rake::TestTask.new do |t|
t.libs << 'lib'
test_files = FileList['test/**/*_test.rb']
test_files.exclude('test/rails/*')
test_files.exclude('test/plugins/*')
test_files.exclude('test/haml/spec/*')
t.test_files = test_files
t.verbose = true

3
TODO
View file

@ -19,6 +19,9 @@
since it's already interpolated.
Can also use () to wrap contents of _haml_temp so that commas throw errors when not in :ugly
Support finer-grained HTML-escaping in filters
Speed
Make tags with dynamic attributes pre-render as much as possible
Including the attribute name where doable
:ugly + :html improvements
Ignore closing tags where we can
http://code.google.com/speed/articles/optimizing-html.html

View file

@ -101,6 +101,45 @@ including the line number and the offending character.
* Attributes are now sorted, to maintain a deterministic order.
## [2.2.9](http://github.com/nex3/haml/commit/2.2.9)
* Fixed a bug where Haml's text was concatenated to the wrong buffer
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
### Rails XSS Protection
Haml 2.2.9 supports the XSS protection in Rails versions 2.3.5+.
There are several components to this:
* If XSS protection is enabled, Haml's {file:HAML_REFERENCE.md#escape_html-option `:escape_html`}
option is set to `true` by default.
* Strings declared as HTML safe won't be escaped by Haml,
including the {file:Haml/Helpers.html#html_escape-instance_method `#html_escape`} helper
and `&=` if `:escape_html` has been disabled.
* Haml helpers that generate HTML are marked as HTML safe,
and will escape their input if it's not HTML safe.
## [2.2.8](http://github.com/nex3/haml/commit/2.2.8)
* Fixed a potential XSS issue with HTML escaping and wacky Unicode nonsense.
This is the same as [the issue fixed in Rails](http://groups.google.com/group/rubyonrails-security/browse_thread/thread/48ab3f4a2c16190f) a bit ago.
## [2.2.7](http://github.com/nex3/haml/commit/2.2.7)
* Fixed an `html2haml` issue where ERB attribute values

View file

@ -76,6 +76,25 @@ may be compiled to:
</div>
</div>
#### Rails XSS Protection
Haml supports Rails' XSS protection scheme,
which was introduced in Rails 2.3.5+ and is enabled by default in 3.0.0+.
If it's enabled, Haml's [`:escape_html`](#escape_html-option)
option is set to `true` by default -
like in ERB, all strings printed to a Haml template are escaped by default.
Also like ERB, strings marked as HTML safe are not escaped.
Haml also has [its own syntax for printing a raw string to the template](#unescaping_html).
If the `:escape_html` option is set to false when XSS protection is enabled,
Haml doesn't escape Ruby strings by default.
However, if a string marked HTML-safe is passed to [Haml's escaping syntax](#escaping_html),
it won't be escaped.
Finally, all the {file:Haml/Helpers.html Haml helpers} that return strings
that are known to be HTML safe are marked as such.
In addition, string input is escaped unless it's HTML safe.
### Ruby Module
Haml can also be used completely separately from Rails and ActionView.
@ -111,10 +130,11 @@ Available options are:
{#escape_html-option} `:escape_html`
: Sets whether or not to escape HTML-sensitive characters in script.
If this is true, `=` behaves like `&=`;
otherwise, it behaves like `!=`.
If this is true, `=` behaves like [`&=`](#escaping_html);
otherwise, it behaves like [`!=`](#unescaping_html).
Note that if this is set, `!=` should be used for yielding to subtemplates
and rendering partials.
See also [Escaping HTML](#escaping_html) and [Unescaping HTML](#unescaping_html)
Defaults to false.
{#ugly-option} `:ugly`

View file

@ -16,6 +16,23 @@ In addition, when the `sass` executable encounters an error,
it now prints the filename where the error occurs,
as well as a backtrace of Sass imports.
### Formatting
Properties of the form
margin: auto
top: 10px
bottom: 20px
That is, properties with a value and *also* nested properties,
are now rendered as such in nested output mode:
margin: auto;
margin-top: 10px;
margin-bottom: 20px;
That is, with the nested properties indented in the source.
### Ruby 1.9 Support
Sass and `css2sass` now produce more descriptive errors
@ -34,6 +51,14 @@ Several bug fixes and minor improvements have been made, including:
* Displaying the expected strings as strings rather than regular expressions
whenever possible.
## [2.2.9](http://github.com/nex3/haml/commit/2.2.9)
There were no changes made to Sass between versions 2.2.8 and 2.2.9.
## [2.2.8](http://github.com/nex3/haml/commit/2.2.8)
There were no changes made to Sass between versions 2.2.7 and 2.2.8.
## [2.2.7](http://github.com/nex3/haml/commit/2.2.7)
There were no changes made to Sass between versions 2.2.6 and 2.2.7.
@ -42,7 +67,7 @@ There were no changes made to Sass between versions 2.2.6 and 2.2.7.
* Don't crash when the `__FILE__` constant of a Ruby file is a relative path,
as apparently happens sometimes in TextMate
(thanks to [Karl Varga](http://github.com/kjvarga).
(thanks to [Karl Varga](http://github.com/kjvarga)).
* Add "Sass" to the `--version` string for the executables.

View file

@ -130,6 +130,8 @@ module Haml
Haml::Util.def_static_method(self, :format_script, [:result],
:preserve_script, :in_tag, :preserve_tag, :escape_html,
:nuke_inner_whitespace, :interpolated, :ugly, <<RUBY)
<% # Escape HTML here so that the safety of the string is preserved in Rails
result_name = escape_html ? "html_escape(result.to_s)" : "result.to_s" %>
<% unless ugly %>
# If we're interpolated,
# then the custom tabulation is handled in #push_text.
@ -140,13 +142,11 @@ module Haml
<% end %>
tabulation = @real_tabs
result = result.to_s.<% if nuke_inner_whitespace %>strip<% else %>rstrip<% end %>
result = <%= result_name %>.<% if nuke_inner_whitespace %>strip<% else %>rstrip<% end %>
<% else %>
result = result.to_s<% if nuke_inner_whitespace %>.strip<% end %>
result = <%= result_name %><% if nuke_inner_whitespace %>.strip<% end %>
<% end %>
<% if escape_html %> result = html_escape(result) <% end %>
<% if preserve_tag %>
result = Haml::Helpers.preserve(result)
<% elsif preserve_script %>

View file

@ -288,6 +288,7 @@ module Haml
:ugly => @options[:ugly],
:format => @options[:format],
:encoding => @options[:encoding],
:escape_html => @options[:escape_html],
}
end

View file

@ -113,9 +113,7 @@ MESSAGE
# @yield The block within which to escape newlines
def find_and_preserve(input = nil, tags = haml_buffer.options[:preserve], &block)
return find_and_preserve(capture_haml(&block), input || tags) if block
input = input.to_s
input.gsub(/<(#{tags.map(&Regexp.method(:escape)).join('|')})([^>]*)>(.*?)(<\/\1>)/im) do
input.to_s.gsub(/<(#{tags.map(&Regexp.method(:escape)).join('|')})([^>]*)>(.*?)(<\/\1>)/im) do
"<#{$1}#{$2}>#{preserve($3)}</#{$1}>"
end
end
@ -132,10 +130,9 @@ MESSAGE
# Escapes newlines within a block of Haml code.
#
# @yield The block within which to escape newlines
def preserve(input = '', &block)
def preserve(input = nil, &block)
return preserve(capture_haml(&block)) if block
input.chomp("\n").gsub(/\n/, '&#x000A;').gsub(/\r/, '')
input.to_s.chomp("\n").gsub(/\n/, '&#x000A;').gsub(/\r/, '')
end
alias_method :flatten, :preserve
@ -470,10 +467,14 @@ END
# Returns a copy of `text` with ampersands, angle brackets and quotes
# escaped into HTML entities.
#
# Note that if ActionView is loaded and XSS protection is enabled
# (as is the default for Rails 3.0+, and optional for version 2.3.5+),
# this won't escape text declared as "safe".
#
# @param text [String] The string to sanitize
# @return [String] The sanitized string
def html_escape(text)
text.to_s.gsub(/[\"><&]/) { |s| HTML_ESCAPE[s] }
text.to_s.gsub(/[\"><&]/n) {|s| HTML_ESCAPE[s]}
end
# Escapes HTML entities in `text`, but without escaping an ampersand
@ -482,7 +483,7 @@ END
# @param text [String] The string to sanitize
# @return [String] The sanitized string
def escape_once(text)
text.to_s.gsub(/[\"><]|&(?!([a-zA-Z]+|(#\d+));)/) { |s| HTML_ESCAPE[s] }
text.to_s.gsub(/[\"><]|&(?!(?:[a-zA-Z]+|(#\d+));)/n) {|s| HTML_ESCAPE[s]}
end
# Returns whether or not the current template is a Haml template.

View file

@ -35,6 +35,21 @@ module Haml
controller.controller_name + " " + controller.action_name
end
alias_method :generate_content_class_names, :page_class
# Treats all input to \{Haml::Helpers#haml\_concat} within the block
# as being HTML safe for Rails' XSS protection.
# This is useful for wrapping blocks of code that concatenate HTML en masse.
#
# This has no effect if Rails' XSS protection isn't enabled.
#
# @yield A block in which all input to `#haml_concat` is treated as raw.
# @see Haml::Util#rails_xss_safe?
def with_raw_haml_concat
@_haml_concat_raw = true
yield
ensure
@_haml_concat_raw = false
end
end
end
end

View file

@ -26,6 +26,7 @@ module ActionView
def set_output_buffer_with_haml(new)
if is_haml?
new = String.new(new) if Haml::Util.rails_xss_safe? && new.is_a?(ActionView::SafeBuffer)
haml_buffer.buffer = new
else
set_output_buffer_without_haml new

View file

@ -0,0 +1,94 @@
module Haml
module Helpers
# This module overrides Haml helpers to work properly
# in the context of ActionView.
# Currently it's only used for modifying the helpers
# to work with Rails' XSS protection methods.
module XssMods
def self.included(base)
%w[html_escape find_and_preserve preserve list_of surround
precede succeed capture_haml haml_concat haml_indent
haml_tag escape_once].each do |name|
base.send(:alias_method, "#{name}_without_haml_xss", name)
base.send(:alias_method, name, "#{name}_with_haml_xss")
end
end
# Don't escape text that's already safe,
# output is always HTML safe
def html_escape_with_haml_xss(text)
return text if text.html_safe?
html_escape_without_haml_xss(text).html_safe!
end
# Output is always HTML safe
def find_and_preserve_with_haml_xss(*args, &block)
find_and_preserve_without_haml_xss(*args, &block).html_safe!
end
# Output is always HTML safe
def preserve_with_haml_xss(*args, &block)
preserve_without_haml_xss(*args, &block).html_safe!
end
# Output is always HTML safe
def list_of_with_haml_xss(*args, &block)
list_of_without_haml_xss(*args, &block).html_safe!
end
# Input is escaped, output is always HTML safe
def surround_with_haml_xss(front, back = front, &block)
surround_without_haml_xss(
haml_xss_html_escape(front),
haml_xss_html_escape(back),
&block).html_safe!
end
# Input is escaped, output is always HTML safe
def precede_with_haml_xss(str, &block)
precede_without_haml_xss(haml_xss_html_escape(str), &block).html_safe!
end
# Input is escaped, output is always HTML safe
def succeed_with_haml_xss(str, &block)
succeed_without_haml_xss(haml_xss_html_escape(str), &block).html_safe!
end
# Output is always HTML safe
def capture_haml_with_haml_xss(*args, &block)
capture_haml_without_haml_xss(*args, &block).html_safe!
end
# Input is escaped
def haml_concat_with_haml_xss(text = "")
haml_concat_without_haml_xss(@_haml_concat_raw ? text : haml_xss_html_escape(text))
end
# Output is always HTML safe
def haml_indent_with_haml_xss
haml_indent_without_haml_xss.html_safe!
end
# Input is escaped, haml_concat'ed output is always HTML safe
def haml_tag_with_haml_xss(name, *rest, &block)
name = haml_xss_html_escape(name.to_s)
rest.unshift(haml_xss_html_escape(rest.shift.to_s)) unless [Symbol, Hash, NilClass].any? {|t| rest.first.is_a? t}
with_raw_haml_concat {haml_tag_without_haml_xss(name, *rest, &block)}
end
# Output is always HTML safe
def escape_once_with_haml_xss(*args)
escape_once_without_haml_xss(*args).html_safe!
end
private
# Escapes the HTML in the text if and only if
# Rails XSS protection is enabled *and* the `:escape_html` option is set.
def haml_xss_html_escape(text)
return text unless Haml::Util.rails_xss_safe? && haml_buffer.options[:escape_html]
html_escape(text)
end
end
end
end

View file

@ -313,7 +313,7 @@ END
@precompiled <<
if @options[:ugly]
"_erbout << \"#{text}\";"
"_hamlout.buffer << \"#{text}\";"
else
"_hamlout.push_text(\"#{text}\", #{tab_change}, #{@dont_tab_up_next_text.inspect});"
end
@ -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
@ -375,7 +378,7 @@ END
push_silent "haml_temp = #{text}"
newline_now
push_and_tabulate([:loud, "_erbout << #{no_format ? "#{output_temp}.to_s;" : out}",
push_and_tabulate([:loud, "_hamlout.buffer << #{no_format ? "#{output_temp}.to_s;" : out}",
!(opts[:in_tag] || opts[:nuke_inner_whitespace] || @options[:ugly])])
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

@ -27,6 +27,21 @@ else
require 'haml/template/patch'
end
if ActionView::Base.respond_to?(:xss_safe?) && ActionView::Base.xss_safe?
Haml::Template.options[:escape_html] = true
module Haml::Util
def rails_xss_safe?
true
end
end
require 'haml/helpers/xss_mods'
module Haml::Helpers
include XssMods
end
end
if defined?(RAILS_ROOT)
# Update init.rb to the current version
# if it's out of date.

View file

@ -122,6 +122,28 @@ module Haml
end
end
## Rails XSS Safety
# Whether or not ActionView's XSS protection is available and enabled,
# as is the default for Rails 3.0+, and optional for version 2.3.5+.
# Overridden in haml/template.rb if this is the case.
#
# @return [Boolean]
def rails_xss_safe?
false
end
# Assert that a given object (usually a String) is HTML safe
# according to Rails' XSS handling, if it's loaded.
#
# @param text [Object]
def assert_html_safe!(text)
return unless rails_xss_safe? && text && !text.to_s.html_safe?
raise Haml::Error.new("Expected #{text.inspect} to be HTML-safe.")
end
## Cross-Ruby-Version Compatibility
# Whether or not this is running under Ruby 1.8 or lower.
#
# @return [Boolean]
@ -171,6 +193,8 @@ MSG
ruby1_8? ? enum.enum_with_index : enum.each_with_index
end
## Static Method Stuff
# The context in which the ERB for \{#def\_static\_method} will be run.
class StaticConditionalContext
# @param set [#include?] The set of variables that are defined for this context.

View file

@ -14,6 +14,18 @@ module Sass::Tree
# @return [String, Script::Node]
attr_accessor :value
# How deep this property is indented
# relative to a normal property.
# This is only greater than 0 in the case that:
#
# * This node is in a static tree
# * The style is :nested
# * This is a child property of another property
# * The parent property has a value, and thus will be rendered
#
# @return [Fixnum]
attr_accessor :indentation
# @param name [String] See \{#name}
# @param value [String] See \{#value}
# @param prop_syntax [Symbol] `:new` if this property uses `a: b`-style syntax,
@ -21,6 +33,7 @@ module Sass::Tree
def initialize(name, value, prop_syntax)
@name = name
@value = value
@indentation = 0
@prop_syntax = prop_syntax
super()
end
@ -39,43 +52,36 @@ module Sass::Tree
# Computes the CSS for the property.
#
# @param tabs [Fixnum] The level of indentation for the CSS
# @param parent_name [String] The name of the parent property (e.g. `text`) or nil
# @return [String] The resulting CSS
# @raise [Sass::SyntaxError] if the property uses invalid syntax
def _to_s(tabs, parent_name = nil)
def _to_s(tabs)
if @options[:property_syntax] == :old && @prop_syntax == :new
raise Sass::SyntaxError.new("Illegal property syntax: can't use new syntax when :property_syntax => :old is set.")
elsif @options[:property_syntax] == :new && @prop_syntax == :old
raise Sass::SyntaxError.new("Illegal property syntax: can't use old syntax when :property_syntax => :new is set.")
elsif value[-1] == ?;
raise Sass::SyntaxError.new("Invalid property: #{declaration.dump} (no \";\" required at end-of-line).")
elsif value.empty? && children.empty?
elsif value.empty?
raise Sass::SyntaxError.new("Invalid property: #{declaration.dump} (no value).")
end
real_name = name
real_name = "#{parent_name}-#{real_name}" if parent_name
join_string = case style
when :compact; ' '
when :compressed; ''
else "\n"
end
spaces = ' ' * (tabs - 1)
to_return = ''
if !value.empty?
to_return << "#{spaces}#{real_name}:#{style == :compressed ? '' : ' '}#{value};#{join_string}"
end
children.each do |kid|
next if kid.invisible?
to_return << kid.to_s(tabs, real_name) << join_string
end
(style == :compressed && parent_name) ? to_return : to_return[0...-1]
to_return = ' ' * (tabs - 1 + indentation) + name + ":" +
(style == :compressed ? '' : ' ') + value + (style == :compressed ? "" : ";")
end
# Runs any SassScript that may be embedded in the property.
# Returns this node's fully-resolved child properties, and/or this node.
#
# @param environment [Sass::Environment] The lexical environment containing
# variable and mixin values
def _perform(environment)
node = super
result = node.children.dup
result.unshift(node) if !node.value.empty? || node.children.empty?
result
end
# Runs any SassScript that may be embedded in the property,
# and invludes the parent property, if any.
#
# @param environment [Sass::Environment] The lexical environment containing
# variable and mixin values
@ -83,6 +89,12 @@ module Sass::Tree
@name = interpolate(@name, environment)
@value = @value.is_a?(String) ? interpolate(@value, environment) : @value.perform(environment).to_s
super
# Once we've called super, the child nodes have been dup'ed
# so we can destructively modify them
children.select {|c| c.is_a?(PropNode)}.each do |c|
c.name = "#{name}-#{c.name}"
c.indentation += 1 if style == :nested && !@value.empty?
end
end
# Returns an error message if the given child node is invalid,

View file

@ -1,6 +1,9 @@
require 'pathname'
module Sass::Tree
# A static node reprenting a CSS rule.
#
# @see Sass::Tree
class RuleNode < Node
# The character used to include the parent selector
PARENT = '&'

View file

@ -168,6 +168,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
@ -603,53 +607,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

View file

@ -84,8 +84,9 @@ class TemplateTest < Test::Unit::TestCase
base
end
def render(text)
Haml::Engine.new(text).to_html(@base)
def render(text, opts = {})
return @base.render(:inline => text, :type => :haml) if opts == :action_view
Haml::Engine.new(text, opts).to_html(@base)
end
def load_result(name)
@ -95,6 +96,8 @@ class TemplateTest < Test::Unit::TestCase
end
def assert_renders_correctly(name, &render_method)
old_options = Haml::Template.options.dup
Haml::Template.options[:escape_html] = false
if ActionPack::VERSION::MAJOR < 2 ||
(ActionPack::VERSION::MAJOR == 2 && ActionPack::VERSION::MINOR < 2)
render_method ||= proc { |name| @base.render(name) }
@ -112,6 +115,8 @@ class TemplateTest < Test::Unit::TestCase
else
raise e
end
ensure
Haml::Template.options = old_options
end
def test_empty_render_should_remain_empty
@ -176,12 +181,31 @@ class TemplateTest < Test::Unit::TestCase
end
def test_haml_options
Haml::Template.options = { :suppress_eval => true }
assert_equal({ :suppress_eval => true }, Haml::Template.options)
old_options = Haml::Template.options.dup
Haml::Template.options[:suppress_eval] = true
old_base, @base = @base, create_base
assert_renders_correctly("eval_suppressed")
ensure
@base = old_base
Haml::Template.options = {}
Haml::Template.options = old_options
end
def test_with_output_buffer_with_ugly
return unless Haml::Util.has?(:instance_method, ActionView::Base, :with_output_buffer)
assert_equal(<<HTML, render(<<HAML, :ugly => true))
<p>
foo
baz
</p>
HTML
%p
foo
- with_output_buffer do
bar
= "foo".gsub(/./) do |s|
- s.ord
baz
HAML
end
def test_exceptions_should_work_correctly
@ -213,5 +237,41 @@ END
else
assert false
end
end
end
## XSS Protection Tests
if Haml::Util.rails_xss_safe?
def test_escape_html_option_set
assert Haml::Template.options[:escape_html]
end
def test_xss_protection
assert_equal("Foo &amp; Bar\n", render('= "Foo & Bar"', :action_view))
end
def test_xss_protection_with_safe_strings
assert_equal("Foo & Bar\n", render('= "Foo & Bar".html_safe!', :action_view))
end
def test_xss_protection_with_bang
assert_equal("Foo & Bar\n", render('!= "Foo & Bar"', :action_view))
end
def test_xss_protection_in_interpolation
assert_equal("Foo &amp; Bar\n", render('Foo #{"&"} Bar', :action_view))
end
def test_xss_protection_with_bang_in_interpolation
assert_equal("Foo & Bar\n", render('! Foo #{"&"} Bar', :action_view))
end
def test_xss_protection_with_safe_strings_in_interpolation
assert_equal("Foo & Bar\n", render('Foo #{"&".html_safe!} Bar', :action_view))
end
def test_xss_protection_with_mixed_strings_in_interpolation
assert_equal("Foo & Bar &amp; Baz\n", render('Foo #{"&".html_safe!} Bar #{"&"} Baz', :action_view))
end
end
end

View file

@ -54,6 +54,11 @@ class UtilTest < Test::Unit::TestCase
powerset([1, 2, 3]))
end
def test_merge_adjacent_strings
assert_equal(["foo bar baz", :bang, "biz bop", 12],
merge_adjacent_strings(["foo ", "bar ", "baz", :bang, "biz", " bop", 12]))
end
def test_has
assert(has?(:instance_method, String, :chomp!))
assert(has?(:private_instance_method, Haml::Engine, :set_locals))

View file

@ -10,3 +10,13 @@ else
end
require 'action_controller'
require 'action_view'
ActionController::Base.logger = Logger.new(nil)
# Load plugins from test/plugins.
# This will only work with very basic plugins,
# since we don't want to load the entirety of Rails.
Dir[File.dirname(__FILE__) + '/plugins/*'].each do |plugin|
$: << plugin + '/lib'
Object.new.instance_eval(File.read(plugin + '/init.rb'))
end

View file

@ -27,7 +27,7 @@ class SassEngineTest < Test::Unit::TestCase
"a\n b: c;" => 'Invalid property: "b: c;" (no ";" required at end-of-line).',
"a\n b : c" => 'Invalid property: "b : c".',
"a\n b=c: d" => 'Invalid property: "b=c: d".',
":a" => 'Properties aren\'t allowed at the root of a document.',
"a: b" => 'Properties aren\'t allowed at the root of a document.',
"!" => 'Invalid variable: "!".',
"!a" => 'Invalid variable: "!a".',
"! a" => 'Invalid variable: "! a".',
@ -463,6 +463,31 @@ SASS
assert_equal("foo {\n a: b;\n c: d;\n e: f; }\n", render("foo\r a: b\r\n c: d\n\r e: f"))
end
def test_property_with_content_and_nested_props
assert_equal(<<CSS, render(<<SASS))
foo {
a: b;
a-c: d;
a-c-e: f; }
CSS
foo
a: b
c: d
e: f
SASS
assert_equal(<<CSS, render(<<SASS))
foo {
a: b;
a-c-e: f; }
CSS
foo
a: b
c:
e: f
SASS
end
def test_or_eq
assert_equal("foo {\n a: b; }\n", render(%Q{!foo = "b"\n!foo ||= "c"\nfoo\n a = !foo}))
assert_equal("foo {\n a: b; }\n", render(%Q{!foo ||= "b"\nfoo\n a = !foo}))