2014年4月にリリースされたClosure Templates 2.4では、いくつか面白い新機能が追加されています。
前記事ではパラメータの型チェック機能について紹介したので、今回はStrict Autoescapingについて紹介します。
Contextual Autoescapingの廃止
Closure Templatesには、Cotextual Autoescapingという強力なオートエスケープ機能があります。
詳しくは上記の記事を参照してもらえるといいのですが、要は出力先がHTMLであるのか、JavaScriptなのか、CSSなのかというコンテキストを自動的に判別して、最適なエスケープをおこなってくれる機能です。
しかしこのContextual Autoescapingは、Closure Templates 2.4で非推奨(deprecated)な機能になっています。
非推奨になっている理由としては、Contextual Autoescapingでは二重エスケープをしてしまう場合があるからです。
二重エスケープの具体的な例をみてみましょう。
下記の例では、let
で変数を組み立てるときと、{$content}
を出力するときの2回HTMLエスケープがおこなわれてしまいます。
そのため、出力される結果は意図しないものとなってしまうでしょう。私もよくやってしまいます。
(templateにautoescape="contextual"
またはautoescape="deprecated-contextual"
を指定すると、Contextual Autoescapingが利用されます)
{namespace example.templates.contextual}
/**
* @param message
*/
{template .render autoescape="deprecated-contextual"}
{let $content}
<pre>{$message}</pre>
{/let}
{$content}
{/template}
上記のような小さなテンプレートであれば分かりやすいので問題ないのですが、大規模なテンプレートを開発している場合は、対象の変数がエスケープ済みなのかどうかを追跡するのが難しくなります。
そこで、エスケープしたかどうかが分かるように変数名にsafe_
というプリフィックスをつけて、出力する際にはnoAutoescape
フィルタを利用する手法を用いることが多いのではないでしょうか。
{namespace example.templates.contextual}
/**
* @param message
*/
{template .render autoescape="deprecated-contextual"}
{let $safe_content}
<pre>{$message}</pre>
{/let}
{$safe_content|noAutoescape}
{/template}
でもプリフィックスとか付けるの面倒ですよね。
それにsafe_
が付いてる変数なのにエスケープし忘れた値を入れてしまったりすると目も当てられません。
Strict Autoescaping
前述したように、Contextual Autoescapingには問題があることが分かりました。
そこで、Closure Templates 2.4では、Contextual Autoescapingでコンテキストを自動判別することを諦めて、出力する文字列の種類を明示的に指定するStrict Autoescapingという方法を導入しました。
Strict Autoescapingでは、出力する文字列の種類を明示することによって、文字列を単純なstring型として扱うのではなく、SanitizedContentと呼ばれるオブジェクトとして扱えるようになります。
これにより、変数に代入するような中間出力のときはエスケープせずにSanitizedContentのまま扱い、最後に出力するときだけエスケープがおこなえるようになります。これで二重エスケープの心配がなくなります。
具体的な利用方法をみてみましょう。
templateにautoescape="strict"
を指定すると、Strict Autoescapingが利用されます。
そしてtemplate
やlet
にkind="html"
を指定することで、このテンプレートや変数がHTMLとして出力されることを明示しています。
{namespace example.templates.strict}
/**
* @param message
*/
{template .render autoescape="strict" kind="html"}
{let $content kind="html"}
<pre>{$message}</pre>
{/let}
{$content}
{/template}
kind
を指定できるのは、templateの定義(template)、templateのパラメータ定義(param)、変数定義(let)の3箇所です。
また、kind
にはhtml
, attributes
, text
, uri
, css
, js
の6種類を指定することができます。ここで指定した種類に応じて最適なエスケープがおこなわれるわけです。
なお、Strict Autoescapingモードでkind
を指定し忘れると、テンプレートをコンパイルした時にエラーを出してくれます。ありがたいですね。
また、前記事で紹介したパラメータの型を指定する方法で、型名にhtml
やtext
などkindに指定できるのと同じものを指定することもできます。
{namespace example.templates.html_typed}
/**
*/
{template .child autoescape="strict" kind="html"}
{@param content : html}
{$content}
{/template}
まとめ
kind="html"
のように、出力の種類を毎回指定するのは面倒に感じるかもしれませんが、出力する変数がエスケープ済みなのかどうかを考えながらテンプレートを書くほうが面倒です。
大きなテンプレートファイルを書く場合には、積極的にStrict Autoescapingを使っていきたいですね。
なお、本記事のサンプルについてはGitHubにアップしてあります。
ちなみに
AngularJSには、SCE(Strict Contextual Escaping)という仕組みがあります。
SCEは、Closure TemplatesのContextual AutoescapingとStrict Autoescapingのいいとこ取りをしたような仕組みになっています。
おそらく、Closure Templatesは文字列ベースのテンプレートエンジンなので、validでないHTMLを扱う必要があって正確にコンテキストを自動判別することができなかったのでしょう。
一方のAngularJSはHTMLベースのテンプレートエンジンなので、正確にコンテキストの自動判別ができたということなのでしょう。