こんにちは、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エスケープについてまずまとめました。Railsガイドもどうぞ。
参考: §5.1.2 安全な文字列 -- Active Support コア拡張機能 - Railsガイド
エスケープした場合としなかった場合
コントローラとビューにそれぞれ以下のように書いたとします。
# コントローラ
@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 以降のビューで表示される変数はデフォルトですべてエスケープされます。必要な場合にのみ、このエスケープを解除することになります。
HTML出力を
h(string)
呼び出しでエスケープする必要はもうありません。h(string)
はデフォルトであらゆるビューテンプレートで有効になります。エスケープを解除した(unescaped)文字列が欲しい場合はraw(string)
を呼び出します。
§7.4.3 その他の変更 -- Ruby on Rails 3.0 リリースノート - Railsガイドより
Rails 2 以前の案件を扱う場合、マニュアルでのエスケープ処理が不完全だったり、まったく行われていない可能性があるので注意が必要です。
ERBやhamlでのエスケープ解除
ERB
ERB で<%==
と%>
で記述したコードの出力は、エスケープされなくなります。
<%== エスケープされないRubyコード %>
参考: 結果をエスケープしないで出力 -- railsdoc.com
haml
hamlの場合は=
の代わりに!=
を使います。
= "I feel <strong>!" <!-- エスケープされる -->
!= "I feel <strong>!" <!-- エスケープされない -->
参考: Unescaping HTML: !=
-- haml.info
個人的にはこれらの形式のエスケープ表記は見落としそうなので使っていません。
#html_escape
または#h
#html_escape
はRubyのERB::Util
のメソッドであり、#h
はそのエイリアスです。前述のとおり、Rails 3 以降は何もしなくてもデフォルトでエスケープされるので、通常のエスケープのためにこのメソッドを呼ぶ必要はありません。
参考: Rails API html_escape
-- ERB::Util
参考: Rails API h
-- ERB::Util
#html_safe
#html_safe
は、対象の文字列が安全であるとマーキングするメソッドです。
<!-- ビューのERB -->
<%= @h1.html_safe %>
参考: Rails API String#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.
Rails APIString#html_safe
より
個人的には、#mark_as_safe
というメソッド名にして欲しかった気もします。#html_safe?
と一貫させるためなのかもしれませんが。
参考: Rails API html_safe?
-- ActiveSupport::SafeBuffer
#raw
#raw
は上述のとおり、#html_safe
と同等です。
<!-- ビューのERB -->
<%= raw @h1 %>
参考: Rails API raw
-- ActionView::Helpers::OutputSafetyHelper
#sanitize
#sanitize
は、tagsやattributesで指定されていないタグや属性をすべて除去します。href
属性やsrc
属性にjavascript:
などの安全でないプロトコルが指定されている場合も削除します。
sanitize(文字列 [, tags => "許可するHTMLタグ名", attributes => "許可するHTML属性名"])
railsdoc.com サニタイズ(sanitize)より
ユーザー入力に#sanitize
を使っても、<
、>
、&
などが残る可能性があるので、安全は保証されません。
参考: Rails API sanitize
-- ActionView::Helpers::SanitizeHelper
#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
など)。
更新情報