added the ability to add safe html tags to input (i.e., images)
[selectricity-live] / vendor / plugins / white_list / lib / white_list_helper.rb
1 module WhiteListHelper
2   @@protocol_attributes = Set.new %w(src href)
3   @@protocol_separator  = /:|(&#0*58)|(&#x70)|(%|%)3A/
4   mattr_reader :protocol_attributes, :protocol_separator
5
6   def self.contains_bad_protocols?(white_listed_protocols, value)
7     value =~ protocol_separator && !white_listed_protocols.include?(value.split(protocol_separator).first)
8   end
9
10   klass = class << self; self; end
11   klass_methods = []
12   inst_methods  = []
13   [:bad_tags, :tags, :attributes, :protocols].each do |attr|
14     # Add class methods to the module itself
15     klass_methods << <<-EOS
16       def #{attr}=(value) @@#{attr} = Set.new(value) end
17       def #{attr}() @@#{attr} end
18     EOS
19     
20     # prefix the instance methods with white_listed_*
21     inst_methods << "def white_listed_#{attr}() ::WhiteListHelper.#{attr} end"
22   end
23   
24   klass.class_eval klass_methods.join("\n"), __FILE__, __LINE__
25   class_eval       inst_methods.join("\n"),  __FILE__, __LINE__
26
27   # This White Listing helper will html encode all tags and strip all attributes that aren't specifically allowed.  
28   # It also strips href/src tags with invalid protocols, like javascript: especially.  It does its best to counter any
29   # tricks that hackers may use, like throwing in unicode/ascii/hex values to get past the javascript: filters.  Check out
30   # the extensive test suite.
31   #
32   #   <%= white_list @article.body %>
33   # 
34   # You can add or remove tags/attributes if you want to customize it a bit.
35   # 
36   # Add table tags
37   #   
38   #   WhiteListHelper.tags.merge %w(table td th)
39   # 
40   # Remove tags
41   #   
42   #   WhiteListHelper.tags.delete 'div'
43   # 
44   # Change allowed attributes
45   # 
46   #   WhiteListHelper.attributes.merge %w(id class style)
47   # 
48   # white_list accepts a block for custom tag escaping.  Shown below is the default block that white_list uses if none is given.
49   # The block is called for all bad tags, and every text node.  node is an instance of HTML::Node (either HTML::Tag or HTML::Text).  
50   # bad is nil for text nodes inside good tags, or is the tag name of the bad tag.  
51   # 
52   #   <%= white_list(@article.body) { |node, bad| white_listed_bad_tags.include?(bad) ? nil : node.to_s.gsub(/</, '&lt;') } %>
53   #
54   def white_list(html, options = {}, &block)
55     return html if html.blank? || !html.include?('<')
56     attrs   = Set.new(options[:attributes]).merge(white_listed_attributes)
57     tags    = Set.new(options[:tags]      ).merge(white_listed_tags)
58     block ||= lambda { |node, bad| white_listed_bad_tags.include?(bad) ? nil : node.to_s.gsub(/</, '&lt;') }
59     returning [] do |new_text|
60       tokenizer = HTML::Tokenizer.new(html)
61       bad       = nil
62       while token = tokenizer.next
63         node = HTML::Node.parse(nil, 0, 0, token, false)
64         new_text << case node
65           when HTML::Tag
66             node.attributes.keys.each do |attr_name|
67               value = node.attributes[attr_name].to_s
68               if !attrs.include?(attr_name) || (protocol_attributes.include?(attr_name) && contains_bad_protocols?(value))
69                 node.attributes.delete(attr_name)
70               else
71                 node.attributes[attr_name] = CGI::escapeHTML(value)
72               end
73             end if node.attributes
74             if tags.include?(node.name)
75               bad = nil
76               node
77             else
78               bad = node.name
79               block.call node, bad
80             end
81           else
82             block.call node, bad
83         end
84       end
85     end.join
86   end
87   
88   protected
89     def contains_bad_protocols?(value)
90       WhiteListHelper.contains_bad_protocols?(white_listed_protocols, value)
91     end
92 end
93
94 WhiteListHelper.bad_tags   = %w(script)
95 WhiteListHelper.tags       = %w(strong em b i p code pre tt output samp kbd var sub sup dfn cite big small address hr br div span h1 h2 h3 h4 h5 h6 ul ol li dt dd abbr acronym a img blockquote del ins fieldset legend)
96 WhiteListHelper.attributes = %w(href src width height alt cite datetime title class)
97 WhiteListHelper.protocols  = %w(ed2k ftp http https irc mailto news gopher nntp telnet webcal xmpp callto feed)

Benjamin Mako Hill || Want to submit a patch?