Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/digest/__init__.py: 56%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

77 statements  

1#!/usr/bin/env python 

2# --------------------------------------------------------------------------- 

3 

4""" 

5Calculate a cryptohash on a file or standard input. 

6 

7The *digest* utility calculates message digests of files or, if no file 

8is specified, standard input. The set of supported digests depends on the 

9current Python interpreter and the version of OpenSSL present on the system. 

10However, at a minimum, *digest* supports the following algorithms: 

11 

12 +-------------+--------------------------------------+ 

13 | Argument | Algorithm | 

14 +=============+======================================+ 

15 | md5 | The MD5 algorithm | 

16 +-------------+--------------------------------------+ 

17 | sha1 | The SHA-1 algorithm | 

18 +-------------+--------------------------------------+ 

19 | sha224 | The SHA-224 algorithm | 

20 +-------------+--------------------------------------+ 

21 | sha256 | The SHA-256 algorithm | 

22 +-------------+--------------------------------------+ 

23 | sha384 | The SHA-384 algorithm | 

24 +-------------+--------------------------------------+ 

25 | sha512 | The SHA-512 algorithm | 

26 +-------------+--------------------------------------+ 

27 

28For usage information, the algorithms supported by your version of Python, 

29and other information, run: 

30 

31 digest --help 

32 

33For additional information, see the README (README.md) or visit 

34https://github.com/bmc/digest 

35""" 

36 

37from __future__ import print_function 

38 

39__docformat__ = "restructuredtext" 

40 

41# Info about the module 

42__version__ = "1.1.2" 

43__author__ = "Brian M. Clapper" 

44__email__ = "bmc@clapper.org" 

45__url__ = "http://software.clapper.org/digest/" 

46__copyright__ = "2008-2023 Brian M. Clapper" 

47__license__ = "Apache Software License Version 2.0" 

48 

49# Package stuff 

50 

51__all__ = ["digest", "main"] 

52 

53# --------------------------------------------------------------------------- 

54# Imports 

55# --------------------------------------------------------------------------- 

56 

57import argparse 

58import hashlib 

59import os 

60import sys 

61from dataclasses import dataclass 

62from typing import BinaryIO, NoReturn, Optional 

63from typing import Sequence as Seq 

64 

65# --------------------------------------------------------------------------- 

66# Constants 

67# --------------------------------------------------------------------------- 

68 

69ALGORITHMS = sorted(hashlib.algorithms_available) 

70BUFSIZE = 1024 * 16 

71DIGEST_LENGTH_REQUIRED = {"shake_128", "shake_256"} 

72 

73# --------------------------------------------------------------------------- 

74# Classes 

75# --------------------------------------------------------------------------- 

76 

77 

78@dataclass(frozen=True) 

79class Params: 

80 buffer_size: int 

81 digest_length: Optional[int] 

82 algorithm: str 

83 paths: Seq[str] 

84 

85 

86# --------------------------------------------------------------------------- 

87# Functions 

88# --------------------------------------------------------------------------- 

89 

90 

91def die(msg: str) -> NoReturn: 

92 print(msg, file=sys.stderr) 

93 sys.exit(1) 

94 

95 

96def parse_params() -> Params: 

97 def positive_number(s: str) -> int: 

98 n = int(s) 

99 if n <= 0: 

100 raise ValueError(f'"{s}" is not a positive number.') 

101 

102 return n 

103 

104 parser = argparse.ArgumentParser( 

105 description="Generate a message digest (cryptohash) of one or more " 

106 "files, or of standard input. Files are read as binary " 

107 "data, even if they're text files. Files are read " 

108 f"{BUFSIZE:,} bytes at a time, by default. Use -b to " 

109 "change that buffer size." 

110 ) 

111 parser.add_argument( 

112 "-b", 

113 "--bufsize", 

114 metavar="N", 

115 type=positive_number, 

116 default=BUFSIZE, 

117 help="Buffer size (in bytes) to use when reading. " 

118 "Defaults to %(default)d.", 

119 ) 

