From: John Napiorkowski Date: Thu, 15 Dec 2016 19:12:53 +0000 (-0600) Subject: Merge branch 'topic/evilstash' X-Git-Tag: 5.90113~2 X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?p=catagits%2FCatalyst-Runtime.git;a=commitdiff_plain;h=14b0dab3b4ba38d8de513a46d902a0eca4068841;hp=be57189a849e35a77232113c0f4523009d865a78 Merge branch 'topic/evilstash' --- diff --git a/lib/Catalyst/Controller.pm b/lib/Catalyst/Controller.pm index ad88a51..fbab60f 100644 --- a/lib/Catalyst/Controller.pm +++ b/lib/Catalyst/Controller.pm @@ -662,10 +662,25 @@ arguments, when it is instantiated: From L, stashes the application instance as $self->_application. -=head2 $self->action_for('name') +=head2 $self->action_for($action_name) -Returns the Catalyst::Action object (if any) for a given method name -in this component. +Returns the Catalyst::Action object (if any) for a given action in this +controller or relative to it. You may refer to actions in controllers +nested under the current controllers namespace, or in controllers 'up' +from the current controller namespace. For example: + + package MyApp::Controller::One::Two; + use base 'Catalyst::Controller'; + + sub foo :Local { + my ($self, $c) = @_; + $self->action_for('foo'); # action 'foo' in Controller 'One::Two' + $self->action_for('three/bar'); # action 'bar' in Controller 'One::Two::Three' + $self->action_for('../boo'); # action 'boo' in Controller 'One' + } + +This returns 'undef' if there is no action matching the requested action +name (after any path normalization) so you should check for this as needed. =head2 $self->action_namespace($c) diff --git a/lib/Catalyst/Dispatcher.pm b/lib/Catalyst/Dispatcher.pm index c51510e..d3f25a3 100644 --- a/lib/Catalyst/Dispatcher.pm +++ b/lib/Catalyst/Dispatcher.pm @@ -407,9 +407,14 @@ sub prepare_action { if ( $c->debug && @args ); } -=head2 $self->get_action( $action, $namespace ) +=head2 $self->get_action( $action_name, $namespace ) -returns a named action from a given namespace. +returns a named action from a given namespace. C<$action_name> +may be a relative path on that C<$namespace> such as + + $self->get_action('../bar', 'foo/baz'); + +In which case we look for the action at 'foo/bar'. =cut @@ -419,17 +424,22 @@ sub get_action { $namespace = join( "/", grep { length } split '/', ( defined $namespace ? $namespace : "" ) ); - return $self->_action_hash->{"${namespace}/${name}"}; + return $self->get_action_by_path("${namespace}/${name}"); } =head2 $self->get_action_by_path( $path ); Returns the named action by its full private path. +This method performs some normalization on C<$path> so that if +it includes '..' it will do the right thing (for example if +C<$path> is '/foo/../bar' that is normalized to '/bar'. + =cut sub get_action_by_path { my ( $self, $path ) = @_; + $path =~s/[^\/]+\/\.\.\/// while $path=~m/\.\./; $path =~ s/^\///; $path = "/$path" unless $path =~ /\//; $self->_action_hash->{$path}; diff --git a/t/relative_root_action_for_bug.t b/t/relative_root_action_for_bug.t new file mode 100644 index 0000000..8db0f1f --- /dev/null +++ b/t/relative_root_action_for_bug.t @@ -0,0 +1,81 @@ +use Test::Most; + +{ + package MyApp::Controller::Root; + $INC{'MyApp/Controller/Root.pm'} = __FILE__; + + use Moose; + use MooseX::MethodAttributes; + + extends 'Catalyst::Controller'; + + sub root :Chained(/) PathPart('') CaptureArgs(0) { + my ($self, $c) = @_; + } + + sub top :Chained('root') Args(0) { + my ($self, $c) = @_; + Test::Most::is $self->action_for('top'), 'top'; + Test::Most::is $self->action_for('story/story'), 'story/story'; + } + + MyApp::Controller::Root->config(namespace=>''); + + package MyApp::Controller::Story; + $INC{'MyApp/Controller/Story.pm'} = __FILE__; + + use Moose; + use MooseX::MethodAttributes; + + extends 'Catalyst::Controller'; + + sub root :Chained(/root) PathPart('') CaptureArgs(0) { + my ($self, $c) = @_; + } + + sub story :Chained(root) Args(0) { + my ($self, $c) = @_; + + Test::Most::is $self->action_for('story'), 'story/story'; + Test::Most::is $self->action_for('author/author'), 'story/author/author'; + } + + __PACKAGE__->meta->make_immutable; + + package MyApp::Controller::Story::Author; + $INC{'MyApp/Controller/Story/Author.pm'} = __FILE__; + + use Moose; + use MooseX::MethodAttributes; + + extends 'Catalyst::Controller'; + + sub root :Chained(/story/root) PathPart('') CaptureArgs(0) { + my ($self, $c) = @_; + } + + sub author :Chained(root) Args(0) { + my ($self, $c, $id) = @_; + Test::Most::is $self->action_for('author'), 'story/author/author'; + Test::Most::is $self->action_for('../story'), 'story/story'; + Test::Most::is $self->action_for('../../top'), 'top'; + } + + __PACKAGE__->meta->make_immutable; + + package MyApp; + $INC{'MyApp.pm'} = __FILE__; + + use Catalyst; + + MyApp->setup; +} + +use Catalyst::Test 'MyApp'; + +ok request '/top'; +ok request '/story'; +ok request '/author'; + +done_testing(10); +