& what's to be done for 5.8.0?
[p5sagit/p5-mst-13.2.git] / lib / File / Find / t / taint.t
1 #!./perl -T
2
3
4 my %Expect_File = (); # what we expect for $_ 
5 my %Expect_Name = (); # what we expect for $File::Find::name/fullname
6 my %Expect_Dir  = (); # what we expect for $File::Find::dir
7 my ($cwd, $cwd_untainted);
8
9
10 BEGIN {
11     chdir 't' if -d 't';
12     unshift @INC => '../lib';
13 }
14
15 use Config;
16
17 BEGIN {
18     for (keys %ENV) { # untaint ENV
19         ($ENV{$_}) = $ENV{$_} =~ /(.*)/;
20     }
21
22     # Remove insecure directories from PATH
23     my @path;
24     my $sep = $Config{path_sep};
25     foreach my $dir (split(/\Q$sep/,$ENV{'PATH'}))
26     {
27         ##
28         ## Match the directory taint tests in mg.c::Perl_magic_setenv()
29         ##
30         push(@path,$dir) unless (length($dir) >= 256
31                                  or
32                                  substr($dir,0,1) ne "/"
33                                  or
34                                  (stat $dir)[2] & 002);
35     }
36     $ENV{'PATH'} = join($sep,@path);
37 }
38
39 use Test::More tests => 45;
40
41 my $symlink_exists = eval { symlink("",""); 1 };
42
43 use File::Find;
44 use File::Spec;
45 use Cwd;
46
47 cleanup();
48
49 my $found;
50 find({wanted => sub { $found = 1 if ($_ eq 'commonsense.t') }, 
51                 untaint => 1, untaint_pattern => qr|^(.+)$|}, File::Spec->curdir);
52   
53 ok($found, 'commonsense.t found');
54 $found = 0;
55   
56 finddepth({wanted => sub { $found = 1 if $_ eq 'commonsense.t'; },
57            untaint => 1, untaint_pattern => qr|^(.+)$|}, File::Spec->curdir);
58   
59 ok($found, 'commonsense.t found again');
60
61 my $case = 2;
62 my $FastFileTests_OK = 0;
63
64 sub cleanup {
65     if (-d dir_path('for_find')) {
66         chdir(dir_path('for_find'));
67     }
68     if (-d dir_path('fa')) {
69         unlink file_path('fa', 'fa_ord'),
70                file_path('fa', 'fsl'),
71                file_path('fa', 'faa', 'faa_ord'),
72                file_path('fa', 'fab', 'fab_ord'),
73                file_path('fa', 'fab', 'faba', 'faba_ord'),
74                file_path('fb', 'fb_ord'),
75                file_path('fb', 'fba', 'fba_ord');
76         rmdir dir_path('fa', 'faa');
77         rmdir dir_path('fa', 'fab', 'faba');
78         rmdir dir_path('fa', 'fab');
79         rmdir dir_path('fa');
80         rmdir dir_path('fb', 'fba');
81         rmdir dir_path('fb');
82         chdir File::Spec->updir;
83         rmdir dir_path('for_find');
84     }
85 }
86
87 END {
88     cleanup();
89 }
90
91 sub touch {
92     ok( open(my $T,'>',$_[0]), "Opened $_[0] successfully" );
93 }
94
95 sub MkDir($$) {
96     ok( mkdir($_[0],$_[1]), "Created directory $_[0] successfully" );
97 }
98
99 sub wanted_File_Dir {
100     print "# \$File::Find::dir => '$File::Find::dir'\n";
101     print "# \$_ => '$_'\n";
102     s#\.$## if ($^O eq 'VMS' && $_ ne '.');
103         ok( $Expect_File{$_}, "Expected and found $File::Find::name" );
104     if ( $FastFileTests_OK ) {
105         delete $Expect_File{ $_} 
106           unless ( $Expect_Dir{$_} && ! -d _ );
107     } else {
108         delete $Expect_File{$_} 
109           unless ( $Expect_Dir{$_} && ! -d $_ );
110     }
111 }
112
113 sub wanted_File_Dir_prune {
114     &wanted_File_Dir;
115     $File::Find::prune=1 if  $_ eq 'faba';
116 }
117
118 sub simple_wanted {
119     print "# \$File::Find::dir => '$File::Find::dir'\n";
120     print "# \$_ => '$_'\n";
121 }
122
123
124 # Use dir_path() to specify a directory path that's expected for
125 # $File::Find::dir (%Expect_Dir). Also use it in file operations like
126 # chdir, rmdir etc.
127 #
128 # dir_path() concatenates directory names to form a _relative_
129 # directory path, independant from the platform it's run on, although
130 # there are limitations.  Don't try to create an absolute path,
131 # because that may fail on operating systems that have the concept of
132 # volume names (e.g. Mac OS). Be careful when you want to create an
133 # updir path like ../fa (Unix) or ::fa: (Mac OS). Plain directory
134 # names will work best. As a special case, you can pass it a "." as
135 # first argument, to create a directory path like "./fa/dir" on
136 # operating systems other than Mac OS (actually, Mac OS will ignore
137 # the ".", if it's the first argument). If there's no second argument,
138 # this function will return the empty string on Mac OS and the string
139 # "./" otherwise.
140
141 sub dir_path {
142     my $first_item = shift @_;
143
144     if ($first_item eq '.') {
145         if ($^O eq 'MacOS') {
146             return '' unless @_;
147             # ignore first argument; return a relative path
148             # with leading ":" and with trailing ":"
149             return File::Spec->catdir("", @_); 
150         } else { # other OS
151             return './' unless @_;
152             my $path = File::Spec->catdir(@_);
153             # add leading "./"
154             $path = "./$path";
155             return $path;
156         }
157
158     } else { # $first_item ne '.'
159         return $first_item unless @_; # return plain filename
160         if ($^O eq 'MacOS') {
161             # relative path with leading ":" and with trailing ":"
162             return File::Spec->catdir("", $first_item, @_);
163         } else { # other OS
164             return File::Spec->catdir($first_item, @_);
165         }
166     }
167 }
168
169
170 # Use topdir() to specify a directory path that you want to pass to
171 #find/finddepth Basically, topdir() does the same as dir_path() (see
172 #above), except that there's no trailing ":" on Mac OS.
173
174 sub topdir {
175     my $path = dir_path(@_);
176     $path =~ s/:$// if ($^O eq 'MacOS');
177     return $path;
178 }
179
180
181 # Use file_path() to specify a file path that's expected for $_ (%Expect_File).
182 # Also suitable for file operations like unlink etc.
183
184 # file_path() concatenates directory names (if any) and a filename to
185 # form a _relative_ file path (the last argument is assumed to be a
186 # file). It's independant from the platform it's run on, although
187 # there are limitations (see the warnings for dir_path() above). As a
188 # special case, you can pass it a "." as first argument, to create a
189 # file path like "./fa/file" on operating systems other than Mac OS
190 # (actually, Mac OS will ignore the ".", if it's the first
191 # argument). If there's no second argument, this function will return
192 # the empty string on Mac OS and the string "./" otherwise.
193
194 sub file_path {
195     my $first_item = shift @_;
196
197     if ($first_item eq '.') {
198         if ($^O eq 'MacOS') {
199             return '' unless @_;
200             # ignore first argument; return a relative path  
201             # with leading ":", but without trailing ":"
202             return File::Spec->catfile("", @_); 
203         } else { # other OS
204             return './' unless @_;
205             my $path = File::Spec->catfile(@_);
206             # add leading "./" 
207             $path = "./$path"; 
208             return $path;
209         }
210
211     } else { # $first_item ne '.'
212         return $first_item unless @_; # return plain filename
213         if ($^O eq 'MacOS') {
214             # relative path with leading ":", but without trailing ":"
215             return File::Spec->catfile("", $first_item, @_);
216         } else { # other OS
217             return File::Spec->catfile($first_item, @_);
218         }
219     }
220 }
221
222
223 # Use file_path_name() to specify a file path that's expected for
224 # $File::Find::Name (%Expect_Name). Note: When the no_chdir => 1
225 # option is in effect, $_ is the same as $File::Find::Name. In that
226 # case, also use this function to specify a file path that's expected
227 # for $_.
228 #
229 # Basically, file_path_name() does the same as file_path() (see
230 # above), except that there's always a leading ":" on Mac OS, even for
231 # plain file/directory names.
232
233 sub file_path_name {
234     my $path = file_path(@_);
235     $path = ":$path" if (($^O eq 'MacOS') && ($path !~ /:/));
236     return $path;
237 }
238
239
240 MkDir( dir_path('for_find'), 0770 );
241 ok( chdir( dir_path('for_find')), 'successful chdir() to for_find' );
242
243 $cwd = cwd(); # save cwd
244 ( $cwd_untainted ) = $cwd =~ m|^(.+)$|; # untaint it
245
246 MkDir( dir_path('fa'), 0770 );
247 MkDir( dir_path('fb'), 0770  );
248 touch( file_path('fb', 'fb_ord') );
249 MkDir( dir_path('fb', 'fba'), 0770  );
250 touch( file_path('fb', 'fba', 'fba_ord') );
251 SKIP: {
252         skip "Creating symlink", 1, unless $symlink_exists;
253 if ($^O eq 'MacOS') {
254       ok( symlink(':fb',':fa:fsl'), 'Created symbolic link' );
255 } else {
256       ok( symlink('../fb','fa/fsl'), 'Created symbolic link' );
257 }
258 }
259 touch( file_path('fa', 'fa_ord') );
260
261 MkDir( dir_path('fa', 'faa'), 0770  );
262 touch( file_path('fa', 'faa', 'faa_ord') );
263 MkDir( dir_path('fa', 'fab'), 0770  );
264 touch( file_path('fa', 'fab', 'fab_ord') );
265 MkDir( dir_path('fa', 'fab', 'faba'), 0770  );
266 touch( file_path('fa', 'fab', 'faba', 'faba_ord') );
267
268 print "# check untainting (no follow)\n";
269
270 # untainting here should work correctly
271
272 %Expect_File = (File::Spec->curdir => 1, file_path('fsl') =>
273                 1,file_path('fa_ord') => 1, file_path('fab') => 1,
274                 file_path('fab_ord') => 1, file_path('faba') => 1,
275                 file_path('faa') => 1, file_path('faa_ord') => 1);
276 delete $Expect_File{ file_path('fsl') } unless $symlink_exists;
277 %Expect_Name = ();
278
279 %Expect_Dir = ( dir_path('fa') => 1, dir_path('faa') => 1,
280                 dir_path('fab') => 1, dir_path('faba') => 1,
281                 dir_path('fb') => 1, dir_path('fba') => 1);
282
283 delete @Expect_Dir{ dir_path('fb'), dir_path('fba') } unless $symlink_exists;
284
285 File::Find::find( {wanted => \&wanted_File_Dir_prune, untaint => 1,
286                    untaint_pattern => qr|^(.+)$|}, topdir('fa') );
287
288 is(scalar keys %Expect_File, 0, 'Found all expected files');
289
290
291 # don't untaint at all, should die
292 %Expect_File = ();
293 %Expect_Name = ();
294 %Expect_Dir  = ();
295 undef $@;
296 eval {File::Find::find( {wanted => \&simple_wanted}, topdir('fa') );};
297 like( $@, qr|Insecure dependency|, 'Tainted directory causes death (good)' );
298 chdir($cwd_untainted);
299
300
301 # untaint pattern doesn't match, should die 
302 undef $@;
303
304 eval {File::Find::find( {wanted => \&simple_wanted, untaint => 1,
305                          untaint_pattern => qr|^(NO_MATCH)$|},
306                          topdir('fa') );};
307
308 like( $@, qr|is still tainted|, 'Bad untaint pattern causes death (good)' );
309 chdir($cwd_untainted);
310
311
312 # untaint pattern doesn't match, should die when we chdir to cwd   
313 print "# check untaint_skip (No follow)\n";
314 undef $@;
315
316 eval {File::Find::find( {wanted => \&simple_wanted, untaint => 1,
317                          untaint_skip => 1, untaint_pattern =>
318                          qr|^(NO_MATCH)$|}, topdir('fa') );};
319
320 print "# $@" if $@;
321 #$^D = 8;
322 like( $@, qr|insecure cwd|, 'Bad untaint pattern causes death in cwd (good)' );
323  
324 chdir($cwd_untainted);
325
326
327 SKIP: {
328     skip "Symbolic link tests", 17, unless $symlink_exists;
329     print "# --- symbolic link tests --- \n";
330     $FastFileTests_OK= 1;
331
332     print "# check untainting (follow)\n";
333
334     # untainting here should work correctly
335     # no_chdir is in effect, hence we use file_path_name to specify the expected paths for %Expect_File
336
337     %Expect_File = (file_path_name('fa') => 1,
338                     file_path_name('fa','fa_ord') => 1,
339                     file_path_name('fa', 'fsl') => 1,
340                     file_path_name('fa', 'fsl', 'fb_ord') => 1,
341                     file_path_name('fa', 'fsl', 'fba') => 1,
342                     file_path_name('fa', 'fsl', 'fba', 'fba_ord') => 1,
343                     file_path_name('fa', 'fab') => 1,
344                     file_path_name('fa', 'fab', 'fab_ord') => 1,
345                     file_path_name('fa', 'fab', 'faba') => 1,
346                     file_path_name('fa', 'fab', 'faba', 'faba_ord') => 1,
347                     file_path_name('fa', 'faa') => 1,
348                     file_path_name('fa', 'faa', 'faa_ord') => 1);
349
350     %Expect_Name = ();
351
352     %Expect_Dir = (dir_path('fa') => 1,
353                    dir_path('fa', 'faa') => 1,
354                    dir_path('fa', 'fab') => 1,
355                    dir_path('fa', 'fab', 'faba') => 1,
356                    dir_path('fb') => 1,
357                    dir_path('fb', 'fba') => 1);
358
359     File::Find::find( {wanted => \&wanted_File_Dir, follow_fast => 1,
360                        no_chdir => 1, untaint => 1, untaint_pattern =>
361                        qr|^(.+)$| }, topdir('fa') );
362
363     is( scalar(keys %Expect_File), 0, 'Found all files in symlink test' );
364  
365     
366     # don't untaint at all, should die
367     undef $@;
368
369     eval {File::Find::find( {wanted => \&simple_wanted, follow => 1},
370                             topdir('fa') );};
371
372     like( $@, qr|Insecure dependency|, 'Not untainting causes death (good)' );
373     chdir($cwd_untainted);
374
375     # untaint pattern doesn't match, should die
376     undef $@;
377
378     eval {File::Find::find( {wanted => \&simple_wanted, follow => 1,
379                              untaint => 1, untaint_pattern =>
380                              qr|^(NO_MATCH)$|}, topdir('fa') );};
381
382     like( $@, qr|is still tainted|, 'Bat untaint pattern causes death (good)' );
383     chdir($cwd_untainted);
384
385     # untaint pattern doesn't match, should die when we chdir to cwd
386     print "# check untaint_skip (Follow)\n";
387     undef $@;
388
389     eval {File::Find::find( {wanted => \&simple_wanted, untaint => 1,
390                              untaint_skip => 1, untaint_pattern =>
391                              qr|^(NO_MATCH)$|}, topdir('fa') );};
392     like( $@, qr|insecure cwd|, 'Cwd not untainted with bad pattern (good)' );
393
394     chdir($cwd_untainted);
395
396