Remove outdated comment and some trailing whitespace.
[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} );
435 $request->secure( $env->{'psgi.url_scheme'} eq 'https' );
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/\?.*$//;
521 my $path = $self->unescape_uri($req_uri);
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
534 # Escape the path
535 $path =~ s/([^$URI::uric])/$URI::Escape::escapes{$1}/go;
536 $path =~ s/\?/%3F/g; # STUPID STUPID SPECIAL CASE
537
538 my $query = $env->{QUERY_STRING} ? '?' . $env->{QUERY_STRING} : '';
539 my $uri = $scheme . '://' . $host . '/' . $path . $query;
540
541 $ctx->request->uri( bless \$uri, $uri_class );
542
543 # set the base URI
544 # base must end in a slash
545 $base_path .= '/' unless $base_path =~ m{/$};
546
547 my $base_uri = $scheme . '://' . $host . $base_path;
548
549 $ctx->request->base( bless \$base_uri, $uri_class );
550
551 return;
552}
fc7ec1d9 553
b5ecfcf0 554=head2 $self->prepare_request($c)
fc7ec1d9 555
b5ecfcf0 556=head2 $self->prepare_query_parameters($c)
fc7ec1d9 557
4ab87e27 558process the query string and extract query parameters.
559
fc7ec1d9 560=cut
561
e0616220 562sub prepare_query_parameters {
44d28c7d 563 my ($self, $c) = @_;
564
565 my $query_string = exists $self->env->{QUERY_STRING}
566 ? $self->env->{QUERY_STRING}
567 : '';
b0ad47c1 568
3b4d1251 569 # Check for keywords (no = signs)
570 # (yes, index() is faster than a regex :))
933ba403 571 if ( index( $query_string, '=' ) < 0 ) {
3b4d1251 572 $c->request->query_keywords( $self->unescape_uri($query_string) );
933ba403 573 return;
574 }
575
576 my %query;
e0616220 577
578 # replace semi-colons
579 $query_string =~ s/;/&/g;
b0ad47c1 580
2f381252 581 my @params = grep { length $_ } split /&/, $query_string;
e0616220 582
933ba403 583 for my $item ( @params ) {
b0ad47c1 584
585 my ($param, $value)
933ba403 586 = map { $self->unescape_uri($_) }
e5542b70 587 split( /=/, $item, 2 );
b0ad47c1 588
933ba403 589 $param = $self->unescape_uri($item) unless defined $param;
b0ad47c1 590
933ba403 591 if ( exists $query{$param} ) {
592 if ( ref $query{$param} ) {
593 push @{ $query{$param} }, $value;
594 }
595 else {
596 $query{$param} = [ $query{$param}, $value ];
597 }
598 }
599 else {
600 $query{$param} = $value;
601 }
e0616220 602 }
933ba403 603
604 $c->request->query_parameters( \%query );
e0616220 605}
fbcc39ad 606
b5ecfcf0 607=head2 $self->prepare_read($c)
fbcc39ad 608
4ab87e27 609prepare to read from the engine.
610
fbcc39ad 611=cut
fc7ec1d9 612
fbcc39ad 613sub prepare_read {
614 my ( $self, $c ) = @_;
4f5ebacd 615
878b821c 616 # Initialize the read position
4f5ebacd 617 $self->read_position(0);
b0ad47c1 618
878b821c 619 # Initialize the amount of data we think we need to read
620 $self->read_length( $c->request->header('Content-Length') || 0 );
fbcc39ad 621}
fc7ec1d9 622
b5ecfcf0 623=head2 $self->prepare_request(@arguments)
fc7ec1d9 624
4ab87e27 625Populate the context object from the request object.
626
fc7ec1d9 627=cut
628
44d28c7d 629sub prepare_request {
630 my ($self, $ctx, %args) = @_;
631 $self->_set_env($args{env});
632}
fc7ec1d9 633
b5ecfcf0 634=head2 $self->prepare_uploads($c)
c9afa5fc 635
fbcc39ad 636=cut
637
638sub prepare_uploads {
639 my ( $self, $c ) = @_;
7fa2c9c1 640
641 my $request = $c->request;
0f56bbcf 642 return unless $request->_body;
7fa2c9c1 643
0f56bbcf 644 my $uploads = $request->_body->upload;
7fa2c9c1 645 my $parameters = $request->parameters;
91772de9 646 foreach my $name (keys %$uploads) {
647 my $files = $uploads->{$name};
fbcc39ad 648 my @uploads;
7fa2c9c1 649 for my $upload (ref $files eq 'ARRAY' ? @$files : ($files)) {
650 my $headers = HTTP::Headers->new( %{ $upload->{headers} } );
651 my $u = Catalyst::Request::Upload->new
652 (
653 size => $upload->{size},
654 type => $headers->content_type,
655 headers => $headers,
656 tempname => $upload->{tempname},
657 filename => $upload->{filename},
658 );
fbcc39ad 659 push @uploads, $u;
660 }
7fa2c9c1 661 $request->uploads->{$name} = @uploads > 1 ? \@uploads : $uploads[0];
f4a57de4 662
c4bed79a 663 # support access to the filename as a normal param
664 my @filenames = map { $_->{filename} } @uploads;
a7e05d9d 665 # append, if there's already params with this name
7fa2c9c1 666 if (exists $parameters->{$name}) {
667 if (ref $parameters->{$name} eq 'ARRAY') {
668 push @{ $parameters->{$name} }, @filenames;
a7e05d9d 669 }
670 else {
7fa2c9c1 671 $parameters->{$name} = [ $parameters->{$name}, @filenames ];
a7e05d9d 672 }
673 }
674 else {
7fa2c9c1 675 $parameters->{$name} = @filenames > 1 ? \@filenames : $filenames[0];
a7e05d9d 676 }
fbcc39ad 677 }
678}
679
b5ecfcf0 680=head2 $self->prepare_write($c)
c9afa5fc 681
4ab87e27 682Abstract method. Implemented by the engines.
683
c9afa5fc 684=cut
685
fbcc39ad 686sub prepare_write { }
687
b5ecfcf0 688=head2 $self->read($c, [$maxlength])
fbcc39ad 689
ea72fece 690Reads from the input stream by calling C<< $self->read_chunk >>.
691
692Maintains the read_length and read_position counters as data is read.
693
fbcc39ad 694=cut
695
696sub read {
697 my ( $self, $c, $maxlength ) = @_;
4f5ebacd 698
fbcc39ad 699 my $remaining = $self->read_length - $self->read_position;
4bd82c41 700 $maxlength ||= $CHUNKSIZE;
4f5ebacd 701
fbcc39ad 702 # Are we done reading?
703 if ( $remaining <= 0 ) {
4f5ebacd 704 $self->finalize_read($c);
fbcc39ad 705 return;
706 }
c9afa5fc 707
fbcc39ad 708 my $readlen = ( $remaining > $maxlength ) ? $maxlength : $remaining;
709 my $rc = $self->read_chunk( $c, my $buffer, $readlen );
710 if ( defined $rc ) {
ea72fece 711 if (0 == $rc) { # Nothing more to read even though Content-Length
9e1f645b 712 # said there should be.
ea72fece 713 $self->finalize_read;
714 return;
715 }
fbcc39ad 716 $self->read_position( $self->read_position + $rc );
717 return $buffer;
718 }
719 else {
4f5ebacd 720 Catalyst::Exception->throw(
721 message => "Unknown error reading input: $!" );
fbcc39ad 722 }
723}
fc7ec1d9 724
b5ecfcf0 725=head2 $self->read_chunk($c, $buffer, $length)
23f9d934 726
10011c19 727Each engine implements read_chunk as its preferred way of reading a chunk
ea72fece 728of data. Returns the number of bytes read. A return of 0 indicates that
729there is no more data to be read.
fc7ec1d9 730
fbcc39ad 731=cut
61b1e958 732
e6b46d80 733sub read_chunk {
ce7abbda 734 my ($self, $ctx) = (shift, shift);
e6b46d80 735 return $self->env->{'psgi.input'}->read(@_);
736}
61b1e958 737
b5ecfcf0 738=head2 $self->read_length
ca39d576 739
fbcc39ad 740The length of input data to be read. This is obtained from the Content-Length
741header.
fc7ec1d9 742
b5ecfcf0 743=head2 $self->read_position
fc7ec1d9 744
fbcc39ad 745The amount of input data that has already been read.
63b763c5 746
b5ecfcf0 747=head2 $self->run($c)
63b763c5 748
4ab87e27 749Start the engine. Implemented by the various engine classes.
750
fbcc39ad 751=cut
fc7ec1d9 752
44d28c7d 753sub run {
754 my ($self, $app) = @_;
755
756 return sub {
757 my ($env) = @_;
758
759 return sub {
760 my ($respond) = @_;
761 $self->_set_response_cb($respond);
762 $app->handle_request(env => $env);
763 };
764 };
765}
fc7ec1d9 766
b5ecfcf0 767=head2 $self->write($c, $buffer)
fc7ec1d9 768
e512dd24 769Writes the buffer to the client.
4ab87e27 770
fc7ec1d9 771=cut
772
fbcc39ad 773sub write {
774 my ( $self, $c, $buffer ) = @_;
4f5ebacd 775
02570318 776 unless ( $self->_prepared_write ) {
4f5ebacd 777 $self->prepare_write($c);
02570318 778 $self->_prepared_write(1);
fc7ec1d9 779 }
b0ad47c1 780
094a0974 781 return 0 if !defined $buffer;
b0ad47c1 782
44d28c7d 783 my $len = length($buffer);
784 $self->_writer->write($buffer);
b0ad47c1 785
44d28c7d 786 return $len;
fc7ec1d9 787}
788
933ba403 789=head2 $self->unescape_uri($uri)
790
6a44fe01 791Unescapes a given URI using the most efficient method available. Engines such
792as Apache may implement this using Apache's C-based modules, for example.
933ba403 793
794=cut
795
796sub unescape_uri {
8c7d83e1 797 my ( $self, $str ) = @_;
7d22a537 798
799 $str =~ s/(?:%([0-9A-Fa-f]{2})|\+)/defined $1 ? chr(hex($1)) : ' '/eg;
800
8c7d83e1 801 return $str;
933ba403 802}
34d28dfd 803
4ab87e27 804=head2 $self->finalize_output
805
806<obsolete>, see finalize_body
807
0c76ec45 808=head2 $self->env
809
810Hash containing enviroment variables including many special variables inserted
811by WWW server - like SERVER_*, REMOTE_*, HTTP_* ...
812
813Before accesing enviroment variables consider whether the same information is
814not directly available via Catalyst objects $c->request, $c->engine ...
815
816BEWARE: If you really need to access some enviroment variable from your Catalyst
817application you should use $c->engine->env->{VARNAME} instead of $ENV{VARNAME},
818as in some enviroments the %ENV hash does not contain what you would expect.
819
fbcc39ad 820=head1 AUTHORS
821
2f381252 822Catalyst Contributors, see Catalyst.pm
fc7ec1d9 823
824=head1 COPYRIGHT
825
536bee89 826This library is free software. You can redistribute it and/or modify it under
fc7ec1d9 827the same terms as Perl itself.
828
829=cut
830
8311;