Add in the REDIRECT_URL handling from trunk and the values for the tests
[catagits/Catalyst-Runtime.git] / lib / Catalyst / Engine.pm
CommitLineData
fc7ec1d9 1package Catalyst::Engine;
2
7fa2c9c1 3use Moose;
4with 'MooseX::Emulate::Class::Accessor::Fast';
5
fa32ac82 6use CGI::Simple::Cookie;
f63c03e4 7use Data::Dump qw/dump/;
d04b2ffd 8use Errno 'EWOULDBLOCK';
fc7ec1d9 9use HTML::Entities;
fbcc39ad 10use HTTP::Body;
fc7ec1d9 11use HTTP::Headers;
e0616220 12use URI::QueryParam;
44d28c7d 13use Moose::Util::TypeConstraints;
a1791811 14use Plack::Loader;
c2f4a965 15use Plack::Middleware::Conditional;
16use Plack::Middleware::ReverseProxy;
fbcc39ad 17
d495753a 18use namespace::clean -except => 'meta';
19
44d28c7d 20has env => (is => 'ro', writer => '_set_env', clearer => '_clear_env');
a50e5b46 21
fbcc39ad 22# input position and length
7fa2c9c1 23has read_length => (is => 'rw');
24has read_position => (is => 'rw');
fbcc39ad 25
02570318 26has _prepared_write => (is => 'rw');
27
44d28c7d 28has _response_cb => (
eebe046f 29 is => 'ro',
30 isa => 'CodeRef',
31 writer => '_set_response_cb',
32 clearer => '_clear_response_cb',
44d28c7d 33);
34
35has _writer => (
eebe046f 36 is => 'ro',
37 isa => duck_type([qw(write close)]),
38 writer => '_set_writer',
39 clearer => '_clear_writer',
44d28c7d 40);
41
4bd82c41 42# Amount of data to read from input on each pass
4bb8bd62 43our $CHUNKSIZE = 64 * 1024;
4bd82c41 44
fc7ec1d9 45=head1 NAME
46
47Catalyst::Engine - The Catalyst Engine
48
49=head1 SYNOPSIS
50
51See L<Catalyst>.
52
53=head1 DESCRIPTION
54
23f9d934 55=head1 METHODS
fc7ec1d9 56
cd3bb248 57
b5ecfcf0 58=head2 $self->finalize_body($c)
06e1b616 59
fbcc39ad 60Finalize body. Prints the response output.
06e1b616 61
62=cut
63
fbcc39ad 64sub finalize_body {
65 my ( $self, $c ) = @_;
7257e9db 66 my $body = $c->response->body;
f9b6d612 67 no warnings 'uninitialized';
7e95ba12 68 if ( blessed($body) && $body->can('read') or ref($body) eq 'GLOB' ) {
be1c9503 69 my $got;
70 do {
1235b30f 71 $got = read $body, my ($buffer), $CHUNKSIZE;
3a64ecc9 72 $got = 0 unless $self->write( $c, $buffer );
be1c9503 73 } while $got > 0;
74
7257e9db 75 close $body;
f4a57de4 76 }
77 else {
7257e9db 78 $self->write( $c, $body );
f4a57de4 79 }
ca3023fc 80
81 $self->_writer->close;
eebe046f 82 $self->_clear_writer;
030674d0 83 $self->_clear_env;
84
ca3023fc 85 return;
fbcc39ad 86}
6dc87a0f 87
b5ecfcf0 88=head2 $self->finalize_cookies($c)
6dc87a0f 89
fa32ac82 90Create CGI::Simple::Cookie objects from $c->res->cookies, and set them as
91response headers.
4ab87e27 92
6dc87a0f 93=cut
94
95sub finalize_cookies {
fbcc39ad 96 my ( $self, $c ) = @_;
6dc87a0f 97
fbcc39ad 98 my @cookies;
7fa2c9c1 99 my $response = $c->response;
c82ed742 100
91772de9 101 foreach my $name (keys %{ $response->cookies }) {
102
103 my $val = $response->cookies->{$name};
fbcc39ad 104
2832cb5d 105 my $cookie = (
7e95ba12 106 blessed($val)
2832cb5d 107 ? $val
108 : CGI::Simple::Cookie->new(
109 -name => $name,
110 -value => $val->{value},
111 -expires => $val->{expires},
112 -domain => $val->{domain},
113 -path => $val->{path},
b21bc468 114 -secure => $val->{secure} || 0,
115 -httponly => $val->{httponly} || 0,
2832cb5d 116 )
6dc87a0f 117 );
118
fbcc39ad 119 push @cookies, $cookie->as_string;
6dc87a0f 120 }
6dc87a0f 121
b39840da 122 for my $cookie (@cookies) {
7fa2c9c1 123 $response->headers->push_header( 'Set-Cookie' => $cookie );
fbcc39ad 124 }
125}
969647fd 126
b5ecfcf0 127=head2 $self->finalize_error($c)
969647fd 128
6e5b548e 129Output an appropriate error message. Called if there's an error in $c
4ab87e27 130after the dispatch has finished. Will output debug messages if Catalyst
131is in debug mode, or a `please come back later` message otherwise.
132
969647fd 133=cut
134
c96cdcef 135sub _dump_error_page_element {
136 my ($self, $i, $element) = @_;
137 my ($name, $val) = @{ $element };
138
139 # This is fugly, but the metaclass is _HUGE_ and demands waaay too much
140 # scrolling. Suggestions for more pleasant ways to do this welcome.
141 local $val->{'__MOP__'} = "Stringified: "
1565e158 142 . $val->{'__MOP__'} if ref $val eq 'HASH' && exists $val->{'__MOP__'};
c96cdcef 143
144 my $text = encode_entities( dump( $val ));
145 sprintf <<"EOF", $name, $text;
146<h2><a href="#" onclick="toggleDump('dump_$i'); return false">%s</a></h2>
147<div id="dump_$i">
148 <pre wrap="">%s</pre>
149</div>
150EOF
151}
152
969647fd 153sub finalize_error {
fbcc39ad 154 my ( $self, $c ) = @_;
969647fd 155
7299a7b4 156 $c->res->content_type('text/html; charset=utf-8');
df960201 157 my $name = ref($c)->config->{name} || join(' ', split('::', ref $c));
969647fd 158
159 my ( $title, $error, $infos );
160 if ( $c->debug ) {
62d9b030 161
162 # For pretty dumps
b5ecfcf0 163 $error = join '', map {
164 '<p><code class="error">'
165 . encode_entities($_)
166 . '</code></p>'
167 } @{ $c->error };
969647fd 168 $error ||= 'No output';
2666dd3b 169 $error = qq{<pre wrap="">$error</pre>};
969647fd 170 $title = $name = "$name on Catalyst $Catalyst::VERSION";
d82cc9ae 171 $name = "<h1>$name</h1>";
fbcc39ad 172
173 # Don't show context in the dump
02570318 174 $c->req->_clear_context;
175 $c->res->_clear_context;
fbcc39ad 176
177 # Don't show body parser in the dump
0f56bbcf 178 $c->req->_clear_body;
fbcc39ad 179
c6ef5e69 180 my @infos;
181 my $i = 0;
c6ef5e69 182 for my $dump ( $c->dump_these ) {
c96cdcef 183 push @infos, $self->_dump_error_page_element($i, $dump);
c6ef5e69 184 $i++;
185 }
186 $infos = join "\n", @infos;
969647fd 187 }
188 else {
189 $title = $name;
190 $error = '';
191 $infos = <<"";
192<pre>
193(en) Please come back later
0c2b4ac0 194(fr) SVP veuillez revenir plus tard
969647fd 195(de) Bitte versuchen sie es spaeter nocheinmal
d82cc9ae 196(at) Konnten's bitt'schoen spaeter nochmal reinschauen
969647fd 197(no) Vennligst prov igjen senere
d82cc9ae 198(dk) Venligst prov igen senere
199(pl) Prosze sprobowac pozniej
2f381252 200(pt) Por favor volte mais tarde
b31c0f2e 201(ru) Попробуйте еще раз позже
202(ua) Спробуйте ще раз пізніше
969647fd 203</pre>
204
205 $name = '';
206 }
e060fe05 207 $c->res->body( <<"" );
7299a7b4 208<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
209 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
210<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
969647fd 211<head>
7299a7b4 212 <meta http-equiv="Content-Language" content="en" />
213 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
969647fd 214 <title>$title</title>
7299a7b4 215 <script type="text/javascript">
c6ef5e69 216 <!--
217 function toggleDump (dumpElement) {
7299a7b4 218 var e = document.getElementById( dumpElement );
219 if (e.style.display == "none") {
220 e.style.display = "";
c6ef5e69 221 }
222 else {
7299a7b4 223 e.style.display = "none";
c6ef5e69 224 }
225 }
226 -->
227 </script>
969647fd 228 <style type="text/css">
229 body {
230 font-family: "Bitstream Vera Sans", "Trebuchet MS", Verdana,
231 Tahoma, Arial, helvetica, sans-serif;
34d28dfd 232 color: #333;
969647fd 233 background-color: #eee;
234 margin: 0px;
235 padding: 0px;
236 }
c6ef5e69 237 :link, :link:hover, :visited, :visited:hover {
34d28dfd 238 color: #000;
c6ef5e69 239 }
969647fd 240 div.box {
9619f23c 241 position: relative;
969647fd 242 background-color: #ccc;
243 border: 1px solid #aaa;
244 padding: 4px;
245 margin: 10px;
969647fd 246 }
247 div.error {
34d28dfd 248 background-color: #cce;
969647fd 249 border: 1px solid #755;
250 padding: 8px;
251 margin: 4px;
252 margin-bottom: 10px;
969647fd 253 }
254 div.infos {
34d28dfd 255 background-color: #eee;
969647fd 256 border: 1px solid #575;
257 padding: 8px;
258 margin: 4px;
259 margin-bottom: 10px;
969647fd 260 }
261 div.name {
34d28dfd 262 background-color: #cce;
969647fd 263 border: 1px solid #557;
264 padding: 8px;
265 margin: 4px;
969647fd 266 }
7f8e0078 267 code.error {
268 display: block;
269 margin: 1em 0;
270 overflow: auto;
7f8e0078 271 }
9619f23c 272 div.name h1, div.error p {
273 margin: 0;
274 }
275 h2 {
276 margin-top: 0;
277 margin-bottom: 10px;
278 font-size: medium;
279 font-weight: bold;
280 text-decoration: underline;
281 }
282 h1 {
283 font-size: medium;
284 font-weight: normal;
285 }
2666dd3b 286 /* from http://users.tkk.fi/~tkarvine/linux/doc/pre-wrap/pre-wrap-css3-mozilla-opera-ie.html */
287 /* Browser specific (not valid) styles to make preformatted text wrap */
b0ad47c1 288 pre {
2666dd3b 289 white-space: pre-wrap; /* css-3 */
290 white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
291 white-space: -pre-wrap; /* Opera 4-6 */
292 white-space: -o-pre-wrap; /* Opera 7 */
293 word-wrap: break-word; /* Internet Explorer 5.5+ */
294 }
969647fd 295 </style>
296</head>
297<body>
298 <div class="box">
299 <div class="error">$error</div>
300 <div class="infos">$infos</div>
301 <div class="name">$name</div>
302 </div>
303</body>
304</html>
305
d82cc9ae 306
4b66aa19 307 # Trick IE. Old versions of IE would display their own error page instead
308 # of ours if we'd give it less than 512 bytes.
d82cc9ae 309 $c->res->{body} .= ( ' ' x 512 );
310
311 # Return 500
33117422 312 $c->res->status(500);
969647fd 313}
314
b5ecfcf0 315=head2 $self->finalize_headers($c)
fc7ec1d9 316
4ab87e27 317Abstract method, allows engines to write headers to response
318
fc7ec1d9 319=cut
320
44d28c7d 321sub finalize_headers {
322 my ($self, $ctx) = @_;
323
324 my @headers;
325 $ctx->response->headers->scan(sub { push @headers, @_ });
326
327 $self->_set_writer($self->_response_cb->([ $ctx->response->status, \@headers ]));
eebe046f 328 $self->_clear_response_cb;
44d28c7d 329
330 return;
331}
fc7ec1d9 332
b5ecfcf0 333=head2 $self->finalize_read($c)
fc7ec1d9 334
335=cut
336
878b821c 337sub finalize_read { }
fc7ec1d9 338
b5ecfcf0 339=head2 $self->finalize_uploads($c)
fc7ec1d9 340
4ab87e27 341Clean up after uploads, deleting temp files.
342
fc7ec1d9 343=cut
344
fbcc39ad 345sub finalize_uploads {
346 my ( $self, $c ) = @_;
99fe1710 347
671123ba 348 # N.B. This code is theoretically entirely unneeded due to ->cleanup(1)
349 # on the HTTP::Body object.
7fa2c9c1 350 my $request = $c->request;
91772de9 351 foreach my $key (keys %{ $request->uploads }) {
352 my $upload = $request->uploads->{$key};
7fa2c9c1 353 unlink grep { -e $_ } map { $_->tempname }
354 (ref $upload eq 'ARRAY' ? @{$upload} : ($upload));
c85ff642 355 }
7fa2c9c1 356
fc7ec1d9 357}
358
b5ecfcf0 359=head2 $self->prepare_body($c)
fc7ec1d9 360
4ab87e27 361sets up the L<Catalyst::Request> object body using L<HTTP::Body>
362
fc7ec1d9 363=cut
364
fbcc39ad 365sub prepare_body {
366 my ( $self, $c ) = @_;
99fe1710 367
df960201 368 my $appclass = ref($c) || $c;
878b821c 369 if ( my $length = $self->read_length ) {
7fa2c9c1 370 my $request = $c->request;
0f56bbcf 371 unless ( $request->_body ) {
7fa2c9c1 372 my $type = $request->header('Content-Type');
0f56bbcf 373 $request->_body(HTTP::Body->new( $type, $length ));
671123ba 374 $request->_body->cleanup(1); # Make extra sure!
df960201 375 $request->_body->tmpdir( $appclass->config->{uploadtmp} )
376 if exists $appclass->config->{uploadtmp};
847e3257 377 }
b0ad47c1 378
ea72fece 379 # Check for definedness as you could read '0'
380 while ( defined ( my $buffer = $self->read($c) ) ) {
4f5ebacd 381 $c->prepare_body_chunk($buffer);
fbcc39ad 382 }
fdb3773e 383
384 # paranoia against wrong Content-Length header
847e3257 385 my $remaining = $length - $self->read_position;
34d28dfd 386 if ( $remaining > 0 ) {
fdb3773e 387 $self->finalize_read($c);
34d28dfd 388 Catalyst::Exception->throw(
847e3257 389 "Wrong Content-Length value: $length" );
fdb3773e 390 }
fc7ec1d9 391 }
847e3257 392 else {
393 # Defined but will cause all body code to be skipped
0f56bbcf 394 $c->request->_body(0);
847e3257 395 }
fc7ec1d9 396}
397
b5ecfcf0 398=head2 $self->prepare_body_chunk($c)
4bd82c41 399
4ab87e27 400Add a chunk to the request body.
401
4bd82c41 402=cut
403
404sub prepare_body_chunk {
405 my ( $self, $c, $chunk ) = @_;
4f5ebacd 406
0f56bbcf 407 $c->request->_body->add($chunk);
4bd82c41 408}
409
b5ecfcf0 410=head2 $self->prepare_body_parameters($c)
06e1b616 411
b0ad47c1 412Sets up parameters from body.
4ab87e27 413
06e1b616 414=cut
415
fbcc39ad 416sub prepare_body_parameters {
417 my ( $self, $c ) = @_;
b0ad47c1 418
0f56bbcf 419 return unless $c->request->_body;
b0ad47c1 420
0f56bbcf 421 $c->request->body_parameters( $c->request->_body->param );
fbcc39ad 422}
0556eb49 423
b5ecfcf0 424=head2 $self->prepare_connection($c)
0556eb49 425
4ab87e27 426Abstract method implemented in engines.
427
0556eb49 428=cut
429
44d28c7d 430sub prepare_connection {
431 my ($self, $ctx) = @_;
432
433 my $env = $self->env;
434 my $request = $ctx->request;
435
436 $request->address( $env->{REMOTE_ADDR} );
437 $request->hostname( $env->{REMOTE_HOST} )
438 if exists $env->{REMOTE_HOST};
439 $request->protocol( $env->{SERVER_PROTOCOL} );
440 $request->remote_user( $env->{REMOTE_USER} );
441 $request->method( $env->{REQUEST_METHOD} );
c9de76f0 442 $request->secure( $env->{'psgi.url_scheme'} eq 'https' ? 1 : 0 );
44d28c7d 443
444 return;
445}
0556eb49 446
b5ecfcf0 447=head2 $self->prepare_cookies($c)
fc7ec1d9 448
fa32ac82 449Parse cookies from header. Sets a L<CGI::Simple::Cookie> object.
4ab87e27 450
fc7ec1d9 451=cut
452
6dc87a0f 453sub prepare_cookies {
fbcc39ad 454 my ( $self, $c ) = @_;
6dc87a0f 455
456 if ( my $header = $c->request->header('Cookie') ) {
fa32ac82 457 $c->req->cookies( { CGI::Simple::Cookie->parse($header) } );
6dc87a0f 458 }
459}
fc7ec1d9 460
b5ecfcf0 461=head2 $self->prepare_headers($c)
fc7ec1d9 462
463=cut
464
44d28c7d 465sub prepare_headers {
466 my ($self, $ctx) = @_;
467
468 my $env = $self->env;
469 my $headers = $ctx->request->headers;
470
471 for my $header (keys %{ $env }) {
472 next unless $header =~ /^(HTTP|CONTENT|COOKIE)/i;
473 (my $field = $header) =~ s/^HTTPS?_//;
474 $field =~ tr/_/-/;
475 $headers->header($field => $env->{$header});
476 }
477}
fc7ec1d9 478
b5ecfcf0 479=head2 $self->prepare_parameters($c)
fc7ec1d9 480
4ab87e27 481sets up parameters from query and post parameters.
482
fc7ec1d9 483=cut
484
fbcc39ad 485sub prepare_parameters {
486 my ( $self, $c ) = @_;
fc7ec1d9 487
7fa2c9c1 488 my $request = $c->request;
489 my $parameters = $request->parameters;
490 my $body_parameters = $request->body_parameters;
491 my $query_parameters = $request->query_parameters;
fbcc39ad 492 # We copy, no references
91772de9 493 foreach my $name (keys %$query_parameters) {
494 my $param = $query_parameters->{$name};
7fa2c9c1 495 $parameters->{$name} = ref $param eq 'ARRAY' ? [ @$param ] : $param;
fbcc39ad 496 }
fc7ec1d9 497
fbcc39ad 498 # Merge query and body parameters
91772de9 499 foreach my $name (keys %$body_parameters) {
500 my $param = $body_parameters->{$name};
7fa2c9c1 501 my @values = ref $param eq 'ARRAY' ? @$param : ($param);
502 if ( my $existing = $parameters->{$name} ) {
503 unshift(@values, (ref $existing eq 'ARRAY' ? @$existing : $existing));
fbcc39ad 504 }
7fa2c9c1 505 $parameters->{$name} = @values > 1 ? \@values : $values[0];
fbcc39ad 506 }
507}
508
b5ecfcf0 509=head2 $self->prepare_path($c)
fc7ec1d9 510
4ab87e27 511abstract method, implemented by engines.
512
fc7ec1d9 513=cut
514
44d28c7d 515sub prepare_path {
516 my ($self, $ctx) = @_;
517
518 my $env = $self->env;
519
520 my $scheme = $ctx->request->secure ? 'https' : 'http';
521 my $host = $env->{HTTP_HOST} || $env->{SERVER_NAME};
522 my $port = $env->{SERVER_PORT} || 80;
523 my $base_path = $env->{SCRIPT_NAME} || "/";
524
525 # set the request URI
661de072 526 my $path;
527 if (!$ctx->config->{use_request_uri_for_path}) {
4904ee27 528 my $path_info = $env->{PATH_INFO};
529 if ( exists $env->{REDIRECT_URL} ) {
530 $base_path = $env->{REDIRECT_URL};
531 $base_path =~ s/\Q$path_info\E$//;
532 }
533 $path = $base_path . $path_info;
661de072 534 $path =~ s{^/+}{};
535 $path =~ s/([^$URI::uric])/$URI::Escape::escapes{$1}/go;
536 $path =~ s/\?/%3F/g; # STUPID STUPID SPECIAL CASE
537 }
538 else {
539 my $req_uri = $env->{REQUEST_URI};
540 $req_uri =~ s/\?.*$//;
541 $path = $req_uri;
542 $path =~ s{^/+}{};
543 }
44d28c7d 544
545 # Using URI directly is way too slow, so we construct the URLs manually
546 my $uri_class = "URI::$scheme";
547
548 # HTTP_HOST will include the port even if it's 80/443
549 $host =~ s/:(?:80|443)$//;
550
551 if ($port !~ /^(?:80|443)$/ && $host !~ /:/) {
552 $host .= ":$port";
553 }
554
44d28c7d 555 my $query = $env->{QUERY_STRING} ? '?' . $env->{QUERY_STRING} : '';
556 my $uri = $scheme . '://' . $host . '/' . $path . $query;
557
4ee03d72 558 $ctx->request->uri( (bless \$uri, $uri_class)->canonical );
44d28c7d 559
560 # set the base URI
561 # base must end in a slash
562 $base_path .= '/' unless $base_path =~ m{/$};
563
564 my $base_uri = $scheme . '://' . $host . $base_path;
565
566 $ctx->request->base( bless \$base_uri, $uri_class );
567
568 return;
569}
fc7ec1d9 570
b5ecfcf0 571=head2 $self->prepare_request($c)
fc7ec1d9 572
b5ecfcf0 573=head2 $self->prepare_query_parameters($c)
fc7ec1d9 574
4ab87e27 575process the query string and extract query parameters.
576
fc7ec1d9 577=cut
578
e0616220 579sub prepare_query_parameters {
44d28c7d 580 my ($self, $c) = @_;
581
582 my $query_string = exists $self->env->{QUERY_STRING}
583 ? $self->env->{QUERY_STRING}
584 : '';
b0ad47c1 585
3b4d1251 586 # Check for keywords (no = signs)
587 # (yes, index() is faster than a regex :))
933ba403 588 if ( index( $query_string, '=' ) < 0 ) {
3b4d1251 589 $c->request->query_keywords( $self->unescape_uri($query_string) );
933ba403 590 return;
591 }
592
593 my %query;
e0616220 594
595 # replace semi-colons
596 $query_string =~ s/;/&/g;
b0ad47c1 597
2f381252 598 my @params = grep { length $_ } split /&/, $query_string;
e0616220 599
933ba403 600 for my $item ( @params ) {
b0ad47c1 601
602 my ($param, $value)
933ba403 603 = map { $self->unescape_uri($_) }
e5542b70 604 split( /=/, $item, 2 );
b0ad47c1 605
933ba403 606 $param = $self->unescape_uri($item) unless defined $param;
b0ad47c1 607
933ba403 608 if ( exists $query{$param} ) {
609 if ( ref $query{$param} ) {
610 push @{ $query{$param} }, $value;
611 }
612 else {
613 $query{$param} = [ $query{$param}, $value ];
614 }
615 }
616 else {
617 $query{$param} = $value;
618 }
e0616220 619 }
933ba403 620
621 $c->request->query_parameters( \%query );
e0616220 622}
fbcc39ad 623
b5ecfcf0 624=head2 $self->prepare_read($c)
fbcc39ad 625
4ab87e27 626prepare to read from the engine.
627
fbcc39ad 628=cut
fc7ec1d9 629
fbcc39ad 630sub prepare_read {
631 my ( $self, $c ) = @_;
4f5ebacd 632
878b821c 633 # Initialize the read position
4f5ebacd 634 $self->read_position(0);
b0ad47c1 635
878b821c 636 # Initialize the amount of data we think we need to read
637 $self->read_length( $c->request->header('Content-Length') || 0 );
fbcc39ad 638}
fc7ec1d9 639
b5ecfcf0 640=head2 $self->prepare_request(@arguments)
fc7ec1d9 641
4ab87e27 642Populate the context object from the request object.
643
fc7ec1d9 644=cut
645
44d28c7d 646sub prepare_request {
647 my ($self, $ctx, %args) = @_;
648 $self->_set_env($args{env});
649}
fc7ec1d9 650
b5ecfcf0 651=head2 $self->prepare_uploads($c)
c9afa5fc 652
fbcc39ad 653=cut
654
655sub prepare_uploads {
656 my ( $self, $c ) = @_;
7fa2c9c1 657
658 my $request = $c->request;
0f56bbcf 659 return unless $request->_body;
7fa2c9c1 660
0f56bbcf 661 my $uploads = $request->_body->upload;
7fa2c9c1 662 my $parameters = $request->parameters;
91772de9 663 foreach my $name (keys %$uploads) {
664 my $files = $uploads->{$name};
fbcc39ad 665 my @uploads;
7fa2c9c1 666 for my $upload (ref $files eq 'ARRAY' ? @$files : ($files)) {
667 my $headers = HTTP::Headers->new( %{ $upload->{headers} } );
668 my $u = Catalyst::Request::Upload->new
669 (
670 size => $upload->{size},
a160c98d 671 type => scalar $headers->content_type,
7fa2c9c1 672 headers => $headers,
673 tempname => $upload->{tempname},
674 filename => $upload->{filename},
675 );
fbcc39ad 676 push @uploads, $u;
677 }
7fa2c9c1 678 $request->uploads->{$name} = @uploads > 1 ? \@uploads : $uploads[0];
f4a57de4 679
c4bed79a 680 # support access to the filename as a normal param
681 my @filenames = map { $_->{filename} } @uploads;
a7e05d9d 682 # append, if there's already params with this name
7fa2c9c1 683 if (exists $parameters->{$name}) {
684 if (ref $parameters->{$name} eq 'ARRAY') {
685 push @{ $parameters->{$name} }, @filenames;
a7e05d9d 686 }
687 else {
7fa2c9c1 688 $parameters->{$name} = [ $parameters->{$name}, @filenames ];
a7e05d9d 689 }
690 }
691 else {
7fa2c9c1 692 $parameters->{$name} = @filenames > 1 ? \@filenames : $filenames[0];
a7e05d9d 693 }
fbcc39ad 694 }
695}
696
b5ecfcf0 697=head2 $self->prepare_write($c)
c9afa5fc 698
4ab87e27 699Abstract method. Implemented by the engines.
700
c9afa5fc 701=cut
702
fbcc39ad 703sub prepare_write { }
704
b5ecfcf0 705=head2 $self->read($c, [$maxlength])
fbcc39ad 706
ea72fece 707Reads from the input stream by calling C<< $self->read_chunk >>.
708
709Maintains the read_length and read_position counters as data is read.
710
fbcc39ad 711=cut
712
713sub read {
714 my ( $self, $c, $maxlength ) = @_;
4f5ebacd 715
fbcc39ad 716 my $remaining = $self->read_length - $self->read_position;
4bd82c41 717 $maxlength ||= $CHUNKSIZE;
4f5ebacd 718
fbcc39ad 719 # Are we done reading?
720 if ( $remaining <= 0 ) {
4f5ebacd 721 $self->finalize_read($c);
fbcc39ad 722 return;
723 }
c9afa5fc 724
fbcc39ad 725 my $readlen = ( $remaining > $maxlength ) ? $maxlength : $remaining;
726 my $rc = $self->read_chunk( $c, my $buffer, $readlen );
727 if ( defined $rc ) {
ea72fece 728 if (0 == $rc) { # Nothing more to read even though Content-Length
9e1f645b 729 # said there should be.
ea72fece 730 $self->finalize_read;
731 return;
732 }
fbcc39ad 733 $self->read_position( $self->read_position + $rc );
734 return $buffer;
735 }
736 else {
4f5ebacd 737 Catalyst::Exception->throw(
738 message => "Unknown error reading input: $!" );
fbcc39ad 739 }
740}
fc7ec1d9 741
b5ecfcf0 742=head2 $self->read_chunk($c, $buffer, $length)
23f9d934 743
10011c19 744Each engine implements read_chunk as its preferred way of reading a chunk
ea72fece 745of data. Returns the number of bytes read. A return of 0 indicates that
746there is no more data to be read.
fc7ec1d9 747
fbcc39ad 748=cut
61b1e958 749
e6b46d80 750sub read_chunk {
ce7abbda 751 my ($self, $ctx) = (shift, shift);
e6b46d80 752 return $self->env->{'psgi.input'}->read(@_);
753}
61b1e958 754
b5ecfcf0 755=head2 $self->read_length
ca39d576 756
fbcc39ad 757The length of input data to be read. This is obtained from the Content-Length
758header.
fc7ec1d9 759
b5ecfcf0 760=head2 $self->read_position
fc7ec1d9 761
fbcc39ad 762The amount of input data that has already been read.
63b763c5 763
9560b708 764=head2 $self->run($app, $server)
63b763c5 765
9560b708 766Start the engine. Builds a PSGI application and calls the
767run method on the server passed in..
4ab87e27 768
fbcc39ad 769=cut
fc7ec1d9 770
44d28c7d 771sub run {
4b0f97fc 772 my ($self, $app, @args) = @_;
773 my $server = pop @args if blessed $args[-1];
9560b708 774 $server ||= Plack::Loader->auto(); # We're not being called from a script,
29bb04ad 775 # so auto detect what backend to run on.
776 # This does *NOT* cover mod_perl.
a1791811 777 # FIXME - Do something sensible with the options we're passed
4b0f97fc 778 my $psgi = $self->build_psgi_app($app, @args);
779 $server->run($psgi);
a1791811 780}
44d28c7d 781
9560b708 782=head2 build_psgi_app ($app, @args)
783
784Builds and returns a PSGI application closure, wrapping it in the reverse proxy
785middleware if the using_frontend_proxy config setting is set.
786
787=cut
788
22a5833d 789sub build_psgi_app {
a1791811 790 my ($self, $app, @args) = @_;
c2f4a965 791
792 my $psgi_app = sub {
44d28c7d 793 my ($env) = @_;
794
795 return sub {
796 my ($respond) = @_;
797 $self->_set_response_cb($respond);
798 $app->handle_request(env => $env);
799 };
800 };
c2f4a965 801
802 $psgi_app = Plack::Middleware::Conditional->wrap(
803 $psgi_app,
804 condition => sub {
805 my ($env) = @_;
806 return if $app->config->{ignore_frontend_proxy};
807 return $env->{REMOTE_ADDR} eq '127.0.0.1' || $app->config->{using_frontend_proxy};
808 },
809 builder => sub { Plack::Middleware::ReverseProxy->wrap($_[0]) },
810 );
811
812 return $psgi_app;
44d28c7d 813}
fc7ec1d9 814
b5ecfcf0 815=head2 $self->write($c, $buffer)
fc7ec1d9 816
e512dd24 817Writes the buffer to the client.
4ab87e27 818
fc7ec1d9 819=cut
820
fbcc39ad 821sub write {
822 my ( $self, $c, $buffer ) = @_;
4f5ebacd 823
02570318 824 unless ( $self->_prepared_write ) {
4f5ebacd 825 $self->prepare_write($c);
02570318 826 $self->_prepared_write(1);
fc7ec1d9 827 }
b0ad47c1 828
094a0974 829 return 0 if !defined $buffer;
b0ad47c1 830
44d28c7d 831 my $len = length($buffer);
832 $self->_writer->write($buffer);
b0ad47c1 833
44d28c7d 834 return $len;
fc7ec1d9 835}
836
933ba403 837=head2 $self->unescape_uri($uri)
838
6a44fe01 839Unescapes a given URI using the most efficient method available. Engines such
840as Apache may implement this using Apache's C-based modules, for example.
933ba403 841
842=cut
843
844sub unescape_uri {
8c7d83e1 845 my ( $self, $str ) = @_;
7d22a537 846
847 $str =~ s/(?:%([0-9A-Fa-f]{2})|\+)/defined $1 ? chr(hex($1)) : ' '/eg;
848
8c7d83e1 849 return $str;
933ba403 850}
34d28dfd 851
4ab87e27 852=head2 $self->finalize_output
853
854<obsolete>, see finalize_body
855
0c76ec45 856=head2 $self->env
857
6356febf 858Hash containing environment variables including many special variables inserted
0c76ec45 859by WWW server - like SERVER_*, REMOTE_*, HTTP_* ...
860
6356febf 861Before accessing environment variables consider whether the same information is
0c76ec45 862not directly available via Catalyst objects $c->request, $c->engine ...
863
6356febf 864BEWARE: If you really need to access some environment variable from your Catalyst
0c76ec45 865application you should use $c->engine->env->{VARNAME} instead of $ENV{VARNAME},
866as in some enviroments the %ENV hash does not contain what you would expect.
867
fbcc39ad 868=head1 AUTHORS
869
2f381252 870Catalyst Contributors, see Catalyst.pm
fc7ec1d9 871
872=head1 COPYRIGHT
873
536bee89 874This library is free software. You can redistribute it and/or modify it under
fc7ec1d9 875the same terms as Perl itself.
876
877=cut
878
8791;