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:
commit
8b45b30ef3
21 changed files with 450 additions and 82 deletions
1
Rakefile
1
Rakefile
|
@ -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
3
TODO
|
@ -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
|
||||
|
|
|
@ -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 < Bar #{"<"} Baz
|
||||
|
||||
with `:escape_html` used to render as
|
||||
|
||||
Foo &lt; Bar < Baz
|
||||
|
||||
but now renders as
|
||||
|
||||
Foo < Bar < 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
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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 %>
|
||||
|
|
|
@ -288,6 +288,7 @@ module Haml
|
|||
:ugly => @options[:ugly],
|
||||
:format => @options[:format],
|
||||
:encoding => @options[:encoding],
|
||||
:escape_html => @options[:escape_html],
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
@ -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/, '
').gsub(/\r/, '')
|
||||
input.to_s.chomp("\n").gsub(/\n/, '
').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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
94
lib/haml/helpers/xss_mods.rb
Normal file
94
lib/haml/helpers/xss_mods.rb
Normal 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
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 = '&'
|
||||
|
|
|
@ -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 & 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&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_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_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&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_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&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_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_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&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_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
|
||||
|
|
|
@ -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 & 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 & 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 & Baz\n", render('Foo #{"&".html_safe!} Bar #{"&"} Baz', :action_view))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}))
|
||||
|
|
Loading…
Add table
Reference in a new issue