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 1 6 16.6
total 230 347 66.2


line stmt bran cond sub pod time code
1             package File::Listing;
2              
3 1     1   509 use strict;
  1         7  
  1         28  
4 1     1   5 use warnings;
  1         2  
  1         22  
5 1     1   5 use Carp ();
  1         1  
  1         17  
6 1     1   495 use HTTP::Date qw(str2time);
  1         4903  
  1         96  
7 1     1   7 use Exporter 5.57 qw( import );
  1         34  
  1         753  
8              
9             # ABSTRACT: Parse directory listing
10             our $VERSION = '6.16'; # VERSION
11              
12 0     0 0 0 sub Version { $File::Listing::VERSION; }
13              
14             our @EXPORT = qw(parse_dir);
15              
16             sub parse_dir ($;$$$)
17             {
18 548     548 1 541495 my($dir, $tz, $fstype, $error) = @_;
19              
20 548   100     1429 $fstype ||= 'unix';
21 548         1037 $fstype = "File::Listing::" . lc $fstype;
22              
23 548         1113 my @args = $_[0];
24 548 100       1301 push(@args, $tz) if(@_ >= 2);
25 548 50       1133 push(@args, $error) if(@_ >= 4);
26              
27 548         1445 $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 9252165 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         34377 local $_ = shift;
44 16465         22162 my $mode = 0;
45 16465         20333 my($type);
46              
47 16465 50       99615 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         48668 s/[Ll](...)$/S$1/;
55              
56 16465         55026 while (/(.)/g) {
57 148185         188479 $mode <<= 1;
58 148185 100 100     639572 $mode |= 1 if $1 ne "-" &&
      100        
      100        
59             $1 ne "*" &&
60             $1 ne 'S' &&
61             $1 ne 'T';
62             }
63              
64 16465 100       40840 $mode |= 0004000 if /^..s....../i;
65 16465 100       36511 $mode |= 0002000 if /^.....s.../i;
66 16465 100       34861 $mode |= 0001000 if /^........t/i;
67              
68             # De facto standard definitions. From 'stat.h' on Solaris 9.
69              
70 16465 0 33     134179 $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         43823 $mode;
81             }
82              
83              
84             sub parse
85             {
86 548     548 0 1037 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 548 50       2544 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 546         2842 $dir = [ split(/\n/, $dir) ];
107             }
108              
109 548         1606 $pkg->init();
110              
111 548         788 my @files = ();
112 548 100       1451 if (ref($dir) eq 'ARRAY') {
113 546         1075 for (@$dir) {
114 1925         5224 push(@files, $pkg->line($_, $tz, $error));
115             }
116             }
117             else {
118 2         6 local($_);
119 2         89 while (my $line = <$dir>) {
120 86         155 chomp $line;
121 86         179 push(@files, $pkg->line($line, $tz, $error));
122             }
123             }
124 548 100       3356 wantarray ? @files : \@files; ## no critic (Community::Wantarray)
125             }
126              
127              
128              
129             package File::Listing::unix;
130              
131 1     1   8 use HTTP::Date qw(str2time);
  1         2  
  1         687  
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   174 shift; # package name
147 135         244 local($_) = shift;
148 135         205 my($tz, $error) = @_;
149              
150 135         218 s/\015//g;
151             #study;
152              
153 135         171 my ($kind, $size, $date, $name);
154 135 100 66     1117 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     396 return if $name eq '.' || $name eq '..';
166 81 100       333 $name = "$curdir/$name" if length $curdir;
167 81         118 my $type = '?';
168 81 50 33     279 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         106 $type = 'f';
174             }
175             elsif ($kind =~ /^[dD]/) {
176 11         19 $type = 'd';
177 11         13 $size = undef; # Don't believe the reported size
178             }
179 81         201 return [$name, $type, $size, str2time($date, $tz),
180             File::Listing::file_mode($kind)];
181              
182             }
183             elsif (/^(.+):$/ && !/^[dcbsp].*\s.*\s.*:$/ ) {
184 9         19 my $dir = $1;
185 9 50       20 return () if $dir eq '.';
186 9         12 $curdir = $dir;
187 9         24 return ();
188             }
189             elsif (/^[Tt]otal\s+(\d+)$/ || /^\s*$/) {
190 21         60 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   7 use HTTP::Date qw(str2time);
  1         2  
  1         1107  
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 2     2   6 $curdir = '';
236             }
237              
238              
239             sub line
240             {
241 4     4   10 shift; # package name
242 4         9 local($_) = shift;
243 4         9 my($tz, $error) = @_;
244              
245 4         10 s/\015//g;
246              
247 4         6 my ($date, $size_or_dir, $name, $size);
248              
249             # usual format:
250             # 02-05-96 10:48AM 1415 src.slf
251             # 09-10-96 09:18AM sl_util
252             # alternative dos format with four-digit year:
253             # 02-05-2022 10:48AM 1415 src.slf
254             # 09-10-2022 09:18AM sl_util
255 4 50       38 if (($date, $size_or_dir, $name) =
256             /^(\d\d-\d\d-\d{2,4}\s+\d\d:\d\d\wM) # Date and time info
257             \s+ # Some space
258             (<\w{3}>|\d+) # Dir or Size
259             \s+ # Some more space
260             (.+)$ # File name
261             /x )
262             {
263 4 50 33     22 return if $name eq '.' || $name eq '..';
264 4 50       10 $name = "$curdir/$name" if length $curdir;
265 4         8 my $type = '?';
266 4 100       8 if ($size_or_dir eq '') {
267 2         4 $type = "d";
268 2         4 $size = ""; # directories have no size in the pc listing
269             }
270             else {
271 2         4 $type = 'f';
272 2         3 $size = $size_or_dir;
273             }
274 4         15 return [$name, $type, $size, str2time($date, $tz), undef];
275             }
276             else {
277 0 0       0 return () unless defined $error;
278 0 0       0 &$error($_) if ref($error) eq 'CODE';
279 0 0       0 warn "Can't parse: $_\n" if $error eq 'warn';
280 0         0 return ();
281             }
282              
283             }
284              
285              
286              
287             package File::Listing::vms;
288             our @ISA = qw(File::Listing);
289              
290             package File::Listing::netware;
291             our @ISA = qw(File::Listing);
292              
293              
294              
295             package File::Listing::apache;
296              
297             our @ISA = qw(File::Listing);
298              
299              
300       541     sub init { }
301              
302              
303             sub line {
304 1872     1872   2547 shift; # package name
305 1872         3617 local($_) = shift;
306 1872         2957 my($tz, $error) = @_; # ignored for now...
307              
308 1872         11871 s!]*>! !g; # clean away various table stuff
309 1872 100       13412 if (m!.*.*?(\d+)-([a-zA-Z]+|\d+)-(\d+)\s+(\d+):(\d+)\s+(?:([\d\.]+[kMG]?|-))!i) {
    100          
310 1608         5076 my($filename, $filesize) = ($1, $7);
311 1608         4534 my($d,$m,$y, $H,$M) = ($2,$3,$4,$5,$6);
312 1608 100       3885 if ($m =~ /^\d+$/) {
313 699         1587 ($d,$y) = ($y,$d) # iso date
314             }
315             else {
316 909         1724 $m = _monthabbrev_number($m);
317             }
318              
319 1608 100       3799 $filesize = 0 if $filesize eq '-';
320 1608 100       5380 if ($filesize =~ s/k$//i) {
    100          
    50          
321 589         1455 $filesize *= 1024;
322             }
323             elsif ($filesize =~ s/M$//) {
324 10         30 $filesize *= 1024*1024;
325             }
326             elsif ($filesize =~ s/G$//) {
327 0         0 $filesize *= 1024*1024*1024;
328             }
329 1608         2698 $filesize = int $filesize;
330              
331 1608         7621 require Time::Local;
332 1608         3624 my $filetime = Time::Local::timelocal(0,$M,$H,$d,$m-1,_guess_year($y));
333 1608 100       116250 my $filetype = ($filename =~ s|/$|| ? "d" : "f");
334 1608         7942 return [$filename, $filetype, $filesize, $filetime, undef];
335             }
336              
337             # the default listing doesn't include timestamps or file sizes
338             # but we don't want to grab navigation links, so we ignore links
339             # that have a non-trailing slash / character or ?
340             elsif(m!.*!i) {
341 23         51 my $filename = $1;
342 23 100       48 my $filetype = ($filename =~ s|/$|| ? "d" : "f");
343 23         69 return [$filename, $filetype, undef, undef, undef];
344             }
345              
346 241         418 return ();
347             }
348              
349              
350             sub _guess_year {
351 1608     1608   2288 my $y = shift;
352              
353             # if the year is already four digit then we shouldn't do
354             # anything to modify it.
355 1608 50       3467 if ($y >= 1900) {
    0          
    0          
356             # do nothing
357              
358             # TODO: for hysterical er historical reasons we assume 9x is in the
359             # 1990s we should probably not do that, but I don't have any examples
360             # where apache provides two digit dates so I am leaving this as-is
361             # for now. Possibly the right thing is to not handle two digit years.
362             } elsif ($y >= 90) {
363 0         0 $y = 1900+$y;
364             }
365              
366             # TODO: likewise assuming 00-89 are 20xx is long term probably wrong.
367             elsif ($y < 100) {
368 0         0 $y = 2000+$y;
369             }
370 1608         4644 $y;
371             }
372              
373              
374             sub _monthabbrev_number {
375 909     909   1331 my $mon = shift;
376             +{'Jan' => 1,
377             'Feb' => 2,
378             'Mar' => 3,
379             'Apr' => 4,
380             'May' => 5,
381             'Jun' => 6,
382             'Jul' => 7,
383             'Aug' => 8,
384             'Sep' => 9,
385             'Oct' => 10,
386             'Nov' => 11,
387             'Dec' => 12,
388 909         6259 }->{$mon};
389             }
390              
391              
392             1;
393              
394             __END__