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