Merge branch 'contributing-with-git' of https://github.com/simbabque/catalyst-runtime...
John Napiorkowski [Fri, 24 Jul 2015 19:40:41 +0000 (14:40 -0500)]
Changes
README.mkdn
lib/Catalyst.pm
lib/Catalyst/Request.pm
lib/Catalyst/Request/PartData.pm
lib/Catalyst/Response.pm
lib/Catalyst/RouteMatching.pod
lib/Catalyst/Runtime.pm
lib/Catalyst/UTF8.pod
lib/Catalyst/Upgrading.pod
t/utf_incoming.t

diff --git a/Changes b/Changes
index d842512..822e1c8 100644 (file)
--- a/Changes
+++ b/Changes
@@ -1,5 +1,19 @@
 # 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++).
index f30025c..d673134 100644 (file)
@@ -1440,7 +1440,7 @@ variable should be used for determining the request path.
         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.
 
@@ -1454,7 +1454,7 @@ variable should be used for determining the request path.
 - `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)
@@ -1515,7 +1515,7 @@ This has been done since not all people need this feature and we wish to restric
 - `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
@@ -2009,7 +2009,7 @@ acme: Leon Brocard <leon@astray.com>
 
 abraxxa: Alexander Hartmaier <abraxxa@cpan.org>
 
-andrewalker: André Walker <andre@cpan.org>
+andrewalker: André Walker <andre@cpan.org>
 
 Andrew Bramble
 
@@ -2067,7 +2067,7 @@ groditi: Guillermo Roditi <groditi@gmail.com>
 
 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
 
index 0148bd2..891b722 100644 (file)
@@ -180,7 +180,7 @@ sub composed_stats_class {
 __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 {
@@ -521,7 +521,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<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
@@ -640,22 +640,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;
 }
index 53f9337..3bcc6c2 100644 (file)
@@ -336,32 +336,35 @@ sub prepare_body_parameters {
         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 ?
@@ -567,10 +570,15 @@ be either a scalar or an arrayref containing scalars.
 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.
index 7089373..d6358f3 100644 (file)
@@ -2,6 +2,7 @@ package Catalyst::Request::PartData;
 
 use Moose;
 use HTTP::Headers;
+use Encode;
 
 has [qw/raw_data name size/] => (is=>'ro', required=>1);
 
@@ -11,7 +12,59 @@ has headers => (
   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},
@@ -22,7 +75,16 @@ sub build_from_part_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;
index fa15afb..e87ba61 100644 (file)
@@ -482,6 +482,12 @@ http 1.1 webservers support this).
 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
index 06df601..e5f567c 100644 (file)
@@ -87,10 +87,11 @@ is a simple example:
 
     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) = @_;
     }
 
index 8332312..5872b5d 100644 (file)
@@ -7,7 +7,7 @@ BEGIN { require 5.008003; }
 
 # 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
index 0f20099..40ba088 100644 (file)
@@ -205,7 +205,7 @@ precedence:
 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>
@@ -490,7 +490,7 @@ L<http://www.catalystframework.org/calendar/2013/12>, L<http://www.catalystframe
 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
index 0d1a60b..094191d 100644 (file)
@@ -2,6 +2,16 @@
 
 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
index ff61a6b..baa3f2a 100644 (file)
@@ -121,6 +121,7 @@ use Scalar::Util ();
 
   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';
@@ -481,17 +482,10 @@ SKIP: {
 
   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';
 
 }