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