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