remove regexp for good
[catagits/Catalyst-Runtime.git] / lib / Catalyst / Engine.pm
1 package Catalyst::Engine;
2
3 use Moose;
4 with 'MooseX::Emulate::Class::Accessor::Fast';
5
6 use CGI::Simple::Cookie;
7 use Data::Dump qw/dump/;
8 use Errno 'EWOULDBLOCK';
9 use HTML::Entities;
10 use HTTP::Body;
11 use HTTP::Headers;
12 use URI::QueryParam;
13 use Plack::Loader;
14 use Catalyst::EngineLoader;
15 use Encode ();
16 use Plack::Request::Upload;
17 use Hash::MultiValue;
18 use utf8;
19
20 use namespace::clean -except => 'meta';
21
22 # Amount of data to read from input on each pass
23 our $CHUNKSIZE = 64 * 1024;
24
25 # XXX - this is only here for compat, do not use!
26 has env => ( is => 'rw', writer => '_set_env' );
27 my $WARN_ABOUT_ENV = 0;
28 around env => sub {
29   my ($orig, $self, @args) = @_;
30   if(@args) {
31     warn "env as a writer is deprecated, you probably need to upgrade Catalyst::Engine::PSGI"
32       unless $WARN_ABOUT_ENV++;
33     return $self->_set_env(@args);
34   }
35   return $self->$orig;
36 };
37
38 # XXX - Only here for Engine::PSGI compat
39 sub prepare_connection {
40     my ($self, $ctx) = @_;
41     $ctx->request->prepare_connection;
42 }
43
44 =head1 NAME
45
46 Catalyst::Engine - The Catalyst Engine
47
48 =head1 SYNOPSIS
49
50 See L<Catalyst>.
51
52 =head1 DESCRIPTION
53
54 =head1 METHODS
55
56
57 =head2 $self->finalize_body($c)
58
59 Finalize body.  Prints the response output as blocking stream if it looks like
60 a filehandle, otherwise write it out all in one go.  If there is no body in
61 the response, we assume you are handling it 'manually', such as for nonblocking
62 style or asynchronous streaming responses.  You do this by calling L</write>
63 several times (which sends HTTP headers if needed) or you close over
64 C<< $response->write_fh >>.
65
66 See L<Catalyst::Response/write> and L<Catalyst::Response/write_fh> for more.
67
68 =cut
69
70 sub finalize_body {
71     my ( $self, $c ) = @_;
72     return if $c->response->_has_write_fh;
73
74     my $body = $c->response->body;
75     no warnings 'uninitialized';
76     if ( blessed($body) && $body->can('read') or ref($body) eq 'GLOB' ) {
77         my $got;
78         do {
79             $got = read $body, my ($buffer), $CHUNKSIZE;
80             $got = 0 unless $self->write( $c, $buffer );
81         } while $got > 0;
82
83         close $body;
84     }
85     else {
86         $self->write( $c, $body );
87     }
88
89     my $res = $c->response;
90     $res->_writer->close;
91     $res->_clear_writer;
92
93     return;
94 }
95
96 =head2 $self->finalize_cookies($c)
97
98 Create CGI::Simple::Cookie objects from $c->res->cookies, and set them as
99 response headers.
100
101 =cut
102
103 sub finalize_cookies {
104     my ( $self, $c ) = @_;
105
106     my @cookies;
107     my $response = $c->response;
108
109     foreach my $name (keys %{ $response->cookies }) {
110
111         my $val = $response->cookies->{$name};
112
113         my $cookie = (
114             blessed($val)
115             ? $val
116             : CGI::Simple::Cookie->new(
117                 -name    => $name,
118                 -value   => $val->{value},
119                 -expires => $val->{expires},
120                 -domain  => $val->{domain},
121                 -path    => $val->{path},
122                 -secure  => $val->{secure} || 0,
123                 -httponly => $val->{httponly} || 0,
124             )
125         );
126         if (!defined $cookie) {
127             $c->log->warn("undef passed in '$name' cookie value - not setting cookie")
128                 if $c->debug;
129             next;
130         }
131
132         push @cookies, $cookie->as_string;
133     }
134
135     for my $cookie (@cookies) {
136         $response->headers->push_header( 'Set-Cookie' => $cookie );
137     }
138 }
139
140 =head2 $self->finalize_error($c)
141
142 Output an appropriate error message. Called if there's an error in $c
143 after the dispatch has finished. Will output debug messages if Catalyst
144 is in debug mode, or a `please come back later` message otherwise.
145
146 =cut
147
148 sub _dump_error_page_element {
149     my ($self, $i, $element) = @_;
150     my ($name, $val)  = @{ $element };
151
152     # This is fugly, but the metaclass is _HUGE_ and demands waaay too much
153     # scrolling. Suggestions for more pleasant ways to do this welcome.
154     local $val->{'__MOP__'} = "Stringified: "
155         . $val->{'__MOP__'} if ref $val eq 'HASH' && exists $val->{'__MOP__'};
156
157     my $text = encode_entities( dump( $val ));
158     sprintf <<"EOF", $name, $text;
159 <h2><a href="#" onclick="toggleDump('dump_$i'); return false">%s</a></h2>
160 <div id="dump_$i">
161     <pre wrap="">%s</pre>
162 </div>
163 EOF
164 }
165
166 sub finalize_error {
167     my ( $self, $c ) = @_;
168
169     $c->res->content_type('text/html; charset=utf-8');
170     my $name = ref($c)->config->{name} || join(' ', split('::', ref $c));
171     
172     # Prevent Catalyst::Plugin::Unicode::Encoding from running.
173     # This is a little nasty, but it's the best way to be clean whether or
174     # not the user has an encoding plugin.
175
176     if ($c->can('encoding')) {
177       $c->{encoding} = '';
178     }
179
180     my ( $title, $error, $infos );
181     if ( $c->debug ) {
182
183         # For pretty dumps
184         $error = join '', map {
185                 '<p><code class="error">'
186               . encode_entities($_)
187               . '</code></p>'
188         } @{ $c->error };
189         $error ||= 'No output';
190         $error = qq{<pre wrap="">$error</pre>};
191         $title = $name = "$name on Catalyst $Catalyst::VERSION";
192         $name  = "<h1>$name</h1>";
193
194         # Don't show context in the dump
195         $c->res->_clear_context;
196
197         # Don't show body parser in the dump
198         $c->req->_clear_body;
199
200         my @infos;
201         my $i = 0;
202         for my $dump ( $c->dump_these ) {
203             push @infos, $self->_dump_error_page_element($i, $dump);
204             $i++;
205         }
206         $infos = join "\n", @infos;
207     }
208     else {
209         $title = $name;
210         $error = '';
211         $infos = <<"";
212 <pre>
213 (en) Please come back later
214 (fr) SVP veuillez revenir plus tard
215 (de) Bitte versuchen sie es spaeter nocheinmal
216 (at) Konnten's bitt'schoen spaeter nochmal reinschauen
217 (no) Vennligst prov igjen senere
218 (dk) Venligst prov igen senere
219 (pl) Prosze sprobowac pozniej
220 (pt) Por favor volte mais tarde
221 (ru) Попробуйте еще раз позже
222 (ua) Спробуйте ще раз пізніше
223 </pre>
224
225         $name = '';
226     }
227     $c->res->body( <<"" );
228 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
229     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
230 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
231 <head>
232     <meta http-equiv="Content-Language" content="en" />
233     <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
234     <title>$title</title>
235     <script type="text/javascript">
236         <!--
237         function toggleDump (dumpElement) {
238             var e = document.getElementById( dumpElement );
239             if (e.style.display == "none") {
240                 e.style.display = "";
241             }
242             else {
243                 e.style.display = "none";
244             }
245         }
246         -->
247     </script>
248     <style type="text/css">
249         body {
250             font-family: "Bitstream Vera Sans", "Trebuchet MS", Verdana,
251                          Tahoma, Arial, helvetica, sans-serif;
252             color: #333;
253             background-color: #eee;
254             margin: 0px;
255             padding: 0px;
256         }
257         :link, :link:hover, :visited, :visited:hover {
258             color: #000;
259         }
260         div.box {
261             position: relative;
262             background-color: #ccc;
263             border: 1px solid #aaa;
264             padding: 4px;
265             margin: 10px;
266         }
267         div.error {
268             background-color: #cce;
269             border: 1px solid #755;
270             padding: 8px;
271             margin: 4px;
272             margin-bottom: 10px;
273         }
274         div.infos {
275             background-color: #eee;
276             border: 1px solid #575;
277             padding: 8px;
278             margin: 4px;
279             margin-bottom: 10px;
280         }
281         div.name {
282             background-color: #cce;
283             border: 1px solid #557;
284             padding: 8px;
285             margin: 4px;
286         }
287         code.error {
288             display: block;
289             margin: 1em 0;
290             overflow: auto;
291         }
292         div.name h1, div.error p {
293             margin: 0;
294         }
295         h2 {
296             margin-top: 0;
297             margin-bottom: 10px;
298             font-size: medium;
299             font-weight: bold;
300             text-decoration: underline;
301         }
302         h1 {
303             font-size: medium;
304             font-weight: normal;
305         }
306         /* from http://users.tkk.fi/~tkarvine/linux/doc/pre-wrap/pre-wrap-css3-mozilla-opera-ie.html */
307         /* Browser specific (not valid) styles to make preformatted text wrap */
308         pre {
309             white-space: pre-wrap;       /* css-3 */
310             white-space: -moz-pre-wrap;  /* Mozilla, since 1999 */
311             white-space: -pre-wrap;      /* Opera 4-6 */
312             white-space: -o-pre-wrap;    /* Opera 7 */
313             word-wrap: break-word;       /* Internet Explorer 5.5+ */
314         }
315     </style>
316 </head>
317 <body>
318     <div class="box">
319         <div class="error">$error</div>
320         <div class="infos">$infos</div>
321         <div class="name">$name</div>
322     </div>
323 </body>
324 </html>
325
326     # Trick IE. Old versions of IE would display their own error page instead
327     # of ours if we'd give it less than 512 bytes.
328     $c->res->{body} .= ( ' ' x 512 );
329
330     $c->res->{body} = Encode::encode("UTF-8", $c->res->{body});
331
332     # Return 500
333     $c->res->status(500);
334 }
335
336 =head2 $self->finalize_headers($c)
337
338 Allows engines to write headers to response
339
340 =cut
341
342 sub finalize_headers {
343     my ($self, $ctx) = @_;
344
345     $ctx->finalize_headers unless $ctx->response->finalized_headers;
346     return;
347 }
348
349 =head2 $self->finalize_uploads($c)
350
351 Clean up after uploads, deleting temp files.
352
353 =cut
354
355 sub finalize_uploads {
356     my ( $self, $c ) = @_;
357
358     # N.B. This code is theoretically entirely unneeded due to ->cleanup(1)
359     #      on the HTTP::Body object.
360     my $request = $c->request;
361     foreach my $key (keys %{ $request->uploads }) {
362         my $upload = $request->uploads->{$key};
363         unlink grep { -e $_ } map { $_->tempname }
364           (ref $upload eq 'ARRAY' ? @{$upload} : ($upload));
365     }
366
367 }
368
369 =head2 $self->prepare_body($c)
370
371 sets up the L<Catalyst::Request> object body using L<HTTP::Body>
372
373 =cut
374
375 sub prepare_body {
376     my ( $self, $c ) = @_;
377
378     $c->request->prepare_body;
379 }
380
381 =head2 $self->prepare_body_chunk($c)
382
383 Add a chunk to the request body.
384
385 =cut
386
387 # XXX - Can this be deleted?
388 sub prepare_body_chunk {
389     my ( $self, $c, $chunk ) = @_;
390
391     $c->request->prepare_body_chunk($chunk);
392 }
393
394 =head2 $self->prepare_body_parameters($c)
395
396 Sets up parameters from body.
397
398 =cut
399
400 sub prepare_body_parameters {
401     my ( $self, $c ) = @_;
402
403     $c->request->prepare_body_parameters;
404 }
405
406 =head2 $self->prepare_parameters($c)
407
408 Sets up parameters from query and post parameters.
409 If parameters have already been set up will clear
410 existing parameters and set up again.
411
412 =cut
413
414 sub prepare_parameters {
415     my ( $self, $c ) = @_;
416
417     $c->request->_clear_parameters;
418     return $c->request->parameters;
419 }
420
421 =head2 $self->prepare_path($c)
422
423 abstract method, implemented by engines.
424
425 =cut
426
427 sub prepare_path {
428     my ($self, $ctx) = @_;
429
430     my $env = $ctx->request->env;
431
432     my $scheme    = $ctx->request->secure ? 'https' : 'http';
433     my $host      = $env->{HTTP_HOST} || $env->{SERVER_NAME};
434     my $port      = $env->{SERVER_PORT} || 80;
435     my $base_path = $env->{SCRIPT_NAME} || "/";
436
437     # set the request URI
438     my $path;
439     if (!$ctx->config->{use_request_uri_for_path}) {
440         my $path_info = $env->{PATH_INFO};
441         if ( exists $env->{REDIRECT_URL} ) {
442             $base_path = $env->{REDIRECT_URL};
443             $base_path =~ s/\Q$path_info\E$//;
444         }
445         $path = $base_path . $path_info;
446         $path =~ s{^/+}{};
447         $path =~ s/([^$URI::uric])/$URI::Escape::escapes{$1}/go;
448         $path =~ s/\?/%3F/g; # STUPID STUPID SPECIAL CASE
449     }
450     else {
451         my $req_uri = $env->{REQUEST_URI};
452         $req_uri =~ s/\?.*$//;
453         $path = $req_uri;
454         $path =~ s{^/+}{};
455     }
456
457     # Using URI directly is way too slow, so we construct the URLs manually
458     my $uri_class = "URI::$scheme";
459
460     # HTTP_HOST will include the port even if it's 80/443
461     $host =~ s/:(?:80|443)$//;
462
463     if ($port !~ /^(?:80|443)$/ && $host !~ /:/) {
464         $host .= ":$port";
465     }
466
467     my $query = $env->{QUERY_STRING} ? '?' . $env->{QUERY_STRING} : '';
468     my $uri   = $scheme . '://' . $host . '/' . $path . $query;
469
470     $ctx->request->uri( (bless \$uri, $uri_class)->canonical );
471
472     # set the base URI
473     # base must end in a slash
474     $base_path .= '/' unless $base_path =~ m{/$};
475
476     my $base_uri = $scheme . '://' . $host . $base_path;
477
478     $ctx->request->base( bless \$base_uri, $uri_class );
479
480     return;
481 }
482
483 =head2 $self->prepare_request($c)
484
485 =head2 $self->prepare_query_parameters($c)
486
487 process the query string and extract query parameters.
488
489 =cut
490
491 sub prepare_query_parameters {
492     my ($self, $c) = @_;
493     my $env = $c->request->env;
494
495     if(my $query_obj = $env->{'plack.request.query'}) {
496          $c->request->query_parameters(
497            $c->request->_use_hash_multivalue ?
498               $query_obj->clone :
499               $query_obj->as_hashref_mixed);
500          return;
501     }
502
503     my $query_string = exists $env->{QUERY_STRING}
504         ? $env->{QUERY_STRING}
505         : '';
506
507     # Check for keywords (no = signs)
508     # (yes, index() is faster than a regex :))
509     if ( index( $query_string, '=' ) < 0 ) {
510         $c->request->query_keywords($self->unescape_uri($query_string));
511         return;
512     }
513
514     my %query;
515
516     # replace semi-colons
517     $query_string =~ s/;/&/g;
518
519     my @params = grep { length $_ } split /&/, $query_string;
520
521     for my $item ( @params ) {
522
523         my ($param, $value)
524             = map { $self->unescape_uri($_) }
525               split( /=/, $item, 2 );
526
527         $param = $self->unescape_uri($item) unless defined $param;
528
529         if ( exists $query{$param} ) {
530             if ( ref $query{$param} ) {
531                 push @{ $query{$param} }, $value;
532             }
533             else {
534                 $query{$param} = [ $query{$param}, $value ];
535             }
536         }
537         else {
538             $query{$param} = $value;
539         }
540     }
541
542     $c->request->query_parameters( 
543       $c->request->_use_hash_multivalue ?
544         Hash::MultiValue->from_mixed(\%query) :
545         \%query);
546 }
547
548 =head2 $self->prepare_read($c)
549
550 Prepare to read by initializing the Content-Length from headers.
551
552 =cut
553
554 sub prepare_read {
555     my ( $self, $c ) = @_;
556
557     # Initialize the amount of data we think we need to read
558     $c->request->_read_length;
559 }
560
561 =head2 $self->prepare_request(@arguments)
562
563 Populate the context object from the request object.
564
565 =cut
566
567 sub prepare_request {
568     my ($self, $ctx, %args) = @_;
569     $ctx->log->psgienv($args{env}) if $ctx->log->can('psgienv');
570     $ctx->request->_set_env($args{env});
571     $self->_set_env($args{env}); # Nasty back compat!
572     $ctx->response->_set_response_cb($args{response_cb});
573 }
574
575 =head2 $self->prepare_uploads($c)
576
577 =cut
578
579 sub prepare_uploads {
580     my ( $self, $c ) = @_;
581
582     my $request = $c->request;
583     return unless $request->_body;
584
585     my $uploads = $request->_body->upload;
586     my $parameters = $request->parameters;
587     foreach my $name (keys %$uploads) {
588         my $files = $uploads->{$name};
589         my @uploads;
590         for my $upload (ref $files eq 'ARRAY' ? @$files : ($files)) {
591             my $headers = HTTP::Headers->new( %{ $upload->{headers} } );
592             my $u = Catalyst::Request::Upload->new
593               (
594                size => $upload->{size},
595                type => scalar $headers->content_type,
596                headers => $headers,
597                tempname => $upload->{tempname},
598                filename => $upload->{filename},
599               );
600             push @uploads, $u;
601         }
602         $request->uploads->{$name} = @uploads > 1 ? \@uploads : $uploads[0];
603
604         # support access to the filename as a normal param
605         my @filenames = map { $_->{filename} } @uploads;
606         # append, if there's already params with this name
607         if (exists $parameters->{$name}) {
608             if (ref $parameters->{$name} eq 'ARRAY') {
609                 push @{ $parameters->{$name} }, @filenames;
610             }
611             else {
612                 $parameters->{$name} = [ $parameters->{$name}, @filenames ];
613             }
614         }
615         else {
616             $parameters->{$name} = @filenames > 1 ? \@filenames : $filenames[0];
617         }
618     }
619 }
620
621 =head2 $self->write($c, $buffer)
622
623 Writes the buffer to the client.
624
625 =cut
626
627 sub write {
628     my ( $self, $c, $buffer ) = @_;
629
630     $c->response->write($buffer);
631 }
632
633 =head2 $self->read($c, [$maxlength])
634
635 Reads from the input stream by calling C<< $self->read_chunk >>.
636
637 Maintains the read_length and read_position counters as data is read.
638
639 =cut
640
641 sub read {
642     my ( $self, $c, $maxlength ) = @_;
643
644     $c->request->read($maxlength);
645 }
646
647 =head2 $self->read_chunk($c, \$buffer, $length)
648
649 Each engine implements read_chunk as its preferred way of reading a chunk
650 of data. Returns the number of bytes read. A return of 0 indicates that
651 there is no more data to be read.
652
653 =cut
654
655 sub read_chunk {
656     my ($self, $ctx) = (shift, shift);
657     return $ctx->request->read_chunk(@_);
658 }
659
660 =head2 $self->run($app, $server)
661
662 Start the engine. Builds a PSGI application and calls the
663 run method on the server passed in, which then causes the
664 engine to loop, handling requests..
665
666 =cut
667
668 sub run {
669     my ($self, $app, $psgi, @args) = @_;
670     # @args left here rather than just a $options, $server for back compat with the
671     # old style scripts which send a few args, then a hashref
672
673     # They should never actually be used in the normal case as the Plack engine is
674     # passed in got all the 'standard' args via the loader in the script already.
675
676     # FIXME - we should stash the options in an attribute so that custom args
677     # like Gitalist's --git_dir are possible to get from the app without stupid tricks.
678     my $server = pop @args if (scalar @args && blessed $args[-1]);
679     my $options = pop @args if (scalar @args && ref($args[-1]) eq 'HASH');
680     # Back compat hack for applications with old (non Catalyst::Script) scripts to work in FCGI.
681     if (scalar @args && !ref($args[0])) {
682         if (my $listen = shift @args) {
683             $options->{listen} ||= [$listen];
684         }
685     }
686     if (! $server ) {
687         $server = Catalyst::EngineLoader->new(application_name => ref($self))->auto(%$options);
688         # We're not being called from a script, so auto detect what backend to
689         # run on.  This should never happen, as mod_perl never calls ->run,
690         # instead the $app->handle method is called per request.
691         $app->log->warn("Not supplied a Plack engine, falling back to engine auto-loader (are your scripts ancient?)")
692     }
693     $app->run_options($options);
694     $server->run($psgi, $options);
695 }
696
697 =head2 build_psgi_app ($app, @args)
698
699 Builds and returns a PSGI application closure. (Raw, not wrapped in middleware)
700
701 =cut
702
703 sub build_psgi_app {
704     my ($self, $app, @args) = @_;
705
706     return sub {
707         my ($env) = @_;
708
709         return sub {
710             my ($respond) = @_;
711             confess("Did not get a response callback for writer, cannot continue") unless $respond;
712             $app->handle_request(env => $env, response_cb => $respond);
713         };
714     };
715 }
716
717 =head2 $self->unescape_uri($uri)
718
719 Unescapes a given URI using the most efficient method available.  Engines such
720 as Apache may implement this using Apache's C-based modules, for example.
721
722 =cut
723
724 sub unescape_uri {
725     my ( $self, $str ) = @_;
726
727     $str =~ s/(?:%([0-9A-Fa-f]{2})|\+)/defined $1 ? chr(hex($1)) : ' '/eg;
728
729     return $str;
730 }
731
732 =head2 $self->finalize_output
733
734 <obsolete>, see finalize_body
735
736 =head2 $self->env
737
738 Hash containing environment variables including many special variables inserted
739 by WWW server - like SERVER_*, REMOTE_*, HTTP_* ...
740
741 Before accessing environment variables consider whether the same information is
742 not directly available via Catalyst objects $c->request, $c->engine ...
743
744 BEWARE: If you really need to access some environment variable from your Catalyst
745 application you should use $c->engine->env->{VARNAME} instead of $ENV{VARNAME},
746 as in some environments the %ENV hash does not contain what you would expect.
747
748 =head1 AUTHORS
749
750 Catalyst Contributors, see Catalyst.pm
751
752 =head1 COPYRIGHT
753
754 This library is free software. You can redistribute it and/or modify it under
755 the same terms as Perl itself.
756
757 =cut
758
759 __PACKAGE__->meta->make_immutable;
760
761 1;