added cgi_chain_root
[catagits/Catalyst-Controller-WrapCGI.git] / lib / Catalyst / Controller / CGIBin.pm
1 package Catalyst::Controller::CGIBin;
2
3 use Moose;
4 use mro 'c3';
5
6 extends 'Catalyst::Controller::WrapCGI';
7
8 use File::Find::Rule ();
9 use Catalyst::Exception ();
10 use File::Spec::Functions qw/splitdir abs2rel/;
11 use IPC::Open3;
12 use Symbol 'gensym';
13 use List::MoreUtils 'any';
14 use IO::File ();
15 use File::Temp 'tempfile';
16 use File::pushd;
17 use CGI::Compile;
18  
19 use namespace::clean -except => 'meta';
20
21 =head1 NAME
22
23 Catalyst::Controller::CGIBin - Serve CGIs from root/cgi-bin
24
25 =head1 VERSION
26
27 Version 0.027
28
29 =cut
30
31 our $VERSION = '0.027';
32
33 =head1 SYNOPSIS
34
35 In your controller:
36
37     package MyApp::Controller::Foo;
38
39     use parent qw/Catalyst::Controller::CGIBin/;
40
41 In your .conf:
42
43     <Controller::Foo>
44         cgi_root_path  cgi-bin
45         cgi_dir        cgi-bin
46         cgi_chain_root /optional/private/path/to/Chained/root
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
57 Dispatches to CGI files in root/cgi-bin for /cgi-bin/ paths.
58
59 Unlike L<ModPerl::Registry> this module does _NOT_ stat and recompile the CGI
60 for every invocation. This may be supported in the future if there's interest.
61
62 CGI paths are converted into action names using L</cgi_action>.
63
64 Inherits from L<Catalyst::Controller::WrapCGI>, see the documentation for that
65 module for other configuration information.
66
67 =head1 CONFIG PARAMS
68
69 =head2 cgi_root_path
70
71 The global URI path prefix for CGIs, defaults to C<cgi-bin>.
72
73 =head2 cgi_chain_root
74
75 By default L<Path|Catalyst::DispatchType::Path> actions are created for CGIs,
76 but if you specify this option, the actions will be created as
77 L<Chained|Catalyst::DispatchType::Chained> end-points, chaining off the
78 specified private path.
79
80 If this option is used, the L</cgi_root_path> option is ignored. The root path
81 will be determined by your chain.
82
83 The L<PathPart|Catalyst::DispatchType::Chained/PathPart> of the action will be
84 the path to the CGI file.
85
86 =head2 cgi_dir
87
88 Path from which to read CGI files. Can be relative to C<$MYAPP_HOME/root> or
89 absolute.  Defaults to C<$MYAPP_HOME/root/cgi-bin>.
90
91 =cut
92
93 has cgi_root_path  => (is => 'ro', isa => 'Str', default => 'cgi-bin');
94 has cgi_chain_root => (is => 'ro', isa => 'Str');
95 has cgi_dir        => (is => 'ro', isa => 'Str', default => 'cgi-bin');
96
97 sub register_actions {
98     my ($self, $app) = @_;
99
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);
103
104     my $namespace = $self->action_namespace($app);
105
106     my $class = ref $self || $self;
107
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;
112         next if $cgi_path =~ /\.swp\z/;
113
114         my $path        = join '/' => splitdir($cgi_path);
115         my $action_name = $self->cgi_action($path);
116         my $reverse     = $namespace ? "$namespace/$action_name" : $action_name;
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         };
126
127         my ($cgi, $type);
128
129         if ($self->is_perl_cgi($file)) { # syntax check passed
130             $type = 'Perl';
131             $cgi  = $self->wrap_perl_cgi($file, $action_name);
132         } else {
133             $type = 'Non-Perl';
134             $cgi  = $self->wrap_nonperl_cgi($file, $action_name);
135         }
136
137         $app->log->info("Registering root/cgi-bin/$cgi_path as a $type CGI.")
138             if $app->debug;
139
140         my $code = sub {
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
154         $app->dispatcher->register($app, $action);
155     }
156
157     $self->next::method($app, @_);
158
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         }
167     }
168 }
169
170 =head1 METHODS
171
172 =head2 cgi_action
173
174 C<< $self->cgi_action($cgi) >>
175
176 Takes a path to a CGI from C<root/cgi-bin> such as C<foo/bar.cgi> and returns
177 the action name it is registered as.
178
179 =cut
180
181 sub cgi_action {
182     my ($self, $cgi) = @_;
183
184     my $action_name = 'CGI_' . $cgi;
185     $action_name =~ s/([^A-Za-z0-9_])/sprintf("_%2x", unpack("C", $1))/eg;
186
187     return $action_name;
188 }
189
190 =head2 cgi_path
191
192 C<< $self->cgi_path($cgi) >>
193
194 Takes a path to a CGI from C<root/cgi-bin> such as C<foo/bar.cgi> and returns
195 the public path it should be registered under.
196
197 The default is to prefix with C<$cgi_root_path/>, using the C<cgi_root_path>
198 config setting, above.
199
200 =cut
201
202 sub cgi_path {
203     my ($self, $cgi) = @_;
204
205     my $root = $self->cgi_root_path;
206     $root =~ s{/*$}{};
207     return "$root/$cgi";
208 }
209
210 =head2 is_perl_cgi
211
212 C<< $self->is_perl_cgi($path) >>
213
214 Tries to figure out whether the CGI is Perl or not.
215
216 If it's Perl, it will be inlined into a sub instead of being forked off, see
217 L</wrap_perl_cgi>.
218
219 =cut
220
221 sub is_perl_cgi {
222     my ($self, $cgi) = @_;
223
224     my (undef, $tempfile) = tempfile;
225
226     my $pid = fork;
227     die "Cannot fork: $!" unless defined $pid;
228
229     if ($pid) {
230         waitpid $pid, 0;
231         my $errors = IO::File->new($tempfile)->getline;
232         unlink $tempfile;
233         return $errors ? 0 : 1;
234     }
235
236     # child
237     local *NULL;
238     open NULL, '>', File::Spec->devnull;
239     open STDOUT, '>&', \*NULL;
240     open STDERR, '>&', \*NULL;
241     close STDIN;
242
243     eval { $self->wrap_perl_cgi($cgi, '__DUMMY__') };
244
245     IO::File->new(">$tempfile")->print($@);
246
247     exit;
248 }
249
250 =head2 wrap_perl_cgi
251
252 C<< $self->wrap_perl_cgi($path, $action_name) >>
253
254 Takes the path to a Perl CGI and returns a coderef suitable for passing to
255 cgi_to_response (from L<Catalyst::Controller::WrapCGI>) using L<CGI::Compile>.
256
257 C<$action_name> is the generated name for the action representing the CGI file
258 from C<cgi_action>.
259
260 This is similar to how L<ModPerl::Registry> works, but will only work for
261 well-written CGIs. Otherwise, you may have to override this method to do
262 something more involved (see L<ModPerl::PerlRun>.)
263
264 Scripts with C<__DATA__> sections now work too, as well as scripts that call
265 C<exit()>.
266
267 =cut
268
269 sub wrap_perl_cgi {
270     my ($self, $cgi, $action_name) = @_;
271
272     return CGI::Compile->compile($cgi,
273         "Catalyst::Controller::CGIBin::_CGIs_::$action_name");
274 }
275
276 =head2 wrap_nonperl_cgi
277
278 C<< $self->wrap_nonperl_cgi($path, $action_name) >>
279
280 Takes the path to a non-Perl CGI and returns a coderef for executing it.
281
282 C<$action_name> is the generated name for the action representing the CGI file.
283
284 By default returns something like:
285
286     sub { system $path }
287
288 =cut
289
290 sub wrap_nonperl_cgi {
291     my ($self, $cgi, $action_name) = @_;
292
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     };
311 }
312
313 __PACKAGE__->meta->make_immutable;
314
315 =head1 SEE ALSO
316
317 L<Catalyst::Controller::WrapCGI>, L<CatalystX::GlobalContext>,
318 L<Catalyst::Controller>, L<CGI>, L<CGI::Compile>, L<Catalyst>
319
320 =head1 BUGS
321
322 Please report any bugs or feature requests to C<bug-catalyst-controller-wrapcgi at
323 rt.cpan.org>, or through the web interface at
324 L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Catalyst-Controller-WrapCGI>.
325 I will be notified, and then you'll automatically be notified of progress on
326 your bug as I make changes.
327
328 =head1 SUPPORT
329
330 More information at:
331
332 =over 4
333
334 =item * RT: CPAN's request tracker
335
336 L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Catalyst-Controller-WrapCGI>
337
338 =item * AnnoCPAN: Annotated CPAN documentation
339
340 L<http://annocpan.org/dist/Catalyst-Controller-WrapCGI>
341
342 =item * CPAN Ratings
343
344 L<http://cpanratings.perl.org/d/Catalyst-Controller-WrapCGI>
345
346 =item * Search CPAN
347
348 L<http://search.cpan.org/dist/Catalyst-Controller-WrapCGI>
349
350 =back
351
352 =head1 AUTHOR
353
354 See L<Catalyst::Controller::WrapCGI/AUTHOR> and
355 L<Catalyst::Controller::WrapCGI/CONTRIBUTORS>.
356
357 =head1 COPYRIGHT & LICENSE
358
359 Copyright (c) 2008-2009 L<Catalyst::Controller::WrapCGI/AUTHOR> and
360 L<Catalyst::Controller::WrapCGI/CONTRIBUTORS>.
361
362 This program is free software; you can redistribute it and/or modify it
363 under the same terms as Perl itself.
364
365 =cut
366
367 1; # End of Catalyst::Controller::CGIBin
368
369 # vim: expandtab shiftwidth=4 ts=4 tw=80: