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