File Coverage

lib/CPANPLUS/Module/Checksums.pm
Criterion Covered Total %
statement 89 106 83.9
branch 34 58 58.6
condition 2 3 66.6
subroutine 13 13 100.0
pod 1 1 100.0
total 139 181 76.8


line stmt bran cond sub pod time code
1             package CPANPLUS::Module::Checksums;
2              
3 20     20   163 use strict;
  20         38  
  20         764  
4 20     20   120 use vars qw[@ISA $VERSION];
  20         39  
  20         1305  
5              
6 20     20   140 use CPANPLUS::Error;
  20         42  
  20         1200  
7 20     20   148 use CPANPLUS::Internals::Constants;
  20         60  
  20         7209  
8              
9 20     20   171 use FileHandle;
  20         39  
  20         168  
10              
11 20     20   7072 use Locale::Maketext::Simple Class => 'CPANPLUS', Style => 'gettext';
  20         42  
  20         137  
12 20     20   5420 use Params::Check qw[check];
  20         54  
  20         1094  
13 20     20   151 use Module::Load::Conditional qw[can_load];
  20         42  
  20         26250  
14              
15             $Params::Check::VERBOSE = 1;
16              
17             @ISA = qw[ CPANPLUS::Module::Signature ];
18             $VERSION = "0.9910";
19              
20             =head1 NAME
21              
22             CPANPLUS::Module::Checksums - checking the checksum of a distribution
23              
24             =head1 SYNOPSIS
25              
26             $file = $modobj->checksums;
27             $bool = $mobobj->_validate_checksum;
28              
29             =head1 DESCRIPTION
30              
31             This is a class that provides functions for checking the checksum
32             of a distribution. Should not be loaded directly, but used via the
33             interface provided via C.
34              
35             =head1 METHODS
36              
37             =head2 $mod->checksums
38              
39             Fetches the checksums file for this module object.
40             For the options it can take, see C.
41              
42             Returns the location of the checksums file on success and false
43             on error.
44              
45             The location of the checksums file is also stored as
46              
47             $mod->status->checksums
48              
49             =cut
50              
51             sub checksums {
52 4 50   4 1 770 my $mod = shift or return;
53              
54 4         30 my $file = $mod->_get_checksums_file( @_ );
55              
56 4 50       62 return $mod->status->checksums( $file ) if $file;
57              
58 0         0 return;
59             }
60              
61             ### checks if the package checksum matches the one
62             ### from the checksums file
63             sub _validate_checksum {
64 16     16   102 my $self = shift; #must be isa CPANPLUS::Module
65 16         199 my $conf = $self->parent->configure_object;
66 16         107 my %hash = @_;
67              
68 16         85 my $verbose;
69 16         205 my $tmpl = {
70             verbose => { default => $conf->get_conf('verbose'),
71             store => \$verbose },
72             };
73              
74 16 50       188 check( $tmpl, \%hash ) or return;
75              
76             ### if we can't check it, we must assume it's ok ###
77 16 50       1568 return $self->status->checksum_ok(1)
78             unless can_load( modules => { 'Digest::SHA' => '0.0' } );
79             #class CPANPLUS::Module::Status is runtime-generated
80              
81 16 50       81049 my $file = $self->_get_checksums_file( verbose => $verbose ) or (
82             error(loc(q[Could not fetch '%1' file], CHECKSUMS)), return );
83              
84 16 50       1191 $self->_check_signature_for_checksum_file( file => $file ) or (
85             error(loc(q[Could not verify '%1' file], CHECKSUMS)), return );
86             #for whole CHECKSUMS file
87              
88 16 50       197 my $href = $self->_parse_checksums_file( file => $file ) or (
89             error(loc(q[Could not parse '%1' file], CHECKSUMS)), return );
90              
91 16         124 my $size = $href->{ $self->package }->{'size'};
92              
93             ### the checksums file tells us the size of the archive
94             ### but the downloaded file is of different size
95 16 100       85 if( defined $size ) {
96 15 50       89 if( not (-s $self->status->fetch == $size) ) {
97 0         0 error(loc( "Archive size does not match for '%1': " .
98             "size is '%2' but should be '%3'",
99             $self->package, -s $self->status->fetch, $size));
100 0         0 return $self->status->checksum_ok(0);
101             }
102             } else {
103 1         5 msg(loc("Archive size is not known for '%1'",$self->package),$verbose);
104             }
105              
106 16         1865 my $sha = $href->{ $self->package }->{'sha256'};
107              
108 16 50       80 unless( defined $sha ) {
109 16         66 msg(loc("No 'sha256' checksum known for '%1'",$self->package),$verbose);
110              
111 16         213 return $self->status->checksum_ok(1);
112             }
113              
114 0         0 $self->status->checksum_value($sha);
115              
116              
117 0 0       0 my $fh = FileHandle->new( $self->status->fetch ) or return;
118 0         0 binmode $fh;
119              
120 0         0 my $ctx = Digest::SHA->new(256);
121 0         0 $ctx->addfile( $fh );
122              
123 0         0 my $hexdigest = $ctx->hexdigest;
124 0         0 my $flag = $hexdigest eq $sha;
125 0 0       0 $flag
126             ? msg(loc("Checksum matches for '%1'", $self->package),$verbose)
127             : error(loc("Checksum does not match for '%1': " .
128             "SHA256 is '%2' but should be '%3'",
129             $self->package, $hexdigest, $sha),$verbose);
130              
131              
132 0 0       0 return $self->status->checksum_ok(1) if $flag;
133 0         0 return $self->status->checksum_ok(0);
134             }
135              
136              
137             ### fetches the module objects checksum file ###
138             sub _get_checksums_file {
139 24     24   125 my $self = shift;
140 24         189 my %hash = @_;
141              
142 24         299 my $clone = $self->clone;
143 24         267 $clone->package( CHECKSUMS );
144              
145             # If the user specified a fetchdir, then every CHECKSUMS file will always
146             # be stored there, not in an author-specific subdir. Thus, in this case,
147             # we need to always re-fetch the CHECKSUMS file and hence need to set the
148             # TTL to something small.
149 24         122 my $have_fetchdir =
150             $self->parent->configure_object->get_conf('fetchdir') ne '';
151 24 50       162 my $ttl = $have_fetchdir ? 0.001 : 3600;
152 24 50       348 my $file = $clone->fetch( ttl => $ttl, %hash ) or return;
153              
154 24         807 return $file;
155             }
156              
157             sub _parse_checksums_file {
158 18     18   105 my $self = shift;
159 18         118 my %hash = @_;
160              
161 18         64 my $file;
162 18         246 my $tmpl = {
163             file => { required => 1, allow => FILE_READABLE, store => \$file },
164             };
165 18         171 my $args = check( $tmpl, \%hash );
166              
167 18 50       761 my $fh = OPEN_FILE->( $file ) or return;
168              
169             ### loop over the header, there might be a pgp signature ###
170 18         80 my $signed;
171 18         655 while (local $_ = <$fh>) {
172 108 100       609 last if /^\$cksum = \{\s*$/; # skip till this line
173 90         293 my $header = PGP_HEADER; # but be tolerant of whitespace
174 90 100       1003 $signed = 1 if /^${header}\s*$/;# due to crossplatform linebreaks
175             }
176              
177             ### read the filehandle, parse it rather than eval it, even though it
178             ### *should* be valid perl code
179 18         73 my $dist;
180 18         64 my $cksum = {};
181 18         132 while (local $_ = <$fh>) {
182              
183 381 100 66     3264 if (/^\s*'([^']+)' => \{\s*$/) {
    100          
    100          
    100          
184 69         355 $dist = $1;
185              
186             } elsif (/^\s*'([^']+)' => '?([^'\n]+)'?,?\s*$/ and defined $dist) {
187 173         1416 $cksum->{$dist}{$1} = $2;
188              
189             } elsif (/^\s*}[,;]?\s*$/) {
190 87         295 undef $dist;
191              
192             } elsif (/^__END__\s*$/) {
193 18         80 last;
194              
195             } else {
196 34         219 error( loc("Malformed %1 line: %2", CHECKSUMS, $_) );
197             }
198             }
199              
200 18         510 return $cksum;
201             }
202              
203             sub _check_signature_for_checksum_file {
204 16     16   892 my $self = shift;
205              
206 16         195 my $conf = $self->parent->configure_object;
207 16         117 my %hash = @_;
208              
209             ### you don't want to check signatures,
210             ### so let's just return true;
211 16 100       394 return 1 unless $conf->get_conf('signature');
212              
213 1         18 my($force,$file,$verbose);
214 1         39 my $tmpl = {
215             file => { required => 1, allow => FILE_READABLE, store => \$file },
216             force => { default => $conf->get_conf('force'), store => \$force },
217             verbose => { default => $conf->get_conf('verbose'), store => \$verbose },
218             };
219              
220 1 50       36 my $args = check( $tmpl, \%hash ) or return;
221              
222 1 50       75 my $fh = OPEN_FILE->($file) or return;
223              
224 1         12 my $signed;
225 1         60 while (local $_ = <$fh>) {
226 35         62 my $header = PGP_HEADER;
227 35 100       237 $signed = 1 if /^$header$/;
228             }
229              
230 1 50       23 if ( !$signed ) {
231 0         0 msg(loc("No signature found in %1 file '%2'",
232             CHECKSUMS, $file), $verbose);
233              
234 0 0       0 return 1 unless $force;
235              
236 0         0 error( loc( "%1 file '%2' is not signed -- aborting",
237             CHECKSUMS, $file ) );
238 0         0 return;
239              
240             }
241              
242 1 50       31 if( can_load( modules => { 'Module::Signature' => '0.06' } ) ) {
243             # local $Module::Signature::SIGNATURE = $file;
244             # ... check signatures ...
245             }
246              
247 1         956 return 1;
248             }
249              
250              
251              
252             # Local variables:
253             # c-indentation-style: bsd
254             # c-basic-offset: 4
255             # indent-tabs-mode: nil
256             # End:
257             # vim: expandtab shiftwidth=4:
258              
259             1;