From: Rafael Kitover Date: Mon, 27 Apr 2009 05:24:45 +0000 (+0000) Subject: C::C::WrapCGI - PATH_INFO and configurable cgi_dir X-Git-Tag: 0.030~42 X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?p=catagits%2FCatalyst-Controller-WrapCGI.git;a=commitdiff_plain;h=f410f043b41cc59d08cb1aa015378888c8fe88b7 C::C::WrapCGI - PATH_INFO and configurable cgi_dir --- diff --git a/Changes b/Changes index 721f40d..ba50373 100644 --- a/Changes +++ b/Changes @@ -1,7 +1,9 @@ Revision history for Catalyst-Controller-WrapCGI - - configurable cgi_dir - - support for PATH_INFO +0.0030 2009-04-27 05:17:17 + - configurable cgi_dir (caelum) + - support for PATH_INFO and SCRIPT_NAME (caelum) + - FILEPATH_INFO (iffy implementation) (caelum) 0.0029 2009-04-26 20:54:28 - Allow more control over public paths to CGIBin actions (hdp) diff --git a/Makefile.PL b/Makefile.PL index 869f036..9d7b089 100644 --- a/Makefile.PL +++ b/Makefile.PL @@ -8,7 +8,7 @@ include 'Module::AutoInstall'; requires 'Catalyst' => '5.7007'; requires 'parent'; requires 'Task::Weaken'; -requires 'HTTP::Request::AsCGI'; +requires 'HTTP::Request::AsCGI' => '0.7';; requires 'URI'; requires 'File::Find::Rule'; requires 'List::MoreUtils'; diff --git a/lib/Catalyst/Controller/CGIBin.pm b/lib/Catalyst/Controller/CGIBin.pm index 0277381..fd9b56c 100644 --- a/lib/Catalyst/Controller/CGIBin.pm +++ b/lib/Catalyst/Controller/CGIBin.pm @@ -14,6 +14,7 @@ use Symbol 'gensym'; use List::MoreUtils 'any'; use IO::File (); use Carp; + use namespace::clean -except => 'meta'; =head1 NAME @@ -22,11 +23,11 @@ Catalyst::Controller::CGIBin - Serve CGIs from root/cgi-bin =head1 VERSION -Version 0.008 +Version 0.009 =cut -our $VERSION = '0.008'; +our $VERSION = '0.009'; =head1 SYNOPSIS @@ -46,6 +47,7 @@ In your .conf: cgi_root_path cgi-bin + cgi_dir cgi-bin username_field username # used for REMOTE_USER env var pass_env PERL5LIB @@ -59,21 +61,35 @@ In your .conf: Dispatches to CGI files in root/cgi-bin for /cgi-bin/ paths. Unlike L this module does _NOT_ stat and recompile the CGI -for every invocation. If this is something you need, let me know. +for every invocation. This may be supported in the future if there's interest. -CGI paths are converted into action names using cgi_action (below.) +CGI paths are converted into action names using L. Inherits from L, see the documentation for that -module for configuration information. +module for other configuration information. + +=head1 CONFIG PARAMS + +=head2 cgi_root_path + +The global URI path prefix for CGIs, defaults to C. + +=head2 cgi_dir + +Path from which to read CGI files. Can be relative to C<$MYAPP_HOME/root> or +absolute. Defaults to C<$MYAPP_HOME/root/cgi-bin>. =cut has cgi_root_path => (is => 'ro', isa => 'Str', default => 'cgi-bin'); +has cgi_dir => (is => 'ro', isa => 'Str', default => 'cgi-bin'); sub register_actions { my ($self, $app) = @_; - my $cgi_bin = $app->path_to('root', 'cgi-bin'); + my $cgi_bin = File::Spec->file_name_is_absolute($self->cgi_dir) ? + $self->cgi_dir + : $app->path_to('root', $self->cgi_dir); my $namespace = $self->action_namespace($app); @@ -83,12 +99,13 @@ sub register_actions { my $cgi_path = abs2rel($file, $cgi_bin); next if any { $_ eq '.svn' } splitdir $cgi_path; + next if $cgi_path =~ /\.swp\z/; my $path = join '/' => splitdir($cgi_path); my $action_name = $self->cgi_action($path); my $public_path = $self->cgi_path($path); my $reverse = $namespace ? "$namespace/$action_name" : $action_name; - my $attrs = { Path => [ $public_path ], Args => [ 0 ] }; + my $attrs = { Path => [ $public_path ] }; my ($cgi, $type); @@ -122,15 +139,22 @@ sub register_actions { $self->next::method($app, @_); -# Tell Static::Simple to ignore the cgi-bin dir. - if (!any{ $_ eq 'cgi-bin' } @{ $app->config->{static}{ignore_dirs}||[] }) { - push @{ $app->config->{static}{ignore_dirs} }, 'cgi-bin'; +# Tell Static::Simple to ignore cgi_dir + if ($cgi_bin =~ /^@{[ $app->path_to('root') ]}/) { + my $rel = File::Spec->abs2rel($cgi_bin, $app->path_to('root')); + + if (!any { $_ eq $rel } + @{ $app->config->{static}{ignore_dirs}||[] }) { + push @{ $app->config->{static}{ignore_dirs} }, $rel; + } } } =head1 METHODS -=head2 $self->cgi_action($cgi) +=head2 cgi_action + +C<<$self->cgi_action($cgi)>> Takes a path to a CGI from C such as C and returns the action name it is registered as. See L for a discussion on how @@ -156,7 +180,9 @@ sub cgi_action { $action_name } -=head2 $self->cgi_path($cgi) +=head2 cgi_path + +C<<$self->cgi_path($cgi)>> Takes a path to a CGI from C such as C and returns the public path it should be registered under. @@ -174,15 +200,14 @@ sub cgi_path { return "$root/$cgi"; } -=head2 $self->is_perl_cgi($path) +=head2 is_perl_cgi + +C<<$self->is_perl_cgi($path)>> Tries to figure out whether the CGI is Perl or not. If it's Perl, it will be inlined into a sub instead of being forked off, see -wrap_perl_cgi (below.) - -If it's not doing what you expect, you might want to override it, and let me -know as well! +L. =cut @@ -203,7 +228,9 @@ sub is_perl_cgi { $? >> 8 == 0 } -=head2 $self->wrap_perl_cgi($path, $action_name) +=head2 wrap_perl_cgi + +C<<$self->wrap_perl_cgi($path, $action_name)>> Takes the path to a Perl CGI and returns a coderef suitable for passing to cgi_to_response (from L.) @@ -215,7 +242,8 @@ This is similar to how L works, but will only work for well-written CGIs. Otherwise, you may have to override this method to do something more involved (see L.) -Scripts with C<__DATA__> sections now work too. +Scripts with C<__DATA__> sections now work too, as well as scripts that call +C. =cut @@ -264,7 +292,9 @@ sub wrap_perl_cgi { $coderef } -=head2 $self->wrap_nonperl_cgi($path, $action_name) +=head2 wrap_nonperl_cgi + +C<<$self->wrap_nonperl_cgi($path, $action_name)>> Takes the path to a non-Perl CGI and returns a coderef for executing it. @@ -282,6 +312,8 @@ sub wrap_nonperl_cgi { sub { system $cgi } } +__PACKAGE__->meta->make_immutable; + =head1 SEE ALSO L, L, diff --git a/lib/Catalyst/Controller/WrapCGI.pm b/lib/Catalyst/Controller/WrapCGI.pm index 11f2fe7..262e960 100644 --- a/lib/Catalyst/Controller/WrapCGI.pm +++ b/lib/Catalyst/Controller/WrapCGI.pm @@ -9,6 +9,9 @@ use HTTP::Request::AsCGI (); use HTTP::Request (); use URI (); use Catalyst::Exception (); +use URI::Escape; + +use namespace::clean -except => 'meta'; =head1 NAME @@ -91,12 +94,14 @@ open my $REAL_STDOUT, ">>&=".fileno(*STDOUT); =head1 METHODS -=head2 $self->cgi_to_response($c, $coderef) +=head2 cgi_to_response + +C<<$self->cgi_to_response($c, $coderef)>> Does the magic of running $coderef in a CGI environment, and populating the appropriate parts of your Catalyst context with the results. -Calls wrap_cgi (below.) +Calls L. =cut @@ -119,7 +124,9 @@ sub cgi_to_response { $c->res->headers($res->headers); } -=head2 $self->wrap_cgi($c, $coderef) +=head2 wrap_cgi + +C<<$self->wrap_cgi($c, $coderef)>> Runs $coderef in a CGI environment using L, returns an L. @@ -130,7 +137,7 @@ The environment variables to pass on are taken from the configuration for your Controller, see L for an example. If you don't supply a list of environment variables to pass, the whole of %ENV is used. -Used by cgi_to_response (above), which is probably what you want to use as well. +Used by L, which is probably what you want to use as well. =cut @@ -167,13 +174,15 @@ sub wrap_cgi { ? eval { $c->user->obj->$username_field } : ''); - my $path_info = '/'.join '/' => @{ $c->req->args }; + my $path_info = '/'.join '/' => map uri_escape_utf8($_), @{ $c->req->args }; my $env = HTTP::Request::AsCGI->new( $req, ($username ? (REMOTE_USER => $username) : ()), %$filtered_env, - PATH_INFO => $path_info + PATH_INFO => $path_info, + FILEPATH_INFO => '/'.$c->action.$path_info, # eww + SCRIPT_NAME => $c->uri_for($c->action)->path ); { @@ -235,6 +244,7 @@ sub _filtered_env { return { map {; $_ => $env->{$_} } @ok }; } +__PACKAGE__->meta->make_immutable; =head1 ACKNOWLEDGEMENTS diff --git a/t/cgibin.t b/t/cgibin.t index 6e71007..910b5f0 100644 --- a/t/cgibin.t +++ b/t/cgibin.t @@ -6,7 +6,7 @@ use warnings; use FindBin '$Bin'; use lib "$Bin/lib"; -use Test::More tests => 7; +use Test::More tests => 8; use Catalyst::Test 'TestCGIBin'; use HTTP::Request::Common; @@ -54,6 +54,10 @@ $response = request '/my-bin/path/testdata.pl'; is($response->content, "testing\n", 'scripts with __DATA__ sections work'); +$response = request '/my-bin/pathinfo.pl/path/info'; +is($response->content, '/path/info', + 'PATH_INFO works'); + SKIP: { skip "Can't run shell scripts on non-*nix", 1 if $^O eq 'MSWin32' || $^O eq 'VMS'; diff --git a/t/cgibin_root.t b/t/cgibin_root.t index d112e28..876fbe7 100644 --- a/t/cgibin_root.t +++ b/t/cgibin_root.t @@ -11,9 +11,9 @@ use Test::More tests => 1; use Catalyst::Test 'TestCGIBinRoot'; use HTTP::Request::Common; -# test default root of "cgi-bin" +# Test configurable path root and dir -my $response = request POST '/cgi-bin/path/test.pl', [ +my $response = request POST '/cgi/path/test.pl', [ foo => 'bar', bar => 'baz' ]; diff --git a/t/lib/TestApp/Controller/Root.pm b/t/lib/TestApp/Controller/Root.pm index 5ade07b..c1729e7 100644 --- a/t/lib/TestApp/Controller/Root.pm +++ b/t/lib/TestApp/Controller/Root.pm @@ -26,4 +26,24 @@ sub test_path_info : Path('/cgi-bin/test_pathinfo.cgi') { }); } +sub test_filepath_info : Path('/cgi-bin/test_filepathinfo.cgi') { + my ($self, $c) = @_; + + $self->cgi_to_response($c, sub { + my $cgi = CGI->new; + print $cgi->header; + print $ENV{FILEPATH_INFO} + }); +} + +sub test_script_name : Path('/cgi-bin/test_scriptname.cgi') { + my ($self, $c) = @_; + + $self->cgi_to_response($c, sub { + my $cgi = CGI->new; + print $cgi->header; + print $ENV{SCRIPT_NAME} + }); +} + 1; diff --git a/t/lib/TestCGIBin/root/cgi-bin/pathinfo.pl b/t/lib/TestCGIBin/root/cgi-bin/pathinfo.pl new file mode 100755 index 0000000..497d68d --- /dev/null +++ b/t/lib/TestCGIBin/root/cgi-bin/pathinfo.pl @@ -0,0 +1,9 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use CGI ':standard'; + +print header; +print $ENV{PATH_INFO}; diff --git a/t/lib/TestCGIBinRoot.pm b/t/lib/TestCGIBinRoot.pm index cea069e..4414cb9 100644 --- a/t/lib/TestCGIBinRoot.pm +++ b/t/lib/TestCGIBinRoot.pm @@ -3,6 +3,13 @@ package TestCGIBinRoot; use Catalyst::Runtime '5.70'; use parent 'Catalyst'; +__PACKAGE__->config({ + Controller::CGIHandler => { + cgi_root_path => 'cgi', + cgi_dir => 'cgi' + } +}); + __PACKAGE__->setup(qw/Static::Simple/); 1; diff --git a/t/lib/TestCGIBinRoot/root/cgi-bin/path/test.pl b/t/lib/TestCGIBinRoot/root/cgi/path/test.pl similarity index 100% rename from t/lib/TestCGIBinRoot/root/cgi-bin/path/test.pl rename to t/lib/TestCGIBinRoot/root/cgi/path/test.pl diff --git a/t/wrap-cgi.t b/t/wrap-cgi.t index 2477c7d..aa253cf 100644 --- a/t/wrap-cgi.t +++ b/t/wrap-cgi.t @@ -6,7 +6,7 @@ use warnings; use FindBin '$Bin'; use lib "$Bin/lib"; -use Test::More tests => 2; +use Test::More tests => 4; use Catalyst::Test 'TestApp'; use HTTP::Request::Common; @@ -18,6 +18,13 @@ my $response = request POST '/cgi-bin/test.cgi', [ is($response->content, 'foo:bar bar:baz', 'POST to CGI'); -$response = request '/cgi-bin/test_pathinfo.cgi/path/info'; +$response = request '/cgi-bin/test_pathinfo.cgi/path/%2Finfo'; +is($response->content, '/path//info', 'PATH_INFO is correct'); -is($response->content, '/path/info', 'PATH_INFO is correct'); +$response = request '/cgi-bin/test_filepathinfo.cgi/path/%2Finfo'; +is($response->content, '/test_filepath_info/path//info', + 'FILEPATH_INFO is correct (maybe)'); + +$response = request '/cgi-bin/test_scriptname.cgi/foo/bar'; +is($response->content, '/cgi-bin/test_scriptname.cgi', + 'SCRIPT_NAME is correct');