railsで特定のタグを通すHTMLエスケープ
掲示板みたいなシステムを作ろうとする時、よく使いそうな処理だけど、
ネットで探してみても見つけられなかったので自分で作ってみた。
普段呼び出す h メソッドを拡張して h(string, :except=>["a", "img"]) のように呼び出せるようにする。
そして、できたのが以下。
def html_escape_extend(str, option = {:except => []}) if option[:except].empty? html_escape(str) else allows = option[:except].map{|t|t.downcase} str.gsub(%r!(</?)([a-z]+)([^>]*)(/?>)!mi){|tag| left, name, attr, right = Regexp.last_match.captures if allows.include? name.downcase attr = attr2hash(attr) if tag !~ /^<\// attrs = (ATTR_BUILDER[name.downcase]||ATTR_BUILDER[:default]).call attr "#{left}#{name} #{attrs}#{right}" else "#{left}#{name}#{right}" end else html_escape(tag) end } end end def attr2hash(str) str.scan(/([^\s]+?)=(?:'(.+?)'|"(.+?)"|([^ >]+))/i). inject({}) {|m, v| m[v[0].intern] = v[1]||v[2]||v[3]; m } end ATTR_BUILDER = { "a" => lambda{|attr| if attr[:href] =~ /^https?:/ attrs = [:class, :target]. map{|key| "#{key}='#{attr[key]}'" if attr.has_key?(key) }.compact.join(" ") "href='#{attr[:href]}' #{attrs}" end }, "img" => lambda{|attr| attrs = attr.map{|key, value| "#{key}='#{value}'" }.join(" ") "<img #{attrs} />" }, :default => lambda{|attr| [:class, :style].map{|key| "#{key}='#{attr[key]}'" if attr.has_key?(key) }.compact.join(" ") } } alias h html_escape_extend
html_escape_extendが関数本体で第2引数にexceptオプションとして、
通したいタグ名をリストで渡してやれば、渡されたタグ以外全てHTMLエスケープをして返してくれる。
上記をApplicationHelperにでも書いてやれば既存の処理に影響なく拡張できる。
処理的には単純でタグ部分を正規表現で引っ張って、タグ名が引数exceptに含まれなければ通常のhtml_escape関数に引き渡し、含まれるならタグとして返す。
この時、タグの種類毎に属性に制限もかけてやりたいので、
ATTR_BUILDERで定義されたlambdaを使って許可する属性を構築している。
とりあえずaタグの場合はhref,class,target属性を許し、imgタグは全ての属性、
それ以外はclassとstyle属性のみ許可する設定になっている。
option引数をhash形式にしたのは、今後拡張として指定したタグのみ許可しないonlyオプションを実装したり、truncateも同時に行えるようtruncateオプションなども追加できると面白いと思ったので。