performance improvements in uri_for by inlining encoding
[catagits/Catalyst-Runtime.git] / lib / Catalyst.pm
index 02af3c1..ba30a67 100644 (file)
@@ -24,7 +24,7 @@ use Tree::Simple qw/use_weak_refs/;
 use Tree::Simple::Visitor::FindByUID;
 use attributes;
 use utf8;
-use Carp qw/croak/;
+use Carp qw/croak carp/;
 
 BEGIN { require 5.008001; }
 
@@ -61,7 +61,7 @@ __PACKAGE__->response_class('Catalyst::Response');
 
 # Remember to update this in Catalyst::Runtime as well!
 
-our $VERSION = '5.7006';
+our $VERSION = '5.7008';
 
 sub import {
     my ( $class, @arguments ) = @_;
@@ -511,6 +511,8 @@ Gets a L<Catalyst::Model> instance by name.
 
     $c->model('Foo')->do_stuff;
 
+Any extra arguments are directly passed to ACCEPT_CONTEXT.
+
 If the name is omitted, it will look for 
  - a model object in $c->stash{current_model_instance}, then
  - a model name in $c->stash->{current_model}, then
@@ -529,10 +531,10 @@ sub model {
           if $c->stash->{current_model_instance};
         return $c->model( $c->stash->{current_model} )
           if $c->stash->{current_model};
-        return $c->model( $c->config->{default_model} )
-          if $c->config->{default_model};
     }
-    return $c->_filter_component( $c->_comp_singular(qw/Model M/), @args );
+    return $c->model( $c->config->{default_model} )
+      if $c->config->{default_model};
+    return $c->_filter_component( $c->_comp_singular(qw/Model M/) );
 
 }
 
@@ -554,6 +556,8 @@ Gets a L<Catalyst::View> instance by name.
 
     $c->view('Foo')->do_stuff;
 
+Any extra arguments are directly passed to ACCEPT_CONTEXT.
+
 If the name is omitted, it will look for 
  - a view object in $c->stash{current_view_instance}, then
  - a view name in $c->stash->{current_view}, then
@@ -572,9 +576,9 @@ sub view {
           if $c->stash->{current_view_instance};
         return $c->view( $c->stash->{current_view} )
           if $c->stash->{current_view};
-        return $c->view( $c->config->{default_view} )
-          if $c->config->{default_view};
     }
+    return $c->view( $c->config->{default_view} )
+      if $c->config->{default_view};
     return $c->_filter_component( $c->_comp_singular(qw/View V/) );
 }
 
@@ -910,11 +914,6 @@ to C<uri_for_action>.
 
 sub uri_for {
     my ( $c, $path, @args ) = @_;
-    my $base     = $c->request->base->clone;
-    my $basepath = $base->path;
-    $basepath =~ s/\/$//;
-    $basepath .= '/';
-    my $namespace = $c->namespace || '';
 
     if ( Scalar::Util::blessed($path) ) { # action object
         my $captures = ( scalar @args && ref $args[0] eq 'ARRAY'
@@ -925,32 +924,52 @@ sub uri_for {
         $path = '/' if $path eq '';
     }
 
-    # massage namespace, empty if absolute path
-    $namespace =~ s/^\/// if $namespace;
-    $namespace .= '/' if $namespace;
-    $path ||= '';
-    $namespace = '' if $path =~ /^\//;
-    $path =~ s/^\///;
-    $path =~ s/\?/%3F/g;
+    undef($path) if (defined $path && $path eq '');
 
     my $params =
       ( scalar @args && ref $args[$#args] eq 'HASH' ? pop @args : {} );
 
-    for my $value ( values %$params ) {
-        next unless defined $value;
-        for ( ref $value eq 'ARRAY' ? @$value : $value ) {
-            $_ = "$_";
-            utf8::encode( $_ );
+    s/([^$URI::uric])/$URI::Escape::escapes{$1}/go for @args;
+
+    unshift(@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{^\.\./}{};
         }
-    };
+        unshift(@args, $namespace || '');
+    }
     
     # join args with '/', or a blank string
-    my $args = ( scalar @args ? '/' . join( '/', map {s/\?/%3F/g; $_} @args ) : '' );
-    $args =~ s/^\/// unless $path;
-    my $res =
-      URI->new_abs( URI->new_abs( "$path$args", "$basepath$namespace" ), $base )
-      ->canonical;
-    $res->query_form(%$params);
+    my $args = join('/', grep { defined($_) } @args);
+    $args =~ s/\?/%3F/g; # STUPID STUPID SPECIAL CASE
+    $args =~ s!^/!!;
+    my $base = $c->req->base;
+    my $class = ref($base);
+    $base =~ s{(?<!/)$}{/};
+
+    my $query = '';
+
+    if (my @keys = keys %$params) {
+      # somewhat lifted from URI::_query's query_form
+      $query = '?'.join('&', map {
+          s/([;\/?:@&=+,\$\[\]%])/$URI::Escape::escapes{$1}/go;
+          s/ /+/g;
+          my $key = $_;
+          my $val = $params->{$_};
+          $val = '' unless defined $val;
+          (map {
+              $_ = "$_";
+              utf8::encode( $_ );
+              # using the URI::Escape pattern here so utf8 chars survive
+              s/([^A-Za-z0-9\-_.!~*'() ])/$URI::Escape::escapes{$1}/go;
+              s/ /+/g;
+              "${key}=$_"; } ( ref $val eq 'ARRAY' ? @$val : $val ));
+      } @keys);
+    }
+
+    my $res = bless(\"${base}${args}${query}", $class);
     $res;
 }
 
@@ -1391,6 +1410,13 @@ sub finalize_headers {
     if ( my $location = $c->response->redirect ) {
         $c->log->debug(qq/Redirecting to "$location"/) if $c->debug;
         $c->response->header( Location => $location );
+        
+        if ( !$c->response->body ) {
+            # Add a default body if none is already present
+            $c->response->body(
+                qq{<html><body><p>This item has moved <a href="$location">here</a>.</p></body></html>}
+            );
+        }
     }
 
     # Content-Length