use warnings;
use strict;
sub dive {
my ($data, @path) = @_;
die "unsafe keys" if grep { /[^a-zA-Z0-9_]/ } @path;
return eval '$data->'.join('', map { "{$_}" } @path);
}
use Test::More tests=>6;
sub exception (&) { eval { shift->(); 1 } ? undef : ($@ || die) }
our %quz = ( quz => 'Hello!' );
our %foo = ( bar => 'quz', baz => { hello => "World!" } );
is dive(\%quz, qw/ quz /), 'Hello!';
is dive(\%foo, qw/ bar /), 'quz';
is dive(\%foo, qw/ bar x /), undef;
is dive(\%foo, qw/ baz hello /), 'World!';
is dive(\%foo, qw/ bar quz /), undef;
like exception { dive(\%foo, qw/ $hello /) },
qr/\bunsafe keys\b/i;
Under no strict 'refs', the code will behave differently and the tests will fail.
Any code with eval is susceptible - for a slightly more realistic scenario, imagine a templating system that is loading data from JSON, for example. Update: Though I think the code above isn't even that unrealistic, given the huge number of modules on CPAN there's bound to be one or two that do something like that. Also added a test case to the above code. |