github.com/ncw/rclone@v1.48.1-0.20190724201158-a35aa1360e3e/fs/hash/hash.go (about)

     1  package hash
     2  
     3  import (
     4  	"crypto/md5"
     5  	"crypto/sha1"
     6  	"encoding/hex"
     7  	"fmt"
     8  	"hash"
     9  	"io"
    10  	"strings"
    11  
    12  	"github.com/jzelinskie/whirlpool"
    13  	"github.com/ncw/rclone/backend/dropbox/dbhash"
    14  	"github.com/ncw/rclone/backend/onedrive/quickxorhash"
    15  	"github.com/pkg/errors"
    16  )
    17  
    18  // Type indicates a standard hashing algorithm
    19  type Type int
    20  
    21  // ErrUnsupported should be returned by filesystem,
    22  // if it is requested to deliver an unsupported hash type.
    23  var ErrUnsupported = errors.New("hash type not supported")
    24  
    25  const (
    26  	// MD5 indicates MD5 support
    27  	MD5 Type = 1 << iota
    28  
    29  	// SHA1 indicates SHA-1 support
    30  	SHA1
    31  
    32  	// Dropbox indicates Dropbox special hash
    33  	// https://www.dropbox.com/developers/reference/content-hash
    34  	Dropbox
    35  
    36  	// QuickXorHash indicates Microsoft onedrive hash
    37  	// https://docs.microsoft.com/en-us/onedrive/developer/code-snippets/quickxorhash
    38  	QuickXorHash
    39  
    40  	// Whirlpool indicates Whirlpool support
    41  	Whirlpool
    42  
    43  	// None indicates no hashes are supported
    44  	None Type = 0
    45  )
    46  
    47  // Supported returns a set of all the supported hashes by
    48  // HashStream and MultiHasher.
    49  var Supported = NewHashSet(MD5, SHA1, Dropbox, QuickXorHash, Whirlpool)
    50  
    51  // Width returns the width in characters for any HashType
    52  var Width = map[Type]int{
    53  	MD5:          32,
    54  	SHA1:         40,
    55  	Dropbox:      64,
    56  	QuickXorHash: 40,
    57  	Whirlpool:    128,
    58  }
    59  
    60  // Stream will calculate hashes of all supported hash types.
    61  func Stream(r io.Reader) (map[Type]string, error) {
    62  	return StreamTypes(r, Supported)
    63  }
    64  
    65  // StreamTypes will calculate hashes of the requested hash types.
    66  func StreamTypes(r io.Reader, set Set) (map[Type]string, error) {
    67  	hashers, err := fromTypes(set)
    68  	if err != nil {
    69  		return nil, err
    70  	}
    71  
    72  	_, err = io.Copy(toMultiWriter(hashers), r)
    73  	if err != nil {
    74  		return nil, err
    75  	}
    76  	var ret = make(map[Type]string)
    77  	for k, v := range hashers {
    78  		ret[k] = hex.EncodeToString(v.Sum(nil))
    79  	}
    80  	return ret, nil
    81  }
    82  
    83  // String returns a string representation of the hash type.
    84  // The function will panic if the hash type is unknown.
    85  func (h Type) String() string {
    86  	switch h {
    87  	case None:
    88  		return "None"
    89  	case MD5:
    90  		return "MD5"
    91  	case SHA1:
    92  		return "SHA-1"
    93  	case Dropbox:
    94  		return "DropboxHash"
    95  	case QuickXorHash:
    96  		return "QuickXorHash"
    97  	case Whirlpool:
    98  		return "Whirlpool"
    99  	default:
   100  		err := fmt.Sprintf("internal error: unknown hash type: 0x%x", int(h))
   101  		panic(err)
   102  	}
   103  }
   104  
   105  // Set a Type from a flag
   106  func (h *Type) Set(s string) error {
   107  	switch s {
   108  	case "None":
   109  		*h = None
   110  	case "MD5":
   111  		*h = MD5
   112  	case "SHA-1":
   113  		*h = SHA1
   114  	case "DropboxHash":
   115  		*h = Dropbox
   116  	case "QuickXorHash":
   117  		*h = QuickXorHash
   118  	case "Whirlpool":
   119  		*h = Whirlpool
   120  	default:
   121  		return errors.Errorf("Unknown hash type %q", s)
   122  	}
   123  	return nil
   124  }
   125  
   126  // Type of the value
   127  func (h Type) Type() string {
   128  	return "string"
   129  }
   130  
   131  // fromTypes will return hashers for all the requested types.
   132  // The types must be a subset of SupportedHashes,
   133  // and this function must support all types.
   134  func fromTypes(set Set) (map[Type]hash.Hash, error) {
   135  	if !set.SubsetOf(Supported) {
   136  		return nil, errors.Errorf("requested set %08x contains unknown hash types", int(set))
   137  	}
   138  	var hashers = make(map[Type]hash.Hash)
   139  	types := set.Array()
   140  	for _, t := range types {
   141  		switch t {
   142  		case MD5:
   143  			hashers[t] = md5.New()
   144  		case SHA1:
   145  			hashers[t] = sha1.New()
   146  		case Dropbox:
   147  			hashers[t] = dbhash.New()
   148  		case QuickXorHash:
   149  			hashers[t] = quickxorhash.New()
   150  		case Whirlpool:
   151  			hashers[t] = whirlpool.New()
   152  		default:
   153  			err := fmt.Sprintf("internal error: Unsupported hash type %v", t)
   154  			panic(err)
   155  		}
   156  	}
   157  	return hashers, nil
   158  }
   159  
   160  // toMultiWriter will return a set of hashers into a
   161  // single multiwriter, where one write will update all
   162  // the hashers.
   163  func toMultiWriter(h map[Type]hash.Hash) io.Writer {
   164  	// Convert to to slice
   165  	var w = make([]io.Writer, 0, len(h))
   166  	for _, v := range h {
   167  		w = append(w, v)
   168  	}
   169  	return io.MultiWriter(w...)
   170  }
   171  
   172  // A MultiHasher will construct various hashes on
   173  // all incoming writes.
   174  type MultiHasher struct {
   175  	w    io.Writer
   176  	size int64
   177  	h    map[Type]hash.Hash // Hashes
   178  }
   179  
   180  // NewMultiHasher will return a hash writer that will write all
   181  // supported hash types.
   182  func NewMultiHasher() *MultiHasher {
   183  	h, err := NewMultiHasherTypes(Supported)
   184  	if err != nil {
   185  		panic("internal error: could not create multihasher")
   186  	}
   187  	return h
   188  }
   189  
   190  // NewMultiHasherTypes will return a hash writer that will write
   191  // the requested hash types.
   192  func NewMultiHasherTypes(set Set) (*MultiHasher, error) {
   193  	hashers, err := fromTypes(set)
   194  	if err != nil {
   195  		return nil, err
   196  	}
   197  	m := MultiHasher{h: hashers, w: toMultiWriter(hashers)}
   198  	return &m, nil
   199  }
   200  
   201  func (m *MultiHasher) Write(p []byte) (n int, err error) {
   202  	n, err = m.w.Write(p)
   203  	m.size += int64(n)
   204  	return n, err
   205  }
   206  
   207  // Sums returns the sums of all accumulated hashes as hex encoded
   208  // strings.
   209  func (m *MultiHasher) Sums() map[Type]string {
   210  	dst := make(map[Type]string)
   211  	for k, v := range m.h {
   212  		dst[k] = hex.EncodeToString(v.Sum(nil))
   213  	}
   214  	return dst
   215  }
   216  
   217  // Size returns the number of bytes written
   218  func (m *MultiHasher) Size() int64 {
   219  	return m.size
   220  }
   221  
   222  // A Set Indicates one or more hash types.
   223  type Set int
   224  
   225  // NewHashSet will create a new hash set with the hash types supplied
   226  func NewHashSet(t ...Type) Set {
   227  	h := Set(None)
   228  	return h.Add(t...)
   229  }
   230  
   231  // Add one or more hash types to the set.
   232  // Returns the modified hash set.
   233  func (h *Set) Add(t ...Type) Set {
   234  	for _, v := range t {
   235  		*h |= Set(v)
   236  	}
   237  	return *h
   238  }
   239  
   240  // Contains returns true if the
   241  func (h Set) Contains(t Type) bool {
   242  	return int(h)&int(t) != 0
   243  }
   244  
   245  // Overlap returns the overlapping hash types
   246  func (h Set) Overlap(t Set) Set {
   247  	return Set(int(h) & int(t))
   248  }
   249  
   250  // SubsetOf will return true if all types of h
   251  // is present in the set c
   252  func (h Set) SubsetOf(c Set) bool {
   253  	return int(h)|int(c) == int(c)
   254  }
   255  
   256  // GetOne will return a hash type.
   257  // Currently the first is returned, but it could be
   258  // improved to return the strongest.
   259  func (h Set) GetOne() Type {
   260  	v := int(h)
   261  	i := uint(0)
   262  	for v != 0 {
   263  		if v&1 != 0 {
   264  			return Type(1 << i)
   265  		}
   266  		i++
   267  		v >>= 1
   268  	}
   269  	return None
   270  }
   271  
   272  // Array returns an array of all hash types in the set
   273  func (h Set) Array() (ht []Type) {
   274  	v := int(h)
   275  	i := uint(0)
   276  	for v != 0 {
   277  		if v&1 != 0 {
   278  			ht = append(ht, Type(1<<i))
   279  		}
   280  		i++
   281  		v >>= 1
   282  	}
   283  	return ht
   284  }
   285  
   286  // Count returns the number of hash types in the set
   287  func (h Set) Count() int {
   288  	if int(h) == 0 {
   289  		return 0
   290  	}
   291  	// credit: https://code.google.com/u/arnehormann/
   292  	x := uint64(h)
   293  	x -= (x >> 1) & 0x5555555555555555
   294  	x = (x>>2)&0x3333333333333333 + x&0x3333333333333333
   295  	x += x >> 4
   296  	x &= 0x0f0f0f0f0f0f0f0f
   297  	x *= 0x0101010101010101
   298  	return int(x >> 56)
   299  }
   300  
   301  // String returns a string representation of the hash set.
   302  // The function will panic if it contains an unknown type.
   303  func (h Set) String() string {
   304  	a := h.Array()
   305  	var r []string
   306  	for _, v := range a {
   307  		r = append(r, v.String())
   308  	}
   309  	return "[" + strings.Join(r, ", ") + "]"
   310  }
   311  
   312  // Equals checks to see if src == dst, but ignores empty strings
   313  // and returns true if either is empty.
   314  func Equals(src, dst string) bool {
   315  	if src == "" || dst == "" {
   316  		return true
   317  	}
   318  	return src == dst
   319  }