revert PathPrefix from trunk
[catagits/Catalyst-Runtime.git] / lib / Catalyst / Engine.pm
CommitLineData
fc7ec1d9 1package Catalyst::Engine;
2
3use strict;
fbcc39ad 4use base 'Class::Accessor::Fast';
fa32ac82 5use CGI::Simple::Cookie;
f63c03e4 6use Data::Dump qw/dump/;
fc7ec1d9 7use HTML::Entities;
fbcc39ad 8use HTTP::Body;
fc7ec1d9 9use HTTP::Headers;
933ba403 10use URI::Escape ();
e0616220 11use URI::QueryParam;
2832cb5d 12use Scalar::Util ();
fbcc39ad 13
14# input position and length
4f5ebacd 15__PACKAGE__->mk_accessors(qw/read_position read_length/);
fbcc39ad 16
17# Stringify to class
18use overload '""' => sub { return ref shift }, fallback => 1;
fc7ec1d9 19
4bd82c41 20# Amount of data to read from input on each pass
4bb8bd62 21our $CHUNKSIZE = 64 * 1024;
4bd82c41 22
0c1ba625 23# See if we can use libapreq2 for URI unescaping
24use constant HAS_APR => eval {
25 require APR::Request;
26};
27
fc7ec1d9 28=head1 NAME
29
30Catalyst::Engine - The Catalyst Engine
31
32=head1 SYNOPSIS
33
34See L<Catalyst>.
35
36=head1 DESCRIPTION
37
23f9d934 38=head1 METHODS
fc7ec1d9 39
cd3bb248 40
b5ecfcf0 41=head2 $self->finalize_body($c)
06e1b616 42
fbcc39ad 43Finalize body. Prints the response output.
06e1b616 44
45=cut
46
fbcc39ad 47sub finalize_body {
48 my ( $self, $c ) = @_;
7257e9db 49 my $body = $c->response->body;
f9b6d612 50 no warnings 'uninitialized';
51 if ( Scalar::Util::blessed($body) && $body->can('read') or ref($body) eq 'GLOB' ) {
7257e9db 52 while ( !eof $body ) {
4c423abf 53 read $body, my ($buffer), $CHUNKSIZE;
6484fba0 54 last unless $self->write( $c, $buffer );
f4a57de4 55 }
7257e9db 56 close $body;
f4a57de4 57 }
58 else {
7257e9db 59 $self->write( $c, $body );
f4a57de4 60 }
fbcc39ad 61}
6dc87a0f 62
b5ecfcf0 63=head2 $self->finalize_cookies($c)
6dc87a0f 64
fa32ac82 65Create CGI::Simple::Cookie objects from $c->res->cookies, and set them as
66response headers.
4ab87e27 67
6dc87a0f 68=cut
69
70sub finalize_cookies {
fbcc39ad 71 my ( $self, $c ) = @_;
6dc87a0f 72
fbcc39ad 73 my @cookies;
c82ed742 74
75 foreach my $name ( keys %{ $c->response->cookies } ) {
76
77 my $val = $c->response->cookies->{$name};
fbcc39ad 78
2832cb5d 79 my $cookie = (
80 Scalar::Util::blessed($val)
81 ? $val
82 : CGI::Simple::Cookie->new(
83 -name => $name,
84 -value => $val->{value},
85 -expires => $val->{expires},
86 -domain => $val->{domain},
87 -path => $val->{path},
88 -secure => $val->{secure} || 0
89 )
6dc87a0f 90 );
91
fbcc39ad 92 push @cookies, $cookie->as_string;
6dc87a0f 93 }
6dc87a0f 94
b39840da 95 for my $cookie (@cookies) {
96 $c->res->headers->push_header( 'Set-Cookie' => $cookie );
fbcc39ad 97 }
98}
969647fd 99
b5ecfcf0 100=head2 $self->finalize_error($c)
969647fd 101
4ab87e27 102Output an apropriate error message, called if there's an error in $c
103after the dispatch has finished. Will output debug messages if Catalyst
104is in debug mode, or a `please come back later` message otherwise.
105
969647fd 106=cut
107
108sub finalize_error {
fbcc39ad 109 my ( $self, $c ) = @_;
969647fd 110
7299a7b4 111 $c->res->content_type('text/html; charset=utf-8');
34d28dfd 112 my $name = $c->config->{name} || join(' ', split('::', ref $c));
969647fd 113
114 my ( $title, $error, $infos );
115 if ( $c->debug ) {
62d9b030 116
117 # For pretty dumps
b5ecfcf0 118 $error = join '', map {
119 '<p><code class="error">'
120 . encode_entities($_)
121 . '</code></p>'
122 } @{ $c->error };
969647fd 123 $error ||= 'No output';
2666dd3b 124 $error = qq{<pre wrap="">$error</pre>};
969647fd 125 $title = $name = "$name on Catalyst $Catalyst::VERSION";
d82cc9ae 126 $name = "<h1>$name</h1>";
fbcc39ad 127
128 # Don't show context in the dump
129 delete $c->req->{_context};
130 delete $c->res->{_context};
131
132 # Don't show body parser in the dump
133 delete $c->req->{_body};
134
135 # Don't show response header state in dump
136 delete $c->res->{_finalized_headers};
137
c6ef5e69 138 my @infos;
139 my $i = 0;
c6ef5e69 140 for my $dump ( $c->dump_these ) {
c6ef5e69 141 my $name = $dump->[0];
f63c03e4 142 my $value = encode_entities( dump( $dump->[1] ));
c6ef5e69 143 push @infos, sprintf <<"EOF", $name, $value;
9619f23c 144<h2><a href="#" onclick="toggleDump('dump_$i'); return false">%s</a></h2>
c6ef5e69 145<div id="dump_$i">
2666dd3b 146 <pre wrap="">%s</pre>
c6ef5e69 147</div>
148EOF
149 $i++;
150 }
151 $infos = join "\n", @infos;
969647fd 152 }
153 else {
154 $title = $name;
155 $error = '';
156 $infos = <<"";
157<pre>
158(en) Please come back later
0c2b4ac0 159(fr) SVP veuillez revenir plus tard
969647fd 160(de) Bitte versuchen sie es spaeter nocheinmal
d82cc9ae 161(at) Konnten's bitt'schoen spaeter nochmal reinschauen
969647fd 162(no) Vennligst prov igjen senere
d82cc9ae 163(dk) Venligst prov igen senere
164(pl) Prosze sprobowac pozniej
969647fd 165</pre>
166
167 $name = '';
168 }
e060fe05 169 $c->res->body( <<"" );
7299a7b4 170<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
171 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
172<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
969647fd 173<head>
7299a7b4 174 <meta http-equiv="Content-Language" content="en" />
175 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
969647fd 176 <title>$title</title>
7299a7b4 177 <script type="text/javascript">
c6ef5e69 178 <!--
179 function toggleDump (dumpElement) {
7299a7b4 180 var e = document.getElementById( dumpElement );
181 if (e.style.display == "none") {
182 e.style.display = "";
c6ef5e69 183 }
184 else {
7299a7b4 185 e.style.display = "none";
c6ef5e69 186 }
187 }
188 -->
189 </script>
969647fd 190 <style type="text/css">
191 body {
192 font-family: "Bitstream Vera Sans", "Trebuchet MS", Verdana,
193 Tahoma, Arial, helvetica, sans-serif;
34d28dfd 194 color: #333;
969647fd 195 background-color: #eee;
196 margin: 0px;
197 padding: 0px;
198 }
c6ef5e69 199 :link, :link:hover, :visited, :visited:hover {
34d28dfd 200 color: #000;
c6ef5e69 201 }
969647fd 202 div.box {
9619f23c 203 position: relative;
969647fd 204 background-color: #ccc;
205 border: 1px solid #aaa;
206 padding: 4px;
207 margin: 10px;
969647fd 208 }
209 div.error {
34d28dfd 210 background-color: #cce;
969647fd 211 border: 1px solid #755;
212 padding: 8px;
213 margin: 4px;
214 margin-bottom: 10px;
969647fd 215 }
216 div.infos {
34d28dfd 217 background-color: #eee;
969647fd 218 border: 1px solid #575;
219 padding: 8px;
220 margin: 4px;
221 margin-bottom: 10px;
969647fd 222 }
223 div.name {
34d28dfd 224 background-color: #cce;
969647fd 225 border: 1px solid #557;
226 padding: 8px;
227 margin: 4px;
969647fd 228 }
7f8e0078 229 code.error {
230 display: block;
231 margin: 1em 0;
232 overflow: auto;
7f8e0078 233 }
9619f23c 234 div.name h1, div.error p {
235 margin: 0;
236 }
237 h2 {
238 margin-top: 0;
239 margin-bottom: 10px;
240 font-size: medium;
241 font-weight: bold;
242 text-decoration: underline;
243 }
244 h1 {
245 font-size: medium;
246 font-weight: normal;
247 }
2666dd3b 248 /* from http://users.tkk.fi/~tkarvine/linux/doc/pre-wrap/pre-wrap-css3-mozilla-opera-ie.html */
249 /* Browser specific (not valid) styles to make preformatted text wrap */
250 pre {
251 white-space: pre-wrap; /* css-3 */
252 white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
253 white-space: -pre-wrap; /* Opera 4-6 */
254 white-space: -o-pre-wrap; /* Opera 7 */
255 word-wrap: break-word; /* Internet Explorer 5.5+ */
256 }
969647fd 257 </style>
258</head>
259<body>
260 <div class="box">
261 <div class="error">$error</div>
262 <div class="infos">$infos</div>
263 <div class="name">$name</div>
264 </div>
265</body>
266</html>
267
d82cc9ae 268
269 # Trick IE
270 $c->res->{body} .= ( ' ' x 512 );
271
272 # Return 500
33117422 273 $c->res->status(500);
969647fd 274}
275
b5ecfcf0 276=head2 $self->finalize_headers($c)
fc7ec1d9 277
4ab87e27 278Abstract method, allows engines to write headers to response
279
fc7ec1d9 280=cut
281
282sub finalize_headers { }
283
b5ecfcf0 284=head2 $self->finalize_read($c)
fc7ec1d9 285
286=cut
287
fbcc39ad 288sub finalize_read {
289 my ( $self, $c ) = @_;
4f5ebacd 290
fbcc39ad 291 undef $self->{_prepared_read};
fc7ec1d9 292}
293
b5ecfcf0 294=head2 $self->finalize_uploads($c)
fc7ec1d9 295
4ab87e27 296Clean up after uploads, deleting temp files.
297
fc7ec1d9 298=cut
299
fbcc39ad 300sub finalize_uploads {
301 my ( $self, $c ) = @_;
99fe1710 302
fbcc39ad 303 if ( keys %{ $c->request->uploads } ) {
304 for my $key ( keys %{ $c->request->uploads } ) {
305 my $upload = $c->request->uploads->{$key};
306 unlink map { $_->tempname }
307 grep { -e $_->tempname }
308 ref $upload eq 'ARRAY' ? @{$upload} : ($upload);
c85ff642 309 }
c85ff642 310 }
fc7ec1d9 311}
312
b5ecfcf0 313=head2 $self->prepare_body($c)
fc7ec1d9 314
4ab87e27 315sets up the L<Catalyst::Request> object body using L<HTTP::Body>
316
fc7ec1d9 317=cut
318
fbcc39ad 319sub prepare_body {
320 my ( $self, $c ) = @_;
847e3257 321
322 my $length = $c->request->header('Content-Length') || 0;
99fe1710 323
847e3257 324 $self->read_length( $length );
99fe1710 325
847e3257 326 if ( $length > 0 ) {
327 unless ( $c->request->{_body} ) {
328 my $type = $c->request->header('Content-Type');
329 $c->request->{_body} = HTTP::Body->new( $type, $length );
330 $c->request->{_body}->{tmpdir} = $c->config->{uploadtmp}
331 if exists $c->config->{uploadtmp};
332 }
333
4f5ebacd 334 while ( my $buffer = $self->read($c) ) {
335 $c->prepare_body_chunk($buffer);
fbcc39ad 336 }
fdb3773e 337
338 # paranoia against wrong Content-Length header
847e3257 339 my $remaining = $length - $self->read_position;
34d28dfd 340 if ( $remaining > 0 ) {
fdb3773e 341 $self->finalize_read($c);
34d28dfd 342 Catalyst::Exception->throw(
847e3257 343 "Wrong Content-Length value: $length" );
fdb3773e 344 }
fc7ec1d9 345 }
847e3257 346 else {
347 # Defined but will cause all body code to be skipped
348 $c->request->{_body} = 0;
349 }
fc7ec1d9 350}
351
b5ecfcf0 352=head2 $self->prepare_body_chunk($c)
4bd82c41 353
4ab87e27 354Add a chunk to the request body.
355
4bd82c41 356=cut
357
358sub prepare_body_chunk {
359 my ( $self, $c, $chunk ) = @_;
4f5ebacd 360
361 $c->request->{_body}->add($chunk);
4bd82c41 362}
363
b5ecfcf0 364=head2 $self->prepare_body_parameters($c)
06e1b616 365
4ab87e27 366Sets up parameters from body.
367
06e1b616 368=cut
369
fbcc39ad 370sub prepare_body_parameters {
371 my ( $self, $c ) = @_;
847e3257 372
373 return unless $c->request->{_body};
374
fbcc39ad 375 $c->request->body_parameters( $c->request->{_body}->param );
376}
0556eb49 377
b5ecfcf0 378=head2 $self->prepare_connection($c)
0556eb49 379
4ab87e27 380Abstract method implemented in engines.
381
0556eb49 382=cut
383
384sub prepare_connection { }
385
b5ecfcf0 386=head2 $self->prepare_cookies($c)
fc7ec1d9 387
fa32ac82 388Parse cookies from header. Sets a L<CGI::Simple::Cookie> object.
4ab87e27 389
fc7ec1d9 390=cut
391
6dc87a0f 392sub prepare_cookies {
fbcc39ad 393 my ( $self, $c ) = @_;
6dc87a0f 394
395 if ( my $header = $c->request->header('Cookie') ) {
fa32ac82 396 $c->req->cookies( { CGI::Simple::Cookie->parse($header) } );
6dc87a0f 397 }
398}
fc7ec1d9 399
b5ecfcf0 400=head2 $self->prepare_headers($c)
fc7ec1d9 401
402=cut
403
404sub prepare_headers { }
405
b5ecfcf0 406=head2 $self->prepare_parameters($c)
fc7ec1d9 407
4ab87e27 408sets up parameters from query and post parameters.
409
fc7ec1d9 410=cut
411
fbcc39ad 412sub prepare_parameters {
413 my ( $self, $c ) = @_;
fc7ec1d9 414
fbcc39ad 415 # We copy, no references
c82ed742 416 foreach my $name ( keys %{ $c->request->query_parameters } ) {
417 my $param = $c->request->query_parameters->{$name};
fbcc39ad 418 $param = ref $param eq 'ARRAY' ? [ @{$param} ] : $param;
419 $c->request->parameters->{$name} = $param;
420 }
fc7ec1d9 421
fbcc39ad 422 # Merge query and body parameters
c82ed742 423 foreach my $name ( keys %{ $c->request->body_parameters } ) {
424 my $param = $c->request->body_parameters->{$name};
fbcc39ad 425 $param = ref $param eq 'ARRAY' ? [ @{$param} ] : $param;
426 if ( my $old_param = $c->request->parameters->{$name} ) {
427 if ( ref $old_param eq 'ARRAY' ) {
428 push @{ $c->request->parameters->{$name} },
429 ref $param eq 'ARRAY' ? @$param : $param;
430 }
431 else { $c->request->parameters->{$name} = [ $old_param, $param ] }
432 }
433 else { $c->request->parameters->{$name} = $param }
434 }
435}
436
b5ecfcf0 437=head2 $self->prepare_path($c)
fc7ec1d9 438
4ab87e27 439abstract method, implemented by engines.
440
fc7ec1d9 441=cut
442
443sub prepare_path { }
444
b5ecfcf0 445=head2 $self->prepare_request($c)
fc7ec1d9 446
b5ecfcf0 447=head2 $self->prepare_query_parameters($c)
fc7ec1d9 448
4ab87e27 449process the query string and extract query parameters.
450
fc7ec1d9 451=cut
452
e0616220 453sub prepare_query_parameters {
454 my ( $self, $c, $query_string ) = @_;
933ba403 455
456 # Check for keywords (no = signs)
457 if ( index( $query_string, '=' ) < 0 ) {
458 $c->request->keywords( $self->unescape_uri($query_string) );
459 return;
460 }
461
462 my %query;
e0616220 463
464 # replace semi-colons
465 $query_string =~ s/;/&/g;
933ba403 466
467 my @params = split /&/, $query_string;
e0616220 468
933ba403 469 for my $item ( @params ) {
470
471 my ($param, $value)
472 = map { $self->unescape_uri($_) }
473 split( /=/, $item );
474
475 $param = $self->unescape_uri($item) unless defined $param;
476
477 if ( exists $query{$param} ) {
478 if ( ref $query{$param} ) {
479 push @{ $query{$param} }, $value;
480 }
481 else {
482 $query{$param} = [ $query{$param}, $value ];
483 }
484 }
485 else {
486 $query{$param} = $value;
487 }
e0616220 488 }
933ba403 489
490 $c->request->query_parameters( \%query );
e0616220 491}
fbcc39ad 492
b5ecfcf0 493=head2 $self->prepare_read($c)
fbcc39ad 494
4ab87e27 495prepare to read from the engine.
496
fbcc39ad 497=cut
fc7ec1d9 498
fbcc39ad 499sub prepare_read {
500 my ( $self, $c ) = @_;
4f5ebacd 501
fbcc39ad 502 # Reset the read position
4f5ebacd 503 $self->read_position(0);
fbcc39ad 504}
fc7ec1d9 505
b5ecfcf0 506=head2 $self->prepare_request(@arguments)
fc7ec1d9 507
4ab87e27 508Populate the context object from the request object.
509
fc7ec1d9 510=cut
511
fbcc39ad 512sub prepare_request { }
fc7ec1d9 513
b5ecfcf0 514=head2 $self->prepare_uploads($c)
c9afa5fc 515
fbcc39ad 516=cut
517
518sub prepare_uploads {
519 my ( $self, $c ) = @_;
847e3257 520
521 return unless $c->request->{_body};
522
fbcc39ad 523 my $uploads = $c->request->{_body}->upload;
524 for my $name ( keys %$uploads ) {
525 my $files = $uploads->{$name};
526 $files = ref $files eq 'ARRAY' ? $files : [$files];
527 my @uploads;
528 for my $upload (@$files) {
529 my $u = Catalyst::Request::Upload->new;
530 $u->headers( HTTP::Headers->new( %{ $upload->{headers} } ) );
531 $u->type( $u->headers->content_type );
532 $u->tempname( $upload->{tempname} );
533 $u->size( $upload->{size} );
534 $u->filename( $upload->{filename} );
535 push @uploads, $u;
536 }
537 $c->request->uploads->{$name} = @uploads > 1 ? \@uploads : $uploads[0];
f4a57de4 538
c4bed79a 539 # support access to the filename as a normal param
540 my @filenames = map { $_->{filename} } @uploads;
a7e05d9d 541 # append, if there's already params with this name
542 if (exists $c->request->parameters->{$name}) {
543 if (ref $c->request->parameters->{$name} eq 'ARRAY') {
544 push @{ $c->request->parameters->{$name} }, @filenames;
545 }
546 else {
547 $c->request->parameters->{$name} =
548 [ $c->request->parameters->{$name}, @filenames ];
549 }
550 }
551 else {
552 $c->request->parameters->{$name} =
553 @filenames > 1 ? \@filenames : $filenames[0];
554 }
fbcc39ad 555 }
556}
557
b5ecfcf0 558=head2 $self->prepare_write($c)
c9afa5fc 559
4ab87e27 560Abstract method. Implemented by the engines.
561
c9afa5fc 562=cut
563
fbcc39ad 564sub prepare_write { }
565
b5ecfcf0 566=head2 $self->read($c, [$maxlength])
fbcc39ad 567
568=cut
569
570sub read {
571 my ( $self, $c, $maxlength ) = @_;
4f5ebacd 572
fbcc39ad 573 unless ( $self->{_prepared_read} ) {
4f5ebacd 574 $self->prepare_read($c);
fbcc39ad 575 $self->{_prepared_read} = 1;
576 }
4f5ebacd 577
fbcc39ad 578 my $remaining = $self->read_length - $self->read_position;
4bd82c41 579 $maxlength ||= $CHUNKSIZE;
4f5ebacd 580
fbcc39ad 581 # Are we done reading?
582 if ( $remaining <= 0 ) {
4f5ebacd 583 $self->finalize_read($c);
fbcc39ad 584 return;
585 }
c9afa5fc 586
fbcc39ad 587 my $readlen = ( $remaining > $maxlength ) ? $maxlength : $remaining;
588 my $rc = $self->read_chunk( $c, my $buffer, $readlen );
589 if ( defined $rc ) {
590 $self->read_position( $self->read_position + $rc );
591 return $buffer;
592 }
593 else {
4f5ebacd 594 Catalyst::Exception->throw(
595 message => "Unknown error reading input: $!" );
fbcc39ad 596 }
597}
fc7ec1d9 598
b5ecfcf0 599=head2 $self->read_chunk($c, $buffer, $length)
23f9d934 600
fbcc39ad 601Each engine inplements read_chunk as its preferred way of reading a chunk
602of data.
fc7ec1d9 603
fbcc39ad 604=cut
61b1e958 605
fbcc39ad 606sub read_chunk { }
61b1e958 607
b5ecfcf0 608=head2 $self->read_length
ca39d576 609
fbcc39ad 610The length of input data to be read. This is obtained from the Content-Length
611header.
fc7ec1d9 612
b5ecfcf0 613=head2 $self->read_position
fc7ec1d9 614
fbcc39ad 615The amount of input data that has already been read.
63b763c5 616
b5ecfcf0 617=head2 $self->run($c)
63b763c5 618
4ab87e27 619Start the engine. Implemented by the various engine classes.
620
fbcc39ad 621=cut
fc7ec1d9 622
fbcc39ad 623sub run { }
fc7ec1d9 624
b5ecfcf0 625=head2 $self->write($c, $buffer)
fc7ec1d9 626
4ab87e27 627Writes the buffer to the client. Can only be called once for a request.
628
fc7ec1d9 629=cut
630
fbcc39ad 631sub write {
632 my ( $self, $c, $buffer ) = @_;
4f5ebacd 633
fbcc39ad 634 unless ( $self->{_prepared_write} ) {
4f5ebacd 635 $self->prepare_write($c);
fbcc39ad 636 $self->{_prepared_write} = 1;
fc7ec1d9 637 }
4f5ebacd 638
639 print STDOUT $buffer;
fc7ec1d9 640}
641
933ba403 642=head2 $self->unescape_uri($uri)
643
0c1ba625 644Unescapes a given URI using the most efficient method available. Engines
645can subclass to provide faster implementations.
933ba403 646
647=cut
648
649sub unescape_uri {
650 my $self = shift;
651
0c1ba625 652 if ( HAS_APR ) {
0bf2648c 653 # This function is ~12x faster than URI::Escape
0c1ba625 654 return APR::Request::decode(@_);
655 }
656
933ba403 657 my $e = URI::Escape::uri_unescape(@_);
658 $e =~ s/\+/ /g;
659
660 return $e;
661}
34d28dfd 662
4ab87e27 663=head2 $self->finalize_output
664
665<obsolete>, see finalize_body
666
fbcc39ad 667=head1 AUTHORS
668
669Sebastian Riedel, <sri@cpan.org>
fc7ec1d9 670
fbcc39ad 671Andy Grundman, <andy@hybridized.org>
fc7ec1d9 672
673=head1 COPYRIGHT
674
675This program is free software, you can redistribute it and/or modify it under
676the same terms as Perl itself.
677
678=cut
679
6801;