OLD | NEW |
| (Empty) |
1 # Copyright © 2008 Frank Lichtenheld <djpig@debian.org> | |
2 # Copyright © 2010 Raphaël Hertzog <hertzog@debian.org> | |
3 # | |
4 # This program is free software; you can redistribute it and/or modify | |
5 # it under the terms of the GNU General Public License as published by | |
6 # the Free Software Foundation; either version 2 of the License, or | |
7 # (at your option) any later version. | |
8 # | |
9 # This program is distributed in the hope that it will be useful, | |
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 # GNU General Public License for more details. | |
13 # | |
14 # You should have received a copy of the GNU General Public License | |
15 # along with this program. If not, see <https://www.gnu.org/licenses/>. | |
16 | |
17 package Dpkg::Checksums; | |
18 | |
19 use strict; | |
20 use warnings; | |
21 | |
22 our $VERSION = '1.00'; | |
23 | |
24 use Dpkg; | |
25 use Dpkg::Gettext; | |
26 use Dpkg::ErrorHandling; | |
27 use Dpkg::IPC; | |
28 | |
29 use Exporter qw(import); | |
30 our @EXPORT = qw(checksums_get_list checksums_is_supported | |
31 checksums_get_property); | |
32 | |
33 =encoding utf8 | |
34 | |
35 =head1 NAME | |
36 | |
37 Dpkg::Checksums - generate and manipulate file checksums | |
38 | |
39 =head1 DESCRIPTION | |
40 | |
41 This module provides an object that can generate and manipulate | |
42 various file checksums as well as some methods to query information | |
43 about supported checksums. | |
44 | |
45 =head1 EXPORTED FUNCTIONS | |
46 | |
47 =over 4 | |
48 | |
49 =cut | |
50 | |
51 my $CHECKSUMS = { | |
52 md5 => { | |
53 program => [ 'md5sum' ], | |
54 regex => qr/[0-9a-f]{32}/, | |
55 }, | |
56 sha1 => { | |
57 program => [ 'sha1sum' ], | |
58 regex => qr/[0-9a-f]{40}/, | |
59 }, | |
60 sha256 => { | |
61 program => [ 'sha256sum' ], | |
62 regex => qr/[0-9a-f]{64}/, | |
63 }, | |
64 }; | |
65 | |
66 =item @list = checksums_get_list() | |
67 | |
68 Returns the list of supported checksums algorithms. | |
69 | |
70 =cut | |
71 | |
72 sub checksums_get_list() { | |
73 my @list = sort keys %{$CHECKSUMS}; | |
74 return @list; | |
75 } | |
76 | |
77 =item $bool = checksums_is_supported($alg) | |
78 | |
79 Returns a boolean indicating whether the given checksum algorithm is | |
80 supported. The checksum algorithm is case-insensitive. | |
81 | |
82 =cut | |
83 | |
84 sub checksums_is_supported($) { | |
85 my ($alg) = @_; | |
86 return exists $CHECKSUMS->{lc($alg)}; | |
87 } | |
88 | |
89 =item $value = checksums_get_property($alg, $property) | |
90 | |
91 Returns the requested property of the checksum algorithm. Returns undef if | |
92 either the property or the checksum algorithm doesn't exist. Valid | |
93 properties currently include "program" (returns an array reference with | |
94 a program name and parameters required to compute the checksum of the | |
95 filename given as last parameter) and "regex" for the regular expression | |
96 describing the common string representation of the checksum (as output | |
97 by the program that generates it). | |
98 | |
99 =cut | |
100 | |
101 sub checksums_get_property($$) { | |
102 my ($alg, $property) = @_; | |
103 return unless checksums_is_supported($alg); | |
104 return $CHECKSUMS->{lc($alg)}{$property}; | |
105 } | |
106 | |
107 =back | |
108 | |
109 =head1 OBJECT METHODS | |
110 | |
111 =over 4 | |
112 | |
113 =item my $ck = Dpkg::Checksums->new() | |
114 | |
115 Create a new Dpkg::Checksums object. This object is able to store | |
116 the checksums of several files to later export them or verify them. | |
117 | |
118 =cut | |
119 | |
120 sub new { | |
121 my ($this, %opts) = @_; | |
122 my $class = ref($this) || $this; | |
123 | |
124 my $self = {}; | |
125 bless $self, $class; | |
126 $self->reset(); | |
127 | |
128 return $self; | |
129 } | |
130 | |
131 =item $ck->reset() | |
132 | |
133 Forget about all checksums stored. The object is again in the same state | |
134 as if it was newly created. | |
135 | |
136 =cut | |
137 | |
138 sub reset { | |
139 my ($self) = @_; | |
140 $self->{files} = []; | |
141 $self->{checksums} = {}; | |
142 $self->{size} = {}; | |
143 } | |
144 | |
145 =item $ck->add_from_file($filename, %opts) | |
146 | |
147 Add checksums information for the file $filename. The file must exists | |
148 for the call to succeed. If you don't want the given filename to appear | |
149 when you later export the checksums you might want to set the "key" | |
150 option with the public name that you want to use. Also if you don't want | |
151 to generate all the checksums, you can pass an array reference of the | |
152 wanted checksums in the "checksums" option. | |
153 | |
154 It the object already contains checksums information associated the | |
155 filename (or key), it will error out if the newly computed information | |
156 does not match what's stored. | |
157 | |
158 =cut | |
159 | |
160 sub add_from_file { | |
161 my ($self, $file, %opts) = @_; | |
162 my $key = exists $opts{key} ? $opts{key} : $file; | |
163 my @alg; | |
164 if (exists $opts{checksums}) { | |
165 push @alg, map { lc($_) } @{$opts{checksums}}; | |
166 } else { | |
167 push @alg, checksums_get_list(); | |
168 } | |
169 | |
170 push @{$self->{files}}, $key unless exists $self->{size}{$key}; | |
171 (my @s = stat($file)) or syserr(_g('cannot fstat file %s'), $file); | |
172 if (exists $self->{size}{$key} and $self->{size}{$key} != $s[7]) { | |
173 error(_g('file %s has size %u instead of expected %u'), | |
174 $file, $s[7], $self->{size}{$key}); | |
175 } | |
176 $self->{size}{$key} = $s[7]; | |
177 | |
178 foreach my $alg (@alg) { | |
179 my @exec = (@{$CHECKSUMS->{$alg}{program}}, $file); | |
180 my $regex = $CHECKSUMS->{$alg}{regex}; | |
181 my $output; | |
182 spawn(exec => \@exec, to_string => \$output); | |
183 if ($output =~ /^($regex)(\s|$)/m) { | |
184 my $newsum = $1; | |
185 if (exists $self->{checksums}{$key}{$alg} and | |
186 $self->{checksums}{$key}{$alg} ne $newsum) { | |
187 error(_g('file %s has checksum %s instead of expected %s (algori
thm %s)'), | |
188 $file, $newsum, $self->{checksums}{$key}{$alg}, $alg); | |
189 } | |
190 $self->{checksums}{$key}{$alg} = $newsum; | |
191 } else { | |
192 error(_g("checksum program gave bogus output `%s'"), $output); | |
193 } | |
194 } | |
195 } | |
196 | |
197 =item $ck->add_from_string($alg, $value) | |
198 | |
199 Add checksums of type $alg that are stored in the $value variable. | |
200 $value can be multi-lines, each line should be a space separated list | |
201 of checksum, file size and filename. Leading or trailing spaces are | |
202 not allowed. | |
203 | |
204 It the object already contains checksums information associated to the | |
205 filenames, it will error out if the newly read information does not match | |
206 what's stored. | |
207 | |
208 =cut | |
209 | |
210 sub add_from_string { | |
211 my ($self, $alg, $fieldtext) = @_; | |
212 $alg = lc($alg); | |
213 my $rx_fname = qr/[0-9a-zA-Z][-+:.,=0-9a-zA-Z_~]+/; | |
214 my $regex = checksums_get_property($alg, 'regex'); | |
215 my $checksums = $self->{checksums}; | |
216 | |
217 for my $checksum (split /\n */, $fieldtext) { | |
218 next if $checksum eq ''; | |
219 unless ($checksum =~ m/^($regex)\s+(\d+)\s+($rx_fname)$/) { | |
220 error(_g('invalid line in %s checksums string: %s'), | |
221 $alg, $checksum); | |
222 } | |
223 my ($sum, $size, $file) = ($1, $2, $3); | |
224 if (exists($checksums->{$file}{$alg}) | |
225 and $checksums->{$file}{$alg} ne $sum) { | |
226 error(_g("conflicting checksums '%s' and '%s' for file '%s'"), | |
227 $checksums->{$file}{$alg}, $sum, $file); | |
228 } | |
229 if (exists $self->{size}{$file} and $self->{size}{$file} != $size) { | |
230 error(_g("conflicting file sizes '%u' and '%u' for file '%s'"), | |
231 $self->{size}{$file}, $size, $file); | |
232 } | |
233 push @{$self->{files}}, $file unless exists $self->{size}{$file}; | |
234 $checksums->{$file}{$alg} = $sum; | |
235 $self->{size}{$file} = $size; | |
236 } | |
237 } | |
238 | |
239 =item $ck->add_from_control($control, %opts) | |
240 | |
241 Read checksums from Checksums-* fields stored in the Dpkg::Control object | |
242 $control. It uses $self->add_from_string() on the field values to do the | |
243 actual work. | |
244 | |
245 If the option "use_files_for_md5" evaluates to true, then the "Files" | |
246 field is used in place of the "Checksums-Md5" field. By default the option | |
247 is false. | |
248 | |
249 =cut | |
250 | |
251 sub add_from_control { | |
252 my ($self, $control, %opts) = @_; | |
253 $opts{use_files_for_md5} = 0 unless exists $opts{use_files_for_md5}; | |
254 foreach my $alg (checksums_get_list()) { | |
255 my $key = "Checksums-$alg"; | |
256 $key = 'Files' if ($opts{use_files_for_md5} and $alg eq 'md5'); | |
257 if (exists $control->{$key}) { | |
258 $self->add_from_string($alg, $control->{$key}); | |
259 } | |
260 } | |
261 } | |
262 | |
263 =item @files = $ck->get_files() | |
264 | |
265 Return the list of files whose checksums are stored in the object. | |
266 | |
267 =cut | |
268 | |
269 sub get_files { | |
270 my ($self) = @_; | |
271 return @{$self->{files}}; | |
272 } | |
273 | |
274 =item $bool = $ck->has_file($file) | |
275 | |
276 Return true if we have checksums for the given file. Returns false | |
277 otherwise. | |
278 | |
279 =cut | |
280 | |
281 sub has_file { | |
282 my ($self, $file) = @_; | |
283 return exists $self->{size}{$file}; | |
284 } | |
285 | |
286 =item $ck->remove_file($file) | |
287 | |
288 Remove all checksums of the given file. | |
289 | |
290 =cut | |
291 | |
292 sub remove_file { | |
293 my ($self, $file) = @_; | |
294 return unless $self->has_file($file); | |
295 delete $self->{checksums}{$file}; | |
296 delete $self->{size}{$file}; | |
297 @{$self->{files}} = grep { $_ ne $file } $self->get_files(); | |
298 } | |
299 | |
300 =item $checksum = $ck->get_checksum($file, $alg) | |
301 | |
302 Return the checksum of type $alg for the requested $file. This will not | |
303 compute the checksum but only return the checksum stored in the object, if | |
304 any. | |
305 | |
306 If $alg is not defined, it returns a reference to a hash: keys are | |
307 the checksum algorithms and values are the checksums themselves. The | |
308 hash returned must not be modified, it's internal to the object. | |
309 | |
310 =cut | |
311 | |
312 sub get_checksum { | |
313 my ($self, $file, $alg) = @_; | |
314 $alg = lc($alg) if defined $alg; | |
315 if (exists $self->{checksums}{$file}) { | |
316 return $self->{checksums}{$file} unless defined $alg; | |
317 return $self->{checksums}{$file}{$alg}; | |
318 } | |
319 return; | |
320 } | |
321 | |
322 =item $size = $ck->get_size($file) | |
323 | |
324 Return the size of the requested file if it's available in the object. | |
325 | |
326 =cut | |
327 | |
328 sub get_size { | |
329 my ($self, $file) = @_; | |
330 return $self->{size}{$file}; | |
331 } | |
332 | |
333 =item $ck->export_to_string($alg, %opts) | |
334 | |
335 Return a multi-line string containing the checksums of type $alg. The | |
336 string can be stored as-is in a Checksum-* field of a Dpkg::Control | |
337 object. | |
338 | |
339 =cut | |
340 | |
341 sub export_to_string { | |
342 my ($self, $alg, %opts) = @_; | |
343 my $res = ''; | |
344 foreach my $file ($self->get_files()) { | |
345 my $sum = $self->get_checksum($file, $alg); | |
346 my $size = $self->get_size($file); | |
347 next unless defined $sum and defined $size; | |
348 $res .= "\n$sum $size $file"; | |
349 } | |
350 return $res; | |
351 } | |
352 | |
353 =item $ck->export_to_control($control, %opts) | |
354 | |
355 Export the checksums in the Checksums-* fields of the Dpkg::Control | |
356 $control object. | |
357 | |
358 =cut | |
359 | |
360 sub export_to_control { | |
361 my ($self, $control, %opts) = @_; | |
362 $opts{use_files_for_md5} = 0 unless exists $opts{use_files_for_md5}; | |
363 foreach my $alg (checksums_get_list()) { | |
364 my $key = "Checksums-$alg"; | |
365 $key = 'Files' if ($opts{use_files_for_md5} and $alg eq 'md5'); | |
366 $control->{$key} = $self->export_to_string($alg, %opts); | |
367 } | |
368 } | |
369 | |
370 =back | |
371 | |
372 =head1 AUTHOR | |
373 | |
374 Raphaël Hertzog <hertzog@debian.org>. | |
375 | |
376 =cut | |
377 | |
378 1; | |
OLD | NEW |