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  }