From 9415935902f120a9bac0bfce7129725a0db38ed3 Mon Sep 17 00:00:00 2001 From: Michael Koziarski Date: Thu, 8 Oct 2009 09:31:20 +1300 Subject: Switch to on-by-default XSS escaping for rails. This consists of: * String#html_safe! a method to mark a string as 'safe' * ActionView::SafeBuffer a string subclass which escapes anything unsafe which is concatenated to it * Calls to String#html_safe! throughout the rails helpers * a 'raw' helper which lets you concatenate trusted HTML from non-safety-aware sources (e.g. presantized strings in the DB) * New ERB implementation based on erubis which uses a SafeBuffer instead of a String Hat tip to Django for the inspiration. --- .../lib/active_support/core_ext/string.rb | 3 +- .../core_ext/string/output_safety.rb | 43 +++++++++++ activesupport/test/core_ext/string_ext_test.rb | 86 ++++++++++++++++++++++ 3 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 activesupport/lib/active_support/core_ext/string/output_safety.rb (limited to 'activesupport') diff --git a/activesupport/lib/active_support/core_ext/string.rb b/activesupport/lib/active_support/core_ext/string.rb index d06a5a32fb..6c52f12712 100644 --- a/activesupport/lib/active_support/core_ext/string.rb +++ b/activesupport/lib/active_support/core_ext/string.rb @@ -7,4 +7,5 @@ require 'active_support/core_ext/string/access' require 'active_support/core_ext/string/iterators' require 'active_support/core_ext/string/xchar' require 'active_support/core_ext/string/behavior' -require 'active_support/core_ext/string/interpolation' \ No newline at end of file +require 'active_support/core_ext/string/interpolation' +require 'active_support/core_ext/string/output_safety' \ No newline at end of file diff --git a/activesupport/lib/active_support/core_ext/string/output_safety.rb b/activesupport/lib/active_support/core_ext/string/output_safety.rb new file mode 100644 index 0000000000..2cca4763f4 --- /dev/null +++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb @@ -0,0 +1,43 @@ +class String + def html_safe? + defined?(@_rails_html_safe) && @_rails_html_safe + end + + def html_safe! + @_rails_html_safe = true + self + end + + def html_safe + dup.html_safe! + end + + alias original_plus + + def +(other) + result = original_plus(other) + if html_safe? && also_html_safe?(other) + result.html_safe! + else + result + end + end + + alias original_concat << + def <<(other) + result = original_concat(other) + unless html_safe? && also_html_safe?(other) + @_rails_html_safe = false + end + result + end + + def concat(other) + self << other + end + + private + def also_html_safe?(other) + other.respond_to?(:html_safe?) && other.html_safe? + end + +end \ No newline at end of file diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb index db9073e298..584a41b631 100644 --- a/activesupport/test/core_ext/string_ext_test.rb +++ b/activesupport/test/core_ext/string_ext_test.rb @@ -356,3 +356,89 @@ class StringBytesizeTest < Test::Unit::TestCase assert_equal 3, 'foo'.bytesize end end + +class OutputSafetyTest < ActiveSupport::TestCase + def setup + @string = "hello" + end + + test "A string is unsafe by default" do + assert !@string.html_safe? + end + + test "A string can be marked safe" do + @string.html_safe! + assert @string.html_safe? + end + + test "Marking a string safe returns the string" do + assert_equal @string, @string.html_safe! + end + + test "Adding a safe string to another safe string returns a safe string" do + @other_string = "other".html_safe! + @string.html_safe! + @combination = @other_string + @string + + assert_equal "otherhello", @combination + assert @combination.html_safe? + end + + test "Adding an unsafe string to a safe string returns an unsafe string" do + @other_string = "other".html_safe! + @combination = @other_string + @string + @other_combination = @string + @other_string + + assert_equal "otherhello", @combination + assert_equal "helloother", @other_combination + + assert !@combination.html_safe? + assert !@other_combination.html_safe? + end + + test "Concatting safe onto unsafe yields unsafe" do + @other_string = "other" + @string.html_safe! + + @other_string.concat(@string) + assert !@other_string.html_safe? + end + + test "Concatting unsafe onto safe yields unsafe" do + @other_string = "other".html_safe! + + @other_string.concat(@string) + assert !@other_string.html_safe? + end + + test "Concatting safe onto safe yields safe" do + @other_string = "other".html_safe! + @string.html_safe! + + @other_string.concat(@string) + assert @other_string.html_safe? + end + + test "Concatting safe onto unsafe with << yields unsafe" do + @other_string = "other" + @string.html_safe! + + @other_string << @string + assert !@other_string.html_safe? + end + + test "Concatting unsafe onto safe with << yields unsafe" do + @other_string = "other".html_safe! + + @other_string << @string + assert !@other_string.html_safe? + end + + test "Concatting safe onto safe with << yields safe" do + @other_string = "other".html_safe! + @string.html_safe! + + @other_string << @string + assert @other_string.html_safe? + end +end -- cgit v1.2.3