3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

RubyAdvent Calendar 2021

Day 9

eRuby(erb)の HTML エスケープ

Last updated at Posted at 2021-12-08

何の話?

この記事のテーマは eRuby に <%= %> などの手段で文字列を挿入するときの HTML エスケープについて。

「eRuby」はテキスト(要するに文字列)に Ruby のコードを埋め込むことができるテンプレート言語の名前。しかし,この名称よりも「ERB」とか「erb」という呼び方で認識している人のほうが多いかもしれない。

「HTML エスケープ」は < とか & といった文字を &lt; とか &amp; といったものに変換する文字列処理。
eRuby の典型的な応用は HTML の生成なので1,式の値(を文字列化したもの)をそのまま挿入したい場合もあれば,HTML エスケープして挿入したい場合もある。

Ruby on Rails では HTML エスケープについてきちんと意識しなくてもよしなに取り計らってくれたりするが,本記事ではテンプレートエンジンをあらわに扱って処理する場合に知っておきたいことをまとめる。

eRuby のテンプレートエンジンとしては,ERB と Erubi を主に取り上げる。

なお,eRuby という名称や eRuby のテンプレートエンジンについては下記の拙記事も参考にしてほしい。
Erubi とは何か - Qiita

ところで,「エスケープ」にもいろいろあるが,本記事では HTML エスケープしか扱わないので,以下では「HTML」を略すことがある。

テンプレートエンジン

eRuby のテンプレートエンジンはいくつもあるが,2021 年時点では,標準添付ライブラリーの ERB と,Rails で採用されている Erubi だけ知っておけばいいと思う。
本記事では説明の都合で,Erubi の前身にあたる Erubis にも触れる。

歴史的には ERB → Erubis → Erubi の順に現れた。

以下,本記事では「ERB」という表記は標準添付ライブラリーで定義された ERB クラスのみを指すこととする。テンプレート言語の名前は「eRuby」とのみ表記する。

例題

この記事では,単純に "&" という文字列を,HTML エスケープ無しと有りの二とおりに挿入したテキストを生成してみる。

以下,ERB と,Erubis,Erubi で処理するコードを見ていくが,Tilt というライブラリーを援用すると記述が楽で説明もしやすいので,そうする。

ERB の場合

ERB ではテキスト中に式の値(を文字列化したもの)を挿入する手段は <%= %> しかない。
また,HTML エスケープは自動的にはされない。

レンダリングの仕方

では処理コードを見てみよう。
あらかじめ

gem install tilt

で Tilt をインストールしておき,

require "tilt"

erb_text = "<%= '&' %>"

p Tilt::ERBTemplate.new{erb_text}.render

とすれば

"&"

と表示される。確かにエスケープはされていない。
require "erb" はあらわに書かなくても require してくれるようだ)

コードの意味を少し説明する。
Tilt::ERBTemplate というのは,Tilt で定義された ERB ベースのテンプレート処理クラス2
eRuby スクリプトを引数ではなくブロックで与えることに注意されたい。
一方,eRuby スクリプトそのものではなく,eRuby スクリプトのファイルパスを与える場合はブロックではなく引数として与える。

エスケープさせたいときは

エスケープさせたいときは,エスケープ用のメソッドを使う。
たとえば標準添付ライブラリーの cgi/escape には escapeHTML というメソッドが定義されている。
こんなふうに使える:

require "cgi/escape"

puts CGI.escapeHTML("&") # => "&amp;"

しかし,ERB クラスには ERB::Util という小さなモジュールが定義されていて,そこに html_escape というメソッドと,そのエイリアスである h メソッドが定義されているので,これを使えばよい。

このメソッドは

require "erb"

p ERB::Util.h("&") # => "&amp;"

のように使える。
ERB::Util のメソッドはいわゆるモジュール関数として定義されているので,(ちょうど Math モジュールの関数たちと同じように)モジュールの特異メソッドしてだけでなく,

require "erb"

include ERB::Util

p h("&") # => "&amp;"

のような使い方も可能だ。

このモジュールを使えば,"&" をエスケープして挿入する eRuby スクリプトは

<%= h("&") %>

のように書くことができるはずだ。
あるいはもっとシンプルに

<%=h "&" %>

と書いてもいい。h の前にスペースを書かなかったのは,まあなんというか,癖というか好みのようなもの。

では eRuby スクリプト中で h メソッドが使えるようにするにはどうすればよいのか?
それには,h メソッドを持つオブジェクトを render メソッドに与えればよい。このようなオブジェクトを「スコープ」と呼ぶらしい。
具体的には,以下のようにする。

require "tilt"
require "erb"

erb = "<%=h '&' %>"

scope = Object.new
scope.extend ERB::Util

