Skip to content

Commit

Permalink
Item13897: Uploads reimplemented.
Browse files Browse the repository at this point in the history
This is more of an example code though it works and it supports multiple
uploads.

Note that the original implementation of Foswiki::UI::Upload didn't make
use of Foswiki::Request::Upload.

- Foswiki::Engine got uploads object attribute.
  • Loading branch information
vrurg committed Jul 7, 2016
1 parent 7f4cf4d commit 4ac91aa
Show file tree
Hide file tree
Showing 6 changed files with 84 additions and 46 deletions.
17 changes: 15 additions & 2 deletions core/lib/Foswiki/Engine.pm
Expand Up @@ -119,6 +119,17 @@ has postData => (

=begin TML
---++ ObjectAttribute uploads
Hash of =$filename => \%uploadInfo= pairs.
=cut

has uploads =>
( is => 'rw', lazy => 1, clearer => 1, builder => '_prepareUploads', );

=begin TML
---++ ObjectAttribute HTMLcompliant
Boolean. True if engine is HTTP compliant. For now the only false is possible
Expand Down Expand Up @@ -411,12 +422,14 @@ sub _preparePath { }
# Abstract initializer for bodyParameters
sub _prepareBodyParameters { return []; }

# Abstract initializer for uploads
sub _prepareUploads { return {}; }

=begin TML
---++ ObjectMethod prepareUploads( $req )
---++ ObjectMethod prepareUploads( )
Abstract method, must be defined by inherited classes.
* =$req= - Foswiki::Request object to populate
Should fill $req's {uploads} field. This is a hashref whose keys are
upload names and values Foswiki::Request::Upload objects.
Expand Down
2 changes: 0 additions & 2 deletions core/lib/Foswiki/Engine/CGI.pm
Expand Up @@ -79,8 +79,6 @@ if ( defined $SAVE_DESTROY ) {
};
}

has uploads => ( is => 'rw', lazy => 1, clearer => 1, default => sub { {} }, );

