何でも埋め込めるのは楽ですが
前回紹介したHTML::MasonやText::MicroTemplateのように生のPerlコードを埋め込めるテンプレートエンジンは,
ただし,
そのこと自体は,
- ※1
テンプレートのテストというとピンと来ない人もいるかもしれませんが,
XSS対策などは, 基本的にはテンプレートとテストデータがあればテストできるものです。危険な文字はテンプレートエンジンのほうでかならず実体参照に変換するようになっているのでテストは省略しても大丈夫と判断できる場合もありますが, 自動でエスケープできるテンプレートエンジンでもファイルインクルードなどに対応するためにエスケープなしでデータを埋め込めるような抜け道は用意されているものですし, ユーザが任意のHTMLやCSSを埋め込めるようになっているような場合は, 実体参照への変換だけでは十分な対策とは言えません。最終的にはブラウザの実装の違いや併用しているJavaScriptの問題なども考慮する必要があるのでPerlレベルのテストだけでは完璧とは言えませんが, セキュリティまわりのバグは周囲に与える影響も大きいので, これで大丈夫なはずと過信するよりは, 可能な範囲でテストを書き, またテストを書けるようにしておいたほうが後々のためです。
テンプレートからコードを追い出す
この問題を解決する方法はいくつか考えられますが,
いわゆるモデル層については,
もう少し具体的にいうと,
このような処理は,
HTML::Template
ドットコムバブル真っ盛りの1999年6月に生まれたHTML::Templateは,
一例として,
use strict;
use warnings;
use HTML::Template;
use ORDB::CPANUploads;
use Time::Piece;
my $users = ORDB::CPANUploads::Uploads->select("order by released desc limit 10");
my $template = <<'TMPL';
<html>
<body>
<TMPL_IF NAME="users">
<ul>
<TMPL_LOOP NAME="users">
<li><TMPL_VAR NAME="dist"> (<TMPL_VAR NAME="author">; <TMPL_VAR NAME="released">)</li>
</TMPL_LOOP>
</ul>
<TMPL_ELSE>
<p>Not found</p>
</TMPL_IF>
</body>
</html>
TMPL
my %params = (users => [
map { +{
dist => $_->dist,
author => $_->author,
released => Time::Piece->new($_->released)->ymd,
}} @$users
]);
my $ht = HTML::Template->new(
scalarref => \$template,
default_escape => 'HTML',
);
$ht->param(%params);
$ht->output(print_to => \*STDOUT);
なお,
use strict;
use warnings;
use HTML::Template;
use CGI;
my $cgi = CGI->new;
my $ht = HTML::Template->new(
associate => $cgi,
default_escape => 'HTML',
);
# $ht->param($_ => $cgi->param($_)) for $cgi->param; は不要
- ※2
ここでは値を取り出す部分をスクリプトの中にベタ書きしていますが,
実際のアプリケーションではORDB::CPANUploadsをラップして, 返り値にオブジェクトを含まないようにしたほうがよいでしょう。あるいは, O/ Rマッパを使わず素のDBI (と, 必要ならSQL::AbstractやSQL::MakerのようなSQLを構築するためのモジュール) を使うことにすればfetchrow_ arrayrefなどで取り出した値をそのまま利用できます。