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