モダンPerlの世界へようこそ

第42回 Template Toolkit:Perl製テンプレートエンジンのデファクトスタンダード

この記事を読むのに必要な時間:およそ 5 分

Template::Plugin::JavaScriptとTemplate::Plugin::JSON::Escape

Template::Stash::AutoEscapeは便利なモジュールですし,単純なHTMLの場合はこれだけでも十分なセキュリティ対策になりますが,JavaScriptに渡すパラメータを埋め込む場合はさらにJavaScript用の対策を追加する必要があります。問題の詳細についてはma.la氏の記事が非常に行き届いているのでそちらをご覧いただくとして,ここではこの用途で使えるモジュールとして,Template::Plugin::JavaScriptと,もうひとつ,先日公開されたばかりのTemplate::Plugin::JSON::Escapeというモジュールの名前をあげておきます。前者はJavaScript全般,後者はJSONに特化したプラグインで,この原稿を書いている時点ではJSONに固有の\u2028,\u2029問題に対応しているかどうかが両者の顕著な違いといえます。

use strict;
use warnings;
use Template;
use Template::Plugin::JavaScript;
use Template::Plugin::JSON::Escape;
use JSON;

my $tt = Template->new;

my $vars = {
    value => '<foo>',
    hash => {foo => '<bar>'},
    jsonstr => encode_json({foo => '<baz>'}),
};

$tt->process(\(my $tmpl =<<'TMPL'), $vars) or die $tt->error;
[% USE JavaScript -%]
[% USE JSON.Escape -%]
<script>
var value = '[% value | html | js %]';
var hash = [% hash.json %];
var hash = [% jsonstr | json %];
</script>
TMPL

なお,同様のモジュールにMooseベースで書かれたTemplate::Plugin::JSONがありますが,こちらはいまのところセキュリティ系のエスケープはしてくれないため,必要に応じて追加のフィルタをあててください。

Template::Directive::XSSAudit

Template::Directive::XSSAuditは,TTのエンジンの奥深く,テンプレートを解釈してPerlコードに変換する部分に手を入れて,値を埋め込むときに適切なフィルタ(デフォルトではhtmlフィルタかuriフィルタ)があたっていない場合は警告が出るようにしてくれるものです。

use strict;
use warnings;
use Template;
use Template::Directive::XSSAudit;

my $tt = Template->new({
    FACTORY => 'Template::Directive::XSSAudit',
});

$tt->process(\(my $tmpl =<<'TMPL'), {tag => '<foo>'}) or die $tt->error;
[% USE JavaScript -%]
<div>[% tag %]</div>
<div>[% tag | html %]</div>
<script>var value='[% tag | js %]'</script>
TMPL

これを実行すると,以下のようにフィルタがまったくあたっていない箇所や,登録されていないフィルタしかあたっていない箇所がわかります。

<unknown_file>  NO_FILTERS      line:0  tag
<unknown_file>  NO_SAFE_FILTER  line:0  tag     js
<div><foo></div>
<div>&lt;foo&gt;</div>
<script>var value='\x3cfoo\x3e'</script>

もう少し細かいチェックが必要な場合は,on_filteredやon_errorにコールバック関数を渡すことで自前のチェックを追加することもできます。以下の例では,変数名に「_js」という文字列が含まれている場合,htmlフィルタやuriフィルタだけでなくjsフィルタもあたっていないと警告を出すようにしています。

use strict;
use warnings;
use Template;
use Template::Directive::XSSAudit;

my $tt = Template->new({
    FACTORY => 'Template::Directive::XSSAudit',
});

Template::Directive::XSSAudit->on_filtered(sub {
    my $e = shift;
    if ($e->{variable_name} =~ /_js/) {
        unless (grep { $_ eq 'js' }, @{ $e->{filtered_by} || [] }) {
            my $file = $e->{file_name} || '<unknown file>';
            my $line = $e->{file_line} || 0;
            warn "$file\tREQUIRES JS FILTER\tline:$line\t$e->{variable_name}\n";
        }
    }
});

$tt->process(\(my $tmpl =<<'TMPL'), {value_js => q/'+alert(1)+'/}) or die $tt->error;
[% USE JavaScript -%]
<script>
var value = '[% value_js | html %]';
var value = '[% value_js | html | js %]';
</script>
TMPL

Template::AutoFilter

Template::AutoFilterは,最初に紹介したApache::Templateなどと同じく,フロントエンド(と,付随するエンジンの一部)自体を差し替えて自動エスケープに対応しようというものです。使い方は素のTTとほとんど同じですが,こちらはTemplate::Stash::AutoEscapeと違って埋め込む値自体には特に手を加えず,単にフィルタがかかっていないところに指定のフィルタ(デフォルトではhtmlフィルタ)を追加するだけなので,たとえば複数のフィルタをあてたい部分には明示的に複数のフィルタをあてておく必要がありますし,フィルタをあてたくない部分には明示的にnoneという専用のフィルタをあてる必要があります。


use strict;
use warnings;
use Template::AutoFilter;

my $tt = Template::AutoFilter->new;

$tt->process(\(my $tmpl =<<'TMPL'), {tag => '<foo>'}) or die $tt->error;
[% USE JavaScript -%]
<div>[% tag %]</div>
<div>[% tag | none %]</div>
<script>var value='[% tag | html | js %]'</script>
TMPL

TT3

TTが非常に柔軟な(場合によっては生のPerlを書けるテンプレートエンジンよりも融通が利く)テンプレートエンジンであるのは誰もが認めることですし,国内ではSledge海外ではMaypoleや,その後を継いだCatalystといったウェブアプリケーションフレームワークが標準のテンプレートとして採用してきたこともあって,TTはいまでもPerl製テンプレートエンジンのデファクトスタンダードとみなされていますが,ベースとなるモジュールの開発が始まったのは1996年のことですから,いまとなってはさまざまな点で古めかしくなっていますし,柔軟すぎる代償として,実行速度などでも不利を抱えています。

そのため,2009年末にロンドンで開催されたワークショップでは作者のアンディ・ウォードリー(Andy Wardley)氏自身が「Template Toolkit 2はもう捨てたい」と述べていましたし,github上では,次期バージョンの最初のプロトタイプとなるTemplate-TT3に続き,2010年7月からはさらなる高速化をねらって,hempという,将来的にはTT3の心臓部となるであろうCライブラリの開発も始まっています。

ただ,その結果として,CPAN上ではもうかれこれ2年近くTT2の更新が止まっていますし,これまでの経緯を見る限り,TT3が実用段階にたどり着くのはまだまだ先のことでしょうから,速度が必要な現場や省メモリが必要な現場ではTTの代用品を求める動きも出てきています。次回はその動きについてまとめていきます。

著者プロフィール

石垣憲一(いしがきけんいち)

あるときは翻訳家。あるときはPerlプログラマ。先日『カクテルホントのうんちく話』(柴田書店)を上梓。最新刊は『ガリア戦記』(平凡社ライブラリー)。

URLhttp://d.hatena.ne.jp/charsbar/