|
package _189 |
|
|
|
import ( |
|
"bytes" |
|
"context" |
|
"crypto/md5" |
|
"encoding/base64" |
|
"encoding/hex" |
|
"errors" |
|
"fmt" |
|
"io" |
|
"math" |
|
"net/http" |
|
"strconv" |
|
"strings" |
|
"time" |
|
|
|
"github.com/alist-org/alist/v3/drivers/base" |
|
"github.com/alist-org/alist/v3/internal/driver" |
|
"github.com/alist-org/alist/v3/internal/model" |
|
"github.com/alist-org/alist/v3/pkg/utils" |
|
myrand "github.com/alist-org/alist/v3/pkg/utils/random" |
|
"github.com/go-resty/resty/v2" |
|
jsoniter "github.com/json-iterator/go" |
|
log "github.com/sirupsen/logrus" |
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func (d *Cloud189) request(url string, method string, callback base.ReqCallback, resp interface{}) ([]byte, error) { |
|
var e Error |
|
req := d.client.R().SetError(&e). |
|
SetHeader("Accept", "application/json;charset=UTF-8"). |
|
SetQueryParams(map[string]string{ |
|
"noCache": random(), |
|
}) |
|
if callback != nil { |
|
callback(req) |
|
} |
|
if resp != nil { |
|
req.SetResult(resp) |
|
} |
|
res, err := req.Execute(method, url) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
if e.ErrorCode != "" { |
|
if e.ErrorCode == "InvalidSessionKey" { |
|
err = d.newLogin() |
|
if err != nil { |
|
return nil, err |
|
} |
|
return d.request(url, method, callback, resp) |
|
} |
|
} |
|
if jsoniter.Get(res.Body(), "res_code").ToInt() != 0 { |
|
err = errors.New(jsoniter.Get(res.Body(), "res_message").ToString()) |
|
} |
|
return res.Body(), err |
|
} |
|
|
|
func (d *Cloud189) getFiles(fileId string) ([]model.Obj, error) { |
|
res := make([]model.Obj, 0) |
|
pageNum := 1 |
|
for { |
|
var resp Files |
|
_, err := d.request("https://cloud.189.cn/api/open/file/listFiles.action", http.MethodGet, func(req *resty.Request) { |
|
req.SetQueryParams(map[string]string{ |
|
|
|
"pageSize": "60", |
|
"pageNum": strconv.Itoa(pageNum), |
|
"mediaType": "0", |
|
"folderId": fileId, |
|
"iconOption": "5", |
|
"orderBy": "lastOpTime", |
|
"descending": "true", |
|
}) |
|
}, &resp) |
|
if err != nil { |
|
return nil, err |
|
} |
|
if resp.FileListAO.Count == 0 { |
|
break |
|
} |
|
for _, folder := range resp.FileListAO.FolderList { |
|
lastOpTime := utils.MustParseCNTime(folder.LastOpTime) |
|
res = append(res, &model.Object{ |
|
ID: strconv.FormatInt(folder.Id, 10), |
|
Name: folder.Name, |
|
Modified: lastOpTime, |
|
IsFolder: true, |
|
}) |
|
} |
|
for _, file := range resp.FileListAO.FileList { |
|
lastOpTime := utils.MustParseCNTime(file.LastOpTime) |
|
res = append(res, &model.ObjThumb{ |
|
Object: model.Object{ |
|
ID: strconv.FormatInt(file.Id, 10), |
|
Name: file.Name, |
|
Modified: lastOpTime, |
|
Size: file.Size, |
|
}, |
|
Thumbnail: model.Thumbnail{Thumbnail: file.Icon.SmallUrl}, |
|
}) |
|
} |
|
pageNum++ |
|
} |
|
return res, nil |
|
} |
|
|
|
func (d *Cloud189) oldUpload(dstDir model.Obj, file model.FileStreamer) error { |
|
res, err := d.client.R().SetMultipartFormData(map[string]string{ |
|
"parentId": dstDir.GetID(), |
|
"sessionKey": "??", |
|
"opertype": "1", |
|
"fname": file.GetName(), |
|
}).SetMultipartField("Filedata", file.GetName(), file.GetMimetype(), file).Post("https://hb02.upload.cloud.189.cn/v1/DCIWebUploadAction") |
|
if err != nil { |
|
return err |
|
} |
|
if utils.Json.Get(res.Body(), "MD5").ToString() != "" { |
|
return nil |
|
} |
|
log.Debugf(res.String()) |
|
return errors.New(res.String()) |
|
} |
|
|
|
func (d *Cloud189) getSessionKey() (string, error) { |
|
resp, err := d.request("https://cloud.189.cn/v2/getUserBriefInfo.action", http.MethodGet, nil, nil) |
|
if err != nil { |
|
return "", err |
|
} |
|
sessionKey := utils.Json.Get(resp, "sessionKey").ToString() |
|
return sessionKey, nil |
|
} |
|
|
|
func (d *Cloud189) getResKey() (string, string, error) { |
|
now := time.Now().UnixMilli() |
|
if d.rsa.Expire > now { |
|
return d.rsa.PubKey, d.rsa.PkId, nil |
|
} |
|
resp, err := d.request("https://cloud.189.cn/api/security/generateRsaKey.action", http.MethodGet, nil, nil) |
|
if err != nil { |
|
return "", "", err |
|
} |
|
pubKey, pkId := utils.Json.Get(resp, "pubKey").ToString(), utils.Json.Get(resp, "pkId").ToString() |
|
d.rsa.PubKey, d.rsa.PkId = pubKey, pkId |
|
d.rsa.Expire = utils.Json.Get(resp, "expire").ToInt64() |
|
return pubKey, pkId, nil |
|
} |
|
|
|
func (d *Cloud189) uploadRequest(uri string, form map[string]string, resp interface{}) ([]byte, error) { |
|
c := strconv.FormatInt(time.Now().UnixMilli(), 10) |
|
r := Random("xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx") |
|
l := Random("xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx") |
|
l = l[0 : 16+int(16*myrand.Rand.Float32())] |
|
|
|
e := qs(form) |
|
data := AesEncrypt([]byte(e), []byte(l[0:16])) |
|
h := hex.EncodeToString(data) |
|
|
|
sessionKey := d.sessionKey |
|
signature := hmacSha1(fmt.Sprintf("SessionKey=%s&Operate=GET&RequestURI=%s&Date=%s¶ms=%s", sessionKey, uri, c, h), l) |
|
|
|
pubKey, pkId, err := d.getResKey() |
|
if err != nil { |
|
return nil, err |
|
} |
|
b := RsaEncode([]byte(l), pubKey, false) |
|
req := d.client.R().SetHeaders(map[string]string{ |
|
"accept": "application/json;charset=UTF-8", |
|
"SessionKey": sessionKey, |
|
"Signature": signature, |
|
"X-Request-Date": c, |
|
"X-Request-ID": r, |
|
"EncryptionText": b, |
|
"PkId": pkId, |
|
}) |
|
if resp != nil { |
|
req.SetResult(resp) |
|
} |
|
res, err := req.Get("https://upload.cloud.189.cn" + uri + "?params=" + h) |
|
if err != nil { |
|
return nil, err |
|
} |
|
data = res.Body() |
|
if utils.Json.Get(data, "code").ToString() != "SUCCESS" { |
|
return nil, errors.New(uri + "---" + jsoniter.Get(data, "msg").ToString()) |
|
} |
|
return data, nil |
|
} |
|
|
|
func (d *Cloud189) newUpload(ctx context.Context, dstDir model.Obj, file model.FileStreamer, up driver.UpdateProgress) error { |
|
sessionKey, err := d.getSessionKey() |
|
if err != nil { |
|
return err |
|
} |
|
d.sessionKey = sessionKey |
|
const DEFAULT int64 = 10485760 |
|
var count = int64(math.Ceil(float64(file.GetSize()) / float64(DEFAULT))) |
|
|
|
res, err := d.uploadRequest("/person/initMultiUpload", map[string]string{ |
|
"parentFolderId": dstDir.GetID(), |
|
"fileName": encode(file.GetName()), |
|
"fileSize": strconv.FormatInt(file.GetSize(), 10), |
|
"sliceSize": strconv.FormatInt(DEFAULT, 10), |
|
"lazyCheck": "1", |
|
}, nil) |
|
if err != nil { |
|
return err |
|
} |
|
uploadFileId := jsoniter.Get(res, "data", "uploadFileId").ToString() |
|
|
|
|
|
|
|
var finish int64 = 0 |
|
var i int64 |
|
var byteSize int64 |
|
md5s := make([]string, 0) |
|
md5Sum := md5.New() |
|
for i = 1; i <= count; i++ { |
|
if utils.IsCanceled(ctx) { |
|
return ctx.Err() |
|
} |
|
byteSize = file.GetSize() - finish |
|
if DEFAULT < byteSize { |
|
byteSize = DEFAULT |
|
} |
|
|
|
byteData := make([]byte, byteSize) |
|
n, err := io.ReadFull(file, byteData) |
|
|
|
if err != nil { |
|
return err |
|
} |
|
finish += int64(n) |
|
md5Bytes := getMd5(byteData) |
|
md5Hex := hex.EncodeToString(md5Bytes) |
|
md5Base64 := base64.StdEncoding.EncodeToString(md5Bytes) |
|
md5s = append(md5s, strings.ToUpper(md5Hex)) |
|
md5Sum.Write(byteData) |
|
var resp UploadUrlsResp |
|
res, err = d.uploadRequest("/person/getMultiUploadUrls", map[string]string{ |
|
"partInfo": fmt.Sprintf("%s-%s", strconv.FormatInt(i, 10), md5Base64), |
|
"uploadFileId": uploadFileId, |
|
}, &resp) |
|
if err != nil { |
|
return err |
|
} |
|
uploadData := resp.UploadUrls["partNumber_"+strconv.FormatInt(i, 10)] |
|
log.Debugf("uploadData: %+v", uploadData) |
|
requestURL := uploadData.RequestURL |
|
uploadHeaders := strings.Split(decodeURIComponent(uploadData.RequestHeader), "&") |
|
req, err := http.NewRequest(http.MethodPut, requestURL, bytes.NewReader(byteData)) |
|
if err != nil { |
|
return err |
|
} |
|
req = req.WithContext(ctx) |
|
for _, v := range uploadHeaders { |
|
i := strings.Index(v, "=") |
|
req.Header.Set(v[0:i], v[i+1:]) |
|
} |
|
r, err := base.HttpClient.Do(req) |
|
log.Debugf("%+v %+v", r, r.Request.Header) |
|
r.Body.Close() |
|
if err != nil { |
|
return err |
|
} |
|
up(float64(i) * 100 / float64(count)) |
|
} |
|
fileMd5 := hex.EncodeToString(md5Sum.Sum(nil)) |
|
sliceMd5 := fileMd5 |
|
if file.GetSize() > DEFAULT { |
|
sliceMd5 = utils.GetMD5EncodeStr(strings.Join(md5s, "\n")) |
|
} |
|
res, err = d.uploadRequest("/person/commitMultiUploadFile", map[string]string{ |
|
"uploadFileId": uploadFileId, |
|
"fileMd5": fileMd5, |
|
"sliceMd5": sliceMd5, |
|
"lazyCheck": "1", |
|
"opertype": "3", |
|
}, nil) |
|
return err |
|
} |
|
|