diff --git a/core/lib/Foswiki/Engine.pm b/core/lib/Foswiki/Engine.pm index ec415312ef..34e5c35b66 100644 --- a/core/lib/Foswiki/Engine.pm +++ b/core/lib/Foswiki/Engine.pm @@ -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 @@ -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. diff --git a/core/lib/Foswiki/Engine/CGI.pm b/core/lib/Foswiki/Engine/CGI.pm index d00d6a7c8a..e76bb60598 100644 --- a/core/lib/Foswiki/Engine/CGI.pm +++ b/core/lib/Foswiki/Engine/CGI.pm @@ -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 => ( diff --git a/core/lib/Foswiki/Engine/PSGI.pm b/core/lib/Foswiki/Engine/PSGI.pm index 825aa414d3..7189cf1266 100644 --- a/core/lib/Foswiki/Engine/PSGI.pm +++ b/core/lib/Foswiki/Engine/PSGI.pm @@ -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 ) { @@ -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 ), @@ -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; diff --git a/core/lib/Foswiki/Request.pm b/core/lib/Foswiki/Request.pm index cce2db1f99..5cc2fd8770 100644 --- a/core/lib/Foswiki/Request.pm +++ b/core/lib/Foswiki/Request.pm @@ -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= @@ -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 @@ -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 } ] ); @@ -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 ) diff --git a/core/lib/Foswiki/Request/Upload.pm b/core/lib/Foswiki/Request/Upload.pm index b4a20e5ff8..7ed3fb8031 100644 --- a/core/lib/Foswiki/Request/Upload.pm +++ b/core/lib/Foswiki/Request/Upload.pm @@ -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 diff --git a/core/lib/Foswiki/UI/Upload.pm b/core/lib/Foswiki/UI/Upload.pm index 4dca6a5601..284f869384 100644 --- a/core/lib/Foswiki/UI/Upload.pm +++ b/core/lib/Foswiki/UI/Upload.pm @@ -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 { "

$_

" } @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; @@ -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; @@ -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', @@ -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;