Mercurial > hgrepos > Python > apps > py-cutils
comparison shasum.py @ 12:5e2c9123f93f
Implemented digest verification: -c or --check option
| author | Franz Glasner <fzglas.hg@dom66.de> |
|---|---|
| date | Fri, 04 Dec 2020 17:37:56 +0100 |
| parents | 15c3416d3677 |
| children | db64e282b049 |
comparison
equal
deleted
inserted
replaced
| 11:15c3416d3677 | 12:5e2c9123f93f |
|---|---|
| 12 | 12 |
| 13 from __future__ import print_function | 13 from __future__ import print_function |
| 14 | 14 |
| 15 import argparse | 15 import argparse |
| 16 import hashlib | 16 import hashlib |
| 17 import io | |
| 18 import re | |
| 17 import sys | 19 import sys |
| 18 | 20 |
| 19 | 21 |
| 20 PY2 = sys.version_info[0] < 3 | 22 PY2 = sys.version_info[0] < 3 |
| 21 | 23 |
| 34 help="read in binary mode (default)") | 36 help="read in binary mode (default)") |
| 35 aparser.add_argument( | 37 aparser.add_argument( |
| 36 "--bsd", "-B", action="store_true", dest="bsd", default=False, | 38 "--bsd", "-B", action="store_true", dest="bsd", default=False, |
| 37 help="write BSD style output; also :command:`openssl dgst` style") | 39 help="write BSD style output; also :command:`openssl dgst` style") |
| 38 aparser.add_argument( | 40 aparser.add_argument( |
| 41 "--check", "-c", action="store_true", | |
| 42 help="read digests from FILEs and check them") | |
| 43 aparser.add_argument( | |
| 39 "--reverse", "-r", action="store_false", dest="bsd", default=False, | 44 "--reverse", "-r", action="store_false", dest="bsd", default=False, |
| 40 help="explicitely select normal coreutils style output (to be option compatible with BSD style commands and :command:`openssl dgst -r`)") | 45 help="explicitely select normal coreutils style output (to be option compatible with BSD style commands and :command:`openssl dgst -r`)") |
| 41 aparser.add_argument( | 46 aparser.add_argument( |
| 42 "--tag", action="store_true", dest="bsd", default=False, | 47 "--tag", action="store_true", dest="bsd", default=False, |
| 43 help="alias for the `--bsd' option (to be compatible with :command:`b2sum`)") | 48 help="alias for the `--bsd' option (to be compatible with :command:`b2sum`)") |
| 56 if not opts.algorithm: | 61 if not opts.algorithm: |
| 57 opts.algorithm = argv2algo("1") | 62 opts.algorithm = argv2algo("1") |
| 58 | 63 |
| 59 if not opts.files: | 64 if not opts.files: |
| 60 opts.files.append('-') | 65 opts.files.append('-') |
| 61 generate_digests(opts) | 66 if opts.check: |
| 67 return verify_digests_from_files(opts) | |
| 68 else: | |
| 69 return generate_digests(opts) | |
| 62 | 70 |
| 63 | 71 |
| 64 def generate_digests(opts): | 72 def generate_digests(opts): |
| 65 if opts.bsd: | 73 if opts.bsd: |
| 66 out = out_bsd | 74 out = out_bsd |
| 85 out(sys.stdout, | 93 out(sys.stdout, |
| 86 compute_digest(opts.algorithm[0], source), | 94 compute_digest(opts.algorithm[0], source), |
| 87 fn, | 95 fn, |
| 88 opts.algorithm[1], | 96 opts.algorithm[1], |
| 89 True) | 97 True) |
| 98 return 0 | |
| 99 | |
| 100 | |
| 101 def verify_digests_from_files(opts): | |
| 102 exit_code = 0 | |
| 103 if len(opts.files) == 1 and opts.files[0] == '-': | |
| 104 for checkline in sys.stdin: | |
| 105 if not checkline: | |
| 106 continue | |
| 107 r, fn = handle_checkline(opts, checkline) | |
| 108 print("{}: {}".format(fn, r.upper())) | |
| 109 if r != "ok" and exit_code == 0: | |
| 110 exit_code = 1 | |
| 111 else: | |
| 112 for fn in opts.files: | |
| 113 with io.open(fn, "rt", encoding="utf-8") as checkfile: | |
| 114 for checkline in checkfile: | |
| 115 if not checkline: | |
| 116 continue | |
| 117 r, fn = handle_checkline(opts, checkline) | |
| 118 print("{}: {}".format(fn, r.upper())) | |
| 119 if r != "ok" and exit_code == 0: | |
| 120 exit_code = 1 | |
| 121 return exit_code | |
| 122 | |
| 123 | |
| 124 def handle_checkline(opts, line): | |
| 125 """ | |
| 126 :return: a tuple with static "ok", "missing", or "failed" and the filename | |
| 127 :rtype: tuple(str, str) | |
| 128 | |
| 129 """ | |
| 130 # determine checkfile format (BSD or coreutils) | |
| 131 # BSD? | |
| 132 mo = re.search(r"\A(\S+)\s*\((.*)\)\s*=\s*(.+)\n?\Z", line) | |
| 133 if mo: | |
| 134 algo = algotag2algotype(mo.group(1)) | |
| 135 fn = mo.group(2) | |
| 136 digest = mo.group(3) | |
| 137 else: | |
| 138 mo = re.search(r"([^\ ]+) [\*\ ]?(.+)\n?\Z", line) | |
| 139 if mo: | |
| 140 algo = opts.algorithm[0] | |
| 141 fn = mo.group(2) | |
| 142 digest = mo.group(1) | |
| 143 else: | |
| 144 raise ValueError( | |
| 145 "improperly formatted digest line: {}".format(line)) | |
| 146 try: | |
| 147 with open(fn, "rb") as input: | |
| 148 d = compute_digest(algo, input) | |
| 149 if d.lower() == digest.lower(): | |
| 150 return ("ok", fn) | |
| 151 else: | |
| 152 return ("failed", fn) | |
| 153 except EnvironmentError: | |
| 154 return ("missing", fn) | |
| 90 | 155 |
| 91 | 156 |
| 92 def argv2algo(s): | 157 def argv2algo(s): |
| 93 """Convert a commane line algorithm specifier into a tuple with the | 158 """Convert a commane line algorithm specifier into a tuple with the |
| 94 type/factory of the digest and the algorithms tag for output purposes | 159 type/factory of the digest and the algorithms tag for output purposes. |
| 95 | 160 |
| 96 :param str s: the specifier from the commane line | 161 :param str s: the specifier from the commane line |
| 97 :return: the internal digest specification | 162 :return: the internal digest specification |
| 98 :rtype: a tuple (digest_type_or_factory, name_in_output) | 163 :rtype: a tuple (digest_type_or_factory, name_in_output) |
| 99 | 164 |
| 128 else: | 193 else: |
| 129 raise argparse.ArgumentTypeError( | 194 raise argparse.ArgumentTypeError( |
| 130 "`{}' is not a recognized algorithm".format(s)) | 195 "`{}' is not a recognized algorithm".format(s)) |
| 131 | 196 |
| 132 | 197 |
| 198 def algotag2algotype(s): | |
| 199 """Convert the algorithm specifier in a BSD-style digest file to the | |
| 200 type/factory of the corresponding algorithm. | |
| 201 | |
| 202 :param str s: the tag (i.e. normalized name) or the algorithm | |
| 203 :return: the digest type or factory for `s` | |
| 204 | |
| 205 All string comparisons are case-sensitive. | |
| 206 | |
| 207 """ | |
| 208 if s == "SHA1": | |
| 209 return hashlib.sha1 | |
| 210 elif s == "SHA224": | |
| 211 return hashlib.sha224 | |
| 212 elif s == "SHA256": | |
| 213 return hashlib.sha256 | |
| 214 elif s == "SHA384": | |
| 215 return hashlib.sha384 | |
| 216 elif s == "SHA512": | |
| 217 return hashlib.sha512 | |
| 218 elif s == "SHA3-224": | |
| 219 return hashlib.sha3_224 | |
| 220 elif s == "SHA3-256": | |
| 221 return hashlib.sha3_256 | |
| 222 elif s == "SHA3-384": | |
| 223 return hashlib.sha3_384 | |
| 224 elif s == "SHA3-512": | |
| 225 return hashlib.sha3_512 | |
| 226 elif s == "BLAKE2b": | |
| 227 return hashlib.blake2b | |
| 228 elif s == "BLAKE2s": | |
| 229 return hashlib.blake2s | |
| 230 elif s == "MD5": | |
| 231 return hashlib.md5 | |
| 232 else: | |
| 233 raise ValueError("unknown algorithm: {}".format(s)) | |
| 234 | |
| 235 | |
| 133 def out_bsd(dest, digest, filename, digestname, binary): | 236 def out_bsd(dest, digest, filename, digestname, binary): |
| 134 """BSD format output, also :command:`openssl dgst` and | 237 """BSD format output, also :command:`openssl dgst` and |
| 135 :command:`b2sum --tag" format output | 238 :command:`b2sum --tag" format output |
| 136 | 239 |
| 137 """ | 240 """ |
