File Coverage

blib/lib/File/Listing.pm
Criterion Covered Total %
statement 119 141 84.4
branch 59 102 57.8
condition 33 77 42.8
subroutine 18 21 85.7
pod 2 6 33.3
total 231 347 66.5


line stmt bran cond sub pod time code
1             package File::Listing;
2              
3 1     1   574 use strict;
  1         8  
  1         30  
4 1     1   5 use warnings;
  1         2  
  1         22  
5 1     1   5 use Carp ();
  1         1  
  1         18  
6 1     1   497 use HTTP::Date qw(str2time);
  1         4804  
  1         78  
7 1     1   8 use base qw( Exporter );
  1         2  
  1         792  
8              
9             # ABSTRACT: Parse directory listing
10             our $VERSION = '6.14'; # VERSION
11              
12 0     0 1 0 sub Version { $File::Listing::VERSION; }
13              
14             our @EXPORT = qw(parse_dir);
15              
16             sub parse_dir ($;$$$)
17             {
18 547     547 1 687151 my($dir, $tz, $fstype, $error) = @_;
19              
20 547   100     1385 $fstype ||= 'unix';
21 547         1067 $fstype = "File::Listing::" . lc $fstype;
22              
23 547         1020 my @args = $_[0];
24 547 100       1478 push(@args, $tz) if(@_ >= 2);
25 547 50       1028 push(@args, $error) if(@_ >= 4);
26              
27 547         1568 $fstype->parse(@args);
28             }
29              
30              
31 0     0 0 0 sub line { Carp::croak("Not implemented yet"); }
32       0 0   sub init { } # Dummy sub
33              
34              
35             sub file_mode ($)
36             {
37 16465 50   16465 0 9520899 Carp::croak("Input to file_mode() must be a 10 character string.")
38             unless length($_[0]) == 10;
39              
40             # This routine was originally borrowed from Graham Barr's
41             # Net::FTP package.
42              
43 16465         32737 local $_ = shift;
44 16465         23849 my $mode = 0;
45 16465         21053 my($type);
46              
47 16465 50       103282 s/^(.)// and $type = $1;
48              
49             # When the set-group-ID bit (file mode bit 02000) is set, and the group
50             # execution bit (file mode bit 00020) is unset, and it is a regular file,
51             # some implementations of `ls' use the letter `S', others use `l' or `L'.
52             # Convert this `S'.
53              
54 16465         52709 s/[Ll](...)$/S$1/;
55              
56 16465         55974 while (/(.)/g) {
57 148185         206518 $mode <<= 1;
58 148185 100 100     680915 $mode |= 1 if $1 ne "-" &&
      100        
      100        
59             $1 ne "*" &&
60             $1 ne 'S' &&
61             $1 ne 'T';
62             }
63              
64 16465 100       41561 $mode |= 0004000 if /^..s....../i;
65 16465 100       40098 $mode |= 0002000 if /^.....s.../i;
66 16465 100       35489 $mode |= 0001000 if /^........t/i;
67              
68             # De facto standard definitions. From 'stat.h' on Solaris 9.
69              
70 16465 0 33     140921 $type eq "p" and $mode |= 0010000 or # fifo
      33        
      33        
      66        
      66        
      33        
      66        
      33        
      33        
      0        
      33        
      0        
      0        
      0        
      0        
71             $type eq "c" and $mode |= 0020000 or # character special
72             $type eq "d" and $mode |= 0040000 or # directory
73             $type eq "b" and $mode |= 0060000 or # block special
74             $type eq "-" and $mode |= 0100000 or # regular
75             $type eq "l" and $mode |= 0120000 or # symbolic link
76             $type eq "s" and $mode |= 0140000 or # socket
77             $type eq "D" and $mode |= 0150000 or # door
78             Carp::croak("Unknown file type: $type");
79              
80 16465         44727 $mode;
81             }
82              
83              
84             sub parse
85             {
86 547     547 0 1070 my($pkg, $dir, $tz, $error) = @_;
87              
88             # First let's try to determine what kind of dir parameter we have
89             # received. We allow both listings, reference to arrays and
90             # file handles to read from.
91              
92 547 50       2795 if (ref($dir) eq 'ARRAY') {
    100          
    50          
    50          
93             # Already split up
94             }
95             elsif (ref($dir) eq 'GLOB') {
96             # A file handle
97             }
98             elsif (ref($dir)) {
99 0         0 Carp::croak("Illegal argument to parse_dir()");
100             }
101             elsif ($dir =~ /^\*\w+(::\w+)+$/) {
102             # This scalar looks like a file handle, so we assume it is
103             }
104             else {
105             # A normal scalar listing
106 545         2792 $dir = [ split(/\n/, $dir) ];
107             }
108              
109 547         1710 $pkg->init();
110              
111 547         818 my @files = ();
112 547 100       1141 if (ref($dir) eq 'ARRAY') {
113 545         933 for (@$dir) {
114 1923         5101 push(@files, $pkg->line($_, $tz, $error));
115             }
116             }
117             else {
118 2         5 local($_);
119 2         86 while (my $line = <$dir>) {
120 86         145 chomp $line;
121 86         192 push(@files, $pkg->line($line, $tz, $error));
122             }
123             }
124 547 100       3424 wantarray ? @files : \@files; ## no critic (Freenode::Wantarray)
125             }
126              
127              
128              
129             package File::Listing::unix;
130              
131 1     1   8 use HTTP::Date qw(str2time);
  1         2  
  1         698  
