Compare commits

...

21 Commits

Author SHA1 Message Date
6fd05d7d72 🐛 fix connMap not init 2022-01-30 00:55:12 +08:00
f26ac57569 🐛 fix ftp conn not store 2022-01-30 00:04:31 +08:00
2434ac54d0 🐛 fix webdav 2022-01-29 18:36:22 +08:00
f25b557327 📝 update readme 2022-01-29 15:31:06 +08:00
81a0706d01 189 chunk upload 2022-01-29 11:16:40 +08:00
5f6b576cbf 🔧 switch cdn to jsdelivr 2022-01-28 23:29:38 +08:00
549877f71e 🔥 Delete Text 2022-01-28 23:21:11 +08:00
c6a5ba9b91 🚧 189 chunk upload 2022-01-28 19:51:54 +08:00
1a69d80489 🎨 Pull away Path 2022-01-28 11:04:56 +08:00
b797f4302c 🎇 use tempFile to cal md5 2022-01-27 23:48:29 +08:00
bf9aa5c3d3 🔒 not allowed use relative path of native 2022-01-27 15:10:33 +08:00
7390e19a7a 🔒 not allowed down with relative path 2022-01-27 15:05:17 +08:00
b31a12a0cc 🔒 not allowed access using relative path for native 2022-01-27 14:54:20 +08:00
26ce001782 🎇 add ocr for 189 2022-01-27 12:34:49 +08:00
a2c7ff3262 ✏️ Invalid Token 2022-01-26 16:27:38 +08:00
8fc7c716c0 copy api 2022-01-26 14:07:51 +08:00
c70fc3fc4b finish teambition chunk upload 2022-01-26 14:05:35 +08:00
df513b7dc0 teambition upload (<= 20 MB) 2022-01-23 14:03:04 +08:00
2a9598f4c6 🐛 fix #407 189cloud use local sort 2022-01-21 19:01:33 +08:00
224c20779c 🔧 add webp to image types 2022-01-20 23:01:59 +08:00
5d722298cb 📝 update readme 2022-01-20 22:38:53 +08:00
33 changed files with 939 additions and 313 deletions

View File

@ -50,7 +50,7 @@ English | [中文](./README_cn.md)
- [x] Cloudflare workers proxy - [x] Cloudflare workers proxy
- [x] File/Folder package download - [x] File/Folder package download
- [x] Support video list playback and subtitles(ass,srt,vtt) - [x] Support video list playback and subtitles(ass,srt,vtt)
- [x] Web upload(Can allow visitors to upload) and delete - [x] Web upload(Can allow visitors to upload), delete, mkdir, rename, move and copy
## Discussion ## Discussion

View File

@ -49,7 +49,7 @@
- [x] Cloudflare workers 中转 - [x] Cloudflare workers 中转
- [x] 文件/文件夹打包下载 - [x] 文件/文件夹打包下载
- [x] 支持视频列表播放和字幕(ass,srt,vtt) - [x] 支持视频列表播放和字幕(ass,srt,vtt)
- [x] 网页上传(可以允许访客上传),删除 - [x] 网页上传(可以允许访客上传),删除,新建文件夹,重命名,移动,复制
## 讨论 ## 讨论

View File

@ -19,6 +19,7 @@ func InitAccounts() {
if !ok { if !ok {
log.Errorf("no [%s] driver", account.Type) log.Errorf("no [%s] driver", account.Type)
} else { } else {
log.Infof("start init account: [%s], type: [%s]", account.Name, account.Type)
err := driver.Save(&accounts[i], nil) err := driver.Save(&accounts[i], nil)
if err != nil { if err != nil {
log.Errorf("init account [%s] error:[%s]", account.Name, err.Error()) log.Errorf("init account [%s] error:[%s]", account.Name, err.Error())
@ -27,4 +28,4 @@ func InitAccounts() {
} }
} }
} }
} }

View File

@ -5,6 +5,7 @@ import (
"github.com/Xhofe/alist/utils" "github.com/Xhofe/alist/utils"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"io/ioutil" "io/ioutil"
"os"
) )
// InitConf init config // InitConf init config
@ -41,4 +42,8 @@ func InitConf() {
if err != nil { if err != nil {
log.Fatalf("update config struct error: %s", err.Error()) log.Fatalf("update config struct error: %s", err.Error())
} }
err = os.MkdirAll("data/temp", 0700)
if err != nil {
log.Fatalf("create temp dir error: %s", err.Error())
}
} }

View File

