From: Thomas Wegner Date: Sun, 17 Jun 2001 14:43:11 +0000 (+0200) Subject: Re: [MacPerl-Porters] Re: [PATCH] File::Find for bleadperl, Mac OS etc. X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=commitdiff_plain;h=3fa6e24bc31afffc996bd23eb0e075394995c5b8;p=p5sagit%2Fp5-mst-13.2.git Re: [MacPerl-Porters] Re: [PATCH] File::Find for bleadperl, Mac OS etc. Message-Id: p4raw-id: //depot/perl@10659 --- diff --git a/MANIFEST b/MANIFEST index bddc9f9..a4c63cf 100644 --- a/MANIFEST +++ b/MANIFEST @@ -1634,6 +1634,7 @@ t/lib/filter-simple.t See if Filter::Simple works t/lib/filter-util.pl See if Filter::Util::Call works t/lib/filter-util.t See if Filter::Util::Call works t/lib/findbin.t See if FindBin works +t/lib/findtaint.t See if File::Find works with taint t/lib/ftmp-mktemp.t See if File::Temp works t/lib/ftmp-posix.t See if File::Temp works t/lib/ftmp-security.t See if File::Temp works diff --git a/lib/File/Find.pm b/lib/File/Find.pm index e2bb8ab..209e6bb 100644 --- a/lib/File/Find.pm +++ b/lib/File/Find.pm @@ -63,7 +63,7 @@ The value should be a code reference. It is invoked just before leaving the currently processed directory. It is called in void context with no arguments. The name of the current directory is in $File::Find::dir. This hook is handy for summarizing a directory, such as calculating its disk -usage. When I or I are in effect, C is a +usage. When I or I are in effect, C is a no-op. =item C @@ -495,7 +495,7 @@ sub _find_opt { if ($Is_MacOS) { ($topdev,$topino,$topmode,$topnlink) = $follow ? stat $top_item : lstat $top_item; $top_item = ":$top_item" - if ( (-d _) && ($top_item =~ /^[^:]+\z/) ); + if ( (-d _) && ( $top_item !~ /:/ ) ); } else { $top_item =~ s|/\z|| unless $top_item eq '/'; @@ -515,7 +515,7 @@ sub _find_opt { else { $abs_dir = contract_name_Mac($cwd, $top_item); unless (defined $abs_dir) { - warn "Can't determine absolute path for $top_item (No such file or directory)\n"; + warn "Can't determine absolute path for $top_item (No such file or directory)\n" if $^W; next Proc_Top_Item; } } @@ -534,7 +534,7 @@ sub _find_opt { } $abs_dir= Follow_SymLink($abs_dir); unless (defined $abs_dir) { - warn "$top_item is a dangling symbolic link\n"; + warn "$top_item is a dangling symbolic link\n" if $^W; next Proc_Top_Item; } @@ -546,7 +546,7 @@ sub _find_opt { else { # no follow $topdir = $top_item; unless (defined $topnlink) { - warn "Can't stat $top_item: $!\n"; + warn "Can't stat $top_item: $!\n" if $^W; next Proc_Top_Item; } if (-d _) { @@ -583,7 +583,7 @@ sub _find_opt { } unless ($no_chdir || chdir $abs_dir) { - warn "Couldn't chdir $abs_dir: $!\n"; + warn "Couldn't chdir $abs_dir: $!\n" if $^W; next Proc_Top_Item; } @@ -652,7 +652,7 @@ sub _find_dir($$$) { } } unless (chdir $udir) { - warn "Can't cd to $udir: $!\n"; + warn "Can't cd to $udir: $!\n" if $^W; return; } } @@ -695,10 +695,10 @@ sub _find_dir($$$) { } unless (chdir $udir) { if ($Is_MacOS) { - warn "Can't cd to ($p_dir) $udir: $!\n"; + warn "Can't cd to ($p_dir) $udir: $!\n" if $^W; } else { - warn "Can't cd to (" . ($p_dir ne '/' ? $p_dir : '') . "/) $udir: $!\n"; + warn "Can't cd to (" . ($p_dir ne '/' ? $p_dir : '') . "/) $udir: $!\n" if $^W; } next; } @@ -713,7 +713,7 @@ sub _find_dir($$$) { # Get the list of files in the current directory. unless (opendir DIR, ($no_chdir ? $dir_name : $File::Find::current_dir)) { - warn "Can't opendir($dir_name): $!\n"; + warn "Can't opendir($dir_name): $!\n" if $^W; next; } @filenames = readdir DIR; @@ -883,7 +883,7 @@ sub _find_dir_symlnk($$$) { } $ok = chdir($updir_loc) unless ($p_dir eq $File::Find::current_dir); unless ($ok) { - warn "Can't cd to $updir_loc: $!\n"; + warn "Can't cd to $updir_loc: $!\n" if $^W; return; } } @@ -900,7 +900,7 @@ sub _find_dir_symlnk($$$) { # change (back) to parent directory (always untainted) unless ($no_chdir) { unless (chdir $updir_loc) { - warn "Can't cd to $updir_loc: $!\n"; + warn "Can't cd to $updir_loc: $!\n" if $^W; next; } } @@ -931,7 +931,7 @@ sub _find_dir_symlnk($$$) { } } unless (chdir $updir_loc) { - warn "Can't cd to $updir_loc: $!\n"; + warn "Can't cd to $updir_loc: $!\n" if $^W; next; } } @@ -944,7 +944,7 @@ sub _find_dir_symlnk($$$) { # Get the list of files in the current directory. unless (opendir DIR, ($no_chdir ? $dir_loc : $File::Find::current_dir)) { - warn "Can't opendir($dir_loc): $!\n"; + warn "Can't opendir($dir_loc): $!\n" if $^W; next; } @filenames = readdir DIR; @@ -989,7 +989,7 @@ sub _find_dir_symlnk($$$) { if ( $byd_flag < 0 ) { # must be finddepth, report dirname now unless ($no_chdir || ($dir_rel eq $File::Find::current_dir)) { unless (chdir $updir_loc) { # $updir_loc (parent dir) is always untainted - warn "Can't cd to $updir_loc: $!\n"; + warn "Can't cd to $updir_loc: $!\n" if $^W; next; } } diff --git a/t/lib/filefind.t b/t/lib/filefind.t index 5bd8324..7b1a935 100755 --- a/t/lib/filefind.t +++ b/t/lib/filefind.t @@ -1,88 +1,55 @@ -#!./perl -T +#!./perl -my %Expect; +my %Expect_File = (); # what we expect for $_ +my %Expect_Name = (); # what we expect for $File::Find::name/fullname +my %Expect_Dir = (); # what we expect for $File::Find::dir my $symlink_exists = eval { symlink("",""); 1 }; my $warn_msg; -my $cwd; -my $cwd_untainted; + BEGIN { chdir 't' if -d 't'; unshift @INC => '../lib'; - for (keys %ENV) { # untaint ENV - ($ENV{$_}) = $ENV{$_} =~ /(.*)/; - } - - $SIG{'__WARN__'} = sub { $warn_msg = $_[0]; warn "# Warn: $_[0]"; } + $SIG{'__WARN__'} = sub { $warn_msg = $_[0]; warn "# $_[0]"; } } -if ( $symlink_exists ) { print "1..193\n"; } -else { print "1..75\n"; } +if ( $symlink_exists ) { print "1..188\n"; } +else { print "1..78\n"; } use File::Find; -use Cwd; - -# Remove insecure directories from PATH -my @path; -my $sep = ($^O eq 'MSWin32') ? ';' : ':'; -foreach my $dir (split(/$sep/,$ENV{'PATH'})) - { - push(@path,$dir) unless -w $dir; - } -$ENV{'PATH'} = join($sep,@path); +use File::Spec; cleanup(); -if ($^O eq 'MacOS') { - find({wanted => sub { print "ok 1\n" if $_ eq 'filefind.t'; }, untaint => 1}, ':'); - finddepth({wanted => sub { print "ok 2\n" if $_ eq 'filefind.t'; }, untaint => 1}, ':'); -} else { - find({wanted => sub { print "ok 1\n" if $_ eq 'filefind.t'; }, untaint => 1, - untaint_pattern => qr|^(.+)$|}, '.'); - finddepth({wanted => sub { print "ok 2\n" if $_ eq 'filefind.t'; }, - untaint => 1, untaint_pattern => qr|^(.+)$|}, '.'); -} +find({wanted => sub { print "ok 1\n" if $_ eq 'filefind.t'; } }, File::Spec->curdir); + +finddepth({wanted => sub { print "ok 2\n" if $_ eq 'filefind.t'; } }, File::Spec->curdir); my $case = 2; my $FastFileTests_OK = 0; sub cleanup { - if ($^O eq 'MacOS') { - if (-d ':for_find') { - chdir(':for_find'); - } - if (-d ':fa') { - unlink ':fa:fa_ord',':fa:fsl',':fa:faa:faa_ord', - ':fa:fab:fab_ord',':fa:fab:faba:faba_ord', - ':fb:fb_ord',':fb:fba:fba_ord'; - rmdir ':fa:faa'; - rmdir ':fa:fab:faba'; - rmdir ':fa:fab'; - rmdir ':fa'; - rmdir ':fb:fba'; - rmdir ':fb'; - chdir '::'; - rmdir ':for_find'; - } - } else { - if (-d 'for_find') { - chdir('for_find'); - } - if (-d 'fa') { - unlink 'fa/fa_ord','fa/fsl','fa/faa/faa_ord', - 'fa/fab/fab_ord','fa/fab/faba/faba_ord', - 'fb/fb_ord','fb/fba/fba_ord'; - rmdir 'fa/faa'; - rmdir 'fa/fab/faba'; - rmdir 'fa/fab'; - rmdir 'fa'; - rmdir 'fb/fba'; - rmdir 'fb'; - chdir '..'; - rmdir 'for_find'; - } + if (-d dir_path('for_find')) { + chdir(dir_path('for_find')); + } + if (-d dir_path('fa')) { + unlink file_path('fa', 'fa_ord'), + file_path('fa', 'fsl'), + file_path('fa', 'faa', 'faa_ord'), + file_path('fa', 'fab', 'fab_ord'), + file_path('fa', 'fab', 'faba', 'faba_ord'), + file_path('fb', 'fb_ord'), + file_path('fb', 'fba', 'fba_ord'); + rmdir dir_path('fa', 'faa'); + rmdir dir_path('fa', 'fab', 'faba'); + rmdir dir_path('fa', 'fab'); + rmdir dir_path('fa'); + rmdir dir_path('fb', 'fba'); + rmdir dir_path('fb'); + chdir File::Spec->updir; + rmdir dir_path('for_find'); } } @@ -91,631 +58,508 @@ END { } sub Check($) { - $case++; - if ($_[0]) { print "ok $case\n"; } - else { print "not ok $case\n"; } + $case++; + if ($_[0]) { print "ok $case\n"; } + else { print "not ok $case\n"; } } sub CheckDie($) { - $case++; - if ($_[0]) { print "ok $case\n"; } - else { print "not ok $case\n $!\n"; exit 0; } + $case++; + if ($_[0]) { print "ok $case\n"; } + else { print "not ok $case\n $!\n"; exit 0; } } sub touch { - CheckDie( open(my $T,'>',$_[0]) ); + CheckDie( open(my $T,'>',$_[0]) ); } sub MkDir($$) { - CheckDie( mkdir($_[0],$_[1]) ); + CheckDie( mkdir($_[0],$_[1]) ); } -sub wanted { - print "# '$_' => 1\n"; - s#\.$## if ($^O eq 'VMS' && $_ ne '.'); - Check( $Expect{$_} ); - if ( $FastFileTests_OK ) { - delete $Expect{$_} - unless ( $Expect_Dir{$_} && ! -d _ ); - } else { - delete $Expect{$_} - unless ( $Expect_Dir{$_} && ! -d $_ ); - } - $File::Find::prune=1 if $_ eq 'faba'; +sub wanted_File_Dir { + print "# \$File::Find::dir => '$File::Find::dir'\n"; + print "# \$_ => '$_'\n"; + s#\.$## if ($^O eq 'VMS' && $_ ne '.'); + Check( $Expect_File{$_} ); + if ( $FastFileTests_OK ) { + delete $Expect_File{ $_} + unless ( $Expect_Dir{$_} && ! -d _ ); + } else { + delete $Expect_File{$_} + unless ( $Expect_Dir{$_} && ! -d $_ ); + } +} +sub wanted_File_Dir_prune { + &wanted_File_Dir; + $File::Find::prune=1 if $_ eq 'faba'; } -sub dn_wanted { - my $n = $File::Find::name; - $n =~ s#\.$## if ($^O eq 'VMS' && $n ne '.'); - print "# '$n' => 1\n"; - my $i = rindex($n,'/'); - my $OK = exists($Expect{$n}); - unless ($^O eq 'MacOS') { - if ( $OK ) { - $OK= exists($Expect{substr($n,0,$i)}) if $i >= 0; +sub wanted_Name { + my $n = $File::Find::name; + $n =~ s#\.$## if ($^O eq 'VMS' && $n ne '.'); + print "# \$File::Find::name => '$n'\n"; + my $i = rindex($n,'/'); + my $OK = exists($Expect_Name{$n}); + unless ($^O eq 'MacOS') { + if ( $OK ) { + $OK= exists($Expect_Name{substr($n,0,$i)}) if $i >= 0; + } } - } - Check($OK); - delete $Expect{$n}; + Check($OK); + delete $Expect_Name{$n}; } -sub d_wanted { - print "# '$_' => 1\n"; - s#\.$## if ($^O eq 'VMS' && $_ ne '.'); - my $i = rindex($_,'/'); - my $OK = exists($Expect{$_}); - unless ($^O eq 'MacOS') { - if ( $OK ) { - $OK= exists($Expect{substr($_,0,$i)}) if $i >= 0; +sub wanted_File { + print "# \$_ => '$_'\n"; + s#\.$## if ($^O eq 'VMS' && $_ ne '.'); + my $i = rindex($_,'/'); + my $OK = exists($Expect_File{ $_}); + unless ($^O eq 'MacOS') { + if ( $OK ) { + $OK= exists($Expect_File{ substr($_,0,$i)}) if $i >= 0; + } } - } - Check($OK); - delete $Expect{$_}; + Check($OK); + delete $Expect_File{ $_}; } sub simple_wanted { - print "# \$File::Find::dir => '$File::Find::dir'\n"; - print "# \$_ => '$_'\n"; + print "# \$File::Find::dir => '$File::Find::dir'\n"; + print "# \$_ => '$_'\n"; } sub noop_wanted {} sub my_preprocess { - @files = @_; - print "# --PREPROCESS--\n"; - print "# \$File::Find::dir => '$File::Find::dir' \n"; - foreach $file (@files) { - print "# $file \n"; - delete $Expect{$File::Find::dir}->{$file}; - } - print "# --END PREPROCESS--\n"; - Check(scalar(keys %{$Expect{$File::Find::dir}}) == 0); - if (scalar(keys %{$Expect{$File::Find::dir}}) == 0) { - delete $Expect{$File::Find::dir} - } - return @files; + @files = @_; + print "# --preprocess--\n"; + print "# \$File::Find::dir => '$File::Find::dir' \n"; + foreach $file (@files) { + print "# $file \n"; + delete $Expect_Dir{ $File::Find::dir }->{$file}; + } + print "# --end preprocess--\n"; + Check(scalar(keys %{$Expect_Dir{ $File::Find::dir }}) == 0); + if (scalar(keys %{$Expect_Dir{ $File::Find::dir }}) == 0) { + delete $Expect_Dir{ $File::Find::dir } + } + return @files; } sub my_postprocess { - print "# POSTPROCESS: \$File::Find::dir => '$File::Find::dir' \n"; - delete $Expect{$File::Find::dir}; + print "# postprocess: \$File::Find::dir => '$File::Find::dir' \n"; + delete $Expect_Dir{ $File::Find::dir}; } -if ($^O eq 'MacOS') { +# Use dir_path() to specify a directory path that's expected for $File::Find::dir +# (%Expect_Dir). Also use it in file operations like chdir, rmdir etc. +# +# dir_path() concatenates directory names to form a _relative_ directory path, +# independant from the platform it's run on, although there are limitations. +# Don't try to create an absolute path, because that may fail on operating +# systems that have the concept of volume names (e.g. Mac OS). Be careful when +# you want to create an updir path like ../fa (Unix) or ::fa: (Mac OS). Plain +# directory names will work best. As a special case, you can pass it a "." as +# first argument, to create a directory path like "./fa/dir" on operating +# systems other than Mac OS (actually, Mac OS will ignore the ".", if it's the +# first argument). If there's no second argument, this function will return the +# empty string on Mac OS and the string "./" otherwise. + +sub dir_path { + my $first_item = shift @_; + + if ($first_item eq '.') { + if ($^O eq 'MacOS') { + return '' unless @_; + # ignore first argument; return a relative path + # with leading ":" and with trailing ":" + return File::Spec->catdir("", @_); + } else { # other OS + return './' unless @_; + my $path = File::Spec->catdir(@_); + # add leading "./" + $path = "./$path"; + return $path; + } + + } else { # $first_item ne '.' + return $first_item unless @_; # return plain filename + if ($^O eq 'MacOS') { + # relative path with leading ":" and with trailing ":" + return File::Spec->catdir("", $first_item, @_); + } else { # other OS + return File::Spec->catdir($first_item, @_); + } + } +} - MkDir( 'for_find',0770 ); - CheckDie(chdir(for_find)); - - $cwd = cwd(); # save cwd - ( $cwd_untainted ) = $cwd =~ m|^(.+)$|; # untaint it - - MkDir( 'fa',0770 ); - MkDir( 'fb',0770 ); - touch(':fb:fb_ord'); - MkDir( ':fb:fba',0770 ); - touch(':fb:fba:fba_ord'); - CheckDie( symlink(':fb',':fa:fsl') ) if $symlink_exists; - touch(':fa:fa_ord'); - - MkDir( ':fa:faa',0770 ); - touch(':fa:faa:faa_ord'); - MkDir( ':fa:fab',0770 ); - touch(':fa:fab:fab_ord'); - MkDir( ':fa:fab:faba',0770 ); - touch(':fa:fab:faba:faba_ord'); - - %Expect = (':' => 1, 'fsl' => 1, 'fa_ord' => 1, 'fab' => 1, 'fab_ord' => 1, - 'faba' => 1, 'faa' => 1, 'faa_ord' => 1); - delete $Expect{'fsl'} unless $symlink_exists; - %Expect_Dir = (':' => 1, 'fa' => 1, 'faa' => 1, 'fab' => 1, 'faba' => 1, - 'fb' => 1, 'fba' => 1); - delete @Expect_Dir{'fb','fba'} unless $symlink_exists; - File::Find::find( {wanted => \&wanted, untaint => 1},':fa' ); - Check( scalar(keys %Expect) == 0 ); - - print "# check re-entancy\n"; - %Expect = (':' => 1, 'fsl' => 1, 'fa_ord' => 1, 'fab' => 1, 'fab_ord' => 1, - 'faba' => 1, 'faa' => 1, 'faa_ord' => 1); - delete $Expect{'fsl'} unless $symlink_exists; - %Expect_Dir = (':' => 1, 'fa' => 1, 'faa' => 1, 'fab' => 1, 'faba' => 1, - 'fb' => 1, 'fba' => 1); - delete @Expect_Dir{'fb','fba'} unless $symlink_exists; - File::Find::find( {wanted => sub { - wanted(); - File::Find::find( {wanted => sub {} , untaint => 1 },':' ); - }, untaint => 1 }, ':fa' ); - Check( scalar(keys %Expect) == 0 ); - - %Expect=(':fa' => 1, ':fa:fsl' => 1, ':fa:fa_ord' => 1, ':fa:fab' => 1, - ':fa:fab:fab_ord' => 1, ':fa:fab:faba' => 1, - ':fa:fab:faba:faba_ord' => 1, ':fa:faa' => 1, ':fa:faa:faa_ord' => 1); - delete $Expect{':fa:fsl'} unless $symlink_exists; - %Expect_Dir = (':fa' => 1, ':fa:faa' => 1, ':fa:fab' => 1, ':fa:fab:faba' => 1, - ':fb' => 1, ':fb:fba' => 1); - delete @Expect_Dir{':fb',':fb:fba'} unless $symlink_exists; - File::Find::find( {wanted => \&wanted, no_chdir => 1, untaint => 1},':fa' ); - Check( scalar(keys %Expect) == 0 ); - - %Expect=(':' => 1, ':fa' => 1, ':fa:fsl' => 1, ':fa:fa_ord' => 1, ':fa:fab' => 1, - ':fa:fab:fab_ord' => 1, ':fa:fab:faba' => 1, - ':fa:fab:faba:faba_ord' => 1, ':fa:faa' => 1, ':fa:faa:faa_ord' => 1, - ':fb' => 1, ':fb:fba' => 1, ':fb:fba:fba_ord' => 1, ':fb:fb_ord' => 1); - delete $Expect{':fa:fsl'} unless $symlink_exists; - %Expect_Dir = (':' => 1, ':fa' => 1, ':fa:faa' => 1, ':fa:fab' => 1, ':fa:fab:faba' => 1, - ':fb' => 1, ':fb:fba' => 1); - delete @Expect_Dir{':fb',':fb:fba'} unless $symlink_exists; - File::Find::finddepth( {wanted => \&dn_wanted, untaint => 1 },':' ); - Check( scalar(keys %Expect) == 0 ); - - %Expect=(':' => 1, ':fa' => 1, ':fa:fsl' => 1, ':fa:fa_ord' => 1, ':fa:fab' => 1, - ':fa:fab:fab_ord' => 1, ':fa:fab:faba' => 1, - ':fa:fab:faba:faba_ord' => 1, ':fa:faa' => 1, ':fa:faa:faa_ord' => 1, - ':fb' => 1, ':fb:fba' => 1, ':fb:fba:fba_ord' => 1, ':fb:fb_ord' => 1); - delete $Expect{':fa:fsl'} unless $symlink_exists; - %Expect_Dir = (':' => 1, ':fa' => 1, ':fa:faa' => 1, ':fa:fab' => 1, ':fa:fab:faba' => 1, - ':fb' => 1, ':fb:fba' => 1); - delete @Expect_Dir{':fb',':fb:fba'} unless $symlink_exists; - File::Find::finddepth( {wanted => \&d_wanted, no_chdir => 1, untaint => 1 },':' ); - Check( scalar(keys %Expect) == 0 ); - - # untaint, preprocess and postprocess tests below added by Thomas Wegner, 17-05-2001 - - print "# check untainting (no follow)\n"; - # don't untaint at all - undef $@; - eval {File::Find::find( {wanted => \&simple_wanted},':fa' );}; - print "# Died: $@"; - Check( $@ =~ m|Insecure dependency| ); - chdir($cwd_untainted); - undef $@; - eval {File::Find::find( {wanted => \&simple_wanted, untaint => 1, - untaint_pattern => qr|^(NO_MATCH)$|},':fa' );}; - print "# Died: $@"; - Check( $@ =~ m|is still tainted| ); - chdir($cwd_untainted); +# Use topdir() to specify a directory path that you want to pass to find/finddepth +# +# Basically, topdir() does the same as dir_path() (see above), except that there's +# no trailing ":" on Mac OS. - print "# check untaint_skip (no follow)\n"; - undef $@; - eval {File::Find::find( {wanted => \&simple_wanted, untaint => 1, untaint_skip => 1, - untaint_pattern => qr|^(NO_MATCH)$|}, ':fa' );}; - print "# Died: $@"; - Check( $@ =~ m|insecure cwd| ); - chdir($cwd_untainted); - - print "# check preprocess\n"; - %Expect=( - ':' => {fa => 1, fb => 1}, - ':fa:' => {faa => 1, fab => 1, fa_ord => 1}, - ':fa:faa:' => {faa_ord => 1}, - ':fa:fab:' => {faba => 1, fab_ord => 1}, - ':fa:fab:faba:' => {faba_ord => 1}, - ':fb:' => {fba => 1, fb_ord => 1}, - ':fb:fba:' => {fba_ord => 1} - ); - File::Find::find( {wanted => \&noop_wanted, untaint => 1, preprocess => \&my_preprocess}, ':' ); - Check( scalar(keys %Expect) == 0 ); - - print "# check postprocess\n"; - %Expect=(':' => 1, ':fa:' => 1, ':fa:faa:' => 1, ':fa:fab:' => 1, ':fa:fab:faba:' => 1, ':fb:' => 1, - ':fb:fba:' => 1 ); - File::Find::find( {wanted => \&noop_wanted, untaint => 1, postprocess => \&my_postprocess}, ':' ); - Check( scalar(keys %Expect) == 0 ); +sub topdir { + my $path = dir_path(@_); + $path =~ s/:$// if ($^O eq 'MacOS'); + return $path; +} - # Verify that File::Find::find will call wanted even if the topdir of - # is a symlink to a directory, and it shouldn't follow the link - # unless follow is set, which it isn't in this case - %Expect = ('fsl' => 1); - %Expect_Dir = (); - File::Find::find( {wanted => \&wanted, untaint => 1},':fa:fsl' ); - Check( scalar(keys %Expect) == 0 ); - - if ( $symlink_exists ) { - $FastFileTests_OK= 1; - %Expect=(':' => 1, 'fa_ord' => 1, 'fsl' => 1, 'fb_ord' => 1, 'fba' => 1, - 'fba_ord' => 1, 'fab' => 1, 'fab_ord' => 1, 'faba' => 1, 'faa' => 1, - 'faa_ord' => 1); - %Expect_Dir = (':' => 1, 'fa' => 1, 'faa' => 1, 'fab' => 1, 'faba' => 1, - 'fb' => 1, 'fba' => 1); - File::Find::find( {wanted => \&wanted, follow_fast => 1, untaint => 1},':fa' ); - Check( scalar(keys %Expect) == 0 ); - - %Expect=(':fa' => 1, ':fa:fa_ord' => 1, ':fa:fsl' => 1, ':fa:fsl:fb_ord' => 1, - ':fa:fsl:fba' => 1, ':fa:fsl:fba:fba_ord' => 1, ':fa:fab' => 1, - ':fa:fab:fab_ord' => 1, ':fa:fab:faba' => 1, ':fa:fab:faba:faba_ord' => 1, - ':fa:faa' => 1, ':fa:faa:faa_ord' => 1); - %Expect_Dir = (':fa' => 1, ':fa:faa' => 1, ':fa:fab' => 1, ':fa:fab:faba' => 1, - ':fb' => 1, ':fb:fba' => 1); - File::Find::find( {wanted => \&wanted, follow_fast => 1, no_chdir => 1, untaint => 1 },':fa' ); - Check( scalar(keys %Expect) == 0 ); - - %Expect=(':fa' => 1, ':fa:fa_ord' => 1, ':fa:fsl' => 1, ':fa:fsl:fb_ord' => 1, - ':fa:fsl:fba' => 1, ':fa:fsl:fba:fba_ord' => 1, ':fa:fab' => 1, - ':fa:fab:fab_ord' => 1, ':fa:fab:faba' => 1, ':fa:fab:faba:faba_ord' => 1, - ':fa:faa' => 1, ':fa:faa:faa_ord' => 1); - %Expect_Dir = (':fa' => 1, ':fa:faa' => 1, ':fa:fab' => 1, ':fa:fab:faba' => 1, - ':fb' => 1, ':fb:fba' => 1); - File::Find::finddepth( {wanted => \&dn_wanted, follow_fast => 1, untaint => 1 },':fa' ); - Check( scalar(keys %Expect) == 0 ); - - %Expect=(':fa' => 1, ':fa:fa_ord' => 1, ':fa:fsl' => 1, ':fa:fsl:fb_ord' => 1, - ':fa:fsl:fba' => 1, ':fa:fsl:fba:fba_ord' => 1, ':fa:fab' => 1, - ':fa:fab:fab_ord' => 1, ':fa:fab:faba' => 1, ':fa:fab:faba:faba_ord' => 1, - ':fa:faa' => 1, ':fa:faa:faa_ord' => 1); - %Expect_Dir = (':fa' => 1, ':fa:faa' => 1, ':fa:fab' => 1, ':fa:fab:faba' => 1, - ':fb' => 1, ':fb:fba' => 1); - File::Find::finddepth( {wanted => \&d_wanted, follow_fast => 1, no_chdir => 1, untaint => 1 },':fa' ); - Check( scalar(keys %Expect) == 0 ); - - # tests below added by Thomas Wegner, 17-05-2001 - - print "# check dangling symbolic links\n"; - MkDir( 'dangling_dir',0770 ); - CheckDie( symlink('dangling_dir','dangling_dir_sl') ); - rmdir 'dangling_dir'; - touch('dangling_file'); - CheckDie( symlink('dangling_file',':fa:dangling_file_sl') ); - unlink 'dangling_file'; - - %Expect=(':' => 1, 'fa_ord' => 1, 'fsl' => 1, 'fb_ord' => 1, 'fba' => 1, - 'fba_ord' => 1, 'fab' => 1, 'fab_ord' => 1, 'faba' => 1, 'faba_ord' => 1, - 'faa' => 1, 'faa_ord' => 1); - %Expect_Dir = (':' => 1, 'fa' => 1, 'faa' => 1, 'fab' => 1, 'faba' => 1, - 'fb' => 1, 'fba' => 1); - undef $warn_msg; - File::Find::find( {wanted => \&d_wanted, follow => 1, untaint => 1 }, 'dangling_dir_sl', ':fa' ); - Check( $warn_msg =~ m|dangling_dir_sl is a dangling symbolic link| ); - unlink ':fa:dangling_file_sl', 'dangling_dir_sl'; - - print "# check recursion\n"; - CheckDie( symlink(':fa:faa',':fa:faa:faa_sl') ); - undef $@; - eval {File::Find::find( {wanted => \&simple_wanted, follow => 1, no_chdir => 1, untaint => 1 },':fa' ); }; - print "# Died: $@"; - Check( $@ =~ m|:for_find:fa:faa:faa_sl is a recursive symbolic link| ); - unlink ':fa:faa:faa_sl'; - - print "# check follow_skip (file)\n"; - CheckDie( symlink(':fa:fa_ord',':fa:fa_ord_sl') ); # symlink to a file - undef $@; - eval {File::Find::finddepth( {wanted => \&simple_wanted, follow => 1,follow_skip => 0, - no_chdir => 1, untaint => 1 },':fa' );}; - print "# Died: $@"; - Check( $@ =~ m|:for_find:fa:fa_ord encountered a second time| ); - - %Expect=(':fa' => 1, ':fa:fa_ord' => 1, ':fa:fsl' => 1, ':fa:fsl:fb_ord' => 1, - ':fa:fsl:fba' => 1, ':fa:fsl:fba:fba_ord' => 1, ':fa:fab' => 1, - ':fa:fab:fab_ord' => 1, ':fa:fab:faba' => 1, ':fa:fab:faba:faba_ord' => 1, - ':fa:faa' => 1, ':fa:faa:faa_ord' => 1); - %Expect_Dir = (':fa' => 1, ':fa:faa' => 1, ':fa:fab' => 1, ':fa:fab:faba' => 1, - ':fb' => 1, ':fb:fba' => 1); - File::Find::finddepth( {wanted => \&wanted, follow => 1, follow_skip => 1, no_chdir => 1, - untaint => 1 },':fa' ); - Check( scalar(keys %Expect) == 0 ); - unlink ':fa:fa_ord_sl'; - - print "# check follow_skip (directory)\n"; - CheckDie( symlink(':fa:faa',':fa:faa_sl') ); # symlink to a directory - undef $@; - eval {File::Find::find( {wanted => \&simple_wanted, follow => 1, follow_skip => 0, - no_chdir => 1, untaint => 1 },':fa' );}; - print "# Died: $@"; - Check( $@ =~ m|:for_find:fa:faa: encountered a second time| ); - - undef $@; - eval {File::Find::find( {wanted => \&simple_wanted, follow => 1, follow_skip => 1, - no_chdir => 1, untaint => 1 },':fa' );}; - print "# Died: $@"; - Check( $@ =~ m|:for_find:fa:faa: encountered a second time| ); - - %Expect=(':fa' => 1, ':fa:fa_ord' => 1, ':fa:fsl' => 1, ':fa:fsl:fb_ord' => 1, - ':fa:fsl:fba' => 1, ':fa:fsl:fba:fba_ord' => 1, ':fa:fab' => 1, - ':fa:fab:fab_ord' => 1, ':fa:fab:faba' => 1, ':fa:fab:faba:faba_ord' => 1, - ':fa:faa' => 1, ':fa:faa:faa_ord' => 1); - %Expect_Dir = (':fa' => 1, ':fa:faa' => 1, ':fa:fab' => 1, ':fa:fab:faba' => 1, - ':fb' => 1, ':fb:fba' => 1); - File::Find::find( {wanted => \&wanted, follow => 1, follow_skip => 2, no_chdir => 1, - untaint => 1},':fa' ); - Check( scalar(keys %Expect) == 0 ); - unlink ':fa:faa_sl'; - - print "# check untainting (follow)\n"; - # don't untaint at all - undef $@; - eval {File::Find::find( {wanted => \&simple_wanted, follow => 1},':fa' );}; - print "# Died: $@"; - Check( $@ =~ m|Insecure dependency| ); - chdir($cwd_untainted); - - undef $@; - eval {File::Find::find( {wanted => \&simple_wanted, follow => 1, untaint => 1, - untaint_pattern => qr|^(NO_MATCH)$|},':fa' );}; - print "# Died: $@"; - Check( $@ =~ m|is still tainted| ); - chdir($cwd_untainted); - - print "# check untaint_skip (follow)\n"; - undef $@; - eval {File::Find::find( {wanted => \&simple_wanted, untaint => 1, untaint_skip => 1, - untaint_pattern => qr|^(NO_MATCH)$|}, ':fa' );}; - print "# Died: $@"; - Check( $@ =~ m|insecure cwd| ); - chdir($cwd_untainted); +# Use file_path() to specify a file path that's expected for $_ (%Expect_File). +# Also suitable for file operations like unlink etc. +# +# file_path() concatenates directory names (if any) and a filename to form a +# _relative_ file path (the last argument is assumed to be a file). It's +# independant from the platform it's run on, although there are limitations +# (see the warnings for dir_path() above). As a special case, you can pass it a +# "." as first argument, to create a file path like "./fa/file" on operating +# systems other than Mac OS (actually, Mac OS will ignore the ".", if it's the +# first argument). If there's no second argument, this function will return the +# empty string on Mac OS and the string "./" otherwise. + +sub file_path { + my $first_item = shift @_; + + if ($first_item eq '.') { + if ($^O eq 'MacOS') { + return '' unless @_; + # ignore first argument; return a relative path + # with leading ":", but without trailing ":" + return File::Spec->catfile("", @_); + } else { # other OS + return './' unless @_; + my $path = File::Spec->catfile(@_); + # add leading "./" + $path = "./$path"; + return $path; + } + + } else { # $first_item ne '.' + return $first_item unless @_; # return plain filename + if ($^O eq 'MacOS') { + # relative path with leading ":", but without trailing ":" + return File::Spec->catfile("", $first_item, @_); + } else { # other OS + return File::Spec->catfile($first_item, @_); + } } +} + + +# Use file_path_name() to specify a file path that's expected for $File::Find::Name +# (%Expect_Name). Note: When the no_chdir => 1 option is in effect, $_ is the same +# as $File::Find::Name. In that case, also use this function to specify a file path +# that's expected for $_. +# +# Basically, file_path_name() does the same as file_path() (see above), except that +# there's always a leading ":" on Mac OS, even for plain file/directory names. + +sub file_path_name { + my $path = file_path(@_); + $path = ":$path" if (($^O eq 'MacOS') && ($path !~ /:/)); + return $path; +} + + +MkDir( dir_path('for_find'), 0770 ); +CheckDie(chdir( dir_path('for_find'))); +MkDir( dir_path('fa'), 0770 ); +MkDir( dir_path('fb'), 0770 ); +touch( file_path('fb', 'fb_ord') ); +MkDir( dir_path('fb', 'fba'), 0770 ); +touch( file_path('fb', 'fba', 'fba_ord') ); +if ($^O eq 'MacOS') { + CheckDie( symlink(':fb',':fa:fsl') ) if $symlink_exists; } else { + CheckDie( symlink('../fb','fa/fsl') ) if $symlink_exists; +} +touch( file_path('fa', 'fa_ord') ); + +MkDir( dir_path('fa', 'faa'), 0770 ); +touch( file_path('fa', 'faa', 'faa_ord') ); +MkDir( dir_path('fa', 'fab'), 0770 ); +touch( file_path('fa', 'fab', 'fab_ord') ); +MkDir( dir_path('fa', 'fab', 'faba'), 0770 ); +touch( file_path('fa', 'fab', 'faba', 'faba_ord') ); + + +%Expect_File = (File::Spec->curdir => 1, file_path('fsl') => 1, file_path('fa_ord') => 1, + file_path('fab') => 1, file_path('fab_ord') => 1, file_path('faba') => 1, + file_path('faa') => 1, file_path('faa_ord') => 1); +delete $Expect_File{ file_path('fsl') } unless $symlink_exists; +%Expect_Name = (); +%Expect_Dir = ( dir_path('fa') => 1, dir_path('faa') => 1, dir_path('fab') => 1, dir_path('faba') => 1, + dir_path('fb') => 1, dir_path('fba') => 1); +delete @Expect_Dir{ dir_path('fb'), dir_path('fba') } unless $symlink_exists; +File::Find::find( {wanted => \&wanted_File_Dir_prune}, topdir('fa') ); +Check( scalar(keys %Expect_File) == 0 ); + + +print "# check re-entrancy\n"; +%Expect_File = (File::Spec->curdir => 1, file_path('fsl') => 1, file_path('fa_ord') => 1, + file_path('fab') => 1, file_path('fab_ord') => 1, file_path('faba') => 1, + file_path('faa') => 1, file_path('faa_ord') => 1); +delete $Expect_File{ file_path('fsl') } unless $symlink_exists; +%Expect_Name = (); +%Expect_Dir = ( dir_path('fa') => 1, dir_path('faa') => 1, dir_path('fab') => 1, dir_path('faba') => 1, + dir_path('fb') => 1, dir_path('fba') => 1); +delete @Expect_Dir{ dir_path('fb'), dir_path('fba') } unless $symlink_exists; +File::Find::find( {wanted => sub { + wanted_File_Dir_prune(); + File::Find::find( {wanted => sub {} }, File::Spec->curdir ); + } + }, topdir('fa') ); +Check( scalar(keys %Expect_File) == 0 ); + + +# no_chdir is in effect, hence we use file_path_name to specify the expected paths for %Expect_File +%Expect_File = (file_path_name('fa') => 1, file_path_name('fa', 'fsl') => 1, file_path_name('fa', 'fa_ord') => 1, + file_path_name('fa', 'fab') => 1, file_path_name('fa', 'fab', 'fab_ord') => 1, + file_path_name('fa', 'fab', 'faba') => 1, file_path_name('fa', 'fab', 'faba', 'faba_ord') => 1, + file_path_name('fa', 'faa') => 1, file_path_name('fa', 'faa', 'faa_ord') => 1,); +delete $Expect_File{ file_path_name('fa', 'fsl') } unless $symlink_exists; +%Expect_Name = (); +%Expect_Dir = (dir_path('fa') => 1, dir_path('fa', 'faa') => 1, dir_path('fa', 'fab') => 1, + dir_path('fa', 'fab', 'faba') => 1, dir_path('fb') => 1, dir_path('fb', 'fba') => 1); +delete @Expect_Dir{ dir_path('fb'), dir_path('fb', 'fba') } unless $symlink_exists; +File::Find::find( {wanted => \&wanted_File_Dir, no_chdir => 1}, topdir('fa') ); +Check( scalar(keys %Expect_File) == 0 ); + + +%Expect_File = (); +%Expect_Name = (File::Spec->curdir => 1, file_path_name('.', 'fa') => 1, file_path_name('.', 'fa', 'fsl') => 1, + file_path_name('.', 'fa', 'fa_ord') => 1, file_path_name('.', 'fa', 'fab') => 1, + file_path_name('.', 'fa', 'fab', 'fab_ord') => 1, file_path_name('.', 'fa', 'fab', 'faba') => 1, + file_path_name('.', 'fa', 'fab', 'faba', 'faba_ord') => 1, file_path_name('.', 'fa', 'faa') => 1, + file_path_name('.', 'fa', 'faa', 'faa_ord') => 1, file_path_name('.', 'fb') => 1, + file_path_name('.', 'fb', 'fba') => 1, file_path_name('.', 'fb', 'fba', 'fba_ord') => 1, + file_path_name('.', 'fb', 'fb_ord') => 1); +delete $Expect_Name{ file_path('.', 'fa', 'fsl') } unless $symlink_exists; +%Expect_Dir = (); +File::Find::finddepth( {wanted => \&wanted_Name}, File::Spec->curdir ); +Check( scalar(keys %Expect_Name) == 0 ); + + +# no_chdir is in effect, hence we use file_path_name to specify the expected paths for %Expect_File +%Expect_File = (File::Spec->curdir => 1, file_path_name('.', 'fa') => 1, file_path_name('.', 'fa', 'fsl') => 1, + file_path_name('.', 'fa', 'fa_ord') => 1, file_path_name('.', 'fa', 'fab') => 1, + file_path_name('.', 'fa', 'fab', 'fab_ord') => 1, file_path_name('.', 'fa', 'fab', 'faba') => 1, + file_path_name('.', 'fa', 'fab', 'faba', 'faba_ord') => 1, file_path_name('.', 'fa', 'faa') => 1, + file_path_name('.', 'fa', 'faa', 'faa_ord') => 1, file_path_name('.', 'fb') => 1, + file_path_name('.', 'fb', 'fba') => 1, file_path_name('.', 'fb', 'fba', 'fba_ord') => 1, + file_path_name('.', 'fb', 'fb_ord') => 1); +delete $Expect_File{ file_path_name('.', 'fa', 'fsl') } unless $symlink_exists; +%Expect_Name = (); +%Expect_Dir = (); +File::Find::finddepth( {wanted => \&wanted_File, no_chdir => 1}, File::Spec->curdir ); +Check( scalar(keys %Expect_File) == 0 ); + + +print "# check preprocess\n"; +%Expect_File = (); +%Expect_Name = (); +%Expect_Dir = ( + File::Spec->curdir => {fa => 1, fb => 1}, + dir_path('.', 'fa') => {faa => 1, fab => 1, fa_ord => 1}, + dir_path('.', 'fa', 'faa') => {faa_ord => 1}, + dir_path('.', 'fa', 'fab') => {faba => 1, fab_ord => 1}, + dir_path('.', 'fa', 'fab', 'faba') => {faba_ord => 1}, + dir_path('.', 'fb') => {fba => 1, fb_ord => 1}, + dir_path('.', 'fb', 'fba') => {fba_ord => 1} + ); +File::Find::find( {wanted => \&noop_wanted, preprocess => \&my_preprocess}, File::Spec->curdir ); +Check( scalar(keys %Expect_Dir) == 0 ); + + +print "# check postprocess\n"; +%Expect_File = (); +%Expect_Name = (); +%Expect_Dir = ( + File::Spec->curdir => 1, + dir_path('.', 'fa') => 1, + dir_path('.', 'fa', 'faa') => 1, + dir_path('.', 'fa', 'fab') => 1, + dir_path('.', 'fa', 'fab', 'faba') => 1, + dir_path('.', 'fb') => 1, + dir_path('.', 'fb', 'fba') => 1 + ); +File::Find::find( {wanted => \&noop_wanted, postprocess => \&my_postprocess}, File::Spec->curdir ); +Check( scalar(keys %Expect_Dir) == 0 ); + + +if ( $symlink_exists ) { + print "\n# --- symbolic link tests --- \n\n"; + $FastFileTests_OK= 1; - MkDir( 'for_find',0770 ); - CheckDie(chdir(for_find)); - - $cwd = cwd(); # save cwd - ( $cwd_untainted ) = $cwd =~ m|^(.+)$|; # untaint it - - MkDir( 'fa',0770 ); - MkDir( 'fb',0770 ); - touch('fb/fb_ord'); - MkDir( 'fb/fba',0770 ); - touch('fb/fba/fba_ord'); - CheckDie( symlink('../fb','fa/fsl') ) if $symlink_exists; - touch('fa/fa_ord'); - - MkDir( 'fa/faa',0770 ); - touch('fa/faa/faa_ord'); - MkDir( 'fa/fab',0770 ); - touch('fa/fab/fab_ord'); - MkDir( 'fa/fab/faba',0770 ); - touch('fa/fab/faba/faba_ord'); - - %Expect = ('.' => 1, 'fsl' => 1, 'fa_ord' => 1, 'fab' => 1, 'fab_ord' => 1, - 'faba' => 1, 'faa' => 1, 'faa_ord' => 1); - delete $Expect{'fsl'} unless $symlink_exists; - %Expect_Dir = ('fa' => 1, 'faa' => 1, 'fab' => 1, 'faba' => 1, - 'fb' => 1, 'fba' => 1); - delete @Expect_Dir{'fb','fba'} unless $symlink_exists; - File::Find::find( {wanted => \&wanted, untaint => 1, untaint_pattern => qr|^(.+)$|},'fa' ); - Check( scalar(keys %Expect) == 0 ); - - print "# check re-entancy\n"; - %Expect = ('.' => 1, 'fsl' => 1, 'fa_ord' => 1, 'fab' => 1, 'fab_ord' => 1, - 'faba' => 1, 'faa' => 1, 'faa_ord' => 1); - delete $Expect{'fsl'} unless $symlink_exists; - %Expect_Dir = ('fa' => 1, 'faa' => 1, 'fab' => 1, 'faba' => 1, - 'fb' => 1, 'fba' => 1); - delete @Expect_Dir{'fb','fba'} unless $symlink_exists; - File::Find::find( {wanted => sub { - wanted(); - File::Find::find( {wanted => sub {} , untaint => 1, untaint_pattern => qr|^(.+)$|},'.' ); - }, untaint => 1, untaint_pattern => qr|^(.+)$|},'fa' ); - Check( scalar(keys %Expect) == 0 ); - - %Expect=('fa' => 1, 'fa/fsl' => 1, 'fa/fa_ord' => 1, 'fa/fab' => 1, - 'fa/fab/fab_ord' => 1, 'fa/fab/faba' => 1, - 'fa/fab/faba/faba_ord' => 1, 'fa/faa' => 1, 'fa/faa/faa_ord' => 1); - delete $Expect{'fa/fsl'} unless $symlink_exists; - %Expect_Dir = ('fa' => 1, 'fa/faa' => 1, '/fa/fab' => 1, 'fa/fab/faba' => 1, - 'fb' => 1, 'fb/fba' => 1); - delete @Expect_Dir{'fb','fb/fba'} unless $symlink_exists; - File::Find::find( {wanted => \&wanted, no_chdir => 1, untaint => 1, untaint_pattern => qr|^(.+)$|},'fa' ); - Check( scalar(keys %Expect) == 0 ); - - %Expect=('.' => 1, './fa' => 1, './fa/fsl' => 1, './fa/fa_ord' => 1, './fa/fab' => 1, - './fa/fab/fab_ord' => 1, './fa/fab/faba' => 1, - './fa/fab/faba/faba_ord' => 1, './fa/faa' => 1, './fa/faa/faa_ord' => 1, - './fb' => 1, './fb/fba' => 1, './fb/fba/fba_ord' => 1, './fb/fb_ord' => 1); - delete $Expect{'./fa/fsl'} unless $symlink_exists; - %Expect_Dir = ('./fa' => 1, './fa/faa' => 1, '/fa/fab' => 1, './fa/fab/faba' => 1, - './fb' => 1, './fb/fba' => 1); - delete @Expect_Dir{'./fb','./fb/fba'} unless $symlink_exists; - File::Find::finddepth( {wanted => \&dn_wanted , untaint => 1, untaint_pattern => qr|^(.+)$|},'.' ); - Check( scalar(keys %Expect) == 0 ); - - %Expect=('.' => 1, './fa' => 1, './fa/fsl' => 1, './fa/fa_ord' => 1, './fa/fab' => 1, - './fa/fab/fab_ord' => 1, './fa/fab/faba' => 1, - './fa/fab/faba/faba_ord' => 1, './fa/faa' => 1, './fa/faa/faa_ord' => 1, - './fb' => 1, './fb/fba' => 1, './fb/fba/fba_ord' => 1, './fb/fb_ord' => 1); - delete $Expect{'./fa/fsl'} unless $symlink_exists; - %Expect_Dir = ('./fa' => 1, './fa/faa' => 1, '/fa/fab' => 1, './fa/fab/faba' => 1, - './fb' => 1, './fb/fba' => 1); - delete @Expect_Dir{'./fb','./fb/fba'} unless $symlink_exists; - File::Find::finddepth( {wanted => \&d_wanted, no_chdir => 1, untaint => 1, untaint_pattern => qr|^(.+)$| },'.' ); - Check( scalar(keys %Expect) == 0 ); - - # untaint, preprocess and postprocess tests below added by Thomas Wegner, 17-05-2001 - - print "# check untainting (no follow)\n"; - # don't untaint at all - undef $@; - eval {File::Find::find( {wanted => \&simple_wanted},'fa' );}; - print "# Died: $@"; - Check( $@ =~ m|Insecure dependency| ); - chdir($cwd_untainted); + # Verify that File::Find::find will call wanted even if the topdir of + # is a symlink to a directory, and it shouldn't follow the link + # unless follow is set, which it isn't in this case + %Expect_File = ( file_path('fsl') => 1 ); + %Expect_Name = (); + %Expect_Dir = (); + File::Find::find( {wanted => \&wanted_File_Dir}, topdir('fa', 'fsl') ); + Check( scalar(keys %Expect_File) == 0 ); + + + %Expect_File = (File::Spec->curdir => 1, file_path('fa_ord') => 1, file_path('fsl') => 1, + file_path('fb_ord') => 1, file_path('fba') => 1, file_path('fba_ord') => 1, + file_path('fab') => 1, file_path('fab_ord') => 1, file_path('faba') => 1, + file_path('faa') => 1, file_path('faa_ord') => 1); + %Expect_Name = (); + %Expect_Dir = (File::Spec->curdir => 1, dir_path('fa') => 1, dir_path('faa') => 1, dir_path('fab') => 1, + dir_path('faba') => 1, dir_path('fb') => 1, dir_path('fba') => 1); + File::Find::find( {wanted => \&wanted_File_Dir_prune, follow_fast => 1}, topdir('fa') ); + Check( scalar(keys %Expect_File) == 0 ); + + + # no_chdir is in effect, hence we use file_path_name to specify the expected paths for %Expect_File + %Expect_File = (file_path_name('fa') => 1, file_path_name('fa', 'fa_ord') => 1, + file_path_name('fa', 'fsl') => 1, file_path_name('fa', 'fsl', 'fb_ord') => 1, + file_path_name('fa', 'fsl', 'fba') => 1, file_path_name('fa', 'fsl', 'fba', 'fba_ord') => 1, + file_path_name('fa', 'fab') => 1, file_path_name('fa', 'fab', 'fab_ord') => 1, + file_path_name('fa', 'fab', 'faba') => 1, file_path_name('fa', 'fab', 'faba', 'faba_ord') => 1, + file_path_name('fa', 'faa') => 1, file_path_name('fa', 'faa', 'faa_ord') => 1); + %Expect_Name = (); + %Expect_Dir = (dir_path('fa') => 1, dir_path('fa', 'faa') => 1, dir_path('fa', 'fab') => 1, + dir_path('fa', 'fab', 'faba') => 1, dir_path('fb') => 1, dir_path('fb', 'fba') => 1); + File::Find::find( {wanted => \&wanted_File_Dir, follow_fast => 1, no_chdir => 1}, topdir('fa') ); + Check( scalar(keys %Expect_File) == 0 ); + + + %Expect_File = (); + %Expect_Name = (file_path_name('fa') => 1, file_path_name('fa', 'fa_ord') => 1, file_path_name('fa', 'fsl') => 1, + file_path_name('fa', 'fsl', 'fb_ord') => 1, file_path_name('fa', 'fsl', 'fba') => 1, + file_path_name('fa', 'fsl', 'fba', 'fba_ord') => 1, file_path_name('fa', 'fab') => 1, + file_path_name('fa', 'fab', 'fab_ord') => 1, file_path_name('fa', 'fab', 'faba') => 1, + file_path_name('fa', 'fab', 'faba', 'faba_ord') => 1, file_path_name('fa', 'faa') => 1, + file_path_name('fa', 'faa', 'faa_ord') => 1); + %Expect_Dir = (); + File::Find::finddepth( {wanted => \&wanted_Name, follow_fast => 1}, topdir('fa') ); + Check( scalar(keys %Expect_Name) == 0 ); + + + # no_chdir is in effect, hence we use file_path_name to specify the expected paths for %Expect_File + %Expect_File = (file_path_name('fa') => 1, file_path_name('fa', 'fa_ord') => 1, file_path_name('fa', 'fsl') => 1, + file_path_name('fa', 'fsl', 'fb_ord') => 1, file_path_name('fa', 'fsl', 'fba') => 1, + file_path_name('fa', 'fsl', 'fba', 'fba_ord') => 1, file_path_name('fa', 'fab') => 1, + file_path_name('fa', 'fab', 'fab_ord') => 1, file_path_name('fa', 'fab', 'faba') => 1, + file_path_name('fa', 'fab', 'faba', 'faba_ord') => 1, file_path_name('fa', 'faa') => 1, + file_path_name('fa', 'faa', 'faa_ord') => 1); + %Expect_Name = (); + %Expect_Dir = (); + File::Find::finddepth( {wanted => \&wanted_File, follow_fast => 1, no_chdir => 1}, topdir('fa') ); + Check( scalar(keys %Expect_File) == 0 ); + + + print "# check dangling symbolic links\n"; + MkDir( dir_path('dangling_dir'), 0770 ); + CheckDie( symlink( dir_path('dangling_dir'), file_path('dangling_dir_sl') ) ); + rmdir dir_path('dangling_dir'); + touch(file_path('dangling_file')); + if ($^O eq 'MacOS') { + CheckDie( symlink('dangling_file', ':fa:dangling_file_sl') ); + } else { + CheckDie( symlink('../dangling_file','fa/dangling_file_sl') ); + } + unlink file_path('dangling_file'); + + { + # these tests should also emit a warning + local $^W = 1; + %Expect_File = (File::Spec->curdir => 1, file_path('fa_ord') => 1, file_path('fsl') => 1, + file_path('fb_ord') => 1, file_path('fba') => 1, file_path('fba_ord') => 1, + file_path('fab') => 1, file_path('fab_ord') => 1, file_path('faba') => 1, + file_path('faba_ord') => 1, file_path('faa') => 1, file_path('faa_ord') => 1); + %Expect_Name = (); + %Expect_Dir = (); + undef $warn_msg; + File::Find::find( {wanted => \&wanted_File, follow => 1}, topdir('dangling_dir_sl'), topdir('fa') ); + Check( scalar(keys %Expect_File) == 0 ); + Check( $warn_msg =~ m|dangling_dir_sl is a dangling symbolic link| ); + unlink file_path('fa', 'dangling_file_sl'), file_path('dangling_dir_sl'); + } + + + print "# check recursion\n"; + if ($^O eq 'MacOS') { + CheckDie( symlink(':fa:faa',':fa:faa:faa_sl') ); + } else { + CheckDie( symlink('../faa','fa/faa/faa_sl') ); + } undef $@; - eval {File::Find::find( {wanted => \&simple_wanted, untaint => 1, - untaint_pattern => qr|^(NO_MATCH)$|},'fa' );}; - print "# Died: $@"; - Check( $@ =~ m|is still tainted| ); - chdir($cwd_untainted); + eval {File::Find::find( {wanted => \&simple_wanted, follow => 1, no_chdir => 1}, topdir('fa') ); }; + Check( $@ =~ m|for_find[:/]fa[:/]faa[:/]faa_sl is a recursive symbolic link| ); + unlink file_path('fa', 'faa', 'faa_sl'); - print "# check untaint_skip (no follow)\n"; + + print "# check follow_skip (file)\n"; + if ($^O eq 'MacOS') { + CheckDie( symlink(':fa:fa_ord',':fa:fa_ord_sl') ); # symlink to a file + } else { + CheckDie( symlink('./fa_ord','fa/fa_ord_sl') ); # symlink to a file + } undef $@; - eval {File::Find::find( {wanted => \&simple_wanted, untaint => 1, untaint_skip => 1, - untaint_pattern => qr|^(NO_MATCH)$|}, 'fa' );}; - print "# Died: $@"; - Check( $@ =~ m|insecure cwd| ); - chdir($cwd_untainted); - - print "# check preprocess\n"; - %Expect=( - '.' => {fa => 1, fb => 1}, - './fa' => {faa => 1, fab => 1, fa_ord => 1}, - './fa/faa' => {faa_ord => 1}, - './fa/fab' => {faba => 1, fab_ord => 1}, - './fa/fab/faba' => {faba_ord => 1}, - './fb' => {fba => 1, fb_ord => 1}, - './fb/fba' => {fba_ord => 1} - ); - - File::Find::find( {wanted => \&noop_wanted, preprocess => \&my_preprocess, untaint => 1, - untaint_pattern => qr|^(.+)$|}, '.' ); - Check( scalar(keys %Expect) == 0 ); - - print "# check postprocess\n"; - %Expect=('.' => 1, './fa' => 1, './fa/faa' => 1, './fa/fab' => 1, './fa/fab/faba' => 1, './fb' => 1, - './fb/fba' => 1 ); - File::Find::find( {wanted => \&noop_wanted, postprocess => \&my_postprocess, untaint => 1, - untaint_pattern => qr|^(.+)$|}, '.' ); - Check( scalar(keys %Expect) == 0 ); + eval {File::Find::finddepth( {wanted => \&simple_wanted, follow => 1,follow_skip => 0, no_chdir => 1}, + topdir('fa') );}; + Check( $@ =~ m|for_find[:/]fa[:/]fa_ord encountered a second time| ); - # Verify that File::Find::find will call wanted even if the topdir of - # is a symlink to a directory, and it shouldn't follow the link - # unless follow is set, which it isn't in this case - %Expect = ('fsl' => 1); - %Expect_Dir = (); - File::Find::find( {wanted => \&wanted, untaint => 1},'fa/fsl' ); - Check( scalar(keys %Expect) == 0 ); - - if ( $symlink_exists ) { - $FastFileTests_OK= 1; - %Expect=('.' => 1, 'fa_ord' => 1, 'fsl' => 1, 'fb_ord' => 1, 'fba' => 1, - 'fba_ord' => 1, 'fab' => 1, 'fab_ord' => 1, 'faba' => 1, 'faa' => 1, - 'faa_ord' => 1); - %Expect_Dir = ('fa' => 1, 'fa/faa' => 1, '/fa/fab' => 1, 'fa/fab/faba' => 1, - 'fb' => 1, 'fb/fba' => 1); - File::Find::find( {wanted => \&wanted, follow_fast => 1, untaint => 1, untaint_pattern => qr|^(.+)$|},'fa' ); - Check( scalar(keys %Expect) == 0 ); - - %Expect=('fa' => 1, 'fa/fa_ord' => 1, 'fa/fsl' => 1, 'fa/fsl/fb_ord' => 1, - 'fa/fsl/fba' => 1, 'fa/fsl/fba/fba_ord' => 1, 'fa/fab' => 1, - 'fa/fab/fab_ord' => 1, 'fa/fab/faba' => 1, 'fa/fab/faba/faba_ord' => 1, - 'fa/faa' => 1, 'fa/faa/faa_ord' => 1); - %Expect_Dir = ('fa' => 1, 'fa/faa' => 1, '/fa/fab' => 1, 'fa/fab/faba' => 1, - 'fb' => 1, 'fb/fba' => 1); - File::Find::find( {wanted => \&wanted, follow_fast => 1, no_chdir => 1, untaint => 1, - untaint_pattern => qr|^(.+)$|},'fa' ); - Check( scalar(keys %Expect) == 0 ); - - %Expect=('fa' => 1, 'fa/fa_ord' => 1, 'fa/fsl' => 1, 'fa/fsl/fb_ord' => 1, - 'fa/fsl/fba' => 1, 'fa/fsl/fba/fba_ord' => 1, 'fa/fab' => 1, - 'fa/fab/fab_ord' => 1, 'fa/fab/faba' => 1, 'fa/fab/faba/faba_ord' => 1, - 'fa/faa' => 1, 'fa/faa/faa_ord' => 1); - %Expect_Dir = ('fa' => 1, 'fa/faa' => 1, '/fa/fab' => 1, 'fa/fab/faba' => 1, - 'fb' => 1, 'fb/fba' => 1); - File::Find::finddepth( {wanted => \&dn_wanted, follow_fast => 1, untaint => 1, - untaint_pattern => qr|^(.+)$|},'fa' ); - Check( scalar(keys %Expect) == 0 ); - - %Expect=('fa' => 1, 'fa/fa_ord' => 1, 'fa/fsl' => 1, 'fa/fsl/fb_ord' => 1, - 'fa/fsl/fba' => 1, 'fa/fsl/fba/fba_ord' => 1, 'fa/fab' => 1, - 'fa/fab/fab_ord' => 1, 'fa/fab/faba' => 1, 'fa/fab/faba/faba_ord' => 1, - 'fa/faa' => 1, 'fa/faa/faa_ord' => 1); - %Expect_Dir = ('fa' => 1, 'fa/faa' => 1, '/fa/fab' => 1, 'fa/fab/faba' => 1, - 'fb' => 1, 'fb/fba' => 1); - File::Find::finddepth( {wanted => \&d_wanted, follow_fast => 1, no_chdir => 1, - untaint => 1, untaint_pattern => qr|^(.+)$|},'fa' ); - Check( scalar(keys %Expect) == 0 ); - - # tests below added by Thomas Wegner, 17-05-2001 - - print "# check dangling symbolic links\n"; - MkDir( 'dangling_dir',0770 ); - CheckDie( symlink('dangling_dir','dangling_dir_sl') ); - rmdir 'dangling_dir'; - touch('dangling_file'); - CheckDie( symlink('../dangling_file','fa/dangling_file_sl') ); - unlink 'dangling_file'; - - %Expect=('.' => 1, 'fa_ord' => 1, 'fsl' => 1, 'fb_ord' => 1, 'fba' => 1, - 'fba_ord' => 1, 'fab' => 1, 'fab_ord' => 1, 'faba' => 1, 'faba_ord' => 1, - 'faa' => 1, 'faa_ord' => 1); - %Expect_Dir = ('fa' => 1, 'fa/faa' => 1, 'fa/fab' => 1, 'fa/fab/faba' => 1, - 'fb' => 1, 'fb/fba' => 1); - undef $warn_msg; - File::Find::find( {wanted => \&d_wanted, follow => 1, untaint => 1, - untaint_pattern => qr|^(.+)$|}, 'dangling_dir_sl', 'fa' ); - Check( $warn_msg =~ m|dangling_dir_sl is a dangling symbolic link| ); - unlink 'fa/dangling_file_sl', 'dangling_dir_sl'; - - print "# check recursion\n"; - CheckDie( symlink('../faa','fa/faa/faa_sl') ); - undef $@; - eval {File::Find::find( {wanted => \&simple_wanted, follow => 1, no_chdir => 1, - untaint => 1, untaint_pattern => qr|^(.+)$|},'fa' ); }; - print "# Died: $@"; - Check( $@ =~ m|for_find/fa/faa/faa_sl is a recursive symbolic link| ); - unlink 'fa/faa/faa_sl'; - - print "# check follow_skip (file)\n"; - CheckDie( symlink('./fa_ord','fa/fa_ord_sl') ); # symlink to a file - undef $@; - eval {File::Find::finddepth( {wanted => \&simple_wanted, follow => 1, follow_skip => 0, no_chdir => 1, - untaint => 1, untaint_pattern => qr|^(.+)$|},'fa' );}; - print "# Died: $@"; - Check( $@ =~ m|for_find/fa/fa_ord encountered a second time| ); - - %Expect=('fa' => 1, 'fa/fa_ord' => 1, 'fa/fsl' => 1, 'fa/fsl/fb_ord' => 1, - 'fa/fsl/fba' => 1, 'fa/fsl/fba/fba_ord' => 1, 'fa/fab' => 1, - 'fa/fab/fab_ord' => 1, 'fa/fab/faba' => 1, 'fa/fab/faba/faba_ord' => 1, - 'fa/faa' => 1, 'fa/faa/faa_ord' => 1); - %Expect_Dir = ('fa' => 1, 'fa/faa' => 1, '/fa/fab' => 1, 'fa/fab/faba' => 1, - 'fb' => 1, 'fb/fba' => 1); - File::Find::finddepth( {wanted => \&wanted, follow => 1, follow_skip => 1, no_chdir => 1, - untaint => 1, untaint_pattern => qr|^(.+)$|},'fa' ); - Check( scalar(keys %Expect) == 0 ); - unlink 'fa/fa_ord_sl'; - - print "# check follow_skip (directory)\n"; - CheckDie( symlink('./faa','fa/faa_sl') ); # symlink to a directory - undef $@; - eval {File::Find::find( {wanted => \&simple_wanted, follow => 1, follow_skip => 0, no_chdir => 1, - untaint => 1, untaint_pattern => qr|^(.+)$|},'fa' );}; - print "# Died: $@"; - Check( $@ =~ m|for_find/fa/faa encountered a second time| ); - - undef $@; - eval {File::Find::find( {wanted => \&simple_wanted, follow => 1, follow_skip => 1, no_chdir => 1, - untaint => 1, untaint_pattern => qr|^(.+)$|},'fa' );}; - print "# Died: $@"; - Check( $@ =~ m|for_find/fa/faa encountered a second time| ); - - %Expect=('fa' => 1, 'fa/fa_ord' => 1, 'fa/fsl' => 1, 'fa/fsl/fb_ord' => 1, - 'fa/fsl/fba' => 1, 'fa/fsl/fba/fba_ord' => 1, 'fa/fab' => 1, - 'fa/fab/fab_ord' => 1, 'fa/fab/faba' => 1, 'fa/fab/faba/faba_ord' => 1, - 'fa/faa' => 1, 'fa/faa/faa_ord' => 1); - %Expect_Dir = ('fa' => 1, 'fa/faa' => 1, '/fa/fab' => 1, 'fa/fab/faba' => 1, - 'fb' => 1, 'fb/fba' => 1); - File::Find::find( {wanted => \&wanted, follow => 1, follow_skip => 2, no_chdir => 1, - untaint => 1, untaint_pattern => qr|^(.+)$|},'fa' ); - Check( scalar(keys %Expect) == 0 ); - unlink 'fa/faa_sl'; - - print "# check untainting (follow)\n"; - # don't untaint at all - undef $@; - eval {File::Find::find( {wanted => \&simple_wanted, follow => 1},'fa' );}; - print "# Died: $@"; - Check( $@ =~ m|Insecure dependency| ); - chdir($cwd_untainted); - - undef $@; - eval {File::Find::find( {wanted => \&simple_wanted, follow => 1, untaint => 1, - untaint_pattern => qr|^(NO_MATCH)$|},'fa' );}; - print "# Died: $@"; - Check( $@ =~ m|is still tainted| ); - chdir($cwd_untainted); - - print "# check untaint_skip (follow)\n"; - undef $@; - eval {File::Find::find( {wanted => \&simple_wanted, untaint => 1, untaint_skip => 1, - untaint_pattern => qr|^(NO_MATCH)$|}, 'fa' );}; - print "# Died: $@"; - Check( $@ =~ m|insecure cwd| ); - chdir($cwd_untainted); + # no_chdir is in effect, hence we use file_path_name to specify the expected paths for %Expect_File + %Expect_File = (file_path_name('fa') => 1, file_path_name('fa', 'fa_ord') => 1, + file_path_name('fa', 'fsl') => 1, file_path_name('fa', 'fsl', 'fb_ord') => 1, + file_path_name('fa', 'fsl', 'fba') => 1, file_path_name('fa', 'fsl', 'fba', 'fba_ord') => 1, + file_path_name('fa', 'fab') => 1, file_path_name('fa', 'fab', 'fab_ord') => 1, + file_path_name('fa', 'fab', 'faba') => 1, file_path_name('fa', 'fab', 'faba', 'faba_ord') => 1, + file_path_name('fa', 'faa') => 1, file_path_name('fa', 'faa', 'faa_ord') => 1); + + %Expect_Name = (); + %Expect_Dir = (dir_path('fa') => 1, dir_path('fa', 'faa') => 1, dir_path('fa', 'fab') => 1, + dir_path('fa', 'fab', 'faba') => 1, dir_path('fb') => 1, dir_path('fb', 'fba') => 1); + File::Find::finddepth( {wanted => \&wanted_File_Dir, follow => 1, follow_skip => 1, no_chdir => 1}, + topdir('fa') ); + Check( scalar(keys %Expect_File) == 0 ); + unlink file_path('fa', 'fa_ord_sl'); + + + print "# check follow_skip (directory)\n"; + if ($^O eq 'MacOS') { + CheckDie( symlink(':fa:faa',':fa:faa_sl') ); # symlink to a directory + } else { + CheckDie( symlink('./faa','fa/faa_sl') ); # symlink to a directory } -} + undef $@; + eval {File::Find::find( {wanted => \&simple_wanted, follow => 1, follow_skip => 0, no_chdir => 1}, + topdir('fa') );}; + Check( $@ =~ m|for_find[:/]fa[:/]faa[:/]? encountered a second time| ); + + + undef $@; + eval {File::Find::find( {wanted => \&simple_wanted, follow => 1, follow_skip => 1, no_chdir => 1}, + topdir('fa') );}; + Check( $@ =~ m|for_find[:/]fa[:/]faa[:/]? encountered a second time| ); + + + # no_chdir is in effect, hence we use file_path_name to specify the expected paths for %Expect_File + %Expect_File = (file_path_name('fa') => 1, file_path_name('fa', 'fa_ord') => 1, + file_path_name('fa', 'fsl') => 1, file_path_name('fa', 'fsl', 'fb_ord') => 1, + file_path_name('fa', 'fsl', 'fba') => 1, file_path_name('fa', 'fsl', 'fba', 'fba_ord') => 1, + file_path_name('fa', 'fab') => 1, file_path_name('fa', 'fab', 'fab_ord') => 1, + file_path_name('fa', 'fab', 'faba') => 1, file_path_name('fa', 'fab', 'faba', 'faba_ord') => 1, + file_path_name('fa', 'faa') => 1, file_path_name('fa', 'faa', 'faa_ord') => 1); + + %Expect_Name = (); + %Expect_Dir = (dir_path('fa') => 1, dir_path('fa', 'faa') => 1, dir_path('fa', 'fab') => 1, + dir_path('fa', 'fab', 'faba') => 1, dir_path('fb') => 1, dir_path('fb', 'fba') => 1); + File::Find::find( {wanted => \&wanted_File_Dir, follow => 1, follow_skip => 2, no_chdir => 1}, topdir('fa') ); + Check( scalar(keys %Expect_File) == 0 ); + unlink file_path('fa', 'faa_sl'); + +} print "# of cases: $case\n"; diff --git a/t/lib/findtaint.t b/t/lib/findtaint.t new file mode 100644 index 0000000..c8de8e6 --- /dev/null +++ b/t/lib/findtaint.t @@ -0,0 +1,345 @@ +#!./perl -T + + +my %Expect_File = (); # what we expect for $_ +my %Expect_Name = (); # what we expect for $File::Find::name/fullname +my %Expect_Dir = (); # what we expect for $File::Find::dir +my $symlink_exists = eval { symlink("",""); 1 }; +my $cwd; +my $cwd_untainted; + +BEGIN { + chdir 't' if -d 't'; + unshift @INC => '../lib'; + + for (keys %ENV) { # untaint ENV + ($ENV{$_}) = $ENV{$_} =~ /(.*)/; + } +} + +if ( $symlink_exists ) { print "1..45\n"; } +else { print "1..27\n"; } + +use File::Find; +use File::Spec; +use Cwd; + +# Remove insecure directories from PATH +my @path; +my $sep = ($^O eq 'MSWin32') ? ';' : ':'; +foreach my $dir (split(/$sep/,$ENV{'PATH'})) + { + push(@path,$dir) unless -w $dir; + } +$ENV{'PATH'} = join($sep,@path); + +cleanup(); + +find({wanted => sub { print "ok 1\n" if $_ eq 'filefind.t'; }, untaint => 1, + untaint_pattern => qr|^(.+)$|}, File::Spec->curdir); + +finddepth({wanted => sub { print "ok 2\n" if $_ eq 'filefind.t'; }, + untaint => 1, untaint_pattern => qr|^(.+)$|}, File::Spec->curdir); + +my $case = 2; +my $FastFileTests_OK = 0; + +sub cleanup { + if (-d dir_path('for_find')) { + chdir(dir_path('for_find')); + } + if (-d dir_path('fa')) { + unlink file_path('fa', 'fa_ord'), + file_path('fa', 'fsl'), + file_path('fa', 'faa', 'faa_ord'), + file_path('fa', 'fab', 'fab_ord'), + file_path('fa', 'fab', 'faba', 'faba_ord'), + file_path('fb', 'fb_ord'), + file_path('fb', 'fba', 'fba_ord'); + rmdir dir_path('fa', 'faa'); + rmdir dir_path('fa', 'fab', 'faba'); + rmdir dir_path('fa', 'fab'); + rmdir dir_path('fa'); + rmdir dir_path('fb', 'fba'); + rmdir dir_path('fb'); + chdir File::Spec->updir; + rmdir dir_path('for_find'); + } +} + +END { + cleanup(); +} + +sub Check($) { + $case++; + if ($_[0]) { print "ok $case\n"; } + else { print "not ok $case\n"; } +} + +sub CheckDie($) { + $case++; + if ($_[0]) { print "ok $case\n"; } + else { print "not ok $case\n $!\n"; exit 0; } +} + +sub touch { + CheckDie( open(my $T,'>',$_[0]) ); +} + +sub MkDir($$) { + CheckDie( mkdir($_[0],$_[1]) ); +} + +sub wanted_File_Dir { + print "# \$File::Find::dir => '$File::Find::dir'\n"; + print "# \$_ => '$_'\n"; + s#\.$## if ($^O eq 'VMS' && $_ ne '.'); + Check( $Expect_File{$_} ); + if ( $FastFileTests_OK ) { + delete $Expect_File{ $_} + unless ( $Expect_Dir{$_} && ! -d _ ); + } else { + delete $Expect_File{$_} + unless ( $Expect_Dir{$_} && ! -d $_ ); + } +} + +sub wanted_File_Dir_prune { + &wanted_File_Dir; + $File::Find::prune=1 if $_ eq 'faba'; +} + + +sub simple_wanted { + print "# \$File::Find::dir => '$File::Find::dir'\n"; + print "# \$_ => '$_'\n"; +} + + +# Use dir_path() to specify a directory path that's expected for $File::Find::dir +# (%Expect_Dir). Also use it in file operations like chdir, rmdir etc. +# +# dir_path() concatenates directory names to form a _relative_ directory path, +# independant from the platform it's run on, although there are limitations. +# Don't try to create an absolute path, because that may fail on operating +# systems that have the concept of volume names (e.g. Mac OS). Be careful when +# you want to create an updir path like ../fa (Unix) or ::fa: (Mac OS). Plain +# directory names will work best. As a special case, you can pass it a "." as +# first argument, to create a directory path like "./fa/dir" on operating +# systems other than Mac OS (actually, Mac OS will ignore the ".", if it's the +# first argument). If there's no second argument, this function will return the +# empty string on Mac OS and the string "./" otherwise. + +sub dir_path { + my $first_item = shift @_; + + if ($first_item eq '.') { + if ($^O eq 'MacOS') { + return '' unless @_; + # ignore first argument; return a relative path + # with leading ":" and with trailing ":" + return File::Spec->catdir("", @_); + } else { # other OS + return './' unless @_; + my $path = File::Spec->catdir(@_); + # add leading "./" + $path = "./$path"; + return $path; + } + + } else { # $first_item ne '.' + return $first_item unless @_; # return plain filename + if ($^O eq 'MacOS') { + # relative path with leading ":" and with trailing ":" + return File::Spec->catdir("", $first_item, @_); + } else { # other OS + return File::Spec->catdir($first_item, @_); + } + } +} + + +# Use topdir() to specify a directory path that you want to pass to find/finddepth +# +# Basically, topdir() does the same as dir_path() (see above), except that there's +# no trailing ":" on Mac OS. + +sub topdir { + my $path = dir_path(@_); + $path =~ s/:$// if ($^O eq 'MacOS'); + return $path; +} + + +# Use file_path() to specify a file path that's expected for $_ (%Expect_File). +# Also suitable for file operations like unlink etc. +# +# file_path() concatenates directory names (if any) and a filename to form a +# _relative_ file path (the last argument is assumed to be a file). It's +# independant from the platform it's run on, although there are limitations +# (see the warnings for dir_path() above). As a special case, you can pass it a +# "." as first argument, to create a file path like "./fa/file" on operating +# systems other than Mac OS (actually, Mac OS will ignore the ".", if it's the +# first argument). If there's no second argument, this function will return the +# empty string on Mac OS and the string "./" otherwise. + +sub file_path { + my $first_item = shift @_; + + if ($first_item eq '.') { + if ($^O eq 'MacOS') { + return '' unless @_; + # ignore first argument; return a relative path + # with leading ":", but without trailing ":" + return File::Spec->catfile("", @_); + } else { # other OS + return './' unless @_; + my $path = File::Spec->catfile(@_); + # add leading "./" + $path = "./$path"; + return $path; + } + + } else { # $first_item ne '.' + return $first_item unless @_; # return plain filename + if ($^O eq 'MacOS') { + # relative path with leading ":", but without trailing ":" + return File::Spec->catfile("", $first_item, @_); + } else { # other OS + return File::Spec->catfile($first_item, @_); + } + } +} + + +# Use file_path_name() to specify a file path that's expected for $File::Find::Name +# (%Expect_Name). Note: When the no_chdir => 1 option is in effect, $_ is the same +# as $File::Find::Name. In that case, also use this function to specify a file path +# that's expected for $_. +# +# Basically, file_path_name() does the same as file_path() (see above), except that +# there's always a leading ":" on Mac OS, even for plain file/directory names. + +sub file_path_name { + my $path = file_path(@_); + $path = ":$path" if (($^O eq 'MacOS') && ($path !~ /:/)); + return $path; +} + + + +MkDir( dir_path('for_find'), 0770 ); +CheckDie(chdir( dir_path('for_find'))); + +$cwd = cwd(); # save cwd +( $cwd_untainted ) = $cwd =~ m|^(.+)$|; # untaint it + +MkDir( dir_path('fa'), 0770 ); +MkDir( dir_path('fb'), 0770 ); +touch( file_path('fb', 'fb_ord') ); +MkDir( dir_path('fb', 'fba'), 0770 ); +touch( file_path('fb', 'fba', 'fba_ord') ); +if ($^O eq 'MacOS') { + CheckDie( symlink(':fb',':fa:fsl') ) if $symlink_exists; +} else { + CheckDie( symlink('../fb','fa/fsl') ) if $symlink_exists; +} +touch( file_path('fa', 'fa_ord') ); + +MkDir( dir_path('fa', 'faa'), 0770 ); +touch( file_path('fa', 'faa', 'faa_ord') ); +MkDir( dir_path('fa', 'fab'), 0770 ); +touch( file_path('fa', 'fab', 'fab_ord') ); +MkDir( dir_path('fa', 'fab', 'faba'), 0770 ); +touch( file_path('fa', 'fab', 'faba', 'faba_ord') ); + + +print "# check untainting (no follow)\n"; + +# untainting here should work correctly +%Expect_File = (File::Spec->curdir => 1, file_path('fsl') => 1, file_path('fa_ord') => 1, + file_path('fab') => 1, file_path('fab_ord') => 1, file_path('faba') => 1, + file_path('faa') => 1, file_path('faa_ord') => 1); +delete $Expect_File{ file_path('fsl') } unless $symlink_exists; +%Expect_Name = (); +%Expect_Dir = ( dir_path('fa') => 1, dir_path('faa') => 1, dir_path('fab') => 1, dir_path('faba') => 1, + dir_path('fb') => 1, dir_path('fba') => 1); +delete @Expect_Dir{ dir_path('fb'), dir_path('fba') } unless $symlink_exists; +File::Find::find( {wanted => \&wanted_File_Dir_prune, untaint => 1, untaint_pattern => qr|^(.+)$|}, topdir('fa') ); +Check( scalar(keys %Expect_File) == 0 ); + + +# don't untaint at all, should die +%Expect_File = (); +%Expect_Name = (); +%Expect_Dir = (); +undef $@; +eval {File::Find::find( {wanted => \&simple_wanted}, topdir('fa') );}; +Check( $@ =~ m|Insecure dependency| ); +chdir($cwd_untainted); + + +# untaint pattern doesn't match, should die +undef $@; +eval {File::Find::find( {wanted => \&simple_wanted, untaint => 1, + untaint_pattern => qr|^(NO_MATCH)$|}, topdir('fa') );}; +Check( $@ =~ m|is still tainted| ); +chdir($cwd_untainted); + + +# untaint pattern doesn't match, should die when we chdir to cwd +print "# check untaint_skip (no follow)\n"; +undef $@; +eval {File::Find::find( {wanted => \&simple_wanted, untaint => 1, untaint_skip => 1, + untaint_pattern => qr|^(NO_MATCH)$|}, topdir('fa') );}; +Check( $@ =~ m|insecure cwd| ); +chdir($cwd_untainted); + + +if ( $symlink_exists ) { + print "\n# --- symbolic link tests --- \n\n"; + $FastFileTests_OK= 1; + + print "# check untainting (follow)\n"; + + # untainting here should work correctly + # no_chdir is in effect, hence we use file_path_name to specify the expected paths for %Expect_File + %Expect_File = (file_path_name('fa') => 1, file_path_name('fa', 'fa_ord') => 1, + file_path_name('fa', 'fsl') => 1, file_path_name('fa', 'fsl', 'fb_ord') => 1, + file_path_name('fa', 'fsl', 'fba') => 1, file_path_name('fa', 'fsl', 'fba', 'fba_ord') => 1, + file_path_name('fa', 'fab') => 1, file_path_name('fa', 'fab', 'fab_ord') => 1, + file_path_name('fa', 'fab', 'faba') => 1, file_path_name('fa', 'fab', 'faba', 'faba_ord') => 1, + file_path_name('fa', 'faa') => 1, file_path_name('fa', 'faa', 'faa_ord') => 1); + %Expect_Name = (); + %Expect_Dir = (dir_path('fa') => 1, dir_path('fa', 'faa') => 1, dir_path('fa', 'fab') => 1, + dir_path('fa', 'fab', 'faba') => 1, dir_path('fb') => 1, dir_path('fb', 'fba') => 1); + File::Find::find( {wanted => \&wanted_File_Dir, follow_fast => 1, no_chdir => 1, untaint => 1, + untaint_pattern => qr|^(.+)$| }, topdir('fa') ); + Check( scalar(keys %Expect_File) == 0 ); + + + # don't untaint at all, should die + undef $@; + eval {File::Find::find( {wanted => \&simple_wanted, follow => 1}, topdir('fa') );}; + Check( $@ =~ m|Insecure dependency| ); + chdir($cwd_untainted); + + # untaint pattern doesn't match, should die + undef $@; + eval {File::Find::find( {wanted => \&simple_wanted, follow => 1, untaint => 1, + untaint_pattern => qr|^(NO_MATCH)$|}, topdir('fa') );}; + Check( $@ =~ m|is still tainted| ); + chdir($cwd_untainted); + + # untaint pattern doesn't match, should die when we chdir to cwd + print "# check untaint_skip (follow)\n"; + undef $@; + eval {File::Find::find( {wanted => \&simple_wanted, untaint => 1, untaint_skip => 1, + untaint_pattern => qr|^(NO_MATCH)$|}, topdir('fa') );}; + Check( $@ =~ m|insecure cwd| ); + chdir($cwd_untainted); + +} + +print "# of cases: $case\n";