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