FilePump bug fixes

Restricted file uris to uploads and extras dirs
Validation messages for invalid file uris
Updated i18n
Added more tests
This commit is contained in:
Patrick Donelan 2009-07-10 03:33:06 +00:00
parent b149367c11
commit 7110febecd
4 changed files with 188 additions and 24 deletions

View file

@ -42,6 +42,11 @@ sub addFile {
my $files = $self->get($collateralType);
my $uriExists = $self->getCollateralDataIndex($files, 'uri', $uri) != -1 ? 1 : 0;
return 0, 'Duplicate URI' if $uriExists;
if (my $msg = $self->validate($uri)) {
return 0, $msg;
}
$self->setCollateral(
$collateralType,
'fileId',
@ -54,6 +59,7 @@ sub addFile {
$self->update({lastModified => time()});
return 1;
}
#-------------------------------------------------------------------
@ -569,7 +575,7 @@ A URI object.
sub fetchDir {
my ($self, $uri ) = @_;
my $filepath = $uri->path;
my $filepath = $self->resolveFilePath($uri->path);
return {} unless (-e $filepath && -r _ && -d _);
my @stats = stat(_);
my $dir = Path::Class::Dir->new($filepath);
@ -601,7 +607,7 @@ A URI object.
sub fetchFile {
my ($self, $uri ) = @_;
my $filepath = $uri->path;
my $filepath = $self->resolveFilePath($uri->path);
return {} unless (-e $filepath && -r _);
return $self->fetchDir($uri) if -d _;
my @stats = stat(_); # recycle stat data from file tests.
@ -994,5 +1000,86 @@ sub setCollateral {
return $keyValue;
}
#-------------------------------------------------------------------
=head2 validate ( $uri )
Check a uri for validation errors. Returns a validation error message (if problems found).
=head3 $uri
The URI to validate
=cut
sub validate {
my $self = shift;
my $uri = shift;
my $urio = URI->new($uri) or return "Invalid URI: $uri";
my $scheme = $urio->scheme;
my $path = $urio->path;
# File validation
if ($scheme eq 'file') {
if ($path !~ m{^uploads/|^extras/}) {
return q{File uri must begin with file:uploads/.. or file:extras/..};
}
# N.B. Once we have Path::Class >= 0.17 we can use resolve() for a better solution to this canonicalisation problem
if ($path =~ m{\.\./}) {
return q{Directory traversal not permitted};
}
my $uploadsDir = Path::Class::Dir->new($self->session->config->get('uploadsPath'));
my $extrasDir = Path::Class::Dir->new($self->session->config->get('extrasPath'));
my $file = $self->resolveFilePath($path);
return q{File not found} unless -e $file;
if (!$uploadsDir->contains($file) && !$extrasDir->contains($file)) {
return q{File uri must correspond to files inside your uploads dir or your extras dir};
}
}
return;
}
#-------------------------------------------------------------------
=head2 resolveFilePath ( $path )
Resolves a relative path into a L<Path::Class::File> object. The path,
which must being with either C<uploads> or C<extras>, is resolved into
an absolute path with C<uploads> or C<extras> replaced with the value
of C<uploadsPath> or c<extrasPath> from the site config file.
For example, the following path
file:extras/path/to/my/file
Resolves to something like:
/data/WebGUI/www/extras/path/to/my/file
=head3 $path
A relative file path that must begine with either C<uploads> or C<extras>.
=cut
sub resolveFilePath {
my $self = shift;
my $path = shift;
if ($path =~ s{^uploads/}{}) {
my $uploadsDir = Path::Class::Dir->new($self->session->config->get('uploadsPath'));
return Path::Class::File->new($uploadsDir, $path);
} elsif ($path =~ s{^extras/}{}) {
my $extrasDir = Path::Class::Dir->new($self->session->config->get('extrasPath'));
return Path::Class::File->new($extrasDir, $path);
}
}
1;

View file

@ -13,6 +13,7 @@ package WebGUI::Macro::FilePump;
use strict;
use WebGUI::FilePump::Bundle;
use Path::Class;
use WebGUI::International;
=head1 NAME
@ -39,7 +40,8 @@ $bundleName, the name of a File Pump bundle.
=item *
$type, the type of files from the Bundle that you are accessing. Either JS or javascript, or CSS or css.
$type, the type of files from the Bundle that you are accessing.
Either JS or javascript, or CSS or css (case-insensitive).
=back
@ -62,15 +64,39 @@ sub process {
my $bundle = WebGUI::FilePump::Bundle->new($session, $bundleId->[0]);
return '' unless $bundle;
my $bundleDir = $bundle->getPathClassDir;
# Sometimes, when migrating sites, restoring from backup, etc., you can
# get into the situation where the bundle is defined in the db but doesn't
# exist on the filesystem. In this case, simply generate a warning and
# trigger a bundle rebuild.
if (!-e $bundleDir) {
$session->log->warn("Bundle is marked as built, but does not exist on filesystem. Attempting to rebuild: $bundleDir");
my ($code, $error) = $bundle->build;
if ($error) {
my $i18n = WebGUI::International->new($session, 'FilePump');
$error = sprintf $i18n->get('build error'), $error;
$session->log->error("Rebuild failed with error: $error");
return $error;
} else {
$session->log->warn("Rebuild succeeded, continuing with macro processing");
}
}
my $uploadsDir = Path::Class::Dir->new($session->config->get('uploadsPath'));
my $extrasDir = Path::Class::Dir->new($session->config->get('extrasPath'));
my $uploadsUrl = Path::Class::Dir->new($session->config->get('uploadsURL'));
my $extrasUrl = Path::Class::Dir->new($session->config->get('extrasURL'));
##Normal mode
if (! $session->var->isAdminOn) {
# Built files live at /path/to/uploads/filepump/bundle.timestamp/ which is
# a sub-dir of uploadsDir, so resolve the dir relative to uploads
my $dir = $bundle->getPathClassDir->relative($uploadsDir);
if ($type eq 'js' || $type eq 'javascript') {
my $file = $uploadsUrl->file($dir, $bundle->bundleUrl . '.js');
return sprintf qq|<script type="text/javascript" src="%s">\n|, $file->stringify;
return sprintf qq|<script type="text/javascript" src="%s"></script>\n|, $file->stringify;
}
elsif ($type eq 'css') {
my $file = $uploadsUrl->file($dir, $bundle->bundleUrl . '.css');
@ -85,7 +111,7 @@ sub process {
my $template;
my $files;
if ($type eq 'js' || $type eq 'javascript') {
$template = qq|<script type="text/javascript" src="%s">\n|;
$template = qq|<script type="text/javascript" src="%s"></script>\n|;
$files = $bundle->get('jsFiles');
}
elsif ($type eq 'css') {
@ -103,10 +129,19 @@ sub process {
$url = $uri->opaque;
}
elsif ($scheme eq 'file') {
my $file = Path::Class::File->new($uri->path);
my $uploadsRelFile = $file->relative($uploadsDir);
$url = $uploadsUrl->file($uploadsRelFile)->stringify;
my $file = $bundle->resolveFilePath($uri->path);
# Un-built files live inside either uploads or extras
if ($uploadsDir->subsumes($file)) {
my $relFile = $file->relative($uploadsDir);
$url = $uploadsUrl->file($relFile)->stringify;
} elsif ($extrasDir->subsumes($file)) {
my $relFile = $file->relative($extrasDir);
$url = $extrasUrl->file($relFile)->stringify;
} else {
$session->log->warn("Invalid file: $file");
next;
}
}
elsif ($scheme eq 'http' or $scheme eq 'https') {
$url = $uri->as_string;

View file

@ -71,8 +71,8 @@ our $I18N = {
},
'otherFiles' => {
message => q|CSS Images|,
lastUpdated => 1242681632,
message => q|Collateral|,
lastUpdated => 1247196636,
context => q|Edit bundle label.|
},