fix tests for new HTTP::Request::AsCGI, 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
21a20b7e 25=cut
26
05f3f060 27our $VERSION = '0.029';
21a20b7e 28
29=head1 SYNOPSIS
30
31In your controller:
32
33 package MyApp::Controller::Foo;
34
35 use parent qw/Catalyst::Controller::CGIBin/;
36
21a20b7e 37In your .conf:
38
39 <Controller::Foo>
9e040aeb 40 cgi_root_path cgi-bin
41 cgi_dir cgi-bin
42 cgi_chain_root /optional/private/path/to/Chained/root
43 cgi_file_pattern *.cgi
44 # or regex
45 cgi_file_pattern /\.pl\z/
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
1d40d9c3 72=head2 cgi_chain_root
73
74By default L<Path|Catalyst::DispatchType::Path> actions are created for CGIs,
75but if you specify this option, the actions will be created as
76L<Chained|Catalyst::DispatchType::Chained> end-points, chaining off the
77specified private path.
78
79If this option is used, the L</cgi_root_path> option is ignored. The root path
80will be determined by your chain.
81
82The L<PathPart|Catalyst::DispatchType::Chained/PathPart> of the action will be
83the path to the CGI file.
84
f410f043 85=head2 cgi_dir
86
87Path from which to read CGI files. Can be relative to C<$MYAPP_HOME/root> or
88absolute. Defaults to C<$MYAPP_HOME/root/cgi-bin>.
21a20b7e 89
9e040aeb 90=head2 cgi_file_pattern
91
92By default all files in L</cgi_dir> will be loaded as CGIs, however, with this
93option you can specify either a glob or a regex to match the names of files you
94want to be loaded.
95
96Can be an array of globs/regexes as well.
97
21a20b7e 98=cut
99
9e040aeb 100has cgi_root_path => (is => 'ro', isa => 'Str', default => 'cgi-bin');
101has cgi_chain_root => (is => 'ro', isa => 'Str');
102has cgi_dir => (is => 'ro', isa => 'Str', default => 'cgi-bin');
103has cgi_file_pattern => (is => 'rw', default => sub { ['*'] });
9cd47364 104
21a20b7e 105sub register_actions {
ee75330f 106 my ($self, $app) = @_;
21a20b7e 107
f410f043 108 my $cgi_bin = File::Spec->file_name_is_absolute($self->cgi_dir) ?
109 $self->cgi_dir
110 : $app->path_to('root', $self->cgi_dir);
21a20b7e 111
ee75330f 112 my $namespace = $self->action_namespace($app);
21a20b7e 113
114 my $class = ref $self || $self;
115
9e040aeb 116 my $patterns = $self->cgi_file_pattern;
117 $patterns = [ $patterns ] if not ref $patterns;
118 for my $pat (@$patterns) {
119 if ($pat =~ m{^/(.*)/\z}) {
120 $pat = qr/$1/;
121 }
122 }
123 $self->cgi_file_pattern($patterns);
124
125 for my $file (File::Find::Rule->file->name(@$patterns)->in($cgi_bin)) {
c264816e 126 my $cgi_path = abs2rel($file, $cgi_bin);
127
128 next if any { $_ eq '.svn' } splitdir $cgi_path;
f410f043 129 next if $cgi_path =~ /\.swp\z/;
c264816e 130
12d29ebf 131 my $path = join '/' => splitdir($cgi_path);
132 my $action_name = $self->cgi_action($path);
133 my $reverse = $namespace ? "$namespace/$action_name" : $action_name;
1d40d9c3 134
135 my $attrs = do {
136 if (my $chain_root = $self->cgi_chain_root) {
137 { Chained => [ $chain_root ], PathPart => [ $path ], Args => [] };
138 }
139 else {
140 { Path => [ $self->cgi_path($path) ] };
141 }
142 };
12d29ebf 143
21a20b7e 144 my ($cgi, $type);
21a20b7e 145
c264816e 146 if ($self->is_perl_cgi($file)) { # syntax check passed
21a20b7e 147 $type = 'Perl';
12d29ebf 148 $cgi = $self->wrap_perl_cgi($file, $action_name);
21a20b7e 149 } else {
21a20b7e 150 $type = 'Non-Perl';
12d29ebf 151 $cgi = $self->wrap_nonperl_cgi($file, $action_name);
21a20b7e 152 }
153
c264816e 154 $app->log->info("Registering root/cgi-bin/$cgi_path as a $type CGI.")
ee75330f 155 if $app->debug;
21a20b7e 156
c264816e 157 my $code = sub {
21a20b7e 158 my ($controller, $context) = @_;
159 $controller->cgi_to_response($context, $cgi)
160 };
161
162 my $action = $self->create_action(
163 name => $action_name,
164 code => $code,
165 reverse => $reverse,
166 namespace => $namespace,
167 class => $class,
168 attributes => $attrs
169 );
170
ee75330f 171 $app->dispatcher->register($app, $action);
21a20b7e 172 }
173
ee75330f 174 $self->next::method($app, @_);
2340af9d 175
f410f043 176# Tell Static::Simple to ignore cgi_dir
177 if ($cgi_bin =~ /^@{[ $app->path_to('root') ]}/) {
178 my $rel = File::Spec->abs2rel($cgi_bin, $app->path_to('root'));
179
180 if (!any { $_ eq $rel }
181 @{ $app->config->{static}{ignore_dirs}||[] }) {
182 push @{ $app->config->{static}{ignore_dirs} }, $rel;
183 }
2340af9d 184 }
21a20b7e 185}
186
187=head1 METHODS
188
f410f043 189=head2 cgi_action
190
b9548267 191C<< $self->cgi_action($cgi) >>
21a20b7e 192
193Takes a path to a CGI from C<root/cgi-bin> such as C<foo/bar.cgi> and returns
d5ba2ab2 194the action name it is registered as.
fbaba9dd 195
21a20b7e 196=cut
197
198sub cgi_action {
199 my ($self, $cgi) = @_;
200
d5ba2ab2 201 my $action_name = 'CGI_' . $cgi;
202 $action_name =~ s/([^A-Za-z0-9_])/sprintf("_%2x", unpack("C", $1))/eg;
21a20b7e 203
d5ba2ab2 204 return $action_name;
21a20b7e 205}
206
f410f043 207=head2 cgi_path
208
b9548267 209C<< $self->cgi_path($cgi) >>
9cc2dd4c 210
211Takes a path to a CGI from C<root/cgi-bin> such as C<foo/bar.cgi> and returns
212the public path it should be registered under.
213
184837e7 214The default is to prefix with C<$cgi_root_path/>, using the C<cgi_root_path>
215config setting, above.
9cc2dd4c 216
217=cut
218
219sub cgi_path {
220 my ($self, $cgi) = @_;
9cd47364 221
222 my $root = $self->cgi_root_path;
223 $root =~ s{/*$}{};
224 return "$root/$cgi";
9cc2dd4c 225}
226
f410f043 227=head2 is_perl_cgi
228
b9548267 229C<< $self->is_perl_cgi($path) >>
c264816e 230
231Tries to figure out whether the CGI is Perl or not.
232
233If it's Perl, it will be inlined into a sub instead of being forked off, see
f410f043 234L</wrap_perl_cgi>.
c264816e 235
236=cut
237
238sub is_perl_cgi {
239 my ($self, $cgi) = @_;
240
e889d526 241 my (undef, $tempfile) = tempfile;
c264816e 242
e889d526 243 my $pid = fork;
244 die "Cannot fork: $!" unless defined $pid;
c264816e 245
e889d526 246 if ($pid) {
247 waitpid $pid, 0;
248 my $errors = IO::File->new($tempfile)->getline;
249 unlink $tempfile;
250 return $errors ? 0 : 1;
251 }
c264816e 252
e889d526 253 # child
254 local *NULL;
c264816e 255 open NULL, '>', File::Spec->devnull;
e889d526 256 open STDOUT, '>&', \*NULL;
257 open STDERR, '>&', \*NULL;
258 close STDIN;
c264816e 259
efa4a434 260 eval { $self->wrap_perl_cgi($cgi, '__DUMMY__') };
261
e889d526 262 IO::File->new(">$tempfile")->print($@);
efa4a434 263
e889d526 264 exit;
c264816e 265}
266
f410f043 267=head2 wrap_perl_cgi
268
b9548267 269C<< $self->wrap_perl_cgi($path, $action_name) >>
c264816e 270
271Takes the path to a Perl CGI and returns a coderef suitable for passing to
d9280b8f 272cgi_to_response (from L<Catalyst::Controller::WrapCGI>) using L<CGI::Compile>.
c264816e 273
fbaba9dd 274C<$action_name> is the generated name for the action representing the CGI file
275from C<cgi_action>.
c264816e 276
277This is similar to how L<ModPerl::Registry> works, but will only work for
278well-written CGIs. Otherwise, you may have to override this method to do
279something more involved (see L<ModPerl::PerlRun>.)
280
f410f043 281Scripts with C<__DATA__> sections now work too, as well as scripts that call
282C<exit()>.
fbaba9dd 283
c264816e 284=cut
285
286sub wrap_perl_cgi {
12d29ebf 287 my ($self, $cgi, $action_name) = @_;
288
d9280b8f 289 return CGI::Compile->compile($cgi,
290 "Catalyst::Controller::CGIBin::_CGIs_::$action_name");
c264816e 291}
292
f410f043 293=head2 wrap_nonperl_cgi
294
b9548267 295C<< $self->wrap_nonperl_cgi($path, $action_name) >>
c264816e 296
297Takes the path to a non-Perl CGI and returns a coderef for executing it.
298
12d29ebf 299C<$action_name> is the generated name for the action representing the CGI file.
300
d9280b8f 301By default returns something like:
c264816e 302
303 sub { system $path }
304
305=cut
306
307sub wrap_nonperl_cgi {
12d29ebf 308 my ($self, $cgi, $action_name) = @_;
c264816e 309
d9280b8f 310 return sub {
311 system $cgi;
312
313 if ($? == -1) {
314 die "failed to execute CGI '$cgi': $!";
315 }
316 elsif ($? & 127) {
317 die sprintf "CGI '$cgi' died with signal %d, %s coredump",
318 ($? & 127), ($? & 128) ? 'with' : 'without';
319 }
320 else {
321 my $exit_code = $? >> 8;
322
323 return 0 if $exit_code == 0;
324
325 die "CGI '$cgi' exited non-zero with: $exit_code";
326 }
327 };
c264816e 328}
329
f410f043 330__PACKAGE__->meta->make_immutable;
331
c264816e 332=head1 SEE ALSO
333
334L<Catalyst::Controller::WrapCGI>, L<CatalystX::GlobalContext>,
d5ba2ab2 335L<Catalyst::Controller>, L<CGI>, L<CGI::Compile>, L<Catalyst>
c264816e 336
21a20b7e 337=head1 BUGS
338
339Please report any bugs or feature requests to C<bug-catalyst-controller-wrapcgi at
340rt.cpan.org>, or through the web interface at
341L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Catalyst-Controller-WrapCGI>.
342I will be notified, and then you'll automatically be notified of progress on
343your bug as I make changes.
344
345=head1 SUPPORT
346
347More information at:
348
349=over 4
350
351=item * RT: CPAN's request tracker
352
353L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Catalyst-Controller-WrapCGI>
354
355=item * AnnoCPAN: Annotated CPAN documentation
356
357L<http://annocpan.org/dist/Catalyst-Controller-WrapCGI>
358
359=item * CPAN Ratings
360
361L<http://cpanratings.perl.org/d/Catalyst-Controller-WrapCGI>
362
363=item * Search CPAN
364
365L<http://search.cpan.org/dist/Catalyst-Controller-WrapCGI>
366
367=back
368
efa4a434 369=head1 AUTHOR
370
371See L<Catalyst::Controller::WrapCGI/AUTHOR> and
372L<Catalyst::Controller::WrapCGI/CONTRIBUTORS>.
373
21a20b7e 374=head1 COPYRIGHT & LICENSE
375
efa4a434 376Copyright (c) 2008-2009 L<Catalyst::Controller::WrapCGI/AUTHOR> and
377L<Catalyst::Controller::WrapCGI/CONTRIBUTORS>.
21a20b7e 378
379This program is free software; you can redistribute it and/or modify it
380under the same terms as Perl itself.
381
382=cut
383
3841; # End of Catalyst::Controller::CGIBin
9e040aeb 385# vim:et sw=4 sts=4 tw=0: