Fix bug in Catalyst::Engine which could cause it to all go wrong if read returned...
[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
111sub finalize_error {
fbcc39ad 112 my ( $self, $c ) = @_;
969647fd 113
7299a7b4 114 $c->res->content_type('text/html; charset=utf-8');
df960201 115 my $name = ref($c)->config->{name} || join(' ', split('::', ref $c));
969647fd 116
117 my ( $title, $error, $infos );
118 if ( $c->debug ) {
62d9b030 119
120 # For pretty dumps
b5ecfcf0 121 $error = join '', map {
122 '<p><code class="error">'
123 . encode_entities($_)
124 . '</code></p>'
125 } @{ $c->error };
969647fd 126 $error ||= 'No output';
2666dd3b 127 $error = qq{<pre wrap="">$error</pre>};
969647fd 128 $title = $name = "$name on Catalyst $Catalyst::VERSION";
d82cc9ae 129 $name = "<h1>$name</h1>";
fbcc39ad 130
131 # Don't show context in the dump
02570318 132 $c->req->_clear_context;
133 $c->res->_clear_context;
fbcc39ad 134
135 # Don't show body parser in the dump
0f56bbcf 136 $c->req->_clear_body;
fbcc39ad 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
2f381252 165(pt) Por favor volte mais tarde
b31c0f2e 166(ru) Попробуйте еще раз позже
167(ua) Спробуйте ще раз пізніше
969647fd 168</pre>
169
170 $name = '';
171 }
e060fe05 172 $c->res->body( <<"" );
7299a7b4 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">
969647fd 176<head>
7299a7b4 177 <meta http-equiv="Content-Language" content="en" />
178 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
969647fd 179 <title>$title</title>
7299a7b4 180 <script type="text/javascript">
c6ef5e69 181 <!--
182 function toggleDump (dumpElement) {
7299a7b4 183 var e = document.getElementById( dumpElement );
184 if (e.style.display == "none") {
185 e.style.display = "";
c6ef5e69 186 }
187 else {
7299a7b4 188 e.style.display = "none";
c6ef5e69 189 }
190 }
191 -->
192 </script>
969647fd 193 <style type="text/css">
194 body {
195 font-family: "Bitstream Vera Sans", "Trebuchet MS", Verdana,
196 Tahoma, Arial, helvetica, sans-serif;
34d28dfd 197 color: #333;
969647fd 198 background-color: #eee;
199 margin: 0px;
200 padding: 0px;
201 }
c6ef5e69 202 :link, :link:hover, :visited, :visited:hover {
34d28dfd 203 color: #000;
c6ef5e69 204 }
969647fd 205 div.box {
9619f23c 206 position: relative;
969647fd 207 background-color: #ccc;
208 border: 1px solid #aaa;
209 padding: 4px;
210 margin: 10px;
969647fd 211 }
212 div.error {
34d28dfd 213 background-color: #cce;
969647fd 214 border: 1px solid #755;
215 padding: 8px;
216 margin: 4px;
217 margin-bottom: 10px;
969647fd 218 }
219 div.infos {
34d28dfd 220 background-color: #eee;
969647fd 221 border: 1px solid #575;
222 padding: 8px;
223 margin: 4px;
224 margin-bottom: 10px;
969647fd 225 }
226 div.name {
34d28dfd 227 background-color: #cce;
969647fd 228 border: 1px solid #557;
229 padding: 8px;
230 margin: 4px;
969647fd 231 }
7f8e0078 232 code.error {
233 display: block;
234 margin: 1em 0;
235 overflow: auto;
7f8e0078 236 }
9619f23c 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 }
2666dd3b 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 */
b0ad47c1 253 pre {
2666dd3b 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 }
969647fd 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
d82cc9ae 271
272 # Trick IE
273 $c->res->{body} .= ( ' ' x 512 );
274
275 # Return 500
33117422 276 $c->res->status(500);
969647fd 277}
278
b5ecfcf0 279=head2 $self->finalize_headers($c)
fc7ec1d9 280
4ab87e27 281Abstract method, allows engines to write headers to response
282
fc7ec1d9 283=cut
284
285sub finalize_headers { }
286
b5ecfcf0 287=head2 $self->finalize_read($c)
fc7ec1d9 288
289=cut
290
878b821c 291sub finalize_read { }
fc7ec1d9 292
b5ecfcf0 293=head2 $self->finalize_uploads($c)
fc7ec1d9 294
4ab87e27 295Clean up after uploads, deleting temp files.
296
fc7ec1d9 297=cut
298
fbcc39ad 299sub finalize_uploads {
300 my ( $self, $c ) = @_;
99fe1710 301
7fa2c9c1 302 my $request = $c->request;
91772de9 303 foreach my $key (keys %{ $request->uploads }) {
304 my $upload = $request->uploads->{$key};
7fa2c9c1 305 unlink grep { -e $_ } map { $_->tempname }
306 (ref $upload eq 'ARRAY' ? @{$upload} : ($upload));
c85ff642 307 }
7fa2c9c1 308
fc7ec1d9 309}
310
b5ecfcf0 311=head2 $self->prepare_body($c)
fc7ec1d9 312
4ab87e27 313sets up the L<Catalyst::Request> object body using L<HTTP::Body>
314
fc7ec1d9 315=cut
316
fbcc39ad 317sub prepare_body {
318 my ( $self, $c ) = @_;
99fe1710 319
df960201 320 my $appclass = ref($c) || $c;
878b821c 321 if ( my $length = $self->read_length ) {
7fa2c9c1 322 my $request = $c->request;
0f56bbcf 323 unless ( $request->_body ) {
7fa2c9c1 324 my $type = $request->header('Content-Type');
0f56bbcf 325 $request->_body(HTTP::Body->new( $type, $length ));
df960201 326 $request->_body->tmpdir( $appclass->config->{uploadtmp} )
327 if exists $appclass->config->{uploadtmp};
847e3257 328 }
b0ad47c1 329
ea72fece 330 # Check for definedness as you could read '0'
331 while ( defined ( my $buffer = $self->read($c) ) ) {
4f5ebacd 332 $c->prepare_body_chunk($buffer);
fbcc39ad 333 }
fdb3773e 334
335 # paranoia against wrong Content-Length header
847e3257 336 my $remaining = $length - $self->read_position;
34d28dfd 337 if ( $remaining > 0 ) {
fdb3773e 338 $self->finalize_read($c);
34d28dfd 339 Catalyst::Exception->throw(
847e3257 340 "Wrong Content-Length value: $length" );
fdb3773e 341 }
fc7ec1d9 342 }
847e3257 343 else {
344 # Defined but will cause all body code to be skipped
0f56bbcf 345 $c->request->_body(0);
847e3257 346 }
fc7ec1d9 347}
348
b5ecfcf0 349=head2 $self->prepare_body_chunk($c)
4bd82c41 350
4ab87e27 351Add a chunk to the request body.
352
4bd82c41 353=cut
354
355sub prepare_body_chunk {
356 my ( $self, $c, $chunk ) = @_;
4f5ebacd 357
0f56bbcf 358 $c->request->_body->add($chunk);
4bd82c41 359}
360
b5ecfcf0 361=head2 $self->prepare_body_parameters($c)
06e1b616 362
b0ad47c1 363Sets up parameters from body.
4ab87e27 364
06e1b616 365=cut
366
fbcc39ad 367sub prepare_body_parameters {
368 my ( $self, $c ) = @_;
b0ad47c1 369
0f56bbcf 370 return unless $c->request->_body;
b0ad47c1 371
0f56bbcf 372 $c->request->body_parameters( $c->request->_body->param );
fbcc39ad 373}
0556eb49 374
b5ecfcf0 375=head2 $self->prepare_connection($c)
0556eb49 376
4ab87e27 377Abstract method implemented in engines.
378
0556eb49 379=cut
380
381sub prepare_connection { }
382
b5ecfcf0 383=head2 $self->prepare_cookies($c)
fc7ec1d9 384
fa32ac82 385Parse cookies from header. Sets a L<CGI::Simple::Cookie> object.
4ab87e27 386
fc7ec1d9 387=cut
388
6dc87a0f 389sub prepare_cookies {
fbcc39ad 390 my ( $self, $c ) = @_;
6dc87a0f 391
392 if ( my $header = $c->request->header('Cookie') ) {
fa32ac82 393 $c->req->cookies( { CGI::Simple::Cookie->parse($header) } );
6dc87a0f 394 }
395}
fc7ec1d9 396
b5ecfcf0 397=head2 $self->prepare_headers($c)
fc7ec1d9 398
399=cut
400
401sub prepare_headers { }
402
b5ecfcf0 403=head2 $self->prepare_parameters($c)
fc7ec1d9 404
4ab87e27 405sets up parameters from query and post parameters.
406
fc7ec1d9 407=cut
408
fbcc39ad 409sub prepare_parameters {
410 my ( $self, $c ) = @_;
fc7ec1d9 411
7fa2c9c1 412 my $request = $c->request;
413 my $parameters = $request->parameters;
414 my $body_parameters = $request->body_parameters;
415 my $query_parameters = $request->query_parameters;
fbcc39ad 416 # We copy, no references
91772de9 417 foreach my $name (keys %$query_parameters) {
418 my $param = $query_parameters->{$name};
7fa2c9c1 419 $parameters->{$name} = ref $param eq 'ARRAY' ? [ @$param ] : $param;
fbcc39ad 420 }
fc7ec1d9 421
fbcc39ad 422 # Merge query and body parameters
91772de9 423 foreach my $name (keys %$body_parameters) {
424 my $param = $body_parameters->{$name};
7fa2c9c1 425 my @values = ref $param eq 'ARRAY' ? @$param : ($param);
426 if ( my $existing = $parameters->{$name} ) {
427 unshift(@values, (ref $existing eq 'ARRAY' ? @$existing : $existing));
fbcc39ad 428 }
7fa2c9c1 429 $parameters->{$name} = @values > 1 ? \@values : $values[0];
fbcc39ad 430 }
431}
432
b5ecfcf0 433=head2 $self->prepare_path($c)
fc7ec1d9 434
4ab87e27 435abstract method, implemented by engines.
436
fc7ec1d9 437=cut
438
439sub prepare_path { }
440
b5ecfcf0 441=head2 $self->prepare_request($c)
fc7ec1d9 442
b5ecfcf0 443=head2 $self->prepare_query_parameters($c)
fc7ec1d9 444
4ab87e27 445process the query string and extract query parameters.
446
fc7ec1d9 447=cut
448
e0616220 449sub prepare_query_parameters {
450 my ( $self, $c, $query_string ) = @_;
b0ad47c1 451
3b4d1251 452 # Check for keywords (no = signs)
453 # (yes, index() is faster than a regex :))
933ba403 454 if ( index( $query_string, '=' ) < 0 ) {
3b4d1251 455 $c->request->query_keywords( $self->unescape_uri($query_string) );
933ba403 456 return;
457 }
458
459 my %query;
e0616220 460
461 # replace semi-colons
462 $query_string =~ s/;/&/g;
b0ad47c1 463
2f381252 464 my @params = grep { length $_ } split /&/, $query_string;
e0616220 465
933ba403 466 for my $item ( @params ) {
b0ad47c1 467
468 my ($param, $value)
933ba403 469 = map { $self->unescape_uri($_) }
e5542b70 470 split( /=/, $item, 2 );
b0ad47c1 471
933ba403 472 $param = $self->unescape_uri($item) unless defined $param;
b0ad47c1 473
933ba403 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 }
e0616220 485 }
933ba403 486
487 $c->request->query_parameters( \%query );
e0616220 488}
fbcc39ad 489
b5ecfcf0 490=head2 $self->prepare_read($c)
fbcc39ad 491
4ab87e27 492prepare to read from the engine.
493
fbcc39ad 494=cut
fc7ec1d9 495
fbcc39ad 496sub prepare_read {
497 my ( $self, $c ) = @_;
4f5ebacd 498
878b821c 499 # Initialize the read position
4f5ebacd 500 $self->read_position(0);
b0ad47c1 501
878b821c 502 # Initialize the amount of data we think we need to read
503 $self->read_length( $c->request->header('Content-Length') || 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 ) = @_;
7fa2c9c1 520
521 my $request = $c->request;
0f56bbcf 522 return unless $request->_body;
7fa2c9c1 523
0f56bbcf 524 my $uploads = $request->_body->upload;
7fa2c9c1 525 my $parameters = $request->parameters;
91772de9 526 foreach my $name (keys %$uploads) {
527 my $files = $uploads->{$name};
fbcc39ad 528 my @uploads;
7fa2c9c1 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 );
fbcc39ad 539 push @uploads, $u;
540 }
7fa2c9c1 541 $request->uploads->{$name} = @uploads > 1 ? \@uploads : $uploads[0];
f4a57de4 542
c4bed79a 543 # support access to the filename as a normal param
544 my @filenames = map { $_->{filename} } @uploads;
a7e05d9d 545 # append, if there's already params with this name
7fa2c9c1 546 if (exists $parameters->{$name}) {
547 if (ref $parameters->{$name} eq 'ARRAY') {
548 push @{ $parameters->{$name} }, @filenames;
a7e05d9d 549 }
550 else {
7fa2c9c1 551 $parameters->{$name} = [ $parameters->{$name}, @filenames ];
a7e05d9d 552 }
553 }
554 else {
7fa2c9c1 555 $parameters->{$name} = @filenames > 1 ? \@filenames : $filenames[0];
a7e05d9d 556 }
fbcc39ad 557 }
558}
559
b5ecfcf0 560=head2 $self->prepare_write($c)
c9afa5fc 561
4ab87e27 562Abstract method. Implemented by the engines.
563
c9afa5fc 564=cut
565
fbcc39ad 566sub prepare_write { }
567
b5ecfcf0 568=head2 $self->read($c, [$maxlength])
fbcc39ad 569
ea72fece 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
fbcc39ad 574=cut
575
576sub read {
577 my ( $self, $c, $maxlength ) = @_;
4f5ebacd 578
fbcc39ad 579 my $remaining = $self->read_length - $self->read_position;
4bd82c41 580 $maxlength ||= $CHUNKSIZE;
4f5ebacd 581
fbcc39ad 582 # Are we done reading?
583 if ( $remaining <= 0 ) {
4f5ebacd 584 $self->finalize_read($c);
fbcc39ad 585 return;
586 }
c9afa5fc 587
fbcc39ad 588 my $readlen = ( $remaining > $maxlength ) ? $maxlength : $remaining;
589 my $rc = $self->read_chunk( $c, my $buffer, $readlen );
590 if ( defined $rc ) {
ea72fece 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 }
fbcc39ad 596 $self->read_position( $self->read_position + $rc );
597 return $buffer;
598 }
599 else {
4f5ebacd 600 Catalyst::Exception->throw(
601 message => "Unknown error reading input: $!" );
fbcc39ad 602 }
603}
fc7ec1d9 604
b5ecfcf0 605=head2 $self->read_chunk($c, $buffer, $length)
23f9d934 606
10011c19 607Each engine implements read_chunk as its preferred way of reading a chunk
ea72fece 608of data. Returns the number of bytes read. A return of 0 indicates that
609there is no more data to be read.
fc7ec1d9 610
fbcc39ad 611=cut
61b1e958 612
fbcc39ad 613sub read_chunk { }
61b1e958 614
b5ecfcf0 615=head2 $self->read_length
ca39d576 616
fbcc39ad 617The length of input data to be read. This is obtained from the Content-Length
618header.
fc7ec1d9 619
b5ecfcf0 620=head2 $self->read_position
fc7ec1d9 621
fbcc39ad 622The amount of input data that has already been read.
63b763c5 623
b5ecfcf0 624=head2 $self->run($c)
63b763c5 625
4ab87e27 626Start the engine. Implemented by the various engine classes.
627
fbcc39ad 628=cut
fc7ec1d9 629
fbcc39ad 630sub run { }
fc7ec1d9 631
b5ecfcf0 632=head2 $self->write($c, $buffer)
fc7ec1d9 633
e512dd24 634Writes the buffer to the client.
4ab87e27 635
fc7ec1d9 636=cut
637
fbcc39ad 638sub write {
639 my ( $self, $c, $buffer ) = @_;
4f5ebacd 640
02570318 641 unless ( $self->_prepared_write ) {
4f5ebacd 642 $self->prepare_write($c);
02570318 643 $self->_prepared_write(1);
fc7ec1d9 644 }
b0ad47c1 645
094a0974 646 return 0 if !defined $buffer;
b0ad47c1 647
d04b2ffd 648 my $len = length($buffer);
649 my $wrote = syswrite STDOUT, $buffer;
b0ad47c1 650
d04b2ffd 651 if ( !defined $wrote && $! == EWOULDBLOCK ) {
652 # Unable to write on the first try, will retry in the loop below
653 $wrote = 0;
654 }
b0ad47c1 655
d04b2ffd 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 }
b0ad47c1 667
d04b2ffd 668 last if $wrote >= $len;
e2b0ddd3 669 }
e512dd24 670 }
b0ad47c1 671
e512dd24 672 return $wrote;
fc7ec1d9 673}
674
933ba403 675=head2 $self->unescape_uri($uri)
676
6a44fe01 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.
933ba403 679
680=cut
681
682sub unescape_uri {
8c7d83e1 683 my ( $self, $str ) = @_;
7d22a537 684
685 $str =~ s/(?:%([0-9A-Fa-f]{2})|\+)/defined $1 ? chr(hex($1)) : ' '/eg;
686
8c7d83e1 687 return $str;
933ba403 688}
34d28dfd 689
4ab87e27 690=head2 $self->finalize_output
691
692<obsolete>, see finalize_body
693
0c76ec45 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
fbcc39ad 706=head1 AUTHORS
707
2f381252 708Catalyst Contributors, see Catalyst.pm
fc7ec1d9 709
710=head1 COPYRIGHT
711
536bee89 712This library is free software. You can redistribute it and/or modify it under
fc7ec1d9 713the same terms as Perl itself.
714
715=cut
716
7171;