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