Corrected upload for all engines
Christian Hansen [Sun, 10 Apr 2005 00:02:22 +0000 (00:02 +0000)]
lib/Catalyst/Engine.pm
lib/Catalyst/Engine/Apache.pm
lib/Catalyst/Engine/Apache/MP1.pm
lib/Catalyst/Engine/Apache/MP2.pm
lib/Catalyst/Engine/CGI.pm
lib/Catalyst/Engine/Test.pm
lib/Catalyst/Request.pm

index 84b7ab0..8e0b513 100644 (file)
@@ -230,7 +230,7 @@ sub finalize {
     if ( my $location = $c->response->redirect ) {
         $c->log->debug(qq/Redirecting to "$location"/) if $c->debug;
         $c->response->header( Location => $location );
-        $c->response->status(302) if $c->response->status !~ /3\d\d$/;
+        $c->response->status(302) if $c->response->status !~ /^3\d\d$/;
     }
 
     if ( $#{ $c->error } >= 0 ) {
index 3259c3b..a968549 100644 (file)
@@ -1,9 +1,8 @@
 package Catalyst::Engine::Apache;
 
 use strict;
-use mod_perl;
-use constant MP2 => $mod_perl::VERSION >= 1.99;
 use base 'Catalyst::Engine';
+
 use URI;
 use URI::http;
 
@@ -37,34 +36,6 @@ This class overloads some methods from C<Catalyst::Engine>.
 
 =over 4
 
-=item $c->finalize_headers
-
-=cut
-
-sub finalize_headers {
-    my $c = shift;
-
-    for my $name ( $c->response->headers->header_field_names ) {
-        next if $name =~ /Content-Type/i;
-        my @values = $c->response->header($name);
-        $c->apache->headers_out->add( $name => $_ ) for @values;
-    }
-
-    if ( $c->response->header('Set-Cookie') && $c->response->status >= 300 ) {
-        my @values = $c->response->header('Set-Cookie');
-        $c->apache->err_headers_out->add( 'Set-Cookie' => $_ ) for @values;
-    }
-
-    $c->apache->status( $c->response->status );
-    $c->apache->content_type( $c->response->header('Content-Type') );
-
-    unless ( MP2 ) {
-        $c->apache->send_http_header;
-    }
-
-    return 0;
-}
-
 =item $c->finalize_output
 
 =cut
@@ -100,12 +71,11 @@ sub prepare_headers {
 
 sub prepare_parameters {
     my $c = shift;
-    my %args;
+
     foreach my $key ( $c->apache->param ) {
         my @values = $c->apache->param($key);
-        $args{$key} = @values == 1 ? $values[0] : \@values;
+        $c->req->parameters->{$key} = ( @values == 1 ) ? $values[0] : \@values;
     }
-    $c->request->parameters( \%args );
 }
 
 =item $c->prepare_path
@@ -138,22 +108,6 @@ sub prepare_request {
     $c->apache( Apache::Request->new($r) );
 }
 
-=item $c->prepare_uploads
-
-=cut
-
-sub prepare_uploads {
-    my $c = shift;
-    for my $upload ( $c->apache->upload ) {
-        $upload = $c->apache->upload($upload) if MP2;
-        $c->request->uploads->{ $upload->filename } = {
-            fh   => $upload->fh,
-            size => $upload->size,
-            type => $upload->type
-        };
-    }
-}
-
 =item $c->run
 
 =cut
index 842a4de..7405269 100644 (file)
@@ -3,12 +3,12 @@ package Catalyst::Engine::Apache::MP1;
 use strict;
 use base 'Catalyst::Engine::Apache';
 
-use Apache ();
-use Apache::Constants qw(:common);
-use Apache::Request ();
-use Apache::Cookie ();
+use Apache            ();
+use Apache::Constants ();
+use Apache::Request   ();
+use Apache::Cookie    ();
 
-sub handler ($$) { shift->SUPER::handler(@_) }
+Apache::Constants->import(':common');
 
 =head1 NAME
 
@@ -28,8 +28,65 @@ This class overloads some methods from C<Catalyst::Engine::Apache>.
 
 =over 4
 
+=item $c->finalize_headers
+
+=cut
+
+sub finalize_headers {
+    my $c = shift;
+
+    for my $name ( $c->response->headers->header_field_names ) {
+        next if $name =~ /Content-Type/i;
+        my @values = $c->response->header($name);
+        $c->apache->headers_out->add( $name => $_ ) for @values;
+    }
+
+    if ( $c->response->header('Set-Cookie') && $c->response->status >= 300 ) {
+        my @values = $c->response->header('Set-Cookie');
+        $c->apache->err_headers_out->add( 'Set-Cookie' => $_ ) for @values;
+    }
+
+    $c->apache->status( $c->response->status );
+    $c->apache->content_type( $c->response->header('Content-Type') );
+
+    $c->apache->send_http_header;
+
+    return 0;
+}
+
 =item $c->handler
 
+=cut
+
+sub handler ($$) {
+    shift->SUPER::handler(@_);
+}
+
+=item $c->prepare_uploads
+
+=cut
+
+sub prepare_uploads {
+    my $c = shift;
+
+    my @uploads;
+
+    for my $upload ( $c->apache->upload ) {
+
+        my $hash = {
+            fh       => $upload->fh,
+            filename => $upload->filename,
+            size     => $upload->size,
+            tempname => $upload->tempname,
+            type     => $upload->type
+        };
+
+        push( @uploads, $upload->name, $hash );
+    }
+
+    $c->req->_assign_values( $c->req->uploads, \@uploads );
+}
+
 =back
 
 =head1 SEE ALSO
index 1afdfc2..fe88555 100644 (file)
@@ -3,19 +3,19 @@ package Catalyst::Engine::Apache::MP2;
 use strict;
 use base 'Catalyst::Engine::Apache';
 
-use Apache2 ();
-use Apache::Connection ();
-use Apache::Const ( -compile => qw(:common) );
-use Apache::RequestIO ();
-use Apache::RequestRec ();
+use Apache2             ();
+use Apache::Connection  ();
+use Apache::Const       ();
+use Apache::RequestIO   ();
+use Apache::RequestRec  ();
 use Apache::RequestUtil ();
-use Apache::Request ();
-use Apache::Cookie ();
-use Apache::Upload ();
-use Apache::URI ();
-use APR::URI ();
+use Apache::Request     ();
+use Apache::Cookie      ();
+use Apache::Upload      ();
+use Apache::URI         ();
+use APR::URI            ();
 
-sub handler : method { shift->SUPER::handler(@_) }
+Apache::Const->import( -compile => ':common' );
 
 =head1 NAME
 
@@ -35,8 +35,66 @@ This class overloads some methods from C<Catalyst::Engine::Apache>.
 
 =over 4
 
+=item $c->finalize_headers
+
+=cut
+
+sub finalize_headers {
+    my $c = shift;
+
+    for my $name ( $c->response->headers->header_field_names ) {
+        next if $name =~ /Content-Type/i;
+        my @values = $c->response->header($name);
+        $c->apache->headers_out->add( $name => $_ ) for @values;
+    }
+
+    if ( $c->response->header('Set-Cookie') && $c->response->status >= 300 ) {
+        my @values = $c->response->header('Set-Cookie');
+        $c->apache->err_headers_out->add( 'Set-Cookie' => $_ ) for @values;
+    }
+
+    $c->apache->status( $c->response->status );
+    $c->apache->content_type( $c->response->header('Content-Type') );
+
+    return 0;
+}
+
 =item $c->handler
 
+=cut
+
+sub handler : method {
+    shift->SUPER::handler(@_);
+}
+
+=item $c->prepare_uploads
+
+=cut
+
+sub prepare_uploads {
+    my $c = shift;
+
+    my @uploads;
+
+    for my $field ( $c->apache->upload ) {
+
+        for my $upload ( $c->apache->upload($field) ) {
+
+            my $hash = {
+                fh       => $upload->fh,
+                filename => $upload->filename,
+                size     => $upload->size,
+                tempname => $upload->tempname,
+                type     => $upload->type
+            };
+
+            push( @uploads, $field, $hash );
+        }
+    }
+
+    $c->req->_assign_values( $c->req->uploads, \@uploads );
+}
+
 =back
 
 =head1 SEE ALSO
index d32247a..af26d08 100644 (file)
@@ -2,14 +2,11 @@ package Catalyst::Engine::CGI;
 
 use strict;
 use base 'Catalyst::Engine';
+
+use CGI;
 use URI;
 use URI::http;
 
-require CGI::Simple;
-
-$CGI::Simple::POST_MAX        = 1048576;
-$CGI::Simple::DISABLE_UPLOADS = 0;
-
 __PACKAGE__->mk_accessors('cgi');
 
 =head1 NAME
@@ -34,7 +31,7 @@ appropriate engine module.
 =head1 DESCRIPTION
 
 This is the Catalyst engine specialized for the CGI environment (using the
-C<CGI::Simple> and C<CGI::Cookie> modules).  Normally Catalyst will select the
+C<CGI> and C<CGI::Cookie> modules).  Normally Catalyst will select the
 appropriate engine according to the environment that it detects, however you
 can force Catalyst to use the CGI engine by specifying the following in your
 application module:
@@ -50,7 +47,7 @@ useful in production applications, but it may be helpful for development.
 
 =item $c->cgi
 
-This config parameter contains the C<CGI::Simple> object.
+This config parameter contains the C<CGI> object.
 
 =back
 
@@ -66,15 +63,11 @@ This class overloads some methods from C<Catalyst::Engine>.
 
 sub finalize_headers {
     my $c = shift;
-    my %headers;
-
-    $headers{-status} = $c->response->status if $c->response->status;
 
-    for my $name ( $c->response->headers->header_field_names ) {
-        $headers{"-$name"} = $c->response->header($name);
-    }
+    $c->response->header( Status => $c->response->status );
 
-    print $c->cgi->header(%headers);
+    print $c->response->headers->as_string("\015\012");
+    print "\015\012";
 }
 
 =item $c->finalize_output
@@ -94,8 +87,8 @@ sub finalize_output {
 
 sub prepare_connection {
     my $c = shift;
-    $c->req->hostname( $c->cgi->remote_host );
-    $c->req->address( $c->cgi->remote_addr );
+    $c->req->hostname( $ENV{REMOTE_HOST} );
+    $c->req->address( $ENV{REMOTE_ADDR} );
 }
 
 =item $c->prepare_headers
@@ -104,13 +97,17 @@ sub prepare_connection {
 
 sub prepare_headers {
     my $c = shift;
-    $c->req->method( $c->cgi->request_method );
-    for my $header ( $c->cgi->http ) {
+
+    while ( my ( $header, $value ) = each %ENV ) {
+
+        next unless $header =~ /^(HTTP|CONTENT)/i;
+
         ( my $field = $header ) =~ s/^HTTPS?_//;
-        $c->req->headers->header( $field => $c->cgi->http($header) );
+
+        $c->req->headers->header( $field => $value );
     }
-    $c->req->headers->header( 'Content-Type'   => $c->cgi->content_type );
-    $c->req->headers->header( 'Content-Length' => $c->cgi->content_length );
+
+    $c->req->method( $ENV{REQUEST_METHOD} || 'GET' );
 }
 
 =item $c->prepare_parameters
@@ -118,16 +115,12 @@ sub prepare_headers {
 =cut
 
 sub prepare_parameters {
-    my $c    = shift;
-
-    $c->cgi->parse_query_string;
-    my %vars = $c->cgi->Vars;
-    while ( my ( $key, $value ) = each %vars ) {
-        my @values = split "\0", $value;
-        $vars{$key} = @values <= 1 ? $values[0] : \@values;
+    my $c = shift;
+
+    for my $param ( $c->cgi->param ) {
+        my @values = $c->cgi->param($param);
+        $c->req->parameters->{$param} = ( @values > 1 ) ? \@values : $values[0];
     }
-    $c->req->parameters( {%vars} );
 }
 
 =item $c->prepare_path
@@ -140,7 +133,7 @@ sub prepare_path {
     my $base;
     {
         my $scheme = $ENV{HTTPS} ? 'https' : 'http';
-        my $host   = $ENV{HTTP_HOST} || $ENV{SERVER_NAME};
+        my $host   = $ENV{HTTP_HOST}   || $ENV{SERVER_NAME};
         my $port   = $ENV{SERVER_PORT} || 80;
         my $path   = $ENV{SCRIPT_NAME} || '/';
 
@@ -155,7 +148,7 @@ sub prepare_path {
 
     my $path = $ENV{PATH_INFO} || '/';
     $path =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg;
-    $path =~  s/^\///;
+    $path =~ s/^\///;
 
     $c->req->base($base);
     $c->req->path($path);
@@ -165,7 +158,11 @@ sub prepare_path {
 
 =cut
 
-sub prepare_request { shift->cgi( CGI::Simple->new ) }
+sub prepare_request { 
+    my $c = shift;
+    $c->cgi( CGI->new );
+    $c->cgi->_reset_globals;
+}
 
 =item $c->prepare_uploads
 
@@ -173,14 +170,38 @@ sub prepare_request { shift->cgi( CGI::Simple->new ) }
 
 sub prepare_uploads {
     my $c = shift;
-    for my $name ( $c->cgi->upload ) {
-        next unless defined $name;
-        $c->req->uploads->{$name} = {
-            fh   => $c->cgi->upload($name),
-            size => $c->cgi->upload_info( $name, 'size' ),
-            type => $c->cgi->upload_info( $name, 'mime' )
-        };
+
+    my @uploads;
+    
+    for my $param ( $c->cgi->param ) {
+    
+        my @values = $c->cgi->param($param);
+
+        next unless ref( $values[0] );
+
+        for my $fh (@values) {
+
+            next unless my $size = ( stat $fh )[7];
+
+            my $info        = $c->cgi->uploadInfo($fh);
+            my $tempname    = $c->cgi->tmpFileName($fh);
+            my $type        = $info->{'Content-Type'};
+            my $disposition = $info->{'Content-Disposition'};
+            my $filename    = ( $disposition =~ / filename="([^;]*)"/ )[0];
+
+            my $upload = {
+                fh       => $fh,
+                filename => $filename,
+                size     => $size,
+                tempname => $tempname,
+                type     => $type
+            };
+            
+            push( @uploads, $param, $upload );
+        }
     }
+    
+    $c->req->_assign_values( $c->req->uploads, \@uploads );
 }
 
 =item $c->run
index 429bdad..0561348 100644 (file)
@@ -7,7 +7,7 @@ use Class::Struct ();
 use HTTP::Headers::Util 'split_header_words';
 use HTTP::Request;
 use HTTP::Response;
-use IO::File;
+use File::Temp;
 use URI;
 
 __PACKAGE__->mk_accessors(qw/http/);
@@ -99,7 +99,8 @@ sub prepare_headers {
 sub prepare_parameters {
     my $c = shift;
 
-    my @params  = ();
+    my ( @params, @uploads );
+
     my $request = $c->http->request;
 
     push( @params, $request->uri->query_form );
@@ -119,38 +120,29 @@ sub prepare_parameters {
 
             if ( $parameters{filename} ) {
 
-                my $fh = IO::File->new_tmpfile;
+                my $fh = File::Temp->new;
                 $fh->write( $part->content ) or die $!;
-                $fh->seek( SEEK_SET, 0 ) or die $!;
-
-                $c->req->uploads->{ $parameters{filename} } = {
-                    fh   => $fh,
-                    size => ( stat $fh )[7],
-                    type => $part->content_type
+                seek( $fh, 0, 0 ) or die $!;
+
+                my $upload = {
+                    fh       => $fh,
+                    filename => $parameters{filename},
+                    size     => ( stat $fh )[7],
+                    tempname => $fh->filename,
+                    type     => $part->content_type
                 };
 
-                push( @params, $parameters{filename}, $fh );
+                push( @uploads, $parameters{name}, $upload );
+                push( @params,  $parameters{name}, $fh );
             }
             else {
                 push( @params, $parameters{name}, $part->content );
             }
         }
     }
-
-    my $parameters = $c->req->parameters;
-
-    while ( my ( $name, $value ) = splice( @params, 0, 2 ) ) {
-
-        if ( exists $parameters->{$name} ) {
-            for ( $parameters->{$name} ) {
-                $_ = [$_] unless ref($_) eq "ARRAY";
-                push( @$_, $value );
-            }
-        }
-        else {
-            $parameters->{$name} = $value;
-        }
-    }
+    
+    $c->req->_assign_values( $c->req->parameters, \@params );
+    $c->req->_assign_values( $c->req->uploads, \@uploads );
 }
 
 =item $c->prepare_path
@@ -209,7 +201,8 @@ sub run {
 
     unless ( ref $request ) {
 
-        my $uri = ( $request =~ m/http/i )
+        my $uri =
+          ( $request =~ m/http/i )
           ? URI->new($request)
           : URI->new( 'http://localhost' . $request );
 
index 141e6be..f076fbd 100644 (file)
@@ -18,6 +18,24 @@ sub header           { shift->headers->header(@_)           }
 sub referer          { shift->headers->referer(@_)          }
 sub user_agent       { shift->headers->user_agent(@_)       }
 
+
+sub _assign_values {
+    my ( $self, $map, $values ) = @_;
+    
+    while ( my ( $name, $value ) = splice( @{ $values }, 0, 2 ) ) {
+
+        if ( exists $map->{$name} ) {
+            for ( $map->{$name} ) {
+                $_ = [$_] unless ref($_) eq "ARRAY";
+                push( @$_, $value );
+            }
+        }
+        else {
+            $map->{$name} = $value;
+        }
+    }
+}
+
 =head1 NAME
 
 Catalyst::Request - Catalyst Request Class
@@ -40,11 +58,13 @@ Catalyst::Request - Catalyst Request Class
     $req->hostname;
     $req->match;
     $req->method;
-    $req->parameters;
+    $req->param;
     $req->params;
+    $req->parameters;
     $req->path;
     $req->referer;
     $req->snippets;
+    $req->upload;
     $req->uploads;
     $req->user_agent
 
@@ -123,7 +143,7 @@ Contains the hostname of the remote user.
 
 =item $req->match
 
-This contains be the matching part of a regexp action. otherwise it 
+This contains be the matching part of a regexp action. otherwise it
 returns the same as 'action'.
 
     print $c->request->match;
@@ -132,7 +152,42 @@ returns the same as 'action'.
 
 Contains the request method (C<GET>, C<POST>, C<HEAD>, etc).
 
-    print $c->request->method
+    print $c->request->method;
+
+=item $req->param
+
+Get request parameters with a CGI.pm like param method.
+
+    $value  = $c->request->param('foo');
+    @values = $c->request->param('foo');
+    @params = $c->request->param;
+
+=cut
+
+sub param {
+    my $self = shift;
+
+    if ( @_ == 0 ) {
+        return keys %{ $self->parameters };
+    }
+
+    my $param = shift;
+
+    unless ( exists $self->parameters->{$param} ) {
+        return wantarray ? () : undef;
+    }
+
+    if ( ref $self->parameters->{$param} eq 'ARRAY' ) {
+        return (wantarray)
+          ? @{ $self->parameters->{$param} }
+          : $self->parameters->{$param}->[0];
+    }
+    else {
+        return (wantarray)
+          ? ( $self->parameters->{$param} )
+          : $self->parameters->{$param};
+    }
+}
 
 =item $req->params
 
@@ -140,9 +195,11 @@ Shortcut for $req->parameters.
 
 =item $req->parameters
 
-Returns a reference to a hash containing the parameters.
+Returns a reference to a hash containing parameters. Values can
+be either a scalar or a arrayref containing scalars.
 
-    print $c->request->parameters->{foo};
+    print $c->request->parameters->{field};
+    print $c->request->parameters->{field}->[0];
 
 =item $req->path
 
@@ -160,15 +217,78 @@ Returns a reference to an array containing regex snippets.
 
     my @snippets = @{ $c->request->snippets };
 
+=item $req->upload
+
+A convenient method to $req->uploads.
+
+    $upload  = $c->request->upload('field');
+    @uploads = $c->request->upload('field');
+    @fields  = $c->request->upload;
+    
+    for my $upload ( $c->request->upload('field') ) {
+        print $upload->{filename};
+    }
+
+=cut
+
+sub upload {
+    my $self = shift;
+
+    if ( @_ == 0 ) {
+        return keys %{ $self->uploads };
+    }
+
+    my $upload = shift;
+
+    unless ( exists $self->uploads->{$upload} ) {
+        return wantarray ? () : undef;
+    }
+
+    if ( ref $self->uploads->{$upload} eq 'ARRAY' ) {
+        return (wantarray)
+          ? @{ $self->uploads->{$upload} }
+          : $self->uploads->{$upload}->[0];
+    }
+    else {
+        return (wantarray)
+          ? ( $self->uploads->{$upload} )
+          : $self->uploads->{$upload};
+    }
+}
+
 =item $req->uploads
 
-Returns a reference to a hash containing the uploads.
+Returns a reference to a hash containing uploads. Values can
+be either a hashref or a arrayref containing hashrefs.
+
+    my $upload = $c->request->uploads->{field};
+    my $upload = $c->request->uploads->{field}->[0];
+
+The upload hashref contains the following keys:
+
+=over 4
+
+=item * fh 
+
+Filehandle.
 
-    my $filename = $c->req->parameters->{foo};
-    print $c->request->uploads->{$filename}->{type};
-    print $c->request->uploads->{$filename}->{size};
-    my $fh = $c->request->uploads->{$filename}->{fh};
-    my $content = do { local $/; <$fh> };
+=item * filename 
+
+Client supplied filename.
+
+=item * size
+
+Size of the file in bytes.
+
+=item * tempname
+
+Path to the temporary spool file.
+
+=item * type
+
+Client supplied Content-Type.
+
+=back
 
 =item $req->user_agent
 
@@ -183,7 +303,7 @@ Marcus Ramberg, C<mramberg@cpan.org>
 
 =head1 COPYRIGHT
 
-This program is free software, you can redistribute it and/or modify 
+This program is free software, you can redistribute it and/or modify
 it under the same terms as Perl itself.
 
 =cut