Merge branch 'topic/debug_warnings'
[catagits/Catalyst-Runtime.git] / lib / Catalyst / Engine.pm
index 194d254..fdd3df9 100644 (file)
@@ -10,10 +10,11 @@ use HTML::Entities;
 use HTTP::Headers;
 use Plack::Loader;
 use Catalyst::EngineLoader;
-use Encode 2.21 'decode_utf8';
+use Encode 2.21 'decode_utf8', 'encode', 'decode';
 use Plack::Request::Upload;
 use Hash::MultiValue;
 use namespace::clean -except => 'meta';
+use utf8;
 
 # Amount of data to read from input on each pass
 our $CHUNKSIZE = 64 * 1024;
@@ -129,7 +130,6 @@ sub finalize_body {
             # There's no body...
             $body = [];
         }
-
         $res->_response_cb->([ $res->status, \@headers, $body]);
         $res->_clear_response_cb;
 
@@ -159,11 +159,11 @@ sub finalize_body {
           }
           else {
               
-              # Case where body was set afgter calling ->write.  We'd prefer not to
+              # Case where body was set after calling ->write.  We'd prefer not to
               # support this, but I can see some use cases with the way most of the
-              # views work.
-
-              $self->write($c, $body );
+              # views work. Since body has already been encoded, we need to do
+              # an 'unencoded_write' here.
+              $self->unencoded_write( $c, $body );
           }
         }
 
@@ -573,14 +573,17 @@ process the query string and extract query parameters.
 sub prepare_query_parameters {
     my ($self, $c) = @_;
     my $env = $c->request->env;
-
-    if(my $query_obj = $env->{'plack.request.query'}) {
-         $c->request->query_parameters(
-           $c->request->_use_hash_multivalue ?
-              $query_obj->clone :
-              $query_obj->as_hashref_mixed);
-         return;
-    }
+    my $do_not_decode_query = $c->config->{do_not_decode_query};
+    my $default_query_encoding = $c->config->{default_query_encoding} || 
+      ($c->config->{decode_query_using_global_encoding} ?
+        $c->encoding : 'UTF-8');
+
+    my $decoder = sub {
+      my $str = shift;
+      return $str if $do_not_decode_query;
+      return $str unless $default_query_encoding;
+      return decode( $default_query_encoding, $str);
+    };
 
     my $query_string = exists $env->{QUERY_STRING}
         ? $env->{QUERY_STRING}
@@ -590,46 +593,20 @@ sub prepare_query_parameters {
     # (yes, index() is faster than a regex :))
     if ( index( $query_string, '=' ) < 0 ) {
         my $keywords = $self->unescape_uri($query_string);
-        $keywords = decode_utf8 $keywords;
+        $keywords = $decoder->($keywords);
         $c->request->query_keywords($keywords);
         return;
     }
 
-    my %query;
-
-    # replace semi-colons
-    $query_string =~ s/;/&/g;
-
-    my @params = grep { length $_ } split /&/, $query_string;
+    $query_string =~ s/\A[&;]+//;
 
-    for my $item ( @params ) {
-
-        my ($param, $value)
-            = map { decode_utf8($self->unescape_uri($_)) }
-              split( /=/, $item, 2 );
-
-        unless(defined $param) {
-            $param = $self->unescape_uri($item);
-            $param = decode_utf8 $param;
-        }
-
-        if ( exists $query{$param} ) {
-            if ( ref $query{$param} ) {
-                push @{ $query{$param} }, $value;
-            }
-            else {
-                $query{$param} = [ $query{$param}, $value ];
-            }
-        }
-        else {
-            $query{$param} = $value;
-        }
-    }
+    my $p = Hash::MultiValue->new(
+        map { defined $_ ? $decoder->($self->unescape_uri($_)) : $_ }
+        map { ( split /=/, $_, 2 )[0,1] } # slice forces two elements
+        split /[&;]+/, $query_string
+    );
 
-    $c->request->query_parameters( 
-      $c->request->_use_hash_multivalue ?
-        Hash::MultiValue->from_mixed(\%query) :
-        \%query);
+    $c->request->query_parameters( $c->request->_use_hash_multivalue ? $p : $p->mixed );
 }
 
 =head2 $self->prepare_read($c)
@@ -673,8 +650,8 @@ sub prepare_uploads {
     my $uploads = $request->_body->upload;
     my $parameters = $request->parameters;
     foreach my $name (keys %$uploads) {
-        $name = $c->_handle_unicode_decoding($name) if $enc;
         my $files = $uploads->{$name};
+        $name = $c->_handle_unicode_decoding($name) if $enc;
         my @uploads;
         for my $upload (ref $files eq 'ARRAY' ? @$files : ($files)) {
             my $headers = HTTP::Headers->new( %{ $upload->{headers} } );
@@ -685,6 +662,7 @@ sub prepare_uploads {
               (
                size => $upload->{size},
                type => scalar $headers->content_type,
+               charset => scalar $headers->content_type_charset,
                headers => $headers,
                tempname => $upload->{tempname},
                filename => $filename,
@@ -722,6 +700,20 @@ sub write {
     $c->response->write($buffer);
 }
 
+=head2 $self->unencoded_write($c, $buffer)
+
+Writes the buffer to the client without encoding. Necessary for
+already encoded buffers. Used when a $c->write has been done
+followed by $c->res->body.
+
+=cut
+
+sub unencoded_write {
+    my ( $self, $c, $buffer ) = @_;
+
+    $c->response->unencoded_write($buffer);
+}
+
 =head2 $self->read($c, [$maxlength])
 
 Reads from the input stream by calling C<< $self->read_chunk >>.