How and when Xslate escapes html special characters
Xslate のエスケープポリシーについて考えたので、ここでまとめておく*1。
Xslate のエスケープ処理について、覚えることは以下の三つである*2。
以下、詳しく解説する。
まず、基本的には Text::MicroTemplate のポリシーを踏襲している*3。すなわち、テンプレートタグ内で生成された文字列については、HTMLのメタ文字(< > & " &apos)が自動的にエスケープされる。エスケープ処理は、一般式に対する出力コマンドが担っている。
# Text::Xslate version 0.1032 $ xslate -e '<foo>' # 地の文字列はそのまま出力 <foo> $ xslate -e '<: $ARGV[0] :>' '<foo>' # 変数はエスケープされる <foo> $ xslate -e '<: "<" ~ $ARGV[0] ~ ">" :>' foo # 式の結果もエスケープされる <foo> $ xslate -e '<: $ARGV.join(" ") :>' '<foo>' '<bar>' # 関数やメソッドの戻り値もエスケープされる <foo> <bar>
エスケープを抑制するためには、raw markをつける。これの実体は文字列化演算子をオーバーロードしたラッパーオブジェクトであり、出力コマンドはこのraw markを見るとその値を加工せずにそのまま出力する*4。この raw mark は 'mark_raw' というフィルタでつけられる*5。
$ xslate -e '<: $ARGV[0] | mark_raw :>' '<foo>' # そのまま出力される <foo> $
強制的にエスケープしたいときは、このraw markを解除してから出力すればよい*6。raw markの解除には 'unmark_raw' フィルタを使う。たとえば、マクロはraw文字列を返すため、マクロの戻り値をエスケープする時などにはこのフィルタを使う。
$ xslate -e '<: macro foo -> {:><foo><: } :><: foo() :>' # マクロの戻り値はraw文字列である <foo> $ xslate -e '<: macro foo -> {:><foo><: } :><: foo() | unmark_raw :>' # raw markを解除するとエスケープされる <foo>
'mark_raw' と 'unmark_raw' は何度重ねても効果は重複しない。
$ xslate -e '<: $ARGV[0] | mark_raw | mark_raw | unmark_raw :>' '<foo>' # 何度markを重ねがけしても、最後にunmarkすると強制的にエスケープされる <foo>
なお、raw文字列を連結した際の挙動や、また、関数に渡した際の扱いは未定義である。このあたりは安全性をもっと検討してから決めたい。
最後に、これらのオーバーヘッドだが、'mark_raw'は新たにSVを生成したりbless()したりしなければならないため、その適用にはある程度時間がかかる。ただし、出力する直前に 'mark_raw' を使う場合はフィルタの適用が最適化で取り除かれるため、オーバーヘッドは全くなくなる。'unmark_raw' については、フィルタの適用そのものは取り除けないものの、新たにSVを生成することはないため、オーバーヘッドは極めて少ない。したがって、ほとんどのケースではこれらのオーバーヘッドは無視できる程度だと考えてよい。
*1:as of version 0.1032
*2:なお、newのオプション escape => 'none' のもとでは挙動が異なるが、これはメールなどの非HTMLテキストを出力するために用意されているモードであり、このモードでHTMLを出力するべきではない。したがって詳細は語らない。
*3:Kazuhoさんの記事Text::MicroTemplate - テンプレートエンジンのセキュリティと利便性も参照のこと。
*4:このあたりの実装はMTと同じである。
*5:0.1031までは 'raw' という名前だった。なお、最新版でも古い名前はサポートされる。
*6:Xslate 0.1031 までは、'html' フィルタでエスケープするしかなかった。こちらは、実際にエスケープ処理を行ってからraw文字列を返す。ただし、このフィルタはraw文字列を受け取るとエスケープせずにそのまま返すため、マクロの出力を強制エスケープできない。