Skip to content

Commit

Permalink
Add an option to .xss_foliate to opt-out of entity encoding.
Browse files Browse the repository at this point in the history
Closes #20. #OSL
  • Loading branch information
flavorjones committed Aug 22, 2014
1 parent a6e6a20 commit aa36bdc
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 18 deletions.
52 changes: 45 additions & 7 deletions lib/loofah/activerecord/xss_foliate.rb
Expand Up @@ -54,6 +54,19 @@ module Loofah
# # was: xss_terminate :except => [:title], :sanitize => [:body]
# xss_foliate :except => [:title], :sanitize => [:body]
#
# OR
#
# # when the final content is intended for non-html contexts,
# # such as plaintext email, you can turn off entity encoding
# # for all fields
# xss_foliate :encode_special_chars => false # do *not* escape HTML entities in any field. NOTE THAT THE RESULT IS UNSAFE FOR RENDERING IN HTML CONTEXTS.
#
# OR
#
# # or you can turn off entity encoding only for specific fields.
# xss_foliate :unencode_special_chars => [:title] # will escape HTML entities in all fields except title. NOTE THAT `TITLE` IS UNSAFE FOR RENDERING IN HTML CONTEXTS.
#
#
# Alternatively, if you would like to *opt-in* to the models and attributes that are sanitized:
#
# # config/initializers/loofah.rb
Expand Down Expand Up @@ -83,9 +96,11 @@ module XssFoliate
#
module ClassMethods
# :stopdoc:
VALID_OPTIONS = [:except, :html5lib_sanitize, :sanitize] + Loofah::Scrubbers.scrubber_symbols
ALIASED_OPTIONS = {:html5lib_sanitize => :escape, :sanitize => :strip}
REAL_OPTIONS = VALID_OPTIONS - ALIASED_OPTIONS.keys
SYMBOL_OPTIONS = [:except, :html5lib_sanitize, :sanitize, :unencode_special_chars] + Loofah::Scrubbers.scrubber_symbols
BOOLEAN_OPTIONS = {:encode_special_chars => true}
ALIASED_SYMBOL_OPTIONS = {:html5lib_sanitize => :escape, :sanitize => :strip}
REAL_SYMBOL_OPTIONS = SYMBOL_OPTIONS - ALIASED_SYMBOL_OPTIONS.keys
VALID_OPTIONS = SYMBOL_OPTIONS + BOOLEAN_OPTIONS.keys + ALIASED_SYMBOL_OPTIONS.keys
# :startdoc:

def self.extended(base)
Expand Down Expand Up @@ -152,14 +167,25 @@ def xss_foliate(options = {})
raise ArgumentError, "unknown xss_foliate option #{option}" unless VALID_OPTIONS.include?(option)
end

REAL_OPTIONS.each do |option|
REAL_SYMBOL_OPTIONS.each do |option|
options[option] = Array(options[option]).collect { |val| val.to_sym }
end

ALIASED_OPTIONS.each do |option, real|
ALIASED_SYMBOL_OPTIONS.each do |option, real|
options[real] += Array(options.delete(option)).collect { |val| val.to_sym } if options[option]
end

BOOLEAN_OPTIONS.each do |option, default|
case options[option]
when FalseClass
when TrueClass
when NilClass
options[option] = default
else
raise "option #{option} only accepts `true` or `false` values"
end
end

if respond_to?(:class_attribute)
# Rails 3.0 and later
self.xss_foliate_options = options
Expand Down Expand Up @@ -196,15 +222,22 @@ def xss_foliate_fields # :nodoc:
field = column.name.to_sym
value = self[field]

next if value.nil? || !value.is_a?(String)
next if !value.is_a?(String)

next if xss_foliate_options[:except].include?(field)

next if xss_foliated_with_standard_scrubber(field)

# :text if we're here
fragment = Loofah.scrub_fragment(value, :strip)
self[field] = fragment.nil? ? "" : fragment.text

