# This file documents the revision history for Perl extension Catalyst.
+5.90094 - 2015-07-24
+ - When there is a multipart POST request and the parts have extended
+ HTTP headers, try harder to decode and squeeze a meaningful value
+ out of it before giving up and crying. Updated docs and tests to
+ reflect this change.
+ - Fixed issue where last_error actually returned the first error. Took
+ the change to add a 'pop_errors' to give the inverse of shift_errors.
+ - Merged Pull Requests:
+ - https://github.com/perl-catalyst/catalyst-runtime/pull/95
+ - https://github.com/perl-catalyst/catalyst-runtime/pull/96
+ - https://github.com/perl-catalyst/catalyst-runtime/pull/97
+ - https://github.com/perl-catalyst/catalyst-runtime/pull/98
+ - https://github.com/perl-catalyst/catalyst-runtime/pull/106
+
5.90093 - 2015-05-29
- Fixed a bug where if you used $res->write and then $res->body, the
contents of body would be double encoded (gshank++).
decoded, this means that applications using this mode can correctly handle URIs including the %2F character
(i.e. with `AllowEncodedSlashes` set to `On` in Apache).
- Given that this method of path resolution is provably more correct, it is recommended that you use
+ Given that this method of path resolution is probably more correct, it is recommended that you use
this unless you have a specific need to deploy your application in a non-standard environment, and you are
aware of the implications of not being able to handle encoded URI paths correctly.
- `using_frontend_proxy_path` - Enabled [Plack::Middleware::ReverseProxyPath](https://metacpan.org/pod/Plack::Middleware::ReverseProxyPath) 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. **NOTE** if you use this feature you should add the required
-middleware to your project dependency list since its not automatically a dependency of [Catalyst](https://metacpan.org/pod/Catalyst).
+middleware to your project dependency list since it's not automatically a dependency of [Catalyst](https://metacpan.org/pod/Catalyst).
This has been done since not all people need this feature and we wish to restrict the growth of
[Catalyst](https://metacpan.org/pod/Catalyst) dependencies.
- `encoding` - See ["ENCODING"](#encoding)
- `do_not_decode_query`
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 [Catalyst](https://metacpan.org/pod/Catalyst)
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
abraxxa: Alexander Hartmaier <abraxxa@cpan.org>
-andrewalker: André Walker <andre@cpan.org>
+andrewalker: André Walker <andre@cpan.org>
Andrew Bramble
hobbs: Andrew Rodland <andrew@cleverdomain.org>
-ilmari: Dagfinn Ilmari Mannsåker <ilmari@ilmari.org>
+ilmari: Dagfinn Ilmari Mannsåker <ilmari@ilmari.org>
jcamacho: Juan Camacho
__PACKAGE__->_encode_check(Encode::FB_CROAK | Encode::LEAVE_SRC);
# Remember to update this in Catalyst::Runtime as well!
-our $VERSION = '5.90093';
+our $VERSION = '5.90094';
$VERSION = eval $VERSION if $VERSION =~ /_/; # numify for warning-free dev releases
sub import {
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<detach>,
C<go> 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
=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;
}
my $proto_value = $part_data{$key};
my ($val, @extra) = (ref($proto_value)||'') eq 'ARRAY' ? @$proto_value : ($proto_value);
+ $key = $c->_handle_param_unicode_decoding($key)
+ if ($c and $c->encoding and !$c->config->{skip_body_param_unicode_decoding});
+
if(@extra) {
- $params->{$key} = [map { Catalyst::Request::PartData->build_from_part_data($_) } ($val,@extra)];
+ $params->{$key} = [map { Catalyst::Request::PartData->build_from_part_data($c, $_) } ($val,@extra)];
} else {
- $params->{$key} = Catalyst::Request::PartData->build_from_part_data($val);
+ $params->{$key} = Catalyst::Request::PartData->build_from_part_data($c, $val);
}
}
} else {
$params = $self->_body->param;
- }
- # If we have an encoding configured (like UTF-8) in general we expect a client
- # to POST with the encoding we fufilled the request in. Otherwise don't do any
- # encoding (good change wide chars could be in HTML entity style llike the old
- # days -JNAP
+ # If we have an encoding configured (like UTF-8) in general we expect a client
+ # to POST with the encoding we fufilled the request in. Otherwise don't do any
+ # encoding (good change wide chars could be in HTML entity style llike the old
+ # days -JNAP
- # so, now that HTTP::Body prepared the body params, we gotta 'walk' the structure
- # and do any needed decoding.
+ # so, now that HTTP::Body prepared the body params, we gotta 'walk' the structure
+ # and do any needed decoding.
- # This only does something if the encoding is set via the encoding param. Remember
- # this is assuming the client is not bad and responds with what you provided. In
- # general you can just use utf8 and get away with it.
- #
- # I need to see if $c is here since this also doubles as a builder for the object :(
+ # This only does something if the encoding is set via the encoding param. Remember
+ # this is assuming the client is not bad and responds with what you provided. In
+ # general you can just use utf8 and get away with it.
+ #
+ # I need to see if $c is here since this also doubles as a builder for the object :(
- if($c and $c->encoding and !$c->config->{skip_body_param_unicode_decoding}) {
+ if($c and $c->encoding and !$c->config->{skip_body_param_unicode_decoding}) {
$params = $c->_handle_unicode_decoding($params);
+ }
}
my $return = $self->_use_hash_multivalue ?
These are the parameters from the POST part of the request, if any.
B<NOTE> If your POST is multipart, but contains non file upload parts (such
-as an line part with an alternative encoding or content type) we cannot determine
-the correct way to extra a meaningful value from the upload. In this case any
+as an line part with an alternative encoding or content type) we do our best to
+try and figure out how the value should be presented. If there's a specified character
+set we will use that to decode rather than the default encoding set by the application.
+However if there are complex headers and we cannot determine
+the correct way to extra a meaningful value from the upload, in this case any
part like this will be represented as an instance of L<Catalyst::Request::PartData>.
+Patches and review of this part of the code welcomed.
+
=head2 $req->body_params
Shortcut for body_parameters.
use Moose;
use HTTP::Headers;
+use Encode;
has [qw/raw_data name size/] => (is=>'ro', required=>1);
handles=>[qw/content_type content_encoding content_type_charset/]);
sub build_from_part_data {
- my ($class, $part_data) = @_;
+ my ($class, $c, $part_data) = @_;
+
+ # If the headers are complex, we need to work harder to figure out what to do
+ if(my $hdrs = $class->part_data_has_complex_headers($part_data)) {
+
+ # Ok so its one of two possibilities. If I can inspect the headers and
+ # Figure out what to do, the I will return data. Otherwise I will return
+ # a PartData object and expect you do deal with it.
+ # For now if I can find a charset in the content type I will just decode and
+ # assume I got it right (patches and bug reports welcomed).
+
+ # Any of these headers means I can't decode
+
+ if(
+ $hdrs->content_encoding
+ ) {
+ return $class->new(
+ raw_data => $part_data->{data},
+ name => $part_data->{name},
+ size => $part_data->{size},
+ headers => HTTP::Headers->new(%{ $part_data->{headers} }));
+ }
+
+ my ($ct, $charset) = $hdrs->content_type_charset;
+
+ if($ct) {
+ # Good news, we probably have data we can return. If there is a charset
+ # then use that to decode otherwise use the default decoding.
+ if($charset) {
+ return Encode::decode($charset, $part_data->{data})
+ } else {
+ if($c and $c->encoding and !$c->config->{skip_body_param_unicode_decoding}) {
+ return $c->_handle_param_unicode_decoding($part_data->{data});
+ } else {
+ return $part_data->{data}
+ }
+ }
+ } else {
+ # I have no idea what to do with this now..
+ return $class->new(
+ raw_data => $part_data->{data},
+ name => $part_data->{name},
+ size => $part_data->{size},
+ headers => HTTP::Headers->new(%{ $part_data->{headers} }));
+ }
+ } else {
+ if($c and $c->encoding and !$c->config->{skip_body_param_unicode_decoding}) {
+ return $c->_handle_param_unicode_decoding($part_data->{data});
+ } else {
+ return $part_data->{data}
+ }
+ }
+
return $part_data->{data} unless $class->part_data_has_complex_headers($part_data);
return $class->new(
raw_data => $part_data->{data},
sub part_data_has_complex_headers {
my ($class, $part_data) = @_;
- return scalar keys %{$part_data->{headers}} > 1 ? 1:0;
+ my %h = %{$part_data->{headers}};
+ my $hdrs = HTTP::Headers->new(%h);
+
+ # Remove non threatening headers.
+ $hdrs->remove_header('Content-Length', 'Expires', 'Last-Modified', 'Content-Language');
+
+ # If we still have more than one (Content-Disposition) header we need to understand
+ # that and deal with it.
+
+ return $hdrs->header_field_names > 1 ? $hdrs :0;
}
__PACKAGE__->meta->make_immutable;
If there is an encoding set, we encode each line of the response (the default
encoding is UTF-8).
+=head2 $res->unencoded_write( $data )
+
+Works just like ->write but we don't apply any content encoding to C<$data>. Use
+this if you are already encoding the $data or the data is arriving from an encoded
+storage.
+
=head2 $res->write_fh
Returns an instance of L<Catalyst::Response::Writer>, which is a lightweight
use Moose;
use MooseX::MethodAttributes;
+ use MooseX::Types::Moose qw(Int);
extends 'Catalyst::Controller';
- sub find :Path('') Args('Int') {
+ sub find :Path('') Args(Int) {
my ($self, $c, $int) = @_;
}
# Remember to update this in Catalyst as well!
-our $VERSION = '5.90093';
+our $VERSION = '5.90094';
$VERSION = eval $VERSION if $VERSION =~ /_/; # numify for warning-free dev releases
=head1 NAME
C<do_not_decode_query>
If true, then do not try to character decode any wide characters in your
-request URL query or keywords. You will need gto handle this manually in your action code
+request URL query or keywords. You will need to handle this manually in your action code
(although if you choose this setting, chances are you already do this).
C<default_query_encoding>
L<http://www.catalystframework.org/calendar/2013/14>.
The main difference this year is that previously calling ->write_fh would return the actual
-L<Plack> writer object that was supplied by your plack application handler, whereas now we wrap
+L<Plack> writer object that was supplied by your Plack application handler, whereas now we wrap
that object in a lightweight decorator object that proxies the C<write> and C<close> methods
and supplies an additional C<write_encoded> method. C<write_encoded> does the exact same thing
as C<write> except that it will first encode the string when necessary. In general if you are
Catalyst::Upgrading - Instructions for upgrading to the latest Catalyst
+=head1 Upgrading to Catalyst 5.90100
+
+The method C<last_error> in L</Catalyst> was actually returning the first error. This has
+been fixed but there is a small chance it could be a breaking issue for you. If this gives
+you trouble changing to C<shift_errors> is the easiest workaround (although that does
+modify the error stack so if you are relying on that not being changed you should try something
+like @{$c->errors}[-1] instead. Since this method is relatively new and the cases when the
+error stack actually has more than one error in it, we feel the exposure is very low, but bug
+reports are very welcomed.
+
=head1 Upgrading to Catalyst 5.90090
L<Catalyst::Utils> has a new method 'inject_component' which works the same as the method of
sub file_upload :POST Consumes(Multipart) Local {
my ($self, $c) = @_;
+
Test::More::is $c->req->body_parameters->{'♥'}, '♥♥';
Test::More::ok my $upload = $c->req->uploads->{file};
Test::More::is $upload->charset, 'UTF-8';
is $c->req->body_parameters->{'arg0'}, 'helloworld', 'got helloworld value';
is $c->req->body_parameters->{'♥'}, '♥♥';
-
- ok Scalar::Util::blessed($c->req->body_parameters->{'arg1'});
- ok Scalar::Util::blessed($c->req->body_parameters->{'arg2'}[0]);
- ok Scalar::Util::blessed($c->req->body_parameters->{'arg2'}[1]);
- ok Scalar::Util::blessed($c->req->body_parameters->{'♥♥♥'});
-
- # Since the form post is COMPLEX you are expected to decode it yourself.
- is Encode::decode('UTF-8', $c->req->body_parameters->{'arg1'}->raw_data), $utf8, 'decoded utf8 param';
- is Encode::decode('SHIFT_JIS', $c->req->body_parameters->{'arg2'}[0]->raw_data), $shiftjs, 'decoded shiftjis param';
- is Encode::decode('SHIFT_JIS', $c->req->body_parameters->{'arg2'}[1]->raw_data), $shiftjs, 'decoded shiftjis param';
- is Encode::decode('SHIFT_JIS', $c->req->body_parameters->{'♥♥♥'}->raw_data), $shiftjs, 'decoded shiftjis param';
+ is $c->req->body_parameters->{'arg1'}, $utf8, 'decoded utf8 param';
+ is $c->req->body_parameters->{'arg2'}[0], $shiftjs, 'decoded shiftjs param';
+ is $c->req->body_parameters->{'arg2'}[1], $shiftjs, 'decoded shiftjs param';
+ is $c->req->body_parameters->{'♥♥♥'}, $shiftjs, 'decoded shiftjs param';
}