Bump deps and changelog for another dev release with new scripts and scary path resol...
[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
4f5ebacd 330 while ( my $buffer = $self->read($c) ) {
331 $c->prepare_body_chunk($buffer);
fbcc39ad 332 }
fdb3773e 333
334 # paranoia against wrong Content-Length header
847e3257 335 my $remaining = $length - $self->read_position;
34d28dfd 336 if ( $remaining > 0 ) {
fdb3773e 337 $self->finalize_read($c);
34d28dfd 338 Catalyst::Exception->throw(
847e3257 339 "Wrong Content-Length value: $length" );
fdb3773e 340 }
fc7ec1d9 341 }
847e3257 342 else {
343 # Defined but will cause all body code to be skipped
0f56bbcf 344 $c->request->_body(0);
847e3257 345 }
fc7ec1d9 346}
347
b5ecfcf0 348=head2 $self->prepare_body_chunk($c)
4bd82c41 349
4ab87e27 350Add a chunk to the request body.
351
4bd82c41 352=cut
353
354sub prepare_body_chunk {
355 my ( $self, $c, $chunk ) = @_;
4f5ebacd 356
0f56bbcf 357 $c->request->_body->add($chunk);
4bd82c41 358}
359
b5ecfcf0 360=head2 $self->prepare_body_parameters($c)
06e1b616 361
b0ad47c1 362Sets up parameters from body.
4ab87e27 363
06e1b616 364=cut
365
fbcc39ad 366sub prepare_body_parameters {
367 my ( $self, $c ) = @_;
b0ad47c1 368
0f56bbcf 369 return unless $c->request->_body;
b0ad47c1 370
0f56bbcf 371 $c->request->body_parameters( $c->request->_body->param );
fbcc39ad 372}
0556eb49 373
b5ecfcf0 374=head2 $self->prepare_connection($c)
0556eb49 375
4ab87e27 376Abstract method implemented in engines.
377
0556eb49 378=cut
379
380sub prepare_connection { }
381
b5ecfcf0 382=head2 $self->prepare_cookies($c)
fc7ec1d9 383
fa32ac82 384Parse cookies from header. Sets a L<CGI::Simple::Cookie> object.
4ab87e27 385
fc7ec1d9 386=cut
387
6dc87a0f 388sub prepare_cookies {
fbcc39ad 389 my ( $self, $c ) = @_;
6dc87a0f 390
391 if ( my $header = $c->request->header('Cookie') ) {
fa32ac82 392 $c->req->cookies( { CGI::Simple::Cookie->parse($header) } );
6dc87a0f 393 }
394}
fc7ec1d9 395
b5ecfcf0 396=head2 $self->prepare_headers($c)
fc7ec1d9 397
398=cut
399
400sub prepare_headers { }
401
b5ecfcf0 402=head2 $self->prepare_parameters($c)
fc7ec1d9 403
4ab87e27 404sets up parameters from query and post parameters.
405
fc7ec1d9 406=cut
407
fbcc39ad 408sub prepare_parameters {
409 my ( $self, $c ) = @_;
fc7ec1d9 410
7fa2c9c1 411 my $request = $c->request;
412 my $parameters = $request->parameters;
413 my $body_parameters = $request->body_parameters;
414 my $query_parameters = $request->query_parameters;
fbcc39ad 415 # We copy, no references
91772de9 416 foreach my $name (keys %$query_parameters) {
417 my $param = $query_parameters->{$name};
7fa2c9c1 418 $parameters->{$name} = ref $param eq 'ARRAY' ? [ @$param ] : $param;
fbcc39ad 419 }
fc7ec1d9 420
fbcc39ad 421 # Merge query and body parameters
91772de9 422 foreach my $name (keys %$body_parameters) {
423 my $param = $body_parameters->{$name};
7fa2c9c1 424 my @values = ref $param eq 'ARRAY' ? @$param : ($param);
425 if ( my $existing = $parameters->{$name} ) {
426 unshift(@values, (ref $existing eq 'ARRAY' ? @$existing : $existing));
fbcc39ad 427 }
7fa2c9c1 428 $parameters->{$name} = @values > 1 ? \@values : $values[0];
fbcc39ad 429 }
430}
431
b5ecfcf0 432=head2 $self->prepare_path($c)
fc7ec1d9 433
4ab87e27 434abstract method, implemented by engines.
435
fc7ec1d9 436=cut
437
438sub prepare_path { }
439
b5ecfcf0 440=head2 $self->prepare_request($c)
fc7ec1d9 441
b5ecfcf0 442=head2 $self->prepare_query_parameters($c)
fc7ec1d9 443
4ab87e27 444process the query string and extract query parameters.
445
fc7ec1d9 446=cut
447
e0616220 448sub prepare_query_parameters {
449 my ( $self, $c, $query_string ) = @_;
b0ad47c1 450
3b4d1251 451 # Check for keywords (no = signs)
452 # (yes, index() is faster than a regex :))
933ba403 453 if ( index( $query_string, '=' ) < 0 ) {
3b4d1251 454 $c->request->query_keywords( $self->unescape_uri($query_string) );
933ba403 455 return;
456 }
457
458 my %query;
e0616220 459
460 # replace semi-colons
461 $query_string =~ s/;/&/g;
b0ad47c1 462
2f381252 463 my @params = grep { length $_ } split /&/, $query_string;
e0616220 464
933ba403 465 for my $item ( @params ) {
b0ad47c1 466
467 my ($param, $value)
933ba403 468 = map { $self->unescape_uri($_) }
e5542b70 469 split( /=/, $item, 2 );
b0ad47c1 470
933ba403 471 $param = $self->unescape_uri($item) unless defined $param;
b0ad47c1 472
933ba403 473 if ( exists $query{$param} ) {
474 if ( ref $query{$param} ) {
475 push @{ $query{$param} }, $value;
476 }
477 else {
478 $query{$param} = [ $query{$param}, $value ];
479 }
480 }
481 else {
482 $query{$param} = $value;
483 }
e0616220 484 }
933ba403 485
486 $c->request->query_parameters( \%query );
e0616220 487}
fbcc39ad 488
b5ecfcf0 489=head2 $self->prepare_read($c)
fbcc39ad 490
4ab87e27 491prepare to read from the engine.
492
fbcc39ad 493=cut
fc7ec1d9 494
fbcc39ad 495sub prepare_read {
496 my ( $self, $c ) = @_;
4f5ebacd 497
878b821c 498 # Initialize the read position
4f5ebacd 499 $self->read_position(0);
b0ad47c1 500
878b821c 501 # Initialize the amount of data we think we need to read
502 $self->read_length( $c->request->header('Content-Length') || 0 );
fbcc39ad 503}
fc7ec1d9 504
b5ecfcf0 505=head2 $self->prepare_request(@arguments)
fc7ec1d9 506
4ab87e27 507Populate the context object from the request object.
508
fc7ec1d9 509=cut
510
fbcc39ad 511sub prepare_request { }
fc7ec1d9 512
b5ecfcf0 513=head2 $self->prepare_uploads($c)
c9afa5fc 514
fbcc39ad 515=cut
516
517sub prepare_uploads {
518 my ( $self, $c ) = @_;
7fa2c9c1 519
520 my $request = $c->request;
0f56bbcf 521 return unless $request->_body;
7fa2c9c1 522
0f56bbcf 523 my $uploads = $request->_body->upload;
7fa2c9c1 524 my $parameters = $request->parameters;
91772de9 525 foreach my $name (keys %$uploads) {
526 my $files = $uploads->{$name};
fbcc39ad 527 my @uploads;
7fa2c9c1 528 for my $upload (ref $files eq 'ARRAY' ? @$files : ($files)) {
529 my $headers = HTTP::Headers->new( %{ $upload->{headers} } );
530 my $u = Catalyst::Request::Upload->new
531 (
532 size => $upload->{size},
533 type => $headers->content_type,
534 headers => $headers,
535 tempname => $upload->{tempname},
536 filename => $upload->{filename},
537 );
fbcc39ad 538 push @uploads, $u;
539 }
7fa2c9c1 540 $request->uploads->{$name} = @uploads > 1 ? \@uploads : $uploads[0];
f4a57de4 541
c4bed79a 542 # support access to the filename as a normal param
543 my @filenames = map { $_->{filename} } @uploads;
a7e05d9d 544 # append, if there's already params with this name
7fa2c9c1 545 if (exists $parameters->{$name}) {
546 if (ref $parameters->{$name} eq 'ARRAY') {
547 push @{ $parameters->{$name} }, @filenames;
a7e05d9d 548 }
549 else {
7fa2c9c1 550 $parameters->{$name} = [ $parameters->{$name}, @filenames ];
a7e05d9d 551 }
552 }
553 else {
7fa2c9c1 554 $parameters->{$name} = @filenames > 1 ? \@filenames : $filenames[0];
a7e05d9d 555 }
fbcc39ad 556 }
557}
558
b5ecfcf0 559=head2 $self->prepare_write($c)
c9afa5fc 560
4ab87e27 561Abstract method. Implemented by the engines.
562
c9afa5fc 563=cut
564
fbcc39ad 565sub prepare_write { }
566
b5ecfcf0 567=head2 $self->read($c, [$maxlength])
fbcc39ad 568
569=cut
570
571sub read {
572 my ( $self, $c, $maxlength ) = @_;
4f5ebacd 573
fbcc39ad 574 my $remaining = $self->read_length - $self->read_position;
4bd82c41 575 $maxlength ||= $CHUNKSIZE;
4f5ebacd 576
fbcc39ad 577 # Are we done reading?
578 if ( $remaining <= 0 ) {
4f5ebacd 579 $self->finalize_read($c);
fbcc39ad 580 return;
581 }
c9afa5fc 582
fbcc39ad 583 my $readlen = ( $remaining > $maxlength ) ? $maxlength : $remaining;
584 my $rc = $self->read_chunk( $c, my $buffer, $readlen );
585 if ( defined $rc ) {
586 $self->read_position( $self->read_position + $rc );
587 return $buffer;
588 }
589 else {
4f5ebacd 590 Catalyst::Exception->throw(
591 message => "Unknown error reading input: $!" );
fbcc39ad 592 }
593}
fc7ec1d9 594
b5ecfcf0 595=head2 $self->read_chunk($c, $buffer, $length)
23f9d934 596
10011c19 597Each engine implements read_chunk as its preferred way of reading a chunk
fbcc39ad 598of data.
fc7ec1d9 599
fbcc39ad 600=cut
61b1e958 601
fbcc39ad 602sub read_chunk { }
61b1e958 603
b5ecfcf0 604=head2 $self->read_length
ca39d576 605
fbcc39ad 606The length of input data to be read. This is obtained from the Content-Length
607header.
fc7ec1d9 608
b5ecfcf0 609=head2 $self->read_position
fc7ec1d9 610
fbcc39ad 611The amount of input data that has already been read.
63b763c5 612
b5ecfcf0 613=head2 $self->run($c)
63b763c5 614
4ab87e27 615Start the engine. Implemented by the various engine classes.
616
fbcc39ad 617=cut
fc7ec1d9 618
fbcc39ad 619sub run { }
fc7ec1d9 620
b5ecfcf0 621=head2 $self->write($c, $buffer)
fc7ec1d9 622
e512dd24 623Writes the buffer to the client.
4ab87e27 624
fc7ec1d9 625=cut
626
fbcc39ad 627sub write {
628 my ( $self, $c, $buffer ) = @_;
4f5ebacd 629
02570318 630 unless ( $self->_prepared_write ) {
4f5ebacd 631 $self->prepare_write($c);
02570318 632 $self->_prepared_write(1);
fc7ec1d9 633 }
b0ad47c1 634
094a0974 635 return 0 if !defined $buffer;
b0ad47c1 636
d04b2ffd 637 my $len = length($buffer);
638 my $wrote = syswrite STDOUT, $buffer;
b0ad47c1 639
d04b2ffd 640 if ( !defined $wrote && $! == EWOULDBLOCK ) {
641 # Unable to write on the first try, will retry in the loop below
642 $wrote = 0;
643 }
b0ad47c1 644
d04b2ffd 645 if ( defined $wrote && $wrote < $len ) {
646 # We didn't write the whole buffer
647 while (1) {
648 my $ret = syswrite STDOUT, $buffer, $CHUNKSIZE, $wrote;
649 if ( defined $ret ) {
650 $wrote += $ret;
651 }
652 else {
653 next if $! == EWOULDBLOCK;
654 return;
655 }
b0ad47c1 656
d04b2ffd 657 last if $wrote >= $len;
e2b0ddd3 658 }
e512dd24 659 }
b0ad47c1 660
e512dd24 661 return $wrote;
fc7ec1d9 662}
663
933ba403 664=head2 $self->unescape_uri($uri)
665
6a44fe01 666Unescapes a given URI using the most efficient method available. Engines such
667as Apache may implement this using Apache's C-based modules, for example.
933ba403 668
669=cut
670
671sub unescape_uri {
8c7d83e1 672 my ( $self, $str ) = @_;
7d22a537 673
674 $str =~ s/(?:%([0-9A-Fa-f]{2})|\+)/defined $1 ? chr(hex($1)) : ' '/eg;
675
8c7d83e1 676 return $str;
933ba403 677}
34d28dfd 678
4ab87e27 679=head2 $self->finalize_output
680
681<obsolete>, see finalize_body
682
0c76ec45 683=head2 $self->env
684
685Hash containing enviroment variables including many special variables inserted
686by WWW server - like SERVER_*, REMOTE_*, HTTP_* ...
687
688Before accesing enviroment variables consider whether the same information is
689not directly available via Catalyst objects $c->request, $c->engine ...
690
691BEWARE: If you really need to access some enviroment variable from your Catalyst
692application you should use $c->engine->env->{VARNAME} instead of $ENV{VARNAME},
693as in some enviroments the %ENV hash does not contain what you would expect.
694
fbcc39ad 695=head1 AUTHORS
696
2f381252 697Catalyst Contributors, see Catalyst.pm
fc7ec1d9 698
699=head1 COPYRIGHT
700
536bee89 701This library is free software. You can redistribute it and/or modify it under
fc7ec1d9 702the same terms as Perl itself.
703
704=cut
705
7061;