p Tilt::ERBTemplate.new{erb}.render(scope) # => "&amp;"

オブジェクトを一つ作って,ERB::Util を extend し,それを render に引数として与えればよいのだ。
scope を作るところは一行で

scope = Object.new.extend(ERB::Util)

と書いてもいい。

余談だが,eRuby スクリプトに埋め込まれた Ruby コード部分では,render メソッドにスコープとして渡されたオブジェクトが self になる。
したがって,もし上記のコードで scope がインスタンス変数 @foo を持っていたとしたら,eRuby スクリプト中でそれを直に @foo として参照することができる。

Erubis と Erubi

Erubis と Erubi では,式の値(を文字列化したもの)を挿入する手段として <%= %> のほかに <%== %> がある。
両者の違いはエスケープされるかどうかだけ。
デフォルトでは

  • <%= %> エスケープされない
  • <%== %> エスケープされる

なのだが,オプションにより逆にすることができる。

レンダリングの仕方

Erubis と Erubi は同じ使い方なので,Erubi のコードで示す。

require "tilt"

erb_text = "<%= '&' %> <%== '&' %>"

p Tilt::ErubiTemplate.new{erb_text}.render
# => "& &amp;"

Erubis の場合は Tilt::ErubiTemplateTilt::ErubisTemplate に変えるだけ。

結果を見ると,確かに

  • <%= %> エスケープ無し
  • <%== %> エスケープ有り

となっている。

エスケープの有無を逆転させるには

おそらく eRuby を書いたことのある人の多くは(デフォルトの逆の)

  • <%= %> エスケープ有り
  • <%== %> エスケープ無し

に馴染んでいると思う。
Ruby on Rails がバージョン 3 以降そうなっているからだ。
もっとも,Rails の場合,ActiveSupport::SafeBuffer という String を継承したクラスを導入して,エスケープすべきかどうかを自動判定してよきに計らってくれるようになっているので,<%== %> はあまり出番が無いかもしれない。
Rails については本記事のテーマを外れるので,これ以上触れない。

エスケープの有無を上記のように逆転させるには,escape というオプションをキーワード引数として与え,

require "tilt"

erb_text = "<%= '&' %> <%== '&' %>"

p Tilt::ErubisTemplate.new(escape: true){erb_text}.render
# => "& &amp;"

とすればよい。
結果を見れば,確かにデフォルトとはエスケープの有無が逆になっている。

オプションで逆ってややこしい?

オプション如何で <%= %><%== %> の意味が全く逆になり,しかも世間ではデフォルトと逆の設定が普通である,という状況はややこしくないだろうか?
eRuby スクリプトだけを見たのでは <%= %> の意味がどちらであるか分からないのだ。

ここからは推測だが,こうなっているのは歴史的な事情ではないかと思う。

既に見たように ERB ではエスケープするために式の中でエスケープ用メソッドを使う必要があった。
Erubis は <%== %> という形式を新たに付け加えることで,自動エスケープ機能を実現した。

ここでこういう考え方が起こる(完全に憶測というか妄想)。HTML 生成において,エスケープはセキュリティーに関わる重大事である。「うっかり h を書き忘れた」は許されない3。自動エスケープこそが基本であるべきではないか。

なので,最もシンプルな <%= %> をエスケープ有りとし,複雑な <%== %> をエスケープ無しとする設定を用意したところ,多くのユーザーに支持された。そういうことではないのか。

こういう話は,Erubis のドキュメントか,Rails 3 のドキュメントに書いてあるかもしれないが,もう(私には)見つけられない。

私もこちら(<%= %> がエスケープ)を断然支持する4。経験上,うっかり <%= %> とすることはあっても,うっかり <%== %> とすることはまず無いからだ。それにイコールを二つ重ねたほうがいかにも「そ・の・ま・ん・ま」感があるし。

最初からそうなってればよかったのに,と思わなくもないが,ERB との互換性を考慮してこういう仕様になっているのかもしれない。

余談だが,eRuby とは全く違うテンプレートシステム Slim でも,= で「エスケープ有りの挿入」,== で「エスケープ無しの挿入」となっている。

  1. eRuby は汎用のテンプレート言語であり,生成するテキストは HTML に限らない。

  2. 「テンプレート処理クラス」という表現は苦し紛れに考えたもので,どう呼ぶのが妥当なのか知らない。しかし「テンプレートエンジン」と呼ぶのは問題があると思う。
    出来たオブジェクトの render メソッドを呼べばレンダリングした結果が返る。

  3. しかし,実際しばしば書き忘れるのである。

  4. もっとも,私は HTML をほぼ Slim で書くので,HTML 生成に eRuby を使う機会はほとんどないのだが。

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?