diff options
-rwxr-xr-x | LedgerSMB/Template.pm | 14 | ||||
-rwxr-xr-x | LedgerSMB/Template/HTML.pm | 14 | ||||
-rw-r--r-- | t/01-load.t | 4 | ||||
-rw-r--r-- | t/04-template-handling.t | 171 | ||||
-rw-r--r-- | t/98-pod-coverage.t | 3 | ||||
-rw-r--r-- | t/data/04-template.html | 2 |
6 files changed, 197 insertions, 11 deletions
diff --git a/LedgerSMB/Template.pm b/LedgerSMB/Template.pm index 938b8254..f302f3fc 100755 --- a/LedgerSMB/Template.pm +++ b/LedgerSMB/Template.pm @@ -39,12 +39,13 @@ option any later version. A copy of the license should have been included with your software. =cut + +package LedgerSMB::Template; + use Error qw(:try); use Template; use LedgerSMB::Sysconfig; -package LedgerSMB::Template; - sub new { my $class = shift; my $self = {}; @@ -89,6 +90,7 @@ sub render { my $self = shift; my $vars = shift; my $template; + my $format = "LedgerSMB::Template::$self->{format}"; $template = Template->new({ INCLUDE_PATH => $self->{include_path}, @@ -97,22 +99,22 @@ sub render { DELIMITER => ';', }) || throw Error::Simple Template->error(); - eval "require LedgerSMB::Template::$self->{format}"; + eval "require $format"; if ($@) { throw Error::Simple $@; } - my $cleanvars = &{"LedgerSMB::Template::$self->{format}::preprocess"}($vars); + my $cleanvars = $format->can('preprocess')->($vars); if (UNIVERSAL::isa($self->{locale}, 'LedgerSMB::Locale')){ $cleanvars->{text} = \&$self->{locale}->text(); } if (not $template->process( - &{"LedgerSMB::Template::$self->{format}::get_template"}($self->{template}), + $format->can('get_template')->($self->{template}), $cleanvars, \$self->{output}, binmode => ':utf8')) { throw Error::Simple $template->error(); } - &{"LedgerSMB::Template::$self->{format}::postprocess"}($self); + $format->can('postprocess')->($self); return $self->{output}; } diff --git a/LedgerSMB/Template/HTML.pm b/LedgerSMB/Template/HTML.pm index 9f1ae252..d052319d 100755 --- a/LedgerSMB/Template/HTML.pm +++ b/LedgerSMB/Template/HTML.pm @@ -9,6 +9,8 @@ LedgerSMB::Template::HTML Template support module for LedgerSMB =item get_template () +Returns the appropriate template filename for this format. + =item preprocess ($vars) This method returns a reference to a hash that contains a copy of the passed @@ -16,6 +18,8 @@ hashref's data with HTML entities converted to escapes. =item postprocess () +Currently does nothing. + =back =head1 Copyright (C) 2007, The LedgerSMB core team. @@ -29,11 +33,11 @@ including contact information of contributors, maintainers, and copyright holders, see the CONTRIBUTORS file. =cut +package LedgerSMB::Template::HTML; + use Error qw(:try); use CGI; -package LedgerSMB::Template::HTML; - sub get_template { my $name = shift; return "${name}.html"; @@ -46,15 +50,19 @@ sub preprocess { #XXX fix escaping function if ( $type eq 'ARRAY' ) { + for (@{$rawvars}) { + push @{$vars}, preprocess( $_ ); + } } elsif ( $type eq 'HASH' ) { for ( keys %{$rawvars} ) { - $vars->{$_} = preprocess( $rawvars[$_] ); + $vars->{$_} = preprocess( $rawvars->{$_} ); } } else { return CGI::escapeHTML($rawvars); } + return $vars; } sub postprocess { diff --git a/t/01-load.t b/t/01-load.t index 60461f02..d2cde8f1 100644 --- a/t/01-load.t +++ b/t/01-load.t @@ -2,7 +2,7 @@ use strict; use warnings; -use Test::More tests => 30; +use Test::More tests => 32; use_ok('LedgerSMB'); use_ok('LedgerSMB::AA'); @@ -32,6 +32,8 @@ use_ok('LedgerSMB::RP'); use_ok('LedgerSMB::Session'); use_ok('LedgerSMB::Sysconfig'); use_ok('LedgerSMB::Tax'); +use_ok('LedgerSMB::Template'); +use_ok('LedgerSMB::Template::HTML'); use_ok('LedgerSMB::User'); SKIP: { diff --git a/t/04-template-handling.t b/t/04-template-handling.t new file mode 100644 index 00000000..8399a8b1 --- /dev/null +++ b/t/04-template-handling.t @@ -0,0 +1,171 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +$ENV{TMPDIR} = 't/var'; + +use Test::More 'no_plan'; +use Test::Trap qw(trap $trap); +use Test::Exception; + +use Error qw(:try); + +use LedgerSMB::AM; +use LedgerSMB::Form; +use LedgerSMB::Sysconfig; +use LedgerSMB::Template; +use LedgerSMB::Template::HTML; + +my @r; +my $temp; +my $form; +my $myconfig; +my $template; +my $FH; + +# AM->check_template_name checks +# check_template operates by calling $form->error if the checks fail +$form = new Form; +$myconfig = {'templates' => 'test'}; +for my $ext ('css', 'tex', 'txt', 'html', 'xml') { + $form->{file} = "test/apples.${ext}"; + @r = trap{AM->check_template_name($myconfig, $form)}; + ok(!defined $trap->die, + "AM, check_template_name: Template directory, ${ext}"); +} +$form->{file} = 'css/apples.txt'; +@r = trap{AM->check_template_name($myconfig, $form)}; +ok(!defined $trap->die, + 'AM, check_template_name: CSS directory, txt'); +$form->{file} = 'test2/apples.txt'; +@r = trap{AM->check_template_name($myconfig, $form)}; +is($trap->die, "Error: Not in a whitelisted directory: test2/apples.txt\n", + 'AM, check_template_name: Invalid directory, non-css denial'); +$form->{file} = 'test/apples.exe'; +@r = trap{AM->check_template_name($myconfig, $form)}; +is($trap->die, "Error: Error: File is of type that is not allowed.\n", + 'AM, check_template_name: Disallowed type denial'); + +# adjusting backuppath to avoid triggering directory traversal detection +$temp = ${LedgerSMB::Sysconfig::backuppath}; +${LedgerSMB::Sysconfig::backuppath} = "foo"; +$form->{file} = "${LedgerSMB::Sysconfig::backuppath}/apples.txt"; +@r = trap{AM->check_template_name($myconfig, $form)}; +is($trap->die, "Error: Not allowed to access foo/ with this method\n", + 'AM, check_template_name: Backup path denial'); +${LedgerSMB::Sysconfig::backuppath} = $temp; + +$form->{file} = "css/../apples.txt"; +@r = trap{AM->check_template_name($myconfig, $form)}; +is($trap->die, "Error: Directory transversal not allowed.\n", + 'AM, check_template_name: Directory transversal denial 1'); +$form->{file} = "/tmp/apples.txt"; +@r = trap{AM->check_template_name($myconfig, $form)}; +is($trap->die, "Error: Directory transversal not allowed.\n", + 'AM, check_template_name: Directory transversal denial 2'); +$form->{file} = "test/apples.txt:evil"; +@r = trap{AM->check_template_name($myconfig, $form)}; +is($trap->die, "Error: Directory transversal not allowed.\n", + 'AM, check_template_name: Directory transversal denial 3'); +$form->{file} = "c:\\evil.txt"; +@r = trap{AM->check_template_name($myconfig, $form)}; +is($trap->die, "Error: Directory transversal not allowed.\n", + 'AM, check_template_name: Directory transversal denial 4'); + +# AM->load_template checks +# load_template takes its file name from form +$form = new Form; +$myconfig = {'templates' => 't/data'}; +$form->{file} = 't/data/04-not-there.txt'; +@r = trap{AM->load_template($myconfig, $form)}; +is($trap->die, "Error: t/data/04-not-there.txt : No such file or directory\n", + 'AM, load_template: Die on non-existent file'); +$form->{file} = 't/data/04-template.html'; +AM->load_template($myconfig, $form); +is($form->{body}, "I am a template.\nLook at me <?lsmb login ?>.\n", + 'AM, load_template: Read existing file'); + +# AM->save_template checks +$form = new Form; +$myconfig = {'templates' => 't/var/not here'}; +$form->{body} = "I am a template.\nLook at me.\n"; +$form->{file} = "$myconfig->{templates}/test.txt"; +@r = trap{AM->save_template($myconfig, $form)}; +is($trap->die, + "Error: t/var/not here/test.txt : No such file or directory\n", + 'AM, save_template: Die on unwritable file'); +$myconfig = {'templates' => 't/var'}; +$form->{body} = "I am a template.\nLook at me."; +$form->{file} = "$myconfig->{templates}/04-template-save-test-$$.txt"; +ok(!-e $form->{file}, 'AM, save_template: Environment clean'); +AM->save_template($myconfig, $form); +ok(-e $form->{file}, 'AM, save_template: File created'); +open($FH, '<', $form->{file}); +@r = <$FH>; +close($FH); +chomp(@r); +is(join("\n", @r), $form->{body}, 'AM, save_template: Good save'); +is(unlink($form->{file}), 1, 'AM, save_template: removing testfile'); +ok(!-e $form->{file}, 'AM, save_template: testfile removed'); + +# Template->new +$myconfig = {'templates' => 't/data'}; +throws_ok{new LedgerSMB::Template('user' => $myconfig, 'language' => 'x/0')} + qr/Invalid language/, 'Template, new: Invalid language caught 1'; +throws_ok{new LedgerSMB::Template('user' => $myconfig, 'language' => '1\\2')} + qr/Invalid language/, 'Template, new: Invalid language caught 2'; +throws_ok{new LedgerSMB::Template('user' => $myconfig, 'language' => '1:2')} + qr/Invalid language/, 'Template, new: Invalid language caught 3'; +throws_ok{new LedgerSMB::Template('user' => $myconfig, 'language' => '..')} + qr/Invalid language/, 'Template, new: Invalid language caught 4'; +throws_ok{new LedgerSMB::Template('user' => $myconfig, 'language' => '.svn')} + qr/Invalid language/, + 'Template, new: Invalid language caught 5'; +$template = undef; +$template = new LedgerSMB::Template('user' => $myconfig, 'language' => 'de'); +ok(defined $template, 'Template, new: Object creation with valid language'); +isa_ok($template, 'LedgerSMB::Template', + 'Template, new: Object creation with valid language'); +is($template->{include_path}, 't/data/de;t/data', + 'Template, new: Object creation with valid language has good include_path'); +$template = undef; +$template = new LedgerSMB::Template('user' => $myconfig, 'language' => 'de', + 'path' => 't/data'); +ok(defined $template, + 'Template, new: Object creation with valid language and path'); +isa_ok($template, 'LedgerSMB::Template', + 'Template, new: Object creation with valid language and path'); +is($template->{include_path}, 't/data', + 'Template, new: Object creation with valid path overrides language'); +$template = undef; +$template = new LedgerSMB::Template('user' => $myconfig, 'format' => 'HTML', + 'template' => '04-template'); +is(LedgerSMB::Template::HTML::get_template('04-template'), '04-template.html', + 'HTML, get_template: Returned correct template file name'); +is(LedgerSMB::Template::HTML::preprocess('04-template'), '04-template', + 'HTML, preprocess: Returned simple string unchanged'); +is(LedgerSMB::Template::HTML::preprocess('14 > 12'), '14 > 12', + 'HTML, preprocess: Returned properly escaped string'); +is_deeply(LedgerSMB::Template::HTML::preprocess([0, 'apple', 'mango&durian']), + [0, 'apple', 'mango&durian'], + 'HTML, preprocess: Returned properly escaped array ref contents'); +is_deeply(LedgerSMB::Template::HTML::preprocess({'fruit' => '&veggies', + 'test' => 1}), + {'fruit' => '&veggies', 'test' => 1}, + 'HTML, preprocess: Returned properly escaped hash ref contents'); +is_deeply(LedgerSMB::Template::HTML::preprocess({'fruit' => '&veggies', + 'test' => ['nest', 'bird', '0 < 15', 1]}), + {'fruit' => '&veggies', 'test' => ['nest', 'bird', '0 < 15', 1]}, + 'HTML, preprocess: Returned properly escaped nested contents'); +is(LedgerSMB::Template::HTML::postprocess('04-template'), undef, + 'HTML, postprocess: Return undef'); +ok(defined $template, + 'Template, new: Object creation with format and template'); +isa_ok($template, 'LedgerSMB::Template', + 'Template, new: Object creation with format and template'); +is($template->{include_path}, 't/data', + 'Template, new: Object creation with format and template'); +is($template->render({'login' => 'foo'}), + "I am a template.\nLook at me foo.\n", + 'Template, render: Simple HTML template'); diff --git a/t/98-pod-coverage.t b/t/98-pod-coverage.t index f2b04717..cd1b6ef2 100644 --- a/t/98-pod-coverage.t +++ b/t/98-pod-coverage.t @@ -8,7 +8,7 @@ use strict; use warnings; -use Test::More tests => 4; +use Test::More tests => 5; use Test::More; eval "use Test::Pod::Coverage"; plan skip_all => "Test::Pod::Coverage required for testing POD coverage" if $@; @@ -17,3 +17,4 @@ pod_coverage_ok("LedgerSMB"); pod_coverage_ok("LedgerSMB::Locale"); pod_coverage_ok("LedgerSMB::Log"); pod_coverage_ok("LedgerSMB::Menufile"); +pod_coverage_ok("LedgerSMB::Template::HTML"); diff --git a/t/data/04-template.html b/t/data/04-template.html new file mode 100644 index 00000000..3b236040 --- /dev/null +++ b/t/data/04-template.html @@ -0,0 +1,2 @@ +I am a template. +Look at me <?lsmb login ?>. |