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   505 use strict;
  1         7  
  1         24  
4 1     1   4 use warnings;
  1         2  
  1         19  
5 1     1   3 use Carp ();
  1         2  
  1         15  
6 1     1   405 use HTTP::Date qw(str2time);
  1         4474  
  1         110  
7 1     1   12 use Exporter 5.57 qw( import );
  1         30  
  1         703  
8              
9             # ABSTRACT: Parse directory listing
10             our $VERSION = '6.15'; # 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 547     547 1 467994 my($dir, $tz, $fstype, $error) = @_;
19              
20 547   100     1159 $fstype ||= 'unix';
21 547         938 $fstype = "File::Listing::" . lc $fstype;
22              
23 547         952 my @args = $_[0];
24 547 100       1212 push(@args, $tz) if(@_ >= 2);
25 547 50       1064 push(@args, $error) if(@_ >= 4);
26              
27 547         1225 $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 7607678 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         26205 local $_ = shift;
44 16465         18092 my $mode = 0;
45 16465         16796 my($type);
46              
47 16465 50       78110 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         41360 s/[Ll](...)$/S$1/;
55              
56 16465         44917 while (/(.)/g) {
57 148185         154111 $mode <<= 1;
58 148185 100 100     523359 $mode |= 1 if $1 ne "-" &&
      100        
      100        
59             $1 ne "*" &&
60             $1 ne 'S' &&
61             $1 ne 'T';
62             }
63              
64 16465 100       31597 $mode |= 0004000 if /^..s....../i;
65 16465 100       30127 $mode |= 0002000 if /^.....s.../i;
66 16465 100       26650 $mode |= 0001000 if /^........t/i;
67              
68             # De facto standard definitions. From 'stat.h' on Solaris 9.
69              
70 16465 0 33     113355 $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         36914 $mode;
81             }
82              
83              
84             sub parse
85             {
86 547     547 0 842 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       2140 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         2436 $dir = [ split(/\n/, $dir) ];
107             }
108              
109 547         1323 $pkg->init();
110              
111 547         846 my @files = ();
112 547 100       991 if (ref($dir) eq 'ARRAY') {
113 545         821 for (@$dir) {
114 1923         4193 push(@files, $pkg->line($_, $tz, $error));
115             }
116             }
117             else {
118 2         7 local($_);
119 2         92 while (my $line = <$dir>) {
120 86         112 chomp $line;
121 86         150 push(@files, $pkg->line($line, $tz, $error));
122             }
123             }
124 547 100       3000 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         662  
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   12 $curdir = '';
141             }
142              
143              
144             sub line
145             {
146 135     135   142 shift; # package name
147 135         219 local($_) = shift;
148 135         173 my($tz, $error) = @_;
149              
150 135         183 s/\015//g;
151             #study;
152              
153 135         147 my ($kind, $size, $date, $name);
154 135 100 66     950 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     324 return if $name eq '.' || $name eq '..';
166 81 100       153 $name = "$curdir/$name" if length $curdir;
167 81         97 my $type = '?';
168 81 50 33     239 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         115 $type = 'f';
174             }
175             elsif ($kind =~ /^[dD]/) {
176 11         15 $type = 'd';
177 11         17 $size = undef; # Don't believe the reported size
178             }
179 81         182 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       19 return () if $dir eq '.';
186 9         16 $curdir = $dir;
187 9         25 return ();
188             }
189             elsif (/^[Tt]otal\s+(\d+)$/ || /^\s*$/) {
190 21         58 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         3  
  1         910  
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   5 shift; # package name
242 2         5 local($_) = shift;
243 2         3 my($tz, $error) = @_;
244              
245 2         4 s/\015//g;
246              
247 2         5 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       18 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     12 return if $name eq '.' || $name eq '..';
260 2 50       5 $name = "$curdir/$name" if length $curdir;
261 2         4 my $type = '?';
262 2 100       4 if ($size_or_dir eq '') {
263 1         3 $type = "d";
264 1         2 $size = ""; # directories have no size in the pc listing
265             }
266             else {
267 1         2 $type = 'f';
268 1         3 $size = $size_or_dir;
269             }
270 2         11 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   2067 shift; # package name
301 1872         2838 local($_) = shift;
302 1872         2529 my($tz, $error) = @_; # ignored for now...
303              
304 1872         9606 s!]*>! !g; # clean away various table stuff
305 1872 100       10715 if (m!.*.*?(\d+)-([a-zA-Z]+|\d+)-(\d+)\s+(\d+):(\d+)\s+(?:([\d\.]+[kMG]?|-))!i) {
    100          
306 1608         4144 my($filename, $filesize) = ($1, $7);
307 1608         4135 my($d,$m,$y, $H,$M) = ($2,$3,$4,$5,$6);
308 1608 100       3205 if ($m =~ /^\d+$/) {
309 699         1415 ($d,$y) = ($y,$d) # iso date
310             }
311             else {
312 909         1390 $m = _monthabbrev_number($m);
313             }
314              
315 1608 100       3263 $filesize = 0 if $filesize eq '-';
316 1608 100       4556 if ($filesize =~ s/k$//i) {
    100          
    50          
317 589         1228 $filesize *= 1024;
318             }
319             elsif ($filesize =~ s/M$//) {
320 10         28 $filesize *= 1024*1024;
321             }
322             elsif ($filesize =~ s/G$//) {
323 0         0 $filesize *= 1024*1024*1024;
324             }
325 1608         2078 $filesize = int $filesize;
326              
327 1608         6373 require Time::Local;
328 1608         3061 my $filetime = Time::Local::timelocal(0,$M,$H,$d,$m-1,_guess_year($y));
329 1608 100       88542 my $filetype = ($filename =~ s|/$|| ? "d" : "f");
330 1608         6870 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         42 my $filename = $1;
338 23 100       80 my $filetype = ($filename =~ s|/$|| ? "d" : "f");
339 23         66 return [$filename, $filetype, undef, undef, undef];
340             }
341              
342 241         359 return ();
343             }
344              
345              
346             sub _guess_year {
347 1608     1608   2261 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       2897 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         3739 $y;
367             }
368              
369              
370             sub _monthabbrev_number {
371 909     909   1073 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         5578 }->{$mon};
385             }
386              
387              
388             1;
389              
390             __END__