github.com/rclone/rclone@v1.66.1-0.20240517100346-7b89735ae726/fs/hash/hash.go (about) 1 // Package hash provides hash utilities for Fs. 2 package hash 3 4 import ( 5 "crypto/md5" 6 "crypto/sha1" 7 "crypto/sha256" 8 "encoding/base64" 9 "encoding/hex" 10 "errors" 11 "fmt" 12 "hash" 13 "hash/crc32" 14 "io" 15 "strings" 16 17 "github.com/jzelinskie/whirlpool" 18 ) 19 20 // Type indicates a standard hashing algorithm 21 type Type int 22 23 type hashDefinition struct { 24 width int 25 name string 26 alias string 27 newFunc func() hash.Hash 28 hashType Type 29 } 30 31 var ( 32 type2hash = map[Type]*hashDefinition{} 33 name2hash = map[string]*hashDefinition{} 34 alias2hash = map[string]*hashDefinition{} 35 supported = []Type{} 36 ) 37 38 // RegisterHash adds a new Hash to the list and returns it Type 39 func RegisterHash(name, alias string, width int, newFunc func() hash.Hash) Type { 40 hashType := Type(1 << len(supported)) 41 supported = append(supported, hashType) 42 43 definition := &hashDefinition{ 44 name: name, 45 alias: alias, 46 width: width, 47 newFunc: newFunc, 48 hashType: hashType, 49 } 50 51 type2hash[hashType] = definition 52 name2hash[name] = definition 53 alias2hash[alias] = definition 54 55 return hashType 56 } 57 58 // SupportOnly makes the hash package only support the types passed 59 // in. Used for testing. 60 // 61 // It returns the previously supported types. 62 func SupportOnly(new []Type) (old []Type) { 63 old = supported 64 supported = new 65 return old 66 } 67 68 // ErrUnsupported should be returned by filesystem, 69 // if it is requested to deliver an unsupported hash type. 70 var ErrUnsupported = errors.New("hash type not supported") 71 72 var ( 73 // None indicates no hashes are supported 74 None Type 75 76 // MD5 indicates MD5 support 77 MD5 Type 78 79 // SHA1 indicates SHA-1 support 80 SHA1 Type 81 82 // Whirlpool indicates Whirlpool support 83 Whirlpool Type 84 85 // CRC32 indicates CRC-32 support 86 CRC32 Type 87 88 // SHA256 indicates SHA-256 support 89 SHA256 Type 90 ) 91 92 func init() { 93 MD5 = RegisterHash("md5", "MD5", 32, md5.New) 94 SHA1 = RegisterHash("sha1", "SHA-1", 40, sha1.New) 95 Whirlpool = RegisterHash("whirlpool", "Whirlpool", 128, whirlpool.New) 96 CRC32 = RegisterHash("crc32", "CRC-32", 8, func() hash.Hash { return crc32.NewIEEE() }) 97 SHA256 = RegisterHash("sha256", "SHA-256", 64, sha256.New) 98 } 99 100 // Supported returns a set of all the supported hashes by 101 // HashStream and MultiHasher. 102 func Supported() Set { 103 return NewHashSet(supported...) 104 } 105 106 // Width returns the width in characters for any HashType 107 func Width(hashType Type, base64Encoded bool) int { 108 if hash := type2hash[hashType]; hash != nil { 109 if base64Encoded { 110 return base64.URLEncoding.EncodedLen(hash.width / 2) 111 } 112 return hash.width 113 } 114 return 0 115 } 116 117 // Stream will calculate hashes of all supported hash types. 118 func Stream(r io.Reader) (map[Type]string, error) { 119 return StreamTypes(r, Supported()) 120 } 121 122 // StreamTypes will calculate hashes of the requested hash types. 123 func StreamTypes(r io.Reader, set Set) (map[Type]string, error) { 124 hashers, err := fromTypes(set) 125 if err != nil { 126 return nil, err 127 } 128 129 _, err = io.Copy(toMultiWriter(hashers), r) 130 if err != nil { 131 return nil, err 132 } 133 var ret = make(map[Type]string) 134 for k, v := range hashers { 135 ret[k] = hex.EncodeToString(v.Sum(nil)) 136 } 137 return ret, nil 138 } 139 140 // String returns a string representation of the hash type. 141 // The function will panic if the hash type is unknown. 142 func (h Type) String() string { 143 if h == None { 144 return "none" 145 } 146 if hash := type2hash[h]; hash != nil { 147 return hash.name 148 } 149 panic(fmt.Sprintf("internal error: unknown hash type: 0x%x", int(h))) 150 } 151 152 // Set a Type from a flag. 153 // Both name and alias are accepted. 154 func (h *Type) Set(s string) error { 155 if s == "none" || s == "None" { 156 *h = None 157 return nil 158 } 159 if hash := name2hash[strings.ToLower(s)]; hash != nil { 160 *h = hash.hashType 161 return nil 162 } 163 if hash := alias2hash[s]; hash != nil { 164 *h = hash.hashType 165 return nil 166 } 167 return fmt.Errorf("unknown hash type %q", s) 168 } 169 170 // Type of the value 171 func (h Type) Type() string { 172 return "string" 173 } 174 175 // fromTypes will return hashers for all the requested types. 176 // The types must be a subset of SupportedHashes, 177 // and this function must support all types. 178 func fromTypes(set Set) (map[Type]hash.Hash, error) { 179 if !set.SubsetOf(Supported()) { 180 return nil, fmt.Errorf("requested set %08x contains unknown hash types", int(set)) 181 } 182 hashers := map[Type]hash.Hash{} 183 184 for _, t := range set.Array() { 185 hash := type2hash[t] 186 if hash == nil { 187 panic(fmt.Sprintf("internal error: Unsupported hash type %v", t)) 188 } 189 hashers[t] = hash.newFunc() 190 } 191 192 return hashers, nil 193 } 194 195 // toMultiWriter will return a set of hashers into a 196 // single multiwriter, where one write will update all 197 // the hashers. 198 func toMultiWriter(h map[Type]hash.Hash) io.Writer { 199 // Convert to to slice 200 var w = make([]io.Writer, 0, len(h)) 201 for _, v := range h { 202 w = append(w, v) 203 } 204 return io.MultiWriter(w...) 205 } 206 207 // A MultiHasher will construct various hashes on 208 // all incoming writes. 209 type MultiHasher struct { 210 w io.Writer 211 size int64 212 h map[Type]hash.Hash // Hashes 213 } 214 215 // NewMultiHasher will return a hash writer that will write all 216 // supported hash types. 217 func NewMultiHasher() *MultiHasher { 218 h, err := NewMultiHasherTypes(Supported()) 219 if err != nil { 220 panic("internal error: could not create multihasher") 221 } 222 return h 223 } 224 225 // NewMultiHasherTypes will return a hash writer that will write 226 // the requested hash types. 227 func NewMultiHasherTypes(set Set) (*MultiHasher, error) { 228 hashers, err := fromTypes(set) 229 if err != nil { 230 return nil, err 231 } 232 m := MultiHasher{h: hashers, w: toMultiWriter(hashers)} 233 return &m, nil 234 } 235 236 func (m *MultiHasher) Write(p []byte) (n int, err error) { 237 n, err = m.w.Write(p) 238 m.size += int64(n) 239 return n, err 240 } 241 242 // Sums returns the sums of all accumulated hashes as hex encoded 243 // strings. 244 func (m *MultiHasher) Sums() map[Type]string { 245 dst := make(map[Type]string) 246 for k, v := range m.h { 247 dst[k] = hex.EncodeToString(v.Sum(nil)) 248 } 249 return dst 250 } 251 252 // Sum returns the specified hash from the multihasher 253 func (m *MultiHasher) Sum(hashType Type) ([]byte, error) { 254 h, ok := m.h[hashType] 255 if !ok { 256 return nil, ErrUnsupported 257 } 258 return h.Sum(nil), nil 259 } 260 261 // SumString returns the specified hash from the multihasher as a hex or base64 encoded string 262 func (m *MultiHasher) SumString(hashType Type, base64Encoded bool) (string, error) { 263 sum, err := m.Sum(hashType) 264 if err != nil { 265 return "", err 266 } 267 if base64Encoded { 268 return base64.URLEncoding.EncodeToString(sum), nil 269 } 270 return hex.EncodeToString(sum), nil 271 } 272 273 // Size returns the number of bytes written 274 func (m *MultiHasher) Size() int64 { 275 return m.size 276 } 277 278 // A Set Indicates one or more hash types. 279 type Set int 280 281 // NewHashSet will create a new hash set with the hash types supplied 282 func NewHashSet(t ...Type) Set { 283 h := Set(None) 284 return h.Add(t...) 285 } 286 287 // Add one or more hash types to the set. 288 // Returns the modified hash set. 289 func (h *Set) Add(t ...Type) Set { 290 for _, v := range t { 291 *h |= Set(v) 292 } 293 return *h 294 } 295 296 // Contains returns true if the 297 func (h Set) Contains(t Type) bool { 298 return int(h)&int(t) != 0 299 } 300 301 // Overlap returns the overlapping hash types 302 func (h Set) Overlap(t Set) Set { 303 return Set(int(h) & int(t)) 304 } 305 306 // SubsetOf will return true if all types of h 307 // is present in the set c 308 func (h Set) SubsetOf(c Set) bool { 309 return int(h)|int(c) == int(c) 310 } 311 312 // GetOne will return a hash type. 313 // Currently the first is returned, but it could be 314 // improved to return the strongest. 315 func (h Set) GetOne() Type { 316 v := int(h) 317 i := uint(0) 318 for v != 0 { 319 if v&1 != 0 { 320 return Type(1 << i) 321 } 322 i++ 323 v >>= 1 324 } 325 return None 326 } 327 328 // Array returns an array of all hash types in the set 329 func (h Set) Array() (ht []Type) { 330 v := int(h) 331 i := uint(0) 332 for v != 0 { 333 if v&1 != 0 { 334 ht = append(ht, Type(1<<i)) 335 } 336 i++ 337 v >>= 1 338 } 339 return ht 340 } 341 342 // Count returns the number of hash types in the set 343 func (h Set) Count() int { 344 if int(h) == 0 { 345 return 0 346 } 347 // credit: https://code.google.com/u/arnehormann/ 348 x := uint64(h) 349 x -= (x >> 1) & 0x5555555555555555 350 x = (x>>2)&0x3333333333333333 + x&0x3333333333333333 351 x += x >> 4 352 x &= 0x0f0f0f0f0f0f0f0f 353 x *= 0x0101010101010101 354 return int(x >> 56) 355 } 356 357 // String returns a string representation of the hash set. 358 // The function will panic if it contains an unknown type. 359 func (h Set) String() string { 360 a := h.Array() 361 var r []string 362 for _, v := range a { 363 r = append(r, v.String()) 364 } 365 return "[" + strings.Join(r, ", ") + "]" 366 } 367 368 // Equals checks to see if src == dst, but ignores empty strings 369 // and returns true if either is empty. 370 func Equals(src, dst string) bool { 371 if src == "" || dst == "" { 372 return true 373 } 374 return src == dst 375 } 376 377 // HelpString returns help message with supported hashes 378 func HelpString(indent int) string { 379 padding := strings.Repeat(" ", indent) 380 var help strings.Builder 381 help.WriteString(padding) 382 help.WriteString("Supported hashes are:\n") 383 for _, h := range supported { 384 fmt.Fprintf(&help, "%s * %v\n", padding, h.String()) 385 } 386 return help.String() 387 }