summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xLedgerSMB/Template.pm14
-rwxr-xr-xLedgerSMB/Template/HTML.pm14
-rw-r--r--t/01-load.t4
-rw-r--r--t/04-template-handling.t171
-rw-r--r--t/98-pod-coverage.t3
-rw-r--r--t/data/04-template.html2
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 &gt; 12',
+ 'HTML, preprocess: Returned properly escaped string');
+is_deeply(LedgerSMB::Template::HTML::preprocess([0, 'apple', 'mango&durian']),
+ [0, 'apple', 'mango&amp;durian'],
+ 'HTML, preprocess: Returned properly escaped array ref contents');
+is_deeply(LedgerSMB::Template::HTML::preprocess({'fruit' => '&veggies',
+ 'test' => 1}),
+ {'fruit' => '&amp;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' => '&amp;veggies', 'test' => ['nest', 'bird', '0 &lt; 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 ?>.