applied patch from github
[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;
a1791811 13use Plack::Loader;
b1ededd4 14use Catalyst::EngineLoader;
361ba9b2 15use Encode ();
191665f3 16use Plack::Request::Upload;
17use Hash::MultiValue;
361ba9b2 18use utf8;
fbcc39ad 19
d495753a 20use namespace::clean -except => 'meta';
21
faa02805 22# Amount of data to read from input on each pass
23our $CHUNKSIZE = 64 * 1024;
a50e5b46 24
faa02805 25# XXX - this is only here for compat, do not use!
26has env => ( is => 'rw', writer => '_set_env' );
0c6352ff 27my $WARN_ABOUT_ENV = 0;
28around env => sub {
29 my ($orig, $self, @args) = @_;
30 if(@args) {
31 warn "env as a writer is deprecated, you probably need to upgrade Catalyst::Engine::PSGI"
32 unless $WARN_ABOUT_ENV++;
33 return $self->_set_env(@args);
34 }
35 return $self->$orig;
36};
37
ddcd2fc4 38# XXX - Only here for Engine::PSGI compat
39sub prepare_connection {
40 my ($self, $ctx) = @_;
41 $ctx->request->prepare_connection;
42}
4bd82c41 43
fc7ec1d9 44=head1 NAME
45
46Catalyst::Engine - The Catalyst Engine
47
48=head1 SYNOPSIS
49
50See L<Catalyst>.
51
52=head1 DESCRIPTION
53
23f9d934 54=head1 METHODS
fc7ec1d9 55
cd3bb248 56
b5ecfcf0 57=head2 $self->finalize_body($c)
06e1b616 58
717fc5c9 59Finalize body. Prints the response output as blocking stream if it looks like
60a filehandle, otherwise write it out all in one go. If there is no body in
61the response, we assume you are handling it 'manually', such as for nonblocking
fc89737e 62style or asynchronous streaming responses. You do this by calling L</write>
63several times (which sends HTTP headers if needed) or you close over
64C<< $response->write_fh >>.
e37f92f5 65
fc89737e 66See L<Catalyst::Response/write> and L<Catalyst::Response/write_fh> for more.
06e1b616 67
68=cut
69
fbcc39ad 70sub finalize_body {
71 my ( $self, $c ) = @_;
46fff667 72 my $res = $c->response; # We use this all over
73
74 ## If we've asked for the write 'filehandle' that means the application is
75 ## doing something custom and is expected to close the response
76 return if $res->_has_write_fh;
77
8a3dcb98 78 my $body = $res->body; # save some typing
46fff667 79 if($res->_has_response_cb) {
80 ## we have not called the response callback yet, so we are safe to send
81 ## the whole body to PSGI
82
83 my @headers;
84 $res->headers->scan(sub { push @headers, @_ });
85
8a3dcb98 86 # We need to figure out what kind of body we have and normalize it to something
87 # PSGI can deal with
46fff667 88 if(defined $body) {
8a3dcb98 89 # Handle objects first
90 if(blessed($body)) {
91 if($body->can('getline')) {
92 # Body is an IO handle that meets the PSGI spec. Nothing to normalize
93 } elsif($body->can('read')) {
94
95 # In the past, Catalyst only looked for ->read not ->getline. It is very possible
96 # that one might have an object that respected read but did not have getline.
97 # As a result, we need to handle this case for backcompat.
56894295 98
8a3dcb98 99 # We will just do the old loop for now. In a future version of Catalyst this support
100 # will be removed and one will have to rewrite their custom object or use
101 # Plack::Middleware::AdaptFilehandleRead. In anycase support for this is officially
102 # deprecated and described as such as of 5.90060
103
104 my $got;
105 do {
106 $got = read $body, my ($buffer), $CHUNKSIZE;
107 $got = 0 unless $self->write($c, $buffer );
108 } while $got > 0;
109
110 close $body;
111 return;
112 } else {
113 # Looks like for backcompat reasons we need to be able to deal
114 # with stringyfiable objects.
115 $body = ["$body"];
116 }
117 } elsif(ref $body) {
118 if( (ref($body) eq 'GLOB') or (ref($body) eq 'ARRAY')) {
119 # Again, PSGI can just accept this, no transform needed. We don't officially
120 # document the body as arrayref at this time (and there's not specific test
121 # cases. we support it because it simplifies some plack compatibility logic
122 # and we might make it official at some point.
123 } else {
124 $c->log->error("${\ref($body)} is not a valid value for Response->body");
125 return;
126 }
46fff667 127 } else {
8a3dcb98 128 # Body is defined and not an object or reference. We assume a simple value
129 # and wrap it in an array for PSGI
130 $body = [$body];
46fff667 131 }
132 } else {
8a3dcb98 133 # There's no body...
134 $body = [];
46fff667 135 }
136
137 $res->_response_cb->([ $res->status, \@headers, $body]);
138 $res->_clear_response_cb;
139
140 } else {
141 ## Now, if there's no response callback anymore, that means someone has
142 ## called ->write in order to stream 'some stuff along the way'. I think
143 ## for backcompat we still need to handle a ->body. I guess I could see
144 ## someone calling ->write to presend some stuff, and then doing the rest
145 ## via ->body, like in a template.
146
147 ## We'll just use the old, existing code for this (or most of it)
148
149 if(my $body = $res->body) {
8a3dcb98 150
46fff667 151 if ( blessed($body) && $body->can('read') or ref($body) eq 'GLOB' ) {
152
153 ## In this case we have no choice and will fall back on the old
8a3dcb98 154 ## manual streaming stuff. Not optimal. This is deprecated as of 5.900560+
46fff667 155
156 my $got;
157 do {
158 $got = read $body, my ($buffer), $CHUNKSIZE;
159 $got = 0 unless $self->write($c, $buffer );
160 } while $got > 0;
161
162 close $body;
163 }
164 else {
165 $self->write($c, $body );
166 }
167 }
168
169 $res->_writer->close;
170 $res->_clear_writer;
f4a57de4 171 }
030674d0 172
ca3023fc 173 return;
fbcc39ad 174}
6dc87a0f 175
b5ecfcf0 176=head2 $self->finalize_cookies($c)
6dc87a0f 177
fa32ac82 178Create CGI::Simple::Cookie objects from $c->res->cookies, and set them as
179response headers.
4ab87e27 180
6dc87a0f 181=cut
182
183sub finalize_cookies {
fbcc39ad 184 my ( $self, $c ) = @_;
6dc87a0f 185
fbcc39ad 186 my @cookies;
7fa2c9c1 187 my $response = $c->response;
c82ed742 188
91772de9 189 foreach my $name (keys %{ $response->cookies }) {
190
191 my $val = $response->cookies->{$name};
fbcc39ad 192
2832cb5d 193 my $cookie = (
7e95ba12 194 blessed($val)
2832cb5d 195 ? $val
196 : CGI::Simple::Cookie->new(
197 -name => $name,
198 -value => $val->{value},
199 -expires => $val->{expires},
200 -domain => $val->{domain},
201 -path => $val->{path},
b21bc468 202 -secure => $val->{secure} || 0,
203 -httponly => $val->{httponly} || 0,
2832cb5d 204 )
6dc87a0f 205 );
0f12bef2 206 if (!defined $cookie) {
207 $c->log->warn("undef passed in '$name' cookie value - not setting cookie")
208 if $c->debug;
209 next;
210 }
6dc87a0f 211
fbcc39ad 212 push @cookies, $cookie->as_string;
6dc87a0f 213 }
6dc87a0f 214
b39840da 215 for my $cookie (@cookies) {
7fa2c9c1 216 $response->headers->push_header( 'Set-Cookie' => $cookie );
fbcc39ad 217 }
218}
969647fd 219
b5ecfcf0 220=head2 $self->finalize_error($c)
969647fd 221
6e5b548e 222Output an appropriate error message. Called if there's an error in $c
4ab87e27 223after the dispatch has finished. Will output debug messages if Catalyst
224is in debug mode, or a `please come back later` message otherwise.
225
969647fd 226=cut
227
c96cdcef 228sub _dump_error_page_element {
229 my ($self, $i, $element) = @_;
230 my ($name, $val) = @{ $element };
231
232 # This is fugly, but the metaclass is _HUGE_ and demands waaay too much
233 # scrolling. Suggestions for more pleasant ways to do this welcome.
234 local $val->{'__MOP__'} = "Stringified: "
1565e158 235 . $val->{'__MOP__'} if ref $val eq 'HASH' && exists $val->{'__MOP__'};
c96cdcef 236
237 my $text = encode_entities( dump( $val ));
238 sprintf <<"EOF", $name, $text;
239<h2><a href="#" onclick="toggleDump('dump_$i'); return false">%s</a></h2>
240<div id="dump_$i">
241 <pre wrap="">%s</pre>
242</div>
243EOF
244}
245
969647fd 246sub finalize_error {
fbcc39ad 247 my ( $self, $c ) = @_;
969647fd 248
7299a7b4 249 $c->res->content_type('text/html; charset=utf-8');
df960201 250 my $name = ref($c)->config->{name} || join(' ', split('::', ref $c));
361ba9b2 251
252 # Prevent Catalyst::Plugin::Unicode::Encoding from running.
253 # This is a little nasty, but it's the best way to be clean whether or
254 # not the user has an encoding plugin.
255
256 if ($c->can('encoding')) {
257 $c->{encoding} = '';
258 }
969647fd 259
260 my ( $title, $error, $infos );
261 if ( $c->debug ) {
62d9b030 262
263 # For pretty dumps
b5ecfcf0 264 $error = join '', map {
265 '<p><code class="error">'
266 . encode_entities($_)
267 . '</code></p>'
268 } @{ $c->error };
969647fd 269 $error ||= 'No output';
2666dd3b 270 $error = qq{<pre wrap="">$error</pre>};
969647fd 271 $title = $name = "$name on Catalyst $Catalyst::VERSION";
d82cc9ae 272 $name = "<h1>$name</h1>";
fbcc39ad 273
258733f1 274 # Don't show context in the dump
275 $c->res->_clear_context;
276
fbcc39ad 277 # Don't show body parser in the dump
0f56bbcf 278 $c->req->_clear_body;
fbcc39ad 279
c6ef5e69 280 my @infos;
281 my $i = 0;
c6ef5e69 282 for my $dump ( $c->dump_these ) {
c96cdcef 283 push @infos, $self->_dump_error_page_element($i, $dump);
c6ef5e69 284 $i++;
285 }
286 $infos = join "\n", @infos;
969647fd 287 }
288 else {
289 $title = $name;
290 $error = '';
291 $infos = <<"";
292<pre>
293(en) Please come back later
0c2b4ac0 294(fr) SVP veuillez revenir plus tard
969647fd 295(de) Bitte versuchen sie es spaeter nocheinmal
d82cc9ae 296(at) Konnten's bitt'schoen spaeter nochmal reinschauen
969647fd 297(no) Vennligst prov igjen senere
d82cc9ae 298(dk) Venligst prov igen senere
299(pl) Prosze sprobowac pozniej
2f381252 300(pt) Por favor volte mais tarde
b31c0f2e 301(ru) Попробуйте еще раз позже
302(ua) Спробуйте ще раз пізніше
969647fd 303</pre>
304
305 $name = '';
306 }
e060fe05 307 $c->res->body( <<"" );
7299a7b4 308<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
309 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
310<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
969647fd 311<head>
7299a7b4 312 <meta http-equiv="Content-Language" content="en" />
313 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
969647fd 314 <title>$title</title>
7299a7b4 315 <script type="text/javascript">
c6ef5e69 316 <!--
317 function toggleDump (dumpElement) {
7299a7b4 318 var e = document.getElementById( dumpElement );
319 if (e.style.display == "none") {
320 e.style.display = "";
c6ef5e69 321 }
322 else {
7299a7b4 323 e.style.display = "none";
c6ef5e69 324 }
325 }
326 -->
327 </script>
969647fd 328 <style type="text/css">
329 body {
330 font-family: "Bitstream Vera Sans", "Trebuchet MS", Verdana,
331 Tahoma, Arial, helvetica, sans-serif;
34d28dfd 332 color: #333;
969647fd 333 background-color: #eee;
334 margin: 0px;
335 padding: 0px;
336 }
c6ef5e69 337 :link, :link:hover, :visited, :visited:hover {
34d28dfd 338 color: #000;
c6ef5e69 339 }
969647fd 340 div.box {
9619f23c 341 position: relative;
969647fd 342 background-color: #ccc;
343 border: 1px solid #aaa;
344 padding: 4px;
345 margin: 10px;
969647fd 346 }
347 div.error {
34d28dfd 348 background-color: #cce;
969647fd 349 border: 1px solid #755;
350 padding: 8px;
351 margin: 4px;
352 margin-bottom: 10px;
969647fd 353 }
354 div.infos {
34d28dfd 355 background-color: #eee;
969647fd 356 border: 1px solid #575;
357 padding: 8px;
358 margin: 4px;
359 margin-bottom: 10px;
969647fd 360 }
361 div.name {
34d28dfd 362 background-color: #cce;
969647fd 363 border: 1px solid #557;
364 padding: 8px;
365 margin: 4px;
969647fd 366 }
7f8e0078 367 code.error {
368 display: block;
369 margin: 1em 0;
370 overflow: auto;
7f8e0078 371 }
9619f23c 372 div.name h1, div.error p {
373 margin: 0;
374 }
375 h2 {
376 margin-top: 0;
377 margin-bottom: 10px;
378 font-size: medium;
379 font-weight: bold;
380 text-decoration: underline;
381 }
382 h1 {
383 font-size: medium;
384 font-weight: normal;
385 }
2666dd3b 386 /* from http://users.tkk.fi/~tkarvine/linux/doc/pre-wrap/pre-wrap-css3-mozilla-opera-ie.html */
387 /* Browser specific (not valid) styles to make preformatted text wrap */
b0ad47c1 388 pre {
2666dd3b 389 white-space: pre-wrap; /* css-3 */
390 white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
391 white-space: -pre-wrap; /* Opera 4-6 */
392 white-space: -o-pre-wrap; /* Opera 7 */
393 word-wrap: break-word; /* Internet Explorer 5.5+ */
394 }
969647fd 395 </style>
396</head>
397<body>
398 <div class="box">
399 <div class="error">$error</div>
400 <div class="infos">$infos</div>
401 <div class="name">$name</div>
402 </div>
403</body>
404</html>
405
4b66aa19 406 # Trick IE. Old versions of IE would display their own error page instead
407 # of ours if we'd give it less than 512 bytes.
d82cc9ae 408 $c->res->{body} .= ( ' ' x 512 );
409
361ba9b2 410 $c->res->{body} = Encode::encode("UTF-8", $c->res->{body});
411
d82cc9ae 412 # Return 500
33117422 413 $c->res->status(500);
969647fd 414}
415
b5ecfcf0 416=head2 $self->finalize_headers($c)
fc7ec1d9 417
9c4288ea 418Allows engines to write headers to response
4ab87e27 419
fc7ec1d9 420=cut
421
44d28c7d 422sub finalize_headers {
423 my ($self, $ctx) = @_;
424
89ba65d5 425 $ctx->finalize_headers unless $ctx->response->finalized_headers;
44d28c7d 426 return;
427}
fc7ec1d9 428
b5ecfcf0 429=head2 $self->finalize_uploads($c)
fc7ec1d9 430
4ab87e27 431Clean up after uploads, deleting temp files.
432
fc7ec1d9 433=cut
434
fbcc39ad 435sub finalize_uploads {
436 my ( $self, $c ) = @_;
99fe1710 437
671123ba 438 # N.B. This code is theoretically entirely unneeded due to ->cleanup(1)
439 # on the HTTP::Body object.
7fa2c9c1 440 my $request = $c->request;
91772de9 441 foreach my $key (keys %{ $request->uploads }) {
442 my $upload = $request->uploads->{$key};
7fa2c9c1 443 unlink grep { -e $_ } map { $_->tempname }
444 (ref $upload eq 'ARRAY' ? @{$upload} : ($upload));
c85ff642 445 }
7fa2c9c1 446
fc7ec1d9 447}
448
b5ecfcf0 449=head2 $self->prepare_body($c)
fc7ec1d9 450
4ab87e27 451sets up the L<Catalyst::Request> object body using L<HTTP::Body>
452
fc7ec1d9 453=cut
454
fbcc39ad 455sub prepare_body {
456 my ( $self, $c ) = @_;
99fe1710 457
398f13db 458 $c->request->prepare_body;
fc7ec1d9 459}
460
b5ecfcf0 461=head2 $self->prepare_body_chunk($c)
4bd82c41 462
4ab87e27 463Add a chunk to the request body.
464
4bd82c41 465=cut
466
398f13db 467# XXX - Can this be deleted?
4bd82c41 468sub prepare_body_chunk {
469 my ( $self, $c, $chunk ) = @_;
4f5ebacd 470
398f13db 471 $c->request->prepare_body_chunk($chunk);
4bd82c41 472}
473
b5ecfcf0 474=head2 $self->prepare_body_parameters($c)
06e1b616 475
b0ad47c1 476Sets up parameters from body.
4ab87e27 477
06e1b616 478=cut
479
fbcc39ad 480sub prepare_body_parameters {
481 my ( $self, $c ) = @_;
b0ad47c1 482
398f13db 483 $c->request->prepare_body_parameters;
44d28c7d 484}
fc7ec1d9 485
b5ecfcf0 486=head2 $self->prepare_parameters($c)
fc7ec1d9 487
11e7af55 488Sets up parameters from query and post parameters.
489If parameters have already been set up will clear
490existing parameters and set up again.
4ab87e27 491
fc7ec1d9 492=cut
493
fbcc39ad 494sub prepare_parameters {
495 my ( $self, $c ) = @_;
fc7ec1d9 496
11e7af55 497 $c->request->_clear_parameters;
498 return $c->request->parameters;
fbcc39ad 499}
500
b5ecfcf0 501=head2 $self->prepare_path($c)
fc7ec1d9 502
4ab87e27 503abstract method, implemented by engines.
504
fc7ec1d9 505=cut
506
44d28c7d 507sub prepare_path {
508 my ($self, $ctx) = @_;
509
faa02805 510 my $env = $ctx->request->env;
44d28c7d 511
512 my $scheme = $ctx->request->secure ? 'https' : 'http';
513 my $host = $env->{HTTP_HOST} || $env->{SERVER_NAME};
514 my $port = $env->{SERVER_PORT} || 80;
515 my $base_path = $env->{SCRIPT_NAME} || "/";
516
517 # set the request URI
661de072 518 my $path;
519 if (!$ctx->config->{use_request_uri_for_path}) {
4904ee27 520 my $path_info = $env->{PATH_INFO};
521 if ( exists $env->{REDIRECT_URL} ) {
522 $base_path = $env->{REDIRECT_URL};
523 $base_path =~ s/\Q$path_info\E$//;
524 }
525 $path = $base_path . $path_info;
661de072 526 $path =~ s{^/+}{};
527 $path =~ s/([^$URI::uric])/$URI::Escape::escapes{$1}/go;
528 $path =~ s/\?/%3F/g; # STUPID STUPID SPECIAL CASE
529 }
530 else {
531 my $req_uri = $env->{REQUEST_URI};
532 $req_uri =~ s/\?.*$//;
533 $path = $req_uri;
534 $path =~ s{^/+}{};
535 }
44d28c7d 536
537 # Using URI directly is way too slow, so we construct the URLs manually
538 my $uri_class = "URI::$scheme";
539
540 # HTTP_HOST will include the port even if it's 80/443
541 $host =~ s/:(?:80|443)$//;
542
543 if ($port !~ /^(?:80|443)$/ && $host !~ /:/) {
544 $host .= ":$port";
545 }
546
44d28c7d 547 my $query = $env->{QUERY_STRING} ? '?' . $env->{QUERY_STRING} : '';
548 my $uri = $scheme . '://' . $host . '/' . $path . $query;
549
4ee03d72 550 $ctx->request->uri( (bless \$uri, $uri_class)->canonical );
44d28c7d 551
552 # set the base URI
553 # base must end in a slash
554 $base_path .= '/' unless $base_path =~ m{/$};
555
556 my $base_uri = $scheme . '://' . $host . $base_path;
557
558 $ctx->request->base( bless \$base_uri, $uri_class );
559
560 return;
561}
fc7ec1d9 562
b5ecfcf0 563=head2 $self->prepare_request($c)
fc7ec1d9 564
b5ecfcf0 565=head2 $self->prepare_query_parameters($c)
fc7ec1d9 566
4ab87e27 567process the query string and extract query parameters.
568
fc7ec1d9 569=cut
570
e0616220 571sub prepare_query_parameters {
44d28c7d 572 my ($self, $c) = @_;
faa02805 573 my $env = $c->request->env;
bd822b43 574
575 if(my $query_obj = $env->{'plack.request.query'}) {
88ba7793 576 $c->request->query_parameters(
577 $c->request->_use_hash_multivalue ?
578 $query_obj->clone :
579 $query_obj->as_hashref_mixed);
bd822b43 580 return;
581 }
582
faa02805 583 my $query_string = exists $env->{QUERY_STRING}
584 ? $env->{QUERY_STRING}
44d28c7d 585 : '';
b0ad47c1 586
3b4d1251 587 # Check for keywords (no = signs)
588 # (yes, index() is faster than a regex :))
933ba403 589 if ( index( $query_string, '=' ) < 0 ) {
bd822b43 590 $c->request->query_keywords($self->unescape_uri($query_string));
933ba403 591 return;
592 }
593
594 my %query;
e0616220 595
596 # replace semi-colons
597 $query_string =~ s/;/&/g;
b0ad47c1 598
2f381252 599 my @params = grep { length $_ } split /&/, $query_string;
e0616220 600
933ba403 601 for my $item ( @params ) {
b0ad47c1 602
603 my ($param, $value)
933ba403 604 = map { $self->unescape_uri($_) }
e5542b70 605 split( /=/, $item, 2 );
b0ad47c1 606
933ba403 607 $param = $self->unescape_uri($item) unless defined $param;
b0ad47c1 608
933ba403 609 if ( exists $query{$param} ) {
610 if ( ref $query{$param} ) {
611 push @{ $query{$param} }, $value;
612 }
613 else {
614 $query{$param} = [ $query{$param}, $value ];
615 }
616 }
617 else {
618 $query{$param} = $value;
619 }
e0616220 620 }
bd822b43 621
88ba7793 622 $c->request->query_parameters(
623 $c->request->_use_hash_multivalue ?
f152ae23 624 Hash::MultiValue->from_mixed(\%query) :
88ba7793 625 \%query);
e0616220 626}
fbcc39ad 627
b5ecfcf0 628=head2 $self->prepare_read($c)
fbcc39ad 629
47b9d68e 630Prepare to read by initializing the Content-Length from headers.
4ab87e27 631
fbcc39ad 632=cut
fc7ec1d9 633
fbcc39ad 634sub prepare_read {
635 my ( $self, $c ) = @_;
4f5ebacd 636
878b821c 637 # Initialize the amount of data we think we need to read
faa02805 638 $c->request->_read_length;
fbcc39ad 639}
fc7ec1d9 640
b5ecfcf0 641=head2 $self->prepare_request(@arguments)
fc7ec1d9 642
c4a17516 643Populate the context object from the request object.
4ab87e27 644
fc7ec1d9 645=cut
646
44d28c7d 647sub prepare_request {
648 my ($self, $ctx, %args) = @_;
0eb98ebd 649 $ctx->log->psgienv($args{env}) if $ctx->log->can('psgienv');
faa02805 650 $ctx->request->_set_env($args{env});
651 $self->_set_env($args{env}); # Nasty back compat!
652 $ctx->response->_set_response_cb($args{response_cb});
44d28c7d 653}
fc7ec1d9 654
b5ecfcf0 655=head2 $self->prepare_uploads($c)
c9afa5fc 656
fbcc39ad 657=cut
658
659sub prepare_uploads {
660 my ( $self, $c ) = @_;
7fa2c9c1 661
662 my $request = $c->request;
0f56bbcf 663 return unless $request->_body;
7fa2c9c1 664
0f56bbcf 665 my $uploads = $request->_body->upload;
7fa2c9c1 666 my $parameters = $request->parameters;
91772de9 667 foreach my $name (keys %$uploads) {
668 my $files = $uploads->{$name};
fbcc39ad 669 my @uploads;
7fa2c9c1 670 for my $upload (ref $files eq 'ARRAY' ? @$files : ($files)) {
671 my $headers = HTTP::Headers->new( %{ $upload->{headers} } );
672 my $u = Catalyst::Request::Upload->new
673 (
674 size => $upload->{size},
a160c98d 675 type => scalar $headers->content_type,
7fa2c9c1 676 headers => $headers,
677 tempname => $upload->{tempname},
678 filename => $upload->{filename},
679 );
fbcc39ad 680 push @uploads, $u;
681 }
7fa2c9c1 682 $request->uploads->{$name} = @uploads > 1 ? \@uploads : $uploads[0];
f4a57de4 683
c4bed79a 684 # support access to the filename as a normal param
685 my @filenames = map { $_->{filename} } @uploads;
a7e05d9d 686 # append, if there's already params with this name
7fa2c9c1 687 if (exists $parameters->{$name}) {
688 if (ref $parameters->{$name} eq 'ARRAY') {
689 push @{ $parameters->{$name} }, @filenames;
a7e05d9d 690 }
691 else {
7fa2c9c1 692 $parameters->{$name} = [ $parameters->{$name}, @filenames ];
a7e05d9d 693 }
694 }
695 else {
7fa2c9c1 696 $parameters->{$name} = @filenames > 1 ? \@filenames : $filenames[0];
a7e05d9d 697 }
fbcc39ad 698 }
699}
700
767480fd 701=head2 $self->write($c, $buffer)
c9afa5fc 702
767480fd 703Writes the buffer to the client.
4ab87e27 704
c9afa5fc 705=cut
706
767480fd 707sub write {
708 my ( $self, $c, $buffer ) = @_;
709
710 $c->response->write($buffer);
711}
fbcc39ad 712
b5ecfcf0 713=head2 $self->read($c, [$maxlength])
fbcc39ad 714
ea72fece 715Reads from the input stream by calling C<< $self->read_chunk >>.
716
717Maintains the read_length and read_position counters as data is read.
718
fbcc39ad 719=cut
720
721sub read {
722 my ( $self, $c, $maxlength ) = @_;
4f5ebacd 723
f083854e 724 $c->request->read($maxlength);
fbcc39ad 725}
fc7ec1d9 726
87f50436 727=head2 $self->read_chunk($c, \$buffer, $length)
23f9d934 728
10011c19 729Each engine implements read_chunk as its preferred way of reading a chunk
ea72fece 730of data. Returns the number of bytes read. A return of 0 indicates that
731there is no more data to be read.
fc7ec1d9 732
fbcc39ad 733=cut
61b1e958 734
e6b46d80 735sub read_chunk {
ce7abbda 736 my ($self, $ctx) = (shift, shift);
87f50436 737 return $ctx->request->read_chunk(@_);
e6b46d80 738}
61b1e958 739
9560b708 740=head2 $self->run($app, $server)
63b763c5 741
9560b708 742Start the engine. Builds a PSGI application and calls the
acbecf08 743run method on the server passed in, which then causes the
744engine to loop, handling requests..
4ab87e27 745
fbcc39ad 746=cut
fc7ec1d9 747
44d28c7d 748sub run {
51857616 749 my ($self, $app, $psgi, @args) = @_;
acbecf08 750 # @args left here rather than just a $options, $server for back compat with the
751 # old style scripts which send a few args, then a hashref
752
753 # They should never actually be used in the normal case as the Plack engine is
754 # passed in got all the 'standard' args via the loader in the script already.
755
756 # FIXME - we should stash the options in an attribute so that custom args
757 # like Gitalist's --git_dir are possible to get from the app without stupid tricks.
1e5dad00 758 my $server = pop @args if (scalar @args && blessed $args[-1]);
759 my $options = pop @args if (scalar @args && ref($args[-1]) eq 'HASH');
ccb13b15 760 # Back compat hack for applications with old (non Catalyst::Script) scripts to work in FCGI.
761 if (scalar @args && !ref($args[0])) {
762 if (my $listen = shift @args) {
763 $options->{listen} ||= [$listen];
764 }
765 }
acbecf08 766 if (! $server ) {
f7a3f8fd 767 $server = Catalyst::EngineLoader->new(application_name => ref($self))->auto(%$options);
f7f55b2f 768 # We're not being called from a script, so auto detect what backend to
769 # run on. This should never happen, as mod_perl never calls ->run,
770 # instead the $app->handle method is called per request.
acbecf08 771 $app->log->warn("Not supplied a Plack engine, falling back to engine auto-loader (are your scripts ancient?)")
772 }
aee7cdcc 773 $app->run_options($options);
acbecf08 774 $server->run($psgi, $options);
a1791811 775}
44d28c7d 776
9560b708 777=head2 build_psgi_app ($app, @args)
778
e3f6b891 779Builds and returns a PSGI application closure. (Raw, not wrapped in middleware)
9560b708 780
781=cut
782
22a5833d 783sub build_psgi_app {
a1791811 784 my ($self, $app, @args) = @_;
c2f4a965 785
fcffcb05 786 return sub {
44d28c7d 787 my ($env) = @_;
788
789 return sub {
790 my ($respond) = @_;
e33d788d 791 confess("Did not get a response callback for writer, cannot continue") unless $respond;
faa02805 792 $app->handle_request(env => $env, response_cb => $respond);
44d28c7d 793 };
794 };
795}
fc7ec1d9 796
933ba403 797=head2 $self->unescape_uri($uri)
798
6a44fe01 799Unescapes a given URI using the most efficient method available. Engines such
800as Apache may implement this using Apache's C-based modules, for example.
933ba403 801
802=cut
803
804sub unescape_uri {
8c7d83e1 805 my ( $self, $str ) = @_;
7d22a537 806
807 $str =~ s/(?:%([0-9A-Fa-f]{2})|\+)/defined $1 ? chr(hex($1)) : ' '/eg;
808
8c7d83e1 809 return $str;
933ba403 810}
34d28dfd 811
4ab87e27 812=head2 $self->finalize_output
813
814<obsolete>, see finalize_body
815
0c76ec45 816=head2 $self->env
817
6356febf 818Hash containing environment variables including many special variables inserted
0c76ec45 819by WWW server - like SERVER_*, REMOTE_*, HTTP_* ...
820
6356febf 821Before accessing environment variables consider whether the same information is
0c76ec45 822not directly available via Catalyst objects $c->request, $c->engine ...
823
6356febf 824BEWARE: If you really need to access some environment variable from your Catalyst
0c76ec45 825application you should use $c->engine->env->{VARNAME} instead of $ENV{VARNAME},
ae7da8f5 826as in some environments the %ENV hash does not contain what you would expect.
0c76ec45 827
fbcc39ad 828=head1 AUTHORS
829
2f381252 830Catalyst Contributors, see Catalyst.pm
fc7ec1d9 831
832=head1 COPYRIGHT
833
536bee89 834This library is free software. You can redistribute it and/or modify it under
fc7ec1d9 835the same terms as Perl itself.
836
837=cut
838
58f86b1a 839__PACKAGE__->meta->make_immutable;
840
fc7ec1d9 8411;