RailsビューのHTMLエスケープは#link_toなどのヘルパーメソッドで解除されることがある

こんにちは、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 以前の案件を扱う場合、マニュアルでのエスケープ処理が不完全だったり、まったく行われていない可能性があるので注意が必要です。 3.0 Release note: Action View 7.4.3 Other Changes ERBやhamlでのエスケープ解除 ERB ERB で<%==と%>で記述したコードの出力は、エスケープされなくなります。 <%== エスケープされないRubyコード %> 結果をエスケープしないで出力 haml hamlの場合は =の代わりに!= を使います。 = “I feel <strong>!” <!– エスケープされる –> != “I feel <strong>!” <!– エスケープされない –> Unescaping HTML: != 個人的にはこれらの形式のエスケープ表記は見落としそうなので使っていません。 #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?と一貫させるためなのかもしれませんが。 ActiveSupport::SafeBuffer #raw #rawは上述のとおり、#html_safeと同等です。 <!– ビューのERB –> <%= raw @h1 %> ActionView::Helper #sanitize #sanitizeは、tagsやattributesで指定されていないタグや属性をすべて除去します。href属性やsrc属性にjavascript:などの安全でないプロトコルが指定されている場合も削除します。 sanitize(文字列 [, tags => “許可するHTMLタグ名”, attributes => “許可するHTML属性名”]) サニタイズ(sanitize) ユーザー入力に#sanitizeを使っても、<、>、&などが残る可能性があるので、安全は保証されません。 ActionView::Helper #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など)。 関連記事 Haml で閉じタグに悪戦苦闘してRails を学んだお話 [Rails]ビューで配列を改行するなら脆弱なjoinではなくsafe_joinにしよう Rails 4.0.2, 3.2.16リリース!重大なセキュリティFIXがあります