こんにちは、hachi8833です。
ここでは次のような結果を期待していました。以下コード中の&
はエスケープを表示するために全角の&
にしています。
<%= link_to '#', data: {key: '<span>piyopiyo</span>'} %> <!-- 元のERBコード -->
<a href="#" data-key="&lt;span&gt;piyopiyo&lt;/span\&gt;"> <!-- 期待するHTML出力 -->
<a href="#" data-key="<span>piyopiyo</span>"> <!-- 実際のHTML出力 -->
Railsビューのエスケープ系操作は、#html_safe
メソッドの名前が微妙に紛らわしいせいかときどき迷うことがあるので、まず整理してみました。
Railsのビューにおけるエスケープ
一般的なHTMLエスケープについてまずまとめました。
エスケープした場合としなかった場合
コントローラとビューにそれぞれ以下のように書いたとします。
## コントローラ
@h1 = "<h1>Railsドキュメント</h1>"
<!-- ビューのERB -->
<%= @h1 %>
@h1変数がエスケープされれば以下が出力され、
<!-- ビューのERBから出力されるHTML -->
&lt;h1&gt;Railsドキュメント&lt;/h1&gt;
ブラウザでタグとして解釈されずに以下のように表示されます。上のSlackのやりとりではこのような出力を期待していました。
@h1変数がエスケープされなければ以下が出力され、
<!-- ビューのERBから出力されるHTML -->
<h1>Railsドキュメント</h1>
ブラウザでh1タグとして解釈されてたとえば以下のように表示されます。
ビューの文字列はデフォルトでエスケープされる
安全のため、Rails 3 以降のビューで表示される変数はデフォルトですべてエスケープされます。必要な場合にのみ、このエスケープを解除することになります。
Rails 2 以前の案件を扱う場合、マニュアルでのエスケープ処理が不完全だったり、まったく行われていない可能性があるので注意が必要です。
ERBやhamlでのエスケープ解除
ERB
ERB で<%==
と%>
で記述したコードの出力は、エスケープされなくなります。
<%== エスケープされないRubyコード %>
haml
hamlの場合は =
の代わりに!=
を使います。
= "I feel <strong>!" <!-- エスケープされる -->
!= "I feel <strong>!" <!-- エスケープされない -->
個人的にはこれらの形式のエスケープ表記は見落としそうなので使っていません。
#html_escapeまたは#h
#html_escape
はRubyのERB::Utilのメソッドであり、#h
はそのエイリアスです。前述のとおり、Rails 3 以降は何もしなくてもデフォルトでエスケープされるので、通常のエスケープのためにこのメソッドを呼ぶ必要はありません。
#html_safe
#html_safe
は、対象の文字列が安全であるとマーキングするメソッドです。
<!-- ビューのERB -->
<%= @h1.html_safe %>
安全であるとマーキングすることで、対象の文字列では以後の処理でエスケープされなくなります。
次の#raw
と同等のメソッドです。
#html_safe
や#raw
ではなく、後述の#sanitize
メソッドを使うことが推奨されています。セキュリティ上の問題が生じるため、ユーザー入力に対して#html_safe
や#raw
を使ってはいけません。
It is recommended that you use
sanitize
instead of this method. It should never be called on user input.
個人的には、#mark_as_safe
というメソッド名にして欲しかったと思います。#html_safe?
と一貫させるためなのかもしれませんが。
#raw
#raw
は上述のとおり、#html_safe
と同等です。
<!-- ビューのERB -->
<%= raw @h1 %>
#sanitize
#sanitize
は、tagsやattributesで指定されていないタグや属性をすべて除去します。href
属性やsrc
属性にjavascript:
などの安全でないプロトコルが指定されている場合も削除します。
sanitize(文字列 [, tags => “許可するHTMLタグ名”, attributes => “許可するHTML属性名”])
ユーザー入力に#sanitize
を使っても、<、>、&などが残る可能性があるので、安全は保証されません。
#link_toではエスケープが解除される
本題に戻ります。baba さんが指摘しているように、ビューの#link_to
メソッドはデフォルトのエスケープ対象とならず、#html_safe
が適用されたのと同じになります。
#link_toメソッドでこの挙動を解除して通常どおりエスケープされるようにするには、#to_str
メソッドを使います。「エスケープ解除を解除」になるので、一瞬考えてしまいました。
<%= link_to("リンク文字", path).to_str %>
#to_s
やString()
はこの目的には使えません。
追伸
#link_to
に限らず、ヘルパーメソッドの多くはエスケープ対象となりません(#button_to、#content_tag、#form_for、#collection_check_boxesなど)。