120 length_required = ", ".join(sorted(DIGEST_LENGTH_REQUIRED)) 

121 parser.add_argument( 

122 "-l", 

123 "--digest-length", 

124 type=positive_number, 

125 help="Length to use, for variable-length digests. " 

126 f"Required for: {length_required}", 

127 ) 

128 parser.add_argument( 

129 "-v", "--version", action="version", version=f"%(prog)s {__version__}" 

130 ) 

131 parser.add_argument( 

132 "algorithm", 

133 action="store", 

134 metavar="algorithm", 

135 choices=ALGORITHMS, 

136 help="The digest algorithm to use, one of: " + ", ".join(ALGORITHMS), 

137 ) 

138 parser.add_argument( 

139 "path", 

140 action="store", 

141 nargs="*", 

142 help="Input file(s) to process. If not specified, " 

143 "standard input is read.", 

144 ) 

145 

146 args = parser.parse_args() 

147 if (args.algorithm in DIGEST_LENGTH_REQUIRED) and ( 

148 args.digest_length is None 

149 ): 

150 die( 

151 f"Digest algorithm {args.algorithm} requires that you specify a " 

152 "digest length via -l or --digest-length." 

153 ) 

154 elif (args.algorithm not in DIGEST_LENGTH_REQUIRED) and ( 

155 args.digest_length is not None 

156 ): 

157 print( 

158 f"WARNING: Digest length (-l) is ignored for {args.algorithm}.", 

159 file=sys.stderr, 

160 ) 

161 args.digest_length = None 

162 

163 return Params( 

164 buffer_size=args.bufsize, 

165 digest_length=args.digest_length, 

166 algorithm=args.algorithm, 

167 paths=args.path, 

168 ) 

169 

170 

171def digest( 

172 f: BinaryIO, 

173 algorithm: str, 

174 bufsize: int, 

175 digest_length: Optional[int] = None, 

176) -> str: 

177 try: 

178 h = hashlib.new(algorithm) 

179 except ValueError as ex: 

180 die("%s: %s" % (algorithm, str(ex))) 

181 

182 buf = bytearray(bufsize) 

183 while True: 

184 # Pyright can't grok BinaryIO.readinto(), so just disable it for 

185 # this line. 

186 n = f.readinto(buf) # pyright: ignore 

187 if n <= 0: 

188 break 

189 h.update(buf[:n]) 

190 

191 # Some algorithms (e.g., the SHAKE algorithms) are variable length, and 

192 # their hexdigest() functions take a length parameter. But the generic 

193 # hexdigest() function doesn't, and the typing doesn't capture this 

194 # difference. So, type-checkers like pyright complain about the first 

195 # call, below. For now, we just disable pyright for that line. 

196 if digest_length is not None: 

197 return h.hexdigest(digest_length) # pyright: ignore 

198 else: 

199 return h.hexdigest() 

200 

201 

202def main(): 

203 params: Params = parse_params() 

204 

205 if len(params.paths) == 0: 

206 # Standard input. 

207 print( 

208 digest( 

209 f=sys.stdin.buffer, 

210 algorithm=params.algorithm, 

211 bufsize=params.buffer_size, 

212 digest_length=params.digest_length, 

213 ) 

214 ) 

215 

216 else: 

217 u_algorithm = params.algorithm.upper() 

218 for path in params.paths: 

219 if not os.path.isfile(path): 

220 print(f'*** Skipping non-file "{path}".') 

221 continue 

222 

223 with open(path, mode="rb") as f: 

224 d = digest( 

225 f=f, 

226 algorithm=params.algorithm, 

227 bufsize=params.buffer_size, 

228 digest_length=params.digest_length, 

229 ) 

230 print(f"{u_algorithm} ({path}): {d}") 

231 

232 return 0 

233 

234 

235# --------------------------------------------------------------------------- 

236# Main 

237# --------------------------------------------------------------------------- 

238 

239if __name__ == "__main__": 

240 sys.exit(main())