feat(cloud189): Added sanitization for file and folder names (#9366)

- Introduced `sanitizeName` function to remove four-byte characters (e.g., emojis) from names before upload or creation.
- Added `StripEmoji` option in driver configurations for cloud189 and cloud189pc.
- Updated file and folder operations (upload, rename, and creation) to use sanitized names.
- Ensured compatibility with both cloud189 and cloud189pc implementations.
This commit is contained in:
千石
2025-11-11 20:26:51 +08:00
committed by GitHub
parent 0cbc7ebc92
commit ce41587095
6 changed files with 88 additions and 18 deletions

View File

@@ -80,9 +80,10 @@ func (d *Cloud189) Link(ctx context.Context, file model.Obj, args model.LinkArgs
} }
func (d *Cloud189) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error { func (d *Cloud189) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
safeName := d.sanitizeName(dirName)
form := map[string]string{ form := map[string]string{
"parentFolderId": parentDir.GetID(), "parentFolderId": parentDir.GetID(),
"folderName": dirName, "folderName": safeName,
} }
_, err := d.request("https://cloud.189.cn/api/open/file/createFolder.action", http.MethodPost, func(req *resty.Request) { _, err := d.request("https://cloud.189.cn/api/open/file/createFolder.action", http.MethodPost, func(req *resty.Request) {
req.SetFormData(form) req.SetFormData(form)
@@ -126,9 +127,10 @@ func (d *Cloud189) Rename(ctx context.Context, srcObj model.Obj, newName string)
idKey = "folderId" idKey = "folderId"
nameKey = "destFolderName" nameKey = "destFolderName"
} }
safeName := d.sanitizeName(newName)
form := map[string]string{ form := map[string]string{
idKey: srcObj.GetID(), idKey: srcObj.GetID(),
nameKey: newName, nameKey: safeName,
} }
_, err := d.request(url, http.MethodPost, func(req *resty.Request) { _, err := d.request(url, http.MethodPost, func(req *resty.Request) {
req.SetFormData(form) req.SetFormData(form)

View File

@@ -9,6 +9,7 @@ type Addition struct {
Username string `json:"username" required:"true"` Username string `json:"username" required:"true"`
Password string `json:"password" required:"true"` Password string `json:"password" required:"true"`
Cookie string `json:"cookie" help:"Fill in the cookie if need captcha"` Cookie string `json:"cookie" help:"Fill in the cookie if need captcha"`
StripEmoji bool `json:"strip_emoji" help:"Remove four-byte characters (e.g., emoji) before upload"`
driver.RootID driver.RootID
} }

View File

@@ -11,9 +11,11 @@ import (
"io" "io"
"math" "math"
"net/http" "net/http"
"path"
"strconv" "strconv"
"strings" "strings"
"time" "time"
"unicode/utf8"
"github.com/alist-org/alist/v3/drivers/base" "github.com/alist-org/alist/v3/drivers/base"
"github.com/alist-org/alist/v3/internal/driver" "github.com/alist-org/alist/v3/internal/driver"
@@ -222,13 +224,37 @@ func (d *Cloud189) getFiles(fileId string) ([]model.Obj, error) {
return res, nil return res, nil
} }
func (d *Cloud189) sanitizeName(name string) string {
if !d.StripEmoji {
return name
}
b := strings.Builder{}
for _, r := range name {
if utf8.RuneLen(r) == 4 {
continue
}
b.WriteRune(r)
}
sanitized := b.String()
if sanitized == "" {
ext := path.Ext(name)
if ext != "" {
sanitized = "file" + ext
} else {
sanitized = "file"
}
}
return sanitized
}
func (d *Cloud189) oldUpload(dstDir model.Obj, file model.FileStreamer) error { func (d *Cloud189) oldUpload(dstDir model.Obj, file model.FileStreamer) error {
safeName := d.sanitizeName(file.GetName())
res, err := d.client.R().SetMultipartFormData(map[string]string{ res, err := d.client.R().SetMultipartFormData(map[string]string{
"parentId": dstDir.GetID(), "parentId": dstDir.GetID(),
"sessionKey": "??", "sessionKey": "??",
"opertype": "1", "opertype": "1",
"fname": file.GetName(), "fname": safeName,
}).SetMultipartField("Filedata", file.GetName(), file.GetMimetype(), file).Post("https://hb02.upload.cloud.189.cn/v1/DCIWebUploadAction") }).SetMultipartField("Filedata", safeName, file.GetMimetype(), file).Post("https://hb02.upload.cloud.189.cn/v1/DCIWebUploadAction")
if err != nil { if err != nil {
return err return err
} }
@@ -313,9 +339,10 @@ func (d *Cloud189) newUpload(ctx context.Context, dstDir model.Obj, file model.F
const DEFAULT int64 = 10485760 const DEFAULT int64 = 10485760
var count = int64(math.Ceil(float64(file.GetSize()) / float64(DEFAULT))) var count = int64(math.Ceil(float64(file.GetSize()) / float64(DEFAULT)))
safeName := d.sanitizeName(file.GetName())
res, err := d.uploadRequest("/person/initMultiUpload", map[string]string{ res, err := d.uploadRequest("/person/initMultiUpload", map[string]string{
"parentFolderId": dstDir.GetID(), "parentFolderId": dstDir.GetID(),
"fileName": encode(file.GetName()), "fileName": encode(safeName),
"fileSize": strconv.FormatInt(file.GetSize(), 10), "fileSize": strconv.FormatInt(file.GetSize(), 10),
"sliceSize": strconv.FormatInt(DEFAULT, 10), "sliceSize": strconv.FormatInt(DEFAULT, 10),
"lazyCheck": "1", "lazyCheck": "1",

View File

@@ -205,10 +205,11 @@ func (y *Cloud189PC) MakeDir(ctx context.Context, parentDir model.Obj, dirName s
fullUrl += "/createFolder.action" fullUrl += "/createFolder.action"
var newFolder Cloud189Folder var newFolder Cloud189Folder
safeName := y.sanitizeName(dirName)
_, err := y.post(fullUrl, func(req *resty.Request) { _, err := y.post(fullUrl, func(req *resty.Request) {
req.SetContext(ctx) req.SetContext(ctx)
req.SetQueryParams(map[string]string{ req.SetQueryParams(map[string]string{
"folderName": dirName, "folderName": safeName,
"relativePath": "", "relativePath": "",
}) })
if isFamily { if isFamily {
@@ -225,6 +226,7 @@ func (y *Cloud189PC) MakeDir(ctx context.Context, parentDir model.Obj, dirName s
if err != nil { if err != nil {
return nil, err return nil, err
} }
newFolder.Name = safeName
return &newFolder, nil return &newFolder, nil
} }
@@ -258,21 +260,29 @@ func (y *Cloud189PC) Rename(ctx context.Context, srcObj model.Obj, newName strin
} }
var newObj model.Obj var newObj model.Obj
safeName := y.sanitizeName(newName)
switch f := srcObj.(type) { switch f := srcObj.(type) {
case *Cloud189File: case *Cloud189File:
fullUrl += "/renameFile.action" fullUrl += "/renameFile.action"
queryParam["fileId"] = srcObj.GetID() queryParam["fileId"] = srcObj.GetID()
queryParam["destFileName"] = newName queryParam["destFileName"] = safeName
newObj = &Cloud189File{Icon: f.Icon} // 复用预览 newObj = &Cloud189File{Icon: f.Icon} // 复用预览
case *Cloud189Folder: case *Cloud189Folder:
fullUrl += "/renameFolder.action" fullUrl += "/renameFolder.action"
queryParam["folderId"] = srcObj.GetID() queryParam["folderId"] = srcObj.GetID()
queryParam["destFolderName"] = newName queryParam["destFolderName"] = safeName
newObj = &Cloud189Folder{} newObj = &Cloud189Folder{}
default: default:
return nil, errs.NotSupport return nil, errs.NotSupport
} }
switch obj := newObj.(type) {
case *Cloud189File:
obj.Name = safeName
case *Cloud189Folder:
obj.Name = safeName
}
_, err := y.request(fullUrl, method, func(req *resty.Request) { _, err := y.request(fullUrl, method, func(req *resty.Request) {
req.SetContext(ctx).SetQueryParams(queryParam) req.SetContext(ctx).SetQueryParams(queryParam)
}, nil, newObj, isFamily) }, nil, newObj, isFamily)

View File

@@ -9,6 +9,7 @@ type Addition struct {
Username string `json:"username" required:"true"` Username string `json:"username" required:"true"`
Password string `json:"password" required:"true"` Password string `json:"password" required:"true"`
VCode string `json:"validate_code"` VCode string `json:"validate_code"`
StripEmoji bool `json:"strip_emoji" help:"Remove four-byte characters (e.g., emoji) before upload"`
driver.RootID driver.RootID
OrderBy string `json:"order_by" type:"select" options:"filename,filesize,lastOpTime" default:"filename"` OrderBy string `json:"order_by" type:"select" options:"filename,filesize,lastOpTime" default:"filename"`
OrderDirection string `json:"order_direction" type:"select" options:"asc,desc" default:"asc"` OrderDirection string `json:"order_direction" type:"select" options:"asc,desc" default:"asc"`

View File

@@ -12,11 +12,13 @@ import (
"net/http/cookiejar" "net/http/cookiejar"
"net/url" "net/url"
"os" "os"
"path"
"regexp" "regexp"
"sort" "sort"
"strconv" "strconv"
"strings" "strings"
"time" "time"
"unicode/utf8"
"golang.org/x/sync/semaphore" "golang.org/x/sync/semaphore"
@@ -57,6 +59,29 @@ const (
CHANNEL_ID = "web_cloud.189.cn" CHANNEL_ID = "web_cloud.189.cn"
) )
func (y *Cloud189PC) sanitizeName(name string) string {
if !y.StripEmoji {
return name
}
b := strings.Builder{}
for _, r := range name {
if utf8.RuneLen(r) == 4 {
continue
}
b.WriteRune(r)
}
sanitized := b.String()
if sanitized == "" {
ext := path.Ext(name)
if ext != "" {
sanitized = "file" + ext
} else {
sanitized = "file"
}
}
return sanitized
}
func (y *Cloud189PC) SignatureHeader(url, method, params string, isFamily bool) map[string]string { func (y *Cloud189PC) SignatureHeader(url, method, params string, isFamily bool) map[string]string {
dateOfGmt := getHttpDateStr() dateOfGmt := getHttpDateStr()
sessionKey := y.getTokenInfo().SessionKey sessionKey := y.getTokenInfo().SessionKey
@@ -475,10 +500,11 @@ func (y *Cloud189PC) refreshSession() (err error) {
func (y *Cloud189PC) StreamUpload(ctx context.Context, dstDir model.Obj, file model.FileStreamer, up driver.UpdateProgress, isFamily bool, overwrite bool) (model.Obj, error) { func (y *Cloud189PC) StreamUpload(ctx context.Context, dstDir model.Obj, file model.FileStreamer, up driver.UpdateProgress, isFamily bool, overwrite bool) (model.Obj, error) {
size := file.GetSize() size := file.GetSize()
sliceSize := partSize(size) sliceSize := partSize(size)
safeName := y.sanitizeName(file.GetName())
params := Params{ params := Params{
"parentFolderId": dstDir.GetID(), "parentFolderId": dstDir.GetID(),
"fileName": url.QueryEscape(file.GetName()), "fileName": url.QueryEscape(safeName),
"fileSize": fmt.Sprint(file.GetSize()), "fileSize": fmt.Sprint(file.GetSize()),
"sliceSize": fmt.Sprint(sliceSize), "sliceSize": fmt.Sprint(sliceSize),
"lazyCheck": "1", "lazyCheck": "1",
@@ -596,7 +622,8 @@ func (y *Cloud189PC) RapidUpload(ctx context.Context, dstDir model.Obj, stream m
return nil, errors.New("invalid hash") return nil, errors.New("invalid hash")
} }
uploadInfo, err := y.OldUploadCreate(ctx, dstDir.GetID(), fileMd5, stream.GetName(), fmt.Sprint(stream.GetSize()), isFamily) safeName := y.sanitizeName(stream.GetName())
uploadInfo, err := y.OldUploadCreate(ctx, dstDir.GetID(), fileMd5, safeName, fmt.Sprint(stream.GetSize()), isFamily)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -615,6 +642,7 @@ func (y *Cloud189PC) FastUpload(ctx context.Context, dstDir model.Obj, file mode
tmpF *os.File tmpF *os.File
err error err error
) )
safeName := y.sanitizeName(file.GetName())
size := file.GetSize() size := file.GetSize()
if _, ok := cache.(io.ReaderAt); !ok && size > 0 { if _, ok := cache.(io.ReaderAt); !ok && size > 0 {
tmpF, err = os.CreateTemp(conf.Conf.TempDir, "file-*") tmpF, err = os.CreateTemp(conf.Conf.TempDir, "file-*")
@@ -697,7 +725,7 @@ func (y *Cloud189PC) FastUpload(ctx context.Context, dstDir model.Obj, file mode
//step.2 预上传 //step.2 预上传
params := Params{ params := Params{
"parentFolderId": dstDir.GetID(), "parentFolderId": dstDir.GetID(),
"fileName": url.QueryEscape(file.GetName()), "fileName": url.QueryEscape(safeName),
"fileSize": fmt.Sprint(file.GetSize()), "fileSize": fmt.Sprint(file.GetSize()),
"fileMd5": fileMd5Hex, "fileMd5": fileMd5Hex,
"sliceSize": fmt.Sprint(sliceSize), "sliceSize": fmt.Sprint(sliceSize),
@@ -833,9 +861,10 @@ func (y *Cloud189PC) OldUpload(ctx context.Context, dstDir model.Obj, file model
return nil, err return nil, err
} }
rateLimited := driver.NewLimitedUploadStream(ctx, io.NopCloser(tempFile)) rateLimited := driver.NewLimitedUploadStream(ctx, io.NopCloser(tempFile))
safeName := y.sanitizeName(file.GetName())
// 创建上传会话 // 创建上传会话
uploadInfo, err := y.OldUploadCreate(ctx, dstDir.GetID(), fileMd5, file.GetName(), fmt.Sprint(file.GetSize()), isFamily) uploadInfo, err := y.OldUploadCreate(ctx, dstDir.GetID(), fileMd5, safeName, fmt.Sprint(file.GetSize()), isFamily)
if err != nil { if err != nil {
return nil, err return nil, err
} }