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