add kill_env
[catagits/Catalyst-Controller-WrapCGI.git] / lib / Catalyst / Controller / WrapCGI.pm
1 package Catalyst::Controller::WrapCGI;
2
3 use strict;
4 use warnings;
5 use parent 'Catalyst::Controller';
6
7 use HTTP::Request::AsCGI;
8 use HTTP::Request;
9 use URI;
10 use Catalyst::Exception ();
11
12 =head1 NAME
13
14 Catalyst::Controller::WrapCGI - Run CGIs in Catalyst
15
16 =head1 VERSION
17
18 Version 0.0026
19
20 =cut
21
22 our $VERSION = '0.0026';
23
24 =head1 SYNOPSIS
25
26     package MyApp::Controller::Foo;
27
28     use parent qw/Catalyst::Controller::WrapCGI/;
29     use CGI ();
30
31     sub hello : Path('cgi-bin/hello.cgi') {
32         my ($self, $c) = @_;
33
34         $self->cgi_to_response($c, sub {
35             my $q = CGI->new;
36             print $q->header, $q->start_html('Hello'),
37                 $q->h1('Catalyst Rocks!'),
38                 $q->end_html;
39         });
40     }
41
42 In your .conf, configure which environment variables to pass:
43
44     <Controller::Foo>
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             kill_env MOD_PERL
51         </CGI>
52     </Controller::Foo>
53
54 =head1 DESCRIPTION
55
56 Allows you to run Perl code in a CGI environment derived from your L<Catalyst>
57 context.
58
59 B<*WARNING*>: do not export L<CGI> functions into a Controller, it will break
60 with L<Catalyst> 5.8 onward.
61
62 If you just want to run CGIs from files, see L<Catalyst::Controller::CGIBin>.
63
64 =head1 CONFIGURATION
65
66 C<< $your_controller->{CGI}{pass_env} >> should be an array of environment variables
67 or regular expressions to pass through to your CGIs. Entries surrounded by C</>
68 characters are considered regular expressions.
69
70 C<< $your_controller->{CGI}{kill_env} >> should be an array of environment
71 variables or regular expressions to remove from the environment before passing
72 it to your CGIs.  Entries surrounded by C</> characters are considered regular
73 expressions.
74
75 Default is to pass the whole of C<%ENV>, except for C<MOD_PERL> (that is, the
76 default C<kill_env> is C<[ 'MOD_PERL' ]>.
77
78 C<< $your_controller->{CGI}{username_field} >> should be the field for your user's name, which will be
79 read from C<< $c->user->obj >>. Defaults to 'username'.
80
81 See L</SYNOPSIS> for an example.
82
83 =cut
84
85 # Hack-around because Catalyst::Engine::HTTP goes and changes
86 # them to be the remote socket, and FCGI.pm does even dumber things.
87
88 open my $REAL_STDIN, "<&=".fileno(*STDIN);
89 open my $REAL_STDOUT, ">>&=".fileno(*STDOUT);
90
91 =head1 METHODS
92
93 =head2 $self->cgi_to_response($c, $coderef)
94
95 Does the magic of running $coderef in a CGI environment, and populating the
96 appropriate parts of your Catalyst context with the results.
97
98 Calls wrap_cgi (below.)
99
100 =cut
101
102 sub cgi_to_response {
103   my ($self, $c, $script) = @_;
104   my $res = $self->wrap_cgi($c, $script);
105
106   # if the CGI doesn't set the response code but sets location they were
107   # probably trying to redirect so set 302 for them
108
109   my $location = $res->headers->header('Location');
110
111   if (defined $location && length $location && $res->code == 200) {
112     $c->res->status(302);
113   } else { 
114     $c->res->status($res->code);
115   }
116   $c->res->body($res->content);
117   $c->res->headers($res->headers);
118 }
119
120 =head2 $self->wrap_cgi($c, $coderef)
121
122 Runs $coderef in a CGI environment using L<HTTP::Request::AsCGI>, returns an
123 L<HTTP::Response>.
124
125 The CGI environment is set up based on $c.
126
127 The environment variables to pass on are taken from the configuration for your
128 Controller, see L</SYNOPSIS> for an example. If you don't supply a list of
129 environment variables to pass, the whole of %ENV is used.
130
131 Used by cgi_to_response (above), which is probably what you want to use as well.
132
133 =cut
134
135 sub _filtered_env {
136   my ($self, $env) = @_;
137   my @ok;
138
139   my $pass_env = $self->{CGI}{pass_env};
140   $pass_env = []            if not defined $pass_env;
141   $pass_env = [ $pass_env ] unless ref $pass_env;
142
143   my $kill_env = $self->{CGI}{kill_env};
144   $kill_env = [ 'MOD_PERL' ] unless defined $kill_env;
145   $kill_env = [ $kill_env ]  unless ref $kill_env;
146
147   if (@$pass_env) {
148     for (@$pass_env) {
149       if (m!^/(.*)/\z!) {
150         my $re = qr/$1/;
151         push @ok, grep /$re/, keys %$env;
152       } else {
153         push @ok, $_;
154       }
155     }
156   } else {
157     @ok = keys %$env;
158   }
159
160   for my $k (@$kill_env) {
161     if ($k =~ m!^/(.*)/\z!) {
162       my $re = qr/$1/;
163       @ok = grep { ! /$re/ } @ok;
164     } else {
165       @ok = grep { $_ ne $k } @ok;
166     }
167   }
168   return { map {; $_ => $env->{$_} } @ok };
169 }
170
171 sub wrap_cgi {
172   my ($self, $c, $call) = @_;
173   my $req = HTTP::Request->new(
174     map { $c->req->$_ } qw/method uri headers/
175   );
176   my $body = $c->req->body;
177   my $body_content = '';
178
179   $req->content_type($c->req->content_type); # set this now so we can override
180
181   if ($body) { # Slurp from body filehandle
182     local $/; $body_content = <$body>;
183   } else {
184     my $body_params = $c->req->body_parameters;
185     if (%$body_params) {
186       my $encoder = URI->new;
187       $encoder->query_form(%$body_params);
188       $body_content = $encoder->query;
189       $req->content_type('application/x-www-form-urlencoded');
190     }
191   }
192
193   my $filtered_env = $self->_filtered_env(\%ENV);
194
195   $req->content($body_content);
196   $req->content_length(length($body_content));
197
198   my $username_field = $self->{CGI}{username_field} || 'username';
199
200   my $username = (($c->can('user_exists') && $c->user_exists)
201                ? eval { $c->user->obj->$username_field }
202                 : '');
203   my $env = HTTP::Request::AsCGI->new(
204               $req,
205               ($username ? (REMOTE_USER => $username) : ()),
206               %$filtered_env,
207             );
208
209   {
210     local *STDIN = $REAL_STDIN;   # restore the real ones so the filenos
211     local *STDOUT = $REAL_STDOUT; # are 0 and 1 for the env setup
212
213     my $old = select($REAL_STDOUT); # in case somebody just calls 'print'
214
215     my $saved_error;
216
217     $env->setup;
218     eval { $call->() };
219     $saved_error = $@;
220     $env->restore;
221
222     select($old);
223
224     Catalyst::Exception->throw(
225         message => "CGI invocation failed: $saved_error"
226     ) if $saved_error;
227   }
228
229   return $env->response;
230 }
231
232 =head1 ACKNOWLEDGEMENTS
233
234 Original development sponsored by L<http://www.altinity.com/>
235
236 =head1 SEE ALSO
237
238 L<Catalyst::Controller::CGIBin>, L<CatalystX::GlobalContext>,
239 L<Catalyst::Controller>, L<CGI>, L<Catalyst>
240
241 =head1 AUTHOR
242
243 Matt S. Trout, C<< <mst at shadowcat.co.uk> >>
244
245 =head1 BUGS
246
247 Please report any bugs or feature requests to C<bug-catalyst-controller-wrapcgi
248 at rt.cpan.org>, or through the web interface at
249 L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Catalyst-Controller-WrapCGI>.
250 I will be notified, and then you'll automatically be notified of progress on
251 your bug as I make changes.
252
253 =head1 SUPPORT
254
255 More information at:
256
257 =over 4
258
259 =item * RT: CPAN's request tracker
260
261 L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Catalyst-Controller-WrapCGI>
262
263 =item * AnnoCPAN: Annotated CPAN documentation
264
265 L<http://annocpan.org/dist/Catalyst-Controller-WrapCGI>
266
267 =item * CPAN Ratings
268
269 L<http://cpanratings.perl.org/d/Catalyst-Controller-WrapCGI>
270
271 =item * Search CPAN
272
273 L<http://search.cpan.org/dist/Catalyst-Controller-WrapCGI>
274
275 =back
276
277 =head1 COPYRIGHT & LICENSE
278
279 Copyright (c) 2008 Matt S. Trout
280
281 This program is free software; you can redistribute it and/or modify it
282 under the same terms as Perl itself.
283
284 =cut
285
286 1; # End of Catalyst::Controller::WrapCGI
287
288 # vim: expandtab shiftwidth=2 ts=2 tw=80: