Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
alist-org
GitHub Repository: alist-org/alist
Path: blob/main/drivers/crypt/driver.go
1987 views
1
package crypt
2
3
import (
4
"context"
5
"fmt"
6
"io"
7
stdpath "path"
8
"regexp"
9
"strings"
10
11
"github.com/alist-org/alist/v3/internal/driver"
12
"github.com/alist-org/alist/v3/internal/errs"
13
"github.com/alist-org/alist/v3/internal/fs"
14
"github.com/alist-org/alist/v3/internal/model"
15
"github.com/alist-org/alist/v3/internal/op"
16
"github.com/alist-org/alist/v3/internal/sign"
17
"github.com/alist-org/alist/v3/internal/stream"
18
"github.com/alist-org/alist/v3/pkg/http_range"
19
"github.com/alist-org/alist/v3/pkg/utils"
20
"github.com/alist-org/alist/v3/server/common"
21
rcCrypt "github.com/rclone/rclone/backend/crypt"
22
"github.com/rclone/rclone/fs/config/configmap"
23
"github.com/rclone/rclone/fs/config/obscure"
24
log "github.com/sirupsen/logrus"
25
)
26
27
type Crypt struct {
28
model.Storage
29
Addition
30
cipher *rcCrypt.Cipher
31
remoteStorage driver.Driver
32
}
33
34
const obfuscatedPrefix = "___Obfuscated___"
35
36
func (d *Crypt) Config() driver.Config {
37
return config
38
}
39
40
func (d *Crypt) GetAddition() driver.Additional {
41
return &d.Addition
42
}
43
44
func (d *Crypt) Init(ctx context.Context) error {
45
//obfuscate credentials if it's updated or just created
46
err := d.updateObfusParm(&d.Password)
47
if err != nil {
48
return fmt.Errorf("failed to obfuscate password: %w", err)
49
}
50
err = d.updateObfusParm(&d.Salt)
51
if err != nil {
52
return fmt.Errorf("failed to obfuscate salt: %w", err)
53
}
54
55
isCryptExt := regexp.MustCompile(`^[.][A-Za-z0-9-_]{2,}$`).MatchString
56
if !isCryptExt(d.EncryptedSuffix) {
57
return fmt.Errorf("EncryptedSuffix is Illegal")
58
}
59
d.FileNameEncoding = utils.GetNoneEmpty(d.FileNameEncoding, "base64")
60
d.EncryptedSuffix = utils.GetNoneEmpty(d.EncryptedSuffix, ".bin")
61
62
op.MustSaveDriverStorage(d)
63
64
//need remote storage exist
65
storage, err := fs.GetStorage(d.RemotePath, &fs.GetStoragesArgs{})
66
if err != nil {
67
return fmt.Errorf("can't find remote storage: %w", err)
68
}
69
d.remoteStorage = storage
70
71
p, _ := strings.CutPrefix(d.Password, obfuscatedPrefix)
72
p2, _ := strings.CutPrefix(d.Salt, obfuscatedPrefix)
73
config := configmap.Simple{
74
"password": p,
75
"password2": p2,
76
"filename_encryption": d.FileNameEnc,
77
"directory_name_encryption": d.DirNameEnc,
78
"filename_encoding": d.FileNameEncoding,
79
"suffix": d.EncryptedSuffix,
80
"pass_bad_blocks": "",
81
}
82
c, err := rcCrypt.NewCipher(config)
83
if err != nil {
84
return fmt.Errorf("failed to create Cipher: %w", err)
85
}
86
d.cipher = c
87
88
return nil
89
}
90
91
func (d *Crypt) updateObfusParm(str *string) error {
92
temp := *str
93
if !strings.HasPrefix(temp, obfuscatedPrefix) {
94
temp, err := obscure.Obscure(temp)
95
if err != nil {
96
return err
97
}
98
temp = obfuscatedPrefix + temp
99
*str = temp
100
}
101
return nil
102
}
103
104
func (d *Crypt) Drop(ctx context.Context) error {
105
return nil
106
}
107
108
func (d *Crypt) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
109
path := dir.GetPath()
110
//return d.list(ctx, d.RemotePath, path)
111
//remoteFull
112
113
objs, err := fs.List(ctx, d.getPathForRemote(path, true), &fs.ListArgs{NoLog: true})
114
// the obj must implement the model.SetPath interface
115
// return objs, err
116
if err != nil {
117
return nil, err
118
}
119
120
var result []model.Obj
121
for _, obj := range objs {
122
if obj.IsDir() {
123
name, err := d.cipher.DecryptDirName(obj.GetName())
124
if err != nil {
125
//filter illegal files
126
continue
127
}
128
if !d.ShowHidden && strings.HasPrefix(name, ".") {
129
continue
130
}
131
objRes := model.Object{
132
Name: name,
133
Size: 0,
134
Modified: obj.ModTime(),
135
IsFolder: obj.IsDir(),
136
Ctime: obj.CreateTime(),
137
// discarding hash as it's encrypted
138
}
139
result = append(result, &objRes)
140
} else {
141
thumb, ok := model.GetThumb(obj)
142
size, err := d.cipher.DecryptedSize(obj.GetSize())
143
if err != nil {
144
//filter illegal files
145
continue
146
}
147
name, err := d.cipher.DecryptFileName(obj.GetName())
148
if err != nil {
149
//filter illegal files
150
continue
151
}
152
if !d.ShowHidden && strings.HasPrefix(name, ".") {
153
continue
154
}
155
objRes := model.Object{
156
Name: name,
157
Size: size,
158
Modified: obj.ModTime(),
159
IsFolder: obj.IsDir(),
160
Ctime: obj.CreateTime(),
161
// discarding hash as it's encrypted
162
}
163
if d.Thumbnail && thumb == "" {
164
thumbPath := stdpath.Join(args.ReqPath, ".thumbnails", name+".webp")
165
thumb = fmt.Sprintf("%s/d%s?sign=%s",
166
common.GetApiUrl(common.GetHttpReq(ctx)),
167
utils.EncodePath(thumbPath, true),
168
sign.Sign(thumbPath))
169
}
170
if !ok && !d.Thumbnail {
171
result = append(result, &objRes)
172
} else {
173
objWithThumb := model.ObjThumb{
174
Object: objRes,
175
Thumbnail: model.Thumbnail{
176
Thumbnail: thumb,
177
},
178
}
179
result = append(result, &objWithThumb)
180
}
181
}
182
}
183
184
return result, nil
185
}
186
187
func (d *Crypt) Get(ctx context.Context, path string) (model.Obj, error) {
188
if utils.PathEqual(path, "/") {
189
return &model.Object{
190
Name: "Root",
191
IsFolder: true,
192
Path: "/",
193
}, nil
194
}
195
remoteFullPath := ""
196
var remoteObj model.Obj
197
var err, err2 error
198
firstTryIsFolder, secondTry := guessPath(path)
199
remoteFullPath = d.getPathForRemote(path, firstTryIsFolder)
200
remoteObj, err = fs.Get(ctx, remoteFullPath, &fs.GetArgs{NoLog: true})
201
if err != nil {
202
if errs.IsObjectNotFound(err) && secondTry {
203
//try the opposite
204
remoteFullPath = d.getPathForRemote(path, !firstTryIsFolder)
205
remoteObj, err2 = fs.Get(ctx, remoteFullPath, &fs.GetArgs{NoLog: true})
206
if err2 != nil {
207
return nil, err2
208
}
209
} else {
210
return nil, err
211
}
212
}
213
var size int64 = 0
214
name := ""
215
if !remoteObj.IsDir() {
216
size, err = d.cipher.DecryptedSize(remoteObj.GetSize())
217
if err != nil {
218
log.Warnf("DecryptedSize failed for %s ,will use original size, err:%s", path, err)
219
size = remoteObj.GetSize()
220
}
221
name, err = d.cipher.DecryptFileName(remoteObj.GetName())
222
if err != nil {
223
log.Warnf("DecryptFileName failed for %s ,will use original name, err:%s", path, err)
224
name = remoteObj.GetName()
225
}
226
} else {
227
name, err = d.cipher.DecryptDirName(remoteObj.GetName())
228
if err != nil {
229
log.Warnf("DecryptDirName failed for %s ,will use original name, err:%s", path, err)
230
name = remoteObj.GetName()
231
}
232
}
233
obj := &model.Object{
234
Path: path,
235
Name: name,
236
Size: size,
237
Modified: remoteObj.ModTime(),
238
IsFolder: remoteObj.IsDir(),
239
}
240
return obj, nil
241
//return nil, errs.ObjectNotFound
242
}
243
244
func (d *Crypt) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
245
dstDirActualPath, err := d.getActualPathForRemote(file.GetPath(), false)
246
if err != nil {
247
return nil, fmt.Errorf("failed to convert path to remote path: %w", err)
248
}
249
remoteLink, remoteFile, err := op.Link(ctx, d.remoteStorage, dstDirActualPath, args)
250
if err != nil {
251
return nil, err
252
}
253
254
if remoteLink.RangeReadCloser == nil && remoteLink.MFile == nil && len(remoteLink.URL) == 0 {
255
return nil, fmt.Errorf("the remote storage driver need to be enhanced to support encrytion")
256
}
257
remoteFileSize := remoteFile.GetSize()
258
remoteClosers := utils.EmptyClosers()
259
rangeReaderFunc := func(ctx context.Context, underlyingOffset, underlyingLength int64) (io.ReadCloser, error) {
260
length := underlyingLength
261
if underlyingLength >= 0 && underlyingOffset+underlyingLength >= remoteFileSize {
262
length = -1
263
}
264
rrc := remoteLink.RangeReadCloser
265
if len(remoteLink.URL) > 0 {
266
var converted, err = stream.GetRangeReadCloserFromLink(remoteFileSize, remoteLink)
267
if err != nil {
268
return nil, err
269
}
270
rrc = converted
271
}
272
if rrc != nil {
273
remoteReader, err := rrc.RangeRead(ctx, http_range.Range{Start: underlyingOffset, Length: length})
274
remoteClosers.AddClosers(rrc.GetClosers())
275
if err != nil {
276
return nil, err
277
}
278
return remoteReader, nil
279
}
280
if remoteLink.MFile != nil {
281
_, err := remoteLink.MFile.Seek(underlyingOffset, io.SeekStart)
282
if err != nil {
283
return nil, err
284
}
285
//keep reuse same MFile and close at last.
286
remoteClosers.Add(remoteLink.MFile)
287
return io.NopCloser(remoteLink.MFile), nil
288
}
289
290
return nil, errs.NotSupport
291
292
}
293
resultRangeReader := func(ctx context.Context, httpRange http_range.Range) (io.ReadCloser, error) {
294
readSeeker, err := d.cipher.DecryptDataSeek(ctx, rangeReaderFunc, httpRange.Start, httpRange.Length)
295
if err != nil {
296
return nil, err
297
}
298
return readSeeker, nil
299
}
300
301
resultRangeReadCloser := &model.RangeReadCloser{RangeReader: resultRangeReader, Closers: remoteClosers}
302
resultLink := &model.Link{
303
RangeReadCloser: resultRangeReadCloser,
304
Expiration: remoteLink.Expiration,
305
}
306
307
return resultLink, nil
308
309
}
310
311
func (d *Crypt) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
312
dstDirActualPath, err := d.getActualPathForRemote(parentDir.GetPath(), true)
313
if err != nil {
314
return fmt.Errorf("failed to convert path to remote path: %w", err)
315
}
316
dir := d.cipher.EncryptDirName(dirName)
317
return op.MakeDir(ctx, d.remoteStorage, stdpath.Join(dstDirActualPath, dir))
318
}
319
320
func (d *Crypt) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
321
srcRemoteActualPath, err := d.getActualPathForRemote(srcObj.GetPath(), srcObj.IsDir())
322
if err != nil {
323
return fmt.Errorf("failed to convert path to remote path: %w", err)
324
}
325
dstRemoteActualPath, err := d.getActualPathForRemote(dstDir.GetPath(), dstDir.IsDir())
326
if err != nil {
327
return fmt.Errorf("failed to convert path to remote path: %w", err)
328
}
329
return op.Move(ctx, d.remoteStorage, srcRemoteActualPath, dstRemoteActualPath)
330
}
331
332
func (d *Crypt) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
333
remoteActualPath, err := d.getActualPathForRemote(srcObj.GetPath(), srcObj.IsDir())
334
if err != nil {
335
return fmt.Errorf("failed to convert path to remote path: %w", err)
336
}
337
var newEncryptedName string
338
if srcObj.IsDir() {
339
newEncryptedName = d.cipher.EncryptDirName(newName)
340
} else {
341
newEncryptedName = d.cipher.EncryptFileName(newName)
342
}
343
return op.Rename(ctx, d.remoteStorage, remoteActualPath, newEncryptedName)
344
}
345
346
func (d *Crypt) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
347
srcRemoteActualPath, err := d.getActualPathForRemote(srcObj.GetPath(), srcObj.IsDir())
348
if err != nil {
349
return fmt.Errorf("failed to convert path to remote path: %w", err)
350
}
351
dstRemoteActualPath, err := d.getActualPathForRemote(dstDir.GetPath(), dstDir.IsDir())
352
if err != nil {
353
return fmt.Errorf("failed to convert path to remote path: %w", err)
354
}
355
return op.Copy(ctx, d.remoteStorage, srcRemoteActualPath, dstRemoteActualPath)
356
357
}
358
359
func (d *Crypt) Remove(ctx context.Context, obj model.Obj) error {
360
remoteActualPath, err := d.getActualPathForRemote(obj.GetPath(), obj.IsDir())
361
if err != nil {
362
return fmt.Errorf("failed to convert path to remote path: %w", err)
363
}
364
return op.Remove(ctx, d.remoteStorage, remoteActualPath)
365
}
366
367
func (d *Crypt) Put(ctx context.Context, dstDir model.Obj, streamer model.FileStreamer, up driver.UpdateProgress) error {
368
dstDirActualPath, err := d.getActualPathForRemote(dstDir.GetPath(), true)
369
if err != nil {
370
return fmt.Errorf("failed to convert path to remote path: %w", err)
371
}
372
373
// Encrypt the data into wrappedIn
374
wrappedIn, err := d.cipher.EncryptData(streamer)
375
if err != nil {
376
return fmt.Errorf("failed to EncryptData: %w", err)
377
}
378
379
// doesn't support seekableStream, since rapid-upload is not working for encrypted data
380
streamOut := &stream.FileStream{
381
Obj: &model.Object{
382
ID: streamer.GetID(),
383
Path: streamer.GetPath(),
384
Name: d.cipher.EncryptFileName(streamer.GetName()),
385
Size: d.cipher.EncryptedSize(streamer.GetSize()),
386
Modified: streamer.ModTime(),
387
IsFolder: streamer.IsDir(),
388
},
389
Reader: wrappedIn,
390
Mimetype: "application/octet-stream",
391
WebPutAsTask: streamer.NeedStore(),
392
ForceStreamUpload: true,
393
Exist: streamer.GetExist(),
394
}
395
err = op.Put(ctx, d.remoteStorage, dstDirActualPath, streamOut, up, false)
396
if err != nil {
397
return err
398
}
399
return nil
400
}
401
402
//func (d *Safe) Other(ctx context.Context, args model.OtherArgs) (interface{}, error) {
403
// return nil, errs.NotSupport
404
//}
405
406
var _ driver.Driver = (*Crypt)(nil)
407
408