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