Apply patch for redacting parameters in the log.
[catagits/Catalyst-Runtime.git] / lib / Catalyst.pm
index 15b231b..21c04d8 100644 (file)
@@ -1,14 +1,9 @@
 package Catalyst;
 
-# we don't need really need this, but if we load it before MRO::Compat gets
-# loaded (via Moose and Class::MOP), we can avoid some nasty warnings
-use Class::C3;
-
 use Moose;
-use Class::MOP::Object ();
 extends 'Catalyst::Component';
 use bytes;
-use B::Hooks::EndOfScope;
+use Scope::Upper ();
 use Catalyst::Exception;
 use Catalyst::Log;
 use Catalyst::Request;
@@ -26,12 +21,12 @@ use Time::HiRes qw/gettimeofday tv_interval/;
 use URI ();
 use URI::http;
 use URI::https;
-use Scalar::Util qw/weaken blessed/;
+use Scalar::Util qw/weaken/;
 use Tree::Simple qw/use_weak_refs/;
 use Tree::Simple::Visitor::FindByUID;
 use attributes;
 use utf8;
-use Carp qw/croak carp/;
+use Carp qw/croak carp shortmess/;
 
 BEGIN { require 5.008001; }
 
@@ -82,7 +77,7 @@ __PACKAGE__->stats_class('Catalyst::Stats');
 
 # Remember to update this in Catalyst::Runtime as well!
 
