->req->remote_user support, new release
[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::Slurp 'slurp';
9use File::Find::Rule ();
21a20b7e 10use Catalyst::Exception ();
c264816e 11use File::Spec::Functions qw/splitdir abs2rel/;
12use IPC::Open3;
13use Symbol 'gensym';
14use List::MoreUtils 'any';
15use IO::File ();
1ce18a56 16use Carp;
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
24=head1 VERSION
25
b9548267 26Version 0.016
21a20b7e 27
28=cut
29
b9548267 30our $VERSION = '0.016';
21a20b7e 31
32=head1 SYNOPSIS
33
34In your controller:
35
36 package MyApp::Controller::Foo;
37
38 use parent qw/Catalyst::Controller::CGIBin/;
39
40 # example of a forward to /cgi-bin/hlagh/mtfnpy.cgi
9cd47364 41 sub serve_cgi : Local Args(0) {
21a20b7e 42 my ($self, $c) = @_;
43 $c->forward($self->cgi_action('hlagh/mtfnpy.cgi'));
44 }
45
46In your .conf:
47
48 <Controller::Foo>
9cd47364 49 cgi_root_path cgi-bin
f410f043 50 cgi_dir cgi-bin
21a20b7e 51 <CGI>
52 username_field username # used for REMOTE_USER env var
53 pass_env PERL5LIB
54 pass_env PATH
55 pass_env /^MYAPP_/
56 </CGI>
57 </Controller::Foo>
58
59=head1 DESCRIPTION
60
c264816e 61Dispatches to CGI files in root/cgi-bin for /cgi-bin/ paths.
62
63Unlike L<ModPerl::Registry> this module does _NOT_ stat and recompile the CGI
f410f043 64for every invocation. This may be supported in the future if there's interest.
21a20b7e 65
f410f043 66CGI paths are converted into action names using L</cgi_action>.
ee75330f 67
21a20b7e 68Inherits from L<Catalyst::Controller::WrapCGI>, see the documentation for that
f410f043 69module for other configuration information.
70
71=head1 CONFIG PARAMS
72
73=head2 cgi_root_path
74
184837e7 75The global URI path prefix for CGIs, defaults to C<cgi-bin>.
f410f043 76
77=head2 cgi_dir
78
79Path from which to read CGI files. Can be relative to C<$MYAPP_HOME/root> or
80absolute. Defaults to C<$MYAPP_HOME/root/cgi-bin>.
21a20b7e 81
82=cut
83
9cd47364 84has cgi_root_path => (is => 'ro', isa => 'Str', default => 'cgi-bin');
f410f043 85has cgi_dir => (is => 'ro', isa => 'Str', default => 'cgi-bin');
9cd47364 86
21a20b7e 87sub register_actions {
ee75330f 88 my ($self, $app) = @_;
21a20b7e 89
f410f043 90 my $cgi_bin = File::Spec->file_name_is_absolute($self->cgi_dir) ?
91 $self->cgi_dir
92 : $app->path_to('root', $self->cgi_dir);
21a20b7e 93
ee75330f 94 my $namespace = $self->action_namespace($app);
21a20b7e 95
96 my $class = ref $self || $self;
97
c264816e 98 for my $file (File::Find::Rule->file->in($cgi_bin)) {
99 my $cgi_path = abs2rel($file, $cgi_bin);
100
101 next if any { $_ eq '.svn' } splitdir $cgi_path;
f410f043 102 next if $cgi_path =~ /\.swp\z/;
c264816e 103
12d29ebf 104 my $path = join '/' => splitdir($cgi_path);
105 my $action_name = $self->cgi_action($path);
9cc2dd4c 106 my $public_path = $self->cgi_path($path);
12d29ebf 107 my $reverse = $namespace ? "$namespace/$action_name" : $action_name;
f410f043 108 my $attrs = { Path => [ $public_path ] };
12d29ebf 109
21a20b7e 110 my ($cgi, $type);
21a20b7e 111
c264816e 112 if ($self->is_perl_cgi($file)) { # syntax check passed
21a20b7e 113 $type = 'Perl';
12d29ebf 114 $cgi = $self->wrap_perl_cgi($file, $action_name);
21a20b7e 115 } else {
21a20b7e 116 $type = 'Non-Perl';
12d29ebf 117 $cgi = $self->wrap_nonperl_cgi($file, $action_name);
21a20b7e 118 }
119
c264816e 120 $app->log->info("Registering root/cgi-bin/$cgi_path as a $type CGI.")
ee75330f 121 if $app->debug;
21a20b7e 122
c264816e 123 my $code = sub {
21a20b7e 124 my ($controller, $context) = @_;
125 $controller->cgi_to_response($context, $cgi)
126 };
127
128 my $action = $self->create_action(
129 name => $action_name,
130 code => $code,
131 reverse => $reverse,
132 namespace => $namespace,
133 class => $class,
134 attributes => $attrs
135 );
136
ee75330f 137 $app->dispatcher->register($app, $action);
21a20b7e 138 }
139
ee75330f 140 $self->next::method($app, @_);
2340af9d 141
f410f043 142# Tell Static::Simple to ignore cgi_dir
143 if ($cgi_bin =~ /^@{[ $app->path_to('root') ]}/) {
144 my $rel = File::Spec->abs2rel($cgi_bin, $app->path_to('root'));
145
146 if (!any { $_ eq $rel }
147 @{ $app->config->{static}{ignore_dirs}||[] }) {
148 push @{ $app->config->{static}{ignore_dirs} }, $rel;
149 }
2340af9d 150 }
21a20b7e 151}
152
153=head1 METHODS
154
f410f043 155=head2 cgi_action
156
b9548267 157C<< $self->cgi_action($cgi) >>
21a20b7e 158
159Takes a path to a CGI from C<root/cgi-bin> such as C<foo/bar.cgi> and returns
c264816e 160the action name it is registered as. See L</DESCRIPTION> for a discussion on how
161CGI actions are named.
21a20b7e 162
fbaba9dd 163A path such as C<root/cgi-bin/hlagh/bar.cgi> will get the private path
164C<foo/CGI_hlagh__bar_cgi>, for controller Foo, with the C</>s converted to C<__>
165and prepended with C<CGI_>, as well as all non-word characters converted to
166C<_>s. This is because L<Catalyst> action names can't have non-word characters
167in them.
168
169This means that C<foo/bar.cgi> and C<foo__bar.cgi> for example will both map to
170the action C<CGI_foo__bar_cgi> so B<DON'T DO THAT>.
171
21a20b7e 172=cut
173
174sub cgi_action {
175 my ($self, $cgi) = @_;
176
fbaba9dd 177 my $action_name = 'CGI_' . join '__' => split '/' => $cgi;
21a20b7e 178 $action_name =~ s/\W/_/g;
179
180 $action_name
181}
182
f410f043 183=head2 cgi_path
184
b9548267 185C<< $self->cgi_path($cgi) >>
9cc2dd4c 186
187Takes a path to a CGI from C<root/cgi-bin> such as C<foo/bar.cgi> and returns
188the public path it should be registered under.
189
184837e7 190The default is to prefix with C<$cgi_root_path/>, using the C<cgi_root_path>
191config setting, above.
9cc2dd4c 192
193=cut
194
195sub cgi_path {
196 my ($self, $cgi) = @_;
9cd47364 197
198 my $root = $self->cgi_root_path;
199 $root =~ s{/*$}{};
200 return "$root/$cgi";
9cc2dd4c 201}
202
f410f043 203=head2 is_perl_cgi
204
b9548267 205C<< $self->is_perl_cgi($path) >>
c264816e 206
207Tries to figure out whether the CGI is Perl or not.
208
209If it's Perl, it will be inlined into a sub instead of being forked off, see
f410f043 210L</wrap_perl_cgi>.
c264816e 211
212=cut
213
214sub is_perl_cgi {
215 my ($self, $cgi) = @_;
216
217 my $shebang = IO::File->new($cgi)->getline;
218
2369e5a4 219 return 0 if $shebang !~ /perl/ && $cgi !~ /\.pl\z/;
c264816e 220
221 my $taint_check = $shebang =~ /-T/ ? '-T' : '';
222
223 open NULL, '>', File::Spec->devnull;
224 my $pid = open3(gensym, '&>NULL', '&>NULL', "$^X $taint_check -c $cgi");
225 close NULL;
226 waitpid $pid, 0;
227
228 $? >> 8 == 0
229}
230
f410f043 231=head2 wrap_perl_cgi
232
b9548267 233C<< $self->wrap_perl_cgi($path, $action_name) >>
c264816e 234
235Takes the path to a Perl CGI and returns a coderef suitable for passing to
236cgi_to_response (from L<Catalyst::Controller::WrapCGI>.)
237
fbaba9dd 238C<$action_name> is the generated name for the action representing the CGI file
239from C<cgi_action>.
c264816e 240
241This is similar to how L<ModPerl::Registry> works, but will only work for
242well-written CGIs. Otherwise, you may have to override this method to do
243something more involved (see L<ModPerl::PerlRun>.)
244
f410f043 245Scripts with C<__DATA__> sections now work too, as well as scripts that call
246C<exit()>.
fbaba9dd 247
c264816e 248=cut
249
250sub wrap_perl_cgi {
12d29ebf 251 my ($self, $cgi, $action_name) = @_;
252
1ce18a56 253 my $code = slurp $cgi;
254
fbaba9dd 255 $code =~ s/^__DATA__(?:\r?\n|\r\n?)(.*)//ms;
1ce18a56 256 my $data = $1;
257
258 my $coderef = do {
12d29ebf 259 no warnings;
bdb35995 260 # catch exit() and turn it into (effectively) a return
261 # we *must* eval STRING because the code needs to be compiled with the
262 # overridden CORE::GLOBAL::exit in view
263 #
264 # set $0 to the name of the cgi file in case it's used there
12d29ebf 265 eval '
bdb35995 266 my $cgi_exited = "EXIT\n";
267 BEGIN { *CORE::GLOBAL::exit = sub (;$) {
268 die [ $cgi_exited, $_[0] || 0 ];
269 } }
12d29ebf 270 package Catalyst::Controller::CGIBin::_CGIs_::'.$action_name.';
1ce18a56 271 sub {'
272 . 'local *DATA;'
273 . q{open DATA, '<', \$data;}
bdb35995 274 . qq{local \$0 = "\Q$cgi\E";}
275 . q/my $rv = eval {/
1ce18a56 276 . $code
bdb35995 277 . q/};/
278 . q{
279 return $rv unless $@;
280 die $@ if $@ and not (
281 ref($@) eq 'ARRAY' and
282 $@->[0] eq $cgi_exited
283 );
284 die "exited nonzero: $@->[1]" if $@->[1] != 0;
285 return $rv;
286 }
1ce18a56 287 . '}';
288 };
289
290 croak __PACKAGE__ . ": Could not compile $cgi to coderef: $@" if $@;
291
292 $coderef
c264816e 293}
294
f410f043 295=head2 wrap_nonperl_cgi
296
b9548267 297C<< $self->wrap_nonperl_cgi($path, $action_name) >>
c264816e 298
299Takes the path to a non-Perl CGI and returns a coderef for executing it.
300
12d29ebf 301C<$action_name> is the generated name for the action representing the CGI file.
302
c264816e 303By default returns:
304
305 sub { system $path }
306
307=cut
308
309sub wrap_nonperl_cgi {
12d29ebf 310 my ($self, $cgi, $action_name) = @_;
c264816e 311
312 sub { system $cgi }
313}
314
f410f043 315__PACKAGE__->meta->make_immutable;
316
c264816e 317=head1 SEE ALSO
318
319L<Catalyst::Controller::WrapCGI>, L<CatalystX::GlobalContext>,
320L<Catalyst::Controller>, L<CGI>, L<Catalyst>
321
0d83c5de 322=head1 AUTHORS
21a20b7e 323
324Rafael Kitover, C<< <rkitover at cpan.org> >>
325
0d83c5de 326Hans Dieter Pearcey, C<< <hdp at cpan.org> >>
327
21a20b7e 328=head1 BUGS
329
330Please report any bugs or feature requests to C<bug-catalyst-controller-wrapcgi at
331rt.cpan.org>, or through the web interface at
332L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Catalyst-Controller-WrapCGI>.
333I will be notified, and then you'll automatically be notified of progress on
334your bug as I make changes.
335
336=head1 SUPPORT
337
338More information at:
339
340=over 4
341
342=item * RT: CPAN's request tracker
343
344L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Catalyst-Controller-WrapCGI>
345
346=item * AnnoCPAN: Annotated CPAN documentation
347
348L<http://annocpan.org/dist/Catalyst-Controller-WrapCGI>
349
350=item * CPAN Ratings
351
352L<http://cpanratings.perl.org/d/Catalyst-Controller-WrapCGI>
353
354=item * Search CPAN
355
356L<http://search.cpan.org/dist/Catalyst-Controller-WrapCGI>
357
358=back
359
360=head1 COPYRIGHT & LICENSE
361
362Copyright (c) 2008 Rafael Kitover
363
364This program is free software; you can redistribute it and/or modify it
365under the same terms as Perl itself.
366
367=cut
368
3691; # End of Catalyst::Controller::CGIBin
370
371# vim: expandtab shiftwidth=4 ts=4 tw=80: