1 # Copyright © 2008 Frank Lichtenheld <djpig@debian.org>
2 # Copyright © 2010 Raphaël Hertzog <hertzog@debian.org>
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.
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.
14 # You should have received a copy of the GNU General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
17 package Dpkg::Checksums;
22 our $VERSION = "1.00";
26 use Dpkg::ErrorHandling;
29 use base qw(Exporter);
30 our @EXPORT = qw(checksums_get_list checksums_is_supported
31 checksums_get_property);
37 Dpkg::Checksums - generate and manipulate file checksums
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.
45 =head1 EXPORTED FUNCTIONS
53 "program" => [ "md5sum" ],
54 "regex" => qr/[0-9a-f]{32}/,
57 "program" => [ "sha1sum" ],
58 "regex" => qr/[0-9a-f]{40}/,
61 "program" => [ "sha256sum" ],
62 "regex" => qr/[0-9a-f]{64}/,
66 =item @list = checksums_get_list()
68 Returns the list of supported checksums algorithms.
72 sub checksums_get_list() {
73 return sort keys %{$CHECKSUMS};
76 =item $bool = checksums_is_supported($alg)
78 Returns a boolean indicating whether the given checksum algorithm is
79 supported. The checksum algorithm is case-insensitive.
83 sub checksums_is_supported($) {
85 return exists $CHECKSUMS->{lc($alg)};
88 =item $value = checksums_get_property($alg, $property)
90 Returns the requested property of the checksum algorithm. Returns undef if
91 either the property or the checksum algorithm doesn't exist. Valid
92 properties currently include "program" (returns an array reference with
93 a program name and parameters required to compute the checksum of the
94 filename given as last parameter) and "regex" for the regular expression
95 describing the common string representation of the checksum (as output
96 by the program that generates it).
100 sub checksums_get_property($$) {
101 my ($alg, $property) = @_;
102 return undef unless checksums_is_supported($alg);
103 return $CHECKSUMS->{lc($alg)}{$property};
108 =head1 OBJECT METHODS
112 =item my $ck = Dpkg::Checksums->new()
114 Create a new Dpkg::Checksums object. This object is able to store
115 the checksums of several files to later export them or verify them.
120 my ($this, %opts) = @_;
121 my $class = ref($this) || $this;
132 Forget about all checksums stored. The object is again in the same state
133 as if it was newly created.
140 $self->{checksums} = {};
144 =item $ck->add_from_file($filename, %opts)
146 Add checksums information for the file $filename. The file must exists
147 for the call to succeed. If you don't want the given filename to appear
148 when you later export the checksums you might want to set the "key"
149 option with the public name that you want to use. Also if you don't want
150 to generate all the checksums, you can pass an array reference of the
151 wanted checksums in the "checksums" option.
153 It the object already contains checksums information associated the
154 filename (or key), it will error out if the newly computed information
155 does not match what's stored.
160 my ($self, $file, %opts) = @_;
161 my $key = exists $opts{key} ? $opts{key} : $file;
163 if (exists $opts{checksums}) {
164 push @alg, map { lc($_) } @{$opts{checksums}};
166 push @alg, checksums_get_list();
169 push @{$self->{files}}, $key unless exists $self->{size}{$key};
170 (my @s = stat($file)) || syserr(_g("cannot fstat file %s"), $file);
171 if (exists $self->{size}{$key} and $self->{size}{$key} != $s[7]) {
172 error(_g("File %s has size %u instead of expected %u"),
173 $file, $s[7], $self->{size}{$key});
175 $self->{size}{$key} = $s[7];
177 foreach my $alg (@alg) {
178 my @exec = (@{$CHECKSUMS->{$alg}{"program"}}, $file);
179 my $regex = $CHECKSUMS->{$alg}{"regex"};
181 spawn('exec' => \@exec, to_string => \$output);
182 if ($output =~ /^($regex)(\s|$)/m) {
184 if (exists $self->{checksums}{$key}{$alg} and
185 $self->{checksums}{$key}{$alg} ne $newsum) {
186 error(_g("File %s has checksum %s instead of expected %s (algorithm %s)"),
187 $file, $newsum, $self->{checksums}{$key}{$alg}, $alg);
189 $self->{checksums}{$key}{$alg} = $newsum;
191 error(_g("checksum program gave bogus output `%s'"), $output);
196 =item $ck->add_from_string($alg, $value)
198 Add checksums of type $alg that are stored in the $value variable.
199 $value can be multi-lines, each line should be a space separated list
200 of checksum, file size and filename. Leading or trailing spaces are
203 It the object already contains checksums information associated to the
204 filenames, it will error out if the newly read information does not match
209 sub add_from_string {
210 my ($self, $alg, $fieldtext) = @_;
212 my $rx_fname = qr/[0-9a-zA-Z][-+:.,=0-9a-zA-Z_~]+/;
213 my $regex = checksums_get_property($alg, "regex");
214 my $checksums = $self->{checksums};
216 for my $checksum (split /\n */, $fieldtext) {
217 next if $checksum eq '';
218 unless ($checksum =~ m/^($regex)\s+(\d+)\s+($rx_fname)$/) {
219 error(_g("invalid line in %s checksums string: %s"),
222 my ($sum, $size, $file) = ($1, $2, $3);
223 if (exists($checksums->{$file}{$alg})
224 and $checksums->{$file}{$alg} ne $sum) {
225 error(_g("Conflicting checksums \`%s\' and \`%s' for file \`%s'"),
226 $checksums->{$file}{$alg}, $sum, $file);
228 if (exists $self->{size}{$file} and $self->{size}{$file} != $size) {
229 error(_g("Conflicting file sizes \`%u\' and \`%u' for file \`%s'"),
230 $self->{size}{$file}, $size, $file);
232 push @{$self->{files}}, $file unless exists $self->{size}{$file};
233 $checksums->{$file}{$alg} = $sum;
234 $self->{size}{$file} = $size;
238 =item $ck->add_from_control($control, %opts)
240 Read checksums from Checksums-* fields stored in the Dpkg::Control object
241 $control. It uses $self->add_from_string() on the field values to do the
244 If the option "use_files_for_md5" evaluates to true, then the "Files"
245 field is used in place of the "Checksums-Md5" field. By default the option
250 sub add_from_control {
251 my ($self, $control, %opts) = @_;
252 $opts{use_files_for_md5} = 0 unless exists $opts{use_files_for_md5};
253 foreach my $alg (checksums_get_list()) {
254 my $key = "Checksums-$alg";
255 $key = "Files" if ($opts{use_files_for_md5} and $alg eq "md5");
256 if (exists $control->{$key}) {
257 $self->add_from_string($alg, $control->{$key});
262 =item @files = $ck->get_files()
264 Return the list of files whose checksums are stored in the object.
270 return @{$self->{files}};
273 =item $bool = $ck->has_file($file)
275 Return true if we have checksums for the given file. Returns false
281 my ($self, $file) = @_;
282 return exists $self->{size}{$file};
285 =item $ck->remove_file($file)
287 Remove all checksums of the given file.
292 my ($self, $file) = @_;
293 return unless $self->has_file($file);
294 delete $self->{'checksums'}{$file};
295 delete $self->{'size'}{$file};
296 @{$self->{'files'}} = grep { $_ ne $file } $self->get_files();
299 =item $checksum = $ck->get_checksum($file, $alg)
301 Return the checksum of type $alg for the requested $file. This will not
302 compute the checksum but only return the checksum stored in the object, if
305 If $alg is not defined, it returns a reference to a hash: keys are
306 the checksum algorithms and values are the checksums themselves. The
307 hash returned must not be modified, it's internal to the object.
312 my ($self, $file, $alg) = @_;
313 $alg = lc($alg) if defined $alg;
314 if (exists $self->{checksums}{$file}) {
315 return $self->{checksums}{$file} unless defined $alg;
316 return $self->{checksums}{$file}{$alg};
321 =item $size = $ck->get_size($file)
323 Return the size of the requested file if it's available in the object.
328 my ($self, $file) = @_;
329 return $self->{size}{$file};
332 =item $ck->export_to_string($alg, %opts)
334 Return a multi-line string containing the checksums of type $alg. The
335 string can be stored as-is in a Checksum-* field of a Dpkg::Control
340 sub export_to_string {
341 my ($self, $alg, %opts) = @_;
343 foreach my $file ($self->get_files()) {
344 my $sum = $self->get_checksum($file, $alg);
345 my $size = $self->get_size($file);
346 next unless defined $sum and defined $size;
347 $res .= "\n$sum $size $file";
352 =item $ck->export_to_control($control, %opts)
354 Export the checksums in the Checksums-* fields of the Dpkg::Control
359 sub export_to_control {
360 my ($self, $control, %opts) = @_;
361 $opts{use_files_for_md5} = 0 unless exists $opts{use_files_for_md5};
362 foreach my $alg (checksums_get_list()) {
363 my $key = "Checksums-$alg";
364 $key = "Files" if ($opts{use_files_for_md5} and $alg eq "md5");
365 $control->{$key} = $self->export_to_string($alg, %opts);
373 Raphaël Hertzog <hertzog@debian.org>.