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