File Coverage

blib/lib/Digest/MD5/File.pm
Criterion Covered Total %
statement 81 137 59.1
branch 40 122 32.7
condition 2 4 50.0
subroutine 11 19 57.8
pod 0 12 0.0
total 134 294 45.5


line stmt bran cond sub pod time code
1             package Digest::MD5::File;
2              
3 1     1   45105 use strict;
  1         2  
  1         122  
4 1     1   6 use warnings;
  1         2  
  1         33  
5 1     1   5 use Carp;
  1         5  
  1         92  
6 1     1   5 use Digest::MD5;
  1         2  
  1         53  
7             eval { require Encode; };
8 1     1   1320 use LWP::UserAgent;
  1         83582  
  1         2522  
9              
10             require Exporter;
11             our @ISA = qw(Exporter Digest::MD5);
12             our @EXPORT_OK = qw(dir_md5 dir_md5_hex dir_md5_base64
13             file_md5 file_md5_hex file_md5_base64
14             url_md5 url_md5_hex url_md5_base64);
15              
16             our $BINMODE = 1;
17             our $UTF8 = 0;
18             our $NOFATALS = 0;
19              
20             sub import {
21 1     1   21 my $me = shift;
22 1         2 my %imp;
23              
24 1         3 @imp{ @_ } = ();
25 1         4 for(@EXPORT_OK) {
26 9 50       20 delete $imp{$_} if exists($imp{$_});
27             }
28              
29 1 50       4 $BINMODE = 0 if exists $imp{-nobin};
30 1 50       4 $UTF8 = 1 if exists $imp{-utf8};
31 1 50       4 $NOFATALS = 1 if exists $imp{-nofatals};
32              
33 1         4 for(keys %imp) {
34 0         0 s/^-//;
35 0 0       0 $imp{$_}='' unless $_ =~ m/^(no)?(bin|utf8|fatals)$/;
36 0 0       0 push @EXPORT_OK, $_ unless $_ =~ m/^(no)?(bin|utf8|fatals)$/;
37 0 0       0 delete $imp{"-$_"} if exists $imp{"-$_"};
38             }
39              
40 1         124 $me->export_to_level(1, $me, grep(!/^-/, @_));
41 1         35 Digest::MD5->import(keys %imp);
42             }
43              
44             our $VERSION = '0.08';
45              
46             my $getfh = sub {
47             my $file = shift;
48              
49             croak "$file: Does not exist" if !-e $file && !$NOFATALS;
50             croak "$file: Is a directory" if -d $file && !$NOFATALS;
51              
52             if(-e $file && !-d $file) {
53             open my $fh, $file or return;
54             binmode $fh if $BINMODE;
55             return $fh;
56             }
57             else { return undef; }
58             };
59              
60             my $getur = sub {
61             my $res = LWP::UserAgent->new->get(shift());
62             return $res->is_success ? $res->content : undef;
63             };
64              
65             sub Digest::MD5::adddir {
66 2     2 0 6 my $md5 = shift;
67 2         4 my $base = shift;
68 2         4 for my $key ( sort keys %{ _dir($base, undef, undef, 3) }) {
  2         7  
69 8 50       15 next if !$key;
70 8         296 my $file = File::Spec->catfile($base, $key);
71 8 100 50     197 $md5->addpath($file) or carp "addpath $file failed: $!" if !-d $file;
72             }
73 2         11 return 1;
74             }
75              
76             sub _dir {
77 6     6   15 my($dir, $hr, $base, $type, $cc) = @_;
78 6         39 require File::Spec; # only load it if its needed
79              
80 6 100       21 $cc = {} if ref $cc ne 'HASH';
81 6 100       15 $hr = {} if ref $hr ne 'HASH';
82 6 100       17 $base = $dir if !defined $base;
83 6 50       14 $type = 0 if ! defined $type;
84              
85 6         12 my $_md5func = \&file_md5;
86 6 100       37 $_md5func = \&file_md5_hex if $type eq '1';
87 6 50       15 $_md5func = \&file_md5_base64 if $type eq '2';
88              
89 6 50       183 opendir(DIR, $dir) or return;
90 6 100       89 my @dircont = sort grep { $_ ne '.' && $_ ne '..' } readdir(DIR);
  24         114  
91 6         82 closedir DIR;
92              
93 6         14 for my $file( @dircont ) {
94 12         90 my $_dirver = File::Spec->catdir($dir, $file);
95 12 100       244 my $full = -d $_dirver ? $_dirver
96             : File::Spec->catfile($dir, $file);
97              
98 12         849 my $short = File::Spec->abs2rel( $full, $base );
99            
100 12 50       167 if(-l $full) {
101 0         0 my $target = readlink $full;
102 0 0       0 $full = $target if -d $target;
103             }
104              
105 12 50       34 if(exists $hr->{$full}) {
106 0         0 carp "$full seen already, you may have circular links";
107 0         0 $cc->{$full}++;
108 0 0       0 croak "$full is in a circular link, bailing out."
109             if $cc->{$full} > 4;
110             }
111              
112 12 100       136 if(-d $full) {
113 3         8 $hr->{ $short } = '';
114 3 50       11 _dir($full, $hr, $base, $type, $cc) or return;
115             }
116             else {
117 9         23 $hr->{ $short } = '';
118 9 100 50     31 $hr->{ $short } = $_md5func->( $full ) or return if $type ne '3';
119             }
120             }
121 6         33 return $hr;
122             }
123              
124             sub dir_md5 {
125 0 0   0 0 0 push @_, undef if @_ < 3;
126 0 0       0 push @_, undef if @_ < 3;
127 0         0 _dir(@_, 0);
128             }
129              
130             sub dir_md5_hex {
131 1 50   1 0 8 push @_, undef if @_ < 3;
132 1 50       5 push @_, undef if @_ < 3;
133 1         4 _dir(@_, 1);
134             }
135              
136             sub dir_md5_base64 {
137 0 0   0 0 0 push @_, undef if @_ < 3;
138 0 0       0 push @_, undef if @_ < 3;
139 0         0 _dir(@_, 2);
140             }
141              
142             sub file_md5 {
143 0     0 0 0 my ($file,$bn,$ut) = @_;
144 0 0       0 local $BINMODE = $bn if defined $bn;
145 0 0       0 local $UTF8 = $ut if defined $ut;
146 0 0       0 my $fh = $getfh->($file) or return;
147            
148 0         0 my $md5 = Digest::MD5->new();
149 0         0 my $buf;
150 0         0 while(my $l = read($fh, $buf, 1024)) {
151 0 0       0 $md5->add( $UTF8 ? Encode::encode_utf8($buf) : $buf );
152             }
153 0         0 return $md5->digest;
154             }
155              
156             sub file_md5_hex {
157 4     4 0 1855 my ($file,$bn,$ut) = @_;
158 4 50       14 local $BINMODE = $bn if defined $bn;
159 4 50       8 local $UTF8 = $ut if defined $ut;
160 4 50       11 my $fh = $getfh->($file) or return;
161            
162 4         32 my $md5 = Digest::MD5->new();
163 4         6 my $buf;
164 4         75 while(my $l = read($fh, $buf, 1024)) {
165 4 50       33 $md5->add( $UTF8 ? Encode::encode_utf8($buf) : $buf );
166             }
167 4         92 return $md5->hexdigest;
168             }
169              
170             sub file_md5_base64 {
171 0     0 0 0 my ($file,$bn,$ut) = @_;
172 0 0       0 local $BINMODE = $bn if defined $bn;
173 0 0       0 local $UTF8 = $ut if defined $ut;
174 0 0       0 my $fh = $getfh->($file) or return;
175              
176 0         0 my $md5 = Digest::MD5->new();
177 0         0 my $buf;
178 0         0 while(my $l = read($fh, $buf, 1024)) {
179 0 0       0 $md5->add( $UTF8 ? Encode::encode_utf8($buf) : $buf );
180             }
181 0         0 return $md5->b64digest;
182             }
183              
184             sub url_md5 {
185 0 0   0 0 0 my $cn = $getur->(shift()) or return;
186 0         0 my ($ut) = shift;
187 0 0       0 local $UTF8 = $ut if defined $ut;
188 0 0       0 return Digest::MD5::md5($cn) if !$UTF8;
189 0         0 return Digest::MD5::md5(Encode::encode_utf8($cn));
190             }
191              
192             sub url_md5_hex {
193 0 0   0 0 0 my $cn = $getur->(shift()) or return;
194 0         0 my ($ut) = shift;
195 0 0       0 local $UTF8 = $ut if defined $ut;
196 0 0       0 return Digest::MD5::md5_hex($cn) if !$UTF8;
197 0         0 return Digest::MD5::md5_hex(Encode::encode_utf8($cn));
198             }
199              
200             sub url_md5_base64 {
201 0 0   0 0 0 my $cn = $getur->(shift()) or return;
202 0         0 my ($ut) = shift;
203 0 0       0 local $UTF8 = $ut if defined $ut;
204 0 0       0 return Digest::MD5::md5_base64($cn) if !$UTF8;
205 0         0 return Digest::MD5::md5_base64(Encode::encode_utf8($cn));
206             }
207              
208             sub Digest::MD5::addpath {
209 6     6 0 8 my $md5 = shift;
210 6         14 my ($fl,$bn,$ut) = @_;
211 6 50       14 local $BINMODE = $bn if defined $bn;
212 6 50       11 local $UTF8 = $ut if defined $ut;
213 6 50       13 if(ref $fl eq 'ARRAY') {
214 0         0 for my $pth (@{ $fl }) {
  0         0  
215 0 0       0 $md5->addpath($pth, $bn, $ut) or return;
216             }
217             }
218             else {
219 6 50       12 my $fh = $getfh->($fl) or return;
220 6         7 my $buf;
221 6         82 while(my $l = read($fh, $buf, 1024)) {
222 6 50       102 !$UTF8 ? $md5->add($buf) : $md5->add(Encode::encode_utf8($buf));
223             }
224             }
225 6         22 return 1;
226             }
227              
228             sub Digest::MD5::addurl {
229 0     0 0   my $md5 = shift;
230 0 0         my $cn = $getur->(shift()) or return;
231 0           my $ut = shift;
232 0 0         local $UTF8 = $ut if defined $ut;
233 0 0         !$UTF8 ? $md5->add($cn) : $md5->add(Encode::encode_utf8($cn));
234             }
235              
236             1;
237              
238             __END__
239              
240             =head1 NAME
241              
242             Digest::MD5::File - Perl extension for getting MD5 sums for files and urls.
243              
244             =head1 SYNOPSIS
245              
246             use Digest::MD5::File qw(dir_md5_hex file_md5_hex url_md5_hex);
247              
248             my $md5 = Digest::MD5->new;
249             $md5->addpath('/path/to/file');
250             my $digest = $md5->hexdigest;
251              
252             my $digest = file_md5($file);
253             my $digest = file_md5_hex($file);
254             my $digest = file_md5_base64($file);
255              
256             my $md5 = Digest::MD5->new;
257             $md5->addurl('http://www.tmbg.com/tour.html');
258             my $digest = $md5->hexdigest;
259              
260             my $digest = url_md5($url);
261             my $digest = url_md5_hex($url);
262             my $digest = url_md5_base64($url);
263            
264             my $md5 = Digest::MD5->new;
265             $md5->adddir('/directory');
266             my $digest = $md5->hexdigest;
267              
268             my $dir_hashref = dir_md5($dir);
269             my $dir_hashref = dir_md5_hex($dir);
270             my $dir_hashref = dir_md5_base64($dir);
271              
272             =head1 DESCRIPTION
273              
274             Get MD5 sums for files of a given path or content of a given url.
275              
276             =head1 EXPORT
277              
278             None by default.
279             You can export any file_* dir_*, or url_* function and anything L<Digest::MD5> can export.
280              
281             use Digest::MD5::File qw(md5 md5_hex md5_base64); # 3 Digest::MD5 functions
282             print md5_hex('abc123'), "\n";
283             print md5_base64('abc123'), "\n";
284              
285             =head1 OBJECT METHODS
286              
287             =head2 addpath()
288              
289             my $md5 = Digest::MD5->new;
290             $md5->addpath('/path/to/file.txt')
291             or die "file.txt is not where you said: $!";
292              
293             or you can add multiple files by specifying an array ref of files:
294              
295             $md5->addpath(\@files);
296              
297             =head2 adddir()
298            
299             addpath()s each file in a directory recursively. Follows the same rules as the dir_* functions.
300              
301             my $md5 = Digest::MD5->new;
302             $md5->adddir('/home/tmbg/')
303             or die "See warning above to see why I bailed: $!";
304              
305             =head2 addurl()
306              
307             my $md5 = Digest::MD5->new;
308             $md5->addurl('http://www.tmbg.com/tour.html')
309             or die "They Must Be not on tour";
310              
311             =head1 file_* functions
312              
313             Get the digest in variouse formats of $file.
314             If file does not exist or is a directory it croaks (See NOFATALS for more info)
315              
316             my $digest = file_md5($file) or warn "$file failed: $!";
317             my $digest = file_md5_hex($file) or warn "$file failed: $!";
318             my $digest = file_md5_base64($file) or warn "$file failed: $!";
319              
320             =head1 dir_* functions
321              
322             Returns a hashref whose keys are files relative to the given path and the values are the MD5 sum of the file or and empty string if a directory.
323             It recurses through the entire depth of the directory.
324             Symlinks to files are just addpath()d and symlinks to directories are followed.
325              
326             my $dir_hashref = dir_md5($dir) or warn "$dir failed: $!";
327             my $dir_hashref = dir_md5_hex($dir) or warn "$dir failed: $!";
328             my $dir_hashref = dir_md5_base64($dir) or warn "$dir failed: $!";
329              
330             =head1 url_* functions
331              
332             Get the digest in various formats of the content at $url (Including, if $url points to directory, the directory listing content).
333             Returns undef if url fails (IE if L<LWP::UserAgent>'s $res->is_success is false)
334              
335             my $digest = url_md5($url) or warn "$url failed";
336             my $digest = url_md5_hex($url) or warn "$url failed";
337             my $digest = url_md5_base64($url) or warn "$url failed";
338              
339             =head1 SPECIAL SETTINGS
340              
341             =head2 BINMODE
342              
343             By default files are opened in binmode. If you do not want to do this you can unset it a variety of ways:
344              
345             use Digest::MD5::File qw(-nobin);
346              
347             or
348              
349             $Digest::MD5::File::BINMODE = 0;
350              
351             or at the function/method level by specifying its value as the second argument:
352              
353             $md5->addpath($file,0);
354              
355             my $digest = file_md5_hex($file,0);
356              
357             =head2 UTF8
358              
359             In some cases you may want to have your data utf8 encoded, you can do this the following ways:
360              
361             use Digest::MD5::File qw(-utf8);
362              
363             or
364              
365             $Digest::MD5::File::UTF8 = 1;
366              
367             or at the function/method level by specifying its value as the third argument for files and second for urls:
368              
369             $md5->addpath($file,$binmode,1);
370              
371             my $digest = file_md5_hex($file,$binmode,1);
372              
373             $md5->addurl($url,1);
374              
375             url_md5_hex($url,1);
376              
377             It use's L<Encode>'s encode_utf8() function to do the encoding. So if you do not have Encode (pre 5.7.3) this won't work :)
378              
379             =head2 NOFATALS
380              
381             Instead of croaking it will return undef if you set NOFATALS to true.
382              
383             You can do this two ways:
384              
385             $Digest::MD5::File::NOFATALS = 1;
386              
387             or the -nofatals flag:
388              
389             use Digest::MD5::File qw(-nofatals);
390              
391             my $digest = file_md5_hex($file) or die "$file failed";
392              
393             $! is not set so its not really helpful if you die().
394              
395             =head1 SEE ALSO
396              
397             L<Digest::MD5>, L<Encode>, L<LWP::UserAgent>
398              
399             =head1 AUTHOR
400              
401             Daniel Muey, L<http://drmuey.com/cpan_contact.pl>
402              
403             =head1 COPYRIGHT AND LICENSE
404              
405             Copyright 2005 by Daniel Muey
406              
407             This library is free software; you can redistribute it and/or modify
408             it under the same terms as Perl itself.
409              
410             =cut