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

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

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

組み合わせ自由なツールキット

Template Toolkit通称TTは,その名前からもわかるように,もともとは単なるテンプレートエンジンではなく,テンプレートエンジンをつくるためのツール群をまとめたものです。そのツール群を組み合わせた標準のエンジン,標準のフロントエンドと呼べるものもありますが,これはあくまでもTTのよくある利用法のひとつであって,そのすべてではありません。

たとえば,CPANにはApache::Templateという,TTのエンジン部分をmod_perl用にカスタマイズしたうえでmod_perl用のフロントエンドをかぶせるモジュールがありますが,これを使えば,最初に多少の設定は必要になるものの,あとはTTのテンプレートを適切なパスに置くだけで,パラメータの取得からルーティング,レンダリングまでよしなに計らってくれるようになります(TTの文法をサポートしたPHPのようなものになります)⁠また,CPANにはあがっていませんが,O'Reilly社から出ている分厚い解説本(いわゆるアナグマ本)には,Mail::Templateという,TTのテンプレートと埋め込む値を渡したらメールを作成・送信してくれるフロントエンドも紹介されています。

もちろん差し替えられるのはフロントエンドだけではありません。たとえば,テンプレートに埋め込む変数やオブジェクトの処理は,もっとも基本的な構成ではTemplate::Stashというモジュールが行いますが,TTのディストリビューションにはその処理を高速化するTemplate::Stash::XSが同梱されていますし,ウェブ系の人であれば外部テンプレートのエンコーディングの問題を解決するためにCPANにあるTemplate::Provider::Encodingをインストールして使っている方も多いでしょう。そのほかにも,CPANには,かつて標準添付されていたデータベースやXMLを扱うものもあわせて大小170以上のプラグインが登録されていますし,その気になればテンプレートの文法規則自体をがらりと差し替えてしまうこともできます。

テンプレート話の3回目となる今回は,そのようなTTの拡張まわり,とりわけ最近のウェブ業界におけるセキュリティ対策の一環として覚えておきたいいくつかのモジュールについてまとめてみます。

Template::Provider::Encoding

上でも例にあげたTemplate::Provider::Encodingは,直接セキュリティの向上に貢献するものではありませんが,文字化け対策になるのはもちろん,文字数をカウントしたり正規表現を使ったフィルタを適用するときも問題が起こりづらくなるので,日本語を扱うテンプレートでは積極的に使っておきたいモジュールのひとつといえます。

use strict;
use warnings;
use Template;
use Template::Provider;

my $tt = Template->new({
    LOAD_TEMPLATES => [ Template::Provider::Encoding->new ],
});

ただし,Template::Provider::Encodingに同梱されているTemplate::Stash::ForceUTF8については,連載第32回でも紹介したように問題を複雑にしてしまううえ,パフォーマンス面でも問題となりうることがわかってきたので,最近では使わないようにするのがベタープラクティスとなっています※1)⁠

このエンコーディング問題についてはTT本体のほうでも対応が進められています。最初にUnicode対応が行われたのは2004年10月リリースのバージョン2.14で,このときはテンプレートにBOMがついている場合のみPerlの内部表現に変換していましたが,2006年5月にリリースされた2.15では(同年1月にリリースされたTemplate::Provider::Encodingの影響もあって)ENCODINGオプションを渡しておけば,BOMがついていない外部テンプレートも指定したエンコーディングでデコードされるようになりました。この機能については更新履歴などへの記載がなく,ドキュメントでもほとんど触れられていないのですが,扱うテンプレートのエンコーディングがひとつに限られている場合は追加のモジュールをインストールしなくてすむ分気軽に使えます(ただし,複数エンコーディングには対応していないので,携帯対応などでエンコーディングの異なるテンプレートを使い分ける必要がある場合はテンプレートごとにエンコーディングを指定できるTemplate::Provider::Encodingを使ってください)⁠

use strict;
use warnings;
use Template;

my $tt = Template->new({ ENCODING => 'utf8' });

このように内部表現に変換したテンプレートは,そのまま標準出力に送ろうとすると(日本語などが含まれている場合)ワイド文字の警告が出ます。一般的なウェブアプリケーションフレームワークを使っている場合はふつうフレームワーク側で対応済みなので気にする必要はありませんが,スクリプトの中などでテンプレートをそのまま出力したい場合は,いったん変数に受けてからEncode::encodeするなり,binmodeを使って出力用のファイルハンドルに変換用のレイヤをかますなりする必要があります。

binmode STDOUT => ':utf8';
$tt->process('tmpl.tt', \%vars) or die $tt->error;

# または

$tt->process('tmpl.tt', \%vars, \my $output) or die $tt->error;
print Encode::encode(utf8 => $output);
※1
このパフォーマンス上の問題についてはTTのパーサレベルで対応するハックが知られています。詳しくは宮川達彦氏の記事をご覧ください。

Template:Stash::AutoEscape

TTはもともと汎用のツールキットであるだけに,デフォルトでは特にウェブ用のエスケープなどは行いません。そのため,XSSなどの問題を防ぐには変数を埋め込む箇所に明示的にhtmlなどのフィルタをあてる必要がありますが,このような仕組みではテンプレートの記述量が増えるうえに,フィルタのあて忘れやあて間違いも発生しやすく,しばしば批判の対象となってきました。

この問題に対する対策はいくつか存在しますが,比較的単純な要件の場合はma.la氏によるTemplate::Stash::AutoEscapeを使っておくのが簡単です。

use strict;
use warnings;
use Template;
use Template::Stash::AutoEscape;

my $tt = Template->new({
    STASH => Template::Stash::AutoEscape->new,
});

$tt->process(\(my $tmpl =<<'TMPL'), {tag => '<foo>'}) or die $tt->error;
<div>[% tag %]</div>
<div>[% tag.raw %]</div>
TMPL

Template::Stash::AutoEscapeを使うと,特に指定がない変数の埋め込みはすべてHTML用のフィルタを経由するようになります。また,何らかの理由で生のHTMLを埋め込みたい場合はrawという仮想メソッドを利用することでエスケープを回避できます。

このrawメソッドはファイルのインクルードやHTMLを含むマクロなどを書くときに重宝しますが,利用頻度が高いマクロなどでは埋め込むときに毎回rawを指定するのも面倒です。このような場合は,初期化の際にオプションを指定しておけば,特定のマクロやフィルタのエスケープを常時回避できるようになります(以下の例では,hl_rawマクロにはrawメソッドを書かなくてもよいようにしています)⁠

use strict;
use warnings;
use Template;
use Template::Stash::AutoEscape;

my $tt = Template->new({
    STASH => Template::Stash::AutoEscape->new({
        ignore_escape => [qw/hl_raw/],
    }),
});

$tt->process(\(my $tmpl =<<'TMPL'), {tag => '<foo>'}) or die $tt->error;
[% MACRO hl(s) GET '<span class="highlight">' _ s _ '</span>' -%]
[% MACRO hl_raw(s) GET '<span class="highlight">' _ s.raw _ '</span>' -%]
[% USE hlformat = format('<span class="highlight">%s</span>') -%]
<div>[% hl('searched word').raw %]</div>
<div>[% hl('<foo>searched word</foo>').raw %]</div>
<div>[% hl_raw('<foo>searched word</foo>') %]</div>
<div>[% hlformat('<foo>searched word</foo>').raw %]</div>
TMPL

著者プロフィール

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

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

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

コメント

コメントの記入