-our $VERSION = '5.8000_04';
+our $VERSION = '5.8000_05';
 
 sub import {
     my ( $class, @arguments ) = @_;
@@ -362,6 +357,13 @@ Almost the same as C<forward>, but does a full dispatch, instead of just
 calling the new C<$action> / C<$class-E<gt>$method>. This means that C<begin>,
 C<auto> and the method you go to are called, just like a new request.
 
+In addition both C<< $c->action >> and C<< $c->namespace >> are localized.
+This means, for example, that $c->action methods such as C<name>, C<class> and
+C<reverse> return information for the visited action when they are invoked
+within the visited action.  This is different from the behavior of C<forward>
+which continues to use the $c->action object from the caller action even when
+invoked from the callee.
+
 C<$c-E<gt>stash> is kept unchanged.
 
 In effect, C<visit> allows you to "wrap" another action, just as it
@@ -511,9 +513,24 @@ sub _comp_search_prefixes {
 
     # don't warn if we didn't find any results, it just might not exist
     if( @result ) {
-        $c->log->warn( qq(Found results for "${name}" using regexp fallback.) );
-        $c->log->warn( 'Relying on the regexp fallback behavior for component resolution is unreliable and unsafe.' );
-        $c->log->warn( 'If you really want to search, pass in a regexp as the argument.' );
+        my $msg = "Used regexp fallback for \$c->model('${name}'), which found '" .
+           (join '", "', @result) . "'. Relying on regexp fallback behavior for " .
+           "component resolution is unreliable and unsafe.";
+        my $short = $result[0];
+        $short =~ s/.*?Model:://;
+        my $shortmess = Carp::shortmess('');
+        if ($shortmess =~ m#Catalyst/Plugin#) {
+           $msg .= " You probably need to set '$short' instead of '${name}' in this " .
+              "plugin's config";
+        } elsif ($shortmess =~ m#Catalyst/lib/(View|Controller)#) {
+           $msg .= " You probably need to set '$short' instead of '${name}' in this " .
+              "component's config";
+        } else {
+           $msg .= " You probably meant \$c->model('$short') instead of \$c->model{'${name}'}, " .
+              "but if you really wanted to search, pass in a regexp as the argument " .
+              "like so: \$c->model(qr/${name}/)";
+        }
+        $c->log->warn( "${msg}$shortmess" );
     }
 
     return @result;
@@ -614,11 +631,11 @@ sub model {
     my( $comp, $rest ) = $c->_comp_search_prefixes( undef, qw/Model M/);
 
     if( $rest ) {
-        $c->log->warn( 'Calling $c->model() will return a random model unless you specify one of:' );
+        $c->log->warn( Carp::shortmess('Calling $c->model() will return a random model unless you specify one of:') );
         $c->log->warn( '* $c->config->{default_model} # the name of the default model to use' );
         $c->log->warn( '* $c->stash->{current_model} # the name of the model to use for this request' );
         $c->log->warn( '* $c->stash->{current_model_instance} # the instance of the model to use for this request' );
-        $c->log->warn( 'NB: in version 5.80, the "random" behavior will not work at all.' );
+        $c->log->warn( 'NB: in version 5.81, the "random" behavior will not work at all.' );
     }
 
     return $c->_filter_component( $comp );
@@ -671,7 +688,7 @@ sub view {
         $c->log->warn( '* $c->config->{default_view} # the name of the default view to use' );
         $c->log->warn( '* $c->stash->{current_view} # the name of the view to use for this request' );
         $c->log->warn( '* $c->stash->{current_view_instance} # the instance of the view to use for this request' );
-        $c->log->warn( 'NB: in version 5.80, the "random" behavior will not work at all.' );
+        $c->log->warn( 'NB: in version 5.81, the "random" behavior will not work at all.' );
     }
 
     return $c->_filter_component( $comp );
@@ -753,7 +770,7 @@ sub component {
         return map { $c->_filter_component( $_, @args ) } @result if ref $name;
 
         if( $result[ 0 ] ) {
-            $c->log->warn( qq(Found results for "${name}" using regexp fallback.) );
+            $c->log->warn( Carp::shortmess(qq(Found results for "${name}" using regexp fallback)) );
             $c->log->warn( 'Relying on the regexp fallback behavior for component resolution' );
             $c->log->warn( 'is unreliable and unsafe. You have been warned' );
             return $c->_filter_component( $result[ 0 ], @args );
@@ -862,11 +879,19 @@ loads and instantiates the given class.
     MyApp->plugin( 'prototype', 'HTML::Prototype' );
 
     $c->prototype->define_javascript_functions;
+    
+B<Note:> This method of adding plugins is deprecated. The ability
+to add plugins like this B<will be removed> in a Catalyst 5.9.
+Please do not use this functionality in new code.
 
 =cut
 
 sub plugin {
     my ( $class, $name, $plugin, @args ) = @_;
+
+    # See block comment in t/unit_core_plugin.t    
+    $class->log->debug(qq/Adding plugin using the ->plugin method is deprecated, and will be removed in Catalyst 5.9/);
+    
     $class->_register_plugin( $plugin, 1 );
 
     eval { $plugin->import };
@@ -964,7 +989,8 @@ EOF
         my @plugins = map { "$_  " . ( $_->VERSION || '' ) } $class->registered_plugins;
 
         if (@plugins) {
-            my $t = Text::SimpleTable->new(74);
+            my $column_width = Catalyst::Utils::term_width() - 6;
+            my $t = Text::SimpleTable->new($column_width);
             $t->row($_) for @plugins;
             $class->log->debug( "Loaded plugins:\n" . $t->draw . "\n" );
         }
@@ -973,8 +999,8 @@ EOF
         my $engine     = $class->engine;
         my $home       = $class->config->{home};
 
-        $class->log->debug(qq/Loaded dispatcher "$dispatcher"/);
-        $class->log->debug(qq/Loaded engine "$engine"/);
+        $class->log->debug(sprintf(q/Loaded dispatcher "%s"/, blessed($dispatcher)));
+        $class->log->debug(sprintf(q/Loaded engine "%s"/, blessed($engine)));
 
         $home
           ? ( -d $home )
@@ -983,7 +1009,7 @@ EOF
           : $class->log->debug(q/Couldn't find home/);
     }
 
-    # Call plugins setup
+    # Call plugins setup, this is stupid and evil.
     {
         no warnings qw/redefine/;
         local *setup = sub { };
@@ -996,7 +1022,8 @@ EOF
     $class->setup_components;
 
     if ( $class->debug ) {
-        my $t = Text::SimpleTable->new( [ 63, 'Class' ], [ 8, 'Type' ] );
+        my $column_width = Catalyst::Utils::term_width() - 8 - 9;
+        my $t = Text::SimpleTable->new( [ $column_width, 'Class' ], [ 8, 'Type' ] );
         for my $comp ( sort keys %{ $class->components } ) {
             my $type = ref $class->components->{$comp} ? 'instance' : 'class';
             $t->row( $comp, $type );
@@ -1024,11 +1051,36 @@ EOF
     # Note however that we have to do the work on scope end, so that method
     # modifiers work correctly in MyApp (as you have to call setup _before_ 
     # applying modifiers).
-    on_scope_end {
-        my $meta = $class->Moose::Object::meta();
+    Scope::Upper::reap(sub {
+        my $meta = Class::MOP::get_metaclass_by_name($class);
         $meta->make_immutable unless $meta->is_immutable;
-    };
+    }, Scope::Upper::SCOPE(1));
+
+    $class->setup_finalize;
+}
+
+
+=head2 $app->setup_finalize
+
+A hook to attach modifiers to.
+Using C< after setup => sub{}; > doesn't work, because of quirky things done for plugin setup.
+Also better than C< setup_finished(); >, as that is a getter method.
+
+    sub setup_finalize {
 
+        my $app = shift;
+
+        ## do stuff, i.e., determine a primary key column for sessions stored in a DB
+
+        $app->next::method(@_);
+
+
+    }
+
+=cut
+
+sub setup_finalize {
+    my ($class) = @_;
     $class->setup_finished(1);
 }
 
@@ -1747,24 +1799,17 @@ Prepares message body.
 sub prepare_body {
     my $c = shift;
 
-    #Moose TODO: what is  _body ??
-    # Do we run for the first time?
-    return if defined $c->request->{_body};
+    return if $c->request->_has_body;
 
     # Initialize on-demand data
     $c->engine->prepare_body( $c, @_ );
     $c->prepare_parameters;
     $c->prepare_uploads;
 
-    if ( $c->debug && keys %{ $c->req->body_parameters } ) {
-        my $t = Text::SimpleTable->new( [ 35, 'Parameter' ], [ 36, 'Value' ] );
-        for my $key ( sort keys %{ $c->req->body_parameters } ) {
-            my $param = $c->req->body_parameters->{$key};
-            my $value = defined($param) ? $param : '';
-            $t->row( $key,
-                ref $value eq 'ARRAY' ? ( join ', ', @$value ) : $value );
-        }
-        $c->log->debug( "Body Parameters are:\n" . $t->draw );
+    if ( $c->debug ) {
+        $c->log_parameters( 
+            'Body Parameters are', $c->request->body_parameters
+        );
     }
 }
 
@@ -1850,15 +1895,65 @@ sub prepare_query_parameters {
 
     $c->engine->prepare_query_parameters( $c, @_ );
 
-    if ( $c->debug && keys %{ $c->request->query_parameters } ) {
-        my $t = Text::SimpleTable->new( [ 35, 'Parameter' ], [ 36, 'Value' ] );
-        for my $key ( sort keys %{ $c->req->query_parameters } ) {
-            my $param = $c->req->query_parameters->{$key};
+    if ( $c->debug ) {
+        $c->log_parameters( 
+            'Query Parameters are', $c->request->query_parameters 
+        );
+    }
+}
+
+=head2 $c->log_parameters($name, $parameters)
+
+Logs a hash reference of key value pairs, with a caption above the table.
+
+Looks like:
+
+ [debug] Query Parameters are:
+ .-------------------------------------+--------------------------------------.
+ | Parameter                           | Value                                |
+ +-------------------------------------+--------------------------------------+
+ | search                              | Moose                                |
+ | searchtype                          | modules                              |
+ '-------------------------------------+--------------------------------------'
+
+If there are query parameters you don't want to display in this output, such
+as passwords or other sensitive input, you can configure your application to
+redact those parameters:
+
+  C<< MyApp->config->{Debug}->{redact_parameters} = [ 'password' ] >>
+
+In that case, the output will look like:
+
+ [debug] Query Parameters are:
+ .-------------------------------------+--------------------------------------.
+ | Parameter                           | Value                                |
+ +-------------------------------------+--------------------------------------+
+ | password                            | (redacted by config)                 |
+ | username                            | some_user                            |
+ '-------------------------------------+--------------------------------------'
+
+=cut
+
+sub log_parameters {
+    my ( $c, $name, $parameters ) = @_;
+
+    my $skip = $c->config->{Debug}->{redact_parameters};
+    if ( 
+        ( not defined $skip or ref $skip eq 'ARRAY' )
+        && keys %{ $parameters } 
+     ) {
+        my $t = Text::SimpleTable->new( 
+            [ 35, 'Parameter' ], [ 36, 'Value' ] );
+        my %skip_params = map { $_ => $_ } @{ $skip || [] };
+        for my $key ( sort keys %$parameters ) {
+            my $param = $parameters->{$key};
             my $value = defined($param) ? $param : '';
+            $value = '(redacted by config)' if exists $skip_params{$key};
+
             $t->row( $key,
                 ref $value eq 'ARRAY' ? ( join ', ', @$value ) : $value );
         }
-        $c->log->debug( "Query Parameters are:\n" . $t->draw );
+        $c->log->debug( "$name:\n" . $t->draw );
     }
 }
 
@@ -2096,7 +2191,7 @@ sub setup_engine {
     }
 
     if ( $ENV{MOD_PERL} ) {
-        my $meta = $class->Class::MOP::Object::meta();
+        my $meta = Class::MOP::get_metaclass_by_name($class);
         
         # create the apache method
         $meta->add_method('apache' => sub { shift->engine->apache });
@@ -2228,25 +2323,25 @@ or if the C<$CATALYST_DEBUG> environment variable is set to a true value.
 
 Note that if the log has already been setup, by either a previous call to
 C<setup_log> or by a call such as C<< __PACKAGE__->log( MyLogger->new ) >>,
-that this method won't actually set up the log.
+that this method won't actually set up the log object.
 
 =cut
 
 sub setup_log {
     my ( $class, $levels ) = @_;
 
-    my %levels;
+    $levels ||= '';
+    $levels =~ s/^\s+//;
+    $levels =~ s/\s+$//;
+    my %levels = map { $_ => 1 } split /\s*,\s*/, $levels || '';
+    
     unless ( $class->log ) {
-        $levels ||= '';
-        $levels =~ s/^\s+//;
-        $levels =~ s/\s+$//;
-        %levels = map { $_ => 1 } split /\s*,\s*/, $levels || '';
         $class->log( Catalyst::Log->new(keys %levels) );
     }
 
     my $env_debug = Catalyst::Utils::env_value( $class, 'DEBUG' );
     if ( defined($env_debug) or $levels{debug} ) {
-        $class->Class::MOP::Object::meta()->add_method('debug' => sub { 1 });
+        Class::MOP::get_metaclass_by_name($class)->add_method('debug' => sub { 1 });
         $class->log->debug('Debug messages enabled');
     }
 }
@@ -2270,7 +2365,7 @@ sub setup_stats {
 
     my $env = Catalyst::Utils::env_value( $class, 'STATS' );
     if ( defined($env) ? $env : ($stats || $class->debug ) ) {
-        $class->Class::MOP::Object::meta()->add_method('use_stats' => sub { 1 });
+        Class::MOP::get_metaclass_by_name($class)->add_method('use_stats' => sub { 1 });
         $class->log->debug('Statistics enabled');
     }
 }
@@ -2313,7 +2408,7 @@ the plugin name does not begin with C<Catalyst::Plugin::>.
         $proto->_plugins->{$plugin} = 1;
         unless ($instant) {
             no strict 'refs';
-            if ( my $meta = $class->Class::MOP::Object::meta() ) {
+            if ( my $meta = Class::MOP::get_metaclass_by_name($class) ) {
               my @superclasses = ($plugin, $meta->superclasses );
               $meta->superclasses(@superclasses);
             } else {
@@ -2515,6 +2610,8 @@ audreyt: Audrey Tang
 
 bricas: Brian Cassidy <bricas@cpan.org>
 
+Byron Young <Byron.Young@riverbed.com>
+
 Caelum: Rafael Kitover <rkitover@io.com>
 
 chansen: Christian Hansen
@@ -2541,6 +2638,8 @@ ilmari: Dagfinn Ilmari MannsÃ¥ker <ilmari@ilmari.org>
 
 jcamacho: Juan Camacho
 
+jhannah: Jay Hannah <jay@jays.net>
+
 Jody Belka
 
 Johan Lindstrom
@@ -2567,6 +2666,8 @@ obra: Jesse Vincent
 
 omega: Andreas Marienborg
 
+Oleg Kostyuk <cub.uanic@gmail.com>
+
 phaylon: Robert Sedlacek <phaylon@dunkelheit.at>
 
 rafl: Florian Ragwitz <rafl@debian.org>
@@ -2575,6 +2676,8 @@ sky: Arthur Bergman
 
 the_jester: Jesse Sheidlower
 
+t0m: Tomas Doran <bobtfish@bobtfish.net>
+
 Ulf Edvinsson
 
 willert: Sebastian Willert <willert@cpan.org>