hanfish's picture
111
215df2f verified
raw
history blame contribute delete
No virus
5.43 kB
package utils
import (
"crypto/md5"
"crypto/sha1"
"crypto/sha256"
"encoding"
"encoding/hex"
"encoding/json"
"errors"
"hash"
"io"
"github.com/alist-org/alist/v3/internal/errs"
log "github.com/sirupsen/logrus"
)
func GetMD5EncodeStr(data string) string {
return HashData(MD5, []byte(data))
}
//inspired by "github.com/rclone/rclone/fs/hash"
// ErrUnsupported should be returned by filesystem,
// if it is requested to deliver an unsupported hash type.
var ErrUnsupported = errors.New("hash type not supported")
// HashType indicates a standard hashing algorithm
type HashType struct {
Width int
Name string
Alias string
NewFunc func(...any) hash.Hash
}
func (ht *HashType) MarshalJSON() ([]byte, error) {
return []byte(`"` + ht.Name + `"`), nil
}
func (ht *HashType) MarshalText() (text []byte, err error) {
return []byte(ht.Name), nil
}
var (
_ json.Marshaler = (*HashType)(nil)
//_ json.Unmarshaler = (*HashType)(nil)
// read/write from/to json keys
_ encoding.TextMarshaler = (*HashType)(nil)
//_ encoding.TextUnmarshaler = (*HashType)(nil)
)
var (
name2hash = map[string]*HashType{}
alias2hash = map[string]*HashType{}
Supported []*HashType
)
// RegisterHash adds a new Hash to the list and returns its Type
func RegisterHash(name, alias string, width int, newFunc func() hash.Hash) *HashType {
return RegisterHashWithParam(name, alias, width, func(a ...any) hash.Hash { return newFunc() })
}
func RegisterHashWithParam(name, alias string, width int, newFunc func(...any) hash.Hash) *HashType {
newType := &HashType{
Name: name,
Alias: alias,
Width: width,
NewFunc: newFunc,
}
name2hash[name] = newType
alias2hash[alias] = newType
Supported = append(Supported, newType)
return newType
}
var (
// MD5 indicates MD5 support
MD5 = RegisterHash("md5", "MD5", 32, md5.New)
// SHA1 indicates SHA-1 support
SHA1 = RegisterHash("sha1", "SHA-1", 40, sha1.New)
// SHA256 indicates SHA-256 support
SHA256 = RegisterHash("sha256", "SHA-256", 64, sha256.New)
)
// HashData get hash of one hashType
func HashData(hashType *HashType, data []byte, params ...any) string {
h := hashType.NewFunc(params...)
h.Write(data)
return hex.EncodeToString(h.Sum(nil))
}
// HashReader get hash of one hashType from a reader
func HashReader(hashType *HashType, reader io.Reader, params ...any) (string, error) {
h := hashType.NewFunc(params...)
_, err := CopyWithBuffer(h, reader)
if err != nil {
return "", errs.NewErr(err, "HashReader error")
}
return hex.EncodeToString(h.Sum(nil)), nil
}
// HashFile get hash of one hashType from a model.File
func HashFile(hashType *HashType, file io.ReadSeeker, params ...any) (string, error) {
str, err := HashReader(hashType, file, params...)
if err != nil {
return "", err
}
if _, err = file.Seek(0, io.SeekStart); err != nil {
return str, err
}
return str, nil
}
// fromTypes will return hashers for all the requested types.
func fromTypes(types []*HashType) map[*HashType]hash.Hash {
hashers := map[*HashType]hash.Hash{}
for _, t := range types {
hashers[t] = t.NewFunc()
}
return hashers
}
// toMultiWriter will return a set of hashers into a
// single multiwriter, where one write will update all
// the hashers.
func toMultiWriter(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 {
w io.Writer
size int64
h map[*HashType]hash.Hash // Hashes
}
// NewMultiHasher will return a hash writer that will write
// the requested hash types.
func NewMultiHasher(types []*HashType) *MultiHasher {
hashers := fromTypes(types)
m := MultiHasher{h: hashers, w: toMultiWriter(hashers)}
return &m
}
func (m *MultiHasher) Write(p []byte) (n int, err error) {
n, err = m.w.Write(p)
m.size += int64(n)
return n, err
}
func (m *MultiHasher) GetHashInfo() *HashInfo {
dst := make(map[*HashType]string)
for k, v := range m.h {
dst[k] = hex.EncodeToString(v.Sum(nil))
}
return &HashInfo{h: dst}
}
// Sum returns the specified hash from the multihasher
func (m *MultiHasher) Sum(hashType *HashType) ([]byte, error) {
h, ok := m.h[hashType]
if !ok {
return nil, ErrUnsupported
}
return h.Sum(nil), nil
}
// Size returns the number of bytes written
func (m *MultiHasher) Size() int64 {
return m.size
}
// A HashInfo contains hash string for one or more hashType
type HashInfo struct {
h map[*HashType]string `json:"hashInfo"`
}
func NewHashInfoByMap(h map[*HashType]string) HashInfo {
return HashInfo{h}
}
func NewHashInfo(ht *HashType, str string) HashInfo {
m := make(map[*HashType]string)
if ht != nil {
m[ht] = str
}
return HashInfo{h: m}
}
func (hi HashInfo) String() string {
result, err := json.Marshal(hi.h)
if err != nil {
return ""
}
return string(result)
}
func FromString(str string) HashInfo {
hi := NewHashInfo(nil, "")
var tmp map[string]string
err := json.Unmarshal([]byte(str), &tmp)
if err != nil {
log.Warnf("failed to unmarsh HashInfo from string=%s", str)
} else {
for k, v := range tmp {
if name2hash[k] != nil && len(v) > 0 {
hi.h[name2hash[k]] = v
}
}
}
return hi
}
func (hi HashInfo) GetHash(ht *HashType) string {
return hi.h[ht]
}
func (hi HashInfo) Export() map[*HashType]string {
return hi.h
}