This is a live mirror of the Perl 5 development currently hosted at https://github.com/perl/perl5
Update Digest-SHA to CPAN version 6.01
[perl5.git] / cpan / Digest-SHA / shasum
1 #!perl
2
3         ## shasum: filter for computing SHA digests (ref. sha1sum/md5sum)
4         ##
5         ## Copyright (C) 2003-2017 Mark Shelor, All Rights Reserved
6         ##
7         ## Version: 6.01
8         ## Mon Dec 25 00:08:08 MST 2017
9
10         ## shasum SYNOPSIS adapted from GNU Coreutils sha1sum. Add
11         ## "-a" option for algorithm selection,
12         ## "-U" option for Universal Newlines support, and
13         ## "-0" option for reading bit strings.
14
15 BEGIN { pop @INC if $INC[-1] eq '.' }
16
17 use strict;
18 use warnings;
19 use Fcntl;
20 use Getopt::Long;
21 use Digest::SHA qw($errmsg);
22
23 my $POD = <<'END_OF_POD';
24
25 =head1 NAME
26
27 shasum - Print or Check SHA Checksums
28
29 =head1 SYNOPSIS
30
31  Usage: shasum [OPTION]... [FILE]...
32  Print or check SHA checksums.
33  With no FILE, or when FILE is -, read standard input.
34
35    -a, --algorithm   1 (default), 224, 256, 384, 512, 512224, 512256
36    -b, --binary      read in binary mode
37    -c, --check       read SHA sums from the FILEs and check them
38        --tag         create a BSD-style checksum
39    -t, --text        read in text mode (default)
40    -U, --UNIVERSAL   read in Universal Newlines mode
41                          produces same digest on Windows/Unix/Mac
42    -0, --01          read in BITS mode
43                          ASCII '0' interpreted as 0-bit,
44                          ASCII '1' interpreted as 1-bit,
45                          all other characters ignored
46
47  The following five options are useful only when verifying checksums:
48        --ignore-missing  don't fail or report status for missing files
49    -q, --quiet           don't print OK for each successfully verified file
50    -s, --status          don't output anything, status code shows success
51        --strict          exit non-zero for improperly formatted checksum lines
52    -w, --warn            warn about improperly formatted checksum lines
53
54    -h, --help        display this help and exit
55    -v, --version     output version information and exit
56
57  When verifying SHA-512/224 or SHA-512/256 checksums, indicate the
58  algorithm explicitly using the -a option, e.g.
59
60    shasum -a 512224 -c checksumfile
61
62  The sums are computed as described in FIPS PUB 180-4.  When checking,
63  the input should be a former output of this program.  The default
64  mode is to print a line with checksum, a character indicating type
65  (`*' for binary, ` ' for text, `U' for UNIVERSAL, `^' for BITS),
66  and name for each FILE.  The line starts with a `\' character if the
67  FILE name contains either newlines or backslashes, which are then
68  replaced by the two-character sequences `\n' and `\\' respectively.
69
70  Report shasum bugs to mshelor@cpan.org
71
72 =head1 DESCRIPTION
73
74 Running I<shasum> is often the quickest way to compute SHA message
75 digests.  The user simply feeds data to the script through files or
76 standard input, and then collects the results from standard output.
77
78 The following command shows how to compute digests for typical inputs
79 such as the NIST test vector "abc":
80
81         perl -e "print qq(abc)" | shasum
82
83 Or, if you want to use SHA-256 instead of the default SHA-1, simply say:
84
85         perl -e "print qq(abc)" | shasum -a 256
86
87 Since I<shasum> mimics the behavior of the combined GNU I<sha1sum>,
88 I<sha224sum>, I<sha256sum>, I<sha384sum>, and I<sha512sum> programs,
89 you can install this script as a convenient drop-in replacement.
90
91 Unlike the GNU programs, I<shasum> encompasses the full SHA standard by
92 allowing partial-byte inputs.  This is accomplished through the BITS
93 option (I<-0>).  The following example computes the SHA-224 digest of
94 the 7-bit message I<0001100>:
95
96         perl -e "print qq(0001100)" | shasum -0 -a 224
97
98 =head1 AUTHOR
99
100 Copyright (c) 2003-2017 Mark Shelor <mshelor@cpan.org>.
101
102 =head1 SEE ALSO
103
104 I<shasum> is implemented using the Perl module L<Digest::SHA>.
105
106 =cut
107
108 END_OF_POD
109
110 my $VERSION = "6.01";
111
112 sub usage {
113         my($err, $msg) = @_;
114
115         $msg = "" unless defined $msg;
116         if ($err) {
117                 warn($msg . "Type shasum -h for help\n");
118                 exit($err);
119         }
120         my($USAGE) = $POD =~ /SYNOPSIS(.+?)^=/sm;
121         $USAGE =~ s/^\s*//;
122         $USAGE =~ s/\s*$//;
123         $USAGE =~ s/^ //gm;
124         print $USAGE, "\n";
125         exit($err);
126 }
127
128
129         ## Sync stdout and stderr by forcing a flush after every write
130
131 select((select(STDOUT), $| = 1)[0]);
132 select((select(STDERR), $| = 1)[0]);
133
134
135         ## Collect options from command line
136
137 my ($alg, $binary, $check, $text, $status, $quiet, $warn, $help);
138 my ($version, $BITS, $UNIVERSAL, $tag, $strict, $ignore_missing);
139
140 eval { Getopt::Long::Configure ("bundling") };
141 GetOptions(
142         'b|binary' => \$binary, 'c|check' => \$check,
143         't|text' => \$text, 'a|algorithm=i' => \$alg,
144         's|status' => \$status, 'w|warn' => \$warn,
145         'q|quiet' => \$quiet,
146         'h|help' => \$help, 'v|version' => \$version,
147         '0|01' => \$BITS,
148         'U|UNIVERSAL' => \$UNIVERSAL,
149         'tag' => \$tag,
150         'strict' => \$strict,
151         'ignore-missing' => \$ignore_missing,
152 ) or usage(1, "");
153
154
155         ## Deal with help requests and incorrect uses
156
157 usage(0)
158         if $help;
159 usage(1, "shasum: Ambiguous file mode\n")
160         if scalar(grep {defined $_}
161                 ($binary, $text, $BITS, $UNIVERSAL)) > 1;
162 usage(1, "shasum: --warn option used only when verifying checksums\n")
163         if $warn && !$check;
164 usage(1, "shasum: --status option used only when verifying checksums\n")
165         if $status && !$check;
166 usage(1, "shasum: --quiet option used only when verifying checksums\n")
167         if $quiet && !$check;
168 usage(1, "shasum: --ignore-missing option used only when verifying checksums\n")
169         if $ignore_missing && !$check;
170 usage(1, "shasum: --strict option used only when verifying checksums\n")
171         if $strict && !$check;
172 usage(1, "shasum: --tag does not support --text mode\n")
173         if $tag && $text;
174 usage(1, "shasum: --tag does not support Universal Newlines mode\n")
175         if $tag && $UNIVERSAL;
176 usage(1, "shasum: --tag does not support BITS mode\n")
177         if $tag && $BITS;
178
179
180         ## Default to SHA-1 unless overridden by command line option
181
182 my %isAlg = map { $_ => 1 } (1, 224, 256, 384, 512, 512224, 512256);
183 $alg = 1 unless defined $alg;
184 usage(1, "shasum: Unrecognized algorithm\n") unless $isAlg{$alg};
185
186 my %Tag = map { $_ => "SHA$_" } (1, 224, 256, 384, 512);
187 $Tag{512224} = "SHA512/224";
188 $Tag{512256} = "SHA512/256";
189
190
191         ## Display version information if requested
192
193 if ($version) {
194         print "$VERSION\n";
195         exit(0);
196 }
197
198
199         ## Try to figure out if the OS is DOS-like.  If it is,
200         ## default to binary mode when reading files, unless
201         ## explicitly overridden by command line "--text" or
202         ## "--UNIVERSAL" options.
203
204 my $isDOSish = ($^O =~ /^(MSWin\d\d|os2|dos|mint|cygwin)$/);
205 if ($isDOSish) { $binary = 1 unless $text || $UNIVERSAL }
206
207 my $modesym = $binary ? '*' : ($UNIVERSAL ? 'U' : ($BITS ? '^' : ' '));
208
209
210         ## Read from STDIN (-) if no files listed on command line
211
212 @ARGV = ("-") unless @ARGV;
213
214
215         ## sumfile($file): computes SHA digest of $file
216
217 sub sumfile {
218         my $file = shift;
219
220         my $mode = $binary ? 'b' : ($UNIVERSAL ? 'U' : ($BITS ? '0' : ''));
221         my $digest = eval { Digest::SHA->new($alg)->addfile($file, $mode) };
222         if ($@) { warn "shasum: $file: $errmsg\n"; return }
223         $digest->hexdigest;
224 }
225
226
227         ## %len2alg: maps hex digest length to SHA algorithm
228
229 my %len2alg = (40 => 1, 56 => 224, 64 => 256, 96 => 384, 128 => 512);
230 $len2alg{56} = 512224 if $alg == 512224;
231 $len2alg{64} = 512256 if $alg == 512256;
232
233
234         ## unescape: convert backslashed filename to plain filename
235
236 sub unescape {
237         $_ = shift;
238         s/\\\\/\0/g;
239         s/\\n/\n/g;
240         s/\0/\\/g;
241         return $_;
242 }
243
244
245         ## verify: confirm the digest values in a checksum file
246
247 sub verify {
248         my $checkfile = shift;
249         my ($err, $fmt_errs, $read_errs, $match_errs) = (0, 0, 0, 0);
250         my ($num_fmt_OK, $num_OK) = (0, 0);
251         my ($bslash, $sum, $fname, $rsp, $digest, $isOK);
252
253         local *FH;
254         $checkfile eq '-' and open(FH, '< -')
255                 and $checkfile = 'standard input'
256         or sysopen(FH, $checkfile, O_RDONLY)
257                 or die "shasum: $checkfile: $!\n";
258         while (<FH>) {
259                 next if /^#/;
260                 if (/^[ \t]*\\?SHA/) {
261                         $modesym = '*';
262                         ($bslash, $alg, $fname, $sum) =
263                         /^[ \t]*(\\?)SHA(\S+) \((.+)\) = ([\da-fA-F]+)/;
264                         $alg =~ tr{/}{}d if defined $alg;
265                 }
266                 else {
267                         ($bslash, $sum, $modesym, $fname) =
268                         /^[ \t]*(\\?)([\da-fA-F]+)[ \t]([ *^U])(.+)/;
269                         $alg = defined $sum ? $len2alg{length($sum)} : undef;
270                 }
271                 if (grep { ! defined $_ } ($alg, $sum, $modesym, $fname) or
272                         ! $isAlg{$alg}) {
273                         warn("shasum: $checkfile: $.: improperly " .
274                                 "formatted SHA checksum line\n") if $warn;
275                         $fmt_errs++;
276                         $err = 1 if $strict;
277                         next;
278                 }
279                 $num_fmt_OK++;
280                 $fname = unescape($fname) if $bslash;
281                 next if $ignore_missing && ! -e $fname;
282                 $rsp = "$fname: ";
283                 ($binary, $text, $UNIVERSAL, $BITS) =
284                         map { $_ eq $modesym } ('*', ' ', 'U', '^');
285                 $isOK = 0;
286                 unless ($digest = sumfile($fname)) {
287                         $rsp .= "FAILED open or read\n";
288                         $err = 1; $read_errs++;
289                 }
290                 elsif (lc($sum) eq $digest) {
291                         $rsp .= "OK\n";
292                         $isOK = 1;
293                         $num_OK++;
294                 }
295                 else { $rsp .= "FAILED\n"; $err = 1; $match_errs++ }
296                 print $rsp unless ($status || ($quiet && $isOK));
297         }
298         close(FH);
299         if (! $num_fmt_OK) {
300                 warn("shasum: $checkfile: no properly formatted " .
301                         "SHA checksum lines found\n");
302                 $err = 1;
303         }
304         elsif (! $status) {
305                 warn("shasum: WARNING: $fmt_errs line" . ($fmt_errs>1?
306                 's are':' is') . " improperly formatted\n") if $fmt_errs;
307                 warn("shasum: WARNING: $read_errs listed file" .
308                 ($read_errs>1?'s':'') . " could not be read\n") if $read_errs;
309                 warn("shasum: WARNING: $match_errs computed checksum" .
310                 ($match_errs>1?'s':'') . " did NOT match\n") if $match_errs;
311         }
312         if ($ignore_missing && ! $num_OK && $num_fmt_OK) {
313                 warn("shasum: $checkfile: no file was verified\n")
314                         unless $status;
315                 $err = 1;
316         }
317         return($err == 0);
318 }
319
320
321         ## Verify or compute SHA checksums of requested files
322
323 my($file, $digest);
324 my $STATUS = 0;
325 for $file (@ARGV) {
326         if ($check) { $STATUS = 1 unless verify($file) }
327         elsif ($digest = sumfile($file)) {
328                 if ($file =~ /[\n\\]/) {
329                         $file =~ s/\\/\\\\/g; $file =~ s/\n/\\n/g;
330                         print "\\";
331                 }
332                 unless ($tag) { print "$digest $modesym$file\n" }
333                 else          { print "$Tag{$alg} ($file) = $digest\n" }
334         }
335         else { $STATUS = 1 }
336 }
337 exit($STATUS);