132              
133             our @ISA = qw(File::Listing);
134              
135             # A place to remember current directory from last line parsed.
136             our $curdir;
137              
138             sub init
139             {
140 5     5   10 $curdir = '';
141             }
142              
143              
144             sub line
145             {
146 135     135   179 shift; # package name
147 135         263 local($_) = shift;
148 135         206 my($tz, $error) = @_;
149              
150 135         231 s/\015//g;
151             #study;
152              
153 135         187 my ($kind, $size, $date, $name);
154 135 100 66     1158 if (($kind, $size, $date, $name) =
    100 66        
    50 0        
    0 0        
    0          
155             /^([\-\*FlrwxsStTdD]{10}) # Type and permission bits
156             .* # Graps
157             \D(\d+) # File size
158             \s+ # Some space
159             (\w{3}\s+\d+\s+(?:\d{1,2}:\d{2}|\d{4})|\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}) # Date
160             \s+ # Some more space
161             (.*)$ # File name
162             /x )
163              
164             {
165 105 100 100     413 return if $name eq '.' || $name eq '..';
166 81 100       178 $name = "$curdir/$name" if length $curdir;
167 81         127 my $type = '?';
168 81 50 33     258 if ($kind =~ /^l/ && $name =~ /(.*) -> (.*)/ ) {
    100          
    50          
169 0         0 $name = $1;
170 0         0 $type = "l $2";
171             }
172             elsif ($kind =~ /^[\-F]/) { # (hopefully) a regular file
173 70         118 $type = 'f';
174             }
175             elsif ($kind =~ /^[dD]/) {
176 11         21 $type = 'd';
177 11         25 $size = undef; # Don't believe the reported size
178             }
179 81         213 return [$name, $type, $size, str2time($date, $tz),
180             File::Listing::file_mode($kind)];
181              
182             }
183             elsif (/^(.+):$/ && !/^[dcbsp].*\s.*\s.*:$/ ) {
184 9         20 my $dir = $1;
185 9 50       22 return () if $dir eq '.';
186 9         12 $curdir = $dir;
187 9         24 return ();
188             }
189             elsif (/^[Tt]otal\s+(\d+)$/ || /^\s*$/) {
190 21         77 return ();
191             }
192             elsif (/not found/ || # OSF1, HPUX, and SunOS return
193             # "$file not found"
194             /No such file/ || # IRIX returns
195             # "UX:ls: ERROR: Cannot access $file: No such file or directory"
196             # Solaris returns
197             # "$file: No such file or directory"
198             /cannot find/ # Windows NT returns
199             # "The system cannot find the path specified."
200             ) {
201 0 0       0 return () unless defined $error;
202 0 0       0 &$error($_) if ref($error) eq 'CODE';
203 0 0       0 warn "Error: $_\n" if $error eq 'warn';
204 0         0 return ();
205             }
206             elsif ($_ eq '') { # AIX, and Linux return nothing
207 0 0       0 return () unless defined $error;
208 0 0       0 &$error("No such file or directory") if ref($error) eq 'CODE';
209 0 0       0 warn "Warning: No such file or directory\n" if $error eq 'warn';
210 0         0 return ();
211             }
212             else {
213             # parse failed, check if the dosftp parse understands it
214 0         0 File::Listing::dosftp->init();
215 0         0 return(File::Listing::dosftp->line($_,$tz,$error));
216             }
217              
218             }
219              
220              
221              
222             package File::Listing::dosftp;
223              
224 1     1   8 use HTTP::Date qw(str2time);
  1         2  
  1         1054  
