Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
alist-org
GitHub Repository: alist-org/alist
Path: blob/main/drivers/189/util.go
1986 views
1
package _189
2
3
import (
4
"bytes"
5
"context"
6
"crypto/md5"
7
"encoding/base64"
8
"encoding/hex"
9
"errors"
10
"fmt"
11
"io"
12
"math"
13
"net/http"
14
"path"
15
"strconv"
16
"strings"
17
"time"
18
"unicode/utf8"
19
20
"github.com/alist-org/alist/v3/drivers/base"
21
"github.com/alist-org/alist/v3/internal/driver"
22
"github.com/alist-org/alist/v3/internal/model"
23
"github.com/alist-org/alist/v3/pkg/utils"
24
myrand "github.com/alist-org/alist/v3/pkg/utils/random"
25
"github.com/go-resty/resty/v2"
26
jsoniter "github.com/json-iterator/go"
27
log "github.com/sirupsen/logrus"
28
)
29
30
// do others that not defined in Driver interface
31
32
//func (d *Cloud189) login() error {
33
// url := "https://cloud.189.cn/api/portal/loginUrl.action?redirectURL=https%3A%2F%2Fcloud.189.cn%2Fmain.action"
34
// b := ""
35
// lt := ""
36
// ltText := regexp.MustCompile(`lt = "(.+?)"`)
37
// var res *resty.Response
38
// var err error
39
// for i := 0; i < 3; i++ {
40
// res, err = d.client.R().Get(url)
41
// if err != nil {
42
// return err
43
// }
44
// // 已经登陆
45
// if res.RawResponse.Request.URL.String() == "https://cloud.189.cn/web/main" {
46
// return nil
47
// }
48
// b = res.String()
49
// ltTextArr := ltText.FindStringSubmatch(b)
50
// if len(ltTextArr) > 0 {
51
// lt = ltTextArr[1]
52
// break
53
// } else {
54
// <-time.After(time.Second)
55
// }
56
// }
57
// if lt == "" {
58
// return fmt.Errorf("get page: %s \nstatus: %d \nrequest url: %s\nredirect url: %s",
59
// b, res.StatusCode(), res.RawResponse.Request.URL.String(), res.Header().Get("location"))
60
// }
61
// captchaToken := regexp.MustCompile(`captchaToken' value='(.+?)'`).FindStringSubmatch(b)[1]
62
// returnUrl := regexp.MustCompile(`returnUrl = '(.+?)'`).FindStringSubmatch(b)[1]
63
// paramId := regexp.MustCompile(`paramId = "(.+?)"`).FindStringSubmatch(b)[1]
64
// //reqId := regexp.MustCompile(`reqId = "(.+?)"`).FindStringSubmatch(b)[1]
65
// jRsakey := regexp.MustCompile(`j_rsaKey" value="(\S+)"`).FindStringSubmatch(b)[1]
66
// vCodeID := regexp.MustCompile(`picCaptcha\.do\?token\=([A-Za-z0-9\&\=]+)`).FindStringSubmatch(b)[1]
67
// vCodeRS := ""
68
// if vCodeID != "" {
69
// // need ValidateCode
70
// log.Debugf("try to identify verification codes")
71
// timeStamp := strconv.FormatInt(time.Now().UnixNano()/1e6, 10)
72
// u := "https://open.e.189.cn/api/logbox/oauth2/picCaptcha.do?token=" + vCodeID + timeStamp
73
// imgRes, err := d.client.R().SetHeaders(map[string]string{
74
// "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:74.0) Gecko/20100101 Firefox/76.0",
75
// "Referer": "https://open.e.189.cn/api/logbox/oauth2/unifyAccountLogin.do",
76
// "Sec-Fetch-Dest": "image",
77
// "Sec-Fetch-Mode": "no-cors",
78
// "Sec-Fetch-Site": "same-origin",
79
// }).Get(u)
80
// if err != nil {
81
// return err
82
// }
83
// // Enter the verification code manually
84
// //err = message.GetMessenger().WaitSend(message.Message{
85
// // Type: "image",
86
// // Content: "data:image/png;base64," + base64.StdEncoding.EncodeToString(imgRes.Body()),
87
// //}, 10)
88
// //if err != nil {
89
// // return err
90
// //}
91
// //vCodeRS, err = message.GetMessenger().WaitReceive(30)
92
// // use ocr api
93
// vRes, err := base.RestyClient.R().SetMultipartField(
94
// "image", "validateCode.png", "image/png", bytes.NewReader(imgRes.Body())).
95
// Post(setting.GetStr(conf.OcrApi))
96
// if err != nil {
97
// return err
98
// }
99
// if jsoniter.Get(vRes.Body(), "status").ToInt() != 200 {
100
// return errors.New("ocr error:" + jsoniter.Get(vRes.Body(), "msg").ToString())
101
// }
102
// vCodeRS = jsoniter.Get(vRes.Body(), "result").ToString()
103
// log.Debugln("code: ", vCodeRS)
104
// }
105
// userRsa := RsaEncode([]byte(d.Username), jRsakey, true)
106
// passwordRsa := RsaEncode([]byte(d.Password), jRsakey, true)
107
// url = "https://open.e.189.cn/api/logbox/oauth2/loginSubmit.do"
108
// var loginResp LoginResp
109
// res, err = d.client.R().
110
// SetHeaders(map[string]string{
111
// "lt": lt,
112
// "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36",
113
// "Referer": "https://open.e.189.cn/",
114
// "accept": "application/json;charset=UTF-8",
115
// }).SetFormData(map[string]string{
116
// "appKey": "cloud",
117
// "accountType": "01",
118
// "userName": "{RSA}" + userRsa,
119
// "password": "{RSA}" + passwordRsa,
120
// "validateCode": vCodeRS,
121
// "captchaToken": captchaToken,
122
// "returnUrl": returnUrl,
123
// "mailSuffix": "@pan.cn",
124
// "paramId": paramId,
125
// "clientType": "10010",
126
// "dynamicCheck": "FALSE",
127
// "cb_SaveName": "1",
128
// "isOauth2": "false",
129
// }).Post(url)
130
// if err != nil {
131
// return err
132
// }
133
// err = utils.Json.Unmarshal(res.Body(), &loginResp)
134
// if err != nil {
135
// log.Error(err.Error())
136
// return err
137
// }
138
// if loginResp.Result != 0 {
139
// return fmt.Errorf(loginResp.Msg)
140
// }
141
// _, err = d.client.R().Get(loginResp.ToUrl)
142
// return err
143
//}
144
145
func (d *Cloud189) request(url string, method string, callback base.ReqCallback, resp interface{}) ([]byte, error) {
146
var e Error
147
req := d.client.R().SetError(&e).
148
SetHeader("Accept", "application/json;charset=UTF-8").
149
SetQueryParams(map[string]string{
150
"noCache": random(),
151
})
152
if callback != nil {
153
callback(req)
154
}
155
if resp != nil {
156
req.SetResult(resp)
157
}
158
res, err := req.Execute(method, url)
159
if err != nil {
160
return nil, err
161
}
162
//log.Debug(res.String())
163
if e.ErrorCode != "" {
164
if e.ErrorCode == "InvalidSessionKey" {
165
err = d.newLogin()
166
if err != nil {
167
return nil, err
168
}
169
return d.request(url, method, callback, resp)
170
}
171
}
172
if jsoniter.Get(res.Body(), "res_code").ToInt() != 0 {
173
err = errors.New(jsoniter.Get(res.Body(), "res_message").ToString())
174
}
175
return res.Body(), err
176
}
177
178
func (d *Cloud189) getFiles(fileId string) ([]model.Obj, error) {
179
res := make([]model.Obj, 0)
180
pageNum := 1
181
for {
182
var resp Files
183
_, err := d.request("https://cloud.189.cn/api/open/file/listFiles.action", http.MethodGet, func(req *resty.Request) {
184
req.SetQueryParams(map[string]string{
185
//"noCache": random(),
186
"pageSize": "60",
187
"pageNum": strconv.Itoa(pageNum),
188
"mediaType": "0",
189
"folderId": fileId,
190
"iconOption": "5",
191
"orderBy": "lastOpTime", //account.OrderBy
192
"descending": "true", //account.OrderDirection
193
})
194
}, &resp)
195
if err != nil {
196
return nil, err
197
}
198
if resp.FileListAO.Count == 0 {
199
break
200
}
201
for _, folder := range resp.FileListAO.FolderList {
202
lastOpTime := utils.MustParseCNTime(folder.LastOpTime)
203
res = append(res, &model.Object{
204
ID: strconv.FormatInt(folder.Id, 10),
205
Name: folder.Name,
206
Modified: lastOpTime,
207
IsFolder: true,
208
})
209
}
210
for _, file := range resp.FileListAO.FileList {
211
lastOpTime := utils.MustParseCNTime(file.LastOpTime)
212
res = append(res, &model.ObjThumb{
213
Object: model.Object{
214
ID: strconv.FormatInt(file.Id, 10),
215
Name: file.Name,
216
Modified: lastOpTime,
217
Size: file.Size,
218
},
219
Thumbnail: model.Thumbnail{Thumbnail: file.Icon.SmallUrl},
220
})
221
}
222
pageNum++
223
}
224
return res, nil
225
}
226
227
func (d *Cloud189) sanitizeName(name string) string {
228
if !d.StripEmoji {
229
return name
230
}
231
b := strings.Builder{}
232
for _, r := range name {
233
if utf8.RuneLen(r) == 4 {
234
continue
235
}
236
b.WriteRune(r)
237
}
238
sanitized := b.String()
239
if sanitized == "" {
240
ext := path.Ext(name)
241
if ext != "" {
242
sanitized = "file" + ext
243
} else {
244
sanitized = "file"
245
}
246
}
247
return sanitized
248
}
249
250
func (d *Cloud189) oldUpload(dstDir model.Obj, file model.FileStreamer) error {
251
safeName := d.sanitizeName(file.GetName())
252
res, err := d.client.R().SetMultipartFormData(map[string]string{
253
"parentId": dstDir.GetID(),
254
"sessionKey": "??",
255
"opertype": "1",
256
"fname": safeName,
257
}).SetMultipartField("Filedata", safeName, file.GetMimetype(), file).Post("https://hb02.upload.cloud.189.cn/v1/DCIWebUploadAction")
258
if err != nil {
259
return err
260
}
261
if utils.Json.Get(res.Body(), "MD5").ToString() != "" {
262
return nil
263
}
264
log.Debugf(res.String())
265
return errors.New(res.String())
266
}
267
268
func (d *Cloud189) getSessionKey() (string, error) {
269
resp, err := d.request("https://cloud.189.cn/v2/getUserBriefInfo.action", http.MethodGet, nil, nil)
270
if err != nil {
271
return "", err
272
}
273
sessionKey := utils.Json.Get(resp, "sessionKey").ToString()
274
return sessionKey, nil
275
}
276
277
func (d *Cloud189) getResKey() (string, string, error) {
278
now := time.Now().UnixMilli()
279
if d.rsa.Expire > now {
280
return d.rsa.PubKey, d.rsa.PkId, nil
281
}
282
resp, err := d.request("https://cloud.189.cn/api/security/generateRsaKey.action", http.MethodGet, nil, nil)
283
if err != nil {
284
return "", "", err
285
}
286
pubKey, pkId := utils.Json.Get(resp, "pubKey").ToString(), utils.Json.Get(resp, "pkId").ToString()
287
d.rsa.PubKey, d.rsa.PkId = pubKey, pkId
288
d.rsa.Expire = utils.Json.Get(resp, "expire").ToInt64()
289
return pubKey, pkId, nil
290
}
291
292
func (d *Cloud189) uploadRequest(uri string, form map[string]string, resp interface{}) ([]byte, error) {
293
c := strconv.FormatInt(time.Now().UnixMilli(), 10)
294
r := Random("xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx")
295
l := Random("xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx")
296
l = l[0 : 16+int(16*myrand.Rand.Float32())]
297
298
e := qs(form)
299
data := AesEncrypt([]byte(e), []byte(l[0:16]))
300
h := hex.EncodeToString(data)
301
302
sessionKey := d.sessionKey
303
signature := hmacSha1(fmt.Sprintf("SessionKey=%s&Operate=GET&RequestURI=%s&Date=%s&params=%s", sessionKey, uri, c, h), l)
304
305
pubKey, pkId, err := d.getResKey()
306
if err != nil {
307
return nil, err
308
}
309
b := RsaEncode([]byte(l), pubKey, false)
310
req := d.client.R().SetHeaders(map[string]string{
311
"accept": "application/json;charset=UTF-8",
312
"SessionKey": sessionKey,
313
"Signature": signature,
314
"X-Request-Date": c,
315
"X-Request-ID": r,
316
"EncryptionText": b,
317
"PkId": pkId,
318
})
319
if resp != nil {
320
req.SetResult(resp)
321
}
322
res, err := req.Get("https://upload.cloud.189.cn" + uri + "?params=" + h)
323
if err != nil {
324
return nil, err
325
}
326
data = res.Body()
327
if utils.Json.Get(data, "code").ToString() != "SUCCESS" {
328
return nil, errors.New(uri + "---" + jsoniter.Get(data, "msg").ToString())
329
}
330
return data, nil
331
}
332
333
func (d *Cloud189) newUpload(ctx context.Context, dstDir model.Obj, file model.FileStreamer, up driver.UpdateProgress) error {
334
sessionKey, err := d.getSessionKey()
335
if err != nil {
336
return err
337
}
338
d.sessionKey = sessionKey
339
const DEFAULT int64 = 10485760
340
var count = int64(math.Ceil(float64(file.GetSize()) / float64(DEFAULT)))
341
342
safeName := d.sanitizeName(file.GetName())
343
res, err := d.uploadRequest("/person/initMultiUpload", map[string]string{
344
"parentFolderId": dstDir.GetID(),
345
"fileName": encode(safeName),
346
"fileSize": strconv.FormatInt(file.GetSize(), 10),
347
"sliceSize": strconv.FormatInt(DEFAULT, 10),
348
"lazyCheck": "1",
349
}, nil)
350
if err != nil {
351
return err
352
}
353
uploadFileId := jsoniter.Get(res, "data", "uploadFileId").ToString()
354
//_, err = d.uploadRequest("/person/getUploadedPartsInfo", map[string]string{
355
// "uploadFileId": uploadFileId,
356
//}, nil)
357
var finish int64 = 0
358
var i int64
359
var byteSize int64
360
md5s := make([]string, 0)
361
md5Sum := md5.New()
362
for i = 1; i <= count; i++ {
363
if utils.IsCanceled(ctx) {
364
return ctx.Err()
365
}
366
byteSize = file.GetSize() - finish
367
if DEFAULT < byteSize {
368
byteSize = DEFAULT
369
}
370
//log.Debugf("%d,%d", byteSize, finish)
371
byteData := make([]byte, byteSize)
372
n, err := io.ReadFull(file, byteData)
373
//log.Debug(err, n)
374
if err != nil {
375
return err
376
}
377
finish += int64(n)
378
md5Bytes := getMd5(byteData)
379
md5Hex := hex.EncodeToString(md5Bytes)
380
md5Base64 := base64.StdEncoding.EncodeToString(md5Bytes)
381
md5s = append(md5s, strings.ToUpper(md5Hex))
382
md5Sum.Write(byteData)
383
var resp UploadUrlsResp
384
res, err = d.uploadRequest("/person/getMultiUploadUrls", map[string]string{
385
"partInfo": fmt.Sprintf("%s-%s", strconv.FormatInt(i, 10), md5Base64),
386
"uploadFileId": uploadFileId,
387
}, &resp)
388
if err != nil {
389
return err
390
}
391
uploadData := resp.UploadUrls["partNumber_"+strconv.FormatInt(i, 10)]
392
log.Debugf("uploadData: %+v", uploadData)
393
requestURL := uploadData.RequestURL
394
uploadHeaders := strings.Split(decodeURIComponent(uploadData.RequestHeader), "&")
395
req, err := http.NewRequest(http.MethodPut, requestURL, driver.NewLimitedUploadStream(ctx, bytes.NewReader(byteData)))
396
if err != nil {
397
return err
398
}
399
req = req.WithContext(ctx)
400
for _, v := range uploadHeaders {
401
i := strings.Index(v, "=")
402
req.Header.Set(v[0:i], v[i+1:])
403
}
404
r, err := base.HttpClient.Do(req)
405
if err != nil {
406
return err
407
}
408
log.Debugf("%+v %+v", r, r.Request.Header)
409
_ = r.Body.Close()
410
up(float64(i) * 100 / float64(count))
411
}
412
fileMd5 := hex.EncodeToString(md5Sum.Sum(nil))
413
sliceMd5 := fileMd5
414
if file.GetSize() > DEFAULT {
415
sliceMd5 = utils.GetMD5EncodeStr(strings.Join(md5s, "\n"))
416
}
417
res, err = d.uploadRequest("/person/commitMultiUploadFile", map[string]string{
418
"uploadFileId": uploadFileId,
419
"fileMd5": fileMd5,
420
"sliceMd5": sliceMd5,
421
"lazyCheck": "1",
422
"opertype": "3",
423
}, nil)
424
return err
425
}
426
427