fix AUTHOR pod sections, new release
[catagits/Catalyst-Controller-WrapCGI.git] / lib / Catalyst / Controller / CGIBin.pm
CommitLineData
21a20b7e 1package Catalyst::Controller::CGIBin;
2
9cd47364 3use Moose;
1ce18a56 4use mro 'c3';
9cd47364 5
6extends 'Catalyst::Controller::WrapCGI';
7
21a20b7e 8use File::Slurp 'slurp';
9use File::Find::Rule ();
21a20b7e 10use Catalyst::Exception ();
c264816e 11use File::Spec::Functions qw/splitdir abs2rel/;
12use IPC::Open3;
13use Symbol 'gensym';
14use List::MoreUtils 'any';
15use IO::File ();
1ce18a56 16use Carp;
16db0bfc 17
2340af9d 18use namespace::clean -except => 'meta';
21a20b7e 19
21a20b7e 20=head1 NAME
21
22Catalyst::Controller::CGIBin - Serve CGIs from root/cgi-bin
23
24=head1 VERSION
25
2e6f9c85 26Version 0.020
21a20b7e 27
28=cut
29
2e6f9c85 30our $VERSION = '0.020';
21a20b7e 31
32=head1 SYNOPSIS
33
34In your controller:
35
36 package MyApp::Controller::Foo;
37
38 use parent qw/Catalyst::Controller::CGIBin/;
39
21a20b7e 40In your .conf:
41
42 <Controller::Foo>
9cd47364 43 cgi_root_path cgi-bin
f410f043 44 cgi_dir cgi-bin
21a20b7e 45 <CGI>
46 username_field username # used for REMOTE_USER env var
47 pass_env PERL5LIB
48 pass_env PATH
49 pass_env /^MYAPP_/
50 </CGI>
51 </Controller::Foo>
52
53=head1 DESCRIPTION
54
c264816e 55Dispatches to CGI files in root/cgi-bin for /cgi-bin/ paths.
56
57Unlike L<ModPerl::Registry> this module does _NOT_ stat and recompile the CGI
f410f043 58for every invocation. This may be supported in the future if there's interest.
21a20b7e 59
f410f043 60CGI paths are converted into action names using L</cgi_action>.
ee75330f 61
21a20b7e 62Inherits from L<Catalyst::Controller::WrapCGI>, see the documentation for that
f410f043 63module for other configuration information.
64
65=head1 CONFIG PARAMS
66
67=head2 cgi_root_path
68
184837e7 69The global URI path prefix for CGIs, defaults to C<cgi-bin>.
f410f043 70
71=head2 cgi_dir
72
73Path from which to read CGI files. Can be relative to C<$MYAPP_HOME/root> or
74absolute. Defaults to C<$MYAPP_HOME/root/cgi-bin>.
21a20b7e 75
76=cut
77
9cd47364 78has cgi_root_path => (is => 'ro', isa => 'Str', default => 'cgi-bin');
f410f043 79has cgi_dir => (is => 'ro', isa => 'Str', default => 'cgi-bin');
9cd47364 80
21a20b7e 81sub register_actions {
ee75330f 82 my ($self, $app) = @_;
21a20b7e 83
f410f043 84 my $cgi_bin = File::Spec->file_name_is_absolute($self->cgi_dir) ?
85 $self->cgi_dir
86 : $app->path_to('root', $self->cgi_dir);
21a20b7e 87
ee75330f 88 my $namespace = $self->action_namespace($app);
21a20b7e 89
90 my $class = ref $self || $self;
91
c264816e 92 for my $file (File::Find::Rule->file->in($cgi_bin)) {
93 my $cgi_path = abs2rel($file, $cgi_bin);
94
95 next if any { $_ eq '.svn' } splitdir $cgi_path;
f410f043 96 next if $cgi_path =~ /\.swp\z/;
c264816e 97
12d29ebf 98 my $path = join '/' => splitdir($cgi_path);
99 my $action_name = $self->cgi_action($path);
9cc2dd4c 100 my $public_path = $self->cgi_path($path);
12d29ebf 101 my $reverse = $namespace ? "$namespace/$action_name" : $action_name;
f410f043 102 my $attrs = { Path => [ $public_path ] };
12d29ebf 103
21a20b7e 104 my ($cgi, $type);
21a20b7e 105
c264816e 106 if ($self->is_perl_cgi($file)) { # syntax check passed
21a20b7e 107 $type = 'Perl';
12d29ebf 108 $cgi = $self->wrap_perl_cgi($file, $action_name);
21a20b7e 109 } else {
21a20b7e 110 $type = 'Non-Perl';
12d29ebf 111 $cgi = $self->wrap_nonperl_cgi($file, $action_name);
21a20b7e 112 }
113
c264816e 114 $app->log->info("Registering root/cgi-bin/$cgi_path as a $type CGI.")
ee75330f 115 if $app->debug;
21a20b7e 116
c264816e 117 my $code = sub {
21a20b7e 118 my ($controller, $context) = @_;
119 $controller->cgi_to_response($context, $cgi)
120 };
121
122 my $action = $self->create_action(
123 name => $action_name,
124 code => $code,
125 reverse => $reverse,
126 namespace => $namespace,
127 class => $class,
128 attributes => $attrs
129 );
130
ee75330f 131 $app->dispatcher->register($app, $action);
21a20b7e 132 }
133
ee75330f 134 $self->next::method($app, @_);
2340af9d 135
f410f043 136# Tell Static::Simple to ignore cgi_dir
137 if ($cgi_bin =~ /^@{[ $app->path_to('root') ]}/) {
138 my $rel = File::Spec->abs2rel($cgi_bin, $app->path_to('root'));
139
140 if (!any { $_ eq $rel }
141 @{ $app->config->{static}{ignore_dirs}||[] }) {
142 push @{ $app->config->{static}{ignore_dirs} }, $rel;
143 }
2340af9d 144 }
21a20b7e 145}
146
147=head1 METHODS
148
f410f043 149=head2 cgi_action
150
b9548267 151C<< $self->cgi_action($cgi) >>
21a20b7e 152
153Takes a path to a CGI from C<root/cgi-bin> such as C<foo/bar.cgi> and returns
c264816e 154the action name it is registered as. See L</DESCRIPTION> for a discussion on how
155CGI actions are named.
21a20b7e 156
fbaba9dd 157A path such as C<root/cgi-bin/hlagh/bar.cgi> will get the private path
158C<foo/CGI_hlagh__bar_cgi>, for controller Foo, with the C</>s converted to C<__>
159and prepended with C<CGI_>, as well as all non-word characters converted to
160C<_>s. This is because L<Catalyst> action names can't have non-word characters
161in them.
162
163This means that C<foo/bar.cgi> and C<foo__bar.cgi> for example will both map to
164the action C<CGI_foo__bar_cgi> so B<DON'T DO THAT>.
165
21a20b7e 166=cut
167
168sub cgi_action {
169 my ($self, $cgi) = @_;
170
fbaba9dd 171 my $action_name = 'CGI_' . join '__' => split '/' => $cgi;
21a20b7e 172 $action_name =~ s/\W/_/g;
173
174 $action_name
175}
176
f410f043 177=head2 cgi_path
178
b9548267 179C<< $self->cgi_path($cgi) >>
9cc2dd4c 180
181Takes a path to a CGI from C<root/cgi-bin> such as C<foo/bar.cgi> and returns
182the public path it should be registered under.
183
184837e7 184The default is to prefix with C<$cgi_root_path/>, using the C<cgi_root_path>
185config setting, above.
9cc2dd4c 186
187=cut
188
189sub cgi_path {
190 my ($self, $cgi) = @_;
9cd47364 191
192 my $root = $self->cgi_root_path;
193 $root =~ s{/*$}{};
194 return "$root/$cgi";
9cc2dd4c 195}
196
f410f043 197=head2 is_perl_cgi
198
b9548267 199C<< $self->is_perl_cgi($path) >>
c264816e 200
201Tries to figure out whether the CGI is Perl or not.
202
203If it's Perl, it will be inlined into a sub instead of being forked off, see
f410f043 204L</wrap_perl_cgi>.
c264816e 205
206=cut
207
208sub is_perl_cgi {
209 my ($self, $cgi) = @_;
210
211 my $shebang = IO::File->new($cgi)->getline;
212
2369e5a4 213 return 0 if $shebang !~ /perl/ && $cgi !~ /\.pl\z/;
c264816e 214
215 my $taint_check = $shebang =~ /-T/ ? '-T' : '';
216
217 open NULL, '>', File::Spec->devnull;
218 my $pid = open3(gensym, '&>NULL', '&>NULL', "$^X $taint_check -c $cgi");
219 close NULL;
220 waitpid $pid, 0;
221
222 $? >> 8 == 0
223}
224
f410f043 225=head2 wrap_perl_cgi
226
b9548267 227C<< $self->wrap_perl_cgi($path, $action_name) >>
c264816e 228
229Takes the path to a Perl CGI and returns a coderef suitable for passing to
230cgi_to_response (from L<Catalyst::Controller::WrapCGI>.)
231
fbaba9dd 232C<$action_name> is the generated name for the action representing the CGI file
233from C<cgi_action>.
c264816e 234
235This is similar to how L<ModPerl::Registry> works, but will only work for
236well-written CGIs. Otherwise, you may have to override this method to do
237something more involved (see L<ModPerl::PerlRun>.)
238
f410f043 239Scripts with C<__DATA__> sections now work too, as well as scripts that call
240C<exit()>.
fbaba9dd 241
c264816e 242=cut
243
244sub wrap_perl_cgi {
12d29ebf 245 my ($self, $cgi, $action_name) = @_;
246
1ce18a56 247 my $code = slurp $cgi;
248
fbaba9dd 249 $code =~ s/^__DATA__(?:\r?\n|\r\n?)(.*)//ms;
1ce18a56 250 my $data = $1;
251
252 my $coderef = do {
12d29ebf 253 no warnings;
bdb35995 254 # catch exit() and turn it into (effectively) a return
255 # we *must* eval STRING because the code needs to be compiled with the
256 # overridden CORE::GLOBAL::exit in view
257 #
258 # set $0 to the name of the cgi file in case it's used there
12d29ebf 259 eval '
bdb35995 260 my $cgi_exited = "EXIT\n";
261 BEGIN { *CORE::GLOBAL::exit = sub (;$) {
262 die [ $cgi_exited, $_[0] || 0 ];
263 } }
12d29ebf 264 package Catalyst::Controller::CGIBin::_CGIs_::'.$action_name.';
1ce18a56 265 sub {'
266 . 'local *DATA;'
267 . q{open DATA, '<', \$data;}
bdb35995 268 . qq{local \$0 = "\Q$cgi\E";}
269 . q/my $rv = eval {/
1ce18a56 270 . $code
bdb35995 271 . q/};/
272 . q{
273 return $rv unless $@;
274 die $@ if $@ and not (
275 ref($@) eq 'ARRAY' and
276 $@->[0] eq $cgi_exited
277 );
278 die "exited nonzero: $@->[1]" if $@->[1] != 0;
279 return $rv;
280 }
1ce18a56 281 . '}';
282 };
283
284 croak __PACKAGE__ . ": Could not compile $cgi to coderef: $@" if $@;
285
286 $coderef
c264816e 287}
288
f410f043 289=head2 wrap_nonperl_cgi
290
b9548267 291C<< $self->wrap_nonperl_cgi($path, $action_name) >>
c264816e 292
293Takes the path to a non-Perl CGI and returns a coderef for executing it.
294
12d29ebf 295C<$action_name> is the generated name for the action representing the CGI file.
296
c264816e 297By default returns:
298
299 sub { system $path }
300
301=cut
302
303sub wrap_nonperl_cgi {
12d29ebf 304 my ($self, $cgi, $action_name) = @_;
c264816e 305
306 sub { system $cgi }
307}
308
f410f043 309__PACKAGE__->meta->make_immutable;
310
c264816e 311=head1 SEE ALSO
312
313L<Catalyst::Controller::WrapCGI>, L<CatalystX::GlobalContext>,
314L<Catalyst::Controller>, L<CGI>, L<Catalyst>
315
2e6f9c85 316=head1 AUTHOR
21a20b7e 317
318Rafael Kitover, C<< <rkitover at cpan.org> >>
319
2e6f9c85 320=head1 CONTRIBUTORS
321
0d83c5de 322Hans Dieter Pearcey, C<< <hdp at cpan.org> >>
323
21a20b7e 324=head1 BUGS
325
326Please report any bugs or feature requests to C<bug-catalyst-controller-wrapcgi at
327rt.cpan.org>, or through the web interface at
328L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Catalyst-Controller-WrapCGI>.
329I will be notified, and then you'll automatically be notified of progress on
330your bug as I make changes.
331
332=head1 SUPPORT
333
334More information at:
335
336=over 4
337
338=item * RT: CPAN's request tracker
339
340L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Catalyst-Controller-WrapCGI>
341
342=item * AnnoCPAN: Annotated CPAN documentation
343
344L<http://annocpan.org/dist/Catalyst-Controller-WrapCGI>
345
346=item * CPAN Ratings
347
348L<http://cpanratings.perl.org/d/Catalyst-Controller-WrapCGI>
349
350=item * Search CPAN
351
352L<http://search.cpan.org/dist/Catalyst-Controller-WrapCGI>
353
354=back
355
356=head1 COPYRIGHT & LICENSE
357
358Copyright (c) 2008 Rafael Kitover
359
360This program is free software; you can redistribute it and/or modify it
361under the same terms as Perl itself.
362
363=cut
364
3651; # End of Catalyst::Controller::CGIBin
366
367# vim: expandtab shiftwidth=4 ts=4 tw=80: