eRuby（erb）の HTML エスケープ

# 何の話？

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

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

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

[^html]: eRuby は汎用のテンプレート言語であり，生成するテキストは HTML に限らない。

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

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

なお，eRuby という名称や eRuby のテンプレートエンジンについては下記の拙記事も参考にしてほしい。
[Erubi とは何か - Qiita](https://qiita.com/scivola/items/f0a66fd310b252d04d4b)

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

# テンプレートエンジン

eRuby のテンプレートエンジンはいくつもあるが，2021 年時点では，標準添付ライブラリーの [ERB](https://docs.ruby-lang.org/ja/master/class/ERB.html) と，Rails で採用されている [Erubi](https://github.com/jeremyevans/erubi) だけ知っておけばいいと思う。
本記事では説明の都合で，Erubi の前身にあたる [Erubis](https://rubygems.org/gems/erubis) にも触れる。

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

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

# 例題

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

以下，ERB と，Erubis，Erubi で処理するコードを見ていくが，[Tilt](https://github.com/rtomayko/tilt/) というライブラリーを援用すると記述が楽で説明もしやすいので，そうする。

# ERB の場合

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

## レンダリングの仕方

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

```
gem install tilt
```

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

```rb
require "tilt"

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

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

とすれば

```
"&"
```

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

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

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

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

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

```rb
require "cgi/escape"

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

しかし，ERB クラスには ERB::Util という小さなモジュールが定義されていて，そこに [html_escape](https://docs.ruby-lang.org/ja/3.0.0/method/ERB=3a=3aUtil/m/h.html) というメソッドと，そのエイリアスである `h` メソッドが定義されているので，これを使えばよい。

このメソッドは

```rb
require "erb"

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

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

```rb
require "erb"

include ERB::Util

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

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

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

```erb
<%= h("&") %>
```

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

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

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

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

```rb
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` を作るところは一行で

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

と書いてもいい。

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

# Erubis と Erubi 

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

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

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

## レンダリングの仕方

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

```rb
require "tilt"

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

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

Erubis の場合は `Tilt::ErubiTemplate` を `Tilt::ErubisTemplate` に変えるだけ。

結果を見ると，確かに

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

となっている。

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

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

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

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

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

```rb
require "tilt"

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

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

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

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

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

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

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

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

[^ukr]: しかし，実際しばしば書き忘れるのである。

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

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

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

[^slm]: もっとも，私は HTML をほぼ Slim で書くので，HTML 生成に eRuby を使う機会はほとんどないのだが。

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

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