tl;dr
- content_tagを連結していく処理で、一時変数の初期化をするときは空文字をhtml_safeしよう
content_string = ''.html_safe
content_string << link_to('Hogeページヘ', hoge_path) if hoge?
content_string << link_to('Fugaページヘ', huga_path) if huga?
kwsk
Railsのヘルパーに、条件によってリンクを動的に作成するようなメソッドを実装しようと思いたち、以下のように作ってみました。
# 利用者を判別してメニューを作成する
# @return [String] メニューのHTML
def user_menu
content_string = ''
content_string << link_to('管理ページ', admin_path) if current_user.try(:admin?)
content_string << if topics_exists?
link_to 'Top', top_with_topics_path
else
link_to 'Top', top_path
end
content_string << content_tag(:p, "ようこそ#{current_user.try(:user_name) || 'Guest'}さん!!")
content_string
end
.user_menu
=user_menu
ブラウザで表示すると
<a href="/admin">管理ページ</a><a href="/">Top</a><p>ようこそGuestさん!!</p>
と表示されます
HTMLソースを覗いてみると<
などの記号が<
のようにエスケープされているようです。
これをちゃんと表示するには
.user_menu
= raw user_menu
としてあげなければいけないのですが、面倒くさい せっかくヘルパーにしたのに、呼び出し元でも更に加工しなければならないのが不便
さらにいえば、例えばuser_nameをユーザーが自由に入力できるときに、ページに意図しないHTMLソースを埋め込まれる可能性があります(今回の場合は、埋め込まれたところでどうってことありませんが)。
実はlink_tag
やform_for
などで生成される<xx>..</xx>
という文字列は、Stringクラスを拡張したActiveSupport::SafeBufferというクラスのオブジェクトです。
ActiveSupport::SafeBufferオブジェクトがViewに描画されるときは、安全な文字列として判断しHTMLタグをエスケープせずに出力してくるようです。
ある文字列をActiveSupport::SafeBufferオブジェクトに変換するにはString#html_safe
を使いましょう。
# 利用者を判別してメニューを作成する
# @return [String] メニューのHTML
def user_menu
content_string = ''.html_safe
content_string << link_to('管理ページ', admin_path) if current_user.try(:admin?)
#中略
content_string
end
.user_menu
=user_menu
ブラウザで確認してみれば、ちゃんとリンクで構成されたメニューが表示されました!
btw
少し話しはずれますが、StringオブジェクトとActiveSupport::SafeBufferオブジェクトを連結した時の動作は以下のようになります。
動作確認はRails4.1.4で行いました。
str = ''
safe_str = ''.html_safe
str.class #=> String
safe_str.class #=> ActiveSupport::SafeBuffer
(str + str).class #=> String
(str + safe_str).class #=> String
(safe_str + str).class #=> ActiveSupport::SafeBuffer
(safe_str + safe_str).class #=> ActiveSupport::SafeBuffer
下から2つめのパターンは時に罠となりえそうです。
content_string = ''.html_safe
content_string << link_to('hoge', hoge_path)
content_string << user.profile # ユーザーの自己紹介を連結
上のコードでは、ユーザーの自己紹介に悪意あるコードが含まれていても、Viewにcontent_stringが書き込まれる際にエスケープされません。注意しましょう。
代換え・類似の手段として、以下のようなものもあるようです。
-
I18n.tでHTMLを出力するのにrawを使ってはいけない - Qiita
- 要点: I18nから引用する文章にHTMLタグがあるなら、キーの名前を「XX_html」にすればエスケープされない
-
ビューで配列を改行するなら脆弱なjoinではなくsafe_joinにしよう -TechRacho
- 要点: Arrayの連結にはsafe_joinを使うといいという話
ref.
rails/actionview/.../tag_helper.rb - Github
rails/actionview /.../buffers.rb - Github