package Catalyst::Controller::CGIBin; use Moose; use mro 'c3'; extends 'Catalyst::Controller::WrapCGI'; use File::Slurp 'slurp'; use File::Find::Rule (); use Catalyst::Exception (); use File::Spec::Functions qw/splitdir abs2rel/; use IPC::Open3; use Symbol 'gensym'; use List::MoreUtils 'any'; use IO::File (); use Carp; use namespace::clean -except => 'meta'; =head1 NAME Catalyst::Controller::CGIBin - Serve CGIs from root/cgi-bin =head1 VERSION Version 0.020 =cut our $VERSION = '0.020'; =head1 SYNOPSIS In your controller: package MyApp::Controller::Foo; use parent qw/Catalyst::Controller::CGIBin/; In your .conf: cgi_root_path cgi-bin cgi_dir cgi-bin username_field username # used for REMOTE_USER env var pass_env PERL5LIB pass_env PATH pass_env /^MYAPP_/ =head1 DESCRIPTION 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. This may be supported in the future if there's interest. CGI paths are converted into action names using L. Inherits from L, see the documentation for that 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 = 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); my $class = ref $self || $self; for my $file (File::Find::Rule->file->in($cgi_bin)) { 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 ] }; my ($cgi, $type); if ($self->is_perl_cgi($file)) { # syntax check passed $type = 'Perl'; $cgi = $self->wrap_perl_cgi($file, $action_name); } else { $type = 'Non-Perl'; $cgi = $self->wrap_nonperl_cgi($file, $action_name); } $app->log->info("Registering root/cgi-bin/$cgi_path as a $type CGI.") if $app->debug; my $code = sub { my ($controller, $context) = @_; $controller->cgi_to_response($context, $cgi) }; my $action = $self->create_action( name => $action_name, code => $code, reverse => $reverse, namespace => $namespace, class => $class, attributes => $attrs ); $app->dispatcher->register($app, $action); } $self->next::method($app, @_); # 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 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 CGI actions are named. A path such as C will get the private path C, for controller Foo, with the Cs converted to C<__> and prepended with C, as well as all non-word characters converted to C<_>s. This is because L action names can't have non-word characters in them. This means that C and C for example will both map to the action C so B. =cut sub cgi_action { my ($self, $cgi) = @_; my $action_name = 'CGI_' . join '__' => split '/' => $cgi; $action_name =~ s/\W/_/g; $action_name } =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. The default is to prefix with C<$cgi_root_path/>, using the C config setting, above. =cut sub cgi_path { my ($self, $cgi) = @_; my $root = $self->cgi_root_path; $root =~ s{/*$}{}; return "$root/$cgi"; } =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 L. =cut sub is_perl_cgi { my ($self, $cgi) = @_; my $shebang = IO::File->new($cgi)->getline; return 0 if $shebang !~ /perl/ && $cgi !~ /\.pl\z/; my $taint_check = $shebang =~ /-T/ ? '-T' : ''; open NULL, '>', File::Spec->devnull; my $pid = open3(gensym, '&>NULL', '&>NULL', "$^X $taint_check -c $cgi"); close NULL; waitpid $pid, 0; $? >> 8 == 0 } =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.) C<$action_name> is the generated name for the action representing the CGI file from C. 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, as well as scripts that call C. =cut sub wrap_perl_cgi { my ($self, $cgi, $action_name) = @_; my $code = slurp $cgi; $code =~ s/^__DATA__(?:\r?\n|\r\n?)(.*)//ms; my $data = $1; my $coderef = do { no warnings; # catch exit() and turn it into (effectively) a return # we *must* eval STRING because the code needs to be compiled with the # overridden CORE::GLOBAL::exit in view # # set $0 to the name of the cgi file in case it's used there eval ' my $cgi_exited = "EXIT\n"; BEGIN { *CORE::GLOBAL::exit = sub (;$) { die [ $cgi_exited, $_[0] || 0 ]; } } package Catalyst::Controller::CGIBin::_CGIs_::'.$action_name.'; sub {' . 'local *DATA;' . q{open DATA, '<', \$data;} . qq{local \$0 = "\Q$cgi\E";} . q/my $rv = eval {/ . $code . q/};/ . q{ return $rv unless $@; die $@ if $@ and not ( ref($@) eq 'ARRAY' and $@->[0] eq $cgi_exited ); die "exited nonzero: $@->[1]" if $@->[1] != 0; return $rv; } . '}'; }; croak __PACKAGE__ . ": Could not compile $cgi to coderef: $@" if $@; $coderef } =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. C<$action_name> is the generated name for the action representing the CGI file. By default returns: sub { system $path } =cut sub wrap_nonperl_cgi { my ($self, $cgi, $action_name) = @_; sub { system $cgi } } __PACKAGE__->meta->make_immutable; =head1 SEE ALSO L, L, L, L, L =head1 AUTHOR Rafael Kitover, C<< >> =head1 CONTRIBUTORS Hans Dieter Pearcey, C<< >> =head1 BUGS Please report any bugs or feature requests to C, or through the web interface at L. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes. =head1 SUPPORT More information at: =over 4 =item * RT: CPAN's request tracker L =item * AnnoCPAN: Annotated CPAN documentation L =item * CPAN Ratings L =item * Search CPAN L =back =head1 COPYRIGHT & LICENSE Copyright (c) 2008 Rafael Kitover This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut 1; # End of Catalyst::Controller::CGIBin # vim: expandtab shiftwidth=4 ts=4 tw=80: