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