Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
alist-org
GitHub Repository: alist-org/alist
Path: blob/main/pkg/utils/hash.go
1560 views
1
package utils
2
3
import (
4
"crypto/md5"
5
"crypto/sha1"
6
"crypto/sha256"
7
"encoding"
8
"encoding/hex"
9
"encoding/json"
10
"errors"
11
"hash"
12
"io"
13
"iter"
14
15
"github.com/alist-org/alist/v3/internal/errs"
16
log "github.com/sirupsen/logrus"
17
)
18
19
func GetMD5EncodeStr(data string) string {
20
return HashData(MD5, []byte(data))
21
}
22
23
//inspired by "github.com/rclone/rclone/fs/hash"
24
25
// ErrUnsupported should be returned by filesystem,
26
// if it is requested to deliver an unsupported hash type.
27
var ErrUnsupported = errors.New("hash type not supported")
28
29
// HashType indicates a standard hashing algorithm
30
type HashType struct {
31
Width int
32
Name string
33
Alias string
34
NewFunc func(...any) hash.Hash
35
}
36
37
func (ht *HashType) MarshalJSON() ([]byte, error) {
38
return []byte(`"` + ht.Name + `"`), nil
39
}
40
41
func (ht *HashType) MarshalText() (text []byte, err error) {
42
return []byte(ht.Name), nil
43
}
44
45
var (
46
_ json.Marshaler = (*HashType)(nil)
47
//_ json.Unmarshaler = (*HashType)(nil)
48
49
// read/write from/to json keys
50
_ encoding.TextMarshaler = (*HashType)(nil)
51
//_ encoding.TextUnmarshaler = (*HashType)(nil)
52
)
53
54
var (
55
name2hash = map[string]*HashType{}
56
alias2hash = map[string]*HashType{}
57
Supported []*HashType
58
)
59
60
// RegisterHash adds a new Hash to the list and returns its Type
61
func RegisterHash(name, alias string, width int, newFunc func() hash.Hash) *HashType {
62
return RegisterHashWithParam(name, alias, width, func(a ...any) hash.Hash { return newFunc() })
63
}
64
65
func RegisterHashWithParam(name, alias string, width int, newFunc func(...any) hash.Hash) *HashType {
66
newType := &HashType{
67
Name: name,
68
Alias: alias,
69
Width: width,
70
NewFunc: newFunc,
71
}
72
73
name2hash[name] = newType
74
alias2hash[alias] = newType
75
Supported = append(Supported, newType)
76
return newType
77
}
78
79
var (
80
// MD5 indicates MD5 support
81
MD5 = RegisterHash("md5", "MD5", 32, md5.New)
82
83
// SHA1 indicates SHA-1 support
84
SHA1 = RegisterHash("sha1", "SHA-1", 40, sha1.New)
85
86
// SHA256 indicates SHA-256 support
87
SHA256 = RegisterHash("sha256", "SHA-256", 64, sha256.New)
88
)
89
90
// HashData get hash of one hashType
91
func HashData(hashType *HashType, data []byte, params ...any) string {
92
h := hashType.NewFunc(params...)
93
h.Write(data)
94
return hex.EncodeToString(h.Sum(nil))
95
}
96
97
// HashReader get hash of one hashType from a reader
98
func HashReader(hashType *HashType, reader io.Reader, params ...any) (string, error) {
99
h := hashType.NewFunc(params...)
100
_, err := CopyWithBuffer(h, reader)
101
if err != nil {
102
return "", errs.NewErr(err, "HashReader error")
103
}
104
return hex.EncodeToString(h.Sum(nil)), nil
105
}
106
107
// HashFile get hash of one hashType from a model.File
108
func HashFile(hashType *HashType, file io.ReadSeeker, params ...any) (string, error) {
109
str, err := HashReader(hashType, file, params...)
110
if err != nil {
111
return "", err
112
}
113
if _, err = file.Seek(0, io.SeekStart); err != nil {
114
return str, err
115
}
116
return str, nil
117
}
118
119
// fromTypes will return hashers for all the requested types.
120
func fromTypes(types []*HashType) map[*HashType]hash.Hash {
121
hashers := map[*HashType]hash.Hash{}
122
for _, t := range types {
123
hashers[t] = t.NewFunc()
124
}
125
return hashers
126
}
127
128
// toMultiWriter will return a set of hashers into a
129
// single multiwriter, where one write will update all
130
// the hashers.
131
func toMultiWriter(h map[*HashType]hash.Hash) io.Writer {
132
// Convert to to slice
133
var w = make([]io.Writer, 0, len(h))
134
for _, v := range h {
135
w = append(w, v)
136
}
137
return io.MultiWriter(w...)
138
}
139
140
// A MultiHasher will construct various hashes on all incoming writes.
141
type MultiHasher struct {
142
w io.Writer
143
size int64
144
h map[*HashType]hash.Hash // Hashes
145
}
146
147
// NewMultiHasher will return a hash writer that will write
148
// the requested hash types.
149
func NewMultiHasher(types []*HashType) *MultiHasher {
150
hashers := fromTypes(types)
151
m := MultiHasher{h: hashers, w: toMultiWriter(hashers)}
152
return &m
153
}
154
155
func (m *MultiHasher) Write(p []byte) (n int, err error) {
156
n, err = m.w.Write(p)
157
m.size += int64(n)
158
return n, err
159
}
160
161
func (m *MultiHasher) GetHashInfo() *HashInfo {
162
dst := make(map[*HashType]string)
163
for k, v := range m.h {
164
dst[k] = hex.EncodeToString(v.Sum(nil))
165
}
166
return &HashInfo{h: dst}
167
}
168
169
// Sum returns the specified hash from the multihasher
170
func (m *MultiHasher) Sum(hashType *HashType) ([]byte, error) {
171
h, ok := m.h[hashType]
172
if !ok {
173
return nil, ErrUnsupported
174
}
175
return h.Sum(nil), nil
176
}
177
178
// Size returns the number of bytes written
179
func (m *MultiHasher) Size() int64 {
180
return m.size
181
}
182
183
// A HashInfo contains hash string for one or more hashType
184
type HashInfo struct {
185
h map[*HashType]string `json:"hashInfo"`
186
}
187
188
func NewHashInfoByMap(h map[*HashType]string) HashInfo {
189
return HashInfo{h}
190
}
191
192
func NewHashInfo(ht *HashType, str string) HashInfo {
193
m := make(map[*HashType]string)
194
if ht != nil {
195
m[ht] = str
196
}
197
return HashInfo{h: m}
198
}
199
200
func (hi HashInfo) String() string {
201
result, err := json.Marshal(hi.h)
202
if err != nil {
203
return ""
204
}
205
return string(result)
206
}
207
func FromString(str string) HashInfo {
208
hi := NewHashInfo(nil, "")
209
var tmp map[string]string
210
err := json.Unmarshal([]byte(str), &tmp)
211
if err != nil {
212
log.Warnf("failed to unmarsh HashInfo from string=%s", str)
213
} else {
214
for k, v := range tmp {
215
if name2hash[k] != nil && len(v) > 0 {
216
hi.h[name2hash[k]] = v
217
}
218
}
219
}
220
221
return hi
222
}
223
func (hi HashInfo) GetHash(ht *HashType) string {
224
return hi.h[ht]
225
}
226
227
func (hi HashInfo) Export() map[*HashType]string {
228
return hi.h
229
}
230
231
func (hi HashInfo) All() iter.Seq2[*HashType, string] {
232
return func(yield func(*HashType, string) bool) {
233
for hashType, hashValue := range hi.h {
234
if !yield(hashType, hashValue) {
235
return
236
}
237
}
238
}
239
}
240
241