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