text_options = if xss_foliate_is_unencoded(field)
{:encode_special_chars => false}
else
{}
end

self[field] = fragment.nil? ? "" : fragment.text(text_options)
end
end

Expand All @@ -220,6 +253,11 @@ def xss_foliated_with_standard_scrubber(field)
end
false
end

def xss_foliate_is_unencoded(field)
(! xss_foliate_options[:encode_special_chars]) \
|| xss_foliate_options[:unencode_special_chars].include?(field)
end
end

def self.xss_foliate_all_models
Expand Down
51 changes: 40 additions & 11 deletions test/unit/test_xss_foliate.rb
Expand Up @@ -103,7 +103,7 @@ def new_post(overrides={})
mock(Loofah).scrub_fragment(HTML_STRING, :strip).once.returns(mock_doc)
mock(Loofah).scrub_fragment(PLAIN_TEXT, :strip).once.returns(mock_doc)
mock(Loofah).scrub_fragment(INTEGER_VALUE, :strip).never
mock(mock_doc).text.times(2)
mock(mock_doc).text({}).times(2)
assert new_post.valid?
end
end
Expand All @@ -118,7 +118,7 @@ def new_post(overrides={})
mock(Loofah).scrub_fragment(HTML_STRING, :strip).once.returns(mock_doc)
mock(Loofah).scrub_fragment(PLAIN_TEXT, :strip).never
mock(Loofah).scrub_fragment(INTEGER_VALUE, :strip).never
mock(mock_doc).text.once
mock(mock_doc).text({}).once
new_post.valid?
end
end
Expand Down Expand Up @@ -181,35 +181,64 @@ def new_post(overrides={})
end
end

context "these tests should pass for libxml 2.7.5 and later" do
context "with bad argument to encode_special_chars" do
it "raises an exception" do
assert_raises(RuntimeError) { Post.xss_foliate :encode_special_chars => [:title] }
end
end

context "with encode_special_chars turned off for all fields" do
before do
Post.xss_foliate
Post.xss_foliate :encode_special_chars => false
end

it "not scrub double quotes into html entities" do
answer = new_post(:plain_text => "\"something\"")
answer = new_post(:plain_text => "\"something\"", :html_string => "\"something\"")
answer.valid?
assert_equal "\"something\"", answer.plain_text
assert_equal "\"something\"", answer.html_string
end

it "not scrub ampersands into html entities" do
answer = new_post(:plain_text => "& Something")
answer = new_post(:plain_text => "& Something", :html_string => "& Something")
answer.valid?
assert_equal "& Something", answer.plain_text
assert_equal "& Something", answer.html_string
end

it "not scrub \\r html entities" do
answer = new_post(:plain_text => "Another \r Something")
answer = new_post(:plain_text => "Another \r Something", :html_string => "Another \r Something")
answer.valid?
assert_equal "Another \r Something", answer.plain_text
assert_equal "Another \r Something", answer.html_string
end
end

it "not scrub \\n html entities" do
answer = new_post(:plain_text => "Another \n Something")
context "with encode_special_chars turned off for one field" do
before do
Post.xss_foliate :unencode_special_chars => [:plain_text]
end

it "not scrub double quotes into html entities" do
answer = new_post(:plain_text => "\"something\"", :html_string => "\"something\"")
answer.valid?
assert_equal "Another \n Something", answer.plain_text
assert_equal "\"something\"", answer.plain_text
assert_equal ""something"", answer.html_string
end

it "not scrub ampersands into html entities" do
answer = new_post(:plain_text => "& Something", :html_string => "& Something")
answer.valid?
assert_equal "& Something", answer.plain_text
assert_equal "& Something", answer.html_string
end

it "not scrub \\r html entities" do
answer = new_post(:plain_text => "Another \r Something", :html_string => "Another \r Something")
answer.valid?
assert_equal "Another \r Something", answer.plain_text
assert_equal "Another 
 Something", answer.html_string
end
end
end
end

0 comments on commit aa36bdc

Please sign in to comment.