Add built local::lib
[catagits/Gitalist.git] / local-lib5 / lib / perl5 / Catalyst / Engine.pm
CommitLineData
3fea05b9 1package Catalyst::Engine;
2
3use Moose;
4with 'MooseX::Emulate::Class::Accessor::Fast';
5
6use CGI::Simple::Cookie;
7use Data::Dump qw/dump/;
8use Errno 'EWOULDBLOCK';
9use HTML::Entities;
10use HTTP::Body;
11use HTTP::Headers;
12use URI::QueryParam;
13
14use namespace::clean -except => 'meta';
15
16has env => (is => 'rw');
17
18# input position and length
19has read_length => (is => 'rw');
20has read_position => (is => 'rw');
21
22has _prepared_write => (is => 'rw');
23
24# Amount of data to read from input on each pass
25our $CHUNKSIZE = 64 * 1024;
26
27=head1 NAME
28
29Catalyst::Engine - The Catalyst Engine
30
31=head1 SYNOPSIS
32
33See L<Catalyst>.
34
35=head1 DESCRIPTION
36
37=head1 METHODS
38
39
40=head2 $self->finalize_body($c)
41
42Finalize body. Prints the response output.
43
44=cut
45
46sub finalize_body {
47 my ( $self, $c ) = @_;
48 my $body = $c->response->body;
49 no warnings 'uninitialized';
50 if ( blessed($body) && $body->can('read') or ref($body) eq 'GLOB' ) {
51 my $got;
52 do {
53 $got = read $body, my ($buffer), $CHUNKSIZE;
54 $got = 0 unless $self->write( $c, $buffer );
55 } while $got > 0;
56
57 close $body;
58 }
59 else {
60 $self->write( $c, $body );
61 }
62}
63
64=head2 $self->finalize_cookies($c)
65
66Create CGI::Simple::Cookie objects from $c->res->cookies, and set them as
67response headers.
68
69=cut
70
71sub finalize_cookies {
72 my ( $self, $c ) = @_;
73
74 my @cookies;
75 my $response = $c->response;
76
77 foreach my $name (keys %{ $response->cookies }) {
78
79 my $val = $response->cookies->{$name};
80
81 my $cookie = (
82 blessed($val)
83 ? $val
84 : CGI::Simple::Cookie->new(
85 -name => $name,
86 -value => $val->{value},
87 -expires => $val->{expires},
88 -domain => $val->{domain},
89 -path => $val->{path},
90 -secure => $val->{secure} || 0,
91 -httponly => $val->{httponly} || 0,
92 )
93 );
94
95 push @cookies, $cookie->as_string;
96 }
97
98 for my $cookie (@cookies) {
99 $response->headers->push_header( 'Set-Cookie' => $cookie );
100 }
101}
102
103=head2 $self->finalize_error($c)
104
105Output an appropriate error message. Called if there's an error in $c
106after the dispatch has finished. Will output debug messages if Catalyst
107is in debug mode, or a `please come back later` message otherwise.
108
109=cut
110
111sub finalize_error {
112 my ( $self, $c ) = @_;
113
114 $c->res->content_type('text/html; charset=utf-8');
115 my $name = ref($c)->config->{name} || join(' ', split('::', ref $c));
116
117 my ( $title, $error, $infos );
118 if ( $c->debug ) {
119
120 # For pretty dumps
121 $error = join '', map {
122 '<p><code class="error">'
123 . encode_entities($_)
124 . '</code></p>'
125 } @{ $c->error };
126 $error ||= 'No output';
127 $error = qq{<pre wrap="">$error</pre>};
128 $title = $name = "$name on Catalyst $Catalyst::VERSION";
129 $name = "<h1>$name</h1>";
130
131 # Don't show context in the dump
132 $c->req->_clear_context;
133 $c->res->_clear_context;
134
135 # Don't show body parser in the dump
136 $c->req->_clear_body;
137
138 my @infos;
139 my $i = 0;
140 for my $dump ( $c->dump_these ) {
141 my $name = $dump->[0];
142 my $value = encode_entities( dump( $dump->[1] ));
143 push @infos, sprintf <<"EOF", $name, $value;
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 $i++;
150 }
151 $infos = join "\n", @infos;
152 }
153 else {
154 $title = $name;
155 $error = '';
156 $infos = <<"";
157<pre>
158(en) Please come back later
159(fr) SVP veuillez revenir plus tard
160(de) Bitte versuchen sie es spaeter nocheinmal
161(at) Konnten's bitt'schoen spaeter nochmal reinschauen
162(no) Vennligst prov igjen senere
163(dk) Venligst prov igen senere
164(pl) Prosze sprobowac pozniej
165(pt) Por favor volte mais tarde
166(ru) Попробуйте еще раз позже
167(ua) Спробуйте ще раз пізніше
168</pre>
169
170 $name = '';
171 }
172 $c->res->body( <<"" );
173<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
174 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
175<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
176<head>
177 <meta http-equiv="Content-Language" content="en" />
178 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
179 <title>$title</title>
180 <script type="text/javascript">
181 <!--
182 function toggleDump (dumpElement) {
183 var e = document.getElementById( dumpElement );
184 if (e.style.display == "none") {
185 e.style.display = "";
186 }
187 else {
188 e.style.display = "none";
189 }
190 }
191 -->
192 </script>
193 <style type="text/css">
194 body {
195 font-family: "Bitstream Vera Sans", "Trebuchet MS", Verdana,
196 Tahoma, Arial, helvetica, sans-serif;
197 color: #333;
198 background-color: #eee;
199 margin: 0px;
200 padding: 0px;
201 }
202 :link, :link:hover, :visited, :visited:hover {
203 color: #000;
204 }
205 div.box {
206 position: relative;
207 background-color: #ccc;
208 border: 1px solid #aaa;
209 padding: 4px;
210 margin: 10px;
211 }
212 div.error {
213 background-color: #cce;
214 border: 1px solid #755;
215 padding: 8px;
216 margin: 4px;
217 margin-bottom: 10px;
218 }
219 div.infos {
220 background-color: #eee;
221 border: 1px solid #575;
222 padding: 8px;
223 margin: 4px;
224 margin-bottom: 10px;
225 }
226 div.name {
227 background-color: #cce;
228 border: 1px solid #557;
229 padding: 8px;
230 margin: 4px;
231 }
232 code.error {
233 display: block;
234 margin: 1em 0;
235 overflow: auto;
236 }
237 div.name h1, div.error p {
238 margin: 0;
239 }
240 h2 {
241 margin-top: 0;
242 margin-bottom: 10px;
243 font-size: medium;
244 font-weight: bold;
245 text-decoration: underline;
246 }
247 h1 {
248 font-size: medium;
249 font-weight: normal;
250 }
251 /* from http://users.tkk.fi/~tkarvine/linux/doc/pre-wrap/pre-wrap-css3-mozilla-opera-ie.html */
252 /* Browser specific (not valid) styles to make preformatted text wrap */
253 pre {
254 white-space: pre-wrap; /* css-3 */
255 white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
256 white-space: -pre-wrap; /* Opera 4-6 */
257 white-space: -o-pre-wrap; /* Opera 7 */
258 word-wrap: break-word; /* Internet Explorer 5.5+ */
259 }
260 </style>
261</head>
262<body>
263 <div class="box">
264 <div class="error">$error</div>
265 <div class="infos">$infos</div>
266 <div class="name">$name</div>
267 </div>
268</body>
269</html>
270
271
272 # Trick IE
273 $c->res->{body} .= ( ' ' x 512 );
274
275 # Return 500
276 $c->res->status(500);
277}
278
279=head2 $self->finalize_headers($c)
280
281Abstract method, allows engines to write headers to response
282
283=cut
284
285sub finalize_headers { }
286
287=head2 $self->finalize_read($c)
288
289=cut
290
291sub finalize_read { }
292
293=head2 $self->finalize_uploads($c)
294
295Clean up after uploads, deleting temp files.
296
297=cut
298
299sub finalize_uploads {
300 my ( $self, $c ) = @_;
301
302 my $request = $c->request;
303 foreach my $key (keys %{ $request->uploads }) {
304 my $upload = $request->uploads->{$key};
305 unlink grep { -e $_ } map { $_->tempname }
306 (ref $upload eq 'ARRAY' ? @{$upload} : ($upload));
307 }
308
309}
310
311=head2 $self->prepare_body($c)
312
313sets up the L<Catalyst::Request> object body using L<HTTP::Body>
314
315=cut
316
317sub prepare_body {
318 my ( $self, $c ) = @_;
319
320 my $appclass = ref($c) || $c;
321 if ( my $length = $self->read_length ) {
322 my $request = $c->request;
323 unless ( $request->_body ) {
324 my $type = $request->header('Content-Type');
325 $request->_body(HTTP::Body->new( $type, $length ));
326 $request->_body->tmpdir( $appclass->config->{uploadtmp} )
327 if exists $appclass->config->{uploadtmp};
328 }
329
330 # Check for definedness as you could read '0'
331 while ( defined ( my $buffer = $self->read($c) ) ) {
332 $c->prepare_body_chunk($buffer);
333 }
334
335 # paranoia against wrong Content-Length header
336 my $remaining = $length - $self->read_position;
337 if ( $remaining > 0 ) {
338 $self->finalize_read($c);
339 Catalyst::Exception->throw(
340 "Wrong Content-Length value: $length" );
341 }
342 }
343 else {
344 # Defined but will cause all body code to be skipped
345 $c->request->_body(0);
346 }
347}
348
349=head2 $self->prepare_body_chunk($c)
350
351Add a chunk to the request body.
352
353=cut
354
355sub prepare_body_chunk {
356 my ( $self, $c, $chunk ) = @_;
357
358 $c->request->_body->add($chunk);
359}
360
361=head2 $self->prepare_body_parameters($c)
362
363Sets up parameters from body.
364
365=cut
366
367sub prepare_body_parameters {
368 my ( $self, $c ) = @_;
369
370 return unless $c->request->_body;
371
372 $c->request->body_parameters( $c->request->_body->param );
373}
374
375=head2 $self->prepare_connection($c)
376
377Abstract method implemented in engines.
378
379=cut
380
381sub prepare_connection { }
382
383=head2 $self->prepare_cookies($c)
384
385Parse cookies from header. Sets a L<CGI::Simple::Cookie> object.
386
387=cut
388
389sub prepare_cookies {
390 my ( $self, $c ) = @_;
391
392 if ( my $header = $c->request->header('Cookie') ) {
393 $c->req->cookies( { CGI::Simple::Cookie->parse($header) } );
394 }
395}
396
397=head2 $self->prepare_headers($c)
398
399=cut
400
401sub prepare_headers { }
402
403=head2 $self->prepare_parameters($c)
404
405sets up parameters from query and post parameters.
406
407=cut
408
409sub prepare_parameters {
410 my ( $self, $c ) = @_;
411
412 my $request = $c->request;
413 my $parameters = $request->parameters;
414 my $body_parameters = $request->body_parameters;
415 my $query_parameters = $request->query_parameters;
416 # We copy, no references
417 foreach my $name (keys %$query_parameters) {
418 my $param = $query_parameters->{$name};
419 $parameters->{$name} = ref $param eq 'ARRAY' ? [ @$param ] : $param;
420 }
421
422 # Merge query and body parameters
423 foreach my $name (keys %$body_parameters) {
424 my $param = $body_parameters->{$name};
425 my @values = ref $param eq 'ARRAY' ? @$param : ($param);
426 if ( my $existing = $parameters->{$name} ) {
427 unshift(@values, (ref $existing eq 'ARRAY' ? @$existing : $existing));
428 }
429 $parameters->{$name} = @values > 1 ? \@values : $values[0];
430 }
431}
432
433=head2 $self->prepare_path($c)
434
435abstract method, implemented by engines.
436
437=cut
438
439sub prepare_path { }
440
441=head2 $self->prepare_request($c)
442
443=head2 $self->prepare_query_parameters($c)
444
445process the query string and extract query parameters.
446
447=cut
448
449sub prepare_query_parameters {
450 my ( $self, $c, $query_string ) = @_;
451
452 # Check for keywords (no = signs)
453 # (yes, index() is faster than a regex :))
454 if ( index( $query_string, '=' ) < 0 ) {
455 $c->request->query_keywords( $self->unescape_uri($query_string) );
456 return;
457 }
458
459 my %query;
460
461 # replace semi-colons
462 $query_string =~ s/;/&/g;
463
464 my @params = grep { length $_ } split /&/, $query_string;
465
466 for my $item ( @params ) {
467
468 my ($param, $value)
469 = map { $self->unescape_uri($_) }
470 split( /=/, $item, 2 );
471
472 $param = $self->unescape_uri($item) unless defined $param;
473
474 if ( exists $query{$param} ) {
475 if ( ref $query{$param} ) {
476 push @{ $query{$param} }, $value;
477 }
478 else {
479 $query{$param} = [ $query{$param}, $value ];
480 }
481 }
482 else {
483 $query{$param} = $value;
484 }
485 }
486
487 $c->request->query_parameters( \%query );
488}
489
490=head2 $self->prepare_read($c)
491
492prepare to read from the engine.
493
494=cut
495
496sub prepare_read {
497 my ( $self, $c ) = @_;
498
499 # Initialize the read position
500 $self->read_position(0);
501
502 # Initialize the amount of data we think we need to read
503 $self->read_length( $c->request->header('Content-Length') || 0 );
504}
505
506=head2 $self->prepare_request(@arguments)
507
508Populate the context object from the request object.
509
510=cut
511
512sub prepare_request { }
513
514=head2 $self->prepare_uploads($c)
515
516=cut
517
518sub prepare_uploads {
519 my ( $self, $c ) = @_;
520
521 my $request = $c->request;
522 return unless $request->_body;
523
524 my $uploads = $request->_body->upload;
525 my $parameters = $request->parameters;
526 foreach my $name (keys %$uploads) {
527 my $files = $uploads->{$name};
528 my @uploads;
529 for my $upload (ref $files eq 'ARRAY' ? @$files : ($files)) {
530 my $headers = HTTP::Headers->new( %{ $upload->{headers} } );
531 my $u = Catalyst::Request::Upload->new
532 (
533 size => $upload->{size},
534 type => $headers->content_type,
535 headers => $headers,
536 tempname => $upload->{tempname},
537 filename => $upload->{filename},
538 );
539 push @uploads, $u;
540 }
541 $request->uploads->{$name} = @uploads > 1 ? \@uploads : $uploads[0];
542
543 # support access to the filename as a normal param
544 my @filenames = map { $_->{filename} } @uploads;
545 # append, if there's already params with this name
546 if (exists $parameters->{$name}) {
547 if (ref $parameters->{$name} eq 'ARRAY') {
548 push @{ $parameters->{$name} }, @filenames;
549 }
550 else {
551 $parameters->{$name} = [ $parameters->{$name}, @filenames ];
552 }
553 }
554 else {
555 $parameters->{$name} = @filenames > 1 ? \@filenames : $filenames[0];
556 }
557 }
558}
559
560=head2 $self->prepare_write($c)
561
562Abstract method. Implemented by the engines.
563
564=cut
565
566sub prepare_write { }
567
568=head2 $self->read($c, [$maxlength])
569
570Reads from the input stream by calling C<< $self->read_chunk >>.
571
572Maintains the read_length and read_position counters as data is read.
573
574=cut
575
576sub read {
577 my ( $self, $c, $maxlength ) = @_;
578
579 my $remaining = $self->read_length - $self->read_position;
580 $maxlength ||= $CHUNKSIZE;
581
582 # Are we done reading?
583 if ( $remaining <= 0 ) {
584 $self->finalize_read($c);
585 return;
586 }
587
588 my $readlen = ( $remaining > $maxlength ) ? $maxlength : $remaining;
589 my $rc = $self->read_chunk( $c, my $buffer, $readlen );
590 if ( defined $rc ) {
591 if (0 == $rc) { # Nothing more to read even though Content-Length
592 # said there should be. FIXME - Warn in the log here?
593 $self->finalize_read;
594 return;
595 }
596 $self->read_position( $self->read_position + $rc );
597 return $buffer;
598 }
599 else {
600 Catalyst::Exception->throw(
601 message => "Unknown error reading input: $!" );
602 }
603}
604
605=head2 $self->read_chunk($c, $buffer, $length)
606
607Each engine implements read_chunk as its preferred way of reading a chunk
608of data. Returns the number of bytes read. A return of 0 indicates that
609there is no more data to be read.
610
611=cut
612
613sub read_chunk { }
614
615=head2 $self->read_length
616
617The length of input data to be read. This is obtained from the Content-Length
618header.
619
620=head2 $self->read_position
621
622The amount of input data that has already been read.
623
624=head2 $self->run($c)
625
626Start the engine. Implemented by the various engine classes.
627
628=cut
629
630sub run { }
631
632=head2 $self->write($c, $buffer)
633
634Writes the buffer to the client.
635
636=cut
637
638sub write {
639 my ( $self, $c, $buffer ) = @_;
640
641 unless ( $self->_prepared_write ) {
642 $self->prepare_write($c);
643 $self->_prepared_write(1);
644 }
645
646 return 0 if !defined $buffer;
647
648 my $len = length($buffer);
649 my $wrote = syswrite STDOUT, $buffer;
650
651 if ( !defined $wrote && $! == EWOULDBLOCK ) {
652 # Unable to write on the first try, will retry in the loop below
653 $wrote = 0;
654 }
655
656 if ( defined $wrote && $wrote < $len ) {
657 # We didn't write the whole buffer
658 while (1) {
659 my $ret = syswrite STDOUT, $buffer, $CHUNKSIZE, $wrote;
660 if ( defined $ret ) {
661 $wrote += $ret;
662 }
663 else {
664 next if $! == EWOULDBLOCK;
665 return;
666 }
667
668 last if $wrote >= $len;
669 }
670 }
671
672 return $wrote;
673}
674
675=head2 $self->unescape_uri($uri)
676
677Unescapes a given URI using the most efficient method available. Engines such
678as Apache may implement this using Apache's C-based modules, for example.
679
680=cut
681
682sub unescape_uri {
683 my ( $self, $str ) = @_;
684
685 $str =~ s/(?:%([0-9A-Fa-f]{2})|\+)/defined $1 ? chr(hex($1)) : ' '/eg;
686
687 return $str;
688}
689
690=head2 $self->finalize_output
691
692<obsolete>, see finalize_body
693
694=head2 $self->env
695
696Hash containing enviroment variables including many special variables inserted
697by WWW server - like SERVER_*, REMOTE_*, HTTP_* ...
698
699Before accesing enviroment variables consider whether the same information is
700not directly available via Catalyst objects $c->request, $c->engine ...
701
702BEWARE: If you really need to access some enviroment variable from your Catalyst
703application you should use $c->engine->env->{VARNAME} instead of $ENV{VARNAME},
704as in some enviroments the %ENV hash does not contain what you would expect.
705
706=head1 AUTHORS
707
708Catalyst Contributors, see Catalyst.pm
709
710=head1 COPYRIGHT
711
712This library is free software. You can redistribute it and/or modify it under
713the same terms as Perl itself.
714
715=cut
716
7171;