Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

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

更新情報

  • 2016/08/31: 初版公開
  • 2022/05/31: 更新

こんにちは、hachi8833です。

160825_0008_G0M3ns

ここでは次のような結果を期待していました。以下コード中の&は、エスケープを表示するために全角の&にしています。

<%= 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のやりとりではこのような出力を期待していました。

160830_1633_Ngn2Gj

@h1変数がエスケープされなければ以下が出力され、

  <!-- ビューのERBから出力されるHTML -->
  <h1>Railsドキュメント</h1>

ブラウザでh1タグとして解釈されてたとえば以下のように表示されます。

160830_1647_iOgHrq

ビューの文字列はデフォルトでエスケープされる

安全のため、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 API String#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_sString()はこの目的には使えません。

追伸

#link_toに限らず、ヘルパーメソッドの多くはエスケープ対象となりません#button_to#content_tag#form_for#collection_check_boxesなど)。

関連記事

[Rails]ビューで配列を改行するなら脆弱なjoinではなくsafe_joinにしよう


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。