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

こんにちは、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エスケープについてまずまとめました。

エスケープした場合としなかった場合

コントローラとビューにそれぞれ以下のように書いたとします。

  ## コントローラ
  @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 以降のビューで表示される変数はデフォルトですべてエスケープされます。必要な場合にのみ、このエスケープを解除することになります。

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)

ユーザー入力に#sanitizeを使っても、<、>、&などが残る可能性があるので、安全は保証されません。

#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など)。

関連記事

Ruby on RailsによるWEBシステム開発、Android/iPhoneアプリ開発、電子書籍配信のことならお任せください この記事を書いた人と働こう! Ruby on Rails の開発なら実績豊富なBPS

この記事の著者

hachi8833

Twitter: @hachi8833、GitHub: @hachi8833 コボラー、ITコンサル、ローカライズ業界、Rails開発を経てTechRachoの編集・記事作成を担当。 これまでにRuby on Rails チュートリアル第2版の半分ほど、Railsガイドの初期翻訳ではほぼすべてを翻訳。その後も折に触れてそれぞれ一部を翻訳。 かと思うと、正規表現の粋を尽くした日本語エラーチェックサービス enno.jpを運営。 実は最近Go言語が好き。 仕事に関係ないすっとこブログ「あけてくれ」は2000年頃から多少の中断をはさんで継続、現在はnote.muに移転。

hachi8833の書いた記事

週刊Railsウォッチ

インフラ

BigBinary記事より

ActiveSupport探訪シリーズ