# cgi attribute must be defined after all other attributes to avoid `handle`
# reimporting of CGI methods with the same names as existing attributes.
has cgi => (
Expand Down
22 changes: 21 additions & 1 deletion core/lib/Foswiki/Engine/PSGI.pm
Expand Up @@ -138,7 +138,6 @@ around _prepareBodyParameters => sub {
my $psgi = $this->psgi;
return [] unless $psgi->content_length;

# SMELL XXX TODO Find out how to manage it with PSGI.
my $params = $psgi->body_parameters;
my @params;
foreach my $pname ( $params->keys ) {
Expand All @@ -149,6 +148,7 @@ around _prepareBodyParameters => sub {
-name => $upname,
-value => \@values,

# SMELL uploads are handled by dedicated psgi->uploads
# Note that we record the encoded name of the upload. It will be
# decoded in prepareUploads, which rewrites the {uploads} hash.
-upload => ( scalar( $psgi->upload($pname) ) ? 1 : 0 ),
Expand Down Expand Up @@ -184,6 +184,26 @@ around _preparePostData => sub {
return $this->psgi->raw_body;
};

around _prepareUploads => sub {
my $orig = shift;
my ( $this, $req ) = @_;

my @uploads;
my $psgi = $this->psgi;
foreach my $key ( keys %{ $psgi->uploads } ) {
my $upload = $psgi->upload($key);
push @uploads,
{
filename => $upload->filename,
basename => $upload->basename,
tmpname => $upload->path,
contentType => $upload->content_type,
size => $upload->size,
};
}
return \@uploads;
};

around finalizeReturn => sub {
my $orig = shift;
my $this = shift;
Expand Down
24 changes: 14 additions & 10 deletions core/lib/Foswiki/Request.pm
Expand Up @@ -19,8 +19,7 @@ Fields:
* =remote_user= Remote HTTP authenticated user
* =secure= Boolean value about use of encryption
* =server_port= Port that the webserver listens on
* =uploads= hashref whose keys are parameter name of uploaded
files
* =uploads= arrayref of Foswiki::Request::Upload objects
* =uri= the request uri
The following fields are parsed from the =pathInfo=
Expand Down Expand Up @@ -166,8 +165,8 @@ as sent by browser, and values are Foswiki::Request::Upload objects.
has uploads => (
is => 'rw',
lazy => 1,
default => sub { {} },
isa => Foswiki::Object::isaHASH( 'uploads', noUndef => 1 ),
builder => '_establishUploads',
isa => Foswiki::Object::isaARRAY( 'uploads', noUndef => 1 ),
);

# upload_list attribute keeps list of request uploads. Used to initialize
Expand Down Expand Up @@ -686,12 +685,6 @@ sub delete {
my $this = shift;
foreach my $p (@_) {
next unless exists $this->_param->{$p};
if ( my $upload = $this->uploads->{ $this->param($p) } ) {

#$upload->finish;
CORE::delete $this->uploads->{ $this->param($p) };
}
CORE::delete $this->_param->{$p};
}
my %deleted_key = map { $_ => 1 } @_;
$this->param_list( [ grep { !$deleted_key{$_} } @{ $this->param_list } ] );
Expand Down Expand Up @@ -1301,6 +1294,17 @@ sub _establishMethod {
return $this->app->engine->connectionData->{method};
}

sub _establishUploads {
my $this = shift;
my $rawUploads = $this->app->engine->uploads;
my @reqUploads;
foreach my $upload (@$rawUploads) {
push @reqUploads,
$this->create( 'Foswiki::Request::Upload', %$upload, );
}
return \@reqUploads;
}

=begin TML
---++ ObjectMethod normalizeWebTopicName( $web, $topic ) -> ( $web, $topic )
Expand Down
7 changes: 5 additions & 2 deletions core/lib/Foswiki/Request/Upload.pm
Expand Up @@ -19,8 +19,11 @@ use Moo;
use namespace::clean;
extends qw(Foswiki::Object);

has headers => ( is => 'ro', );
has tmpname => ( is => 'rw', );
has filename => ( is => 'ro', );
has size => ( is => 'ro', );
has contentType => ( is => 'ro', );
has basename => ( is => 'ro', );
has tmpname => ( is => 'ro', );

=begin TML
Expand Down
58 changes: 29 additions & 29 deletions core/lib/Foswiki/UI/Upload.pm
Expand Up @@ -110,10 +110,24 @@ sub upload {
}
}

# Real work of upload
sub _upload {
my $this = shift;

my @msgs;
foreach my $upload ( @{ $this->app->request->uploads } ) {
push @msgs, $this->_upload_file($upload);
}

# XXX Temporary!
# SMELL Any other way to return mupltiple messages? A template?
return join( '', map { "<p>$_</p>" } @msgs );
}

# Real work of upload
sub _upload_file {
my $this = shift;
my $upload = shift;

my $app = $this->app;
my $req = $app->request;
my $web = $req->web;
Expand All @@ -126,8 +140,10 @@ sub _upload {
my $fileComment = $req->param('filecomment') || '';
my $createLink = $req->param('createlink') || '';
my $doPropsOnly = $req->param('changeproperties');
my $filePath = $req->param('filepath') || '';
my $fileName = $req->param('filename') || '';
my $filePath = $upload->filename || '';
my $fileName = $upload->basename || '';
my $tmpFilePath = $upload->tmpname;
my $fileSize = $upload->size;
if ( $filePath && !$fileName ) {
$filePath =~ m|([^/\\]*$)|;
$fileName = $1;
Expand All @@ -153,39 +169,18 @@ sub _upload {
Foswiki::Sandbox::sanitizeAttachmentName($fileName);

my $stream;
my ( $fileSize, $fileDate, $tmpFilePath ) = '';
my ( $streamSize, $fileDate );

unless ($doPropsOnly) {
my $fh = $req->param('filepath');

try {
$tmpFilePath = $req->tmpFileName($fh);
}
catch {

# Item5130, Item5133 - Illegal file name, bad path,
# something like that
Foswiki::OopsException->rethrowAs(
$_,
app => $app,
template => 'attention',
def => 'zero_size_upload',
web => $web,
topic => $topic,
params => [ ( $filePath || '""' ) ],
status => 400,
);
};

$stream = $req->upload('filepath');
$stream = $upload->handle;

# check if upload has non zero size
if ($stream) {
my @stats = stat $stream;
$fileSize = $stats[7];
$fileDate = $stats[9];
$streamSize = $stats[7];
$fileDate = $stats[9];
}
unless ( $fileSize && $fileName ) {
unless ( $streamSize && $fileName ) {
Foswiki::OopsException->throw(
app => $app,
template => 'attention',
Expand All @@ -196,6 +191,11 @@ sub _upload {
status => 400,
);
}
unless ( $streamSize == $fileSize ) {

# TODO Check if actual file size is the same as declared in POST
# headers. Prevent broken uploads.
}

my $maxSize = $app->prefs->getPreference('ATTACHFILESIZELIMIT')
|| 0;
Expand Down

0 comments on commit 4ac91aa

Please sign in to comment.