1 # Copyright © 2008 Frank Lichtenheld <djpig@debian.org>
2 # Copyright © 2008, 2012-2015 Guillem Jover <guillem@debian.org>
3 # Copyright © 2010 Raphaël Hertzog <hertzog@debian.org>
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or
8 # (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program. If not, see <https://www.gnu.org/licenses/>.
22 Dpkg::Checksums - generate and manipulate file checksums
26 This module provides a class that can generate and manipulate
27 various file checksums as well as some methods to query information
28 about supported checksums.
32 package Dpkg
::Checksums
1.04;
38 checksums_is_supported
40 checksums_get_property
43 use Exporter
qw(import);
47 use Dpkg
::ErrorHandling
;
58 regex
=> qr/[0-9a-f]{32}/,
63 regex
=> qr/[0-9a-f]{40}/,
68 regex
=> qr/[0-9a-f]{64}/,
73 =item @list = checksums_get_list()
75 Returns the list of supported checksums algorithms.
79 sub checksums_get_list
() {
80 my @list = sort keys %{$CHECKSUMS};
84 =item $bool = checksums_is_supported($alg)
86 Returns a boolean indicating whether the given checksum algorithm is
87 supported. The checksum algorithm is case-insensitive.
91 sub checksums_is_supported
($) {
93 return exists $CHECKSUMS->{lc($alg)};
96 =item $value = checksums_get_property($alg, $property)
98 Returns the requested property of the checksum algorithm. Returns undef if
99 either the property or the checksum algorithm doesn't exist. Valid
100 properties currently include "name" (returns the name of the digest
101 algorithm), "regex" for the regular expression describing the common
102 string representation of the checksum, and "strong" for a boolean describing
103 whether the checksum algorithm is considered cryptographically strong.
107 sub checksums_get_property
($$) {
108 my ($alg, $property) = @_;
110 return unless checksums_is_supported
($alg);
111 return $CHECKSUMS->{lc($alg)}{$property};
120 =item $ck = Dpkg::Checksums->new()
122 Create a new Dpkg::Checksums object. This object is able to store
123 the checksums of several files to later export them or verify them.
128 my ($this, %opts) = @_;
129 my $class = ref($this) || $this;
140 Forget about all checksums stored. The object is again in the same state
141 as if it was newly created.
149 $self->{checksums
} = {};
153 =item $ck->add_from_file($filename, %opts)
155 Add or verify checksums information for the file $filename. The file must
156 exists for the call to succeed. If you don't want the given filename to
157 appear when you later export the checksums you might want to set the "key"
158 option with the public name that you want to use. Also if you don't want
159 to generate all the checksums, you can pass an array reference of the
160 wanted checksums in the "checksums" option.
162 It the object already contains checksums information associated the
163 filename (or key), it will error out if the newly computed information
164 does not match what's stored, and the caller did not request that it be
165 updated with the boolean "update" option.
170 my ($self, $file, %opts) = @_;
171 my $key = exists $opts{key
} ?
$opts{key
} : $file;
173 if (exists $opts{checksums
}) {
174 push @alg, map { lc } @
{$opts{checksums
}};
176 push @alg, checksums_get_list
();
179 push @
{$self->{files
}}, $key unless exists $self->{size
}{$key};
180 (my @s = stat($file)) or syserr
(g_
('cannot fstat file %s'), $file);
181 if (not $opts{update
} and exists $self->{size
}{$key} and
182 $self->{size
}{$key} != $s[7]) {
183 error
(g_
('file %s has size %u instead of expected %u'),
184 $file, $s[7], $self->{size
}{$key});
186 $self->{size
}{$key} = $s[7];
188 foreach my $alg (@alg) {
189 my $digest = Digest
->new($CHECKSUMS->{$alg}{name
});
190 open my $fh, '<', $file or syserr
(g_
('cannot open file %s'), $file);
191 $digest->addfile($fh);
194 my $newsum = $digest->hexdigest;
195 if (not $opts{update
} and exists $self->{checksums
}{$key}{$alg} and
196 $self->{checksums
}{$key}{$alg} ne $newsum) {
197 error
(g_
('file %s has checksum %s instead of expected %s (algorithm %s)'),
198 $file, $newsum, $self->{checksums
}{$key}{$alg}, $alg);
200 $self->{checksums
}{$key}{$alg} = $newsum;
204 =item $ck->add_from_string($alg, $value, %opts)
206 Add checksums of type $alg that are stored in the $value variable.
207 $value can be multi-lines, each line should be a space separated list
208 of checksum, file size and filename. Leading or trailing spaces are
211 It the object already contains checksums information associated to the
212 filenames, it will error out if the newly read information does not match
213 what's stored, and the caller did not request that it be updated with
214 the boolean "update" option.
218 sub add_from_string
{
219 my ($self, $alg, $fieldtext, %opts) = @_;
221 my $rx_fname = qr/[0-9a-zA-Z][-+:.,=0-9a-zA-Z_~]+/;
222 my $regex = checksums_get_property
($alg, 'regex');
223 my $checksums = $self->{checksums
};
225 for my $checksum (split /\n */, $fieldtext) {
226 next if $checksum eq '';
227 unless ($checksum =~ m/^($regex)\s+(\d+)\s+($rx_fname)$/) {
228 error
(g_
('invalid line in %s checksums string: %s'),
231 ## no critic (RegularExpressions::ProhibitCaptureWithoutTest)
232 my ($sum, $size, $file) = ($1, $2, $3);
233 if (not $opts{update
} and exists($checksums->{$file}{$alg})
234 and $checksums->{$file}{$alg} ne $sum) {
235 error
(g_
("conflicting checksums '%s' and '%s' for file '%s'"),
236 $checksums->{$file}{$alg}, $sum, $file);
238 if (not $opts{update
} and exists $self->{size
}{$file}
239 and $self->{size
}{$file} != $size) {
240 error
(g_
("conflicting file sizes '%u' and '%u' for file '%s'"),
241 $self->{size
}{$file}, $size, $file);
243 push @
{$self->{files
}}, $file unless exists $self->{size
}{$file};
244 $checksums->{$file}{$alg} = $sum;
245 $self->{size
}{$file} = $size;
249 =item $ck->add_from_control($control, %opts)
251 Read checksums from Checksums-* fields stored in the Dpkg::Control object
252 $control. It uses $self->add_from_string() on the field values to do the
255 If the option "use_files_for_md5" evaluates to true, then the "Files"
256 field is used in place of the "Checksums-Md5" field. By default the option
261 sub add_from_control
{
262 my ($self, $control, %opts) = @_;
263 $opts{use_files_for_md5
} //= 0;
264 foreach my $alg (checksums_get_list
()) {
265 my $key = "Checksums-$alg";
266 $key = 'Files' if ($opts{use_files_for_md5
} and $alg eq 'md5');
267 if (exists $control->{$key}) {
268 $self->add_from_string($alg, $control->{$key}, %opts);
273 =item @files = $ck->get_files()
275 Return the list of files whose checksums are stored in the object.
281 return @
{$self->{files
}};
284 =item $bool = $ck->has_file($file)
286 Return true if we have checksums for the given file. Returns false
292 my ($self, $file) = @_;
293 return exists $self->{size
}{$file};
296 =item $ck->remove_file($file)
298 Remove all checksums of the given file.
303 my ($self, $file) = @_;
304 return unless $self->has_file($file);
305 delete $self->{checksums
}{$file};
306 delete $self->{size
}{$file};
307 @
{$self->{files
}} = grep { $_ ne $file } $self->get_files();
310 =item $checksum = $ck->get_checksum($file, $alg)
312 Return the checksum of type $alg for the requested $file. This will not
313 compute the checksum but only return the checksum stored in the object, if
316 If $alg is not defined, it returns a reference to a hash: keys are
317 the checksum algorithms and values are the checksums themselves. The
318 hash returned must not be modified, it's internal to the object.
323 my ($self, $file, $alg) = @_;
324 $alg = lc($alg) if defined $alg;
325 if (exists $self->{checksums
}{$file}) {
326 return $self->{checksums
}{$file} unless defined $alg;
327 return $self->{checksums
}{$file}{$alg};
332 =item $size = $ck->get_size($file)
334 Return the size of the requested file if it's available in the object.
339 my ($self, $file) = @_;
340 return $self->{size
}{$file};
343 =item $bool = $ck->has_strong_checksums($file)
345 Return a boolean on whether the file has a strong checksum.
349 sub has_strong_checksums
{
350 my ($self, $file) = @_;
352 foreach my $alg (checksums_get_list
()) {
353 return 1 if defined $self->get_checksum($file, $alg) and
354 checksums_get_property
($alg, 'strong');
360 =item $ck->export_to_string($alg, %opts)
362 Return a multi-line string containing the checksums of type $alg. The
363 string can be stored as-is in a Checksum-* field of a Dpkg::Control
368 sub export_to_string
{
369 my ($self, $alg, %opts) = @_;
371 foreach my $file ($self->get_files()) {
372 my $sum = $self->get_checksum($file, $alg);
373 my $size = $self->get_size($file);
374 next unless defined $sum and defined $size;
375 $res .= "\n$sum $size $file";
380 =item $ck->export_to_control($control, %opts)
382 Export the checksums in the Checksums-* fields of the Dpkg::Control
387 sub export_to_control
{
388 my ($self, $control, %opts) = @_;
389 $opts{use_files_for_md5
} //= 0;
390 foreach my $alg (checksums_get_list
()) {
391 my $key = "Checksums-$alg";
392 $key = 'Files' if ($opts{use_files_for_md5
} and $alg eq 'md5');
393 $control->{$key} = $self->export_to_string($alg, %opts);
401 =head2 Version 1.04 (dpkg 1.20.0)
403 Remove warning: For obsolete property 'program'.
405 =head2 Version 1.03 (dpkg 1.18.5)
407 New property: Add new 'strong' property.
409 New member: $ck->has_strong_checksums().
411 =head2 Version 1.02 (dpkg 1.18.0)
413 Obsolete property: Getting the 'program' checksum property will warn and
414 return undef, the Digest module is used internally now.
416 New property: Add new 'name' property with the name of the Digest algorithm
419 =head2 Version 1.01 (dpkg 1.17.6)
421 New argument: Accept an options argument in $ck->export_to_string().
423 New option: Accept new option 'update' in $ck->add_from_file() and
424 $ck->add_from_control().
426 =head2 Version 1.00 (dpkg 1.15.6)
428 Mark the module as public.