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