X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?p=catagits%2FCatalyst-Runtime.git;a=blobdiff_plain;f=lib%2FCatalyst.pm;h=f0130271d1daf410c35f8280326190ce21ad3174;hp=dc401c150430446398ff53c76ca772a190fe61f3;hb=c1192f1ed63f124eb2d143e10b215703e7dc6284;hpb=80172e7dd3ee0325d38c0e9b10b379af529e5790 diff --git a/lib/Catalyst.pm b/lib/Catalyst.pm index dc401c1..f013027 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'); @@ -118,7 +120,7 @@ __PACKAGE__->mk_classdata($_) for qw/components arguments dispatcher engine log dispatcher_class engine_loader context_class request_class response_class stats_class setup_finished _psgi_app loading_psgi_file run_options _psgi_middleware - _data_handlers _encoding _encode_check/; + _data_handlers _encoding _encode_check finalized_default_middleware/; __PACKAGE__->dispatcher_class('Catalyst::Dispatcher'); __PACKAGE__->request_class('Catalyst::Request'); @@ -127,7 +129,8 @@ __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.90077'; +our $VERSION = '5.90089_001'; +$VERSION = eval $VERSION if $VERSION =~ /_/; # numify for warning-free dev releases sub import { my ( $class, @arguments ) = @_; @@ -495,6 +498,18 @@ Catalyst). # stash is automatically passed to the view for use in a template $c->forward( 'MyApp::View::TT' ); +The stash hash is currently stored in the PSGI C<$env> and is managed by +L. Since it's part of the C<$env> items in +the stash can be accessed in sub applications mounted under your main +L application. For example if you delegate the response of an +action to another L application, that sub application will have +access to all the stash keys of the main one, and if can of course add +more keys of its own. However those new keys will not 'bubble' back up +to the main application. + +For more information the best thing to do is to review the test case: +t/middleware-stash.t in the distribution /t directory. + =cut sub stash { @@ -518,6 +533,9 @@ Add a new error. $c->error('Something bad happened'); +Calling this will always return an arrayref (if there are no errors it +will be an empty arrayref. + =cut sub error { @@ -562,6 +580,29 @@ Returns true if you have errors 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. + +=cut + +sub last_error { my ($err, @errs) = @{shift->error}; return $err } + +=head2 shift_errors + +shifts the most recently added error off the error stack and returns if. Returns +nothing if there are no more errors. + +=cut + +sub shift_errors { + my ($self) = @_; + my ($err, @errors) = @{$self->error}; + $self->{error} = \@errors; + return $err; +} + sub _comp_search_prefixes { my $c = shift; return map $c->components->{ $_ }, $c->_comp_names_search_prefixes(@_); @@ -998,17 +1039,47 @@ And later: Your log class should implement the methods described in L. +=head2 has_encoding + +Returned True if there's a valid encoding + +=head2 clear_encoding + +Clears the encoding for the current context + =head2 encoding -Sets or gets the application encoding. +Sets or gets the application encoding. Setting encoding takes either an +Encoding object or a string that we try to resolve via L. + +You would expect to get the encoding object back if you attempt to set it. If +there is a failure you will get undef returned and an error message in the log. =cut +sub has_encoding { shift->encoding ? 1:0 } + +sub clear_encoding { + my $c = shift; + if(blessed $c) { + $c->encoding(undef); + } else { + $c->log->error("You can't clear encoding on the application"); + } +} + sub encoding { my $c = shift; my $encoding; if ( scalar @_ ) { + + # Don't let one change this once we are too far into the response + if(blessed $c && $c->res->finalized_headers) { + Carp::croak("You may not change the encoding once the headers are finalized"); + return; + } + # Let it be set to undef if (my $wanted = shift) { $encoding = Encode::find_encoding($wanted) @@ -1124,6 +1195,17 @@ Catalyst> line. B You B wrap this method with method modifiers or bad things will happen - wrap the C method instead. +B You can create a custom setup stage that will execute when the +application is starting. Use this to customize setup. + + MyApp->setup(-Custom=value); + + sub setup_custom { + my ($class, $value) = @_; + } + +Can be handy if you want to hook into the setup phase. + =cut sub setup { @@ -1320,6 +1402,8 @@ sub setup_finalize { =head2 $c->uri_for( $action, \@captures?, @args?, \%query_values? ) +=head2 $c->uri_for( $action, [@captures, @args], \%query_values? ) + Constructs an absolute L object based on the application root, the provided path, and the additional arguments and query parameters provided. When used as a string, provides a textual URI. If you need more flexibility @@ -1359,6 +1443,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 { @@ -1376,60 +1464,95 @@ sub uri_for { ( scalar @args && ref $args[$#args] eq 'HASH' ? pop @args : {} ); carp "uri_for called with undef argument" if grep { ! defined $_ } @args; + + my @encoded_args = (); foreach my $arg (@args) { - utf8::encode($arg) if utf8::is_utf8($arg); - $arg =~ s/([^$URI::uric])/$URI::Escape::escapes{$1}/go; + 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) } : ()) ]; - foreach my $capture (@$captures) { - utf8::encode($capture) if utf8::is_utf8($capture); - $capture =~ s/([^$URI::uric])/$URI::Escape::escapes{$1}/go; - } - 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; + } + + # use Devel::Dwarn;Dwarn $captures; + + if($action->has_captures_constraints) { + unless($action->match_captures($c, $captures)) { + carp "@{$captures} do not match the type constraints in $action"; + } } - $path = $c->dispatcher->uri_for_action($action, $captures); + $path = $c->dispatcher->uri_for_action($action, $captures); if (not defined $path) { $c->log->debug(qq/Can't find uri_for action '$action' @$captures/) if $c->debug; return undef; } $path = '/' if $path eq ''; + + # At this point @encoded_args is the remaining Args (all captures removed). + if($action->has_args_constraints) { + unless($action->match_args($c,\@encoded_args)) { + carp "@encoded_args do not match the type constraints in $action"; + } + } } - 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{(?{$_}; - s/([;\/?:@&=+,\$\[\]%])/$URI::Escape::escapes{$1}/go; + #s/([;\/?:@&=+,\$\[\]%])/$URI::Escape::escapes{$1}/go; ## Commented out because seems to lead to double encoding - JNAP s/ /+/g; my $key = $_; $val = '' unless defined $val; (map { my $param = "$_"; - utf8::encode( $param ) if utf8::is_utf8($param); + $param = encode_utf8($param); # 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 )); } @keys); } + warn $base; + warn $args; + my $res = bless(\"${base}${args}${query}", $class); $res; } @@ -1991,6 +2123,8 @@ sub finalize_headers { $c->finalize_cookies; + # This currently is a NOOP but I don't want to remove it since I guess people + # might have Response subclasses that use it for something... (JNAP) $c->response->finalize_headers(); # Done @@ -1999,42 +2133,49 @@ sub finalize_headers { =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 explicitly setting the +encoding configuration value to undef. + +We can only encode when the body is a scalar. Methods for encoding via the +streaming interfaces (such as C and C on L +are available). + See L. =cut sub finalize_encoding { my $c = shift; - - my $body = $c->response->body; - - return unless defined($body); - - my $enc = $c->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); + my $res = $c->res || return; + + # Warn if the set charset is different from the one you put into encoding. We need + # 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 + (uc($c->encoding->mime_name) ne uc($res->content_type_charset)) + ) { + my $ct = lc($res->content_type_charset); + $c->log->debug("Catalyst encoding config is set to encode in '" . + $c->encoding->mime_name . + "', content type is '$ct', not encoding "); } - # 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'; + if( + ($res->encodable_response) and + (defined($res->body)) and + (ref(\$res->body) eq 'SCALAR') + ) { + $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 + # confusing action at a distance here.. + # Don't try to set the charset if one already exists + $c->res->content_type($c->res->content_type . "; charset=" . $c->encoding->mime_name) + unless($c->res->content_type_charset); + } } =head2 $c->finalize_output @@ -2248,7 +2389,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 @@ -2342,6 +2483,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); @@ -2527,37 +2672,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 @@ -2932,15 +3046,30 @@ EOW Adds the following L middlewares to your application, since they are useful and commonly needed: -L, (conditionally added based on the status -of your $ENV{REMOTE_ADDR}, and can be forced on with C -or forced off with C), L -(if you are using Lighttpd), L (always -applied since this middleware is smart enough to conditionally apply itself). +L (if you are using Lighttpd), +L (always applied since this middleware +is smart enough to conditionally apply itself). + +We will also automatically add L if we notice +that your HTTP $env variable C is '127.0.0.1'. This is usually +an indication that your server is running behind a proxy frontend. However in +2014 this is often not the case. We preserve this code for backwards compatibility +however I B recommend that if you are running the server behind a front +end proxy that you clearly indicate so with the C configuration +setting to true for your environment configurations that run behind a proxy. This +way if you change your front end proxy address someday your code would inexplicably +stop working as expected. Additionally if we detect we are using Nginx, we add a bit of custom middleware to solve some problems with the way that server handles $ENV{PATH_INFO} and -$ENV{SCRIPT_NAME} +$ENV{SCRIPT_NAME}. + +Please B that if you do use C the middleware is now +adding via C rather than this method. + +If you are using Lighttpd or IIS6 you may wish to apply these middlewares. In +general this is no longer a common case but we have this here for backward +compatibility. =cut @@ -2948,16 +3077,21 @@ $ENV{SCRIPT_NAME} sub apply_default_middlewares { my ($app, $psgi_app) = @_; - $psgi_app = Plack::Middleware::Conditional->wrap( - $psgi_app, - builder => sub { Plack::Middleware::ReverseProxy->wrap($_[0]) }, - condition => sub { - my ($env) = @_; - return if $app->config->{ignore_frontend_proxy}; - return $env->{REMOTE_ADDR} eq '127.0.0.1' - || $app->config->{using_frontend_proxy}; - }, - ); + # Don't add this conditional IF we are explicitly saying we want the + # frontend proxy support. We don't need it here since if that is the + # case it will be always loaded in the default_middleware. + + unless($app->config->{using_frontend_proxy}) { + $psgi_app = Plack::Middleware::Conditional->wrap( + $psgi_app, + builder => sub { Plack::Middleware::ReverseProxy->wrap($_[0]) }, + condition => sub { + my ($env) = @_; + return if $app->config->{ignore_frontend_proxy}; + return $env->{REMOTE_ADDR} eq '127.0.0.1'; + }, + ); + } # If we're running under Lighttpd, swap PATH_INFO and SCRIPT_NAME # http://lists.scsys.co.uk/pipermail/catalyst/2006-June/008361.html @@ -2990,17 +3124,34 @@ sub apply_default_middlewares { return $psgi_app; } -=head2 $c->psgi_app +=head2 App->psgi_app + +=head2 App->to_app Returns a PSGI application code reference for the catalyst application -C<$c>. This is the bare application without any middlewares -applied. C<${myapp}.psgi> is not taken into account. +C<$c>. This is the bare application created without the C +method called. We do however apply C since those are +integral to how L functions. Also, unlike starting your application +with a generated server script (via L and C) we do +not attempt to return a valid L application using any existing C<${myapp}.psgi> +scripts in your $HOME directory. + +B C was originally created when the first PSGI +port was done for v5.90000. These are middlewares that are added to achieve +backward compatibility with older applications. If you start your application +using one of the supplied server scripts (generated with L and +the project skeleton script C) we apply C +automatically. This was done so that pre and post PSGI port applications would +work the same way. This is what you want to be using to retrieve the PSGI application code -reference of your Catalyst application for use in F<.psgi> files. +reference of your Catalyst application for use in a custom F<.psgi> or in your +own created server modules. =cut +*to_app = \&psgi_app; + sub psgi_app { my ($app) = @_; my $psgi = $app->engine->build_psgi_app($app); @@ -3037,8 +3188,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 @@ -3076,8 +3233,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; } @@ -3089,12 +3251,11 @@ sub _handle_unicode_decoding { sub _handle_param_unicode_decoding { my ( $self, $value ) = @_; 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 try { - Encode::is_utf8( $value ) ? - $value - : $enc->decode( $value, $self->_encode_check ); + $enc->decode( $value, $self->_encode_check ); } catch { $self->handle_unicode_encoding_exception({ @@ -3266,6 +3427,68 @@ the plugin name does not begin with C. } } +=head2 default_middleware + +Returns a list of instantiated PSGI middleware objects which is the default +middleware that is active for this application (taking any configuration +options into account, excluding your custom added middleware via the C +configuration option). You can override this method if you wish to change +the default middleware (although do so at risk since some middleware is vital +to application function.) + +The current default middleware list is: + + Catalyst::Middleware::Stash + Plack::Middleware::HTTPExceptions + Plack::Middleware::RemoveRedundantBody + Plack::Middleware::FixMissingBodyInRedirect + Plack::Middleware::ContentLength + Plack::Middleware::MethodOverride + Plack::Middleware::Head + +If the configuration setting C is true we add: + + Plack::Middleware::ReverseProxy + +If the configuration setting C is true we add: + + Plack::Middleware::ReverseProxyPath + +But B that L is not a dependency of the +L distribution so if you want to use this option you should add it to +your project distribution file. + +These middlewares will be added at L during the +L phase of application startup. + +=cut + +sub default_middleware { + my $class = shift; + my @mw = ( + Catalyst::Middleware::Stash->new, + Plack::Middleware::HTTPExceptions->new, + Plack::Middleware::RemoveRedundantBody->new, + Plack::Middleware::FixMissingBodyInRedirect->new, + Plack::Middleware::ContentLength->new, + Plack::Middleware::MethodOverride->new, + Plack::Middleware::Head->new); + + if($class->config->{using_frontend_proxy}) { + push @mw, Plack::Middleware::ReverseProxy->new; + } + + if($class->config->{using_frontend_proxy_path}) { + if(Class::Load::try_load_class('Plack::Middleware::ReverseProxyPath')) { + push @mw, Plack::Middleware::ReverseProxyPath->new; + } else { + $class->log->error("Cannot use configuration 'using_frontend_proxy_path' because 'Plack::Middleware::ReverseProxyPath' is not installed"); + } + } + + return @mw; +} + =head2 registered_middlewares Read only accessor that returns an array of all the middleware in the order @@ -3307,15 +3530,13 @@ up. sub registered_middlewares { my $class = shift; if(my $middleware = $class->_psgi_middleware) { - return ( - Catalyst::Middleware::Stash->new, - Plack::Middleware::HTTPExceptions->new, - Plack::Middleware::RemoveRedundantBody->new, - Plack::Middleware::FixMissingBodyInRedirect->new, - Plack::Middleware::ContentLength->new, - Plack::Middleware::MethodOverride->new, - Plack::Middleware::Head->new, - @$middleware); + my @mw = ($class->default_middleware, @$middleware); + + if($class->config->{using_frontend_proxy}) { + push @mw, Plack::Middleware::ReverseProxy->new; + } + + return @mw; } else { die "You cannot call ->registered_middlewares until middleware has been setup"; } @@ -3323,8 +3544,17 @@ sub registered_middlewares { sub setup_middleware { my $class = shift; - my @middleware_definitions = @_ ? - reverse(@_) : reverse(@{$class->config->{'psgi_middleware'}||[]}); + my @middleware_definitions; + + # If someone calls this method you can add middleware with args. However if its + # called without an arg we need to setup the configuration middleware. + if(@_) { + @middleware_definitions = reverse(@_); + } else { + @middleware_definitions = reverse(@{$class->config->{'psgi_middleware'}||[]}) + unless $class->finalized_default_middleware; + $class->finalized_default_middleware(1); # Only do this once, just in case some people call setup over and over... + } my @middleware = (); while(my $next = shift(@middleware_definitions)) { @@ -3415,9 +3645,15 @@ sub default_data_handlers { ->can('build_cgi_struct')->($params); }, 'application/json' => sub { - Class::Load::load_first_existing_class('JSON::MaybeXS', 'JSON') - ->can('decode_json')->(do { local $/; $_->getline }); - }, + my ($fh, $req) = @_; + my $parser = Class::Load::load_first_existing_class('JSON::MaybeXS', 'JSON'); + my $slurped; + return eval { + local $/; + $slurped = $fh->getline; + $parser->can("decode_json")->($slurped); # decode_json does utf8 decoding for us + } || Catalyst::Exception->throw(sprintf "Error Parsing POST '%s', Error: %s", (defined($slurped) ? $slurped : 'undef') ,$@); + }, }; } @@ -3612,8 +3848,20 @@ C - See L. =item * +C - Enabled L on your application (if +installed, otherwise log an error). This is useful if your application is not running on the +'root' (or /) of your host server. B if you use this feature you should add the required +middleware to your project dependency list since its not automatically a dependency of L. +This has been done since not all people need this feature and we wish to restrict the growth of +L dependencies. + +=item * + C - See L +This now defaults to 'UTF-8'. You my turn it off by setting this configuration +value to undef. + =item * C @@ -3653,6 +3901,72 @@ backwardly compatible). =item * +C + +When creating body parameters from a POST, if we run into a multpart 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). + +=item * + +C + +Generally we decode incoming POST params based on your declared encoding (the +default for this is to decode UTF-8). If this is causing you trouble and you +do not wish to turn all encoding support off (with the C configuration +parameter) you may disable this step atomically by setting this configuration +parameter to true. + +=item * + +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 +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 +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 +the UTF-8 decoding globally using this configuration. + +This setting takes precedence over C and +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. + +This setting take precedence over C. + +=item * + +C + +Setting this to true will default your query decoding to whatever your +general global encoding is (the default is UTF-8). + +=item * + +C + +In older versions of Catalyst, when more than one action matched the same path +AND all those matching actions declared Args(0), we'd break the tie by choosing +the first action defined. We now normalized how Args(0) works so that it +follows the same rule as Args(N), which is to say when we need to break a tie +we choose the LAST action defined. If this breaks your code and you don't +have time to update to follow the new normalized approach, you may set this +value to true and it will globally revert to the original chaining behavior. + +=item * + C - See L. =item * @@ -3956,8 +4270,45 @@ Please see L for more on middleware. =head1 ENCODING -On request, decodes all params from encoding into a sequence of -logical characters. On response, encodes body into encoding. +Starting in L version 5.90080 encoding is automatically enabled +and set to encode all body responses to UTF8 when possible and applicable. +Following is documentation on this process. If you are using an older +version of L you should review documentation for that version since +a lot has changed. + +By default encoding is now 'UTF-8'. You may turn it off by setting +the encoding configuration to undef. + + MyApp->config(encoding => undef); + +This is recommended for temporary backwards compatibility only. + +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: + + $content_type =~ /^text|xml$|javascript$/ + +Encoding is set on the application, but it is copied to the context object +so that you can override it on a request basis. + +Be default we don't automatically encode 'application/json' since the most +common approaches to generating this type of response (Either via L +or L) will do so already and we want to avoid double +encoding issues. + +If you are producing JSON response in an unconventional manner (such +as via a template or manual strings) you should perform the UTF8 encoding +manually as well such as to conform to the JSON specification. + +NOTE: We also examine the value of $c->response->content_encoding. If +you set this (like for example 'gzip', and manually gzipping the body) +we assume that you have done all the necessary encoding yourself, since +we cannot encode the gzipped contents. If you use a plugin like +L you need to update to a modern version in order +to have this function correctly with the new UTF8 encoding code, or you +can use L or (probably best) do your compression on +a front end proxy. =head2 Methods @@ -4050,6 +4401,8 @@ acme: Leon Brocard abraxxa: Alexander Hartmaier +andrewalker: André Walker + Andrew Bramble Andrew Ford EA.Ford@ford-mason.co.ukE @@ -4188,9 +4541,11 @@ dd070: Dhaval Dhanani Upasana +John Napiorkowski (jnap) + =head1 COPYRIGHT -Copyright (c) 2005-2014, the above named PROJECT FOUNDER and CONTRIBUTORS. +Copyright (c) 2005-2015, the above named PROJECT FOUNDER and CONTRIBUTORS. =head1 LICENSE