@ -219,6 +219,14 @@ func InitSettings() {
Access: model.PUBLIC, Access: model.PUBLIC,
Group: model.FRONT, Group: model.FRONT,
}, },
{
Key: "ocr api",
Value: "https://api.xhofe.top/ocr/file/json",
Description: "Used to identify verification codes",
Type: "string",
Access: model.PRIVATE,
Group: model.BACK,
},
} }
for i, _ := range settings { for i, _ := range settings {
v := settings[i] v := settings[i]

View File

@ -36,7 +36,7 @@ func DefaultConfig() *Config {
return &Config{ return &Config{
Address: "0.0.0.0", Address: "0.0.0.0",
Port: 5244, Port: 5244,
Assets: "zhimg", Assets: "jsdelivr",
Database: Database{ Database: Database{
Type: "sqlite3", Type: "sqlite3",
Port: 0, Port: 0,

View File

@ -36,10 +36,10 @@ var (
OfficeTypes = []string{"doc", "docx", "xls", "xlsx", "ppt", "pptx", "pdf"} OfficeTypes = []string{"doc", "docx", "xls", "xlsx", "ppt", "pptx", "pdf"}
VideoTypes = []string{"mp4", "mkv", "avi", "mov", "rmvb", "webm", "flv"} VideoTypes = []string{"mp4", "mkv", "avi", "mov", "rmvb", "webm", "flv"}
AudioTypes = []string{"mp3", "flac", "ogg", "m4a", "wav"} AudioTypes = []string{"mp3", "flac", "ogg", "m4a", "wav"}
ImageTypes = []string{"jpg", "tiff", "jpeg", "png", "gif", "bmp", "svg", "ico", "swf"} ImageTypes = []string{"jpg", "tiff", "jpeg", "png", "gif", "bmp", "svg", "ico", "swf", "webp"}
) )
var settingsMap = make(map[string]string, 0) var settingsMap = make(map[string]string)
func Set(key string, value string) { func Set(key string, value string) {
settingsMap[key] = value settingsMap[key] = value
@ -78,6 +78,7 @@ var (
"check parent folder", "check down link", "WebDAV username", "WebDAV password", "check parent folder", "check down link", "WebDAV username", "WebDAV password",
"Visitor WebDAV username", "Visitor WebDAV password", "Visitor WebDAV username", "Visitor WebDAV password",
"default page size", "load type", "default page size", "load type",
"ocr api",
} }
) )

View File

@ -1,6 +1,8 @@
package _23 package _23
import ( import (
"crypto/md5"
"encoding/hex"
"fmt" "fmt"
"github.com/Xhofe/alist/conf" "github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base" "github.com/Xhofe/alist/drivers/base"
@ -12,7 +14,10 @@ import (
"github.com/aws/aws-sdk-go/service/s3/s3manager" "github.com/aws/aws-sdk-go/service/s3/s3manager"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"io"
"io/ioutil"
"net/url" "net/url"
"os"
"path/filepath" "path/filepath"
"strconv" "strconv"
) )
@ -294,10 +299,37 @@ func (driver Pan123) Upload(file *model.FileStream, account *model.Account) erro
return base.ErrNotFolder return base.ErrNotFolder
} }
parentFileId, _ := strconv.Atoi(parentFile.Id) parentFileId, _ := strconv.Atoi(parentFile.Id)
tempFile, err := ioutil.TempFile("data/temp", "file-*")
if err != nil {
return err
}
defer func() {
_ = tempFile.Close()
_ = os.Remove(tempFile.Name())
}()
_, err = io.Copy(tempFile, file)
if err != nil {
return err
}
_, err = tempFile.Seek(0, io.SeekStart)
if err != nil {
return err
}
h := md5.New()
_, err = io.Copy(h, tempFile)
if err != nil {
return err
}
etag := hex.EncodeToString(h.Sum(nil))
log.Debugln("md5:", etag)
_, err = tempFile.Seek(0, io.SeekStart)
if err != nil {
return err
}
data := base.Json{ data := base.Json{
"driveId": 0, "driveId": 0,
"duplicate": true, "duplicate": true,
"etag": "836aae6cac845e17fce51919594737d0", //maybe file's md5 "etag": etag,
"fileName": file.GetFileName(), "fileName": file.GetFileName(),
"parentFileId": parentFileId, "parentFileId": parentFileId,
"size": file.GetSize(), "size": file.GetSize(),
@ -310,6 +342,9 @@ func (driver Pan123) Upload(file *model.FileStream, account *model.Account) erro
if err != nil { if err != nil {
return err return err
} }
if resp.Data.Key == "" {
return nil
}
cfg := &aws.Config{ cfg := &aws.Config{
Credentials: credentials.NewStaticCredentials(resp.Data.AccessKeyId, resp.Data.SecretAccessKey, resp.Data.SessionToken), Credentials: credentials.NewStaticCredentials(resp.Data.AccessKeyId, resp.Data.SecretAccessKey, resp.Data.SessionToken),
Region: aws.String("123pan"), Region: aws.String("123pan"),
@ -324,7 +359,7 @@ func (driver Pan123) Upload(file *model.FileStream, account *model.Account) erro
input := &s3manager.UploadInput{ input := &s3manager.UploadInput{
Bucket: &resp.Data.Bucket, Bucket: &resp.Data.Bucket,
Key: &resp.Data.Key, Key: &resp.Data.Key,
Body: file, Body: tempFile,
} }
_, err = uploader.Upload(input) _, err = uploader.Upload(input)
if err != nil { if err != nil {

View File

@ -2,16 +2,9 @@ package _89
import ( import (
"bytes" "bytes"
"crypto/aes"
"crypto/hmac"
"crypto/md5" "crypto/md5"
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"crypto/x509"
"encoding/base64" "encoding/base64"
"encoding/hex" "encoding/hex"
"encoding/pem"
"errors" "errors"
"fmt" "fmt"
"github.com/Xhofe/alist/conf" "github.com/Xhofe/alist/conf"
@ -19,14 +12,12 @@ import (
"github.com/Xhofe/alist/model" "github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils" "github.com/Xhofe/alist/utils"
"github.com/go-resty/resty/v2" "github.com/go-resty/resty/v2"
"github.com/google/uuid"
jsoniter "github.com/json-iterator/go" jsoniter "github.com/json-iterator/go"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"io" "io"
"math" "math"
mathRand "math/rand" mathRand "math/rand"
"net/http" "net/http"
"net/url"
"path/filepath" "path/filepath"
"regexp" "regexp"
"strconv" "strconv"
@ -35,6 +26,7 @@ import (
) )
var client189Map map[string]*resty.Client var client189Map map[string]*resty.Client
var infoMap = make(map[string]Rsa)
func (driver Cloud189) getClient(account *model.Account) (*resty.Client, error) { func (driver Cloud189) getClient(account *model.Account) (*resty.Client, error) {
client, ok := client189Map[account.Name] client, ok := client189Map[account.Name]
@ -153,9 +145,33 @@ func (driver Cloud189) Login(account *model.Account) error {
vCodeRS := "" vCodeRS := ""
if vCodeID != "" { if vCodeID != "" {
// need ValidateCode // need ValidateCode
log.Debugf("try to identify verification codes")
timeStamp := strconv.FormatInt(time.Now().UnixNano()/1e6, 10)
u := "https://open.e.189.cn/api/logbox/oauth2/picCaptcha.do?token=" + vCodeID + timeStamp
imgRes, err := client.R().SetHeaders(map[string]string{
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:74.0) Gecko/20100101 Firefox/76.0",
"Referer": "https://open.e.189.cn/api/logbox/oauth2/unifyAccountLogin.do",
"Sec-Fetch-Dest": "image",
"Sec-Fetch-Mode": "no-cors",
"Sec-Fetch-Site": "same-origin",
}).Get(u)
if err != nil {
return err
}
vRes, err := client.R().SetMultipartField(
"image", "validateCode.png", "image/png", bytes.NewReader(imgRes.Body())).
Post(conf.GetStr("ocr api"))
if err != nil {
return err
}
if jsoniter.Get(vRes.Body(), "status").ToInt() != 200 {
return errors.New("ocr error:" + jsoniter.Get(vRes.Body(), "msg").ToString())
}
vCodeRS = jsoniter.Get(vRes.Body(), "result").ToString()
log.Debugln("code: ", vCodeRS)
} }
userRsa := RsaEncode([]byte(account.Username), jRsakey) userRsa := RsaEncode([]byte(account.Username), jRsakey, true)
passwordRsa := RsaEncode([]byte(account.Password), jRsakey) passwordRsa := RsaEncode([]byte(account.Password), jRsakey, true)
url = "https://open.e.189.cn/api/logbox/oauth2/loginSubmit.do" url = "https://open.e.189.cn/api/logbox/oauth2/loginSubmit.do"
var loginResp LoginResp var loginResp LoginResp
res, err := client.R(). res, err := client.R().
@ -199,39 +215,6 @@ func (driver Cloud189) Login(account *model.Account) error {
return nil return nil
} }
type Cloud189Error struct {
ErrorCode string `json:"errorCode"`
ErrorMsg string `json:"errorMsg"`
}
type Cloud189File struct {
Id int64 `json:"id"`
LastOpTime string `json:"lastOpTime"`
Name string `json:"name"`
Size int64 `json:"size"`
Icon struct {
SmallUrl string `json:"smallUrl"`
//LargeUrl string `json:"largeUrl"`
} `json:"icon"`
Url string `json:"url"`
}
type Cloud189Folder struct {
Id int64 `json:"id"`
LastOpTime string `json:"lastOpTime"`
Name string `json:"name"`
}
type Cloud189Files struct {
ResCode int `json:"res_code"`
ResMessage string `json:"res_message"`
FileListAO struct {
Count int `json:"count"`
FileList []Cloud189File `json:"fileList"`
FolderList []Cloud189Folder `json:"folderList"`
} `json:"fileListAO"`
}
func (driver Cloud189) isFamily(account *model.Account) bool { func (driver Cloud189) isFamily(account *model.Account) bool {
return account.InternalType == "Family" return account.InternalType == "Family"
} }
@ -249,8 +232,8 @@ func (driver Cloud189) GetFiles(fileId string, account *model.Account) ([]Cloud1
"mediaType": "0", "mediaType": "0",
"folderId": fileId, "folderId": fileId,
"iconOption": "5", "iconOption": "5",
"orderBy": account.OrderBy, "orderBy": "lastOpTime", //account.OrderBy
"descending": account.OrderDirection, "descending": "true", //account.OrderDirection
}, nil, nil, account) }, nil, nil, account)
if err != nil { if err != nil {
return nil, err return nil, err
@ -321,7 +304,7 @@ func (driver Cloud189) Request(url string, method int, query, form map[string]st
if err != nil { if err != nil {
return nil, err return nil, err
} }
log.Debug(res.String()) //log.Debug(res.String())
if e.ErrorCode != "" { if e.ErrorCode != "" {
if e.ErrorCode == "InvalidSessionKey" { if e.ErrorCode == "InvalidSessionKey" {
err = driver.Login(account) err = driver.Login(account)
@ -338,61 +321,198 @@ func (driver Cloud189) Request(url string, method int, query, form map[string]st
} }
func (driver Cloud189) GetSessionKey(account *model.Account) (string, error) { func (driver Cloud189) GetSessionKey(account *model.Account) (string, error) {
//info, ok := infoMap[account.Name]
//if !ok {
// info = Info{}
// infoMap[account.Name] = info
//} else {
// log.Debugf("hit")
//}
//if info.SessionKey != "" {
// return info.SessionKey, nil
//}
resp, err := driver.Request("https://cloud.189.cn/v2/getUserBriefInfo.action", base.Get, nil, nil, nil, account) resp, err := driver.Request("https://cloud.189.cn/v2/getUserBriefInfo.action", base.Get, nil, nil, nil, account)
if err != nil { if err != nil {
return "", err return "", err
} }
return jsoniter.Get(resp, "sessionKey").ToString(), nil sessionKey := jsoniter.Get(resp, "sessionKey").ToString()
//info.SessionKey = sessionKey
return sessionKey, nil
} }
func (driver Cloud189) GetResKey(account *model.Account) (string, string, error) { func (driver Cloud189) GetResKey(account *model.Account) (string, string, error) {
rsa, ok := infoMap[account.Name]
if !ok {
rsa = Rsa{}
infoMap[account.Name] = rsa
}
now := time.Now().UnixMilli()
if rsa.Expire > now {
return rsa.PubKey, rsa.PkId, nil
}
resp, err := driver.Request("https://cloud.189.cn/api/security/generateRsaKey.action", base.Get, nil, nil, nil, account) resp, err := driver.Request("https://cloud.189.cn/api/security/generateRsaKey.action", base.Get, nil, nil, nil, account)
if err != nil { if err != nil {
return "", "", err return "", "", err
} }
return jsoniter.Get(resp, "pubKey").ToString(), jsoniter.Get(resp, "pkId").ToString(), nil pubKey, pkId := jsoniter.Get(resp, "pubKey").ToString(), jsoniter.Get(resp, "pkId").ToString()
rsa.PubKey, rsa.PkId = pubKey, pkId
rsa.Expire = jsoniter.Get(resp, "expire").ToInt64()
return pubKey, pkId, nil
} }
func (driver Cloud189) UploadRequest(url string, form map[string]string, account *model.Account) ([]byte, error) { //func (driver Cloud189) UploadRequest1(uri string, form map[string]string, account *model.Account, resp interface{}) ([]byte, error) {
sessionKey, err := driver.GetSessionKey(account) // //sessionKey, err := driver.GetSessionKey(account)
if err != nil { // //if err != nil {
return nil, err // // return nil, err
} // //}
// sessionKey := account.DriveId
// pubKey, pkId, err := driver.GetResKey(account)
// log.Debugln(sessionKey, pubKey, pkId)
// if err != nil {
// return nil, err
// }
// xRId := Random("xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx")
// pkey := Random("xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx")[0 : 16+int(16*mathRand.Float32())]
// params := hex.EncodeToString(AesEncrypt([]byte(qs(form)), []byte(pkey[0:16])))
// date := strconv.FormatInt(time.Now().Unix(), 10)
// a := make(url.Values)
// a.Set("SessionKey", sessionKey)
// a.Set("Operate", http.MethodGet)
// a.Set("RequestURI", uri)
// a.Set("Date", date)
// a.Set("params", params)
// signature := hex.EncodeToString(SHA1(EncodeParam(a), pkey))
// encryptionText := RsaEncode([]byte(pkey), pubKey, false)
// headers := map[string]string{
// "signature": signature,
// "sessionKey": sessionKey,
// "encryptionText": encryptionText,
// "pkId": pkId,
// "x-request-id": xRId,
// "x-request-date": date,
// }
// req := base.RestyClient.R().SetHeaders(headers).SetQueryParam("params", params)
// if resp != nil {
// req.SetResult(resp)
// }
// res, err := req.Get("https://upload.cloud.189.cn" + uri)
// if err != nil {
// return nil, err
// }
// //log.Debug(res.String())
// data := res.Body()
// if jsoniter.Get(data, "code").ToString() != "SUCCESS" {
// return nil, errors.New(uri + "---" + jsoniter.Get(data, "msg").ToString())
// }
// return data, nil
//}
//
//func (driver Cloud189) UploadRequest2(uri string, form map[string]string, account *model.Account, 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*mathRand.Float32())]
//
// e := qs(form)
// data := AesEncrypt([]byte(e), []byte(l[0:16]))
// h := hex.EncodeToString(data)
//
// sessionKey := account.DriveId
// a := make(url.Values)
// a.Set("SessionKey", sessionKey)
// a.Set("Operate", http.MethodGet)
// a.Set("RequestURI", uri)
// a.Set("Date", c)
// a.Set("params", h)
// g := SHA1(EncodeParam(a), l)
//
// pubKey, pkId, err := driver.GetResKey(account)
// if err != nil {
// return nil, err
// }
// b := RsaEncode([]byte(l), pubKey, false)
// client, err := driver.getClient(account)
// if err != nil {
// return nil, err
// }
// req := client.R()
// req.Header.Set("accept", "application/json;charset=UTF-8")
// req.Header.Set("SessionKey", sessionKey)
// req.Header.Set("Signature", hex.EncodeToString(g))
// req.Header.Set("X-Request-Date", c)
// req.Header.Set("X-Request-ID", r)
// req.Header.Set("EncryptionText", b)
// req.Header.Set("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
// }
// //log.Debug(res.String())
// data = res.Body()
// if jsoniter.Get(data, "code").ToString() != "SUCCESS" {
// return nil, errors.New(uri + "---" + jsoniter.Get(data, "msg").ToString())
// }
// return data, nil
//}
func (driver Cloud189) UploadRequest(uri string, form map[string]string, account *model.Account, 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*mathRand.Float32())]
e := qs(form)
data := AesEncrypt([]byte(e), []byte(l[0:16]))
h := hex.EncodeToString(data)
sessionKey := account.DriveId
signature := hmacSha1(fmt.Sprintf("SessionKey=%s&Operate=GET&RequestURI=%s&Date=%s&params=%s", sessionKey, uri, c, h), l)
pubKey, pkId, err := driver.GetResKey(account) pubKey, pkId, err := driver.GetResKey(account)
if err != nil { if err != nil {
return nil, err return nil, err
} }
xRId := uuid.New().String() b := RsaEncode([]byte(l), pubKey, false)
pkey := strings.ReplaceAll(xRId, "-", "")[:mathRand.Intn(16)+16] client, err := driver.getClient(account)
params := aesEncrypt(qs(form), pkey[:16])
date := strconv.FormatInt(time.Now().Unix(), 10)
signature := hmacSha1(fmt.Sprintf("SessionKey=%s&Operate=GET&RequestURI=%s&Date=%s&params=%s", sessionKey, url, date, params), pkey)
encryptionText := RsaEncode([]byte(pkey), pubKey)
headers := map[string]string{
"signature": signature,
"sessionKey": sessionKey,
"encryptionText": encryptionText,
"pkId": pkId,
"x-request-id": xRId,
"x-request-date": date,
"origin": "https://cloud.189.cn",
"referer": "https://cloud.189.cn/",
}
log.Debugf("%+v\n%s", headers, params)
res, err := base.RestyClient.R().SetHeaders(headers).SetQueryParam("params", params).Get("https://upload.cloud.189.cn" + url)
if err != nil { if err != nil {
return nil, err return nil, err
} }
log.Debug(res.String()) req := client.R()
data := res.Body() req.Header.Set("accept", "application/json;charset=UTF-8")
req.Header.Set("SessionKey", sessionKey)
req.Header.Set("Signature", signature)
req.Header.Set("X-Request-Date", c)
req.Header.Set("X-Request-ID", r)
req.Header.Set("EncryptionText", b)
req.Header.Set("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
}
//log.Debug(res.String())
data = res.Body()
if jsoniter.Get(data, "code").ToString() != "SUCCESS" { if jsoniter.Get(data, "code").ToString() != "SUCCESS" {
return nil, errors.New(jsoniter.Get(data, "msg").ToString()) return nil, errors.New(uri + "---" + jsoniter.Get(data, "msg").ToString())
} }
return data, nil return data, nil
} }
// Upload Error: decrypt encryptionText failed // NewUpload Error: signature check false
func (driver Cloud189) NewUpload(file *model.FileStream, account *model.Account) error { func (driver Cloud189) NewUpload(file *model.FileStream, account *model.Account) error {
sessionKey, err := driver.GetSessionKey(account)
if err != nil {
account.Status = err.Error()
} else {
account.Status = "work"
account.DriveId = sessionKey
}
_ = model.SaveAccount(account)
const DEFAULT uint64 = 10485760 const DEFAULT uint64 = 10485760
var count = int64(math.Ceil(float64(file.GetSize()) / float64(DEFAULT))) var count = int64(math.Ceil(float64(file.GetSize()) / float64(DEFAULT)))
var finish uint64 = 0 var finish uint64 = 0
@ -409,11 +529,14 @@ func (driver Cloud189) NewUpload(file *model.FileStream, account *model.Account)
"fileSize": strconv.FormatInt(int64(file.Size), 10), "fileSize": strconv.FormatInt(int64(file.Size), 10),
"sliceSize": strconv.FormatInt(int64(DEFAULT), 10), "sliceSize": strconv.FormatInt(int64(DEFAULT), 10),
"lazyCheck": "1", "lazyCheck": "1",
}, account) }, account, nil)
if err != nil { if err != nil {
return err return err
} }
uploadFileId := jsoniter.Get(res, "data.uploadFileId").ToString() uploadFileId := jsoniter.Get(res, "data", "uploadFileId").ToString()
//_, err = driver.UploadRequest("/person/getUploadedPartsInfo", map[string]string{
// "uploadFileId": uploadFileId,
//}, account, nil)
var i int64 var i int64
var byteSize uint64 var byteSize uint64
md5s := make([]string, 0) md5s := make([]string, 0)
@ -423,180 +546,82 @@ func (driver Cloud189) NewUpload(file *model.FileStream, account *model.Account)
if DEFAULT < byteSize { if DEFAULT < byteSize {
byteSize = DEFAULT byteSize = DEFAULT
} }
log.Debugf("%d,%d", byteSize, finish) //log.Debugf("%d,%d", byteSize, finish)
byteData := make([]byte, byteSize) byteData := make([]byte, byteSize)
n, err := io.ReadFull(file, byteData) n, err := io.ReadFull(file, byteData)
log.Debug(err, n) //log.Debug(err, n)
if err != nil { if err != nil {
return err return err
} }
finish += uint64(n) finish += uint64(n)
md5Bytes := getMd5(byteData) md5Bytes := getMd5(byteData)
md5Str := hex.EncodeToString(md5Bytes) md5Hex := hex.EncodeToString(md5Bytes)
md5Base64 := base64.StdEncoding.EncodeToString(md5Bytes) md5Base64 := base64.StdEncoding.EncodeToString(md5Bytes)
md5s = append(md5s, md5Str) md5s = append(md5s, strings.ToUpper(md5Hex))
md5Sum.Write(byteData) md5Sum.Write(byteData)
//log.Debugf("md5Bytes: %+v,md5Str:%s,md5Base64:%s", md5Bytes, md5Hex, md5Base64)
var resp UploadUrlsResp
res, err = driver.UploadRequest("/person/getMultiUploadUrls", map[string]string{ res, err = driver.UploadRequest("/person/getMultiUploadUrls", map[string]string{
"partInfo": fmt.Sprintf("%s-%s", strconv.FormatInt(i, 10), md5Base64), "partInfo": fmt.Sprintf("%s-%s", strconv.FormatInt(i, 10), md5Base64),
"uploadFileId": uploadFileId, "uploadFileId": uploadFileId,
}, account) }, account, &resp)
if err != nil { if err != nil {
return err return err
} }
uploadData := jsoniter.Get(res, "uploadUrls.partNumber_"+strconv.FormatInt(i, 10)) uploadData := resp.UploadUrls["partNumber_"+strconv.FormatInt(i, 10)]
headers := strings.Split(uploadData.Get("requestHeader").ToString(), "&") log.Debugf("uploadData: %+v", uploadData)
req, err := http.NewRequest("PUT", uploadData.Get("requestURL").ToString(), bytes.NewBuffer(byteData)) requestURL := uploadData.RequestURL
uploadHeaders := strings.Split(decodeURIComponent(uploadData.RequestHeader), "&")
req, _ := http.NewRequest(http.MethodPut, requestURL, bytes.NewReader(byteData))
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)
if err != nil { if err != nil {
return err return err
} }
for _, header := range headers {
kv := strings.Split(header, "=")
req.Header.Set(kv[0], strings.Join(kv[1:], "="))
}
res, err := base.HttpClient.Do(req)
if err != nil {
return err
}
log.Debugf("%+v", res)
} }
id := md5Sum.Sum(nil) fileMd5 := hex.EncodeToString(md5Sum.Sum(nil))
sliceMd5 := fileMd5
if file.GetSize() > DEFAULT {
sliceMd5 = utils.GetMD5Encode(strings.Join(md5s, "\n"))
}
res, err = driver.UploadRequest("/person/commitMultiUploadFile", map[string]string{ res, err = driver.UploadRequest("/person/commitMultiUploadFile", map[string]string{
"uploadFileId": uploadFileId, "uploadFileId": uploadFileId,
"fileMd5": hex.EncodeToString(id), "fileMd5": fileMd5,
"sliceMd5": utils.GetMD5Encode(strings.Join(md5s, "\n")), "sliceMd5": sliceMd5,
"lazyCheck": "1", "lazyCheck": "1",
}, account) }, account, nil)
account.DriveId, _ = driver.GetSessionKey(account)
return err return err
} }
func random() string { func (driver Cloud189) OldUpload(file *model.FileStream, account *model.Account) error {
return fmt.Sprintf("0.%17v", mathRand.New(mathRand.NewSource(time.Now().UnixNano())).Int63n(100000000000000000)) //return base.ErrNotImplement
} client, err := driver.getClient(account)
func RsaEncode(origData []byte, j_rsakey string) string {
publicKey := []byte("-----BEGIN PUBLIC KEY-----\n" + j_rsakey + "\n-----END PUBLIC KEY-----")
block, _ := pem.Decode(publicKey)
pubInterface, _ := x509.ParsePKIXPublicKey(block.Bytes)
pub := pubInterface.(*rsa.PublicKey)
b, err := rsa.EncryptPKCS1v15(rand.Reader, pub, origData)
if err != nil { if err != nil {
log.Errorf("err: %s", err.Error()) return err
} }
return b64tohex(base64.StdEncoding.EncodeToString(b)) parentFile, err := driver.File(file.ParentPath, account)
} if err != nil {
return err
var b64map = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
var BI_RM = "0123456789abcdefghijklmnopqrstuvwxyz"
func int2char(a int) string {
return strings.Split(BI_RM, "")[a]
}
func b64tohex(a string) string {
d := ""
e := 0
c := 0
for i := 0; i < len(a); i++ {
m := strings.Split(a, "")[i]
if m != "=" {
v := strings.Index(b64map, m)
if 0 == e {
e = 1
d += int2char(v >> 2)
c = 3 & v
} else if 1 == e {
e = 2
d += int2char(c<<2 | v>>4)
c = 15 & v
} else if 2 == e {
e = 3
d += int2char(c)
d += int2char(v >> 2)
c = 3 & v
} else {
e = 0
d += int2char(c<<2 | v>>4)
d += int2char(15 & v)
}
}
} }
if e == 1 { // api refer to PanIndex
d += int2char(c << 2) res, err := client.R().SetMultipartFormData(map[string]string{
"parentId": parentFile.Id,
"sessionKey": account.DriveId,
"opertype": "1",
"fname": file.GetFileName(),
}).SetMultipartField("Filedata", file.GetFileName(), file.GetMIMEType(), file).Post("https://hb02.upload.cloud.189.cn/v1/DCIWebUploadAction")
if err != nil {
return err
} }
return d if jsoniter.Get(res.Body(), "MD5").ToString() != "" {
} return nil
func qs(form map[string]string) string {
strList := make([]string, 0)
for k, v := range form {
strList = append(strList, fmt.Sprintf("%s=%s", k, url.QueryEscape(v)))
} }
return strings.Join(strList, "&") log.Debugf(res.String())
} return errors.New(res.String())
func aesEncrypt(data, key string) string {
encrypted := AesEncryptECB([]byte(data), []byte(key))
//return string(encrypted)
return hex.EncodeToString(encrypted)
}
func hmacSha1(data string, secret string) string {
h := hmac.New(sha1.New, []byte(secret))
h.Write([]byte(data))
return hex.EncodeToString(h.Sum(nil))
}
func AesEncryptECB(origData []byte, key []byte) (encrypted []byte) {
cipher, _ := aes.NewCipher(generateKey(key))
length := (len(origData) + aes.BlockSize) / aes.BlockSize
plain := make([]byte, length*aes.BlockSize)
copy(plain, origData)
pad := byte(len(plain) - len(origData))
for i := len(origData); i < len(plain); i++ {
plain[i] = pad
}
encrypted = make([]byte, len(plain))
// 分组分块加密
for bs, be := 0, cipher.BlockSize(); bs <= len(origData); bs, be = bs+cipher.BlockSize(), be+cipher.BlockSize() {
cipher.Encrypt(encrypted[bs:be], plain[bs:be])
}
return encrypted
}
func AesDecryptECB(encrypted []byte, key []byte) (decrypted []byte) {
cipher, _ := aes.NewCipher(generateKey(key))
decrypted = make([]byte, len(encrypted))
//
for bs, be := 0, cipher.BlockSize(); bs < len(encrypted); bs, be = bs+cipher.BlockSize(), be+cipher.BlockSize() {
cipher.Decrypt(decrypted[bs:be], encrypted[bs:be])
}
trim := 0
if len(decrypted) > 0 {
trim = len(decrypted) - int(decrypted[len(decrypted)-1])
}
return decrypted[:trim]
}
func generateKey(key []byte) (genKey []byte) {
genKey = make([]byte, 16)
copy(genKey, key)
for i := 16; i < len(key); {
for j := 0; j < 16 && i < len(key); j, i = j+1, i+1 {
genKey[j] ^= key[i]
}
}
return genKey
}
func getMd5(data []byte) []byte {
h := md5.New()
h.Write(data)
return h.Sum(nil)
}
func init() {
base.RegisterDriver(&Cloud189{})
client189Map = make(map[string]*resty.Client, 0)
} }

View File

@ -1,14 +1,12 @@
package _89 package _89
import ( import (
"errors"
"fmt" "fmt"
"github.com/Xhofe/alist/conf" "github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base" "github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model" "github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils" "github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
jsoniter "github.com/json-iterator/go"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"path/filepath" "path/filepath"
) )
@ -17,7 +15,8 @@ type Cloud189 struct{}
func (driver Cloud189) Config() base.DriverConfig { func (driver Cloud189) Config() base.DriverConfig {
return base.DriverConfig{ return base.DriverConfig{
Name: "189Cloud", Name: "189Cloud",
LocalSort: true,
} }
} }
@ -55,20 +54,20 @@ func (driver Cloud189) Items() []base.Item {
// Label: "family id", // Label: "family id",
// Type: base.TypeString, // Type: base.TypeString,
//}, //},
{ //{
Name: "order_by", // Name: "order_by",
Label: "order_by", // Label: "order_by",
Type: base.TypeSelect, // Type: base.TypeSelect,
Values: "name,size,lastOpTime,createdDate", // Values: "name,size,lastOpTime,createdDate",
Required: true, // Required: true,
}, //},
{ //{
Name: "order_direction", // Name: "order_direction",
Label: "desc", // Label: "desc",
Type: base.TypeSelect, // Type: base.TypeSelect,
Values: "true,false", // Values: "true,false",
Required: true, // Required: true,
}, //},
} }
} }
@ -349,29 +348,8 @@ func (driver Cloud189) Upload(file *model.FileStream, account *model.Account) er
if file == nil { if file == nil {
return base.ErrEmptyFile return base.ErrEmptyFile
} }
client, err := driver.getClient(account) return driver.NewUpload(file, account)
if err != nil { //return driver.OldUpload(file, account)
return err
}
parentFile, err := driver.File(file.ParentPath, account)
if err != nil {
return err
}
// api refer to PanIndex
res, err := client.R().SetMultipartFormData(map[string]string{
"parentId": parentFile.Id,
"sessionKey": account.DriveId,
"opertype": "1",
"fname": file.GetFileName(),
}).SetMultipartField("Filedata", file.GetFileName(), file.GetMIMEType(), file).Post("https://hb02.upload.cloud.189.cn/v1/DCIWebUploadAction")
if err != nil {
return err
}
if jsoniter.Get(res.Body(), "MD5").ToString() != "" {
return nil
}
log.Debugf(res.String())
return errors.New(res.String())
} }
var _ base.Driver = (*Cloud189)(nil) var _ base.Driver = (*Cloud189)(nil)

55
drivers/189/types.go Normal file
View File

@ -0,0 +1,55 @@
package _89
type Cloud189Error struct {
ErrorCode string `json:"errorCode"`
ErrorMsg string `json:"errorMsg"`
}
type Cloud189File struct {
Id int64 `json:"id"`
LastOpTime string `json:"lastOpTime"`
Name string `json:"name"`
Size int64 `json:"size"`
Icon struct {
SmallUrl string `json:"smallUrl"`
//LargeUrl string `json:"largeUrl"`
} `json:"icon"`
Url string `json:"url"`
}
type Cloud189Folder struct {
Id int64 `json:"id"`
LastOpTime string `json:"lastOpTime"`
Name string `json:"name"`
}
type Cloud189Files struct {
ResCode int `json:"res_code"`
ResMessage string `json:"res_message"`
FileListAO struct {
Count int `json:"count"`
FileList []Cloud189File `json:"fileList"`
FolderList []Cloud189Folder `json:"folderList"`
} `json:"fileListAO"`
}
type UploadUrlsResp struct {
Code string `json:"code"`
UploadUrls map[string]Part `json:"uploadUrls"`
}
type Part struct {
RequestURL string `json:"requestURL"`
RequestHeader string `json:"requestHeader"`
}
//type Info struct {
// SessionKey string
// Rsa Rsa
//}
type Rsa struct {
Expire int64 `json:"expire"`
PkId string `json:"pkId"`
PubKey string `json:"pubKey"`
}

188
drivers/189/util.go Normal file
View File

@ -0,0 +1,188 @@
package _89
import (
"bytes"
"crypto/aes"
"crypto/hmac"
"crypto/md5"
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"crypto/x509"
"encoding/base64"
"encoding/hex"
"encoding/pem"
"fmt"
"github.com/Xhofe/alist/drivers/base"
"github.com/go-resty/resty/v2"
log "github.com/sirupsen/logrus"
mathRand "math/rand"
"net/url"
"regexp"
"strconv"
"strings"
"time"
)
func random() string {
return fmt.Sprintf("0.%17v", mathRand.New(mathRand.NewSource(time.Now().UnixNano())).Int63n(100000000000000000))
}
func RsaEncode(origData []byte, j_rsakey string, hex bool) string {
publicKey := []byte("-----BEGIN PUBLIC KEY-----\n" + j_rsakey + "\n-----END PUBLIC KEY-----")
block, _ := pem.Decode(publicKey)
pubInterface, _ := x509.ParsePKIXPublicKey(block.Bytes)
pub := pubInterface.(*rsa.PublicKey)
b, err := rsa.EncryptPKCS1v15(rand.Reader, pub, origData)
if err != nil {
log.Errorf("err: %s", err.Error())
}
res := base64.StdEncoding.EncodeToString(b)
if hex {
return b64tohex(res)
}
return res
}
var b64map = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
var BI_RM = "0123456789abcdefghijklmnopqrstuvwxyz"
func int2char(a int) string {
return strings.Split(BI_RM, "")[a]
}
func b64tohex(a string) string {
d := ""
e := 0
c := 0
for i := 0; i < len(a); i++ {
m := strings.Split(a, "")[i]
if m != "=" {
v := strings.Index(b64map, m)
if 0 == e {
e = 1
d += int2char(v >> 2)
c = 3 & v
} else if 1 == e {
e = 2
d += int2char(c<<2 | v>>4)
c = 15 & v
} else if 2 == e {
e = 3
d += int2char(c)
d += int2char(v >> 2)
c = 3 & v
} else {
e = 0
d += int2char(c<<2 | v>>4)
d += int2char(15 & v)
}
}
}
if e == 1 {
d += int2char(c << 2)
}
return d
}
func qs(form map[string]string) string {
f := make(url.Values)
for k, v := range form {
f.Set(k, v)
}
return EncodeParam(f)
//strList := make([]string, 0)
//for k, v := range form {
// strList = append(strList, fmt.Sprintf("%s=%s", k, url.QueryEscape(v)))
//}
//return strings.Join(strList, "&")
}
func EncodeParam(v url.Values) string {
if v == nil {
return ""
}
var buf strings.Builder
keys := make([]string, 0, len(v))
for k := range v {
keys = append(keys, k)
}
for _, k := range keys {
vs := v[k]
for _, v := range vs {
if buf.Len() > 0 {
buf.WriteByte('&')
}
buf.WriteString(k)
buf.WriteByte('=')
buf.WriteString(v)
}
}
return buf.String()
}
func AesEncrypt(data, key []byte) []byte {
block, _ := aes.NewCipher(key)
if block == nil {
return []byte{}
}
data = PKCS7Padding(data, block.BlockSize())
decrypted := make([]byte, len(data))
size := block.BlockSize()
for bs, be := 0, size; bs < len(data); bs, be = bs+size, be+size {
block.Encrypt(decrypted[bs:be], data[bs:be])
}
return decrypted
}
func PKCS7Padding(ciphertext []byte, blockSize int) []byte {
padding := blockSize - len(ciphertext)%blockSize
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
return append(ciphertext, padtext...)
}
func hmacSha1(data string, secret string) string {
h := hmac.New(sha1.New, []byte(secret))
h.Write([]byte(data))
return hex.EncodeToString(h.Sum(nil))
}
func getMd5(data []byte) []byte {
h := md5.New()
h.Write(data)
return h.Sum(nil)
}
func init() {
base.RegisterDriver(&Cloud189{})
client189Map = make(map[string]*resty.Client)
}
func decodeURIComponent(str string) string {
r, _ := url.PathUnescape(str)
//r = strings.ReplaceAll(r, " ", "+")
return r
}
func Random(v string) string {
reg := regexp.MustCompilePOSIX("[xy]")
data := reg.ReplaceAllFunc([]byte(v), func(msg []byte) []byte {
var i int64
t := int64(16 * mathRand.Float32())
if msg[0] == 120 {
i = t
} else {
i = 3&t | 8
}
return []byte(strconv.FormatInt(i, 16))
})
return string(data)
}
//func SHA1(v, l string) []byte {
// key := []byte(l)
// mac := hmac.New(sha1.New, key)
// mac.Write([]byte(v))
// return mac.Sum(nil)
//}

View File

@ -84,7 +84,7 @@ func GetDriversMap() map[string]Driver {
} }
func GetDrivers() map[string][]Item { func GetDrivers() map[string][]Item {
res := make(map[string][]Item, 0) res := make(map[string][]Item)
for k, v := range driversMap { for k, v := range driversMap {
if v.Config().OnlyProxy { if v.Config().OnlyProxy {
res[k] = v.Items() res[k] = v.Items()

View File

@ -12,6 +12,7 @@ var (
ErrNotSupport = errors.New("not support") ErrNotSupport = errors.New("not support")
ErrNotFolder = errors.New("not a folder") ErrNotFolder = errors.New("not a folder")
ErrEmptyFile = errors.New("empty file") ErrEmptyFile = errors.New("empty file")
ErrRelativePath = errors.New("access using relative path is not allowed")
) )
const ( const (

View File

@ -21,9 +21,11 @@ func (driver FTP) Login(account *model.Account) (*ftp.ServerConn, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
connMap[account.Name] = conn
return conn, nil return conn, nil
} }
func init() { func init() {
base.RegisterDriver(&FTP{}) base.RegisterDriver(&FTP{})
connMap = make(map[string]*ftp.ServerConn)
} }

View File

@ -1,7 +1,6 @@
package mediatrack package mediatrack
import ( import (
"bytes"
"crypto/md5" "crypto/md5"
"encoding/hex" "encoding/hex"
"fmt" "fmt"
@ -18,6 +17,8 @@ import (
jsoniter "github.com/json-iterator/go" jsoniter "github.com/json-iterator/go"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"io" "io"
"io/ioutil"
"os"
"path/filepath" "path/filepath"
) )
@ -265,21 +266,39 @@ func (driver MediaTrack) Upload(file *model.FileStream, account *model.Account)
if err != nil { if err != nil {
return err return err
} }
var buf bytes.Buffer tempFile, err := ioutil.TempFile("data/temp", "file-*")
read := io.TeeReader(file, &buf) if err != nil {
return err
}
defer func() {
_ = tempFile.Close()
_ = os.Remove(tempFile.Name())
}()
_, err = io.Copy(tempFile, file)
if err != nil {
return err
}
_, err = tempFile.Seek(0, io.SeekStart)
if err != nil {
return err
}
uploader := s3manager.NewUploader(s) uploader := s3manager.NewUploader(s)
input := &s3manager.UploadInput{ input := &s3manager.UploadInput{
Bucket: &resp.Data.Bucket, Bucket: &resp.Data.Bucket,
Key: &resp.Data.Object, Key: &resp.Data.Object,
Body: read, Body: tempFile,
} }
_, err = uploader.Upload(input) _, err = uploader.Upload(input)
if err != nil { if err != nil {
return err return err
} }
url := fmt.Sprintf("https://jayce.api.mediatrack.cn/v3/assets/%s/children", parentFile.Id) url := fmt.Sprintf("https://jayce.api.mediatrack.cn/v3/assets/%s/children", parentFile.Id)
_, err = tempFile.Seek(0, io.SeekStart)
if err != nil {
return err
}
h := md5.New() h := md5.New()
_, err = io.Copy(h, &buf) _, err = io.Copy(h, tempFile)
if err != nil { if err != nil {
return err return err
} }

View File

@ -58,6 +58,9 @@ func (driver Native) Save(account *model.Account, old *model.Account) error {
} }
func (driver Native) File(path string, account *model.Account) (*model.File, error) { func (driver Native) File(path string, account *model.Account) (*model.File, error) {
if utils.IsContain(strings.Split(path, "/"), "..") {
return nil, base.ErrRelativePath
}
fullPath := filepath.Join(account.RootFolder, path) fullPath := filepath.Join(account.RootFolder, path)
if !utils.Exists(fullPath) { if !utils.Exists(fullPath) {
return nil, base.ErrPathNotFound return nil, base.ErrPathNotFound
@ -82,6 +85,9 @@ func (driver Native) File(path string, account *model.Account) (*model.File, err
} }
func (driver Native) Files(path string, account *model.Account) ([]model.File, error) { func (driver Native) Files(path string, account *model.Account) ([]model.File, error) {
if utils.IsContain(strings.Split(path, "/"), "..") {
return nil, base.ErrRelativePath
}
fullPath := filepath.Join(account.RootFolder, path) fullPath := filepath.Join(account.RootFolder, path)
if !utils.Exists(fullPath) { if !utils.Exists(fullPath) {
return nil, base.ErrPathNotFound return nil, base.ErrPathNotFound
@ -114,6 +120,10 @@ func (driver Native) Files(path string, account *model.Account) ([]model.File, e
} }
func (driver Native) Link(args base.Args, account *model.Account) (*base.Link, error) { func (driver Native) Link(args base.Args, account *model.Account) (*base.Link, error) {
_, err := driver.File(args.Path, account)
if err != nil {
return nil, err
}
fullPath := filepath.Join(account.RootFolder, args.Path) fullPath := filepath.Join(account.RootFolder, args.Path)
s, err := os.Stat(fullPath) s, err := os.Stat(fullPath)
if err != nil { if err != nil {
@ -155,12 +165,18 @@ func (driver Native) Preview(path string, account *model.Account) (interface{},
} }
func (driver Native) MakeDir(path string, account *model.Account) error { func (driver Native) MakeDir(path string, account *model.Account) error {
if utils.IsContain(strings.Split(path, "/"), "..") {
return base.ErrRelativePath
}
fullPath := filepath.Join(account.RootFolder, path) fullPath := filepath.Join(account.RootFolder, path)
err := os.MkdirAll(fullPath, 0700) err := os.MkdirAll(fullPath, 0700)
return err return err
} }
func (driver Native) Move(src string, dst string, account *model.Account) error { func (driver Native) Move(src string, dst string, account *model.Account) error {
if utils.IsContain(strings.Split(src+"/"+dst, "/"), "..") {
return base.ErrRelativePath
}
fullSrc := filepath.Join(account.RootFolder, src) fullSrc := filepath.Join(account.RootFolder, src)
fullDst := filepath.Join(account.RootFolder, dst) fullDst := filepath.Join(account.RootFolder, dst)
return os.Rename(fullSrc, fullDst) return os.Rename(fullSrc, fullDst)
@ -171,6 +187,9 @@ func (driver Native) Rename(src string, dst string, account *model.Account) erro
} }
func (driver Native) Copy(src string, dst string, account *model.Account) error { func (driver Native) Copy(src string, dst string, account *model.Account) error {
if utils.IsContain(strings.Split(src+"/"+dst, "/"), "..") {
return base.ErrRelativePath
}
fullSrc := filepath.Join(account.RootFolder, src) fullSrc := filepath.Join(account.RootFolder, src)
fullDst := filepath.Join(account.RootFolder, dst) fullDst := filepath.Join(account.RootFolder, dst)
srcFile, err := driver.File(src, account) srcFile, err := driver.File(src, account)
@ -190,6 +209,9 @@ func (driver Native) Copy(src string, dst string, account *model.Account) error
} }
func (driver Native) Delete(path string, account *model.Account) error { func (driver Native) Delete(path string, account *model.Account) error {
if utils.IsContain(strings.Split(path, "/"), "..") {
return base.ErrRelativePath
}
fullPath := filepath.Join(account.RootFolder, path) fullPath := filepath.Join(account.RootFolder, path)
file, err := driver.File(path, account) file, err := driver.File(path, account)
if err != nil { if err != nil {
@ -205,6 +227,9 @@ func (driver Native) Upload(file *model.FileStream, account *model.Account) erro
if file == nil { if file == nil {
return base.ErrEmptyFile return base.ErrEmptyFile
} }
if utils.IsContain(strings.Split(file.ParentPath, "/"), "..") {
return base.ErrRelativePath
}
fullPath := filepath.Join(account.RootFolder, file.ParentPath, file.Name) fullPath := filepath.Join(account.RootFolder, file.ParentPath, file.Name)
_, err := driver.File(filepath.Join(file.ParentPath, file.Name), account) _, err := driver.File(filepath.Join(file.ParentPath, file.Name), account)
if err == nil { if err == nil {
@ -224,6 +249,16 @@ func (driver Native) Upload(file *model.FileStream, account *model.Account) erro
defer func() { defer func() {
_ = out.Close() _ = out.Close()
}() }()
//var buf bytes.Buffer
//reader := io.TeeReader(file, &buf)
//h := md5.New()
//_, err = io.Copy(h, reader)
//if err != nil {
// return err
//}
//hash := hex.EncodeToString(h.Sum(nil))
//log.Debugln("md5:", hash)
//_, err = io.Copy(out, &buf)
_, err = io.Copy(out, file) _, err = io.Copy(out, file)
return err return err
} }

View File

@ -5,8 +5,28 @@ import (
"github.com/Xhofe/alist/model" "github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils" "github.com/Xhofe/alist/utils"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"runtime/debug"
) )
func Path(driver base.Driver, account *model.Account, path string) (*model.File, []model.File, error) {
return driver.Path(path, account)
}
func Files(driver base.Driver, account *model.Account, path string) ([]model.File, error) {
_, files, err := Path(driver, account, path)
if err != nil {
return nil, err
}
if files == nil {
return nil, base.ErrNotFolder
}
return files, nil
}
func File(driver base.Driver, account *model.Account, path string) (*model.File, error) {
return driver.File(path, account)
}
func MakeDir(driver base.Driver, account *model.Account, path string, clearCache bool) error { func MakeDir(driver base.Driver, account *model.Account, path string, clearCache bool) error {
log.Debugf("mkdir: %s", path) log.Debugf("mkdir: %s", path)
err := driver.MakeDir(path, account) err := driver.MakeDir(path, account)
@ -78,5 +98,6 @@ func Upload(driver base.Driver, account *model.Account, file *model.FileStream,
if err != nil { if err != nil {
log.Errorf("upload error: %s", err.Error()) log.Errorf("upload error: %s", err.Error())
} }
debug.FreeOSMemory()
return err return err
} }

View File

@ -118,6 +118,6 @@ func (driver S3) GetKey(path string, account *model.Account, dir bool) string {
} }
func init() { func init() {
sessionsMap = make(map[string]*session.Session, 0) sessionsMap = make(map[string]*session.Session)
base.RegisterDriver(&S3{}) base.RegisterDriver(&S3{})
} }

View File

@ -252,7 +252,34 @@ func (driver Teambition) Delete(path string, account *model.Account) error {
} }
func (driver Teambition) Upload(file *model.FileStream, account *model.Account) error { func (driver Teambition) Upload(file *model.FileStream, account *model.Account) error {
return base.ErrNotImplement if file == nil {
return base.ErrEmptyFile
}
parentFile, err := driver.File(file.ParentPath, account)
if !parentFile.IsDir() {
return base.ErrNotFolder
}
if err != nil {
return err
}
res, err := driver.Request("/projects", base.Get, nil, nil, nil, nil, nil, account)
if err != nil {
return err
}
token := GetBetweenStr(string(res), "strikerAuth&quot;:&quot;", "&quot;,&quot;phoneForLogin")
var newFile *FileUpload
if file.Size <= 20971520 {
// post upload
newFile, err = driver.upload(file, token, account)
} else {
// chunk upload
//err = base.ErrNotImplement
newFile, err = driver.chunkUpload(file, token, account)
}
if err != nil {
return err
}
return driver.finishUpload(newFile, parentFile.Id, account)
} }
var _ base.Driver = (*Teambition)(nil) var _ base.Driver = (*Teambition)(nil)

View File

@ -2,11 +2,14 @@ package teambition
import ( import (
"errors" "errors"
"fmt"
"github.com/Xhofe/alist/conf" "github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base" "github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model" "github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils" "github.com/Xhofe/alist/utils"
"github.com/go-resty/resty/v2" "github.com/go-resty/resty/v2"
log "github.com/sirupsen/logrus"
"io"
"path" "path"
"strconv" "strconv"
"time" "time"
@ -66,25 +69,6 @@ func (driver Teambition) Request(pathname string, method int, headers, query, fo
return res.Body(), nil return res.Body(), nil
} }
type Collection struct {
ID string `json:"_id"`
Title string `json:"title"`
Updated time.Time `json:"updated"`
}
type Work struct {
ID string `json:"_id"`
FileName string `json:"fileName"`
FileSize int64 `json:"fileSize"`
FileKey string `json:"fileKey"`
FileCategory string `json:"fileCategory"`
DownloadURL string `json:"downloadUrl"`
ThumbnailURL string `json:"thumbnailUrl"`
Thumbnail string `json:"thumbnail"`
Updated time.Time `json:"updated"`
PreviewURL string `json:"previewUrl"`
}
func (driver Teambition) GetFiles(parentId string, account *model.Account) ([]model.File, error) { func (driver Teambition) GetFiles(parentId string, account *model.Account) ([]model.File, error) {
files := make([]model.File, 0) files := make([]model.File, 0)
page := 1 page := 1
@ -151,6 +135,101 @@ func (driver Teambition) GetFiles(parentId string, account *model.Account) ([]mo
return files, nil return files, nil
} }
func (driver Teambition) upload(file *model.FileStream, token string, account *model.Account) (*FileUpload, error) {
prefix := "tcs"
if account.InternalType == "International" {
prefix = "us-tcs"
}
var newFile FileUpload
_, err := base.RestyClient.R().SetResult(&newFile).SetHeader("Authorization", token).
SetMultipartFormData(map[string]string{
"name": file.GetFileName(),
"type": file.GetMIMEType(),
"size": strconv.FormatUint(file.GetSize(), 10),
//"lastModifiedDate": "",
}).SetMultipartField("file", file.GetFileName(), file.GetMIMEType(), file).
Post(fmt.Sprintf("https://%s.teambition.net/upload", prefix))
if err != nil {
return nil, err
}
return &newFile, nil
}
func (driver Teambition) chunkUpload(file *model.FileStream, token string, account *model.Account) (*FileUpload, error) {
prefix := "tcs"
if account.InternalType == "International" {
prefix = "us-tcs"
}
var newChunk ChunkUpload
_, err := base.RestyClient.R().SetResult(&newChunk).SetHeader("Authorization", token).
SetBody(base.Json{
"fileName": file.GetFileName(),
"fileSize": file.GetSize(),
"lastUpdated": time.Now(),
}).Post(fmt.Sprintf("https://%s.teambition.net/upload/chunk", prefix))
if err != nil {
return nil, err
}
for i := 0; i < newChunk.Chunks; i++ {
chunkSize := newChunk.ChunkSize
if i == newChunk.Chunks-1 {
chunkSize = int(file.GetSize()) - i*chunkSize
}
log.Debugf("%d : %d", i, chunkSize)
chunkData := make([]byte, chunkSize)
_, err = io.ReadFull(file, chunkData)
if err != nil {
return nil, err
}
u := fmt.Sprintf("https://%s.teambition.net/upload/chunk/%s?chunk=%d&chunks=%d",
prefix, newChunk.FileKey, i+1, newChunk.Chunks)
log.Debugf("url: %s", u)
res, err := base.RestyClient.R().SetHeaders(map[string]string{
"Authorization": token,
"Content-Type": "application/octet-stream",
"Referer": "https://www.teambition.com/",
}).SetBody(chunkData).Post(u)
if err != nil {
return nil, err
}
log.Debug(res.Status(), res.String())
//req, err := http.NewRequest("POST",
// u,
// bytes.NewBuffer(chunkData))
//if err != nil {
// return nil, err
//}
//req.Header.Set("Authorization", token)
//req.Header.Set("Content-Type", "application/octet-stream")
//req.Header.Set("Referer", "https://www.teambition.com/")
//resp, err := base.HttpClient.Do(req)
//res, _ := ioutil.ReadAll(resp.Body)
//log.Debugf("chunk upload status: %s, res: %s", resp.Status, string(res))
if err != nil {
return nil, err
}
}
res, err := base.RestyClient.R().SetHeader("Authorization", token).Post(
fmt.Sprintf("https://%s.teambition.net/upload/chunk/%s",
prefix, newChunk.FileKey))
log.Debug(res.Status(), res.String())
if err != nil {
return nil, err
}
return &newChunk.FileUpload, nil
}
func (driver Teambition) finishUpload(file *FileUpload, parentId string, account *model.Account) error {
file.InvolveMembers = []interface{}{}
file.Visible = "members"
file.ParentId = parentId
_, err := driver.Request("/api/works", base.Post, nil, nil, nil, base.Json{
"works": []FileUpload{*file},
"_parentId": parentId,
}, nil, account)
return err
}
func init() { func init() {
base.RegisterDriver(&Teambition{}) base.RegisterDriver(&Teambition{})
} }

View File

@ -0,0 +1,63 @@
package teambition
import "time"
type Collection struct {
ID string `json:"_id"`
Title string `json:"title"`
Updated time.Time `json:"updated"`
}
type Work struct {
ID string `json:"_id"`
FileName string `json:"fileName"`
FileSize int64 `json:"fileSize"`
FileKey string `json:"fileKey"`
FileCategory string `json:"fileCategory"`
DownloadURL string `json:"downloadUrl"`
ThumbnailURL string `json:"thumbnailUrl"`
Thumbnail string `json:"thumbnail"`
Updated time.Time `json:"updated"`
PreviewURL string `json:"previewUrl"`
}
type FileUpload struct {
FileKey string `json:"fileKey"`
FileName string `json:"fileName"`
FileType string `json:"fileType"`
FileSize int `json:"fileSize"`
FileCategory string `json:"fileCategory"`
ImageWidth int `json:"imageWidth"`
ImageHeight int `json:"imageHeight"`
InvolveMembers []interface{} `json:"involveMembers"`
Source string `json:"source"`
Visible string `json:"visible"`
ParentId string `json:"_parentId"`
}
type ChunkUpload struct {
FileUpload
Storage string `json:"storage"`
MimeType string `json:"mimeType"`
Chunks int `json:"chunks"`
ChunkSize int `json:"chunkSize"`
Created time.Time `json:"created"`
FileMD5 string `json:"fileMD5"`
LastUpdated time.Time `json:"lastUpdated"`
UploadedChunks []interface{} `json:"uploadedChunks"`
Token struct {
AppID string `json:"AppID"`
OrganizationID string `json:"OrganizationID"`
UserID string `json:"UserID"`
Exp time.Time `json:"Exp"`
Storage string `json:"Storage"`
Resource string `json:"Resource"`
Speed int `json:"Speed"`
} `json:"token"`
DownloadUrl string `json:"downloadUrl"`
ThumbnailUrl string `json:"thumbnailUrl"`
PreviewUrl string `json:"previewUrl"`
ImmPreviewUrl string `json:"immPreviewUrl"`
PreviewExt string `json:"previewExt"`
LastUploadTime interface{} `json:"lastUploadTime"`
}

View File

@ -0,0 +1,18 @@
package teambition
import "strings"
func GetBetweenStr(str, start, end string) string {
n := strings.Index(str, start)
if n == -1 {
return ""
}
n = n + len(start)
str = string([]byte(str)[n:])
m := strings.Index(str, end)
if m == -1 {
return ""
}
str = string([]byte(str)[:m])
return str
}

View File

@ -0,0 +1,61 @@
package file
import (
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/drivers/operate"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/server/common"
"github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin"
)
func Copy(c *gin.Context) {
var req MoveCopyReq
if err := c.ShouldBind(&req); err != nil {
common.ErrorResp(c, err, 400)
return
}
if len(req.Names) == 0 {
common.ErrorStrResp(c, "Empty file names", 400)
return
}
if model.AccountsCount() > 1 && (req.SrcDir == "/" || req.DstDir == "/") {
common.ErrorStrResp(c, "Can't operate root folder", 400)
return
}
srcAccount, srcPath, srcDriver, err := common.ParsePath(utils.Join(req.SrcDir, req.Names[0]))
if err != nil {
common.ErrorResp(c, err, 500)
return
}
dstAccount, dstPath, _, err := common.ParsePath(utils.Join(req.DstDir, req.Names[0]))
if err != nil {
common.ErrorResp(c, err, 500)
return
}
if srcAccount.Name != dstAccount.Name {
common.ErrorStrResp(c, "Can't copy files between two accounts", 400)
return
}
if srcPath == "/" || dstPath == "/" {
common.ErrorStrResp(c, "Can't copy root folder", 400)
return
}
srcDir, dstDir := utils.Dir(srcPath), utils.Dir(dstPath)
for i, name := range req.Names {
clearCache := false
if i == len(req.Names)-1 {
clearCache = true
}
err := operate.Copy(srcDriver, srcAccount, utils.Join(srcDir, name), utils.Join(dstDir, name), clearCache)
if err != nil {
if i == 0 {
_ = base.DeleteCache(srcDir, srcAccount)
_ = base.DeleteCache(dstDir, dstAccount)
}
common.ErrorResp(c, err, 500)
return
}
}
common.SuccessResp(c)
}

View File

@ -1,6 +1,7 @@
package file package file
import ( import (
"github.com/Xhofe/alist/drivers/operate"
"github.com/Xhofe/alist/model" "github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/server/common" "github.com/Xhofe/alist/server/common"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@ -30,7 +31,7 @@ func Folder(c *gin.Context) {
common.ErrorResp(c, err, 500) common.ErrorResp(c, err, 500)
return return
} }
file, rawFiles, err := driver.Path(path, account) file, rawFiles, err := operate.Path(driver, account, path)
if err != nil { if err != nil {
common.ErrorResp(c, err, 500) common.ErrorResp(c, err, 500)
return return

View File

@ -9,14 +9,14 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
type MoveReq struct { type MoveCopyReq struct {
SrcDir string `json:"src_dir"` SrcDir string `json:"src_dir"`
DstDir string `json:"dst_dir"` DstDir string `json:"dst_dir"`
Names []string `json:"names"` Names []string `json:"names"`
} }
func Move(c *gin.Context) { func Move(c *gin.Context) {
var req MoveReq var req MoveCopyReq
if err := c.ShouldBind(&req); err != nil { if err := c.ShouldBind(&req); err != nil {
common.ErrorResp(c, err, 400) common.ErrorResp(c, err, 400)
return return

View File

@ -24,7 +24,7 @@ func Rename(c *gin.Context) {
return return
} }
if path_ == "/" { if path_ == "/" {
common.ErrorStrResp(c, "Can't account name here", 400) common.ErrorStrResp(c, "Can't edit account name here", 400)
return return
} }
err = operate.Move(driver, account, path_, utils.Join(utils.Dir(path_), req.Name), true) err = operate.Move(driver, account, path_, utils.Join(utils.Dir(path_), req.Name), true)

View File

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"github.com/Xhofe/alist/conf" "github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base" "github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/drivers/operate"
"github.com/Xhofe/alist/model" "github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/server/common" "github.com/Xhofe/alist/server/common"
"github.com/Xhofe/alist/utils" "github.com/Xhofe/alist/utils"
@ -109,7 +110,7 @@ func Path(c *gin.Context) {
common.ErrorResp(c, err, 500) common.ErrorResp(c, err, 500)
return return
} }
file, files, err := driver.Path(path, account) file, files, err := operate.Path(driver, account, path)
if err != nil { if err != nil {
common.ErrorResp(c, err, 500) common.ErrorResp(c, err, 500)
return return

View File

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"github.com/Xhofe/alist/conf" "github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base" "github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/drivers/operate"
"github.com/Xhofe/alist/server/common" "github.com/Xhofe/alist/server/common"
"github.com/Xhofe/alist/utils" "github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@ -50,7 +51,7 @@ func Proxy(c *gin.Context) {
return return
} }
// 检查文件 // 检查文件
file, err := driver.File(path, account) file, err := operate.File(driver, account, path)
if err != nil { if err != nil {
common.ErrorResp(c, err, 500) common.ErrorResp(c, err, 500)
return return
@ -100,10 +101,10 @@ func Proxy(c *gin.Context) {
c.File(link.Url) c.File(link.Url)
return return
} else { } else {
if utils.GetFileType(filepath.Ext(rawPath)) == conf.TEXT { //if utils.GetFileType(filepath.Ext(rawPath)) == conf.TEXT {
Text(c, link) // Text(c, link)
return // return
} //}
driver.Proxy(c, account) driver.Proxy(c, account)
r := c.Request r := c.Request
w := c.Writer w := c.Writer

View File

@ -19,7 +19,7 @@ func Auth(c *gin.Context) {
//} //}
//if token != utils.GetMD5Encode(password.Value) { //if token != utils.GetMD5Encode(password.Value) {
if token != conf.Token { if token != conf.Token {
common.ErrorStrResp(c, "Wrong password", 401) common.ErrorStrResp(c, "Invalid token", 401)
return return
} }
c.Next() c.Next()

View File

@ -54,6 +54,7 @@ func InitApiRouter(r *gin.Engine) {
admin.POST("/mkdir", file.Mkdir) admin.POST("/mkdir", file.Mkdir)
admin.POST("/rename", file.Rename) admin.POST("/rename", file.Rename)
admin.POST("/move", file.Move) admin.POST("/move", file.Move)
admin.POST("/copy", file.Copy)
admin.POST("/folder", file.Folder) admin.POST("/folder", file.Folder)
} }
WebDav(r) WebDav(r)

View File

@ -40,7 +40,7 @@ func (fs *FileSystem) File(rawPath string) (*model.File, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return driver.File(path_, account) return operate.File(driver, account, path_)
} }
func (fs *FileSystem) Files(ctx context.Context, rawPath string) ([]model.File, error) { func (fs *FileSystem) Files(ctx context.Context, rawPath string) ([]model.File, error) {
@ -56,7 +56,7 @@ func (fs *FileSystem) Files(ctx context.Context, rawPath string) ([]model.File,
if err != nil { if err != nil {
return nil, err return nil, err
} }
files, err := driver.Files(path_, account) files, err := operate.Files(driver, account, path_)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -46,7 +46,7 @@ func (h *Handler) stripPrefix(p string) (string, int, error) {
func isPathExist(ctx context.Context, fs *FileSystem, path string) (bool, FileInfo) { func isPathExist(ctx context.Context, fs *FileSystem, path string) (bool, FileInfo) {
file, err := fs.File(path) file, err := fs.File(path)
if err != nil { if err != nil {
log.Debug(path, err) log.Debugln(path, err)
return false, nil return false, nil
} }
return true, file return true, file