X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?p=catagits%2FCatalyst-Runtime.git;a=blobdiff_plain;f=lib%2FCatalyst.pm;h=570abc8878e2cdf2183af75d94eacf356afa6c90;hp=2fe20f0563171dd0daa7d06fd82e1f329c0e3a58;hb=342d21698a97962c51114b6ebc6bb8626511cfc6;hpb=5c779e9841d052721162a48ad96fdbda2acd1035 diff --git a/lib/Catalyst.pm b/lib/Catalyst.pm index 2fe20f0..570abc8 100644 --- a/lib/Catalyst.pm +++ b/lib/Catalyst.pm @@ -50,7 +50,7 @@ use Plack::Middleware::RemoveRedundantBody; use Catalyst::Middleware::Stash; use Plack::Util; use Class::Load 'load_class'; -use Encode 2.21 (); +use Encode 2.21 'decode_utf8', 'encode_utf8'; BEGIN { require 5.008003; } @@ -86,8 +86,10 @@ has response => ( lazy => 1, ); sub _build_response_constructor_args { - my $self = shift; - { _log => $self->log }; + return +{ + _log => $_[0]->log, + encoding => $_[0]->encoding, + }; } has namespace => (is => 'rw'); @@ -127,7 +129,7 @@ __PACKAGE__->stats_class('Catalyst::Stats'); __PACKAGE__->_encode_check(Encode::FB_CROAK | Encode::LEAVE_SRC); # Remember to update this in Catalyst::Runtime as well! -our $VERSION = '5.90080_001'; +our $VERSION = '5.90079_001'; $VERSION = eval $VERSION if $VERSION =~ /_/; # numify for warning-free dev releases sub import { @@ -1372,6 +1374,10 @@ path, use C<< $c->uri_for_action >> instead. # Path to a static resource $c->uri_for('/static/images/logo.png'); +In general the scheme of the generated URI object will follow the incoming request +however if your targeted action or action chain has the Scheme attribute it will +use that instead. + =cut sub uri_for { @@ -1390,26 +1396,37 @@ sub uri_for { carp "uri_for called with undef argument" if grep { ! defined $_ } @args; - foreach my $arg (@args) { - ref $arg eq 'ARRAY' ? do { utf8::encode($_) for @$arg } : utf8::encode($arg); - ref $arg eq 'ARRAY' ? do { $_ =~ s/([^$URI::uric])/$URI::Escape::escapes{$1}/go for @$arg } : - $arg =~ s/([^$URI::uric])/$URI::Escape::escapes{$1}/go; + my @encoded_args = (); + foreach my $arg (@args) { + if(ref($arg)||'' eq 'ARRAY') { + push @encoded_args, [map { + my $encoded = encode_utf8 $_; + $encoded =~ s/([^$URI::uric])/$URI::Escape::escapes{$1}/go; + $encoded; + } @$arg]; + } else { + push @encoded_args, do { + my $encoded = encode_utf8 $arg; + $encoded =~ s/([^$URI::uric])/$URI::Escape::escapes{$1}/go; + $encoded; + } } + } + my $target_action = $path->$_isa('Catalyst::Action') ? $path : undef; if ( $path->$_isa('Catalyst::Action') ) { # action object - s|/|%2F|g for @args; + s|/|%2F|g for @encoded_args; my $captures = [ map { s|/|%2F|g; $_; } - ( scalar @args && ref $args[0] eq 'ARRAY' - ? @{ shift(@args) } + ( scalar @encoded_args && ref $encoded_args[0] eq 'ARRAY' + ? @{ shift(@encoded_args) } : ()) ]; my $action = $path; # ->uri_for( $action, \@captures_and_args, \%query_values? ) - if( !@args && $action->number_of_args ) { + if( !@encoded_args && $action->number_of_args ) { my $expanded_action = $c->dispatcher->expand_action( $action ); - my $num_captures = $expanded_action->number_of_captures; - unshift @args, splice @$captures, $num_captures; + unshift @encoded_args, splice @$captures, $num_captures; } $path = $c->dispatcher->uri_for_action($action, $captures); @@ -1421,25 +1438,37 @@ sub uri_for { $path = '/' if $path eq ''; } - unshift(@args, $path); + unshift(@encoded_args, $path); unless (defined $path && $path =~ s!^/!!) { # in-place strip my $namespace = $c->namespace; if (defined $path) { # cheesy hack to handle path '../foo' - $namespace =~ s{(?:^|/)[^/]+$}{} while $args[0] =~ s{^\.\./}{}; + $namespace =~ s{(?:^|/)[^/]+$}{} while $encoded_args[0] =~ s{^\.\./}{}; } - unshift(@args, $namespace || ''); + unshift(@encoded_args, $namespace || ''); } # join args with '/', or a blank string - my $args = join('/', grep { defined($_) } @args); + my $args = join('/', grep { defined($_) } @encoded_args); $args =~ s/\?/%3F/g; # STUPID STUPID SPECIAL CASE $args =~ s!^/+!!; my ($base, $class) = ('/', 'URI::_generic'); if(blessed($c)) { $base = $c->req->base; - $class = ref($base); + if($target_action) { + $target_action = $c->dispatcher->expand_action($target_action); + if(my $s = $target_action->scheme) { + $s = lc($s); + $class = "URI::$s"; + $base->scheme($s); + } else { + $class = ref($base); + } + } else { + $class = ref($base); + } + $base =~ s{(?can('as_psgi') || $error->can('code'))) { + if( + blessed $error && ( + $error->can('as_psgi') || + ( + $error->can('code') && + $error->code =~m/^[1-5][0-9][0-9]$/ + ) + ) + ) { foreach my $err (@{$c->error}) { $c->log->error($err); } @@ -2012,13 +2049,33 @@ sub finalize_headers { $c->response->finalize_headers(); + if(my $enc = $c->encoding) { + my ($ct, $ct_enc) = $c->response->content_type; + + # Only touch 'text-like' contents + if($c->response->content_type =~ /^text|xml$|javascript$/) { + if ($ct_enc && $ct_enc =~ /charset=([^;]*)/) { + if (uc($1) ne uc($enc->mime_name)) { + $c->log->debug("Catalyst encoding config is set to encode in '" . + $enc->mime_name . + "', content type is '$1', not encoding "); + } + } else { + $c->res->content_type($c->res->content_type . "; charset=" . $enc->mime_name); + } + } + } + # Done $response->finalized_headers(1); } =head2 $c->finalize_encoding -Make sure your headers and body are encoded properly IF you set an encoding. +Make sure your body is encoded properly IF you set an encoding. By +default the encoding is UTF-8 but you can disable it by explictly setting the +encoding configuration value to undef. + See L. =cut @@ -2034,26 +2091,12 @@ sub finalize_encoding { return unless $enc; - my ($ct, $ct_enc) = $c->response->content_type; - # Only touch 'text-like' contents - return unless $c->response->content_type =~ /^text|xml$|javascript$/; - - if ($ct_enc && $ct_enc =~ /charset=([^;]*)/) { - if (uc($1) ne uc($enc->mime_name)) { - $c->log->debug("Unicode::Encoding is set to encode in '" . - $enc->mime_name . - "', content type is '$1', not encoding "); - return; - } - } else { - $c->res->content_type($c->res->content_type . "; charset=" . $enc->mime_name); + if($c->response->content_type =~ /^text|xml$|javascript$/) { + if (ref(\$body) eq 'SCALAR') { + $c->response->body( $c->encoding->encode( $body, $c->_encode_check ) ); + } } - - # Oh my, I wonder what filehandle responses and streams do... - jnap. - # Encode expects plain scalars (IV, NV or PV) and segfaults on ref's - $c->response->body( $c->encoding->encode( $body, $c->_encode_check ) ) - if ref(\$body) eq 'SCALAR'; } =head2 $c->finalize_output @@ -2117,7 +2160,15 @@ sub handle_request { $status = $c->finalize; } catch { #rethow if this can be handled by middleware - if(blessed $_ && ($_->can('as_psgi') || $_->can('code'))) { + if( + blessed($_) && ( + $_->can('as_psgi') || + ( + $_->can('code') && + $_->code =~m/^[1-5][0-9][0-9]$/ + ) + ) + ) { $_->can('rethrow') ? $_->rethrow : croak $_; } chomp(my $error = $_); @@ -2267,7 +2318,7 @@ Prepares body parameters. sub prepare_body_parameters { my $c = shift; - $c->engine->prepare_body_parameters( $c, @_ ); + $c->request->prepare_body_parameters( $c, @_ ); } =head2 $c->prepare_connection @@ -2361,6 +2412,10 @@ sub log_request { $method ||= ''; $path = '/' unless length $path; $address ||= ''; + + $path =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg; + $path = decode_utf8($path); + $c->log->debug(qq/"$method" request for "$path" from "$address"/); $c->log_request_headers($request->headers); @@ -2546,37 +2601,6 @@ Prepares uploads. sub prepare_uploads { my $c = shift; $c->engine->prepare_uploads( $c, @_ ); - - my $enc = $c->encoding; - return unless $enc; - - # Uggg we hook prepare uploads to do the encoding crap on post and query - # parameters! Sorry -jnap - for my $key (qw/ parameters query_parameters body_parameters /) { - for my $value ( values %{ $c->request->{$key} } ) { - # N.B. Check if already a character string and if so do not try to double decode. - # http://www.mail-archive.com/catalyst@lists.scsys.co.uk/msg02350.html - # this avoids exception if we have already decoded content, and is _not_ the - # same as not encoding on output which is bad news (as it does the wrong thing - # for latin1 chars for example).. - $value = $c->_handle_unicode_decoding($value); - } - } - for my $value ( values %{ $c->request->uploads } ) { - # skip if it fails for uploads, as we don't usually want uploads touched - # in any way - for my $inner_value ( ref($value) eq 'ARRAY' ? @{$value} : $value ) { - $inner_value->{filename} = try { - $enc->decode( $inner_value->{filename}, $c->_encode_check ) - } catch { - $c->handle_unicode_encoding_exception({ - param_value => $inner_value->{filename}, - error_msg => $_, - encoding_step => 'uploads', - }); - }; - } - } } =head2 $c->prepare_write @@ -3060,8 +3084,14 @@ Sets up the input/output encoding. See L sub setup_encoding { my $c = shift; - my $enc = delete $c->config->{encoding}; - $c->encoding( $enc ) if defined $enc; + if( exists($c->config->{encoding}) && !defined($c->config->{encoding}) ) { + # Ok, so the user has explicitly said "I don't want encoding..." + return; + } else { + my $enc = defined($c->config->{encoding}) ? + delete $c->config->{encoding} : 'UTF-8'; # not sure why we delete it... (JNAP) + $c->encoding($enc); + } } =head2 handle_unicode_encoding_exception @@ -3099,8 +3129,13 @@ sub _handle_unicode_decoding { return $value; } elsif ( ref $value eq 'HASH' ) { - foreach ( values %$value ) { - $_ = $self->_handle_unicode_decoding($_); + foreach (keys %$value) { + my $encoded_key = $self->_handle_param_unicode_decoding($_); + $value->{$encoded_key} = $self->_handle_unicode_decoding($value->{$_}); + + # If the key was encoded we now have two (the original and current so + # delete the original. + delete $value->{$_} if $_ ne $encoded_key; } return $value; } @@ -3612,6 +3647,9 @@ C - See L. C - See L +This now defaults to 'UTF-8'. You my turn it off by setting this configuration +value to undef. + =item * C @@ -3957,6 +3995,9 @@ Please see L for more on middleware. On request, decodes all params from encoding into a sequence of logical characters. On response, encodes body into encoding. +By default encoding is now 'UTF-8'. You may turn it off by setting +the encoding configuration to undef. + =head2 Methods =over 4