2016-01-11 13:39:33 +01:00
|
|
|
package fs
|
|
|
|
|
|
|
|
import (
|
|
|
|
"crypto/md5"
|
|
|
|
"crypto/sha1"
|
|
|
|
"encoding/hex"
|
|
|
|
"fmt"
|
|
|
|
"hash"
|
|
|
|
"io"
|
|
|
|
"strings"
|
2016-06-12 16:06:02 +02:00
|
|
|
|
2017-05-26 16:09:31 +02:00
|
|
|
"github.com/ncw/rclone/dropbox/dbhash"
|
2016-06-12 16:06:02 +02:00
|
|
|
"github.com/pkg/errors"
|
2016-01-11 13:39:33 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
// HashType indicates a standard hashing algorithm
|
|
|
|
type HashType int
|
|
|
|
|
|
|
|
// ErrHashUnsupported should be returned by filesystem,
|
|
|
|
// if it is requested to deliver an unsupported hash type.
|
2016-06-12 16:06:02 +02:00
|
|
|
var ErrHashUnsupported = errors.New("hash type not supported")
|
2016-01-11 13:39:33 +01:00
|
|
|
|
|
|
|
const (
|
|
|
|
// HashMD5 indicates MD5 support
|
|
|
|
HashMD5 HashType = 1 << iota
|
|
|
|
|
|
|
|
// HashSHA1 indicates SHA-1 support
|
|
|
|
HashSHA1
|
2016-01-17 11:33:40 +01:00
|
|
|
|
2017-05-26 16:09:31 +02:00
|
|
|
// HashDropbox indicates Dropbox special hash
|
|
|
|
// https://www.dropbox.com/developers/reference/content-hash
|
|
|
|
HashDropbox
|
|
|
|
|
2016-01-17 11:33:40 +01:00
|
|
|
// HashNone indicates no hashes are supported
|
|
|
|
HashNone HashType = 0
|
2016-01-11 13:39:33 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
// SupportedHashes returns a set of all the supported hashes by
|
|
|
|
// HashStream and MultiHasher.
|
2017-05-26 16:09:31 +02:00
|
|
|
var SupportedHashes = NewHashSet(HashMD5, HashSHA1, HashDropbox)
|
2016-01-11 13:39:33 +01:00
|
|
|
|
2016-01-17 14:56:00 +01:00
|
|
|
// HashWidth returns the width in characters for any HashType
|
|
|
|
var HashWidth = map[HashType]int{
|
2017-05-26 16:09:31 +02:00
|
|
|
HashMD5: 32,
|
|
|
|
HashSHA1: 40,
|
|
|
|
HashDropbox: 64,
|
2016-01-17 14:56:00 +01:00
|
|
|
}
|
|
|
|
|
2016-01-11 13:39:33 +01:00
|
|
|
// HashStream will calculate hashes of all supported hash types.
|
|
|
|
func HashStream(r io.Reader) (map[HashType]string, error) {
|
|
|
|
return HashStreamTypes(r, SupportedHashes)
|
|
|
|
}
|
|
|
|
|
|
|
|
// HashStreamTypes will calculate hashes of the requested hash types.
|
|
|
|
func HashStreamTypes(r io.Reader, set HashSet) (map[HashType]string, error) {
|
|
|
|
hashers, err := hashFromTypes(set)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = io.Copy(hashToMultiWriter(hashers), r)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
var ret = make(map[HashType]string)
|
|
|
|
for k, v := range hashers {
|
|
|
|
ret[k] = hex.EncodeToString(v.Sum(nil))
|
|
|
|
}
|
|
|
|
return ret, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// String returns a string representation of the hash type.
|
|
|
|
// The function will panic if the hash type is unknown.
|
|
|
|
func (h HashType) String() string {
|
|
|
|
switch h {
|
|
|
|
case HashNone:
|
|
|
|
return "None"
|
|
|
|
case HashMD5:
|
|
|
|
return "MD5"
|
|
|
|
case HashSHA1:
|
|
|
|
return "SHA-1"
|
2017-05-26 16:09:31 +02:00
|
|
|
case HashDropbox:
|
|
|
|
return "DropboxHash"
|
2016-01-11 13:39:33 +01:00
|
|
|
default:
|
|
|
|
err := fmt.Sprintf("internal error: unknown hash type: 0x%x", int(h))
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// hashFromTypes will return hashers for all the requested types.
|
|
|
|
// The types must be a subset of SupportedHashes,
|
|
|
|
// and this function must support all types.
|
|
|
|
func hashFromTypes(set HashSet) (map[HashType]hash.Hash, error) {
|
|
|
|
if !set.SubsetOf(SupportedHashes) {
|
2016-06-12 16:06:02 +02:00
|
|
|
return nil, errors.Errorf("requested set %08x contains unknown hash types", int(set))
|
2016-01-11 13:39:33 +01:00
|
|
|
}
|
|
|
|
var hashers = make(map[HashType]hash.Hash)
|
|
|
|
types := set.Array()
|
|
|
|
for _, t := range types {
|
|
|
|
switch t {
|
|
|
|
case HashMD5:
|
|
|
|
hashers[t] = md5.New()
|
|
|
|
case HashSHA1:
|
|
|
|
hashers[t] = sha1.New()
|
2017-05-26 16:09:31 +02:00
|
|
|
case HashDropbox:
|
|
|
|
hashers[t] = dbhash.New()
|
2016-01-11 13:39:33 +01:00
|
|
|
default:
|
|
|
|
err := fmt.Sprintf("internal error: Unsupported hash type %v", t)
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return hashers, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// hashToMultiWriter will return a set of hashers into a
|
|
|
|
// single multiwriter, where one write will update all
|
|
|
|
// the hashers.
|
|
|
|
func hashToMultiWriter(h map[HashType]hash.Hash) io.Writer {
|
|
|
|
// Convert to to slice
|
|
|
|
var w = make([]io.Writer, 0, len(h))
|
|
|
|
for _, v := range h {
|
|
|
|
w = append(w, v)
|
|
|
|
}
|
|
|
|
return io.MultiWriter(w...)
|
|
|
|
}
|
|
|
|
|
|
|
|
// A MultiHasher will construct various hashes on
|
|
|
|
// all incoming writes.
|
|
|
|
type MultiHasher struct {
|
2016-06-19 14:49:34 +02:00
|
|
|
w io.Writer
|
|
|
|
size int64
|
|
|
|
h map[HashType]hash.Hash // Hashes
|
2016-01-11 13:39:33 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewMultiHasher will return a hash writer that will write all
|
|
|
|
// supported hash types.
|
|
|
|
func NewMultiHasher() *MultiHasher {
|
|
|
|
h, err := NewMultiHasherTypes(SupportedHashes)
|
|
|
|
if err != nil {
|
|
|
|
panic("internal error: could not create multihasher")
|
|
|
|
}
|
|
|
|
return h
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewMultiHasherTypes will return a hash writer that will write
|
|
|
|
// the requested hash types.
|
|
|
|
func NewMultiHasherTypes(set HashSet) (*MultiHasher, error) {
|
|
|
|
hashers, err := hashFromTypes(set)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2016-06-19 14:49:34 +02:00
|
|
|
m := MultiHasher{h: hashers, w: hashToMultiWriter(hashers)}
|
2016-01-11 13:39:33 +01:00
|
|
|
return &m, nil
|
|
|
|
}
|
|
|
|
|
2016-06-19 14:49:34 +02:00
|
|
|
func (m *MultiHasher) Write(p []byte) (n int, err error) {
|
|
|
|
n, err = m.w.Write(p)
|
|
|
|
m.size += int64(n)
|
|
|
|
return n, err
|
|
|
|
}
|
|
|
|
|
2016-01-11 13:39:33 +01:00
|
|
|
// Sums returns the sums of all accumulated hashes as hex encoded
|
|
|
|
// strings.
|
|
|
|
func (m *MultiHasher) Sums() map[HashType]string {
|
|
|
|
dst := make(map[HashType]string)
|
|
|
|
for k, v := range m.h {
|
|
|
|
dst[k] = hex.EncodeToString(v.Sum(nil))
|
|
|
|
}
|
|
|
|
return dst
|
|
|
|
}
|
|
|
|
|
2016-06-19 14:49:34 +02:00
|
|
|
// Size returns the number of bytes written
|
|
|
|
func (m *MultiHasher) Size() int64 {
|
|
|
|
return m.size
|
|
|
|
}
|
|
|
|
|
2016-01-11 13:39:33 +01:00
|
|
|
// A HashSet Indicates one or more hash types.
|
|
|
|
type HashSet int
|
|
|
|
|
|
|
|
// NewHashSet will create a new hash set with the hash types supplied
|
|
|
|
func NewHashSet(t ...HashType) HashSet {
|
|
|
|
h := HashSet(HashNone)
|
|
|
|
return h.Add(t...)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add one or more hash types to the set.
|
|
|
|
// Returns the modified hash set.
|
|
|
|
func (h *HashSet) Add(t ...HashType) HashSet {
|
|
|
|
for _, v := range t {
|
|
|
|
*h |= HashSet(v)
|
|
|
|
}
|
|
|
|
return *h
|
|
|
|
}
|
|
|
|
|
|
|
|
// Contains returns true if the
|
|
|
|
func (h HashSet) Contains(t HashType) bool {
|
|
|
|
return int(h)&int(t) != 0
|
|
|
|
}
|
|
|
|
|
|
|
|
// Overlap returns the overlapping hash types
|
|
|
|
func (h HashSet) Overlap(t HashSet) HashSet {
|
|
|
|
return HashSet(int(h) & int(t))
|
|
|
|
}
|
|
|
|
|
|
|
|
// SubsetOf will return true if all types of h
|
|
|
|
// is present in the set c
|
|
|
|
func (h HashSet) SubsetOf(c HashSet) bool {
|
|
|
|
return int(h)|int(c) == int(c)
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetOne will return a hash type.
|
|
|
|
// Currently the first is returned, but it could be
|
|
|
|
// improved to return the strongest.
|
|
|
|
func (h HashSet) GetOne() HashType {
|
|
|
|
v := int(h)
|
|
|
|
i := uint(0)
|
|
|
|
for v != 0 {
|
|
|
|
if v&1 != 0 {
|
|
|
|
return HashType(1 << i)
|
|
|
|
}
|
|
|
|
i++
|
|
|
|
v >>= 1
|
|
|
|
}
|
|
|
|
return HashType(HashNone)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Array returns an array of all hash types in the set
|
|
|
|
func (h HashSet) Array() (ht []HashType) {
|
|
|
|
v := int(h)
|
|
|
|
i := uint(0)
|
|
|
|
for v != 0 {
|
|
|
|
if v&1 != 0 {
|
|
|
|
ht = append(ht, HashType(1<<i))
|
|
|
|
}
|
|
|
|
i++
|
|
|
|
v >>= 1
|
|
|
|
}
|
|
|
|
return ht
|
|
|
|
}
|
|
|
|
|
|
|
|
// Count returns the number of hash types in the set
|
|
|
|
func (h HashSet) Count() int {
|
|
|
|
if int(h) == 0 {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
// credit: https://code.google.com/u/arnehormann/
|
|
|
|
x := uint64(h)
|
|
|
|
x -= (x >> 1) & 0x5555555555555555
|
|
|
|
x = (x>>2)&0x3333333333333333 + x&0x3333333333333333
|
|
|
|
x += x >> 4
|
|
|
|
x &= 0x0f0f0f0f0f0f0f0f
|
|
|
|
x *= 0x0101010101010101
|
|
|
|
return int(x >> 56)
|
|
|
|
}
|
|
|
|
|
|
|
|
// String returns a string representation of the hash set.
|
|
|
|
// The function will panic if it contains an unknown type.
|
|
|
|
func (h HashSet) String() string {
|
|
|
|
a := h.Array()
|
|
|
|
var r []string
|
|
|
|
for _, v := range a {
|
|
|
|
r = append(r, v.String())
|
|
|
|
}
|
|
|
|
return "[" + strings.Join(r, ", ") + "]"
|
|
|
|
}
|