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   155 use strict;
  20         47  
  20         645  
4 20     20   111 use vars qw[@ISA $VERSION];
  20         45  
  20         1086  
5              
6 20     20   189 use CPANPLUS::Error;
  20         61  
  20         1252  
7 20     20   150 use CPANPLUS::Internals::Constants;
  20         42  
  20         7484  
8              
9 20     20   149 use FileHandle;
  20         44  
  20         160  
10              
11 20     20   6350 use Locale::Maketext::Simple Class => 'CPANPLUS', Style => 'gettext';
  20         48  
  20         176  
12 20     20   5095 use Params::Check qw[check];
  20         54  
  20         1118  
13 20     20   135 use Module::Load::Conditional qw[can_load];
  20         107  
  20         26106  
14              
15             $Params::Check::VERBOSE = 1;
16              
17             @ISA = qw[ CPANPLUS::Module::Signature ];
18             $VERSION = "0.9912";
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<CPANPLUS::Module>.
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<CPANPLUS::Module::fetch()>.
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 555 my $mod = shift or return;
53              
54 4         28 my $file = $mod->_get_checksums_file( @_ );
55              
56 4 50       54 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   81 my $self = shift; #must be isa CPANPLUS::Module
65 16         179 my $conf = $self->parent->configure_object;
66 16         143 my %hash = @_;
67              
68 16         64 my $verbose;
69 16         179 my $tmpl = {
70             verbose => { default => $conf->get_conf('verbose'),
71             store => \$verbose },
72             };
73              
74 16 50       141 check( $tmpl, \%hash ) or return;
75              
76             ### if we can't check it, we must assume it's ok ###
77 16 50       1249 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       77754 my $file = $self->_get_checksums_file( verbose => $verbose ) or (
82             error(loc(q[Could not fetch '%1' file], CHECKSUMS)), return );
83              
84 16 50       1095 $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       376 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       90 if( defined $size ) {
96 15 50       96 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         9 msg(loc("Archive size is not known for '%1'",$self->package),$verbose);
104             }
105              
106 16         1875 my $sha = $href->{ $self->package }->{'sha256'};
107              
108 16 50       80 unless( defined $sha ) {
109 16         93 msg(loc("No 'sha256' checksum known for '%1'",$self->package),$verbose);
110              
111 16         246 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   101 my $self = shift;
140 24         161 my %hash = @_;
141              
142 24         316 my $clone = $self->clone;
143 24         248 $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         134 my $have_fetchdir =
150             $self->parent->configure_object->get_conf('fetchdir') ne '';
151 24 50       203 my $ttl = $have_fetchdir ? 0.001 : 3600;
152 24 50       403 my $file = $clone->fetch( ttl => $ttl, %hash ) or return;
153              
154 24         424 return $file;
155             }
156              
157             sub _parse_checksums_file {
158 18     18   62 my $self = shift;
159 18         96 my %hash = @_;
160              
161 18         65 my $file;
162 18         207 my $tmpl = {
163             file => { required => 1, allow => FILE_READABLE, store => \$file },
164             };
165 18         122 my $args = check( $tmpl, \%hash );
166              
167 18 50       686 my $fh = OPEN_FILE->( $file ) or return;
168              
169             ### loop over the header, there might be a pgp signature ###
170 18         62 my $signed;
171 18         601 while (local $_ = <$fh>) {
172 108 100       555 last if /^\$cksum = \{\s*$/; # skip till this line
173 90         236 my $header = PGP_HEADER; # but be tolerant of whitespace
174 90 100       875 $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         71 my $dist;
180 18         75 my $cksum = {};
181 18         125 while (local $_ = <$fh>) {
182              
183 381 100 66     3070 if (/^\s*'([^']+)' => \{\s*$/) {
    100          
    100          
    100          
184 69         346 $dist = $1;
185              
186             } elsif (/^\s*'([^']+)' => '?([^'\n]+)'?,?\s*$/ and defined $dist) {
187 173         1290 $cksum->{$dist}{$1} = $2;
188              
189             } elsif (/^\s*}[,;]?\s*$/) {
190 87         312 undef $dist;
191              
192             } elsif (/^__END__\s*$/) {
193 18         73 last;
194              
195             } else {
196 34         219 error( loc("Malformed %1 line: %2", CHECKSUMS, $_) );
197             }
198             }
199              
200 18         527 return $cksum;
201             }
202              
203             sub _check_signature_for_checksum_file {
204 16     16   651 my $self = shift;
205              
206 16         138 my $conf = $self->parent->configure_object;
207 16         147 my %hash = @_;
208              
209             ### you don't want to check signatures,
210             ### so let's just return true;
211 16 100       179 return 1 unless $conf->get_conf('signature');
212              
213 1         15 my($force,$file,$verbose);
214 1         21 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       12 my $args = check( $tmpl, \%hash ) or return;
221              
222 1 50       54 my $fh = OPEN_FILE->($file) or return;
223              
224 1         10 my $signed;
225 1         36 while (local $_ = <$fh>) {
226 35         63 my $header = PGP_HEADER;
227 35 100       197 $signed = 1 if /^$header$/;
228             }
229              
230 1 50       11 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       14 if( can_load( modules => { 'Module::Signature' => '0.06' } ) ) {
243             # local $Module::Signature::SIGNATURE = $file;
244             # ... check signatures ...
245             }
246              
247 1         708 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;