Skip to content

Commit e4bb6d2

Browse files
authored
Merge pull request #36 from cpan-authors/koan.toddr.bot/fix-config-bugs
fix: correct razorhome option and readlink safety in Config.pm
2 parents 907c9a2 + 70dc57d commit e4bb6d2

3 files changed

Lines changed: 115 additions & 2 deletions

File tree

MANIFEST

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ README.md
4646
SECURITY.md
4747
SERVICE_POLICY
4848
t/basic.t
49+
t/config.t
4950
t/errorhandler.t
5051
t/logger.t
5152
t/preproc.t

lib/Razor2/Client/Config.pm

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ sub find_home {
218218
}
219219

220220
if ( defined $self->{opt}->{razorhome} ) {
221-
$self->{razorhome_computed} = $self->{razorhome};
221+
$self->{razorhome_computed} = $self->{opt}->{razorhome};
222222
return 1;
223223
}
224224

@@ -408,11 +408,13 @@ sub my_readlink {
408408
if ( $fn =~ /^(.*)\/([^\/]+)$/ ) {
409409
my $dir = $1;
410410
$fn = readlink $fn;
411+
return unless defined $fn; # broken symlink
411412
$fn = $1 if $fn =~ /^(\S+)$/; # untaint readlink
412-
$fn = "$dir/$fn" unless $fn =~ /^\//;
413+
$fn = "$dir/$fn" unless $fn =~ /^\// || $fn =~ /^[A-Za-z]:/;
413414
}
414415
else {
415416
$fn = readlink $fn;
417+
return unless defined $fn; # broken symlink
416418
$fn = $1 if $fn =~ /^(\S+)$/; # untaint readlink
417419
}
418420
}

t/config.t

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
#!perl
2+
3+
use strict;
4+
use warnings;
5+
6+
use Test::More;
7+
use File::Temp qw(tempdir);
8+
use File::Spec;
9+
10+
use Razor2::Client::Config;
11+
12+
# Config.pm expects a log() method provided via multiple inheritance
13+
# in the full Agent stack. Provide a no-op stub for testing.
14+
no warnings 'once';
15+
*Razor2::Client::Config::log = sub { };
16+
17+
# === find_home tests ===
18+
19+
subtest 'find_home uses razorhome attribute when set' => sub {
20+
my $config = Razor2::Client::Config->new;
21+
$config->{razorhome} = '/custom/razor';
22+
$config->find_home();
23+
is( $config->{razorhome_computed}, '/custom/razor',
24+
'razorhome_computed set from razorhome attribute' );
25+
};
26+
27+
subtest 'find_home uses opt->{razorhome} when set' => sub {
28+
my $config = Razor2::Client::Config->new;
29+
$config->{opt} = { razorhome => '/opt/razor/home' };
30+
$config->find_home();
31+
is( $config->{razorhome_computed}, '/opt/razor/home',
32+
'razorhome_computed set from opt->{razorhome}' );
33+
};
34+
35+
subtest 'find_home prefers razorhome over opt->{razorhome}' => sub {
36+
my $config = Razor2::Client::Config->new;
37+
$config->{razorhome} = '/direct';
38+
$config->{opt} = { razorhome => '/from-opt' };
39+
$config->find_home();
40+
is( $config->{razorhome_computed}, '/direct',
41+
'razorhome attribute takes priority' );
42+
};
43+
44+
subtest 'find_home falls back to HOME env' => sub {
45+
my $tmpdir = tempdir( CLEANUP => 1 );
46+
local $ENV{HOME} = $tmpdir;
47+
my $config = Razor2::Client::Config->new;
48+
$config->find_home();
49+
my $expected = File::Spec->catdir( $tmpdir, '.razor' );
50+
is( $config->{razorhome_computed}, $expected,
51+
'razorhome_computed derived from HOME env' );
52+
};
53+
54+
# === my_readlink tests ===
55+
56+
subtest 'my_readlink returns regular file unchanged' => sub {
57+
my $tmpdir = tempdir( CLEANUP => 1 );
58+
my $file = "$tmpdir/regular.txt";
59+
open my $fh, '>', $file or die "Cannot create $file: $!";
60+
close $fh;
61+
62+
my $config = Razor2::Client::Config->new;
63+
my $result = $config->my_readlink($file);
64+
is( $result, $file, 'regular file returned as-is' );
65+
};
66+
67+
subtest 'my_readlink follows valid symlink' => sub {
68+
my $tmpdir = tempdir( CLEANUP => 1 );
69+
my $target = "$tmpdir/target.txt";
70+
my $link = "$tmpdir/link.txt";
71+
open my $fh, '>', $target or die "Cannot create $target: $!";
72+
close $fh;
73+
symlink( $target, $link ) or plan skip_all => 'symlinks not supported';
74+
75+
my $config = Razor2::Client::Config->new;
76+
my $result = $config->my_readlink($link);
77+
( my $norm_result = defined $result ? $result : '' ) =~ s{\\}{/}g;
78+
( my $norm_target = $target ) =~ s{\\}{/}g;
79+
is( $norm_result, $norm_target, 'symlink resolved to target' );
80+
};
81+
82+
subtest 'my_readlink handles dangling symlink without crashing' => sub {
83+
my $tmpdir = tempdir( CLEANUP => 1 );
84+
my $link = "$tmpdir/broken";
85+
symlink( "$tmpdir/nonexistent", $link ) or plan skip_all => 'symlinks not supported';
86+
87+
my $config = Razor2::Client::Config->new;
88+
# readlink succeeds on dangling symlinks (returns target path),
89+
# but the target doesn't exist. The function should not crash.
90+
my $result = $config->my_readlink($link);
91+
( my $norm_result = defined $result ? $result : '' ) =~ s{\\}{/}g;
92+
( my $norm_expected = "$tmpdir/nonexistent" ) =~ s{\\}{/}g;
93+
is( $norm_result, $norm_expected,
94+
'dangling symlink resolved to target path without crashing' );
95+
};
96+
97+
subtest 'my_readlink follows relative symlink' => sub {
98+
my $tmpdir = tempdir( CLEANUP => 1 );
99+
my $target = "$tmpdir/real.txt";
100+
my $link = "$tmpdir/relative_link.txt";
101+
open my $fh, '>', $target or die "Cannot create $target: $!";
102+
close $fh;
103+
symlink( 'real.txt', $link ) or plan skip_all => 'symlinks not supported';
104+
105+
my $config = Razor2::Client::Config->new;
106+
my $result = $config->my_readlink($link);
107+
is( $result, $target, 'relative symlink resolved with directory prefix' );
108+
};
109+
110+
done_testing;

0 commit comments

Comments
 (0)