Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
[Haml] Add XSS support. No tests yet.
  • Loading branch information
nex3 committed Oct 17, 2009
1 parent cad2f02 commit b239cca
Show file tree
Hide file tree
Showing 8 changed files with 154 additions and 4 deletions.
8 changes: 4 additions & 4 deletions lib/haml/buffer.rb
Expand Up @@ -130,6 +130,8 @@ def adjust_tabs(tab_change)
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.
Expand All @@ -140,13 +142,11 @@ def adjust_tabs(tab_change)
<% 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 %>
Expand Down
1 change: 1 addition & 0 deletions lib/haml/engine.rb
Expand Up @@ -284,6 +284,7 @@ def options_for_buffer
:ugly => @options[:ugly],
:format => @options[:format],
:encoding => @options[:encoding],
:escape_html => @options[:escape_html],
}
end

Expand Down
4 changes: 4 additions & 0 deletions lib/haml/helpers.rb
Expand Up @@ -467,6 +467,10 @@ def haml_tag(name, *rest, &block)
# 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)
Expand Down
15 changes: 15 additions & 0 deletions lib/haml/helpers/action_view_extensions.rb
Expand Up @@ -35,6 +35,21 @@ def page_class
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
1 change: 1 addition & 0 deletions lib/haml/helpers/action_view_mods.rb
Expand Up @@ -26,6 +26,7 @@ def output_buffer_with_haml

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
Expand Down
94 changes: 94 additions & 0 deletions lib/haml/helpers/xss_mods.rb
@@ -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
15 changes: 15 additions & 0 deletions lib/haml/template.rb
Expand Up @@ -27,6 +27,21 @@ module Template
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.
Expand Down
20 changes: 20 additions & 0 deletions lib/haml/util.rb
Expand Up @@ -122,6 +122,26 @@ def merge_adjacent_strings(enum)
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.
Expand Down

0 comments on commit b239cca

Please sign in to comment.