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