X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?p=catagits%2FCatalyst-Runtime.git;a=blobdiff_plain;f=lib%2FCatalyst.pm;h=7d60f20388818a362452fc2a34c1e8ab312d9cca;hp=ff4ad244f839d06c1486c425dad9bff2d8f22532;hb=8e322ed0b39da75b8d888025782f496ca781527f;hpb=7cee84a9da0fc31861e2f81d333be5dac73e15d5 diff --git a/lib/Catalyst.pm b/lib/Catalyst.pm index ff4ad24..7d60f20 100644 --- a/lib/Catalyst.pm +++ b/lib/Catalyst.pm @@ -27,7 +27,7 @@ use HTML::Entities; use Tree::Simple qw/use_weak_refs/; use Tree::Simple::Visitor::FindByUID; use Class::C3::Adopt::NEXT; -use List::MoreUtils qw/uniq/; +use List::Util qw/uniq/; use attributes; use String::RewritePrefix; use Catalyst::EngineLoader; @@ -51,6 +51,7 @@ use Catalyst::Middleware::Stash; use Plack::Util; use Class::Load 'load_class'; use Encode 2.21 'decode_utf8', 'encode_utf8'; +use Scalar::Util; BEGIN { require 5.008003; } @@ -81,6 +82,8 @@ sub _build_request_constructor_args { sub composed_request_class { my $class = shift; + return $class->_composed_request_class if $class->_composed_request_class; + my @traits = (@{$class->request_class_traits||[]}, @{$class->config->{request_class_traits}||[]}); # For each trait listed, figure out what the namespace is. First we try the $trait @@ -92,8 +95,14 @@ sub composed_request_class { Class::Load::load_first_existing_class($_, $class.'::'.$trait_ns.'::'. $_, 'Catalyst::'.$trait_ns.'::'.$_) } @traits; - return $class->_composed_request_class || - $class->_composed_request_class(Moose::Util::with_traits($class->request_class, @normalized_traits)); + if ($class->debug && scalar(@normalized_traits)) { + my $column_width = Catalyst::Utils::term_width() - 6; + my $t = Text::SimpleTable->new($column_width); + $t->row($_) for @normalized_traits; + $class->log->debug( "Composed Request Class Traits:\n" . $t->draw . "\n" ); + } + + return $class->_composed_request_class(Moose::Util::with_traits($class->request_class, @normalized_traits)); } has response => ( @@ -115,6 +124,8 @@ sub _build_response_constructor_args { sub composed_response_class { my $class = shift; + return $class->_composed_response_class if $class->_composed_response_class; + my @traits = (@{$class->response_class_traits||[]}, @{$class->config->{response_class_traits}||[]}); my $trait_ns = 'TraitFor::Response'; @@ -122,8 +133,14 @@ sub composed_response_class { Class::Load::load_first_existing_class($_, $class.'::'.$trait_ns.'::'. $_, 'Catalyst::'.$trait_ns.'::'.$_) } @traits; - return $class->_composed_response_class || - $class->_composed_response_class(Moose::Util::with_traits($class->response_class, @normalized_traits)); + if ($class->debug && scalar(@normalized_traits)) { + my $column_width = Catalyst::Utils::term_width() - 6; + my $t = Text::SimpleTable->new($column_width); + $t->row($_) for @normalized_traits; + $class->log->debug( "Composed Response Class Traits:\n" . $t->draw . "\n" ); + } + + return $class->_composed_response_class(Moose::Util::with_traits($class->response_class, @normalized_traits)); } has namespace => (is => 'rw'); @@ -148,7 +165,7 @@ our $RECURSION = 1000; our $DETACH = Catalyst::Exception::Detach->new; our $GO = Catalyst::Exception::Go->new; -#I imagine that very few of these really +#I imagine that very few of these really #need to be class variables. if any. #maybe we should just make them attributes with a default? __PACKAGE__->mk_classdata($_) @@ -166,6 +183,8 @@ __PACKAGE__->stats_class('Catalyst::Stats'); sub composed_stats_class { my $class = shift; + return $class->_composed_stats_class if $class->_composed_stats_class; + my @traits = (@{$class->stats_class_traits||[]}, @{$class->config->{stats_class_traits}||[]}); my $trait_ns = 'TraitFor::Stats'; @@ -173,14 +192,20 @@ sub composed_stats_class { Class::Load::load_first_existing_class($_, $class.'::'.$trait_ns.'::'. $_, 'Catalyst::'.$trait_ns.'::'.$_) } @traits; - return $class->_composed_stats_class || - $class->_composed_stats_class(Moose::Util::with_traits($class->stats_class, @normalized_traits)); + if ($class->debug && scalar(@normalized_traits)) { + my $column_width = Catalyst::Utils::term_width() - 6; + my $t = Text::SimpleTable->new($column_width); + $t->row($_) for @normalized_traits; + $class->log->debug( "Composed Stats Class Traits:\n" . $t->draw . "\n" ); + } + + return $class->_composed_stats_class(Moose::Util::with_traits($class->stats_class, @normalized_traits)); } __PACKAGE__->_encode_check(Encode::FB_CROAK | Encode::LEAVE_SRC); # Remember to update this in Catalyst::Runtime as well! -our $VERSION = '5.90092'; +our $VERSION = '5.90123'; $VERSION = eval $VERSION if $VERSION =~ /_/; # numify for warning-free dev releases sub import { @@ -222,11 +247,6 @@ sub _application { $_[0] } Catalyst - The Elegant MVC Web Application Framework -=for html -CPAN version -Catalyst></a>
-<a href=Kwalitee Score - =head1 SYNOPSIS See the L distribution for comprehensive @@ -450,7 +470,7 @@ or stash it like so: and access it from the stash. -Keep in mind that the C method used is that of the caller action. So a C<$c-Edetach> inside a forwarded action would run the C method from the original action requested. +Keep in mind that the C method used is that of the caller action. So a C<< $c->detach >> inside a forwarded action would run the C method from the original action requested. =cut @@ -521,7 +541,7 @@ L<< detach|/"$c->detach( $action [, \@arguments ] )" >>. Like C<< $c->visit >>, C<< $c->go >> will perform a full dispatch on the specified action or method, with localized C<< $c->action >> and C<< $c->namespace >>. Like C, C escapes the processing of the current request chain on completion, and -does not return to its cunless blessed $cunless blessed $caller. +does not return to its caller. @arguments are arguments to the final destination of $action. @captures are arguments to the intermediate steps, if any, on the way to the final sub of @@ -606,13 +626,17 @@ sub error { return $c->{error} || []; } - =head2 $c->state Contains the return value of the last executed action. Note that << $c->state >> operates in a scalar context which means that all values it returns are scalar. +Please note that if an action throws an exception, the value of state +should no longer be considered the return if the last action. It is generally +going to be 0, which indicates an error state. Examine $c->error for error +details. + =head2 $c->clear_errors Clear errors. You probably don't want to clear the errors unless you are @@ -640,22 +664,42 @@ sub has_errors { scalar(@{shift->error}) ? 1:0 } =head2 $c->last_error Returns the most recent error in the stack (the one most recently added...) -or nothing if there are no errors. +or nothing if there are no errors. This does not modify the contents of the +error stack. =cut -sub last_error { my ($err, @errs) = @{shift->error}; return $err } +sub last_error { + my (@errs) = @{shift->error}; + return scalar(@errs) ? $errs[-1]: undef; +} =head2 shift_errors -shifts the most recently added error off the error stack and returns if. Returns +shifts the most recently added error off the error stack and returns it. Returns nothing if there are no more errors. =cut sub shift_errors { my ($self) = @_; - my ($err, @errors) = @{$self->error}; + my @errors = @{$self->error}; + my $err = shift(@errors); + $self->{error} = \@errors; + return $err; +} + +=head2 pop_errors + +pops the most recently added error off the error stack and returns it. Returns +nothing if there are no more errors. + +=cut + +sub pop_errors { + my ($self) = @_; + my @errors = @{$self->error}; + my $err = pop(@errors); $self->{error} = \@errors; return $err; } @@ -787,6 +831,11 @@ sub controller { my $comps = $c->components; my $check = $appclass."::Controller::".$name; return $c->_filter_component( $comps->{$check}, @args ) if exists $comps->{$check}; + foreach my $path (@{$appclass->config->{ setup_components }->{ search_extra }}) { + next unless $path =~ /.*::Controller/; + $check = $path."::".$name; + return $c->_filter_component( $comps->{$check}, @args ) if exists $comps->{$check}; + } } my @result = $c->_comp_search_prefixes( $name, qw/Controller C/ ); return map { $c->_filter_component( $_, @args ) } @result if ref $name; @@ -826,6 +875,11 @@ sub model { my $comps = $c->components; my $check = $appclass."::Model::".$name; return $c->_filter_component( $comps->{$check}, @args ) if exists $comps->{$check}; + foreach my $path (@{$appclass->config->{ setup_components }->{ search_extra }}) { + next unless $path =~ /.*::Model/; + $check = $path."::".$name; + return $c->_filter_component( $comps->{$check}, @args ) if exists $comps->{$check}; + } } my @result = $c->_comp_search_prefixes( $name, qw/Model M/ ); return map { $c->_filter_component( $_, @args ) } @result if ref $name; @@ -890,6 +944,11 @@ sub view { else { $c->log->warn( "Attempted to use view '$check', but does not exist" ); } + foreach my $path (@{$appclass->config->{ setup_components }->{ search_extra }}) { + next unless $path =~ /.*::View/; + $check = $path."::".$name; + return $c->_filter_component( $comps->{$check}, @args ) if exists $comps->{$check}; + } } my @result = $c->_comp_search_prefixes( $name, qw/View V/ ); return map { $c->_filter_component( $_, @args ) } @result if ref $name; @@ -1373,9 +1432,9 @@ EOF } my @middleware = map { - ref $_ eq 'CODE' ? - "Inline Coderef" : - (ref($_) .' '. ($_->can('VERSION') ? $_->VERSION || '' : '') + ref $_ eq 'CODE' ? + "Inline Coderef" : + (ref($_) .' '. ($_->can('VERSION') ? $_->VERSION || '' : '') || '') } $class->registered_middlewares; if (@middleware) { @@ -1434,6 +1493,11 @@ EOF $class->log->warn("This setting is deprecated and planned to be removed in Catalyst 5.81."); } + # call these so we pre setup the composed classes + $class->composed_request_class; + $class->composed_response_class; + $class->composed_stats_class; + $class->setup_finalize; # Flush the log for good measure (in case something turned off 'autoflush' early) @@ -1464,11 +1528,11 @@ sub setup_finalize { $class->setup_finished(1); } -=head2 $c->uri_for( $path?, @args?, \%query_values? ) +=head2 $c->uri_for( $path?, @args?, \%query_values?, \$fragment? ) -=head2 $c->uri_for( $action, \@captures?, @args?, \%query_values? ) +=head2 $c->uri_for( $action, \@captures?, @args?, \%query_values?, \$fragment? ) -=head2 $c->uri_for( $action, [@captures, @args], \%query_values? ) +=head2 $c->uri_for( $action, [@captures, @args], \%query_values?, \$fragment? ) Constructs an absolute L object based on the application root, the provided path, and the additional arguments and query parameters provided. @@ -1486,6 +1550,15 @@ relative to the application root (if it does). It is then merged with C<< $c->request->base >>; any C<@args> are appended as additional path components; and any C<%query_values> are appended as C parameters. +B If you are using this 'stringy' first argument, we skip encoding and +allow you to declare something like: + + $c->uri_for('/foo/bar#baz') + +Where 'baz' is a URI fragment. We consider this first argument string to be +'expert' mode where you are expected to create a valid URL and we for the most +part just pass it through without a lot of internal effort to escape and encode. + If the first argument is a L it represents an action which will have its path resolved using C<< $c->dispatcher->uri_for_action >>. The optional C<\@captures> argument (an arrayref) allows passing the captured @@ -1528,11 +1601,24 @@ sub uri_for { $path .= '/'; } - undef($path) if (defined $path && $path eq ''); + my $fragment = ((scalar(@args) && ref($args[-1]) eq 'SCALAR') ? ${pop @args} : undef ); + + unless(blessed $path) { + if (defined($path) and $path =~ s/#(.+)$//) { + if(defined($1) and defined $fragment) { + carp "Abiguious fragment declaration: You cannot define a fragment in '$path' and as an argument '$fragment'"; + } + if(defined($1)) { + $fragment = $1; + } + } + } my $params = ( scalar @args && ref $args[$#args] eq 'HASH' ? pop @args : {} ); + undef($path) if (defined $path && $path eq ''); + carp "uri_for called with undef argument" if grep { ! defined $_ } @args; my $target_action = $path->$_isa('Catalyst::Action') ? $path : undef; @@ -1554,8 +1640,9 @@ sub uri_for { if($num_captures) { unless($expanded_action->match_captures_constraints($c, $captures)) { - carp "captures [@{$captures}] do not match the type constraints in actionchain ending with '$expanded_action'"; - return; + $c->log->debug("captures [@{$captures}] do not match the type constraints in actionchain ending with '$expanded_action'") + if $c->debug; + return undef; } } @@ -1570,8 +1657,9 @@ sub uri_for { # At this point @encoded_args is the remaining Args (all captures removed). if($expanded_action->has_args_constraints) { unless($expanded_action->match_args($c,\@args)) { - carp "args [@args] do not match the type constraints in action '$expanded_action'"; - return; + $c->log->debug("args [@args] do not match the type constraints in action '$expanded_action'") + if $c->debug; + return undef; } } } @@ -1611,28 +1699,24 @@ sub uri_for { } my $query = ''; - if (my @keys = keys %$params) { # somewhat lifted from URI::_query's query_form $query = '?'.join('&', map { my $val = $params->{$_}; - #s/([;\/?:@&=+,\$\[\]%])/$URI::Escape::escapes{$1}/go; ## Commented out because seems to lead to double encoding - JNAP - s/ /+/g; - my $key = $_; + my $key = encode_utf8($_); + # using the URI::Escape pattern here so utf8 chars survive + $key =~ s/([^A-Za-z0-9\-_.!~*'() ])/$URI::Escape::escapes{$1}/go; + $key =~ s/ /+/g; + $val = '' unless defined $val; (map { - my $param = "$_"; - $param = encode_utf8($param); + my $param = encode_utf8($_); # using the URI::Escape pattern here so utf8 chars survive $param =~ s/([^A-Za-z0-9\-_.!~*'() ])/$URI::Escape::escapes{$1}/go; $param =~ s/ /+/g; - $key = encode_utf8($key); - # using the URI::Escape pattern here so utf8 chars survive - $key =~ s/([^A-Za-z0-9\-_.!~*'() ])/$URI::Escape::escapes{$1}/go; - $key =~ s/ /+/g; - - "${key}=$param"; } ( ref $val eq 'ARRAY' ? @$val : $val )); + "${key}=$param"; + } ( ref $val eq 'ARRAY' ? @$val : $val )); } @keys); } @@ -1640,7 +1724,16 @@ sub uri_for { $base =~ s/([^$URI::uric])/$URI::Escape::escapes{$1}/go; $args = encode_utf8 $args; $args =~ s/([^$URI::uric])/$URI::Escape::escapes{$1}/go; - + + if(defined $fragment) { + if(blessed $path) { + $fragment = encode_utf8($fragment); + $fragment =~ s/([^A-Za-z0-9\-_.!~*'() ])/$URI::Escape::escapes{$1}/go; + $fragment =~ s/ /+/g; + } + $query .= "#$fragment"; + } + my $res = bless(\"${base}${args}${query}", $class); $res; } @@ -1678,7 +1771,7 @@ and it will create the URI /users/the-list. =item \@captures_and_args? -Optional array reference of Captures (i.e. C<req->captures>) +Optional array reference of Captures (i.e. C or C<< $c->req->captures >>) and arguments to the request. Usually used with L to interpolate all the parameters in the URI. @@ -1936,7 +2029,7 @@ via $c->error. sub execute { my ( $c, $class, $code ) = @_; $class = $c->component($class) || $class; - $c->state(0); + #$c->state(0); if ( $c->depth >= $RECURSION ) { my $action = $code->reverse(); @@ -1988,7 +2081,7 @@ sub execute { } $c->error($error); } - $c->state(0); + #$c->state(0); } return $c->state; } @@ -2103,16 +2196,27 @@ sub finalize { $c->log_response; - if ($c->use_stats) { - my $elapsed = $c->stats->elapsed; - my $av = $elapsed == 0 ? '??' : sprintf '%.3f', 1 / $elapsed; - $c->log->info( - "Request took ${elapsed}s ($av/s)\n" . $c->stats->report . "\n" ); - } + $c->log_stats if $c->use_stats; return $c->response->status; } +=head2 $c->log_stats + +Logs statistics. + +=cut + +sub log_stats { + my $c = shift; + + my $elapsed = $c->stats->elapsed; + my $av = $elapsed == 0 ? '??' : sprintf '%.3f', 1 / $elapsed; + $c->log->info( + "Request took ${elapsed}s ($av/s)\n" . $c->stats->report . "\n" ); +} + + =head2 $c->finalize_body Finalizes body. @@ -2209,7 +2313,7 @@ sub finalize_encoding { # to do this early since encodable_response is false for this condition and we need # to match the debug output for backcompat (there's a test for this...) -JNAP if( - $res->content_type_charset and $c->encoding and + $res->content_type_charset and $c->encoding and (uc($c->encoding->mime_name) ne uc($res->content_type_charset)) ) { my $ct = lc($res->content_type_charset); @@ -2223,14 +2327,19 @@ sub finalize_encoding { (defined($res->body)) and (ref(\$res->body) eq 'SCALAR') ) { + # if you are finding yourself here and your body is already encoded correctly + # and you want to turn this off, use $c->clear_encoding to prevent encoding + # at this step, or set encoding to undef in the config to do so for the whole + # application. See the ENCODING documentaiton for better notes. $c->res->body( $c->encoding->encode( $c->res->body, $c->_encode_check ) ); # Set the charset if necessary. This might be a bit bonkers since encodable response - # is false when the set charset is not the same as the encoding mimetype (maybe + # is false when the set charset is not the same as the encoding mimetype (maybe # confusing action at a distance here.. - # Don't try to set the charset if one already exists + # Don't try to set the charset if one already exists or if headers are already finalized $c->res->content_type($c->res->content_type . "; charset=" . $c->encoding->mime_name) - unless($c->res->content_type_charset); + unless($c->res->content_type_charset || + ($c->res->_context && $c->res->finalized_headers && !$c->res->_has_response_cb)); } } @@ -2334,7 +2443,6 @@ sub prepare { my $c = $class->context_class->new({ $uploadtmp ? (_uploadtmp => $uploadtmp) : ()}); $c->response->_context($c); - $c->stats($class->stats_class->new)->enable($c->use_stats); if ( $c->debug || $c->config->{enable_catalyst_header} ) { @@ -2368,9 +2476,6 @@ sub prepare { # VERY ugly and probably shouldn't rely on ->finalize actually working catch { # failed prepare is always due to an invalid request, right? - $c->response->status(400); - $c->response->content_type('text/plain'); - $c->response->body('Bad Request'); # Note we call finalize and then die here, which escapes # finalize being called in the enclosing block.. # It in fact couldn't be called, as we don't return $c.. @@ -2378,11 +2483,25 @@ sub prepare { # breaking compat for people doing crazy things (we should set # the 400 and just return the ctx here IMO, letting finalize get called # above... - $c->finalize; - die $_; + if ( $c->_handle_http_exception($_) ) { + foreach my $err (@{$c->error}) { + $c->log->error($err); + } + $c->clear_errors; + $c->log->_flush if $c->log->can('_flush'); + $_->can('rethrow') ? $_->rethrow : croak $_; + } else { + $c->response->status(400); + $c->response->content_type('text/plain'); + $c->response->body('Bad Request'); + $c->finalize; + die $_; + } }; $c->log_request; + $c->{stash} = $c->stash; + Scalar::Util::weaken($c->{stash}); return $c; } @@ -2631,9 +2750,16 @@ sub log_request_parameters { next if ! keys %$params; my $t = Text::SimpleTable->new( [ 35, 'Parameter' ], [ $column_width, 'Value' ] ); for my $key ( sort keys %$params ) { - my $param = $params->{$key}; - my $value = defined($param) ? $param : ''; - $t->row( $key, ref $value eq 'ARRAY' ? ( join ', ', @$value ) : $value ); + my @values = (); + if(ref $params eq 'Hash::MultiValue') { + @values = $params->get_all($key); + } else { + my $param = $params->{$key}; + if( defined($param) ) { + @values = ref $param eq 'ARRAY' ? @$param : $param; + } + } + $t->row( $key.( scalar @values > 1 ? ' [multiple]' : ''), join(', ', @values) ); } $c->log->debug( ucfirst($type) . " Parameters are:\n" . $t->draw ); } @@ -2761,10 +2887,10 @@ We try each possible role in turn (and throw an error if none load) MyApp::TraitFor::Request::Foo Catalyst::TraitFor::Request::Foo -The namespace part 'TraitFor::Request' was choosen to assist in backwards +The namespace part 'TraitFor::Request' was chosen to assist in backwards compatibility with L which previously provided these features in a stand alone package. - + =head2 $app->composed_request_class This is the request class which has been composed with any request_class_traits. @@ -2792,7 +2918,7 @@ We try each possible role in turn (and throw an error if none load) MyApp::TraitFor::Response::Foo Catalyst::TraitFor::Responset::Foo -The namespace part 'TraitFor::Response' was choosen to assist in backwards +The namespace part 'TraitFor::Response' was chosen to assist in backwards compatibility with L which previously provided these features in a stand alone package. @@ -2919,7 +3045,7 @@ sub setup_components { my @injected = $class->setup_injected_components; # All components are registered, now we need to 'init' them. - foreach my $component_name (@injected, @comps) { + foreach my $component_name (@comps, @injected) { $class->components->{$component_name} = $class->components->{$component_name}->() if (ref($class->components->{$component_name}) || '') eq 'CODE'; } @@ -3039,7 +3165,7 @@ sub locate_components { my $config = shift; my @paths = qw( ::M ::Model ::V ::View ::C ::Controller ); - my $extra = delete $config->{ search_extra } || []; + my $extra = $config->{ search_extra } || []; unshift @paths, @$extra; @@ -3124,7 +3250,7 @@ sub setup_component { $class->components->{ $component } = $class->setup_component($component); } - return $instance; + return $instance; } =head2 $app->config_for( $component_name ) @@ -3139,7 +3265,7 @@ component or component object. Example: my $config = MyApp->config_for('MyApp::Model::Foo'); -In this case $config is the hashref C< {a=>1, b=>2} >. +In this case $config is the hashref C<< {a=>1, b=>2} >>. This is also handy for looking up configuration for a plugin, to make sure you follow existing L standards for where a plugin should put its configuration. @@ -3249,7 +3375,7 @@ sub setup_engine { return; } -## This exists just to supply a prebuild psgi app for mod_perl and for the +## This exists just to supply a prebuild psgi app for mod_perl and for the ## build in server support (back compat support for pre psgi port behavior). ## This is so that we don't build a new psgi app for each request when using ## the mod_perl handler or the built in servers (http and fcgi, etc). @@ -3266,7 +3392,7 @@ sub _finalized_psgi_app { } ## Look for a psgi file like 'myapp_web.psgi' (if the app is MyApp::Web) in the -## home directory and load that and return it (just assume it is doing the +## home directory and load that and return it (just assume it is doing the ## right thing :) ). If that does not exist, call $app->psgi_app, wrap that ## in default_middleware and return it ( this is for backward compatibility ## with pre psgi port behavior ). @@ -3352,7 +3478,7 @@ sub apply_default_middlewares { condition => sub { my ($env) = @_; return if $app->config->{ignore_frontend_proxy}; - return $env->{REMOTE_ADDR} eq '127.0.0.1'; + return $env->{REMOTE_ADDR} && $env->{REMOTE_ADDR} eq '127.0.0.1'; }, ); } @@ -3464,15 +3590,44 @@ sub setup_encoding { =head2 handle_unicode_encoding_exception -Hook to let you customize how encoding errors are handled. By default -we just throw an exception. Receives a hashref of debug information. -Example: +Hook to let you customize how encoding errors are handled. By default +we just throw an exception and the default error page will pick it up. +Receives a hashref of debug information. Example of call (from the +Catalyst internals): - $c->handle_unicode_encoding_exception({ + my $decoded_after_fail = $c->handle_unicode_encoding_exception({ param_value => $value, error_msg => $_, - encoding_step => 'params', - }); + encoding_step => 'params', + }); + +The calling code expects to receive a decoded string or an exception. + +You can override this for custom handling of unicode errors. By +default we just die. If you want a custom response here, one approach +is to throw an HTTP style exception, instead of returning a decoded +string or throwing a generic exception. + + sub handle_unicode_encoding_exception { + my ($c, $params) = @_; + HTTP::Exception::BAD_REQUEST->throw(status_message=>$params->{error_msg}); + } + +Alternatively you can 'catch' the error, stash it and write handling code later +in your application: + + sub handle_unicode_encoding_exception { + my ($c, $params) = @_; + $c->stash(BAD_UNICODE_DATA=>$params); + # return a dummy string. + return 1; + } + +NOTE: Please keep in mind that once an error like this occurs, +the request setup is still ongoing, which means the state of C<$c> and +related context parts like the request and response may not be setup +up correctly (since we haven't finished the setup yet). If you throw +an exception the setup is aborted. =cut @@ -3513,16 +3668,20 @@ sub _handle_unicode_decoding { } sub _handle_param_unicode_decoding { - my ( $self, $value ) = @_; + my ( $self, $value, $check ) = @_; return unless defined $value; # not in love with just ignoring undefs - jnap return $value if blessed($value); #don't decode when the value is an object. my $enc = $self->encoding; + + return $value unless $enc; # don't decode if no encoding is specified + + $check ||= $self->_encode_check; return try { - $enc->decode( $value, $self->_encode_check ); + $enc->decode( $value, $check); } catch { - $self->handle_unicode_encoding_exception({ + return $self->handle_unicode_encoding_exception({ param_value => $value, error_msg => $_, encoding_step => 'params', @@ -3868,14 +4027,14 @@ only two default data handlers, for 'application/json' and an alternative to L or via L IF you've installed it. The 'application/json' data handler is used to parse incoming JSON into a Perl -data structure. It used either L or L, depending on which -is installed. This allows you to fail back to L, which is a Pure Perl -JSON decoder, and has the smallest dependency impact. +data structure. It uses L. This allows you to fail back to +L, which is a Pure Perl JSON decoder, and has the smallest dependency +impact. Because we don't wish to add more dependencies to L, if you wish to -use this new feature we recommend installing L or L in -order to get the best performance. You should add either to your dependency -list (Makefile.PL, dist.ini, cpanfile, etc.) +use this new feature we recommend installing L in order to get +the best performance. You should add either to your dependency list +(Makefile.PL, dist.ini, cpanfile, etc.) =cut @@ -3910,12 +4069,12 @@ sub default_data_handlers { }, 'application/json' => sub { my ($fh, $req) = @_; - my $parser = Class::Load::load_first_existing_class('JSON::MaybeXS', 'JSON'); + require JSON::MaybeXS; my $slurped; - return eval { + return eval { local $/; $slurped = $fh->getline; - $parser->can("decode_json")->($slurped); # decode_json does utf8 decoding for us + JSON::MaybeXS::decode_json($slurped); # decode_json does utf8 decoding for us } || Catalyst::Exception->throw(sprintf "Error Parsing POST '%s', Error: %s", (defined($slurped) ? $slurped : 'undef') ,$@); }, }; @@ -3949,7 +4108,7 @@ L, but can be set otherwise with L<< stats_class|/"$c->stats_class" >>. Even if L<< -Stats|/"-Stats" >> is not enabled, the stats object is still -available. By enabling it with C< $c->stats->enabled(1) >, it can be used to +available. By enabling it with C<< $c->stats->enabled(1) >>, it can be used to profile explicitly, although MyApp.pm still won't profile nor output anything by itself. @@ -3980,7 +4139,7 @@ We try each possible role in turn (and throw an error if none load) MyApp::TraitFor::Stats::Foo Catalyst::TraitFor::Stats::Foo -The namespace part 'TraitFor::Stats' was choosen to assist in backwards +The namespace part 'TraitFor::Stats' was chosen to assist in backwards compatibility with L which previously provided these features in a stand alone package. @@ -4157,18 +4316,20 @@ value to undef. C -When there is an error in an action chain, the default behavior is to continue -processing the remaining actions and then catch the error upon chain end. This -can lead to running actions when the application is in an unexpected state. If -you have this issue, setting this config value to true will promptly exit a -chain when there is an error raised in any action (thus terminating the chain -early.) +Defaults to true. -use like: +When there is an error in an action chain, the default behavior is to +abort the processing of the remaining actions to avoid running them +when the application is in an unexpected state. - __PACKAGE__->config(abort_chain_on_error_fix => 1); +Before version 5.90070, the default used to be false. To keep the old +behaviour, you can explicitly set the value to false. E.g. + + __PACKAGE__->config(abort_chain_on_error_fix => 0); + +If this setting is set to false, then the remaining actions are +performed and the error is caught at the end of the chain. -In the future this might become the default behavior. =item * @@ -4194,12 +4355,12 @@ backwardly compatible). C -When creating body parameters from a POST, if we run into a multpart POST +When creating body parameters from a POST, if we run into a multipart POST that does not contain uploads, but instead contains inlined complex data (very uncommon) we cannot reliably convert that into field => value pairs. So instead we create an instance of L. If this causes issue for you, you can disable this by setting C -to true (default is false). +to true (default is false). =item * @@ -4216,33 +4377,32 @@ parameter to true. C If true, then do not try to character decode any wide characters in your -request URL query or keywords. Most readings of the relevent specifications +request URL query or keywords. Most readings of the relevant specifications suggest these should be UTF-* encoded, which is the default that L -will use, hwoever if you are creating a lot of URLs manually or have external +will use, however if you are creating a lot of URLs manually or have external evil clients, this might cause you trouble. If you find the changes introduced -in Catalyst version 5.90080+ break some of your query code, you may disable +in Catalyst version 5.90080+ break some of your query code, you may disable the UTF-8 decoding globally using this configuration. -This setting takes precedence over C and -C +This setting takes precedence over C =item * -C - -By default we decode query and keywords in your request URL using UTF-8, which -is our reading of the relevent specifications. This setting allows one to -specify a fixed value for how to decode your query. You might need this if -you are doing a lot of custom encoding of your URLs and not using UTF-8. +C -This setting take precedence over C. +Catalyst versions 5.90080 - 5.90106 would decode query parts of an incoming +request but would not raise an exception when the decoding failed due to +incorrect unicode. It now does, but if this change is giving you trouble +you may disable it by setting this configuration to true. =item * -C +C -Setting this to true will default your query decoding to whatever your -general global encoding is (the default is UTF-8). +By default we decode query and keywords in your request URL using UTF-8, which +is our reading of the relevant specifications. This setting allows one to +specify a fixed value for how to decode your query. You might need this if +you are doing a lot of custom encoding of your URLs and not using UTF-8. =item * @@ -4268,19 +4428,19 @@ C - See L. C -An arrayref of Ls that get componsed into your stats class. +An arrayref of Ls that get composed into your stats class. =item * C -An arrayref of Ls that get componsed into your request class. +An arrayref of Ls that get composed into your request class. =item * C -An arrayref of Ls that get componsed into your response class. +An arrayref of Ls that get composed into your response class. =item * @@ -4438,21 +4598,21 @@ option, C, which lets you associate a content type with a coderef that parses that content type into something Perl can readily access. package MyApp::Web; - + use Catalyst; - use JSON::Maybe; - + use JSON::MaybeXS; + __PACKAGE__->config( data_handlers => { 'application/json' => sub { local $/; decode_json $_->getline }, }, ## Any other configuration. ); - + __PACKAGE__->setup; By default L comes with a generic JSON data handler similar to the -example given above, which uses L to provide either L +example given above, which uses L to provide either L (a pure Perl, dependency free JSON parser) or L if you have it installed (if you want the faster XS parser, add it to you project Makefile.PL or dist.ini, cpanfile, etc.) @@ -4474,12 +4634,12 @@ arrayref under the configuration key C. Here's an example with details to follow: package MyApp::Web; - + use Catalyst; use Plack::Middleware::StackTrace; - + my $stacktrace_middleware = Plack::Middleware::StackTrace->new; - + __PACKAGE__->config( 'psgi_middleware', [ 'Debug', @@ -4496,7 +4656,7 @@ with details to follow: }, ], ); - + __PACKAGE__->setup; So the general form is: @@ -4522,26 +4682,26 @@ middleware will wrap closer to the application). Keep this in mind since in some cases the order of middleware is important. The two approaches are not exclusive. - + =over 4 - + =item Middleware Object - + An already initialized object that conforms to the L specification: - + my $stacktrace_middleware = Plack::Middleware::StackTrace->new; - + __PACKAGE__->config( 'psgi_middleware', [ $stacktrace_middleware, ]); - - + + =item coderef - + A coderef that is an inlined middleware: - + __PACKAGE__->config( 'psgi_middleware', [ sub { @@ -4558,11 +4718,11 @@ A coderef that is an inlined middleware: }, }, ]); - - - + + + =item a scalar - + We assume the scalar refers to a namespace after normalizing it using the following rules: @@ -4593,12 +4753,12 @@ Examples: '+MyApp::Custom', ## MyApp::Custom->wrap ], ); - + =item a scalar followed by a hashref - + Just like the previous, except the following C is used as arguments to initialize the middleware object. - + __PACKAGE__->config( 'psgi_middleware', [ 'Session' => {store => 'File'}, @@ -4623,6 +4783,11 @@ the encoding configuration to undef. This is recommended for temporary backwards compatibility only. +To turn it off for a single request use the L +method to turn off encoding for this request. This can be useful +when you are setting the body to be an arbitrary block of bytes, +especially if that block happens to be a block of UTF8 text. + Encoding is automatically applied when the content-type is set to a type that can be encoded. Currently we encode when the content type matches the following regular expression: @@ -4745,7 +4910,7 @@ andrewalker: André Walker Andrew Bramble -Andrew Ford EA.Ford@ford-mason.co.ukE +Andrew Ford Andrew Ruthven @@ -4759,17 +4924,19 @@ Caelum: Rafael Kitover chansen: Christian Hansen +Chase Venters + chicks: Christopher Hicks -Chisel Wright C +Chisel Wright -Danijel Milicevic C +Danijel Milicevic davewood: David Schmidt -David Kamholz Edkamholz@cpan.orgE +David Kamholz -David Naughton, C +David Naughton David E. Wheeler @@ -4791,7 +4958,7 @@ gabb: Danijel Milicevic Gary Ashton Jones -Gavin Henry C +Gavin Henry Geoff Richards @@ -4803,7 +4970,7 @@ ilmari: Dagfinn Ilmari Mannsåker jcamacho: Juan Camacho -jester: Jesse Sheidlower C +jester: Jesse Sheidlower jhannah: Jay Hannah @@ -4813,9 +4980,9 @@ Johan Lindstrom jon: Jon Schutz -Jonathan Rockway C<< >> +Jonathan Rockway -Kieren Diment C +Kieren Diment konobi: Scott McWhirter @@ -4851,7 +5018,11 @@ rafl: Florian Ragwitz random: Roland Lammel -Robert Sedlacek C<< >> +revmischa: Mischa Spiegelmock + +Robert Sedlacek + +rrwo: Robert Rothenberg SpiceMan: Marcel Montes @@ -4865,17 +5036,17 @@ Ulf Edvinsson vanstyn: Henry Van Styn -Viljo Marrandi C +Viljo Marrandi -Will Hawes C +Will Hawes willert: Sebastian Willert wreis: Wallace Reis -Yuval Kogman, C +Yuval Kogman -rainboxx: Matthias Dietrich, C +rainboxx: Matthias Dietrich dd070: Dhaval Dhanani