Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
alist-org
GitHub Repository: alist-org/alist
Path: blob/main/drivers/halalcloud/util.go
1986 views
1
package halalcloud
2
3
import (
4
"bytes"
5
"context"
6
"crypto/md5"
7
"crypto/tls"
8
"encoding/hex"
9
"errors"
10
"fmt"
11
"github.com/alist-org/alist/v3/internal/model"
12
"github.com/alist-org/alist/v3/pkg/utils"
13
pbPublicUser "github.com/city404/v6-public-rpc-proto/go/v6/user"
14
pubUserFile "github.com/city404/v6-public-rpc-proto/go/v6/userfile"
15
"github.com/google/uuid"
16
"github.com/ipfs/go-cid"
17
"google.golang.org/grpc"
18
"google.golang.org/grpc/codes"
19
"google.golang.org/grpc/credentials"
20
"google.golang.org/grpc/metadata"
21
"google.golang.org/grpc/status"
22
"hash"
23
"io"
24
"net/http"
25
"strconv"
26
"strings"
27
"sync"
28
"time"
29
)
30
31
const (
32
AppID = "alist/10001"
33
AppVersion = "1.0.0"
34
AppSecret = "bR4SJwOkvnG5WvVJ"
35
)
36
37
const (
38
grpcServer = "grpcuserapi.2dland.cn:443"
39
grpcServerAuth = "grpcuserapi.2dland.cn"
40
)
41
42
func (d *HalalCloud) NewAuthServiceWithOauth(options ...HalalOption) (*AuthService, error) {
43
44
aService := &AuthService{}
45
err2 := errors.New("")
46
47
svc := d.HalalCommon.AuthService
48
for _, opt := range options {
49
opt.apply(&svc.dopts)
50
}
51
52
grpcOptions := svc.dopts.grpcOptions
53
grpcOptions = append(grpcOptions, grpc.WithAuthority(grpcServerAuth), grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{})), grpc.WithUnaryInterceptor(func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
54
ctxx := svc.signContext(method, ctx)
55
err := invoker(ctxx, method, req, reply, cc, opts...) // invoking RPC method
56
return err
57
}))
58
59
grpcConnection, err := grpc.NewClient(grpcServer, grpcOptions...)
60
if err != nil {
61
return nil, err
62
}
63
defer grpcConnection.Close()
64
userClient := pbPublicUser.NewPubUserClient(grpcConnection)
65
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
66
defer cancel()
67
stateString := uuid.New().String()
68
// queryValues.Add("callback", oauthToken.Callback)
69
oauthToken, err := userClient.CreateAuthToken(ctx, &pbPublicUser.LoginRequest{
70
ReturnType: 2,
71
State: stateString,
72
ReturnUrl: "",
73
})
74
if err != nil {
75
return nil, err
76
}
77
if len(oauthToken.State) < 1 {
78
oauthToken.State = stateString
79
}
80
81
if oauthToken.Url != "" {
82
83
return nil, fmt.Errorf(`need verify: <a target="_blank" href="%s">Click Here</a>`, oauthToken.Url)
84
}
85
86
return aService, err2
87
88
}
89
90
func (d *HalalCloud) NewAuthService(refreshToken string, options ...HalalOption) (*AuthService, error) {
91
svc := d.HalalCommon.AuthService
92
93
if len(refreshToken) < 1 {
94
refreshToken = d.Addition.RefreshToken
95
}
96
97
if len(d.tr.AccessToken) > 0 {
98
accessTokenExpiredAt := d.tr.AccessTokenExpiredAt
99
current := time.Now().UnixMilli()
100
if accessTokenExpiredAt < current {
101
// access token expired
102
d.tr.AccessToken = ""
103
d.tr.AccessTokenExpiredAt = 0
104
} else {
105
svc.tr.AccessTokenExpiredAt = accessTokenExpiredAt
106
svc.tr.AccessToken = d.tr.AccessToken
107
}
108
}
109
110
for _, opt := range options {
111
opt.apply(&svc.dopts)
112
}
113
114
grpcOptions := svc.dopts.grpcOptions
115
grpcOptions = append(grpcOptions, grpc.WithDefaultCallOptions(grpc.MaxCallSendMsgSize(10*1024*1024), grpc.MaxCallRecvMsgSize(10*1024*1024)), grpc.WithAuthority(grpcServerAuth), grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{})), grpc.WithUnaryInterceptor(func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
116
ctxx := svc.signContext(method, ctx)
117
err := invoker(ctxx, method, req, reply, cc, opts...) // invoking RPC method
118
if err != nil {
119
grpcStatus, ok := status.FromError(err)
120
121
if ok && grpcStatus.Code() == codes.Unauthenticated && strings.Contains(grpcStatus.Err().Error(), "invalid accesstoken") && len(refreshToken) > 0 {
122
// refresh token
123
refreshResponse, err := pbPublicUser.NewPubUserClient(cc).Refresh(ctx, &pbPublicUser.Token{
124
RefreshToken: refreshToken,
125
})
126
if err != nil {
127
return err
128
}
129
if len(refreshResponse.AccessToken) > 0 {
130
svc.tr.AccessToken = refreshResponse.AccessToken
131
svc.tr.AccessTokenExpiredAt = refreshResponse.AccessTokenExpireTs
132
svc.OnAccessTokenRefreshed(refreshResponse.AccessToken, refreshResponse.AccessTokenExpireTs, refreshResponse.RefreshToken, refreshResponse.RefreshTokenExpireTs)
133
}
134
// retry
135
ctxx := svc.signContext(method, ctx)
136
err = invoker(ctxx, method, req, reply, cc, opts...) // invoking RPC method
137
if err != nil {
138
return err
139
} else {
140
return nil
141
}
142
}
143
}
144
return err
145
}))
146
grpcConnection, err := grpc.NewClient(grpcServer, grpcOptions...)
147
148
if err != nil {
149
return nil, err
150
}
151
152
svc.grpcConnection = grpcConnection
153
return svc, err
154
}
155
156
func (s *AuthService) OnAccessTokenRefreshed(accessToken string, accessTokenExpiredAt int64, refreshToken string, refreshTokenExpiredAt int64) {
157
s.tr.AccessToken = accessToken
158
s.tr.AccessTokenExpiredAt = accessTokenExpiredAt
159
s.tr.RefreshToken = refreshToken
160
s.tr.RefreshTokenExpiredAt = refreshTokenExpiredAt
161
162
if s.dopts.onTokenRefreshed != nil {
163
s.dopts.onTokenRefreshed(accessToken, accessTokenExpiredAt, refreshToken, refreshTokenExpiredAt)
164
}
165
166
}
167
168
func (s *AuthService) GetGrpcConnection() *grpc.ClientConn {
169
return s.grpcConnection
170
}
171
172
func (s *AuthService) Close() {
173
_ = s.grpcConnection.Close()
174
}
175
176
func (s *AuthService) signContext(method string, ctx context.Context) context.Context {
177
var kvString []string
178
currentTimeStamp := strconv.FormatInt(time.Now().UnixMilli(), 10)
179
bufferedString := bytes.NewBufferString(method)
180
kvString = append(kvString, "timestamp", currentTimeStamp)
181
bufferedString.WriteString(currentTimeStamp)
182
kvString = append(kvString, "appid", s.appID)
183
bufferedString.WriteString(s.appID)
184
kvString = append(kvString, "appversion", s.appVersion)
185
bufferedString.WriteString(s.appVersion)
186
if s.tr != nil && len(s.tr.AccessToken) > 0 {
187
authorization := "Bearer " + s.tr.AccessToken
188
kvString = append(kvString, "authorization", authorization)
189
bufferedString.WriteString(authorization)
190
}
191
bufferedString.WriteString(s.appSecret)
192
sign := GetMD5Hash(bufferedString.String())
193
kvString = append(kvString, "sign", sign)
194
return metadata.AppendToOutgoingContext(ctx, kvString...)
195
}
196
197
func (d *HalalCloud) GetCurrentOpDir(dir model.Obj, args []string, index int) string {
198
currentDir := dir.GetPath()
199
if len(currentDir) == 0 {
200
currentDir = "/"
201
}
202
opPath := currentDir + "/" + args[index]
203
if strings.HasPrefix(args[index], "/") {
204
opPath = args[index]
205
}
206
return opPath
207
}
208
209
func (d *HalalCloud) GetCurrentDir(dir model.Obj) string {
210
currentDir := dir.GetPath()
211
if len(currentDir) == 0 {
212
currentDir = "/"
213
}
214
return currentDir
215
}
216
217
type Common struct {
218
}
219
220
func getRawFiles(addr *pubUserFile.SliceDownloadInfo) ([]byte, error) {
221
222
if addr == nil {
223
return nil, errors.New("addr is nil")
224
}
225
226
client := http.Client{
227
Timeout: time.Duration(60 * time.Second), // Set timeout to 5 seconds
228
}
229
resp, err := client.Get(addr.DownloadAddress)
230
if err != nil {
231
232
return nil, err
233
}
234
defer resp.Body.Close()
235
body, err := io.ReadAll(resp.Body)
236
if err != nil {
237
return nil, err
238
}
239
if resp.StatusCode != http.StatusOK {
240
return nil, fmt.Errorf("bad status: %s, body: %s", resp.Status, body)
241
}
242
243
if addr.Encrypt > 0 {
244
cd := uint8(addr.Encrypt)
245
for idx := 0; idx < len(body); idx++ {
246
body[idx] = body[idx] ^ cd
247
}
248
}
249
250
if addr.StoreType != 10 {
251
252
sourceCid, err := cid.Decode(addr.Identity)
253
if err != nil {
254
return nil, err
255
}
256
checkCid, err := sourceCid.Prefix().Sum(body)
257
if err != nil {
258
return nil, err
259
}
260
if !checkCid.Equals(sourceCid) {
261
return nil, fmt.Errorf("bad cid: %s, body: %s", checkCid.String(), body)
262
}
263
}
264
265
return body, nil
266
267
}
268
269
type openObject struct {
270
ctx context.Context
271
mu sync.Mutex
272
d []*pubUserFile.SliceDownloadInfo
273
id int
274
skip int64
275
chunk *[]byte
276
chunks *[]chunkSize
277
closed bool
278
sha string
279
shaTemp hash.Hash
280
}
281
282
// get the next chunk
283
func (oo *openObject) getChunk(ctx context.Context) (err error) {
284
if oo.id >= len(*oo.chunks) {
285
return io.EOF
286
}
287
var chunk []byte
288
err = utils.Retry(3, time.Second, func() (err error) {
289
chunk, err = getRawFiles(oo.d[oo.id])
290
return err
291
})
292
if err != nil {
293
return err
294
}
295
oo.id++
296
oo.chunk = &chunk
297
return nil
298
}
299
300
// Read reads up to len(p) bytes into p.
301
func (oo *openObject) Read(p []byte) (n int, err error) {
302
oo.mu.Lock()
303
defer oo.mu.Unlock()
304
if oo.closed {
305
return 0, fmt.Errorf("read on closed file")
306
}
307
// Skip data at the start if requested
308
for oo.skip > 0 {
309
//size := 1024 * 1024
310
_, size, err := oo.ChunkLocation(oo.id)
311
if err != nil {
312
return 0, err
313
}
314
if oo.skip < int64(size) {
315
break
316
}
317
oo.id++
318
oo.skip -= int64(size)
319
}
320
if len(*oo.chunk) == 0 {
321
err = oo.getChunk(oo.ctx)
322
if err != nil {
323
return 0, err
324
}
325
if oo.skip > 0 {
326
*oo.chunk = (*oo.chunk)[oo.skip:]
327
oo.skip = 0
328
}
329
}
330
n = copy(p, *oo.chunk)
331
*oo.chunk = (*oo.chunk)[n:]
332
333
oo.shaTemp.Write(*oo.chunk)
334
335
return n, nil
336
}
337
338
// Close closed the file - MAC errors are reported here
339
func (oo *openObject) Close() (err error) {
340
oo.mu.Lock()
341
defer oo.mu.Unlock()
342
if oo.closed {
343
return nil
344
}
345
// 校验Sha1
346
if string(oo.shaTemp.Sum(nil)) != oo.sha {
347
return fmt.Errorf("failed to finish download: %w", err)
348
}
349
350
oo.closed = true
351
return nil
352
}
353
354
func GetMD5Hash(text string) string {
355
tHash := md5.Sum([]byte(text))
356
return hex.EncodeToString(tHash[:])
357
}
358
359
// chunkSize describes a size and position of chunk
360
type chunkSize struct {
361
position int64
362
size int
363
}
364
365
func getChunkSizes(sliceSize []*pubUserFile.SliceSize) (chunks []chunkSize) {
366
chunks = make([]chunkSize, 0)
367
for _, s := range sliceSize {
368
// 对最后一个做特殊处理
369
if s.EndIndex == 0 {
370
s.EndIndex = s.StartIndex
371
}
372
for j := s.StartIndex; j <= s.EndIndex; j++ {
373
chunks = append(chunks, chunkSize{position: j, size: int(s.Size)})
374
}
375
}
376
return chunks
377
}
378
379
func (oo *openObject) ChunkLocation(id int) (position int64, size int, err error) {
380
if id < 0 || id >= len(*oo.chunks) {
381
return 0, 0, errors.New("invalid arguments")
382
}
383
384
return (*oo.chunks)[id].position, (*oo.chunks)[id].size, nil
385
}
386
387