225              
226             our @ISA = qw(File::Listing);
227              
228             # A place to remember current directory from last line parsed.
229             our $curdir;
230              
231              
232              
233             sub init
234             {
235 1     1   4 $curdir = '';
236             }
237              
238              
239             sub line
240             {
241 2     2   4 shift; # package name
242 2         4 local($_) = shift;
243 2         5 my($tz, $error) = @_;
244              
245 2         6 s/\015//g;
246              
247 2         4 my ($date, $size_or_dir, $name, $size);
248              
249             # 02-05-96 10:48AM 1415 src.slf
250             # 09-10-96 09:18AM sl_util
251 2 50       19 if (($date, $size_or_dir, $name) =
252             /^(\d\d-\d\d-\d\d\s+\d\d:\d\d\wM) # Date and time info
253             \s+ # Some space
254             (<\w{3}>|\d+) # Dir or Size
255             \s+ # Some more space
256             (.+)$ # File name
257             /x )
258             {
259 2 50 33     14 return if $name eq '.' || $name eq '..';
260 2 50       6 $name = "$curdir/$name" if length $curdir;
261 2         6 my $type = '?';
262 2 100       6 if ($size_or_dir eq '') {
263 1         3 $type = "d";
264 1         3 $size = ""; # directories have no size in the pc listing
265             }
266             else {
267 1         4 $type = 'f';
268 1         3 $size = $size_or_dir;
269             }
270 2         10 return [$name, $type, $size, str2time($date, $tz), undef];
271             }
272             else {
273 0 0       0 return () unless defined $error;
274 0 0       0 &$error($_) if ref($error) eq 'CODE';
275 0 0       0 warn "Can't parse: $_\n" if $error eq 'warn';
276 0         0 return ();
277             }
278              
279             }
280              
281              
282              
283             package File::Listing::vms;
284             our @ISA = qw(File::Listing);
285              
286             package File::Listing::netware;
287             our @ISA = qw(File::Listing);
288              
289              
290              
291             package File::Listing::apache;
292              
293             our @ISA = qw(File::Listing);
294              
295              
296       541     sub init { }
297              
298              
299             sub line {
300 1872     1872   2582 shift; # package name
301 1872         3789 local($_) = shift;
302 1872         3186 my($tz, $error) = @_; # ignored for now...
303              
304 1872         12225 s!]*>! !g; # clean away various table stuff
305 1872 100       13343 if (m!.*.*?(\d+)-([a-zA-Z]+|\d+)-(\d+)\s+(\d+):(\d+)\s+(?:([\d\.]+[kMG]?|-))!i) {
    100          
306 1608         5102 my($filename, $filesize) = ($1, $7);
307 1608         4959 my($d,$m,$y, $H,$M) = ($2,$3,$4,$5,$6);
308 1608 100       3970 if ($m =~ /^\d+$/) {
309 699         1718 ($d,$y) = ($y,$d) # iso date
310             }
311             else {
312 909         1880 $m = _monthabbrev_number($m);
313             }
314              
315 1608 100       4216 $filesize = 0 if $filesize eq '-';
316 1608 100       5754 if ($filesize =~ s/k$//i) {
    100          
    50          
317 589         1525 $filesize *= 1024;
318             }
319             elsif ($filesize =~ s/M$//) {
320 10         32 $filesize *= 1024*1024;
321             }
322             elsif ($filesize =~ s/G$//) {
323 0         0 $filesize *= 1024*1024*1024;
324             }
325 1608         2507 $filesize = int $filesize;
326              
327 1608         7795 require Time::Local;
328 1608         3783 my $filetime = Time::Local::timelocal(0,$M,$H,$d,$m-1,_guess_year($y));
329 1608 100       108375 my $filetype = ($filename =~ s|/$|| ? "d" : "f");
330 1608         8281 return [$filename, $filetype, $filesize, $filetime, undef];
331             }
332              
333             # the default listing doesn't include timestamps or file sizes
334             # but we don't want to grab navigation links, so we ignore links
335             # that have a non-trailing slash / character or ?
336             elsif(m!.*!i) {
337 23         52 my $filename = $1;
338 23 100       56 my $filetype = ($filename =~ s|/$|| ? "d" : "f");
339 23         71 return [$filename, $filetype, undef, undef, undef];
340             }
341              
342 241         453 return ();
343             }
344              
345              
346             sub _guess_year {
347 1608     1608   2318 my $y = shift;
348              
349             # if the year is already four digit then we shouldn't do
350             # anything to modify it.
351 1608 50       3718 if ($y >= 1900) {
    0          
    0          
352             # do nothing
353              
354             # TODO: for hysterical er historical reasons we assume 9x is in the
355             # 1990s we should probably not do that, but I don't have any examples
356             # where apache provides two digit dates so I am leaving this as-is
357             # for now. Possibly the right thing is to not handle two digit years.
358             } elsif ($y >= 90) {
359 0         0 $y = 1900+$y;
360             }
361              
362             # TODO: likewise assuming 00-89 are 20xx is long term probably wrong.
363             elsif ($y < 100) {
364 0         0 $y = 2000+$y;
365             }
366 1608         4838 $y;
367             }
368              
369              
370             sub _monthabbrev_number {
371 909     909   1545 my $mon = shift;
372             +{'Jan' => 1,
373             'Feb' => 2,
374             'Mar' => 3,
375             'Apr' => 4,
376             'May' => 5,
377             'Jun' => 6,
378             'Jul' => 7,
379             'Aug' => 8,
380             'Sep' => 9,
381             'Oct' => 10,
382             'Nov' => 11,
383             'Dec' => 12,
384 909         6336 }->{$mon};
385             }
386              
387              
388             1;
389              
390             __END__