Feat: disable overwrite for non-updating put request, only works under local,slave,OneDrive,OSS policy. (#764)

feat-album
HFO4 3 years ago
parent c949d47161
commit 5e226efea1

@ -1 +1 @@
Subproject commit 3ffab03068e88d627825710798b5cc0d5ce8df20 Subproject commit e2d4f13a54dfd424cfbc129664772e104ccf97fc

@ -100,6 +100,14 @@ func (handler Driver) Put(ctx context.Context, file io.ReadCloser, dst string, s
defer file.Close() defer file.Close()
dst = util.RelativePath(filepath.FromSlash(dst)) dst = util.RelativePath(filepath.FromSlash(dst))
// 如果禁止了 Overwrite则检查是否有重名冲突
if ctx.Value(fsctx.DisableOverwrite) != nil {
if util.Exists(dst) {
util.Log().Warning("物理同名文件已存在或不可用: %s", dst)
return errors.New("物理同名文件已存在或不可用")
}
}
// 如果目标目录不存在,创建 // 如果目标目录不存在,创建
basePath := filepath.Dir(dst) basePath := filepath.Dir(dst)
if !util.Exists(basePath) { if !util.Exists(basePath) {

@ -21,7 +21,8 @@ import (
func TestHandler_Put(t *testing.T) { func TestHandler_Put(t *testing.T) {
asserts := assert.New(t) asserts := assert.New(t)
handler := Driver{} handler := Driver{}
ctx := context.Background() ctx := context.WithValue(context.Background(), fsctx.DisableOverwrite, true)
os.Remove(util.RelativePath("test/test/txt"))
testCases := []struct { testCases := []struct {
file io.ReadCloser file io.ReadCloser
@ -33,6 +34,11 @@ func TestHandler_Put(t *testing.T) {
dst: "test/test/txt", dst: "test/test/txt",
err: false, err: false,
}, },
{
file: ioutil.NopCloser(strings.NewReader("test input file")),
dst: "test/test/txt",
err: true,
},
{ {
file: ioutil.NopCloser(strings.NewReader("test input file")), file: ioutil.NopCloser(strings.NewReader("test input file")),
dst: "/notexist:/S.TXT", dst: "/notexist:/S.TXT",

@ -235,8 +235,15 @@ func (handler Driver) Put(ctx context.Context, file io.ReadCloser, dst string, s
// 凭证有效期 // 凭证有效期
credentialTTL := model.GetIntSetting("upload_credential_timeout", 3600) credentialTTL := model.GetIntSetting("upload_credential_timeout", 3600)
// 是否允许覆盖
overwrite := true
if ctx.Value(fsctx.DisableOverwrite) != nil {
overwrite = false
}
options := []oss.Option{ options := []oss.Option{
oss.Expires(time.Now().Add(time.Duration(credentialTTL) * time.Second)), oss.Expires(time.Now().Add(time.Duration(credentialTTL) * time.Second)),
oss.ForbidOverWrite(!overwrite),
} }
// 上传文件 // 上传文件

@ -265,10 +265,11 @@ func TestDriver_Put(t *testing.T) {
}, },
} }
cache.Set("setting_upload_credential_timeout", "3600", 0) cache.Set("setting_upload_credential_timeout", "3600", 0)
ctx := context.WithValue(context.Background(), fsctx.DisableOverwrite, true)
// 失败 // 失败
{ {
err := handler.Put(context.Background(), ioutil.NopCloser(strings.NewReader("123")), "/123.txt", 3) err := handler.Put(ctx, ioutil.NopCloser(strings.NewReader("123")), "/123.txt", 3)
asserts.Error(err) asserts.Error(err)
} }
} }

@ -155,6 +155,13 @@ func (handler Driver) Put(ctx context.Context, file io.ReadCloser, dst string, s
if err != nil { if err != nil {
return err return err
} }
// 决定是否要禁用文件覆盖
overwrite := "true"
if ctx.Value(fsctx.DisableOverwrite) != nil {
overwrite = "false"
}
// 上传文件 // 上传文件
resp, err := handler.Client.Request( resp, err := handler.Client.Request(
"POST", "POST",
@ -164,6 +171,7 @@ func (handler Driver) Put(ctx context.Context, file io.ReadCloser, dst string, s
"Authorization": {credential.Token}, "Authorization": {credential.Token},
"X-Policy": {credential.Policy}, "X-Policy": {credential.Policy},
"X-FileName": {fileName}, "X-FileName": {fileName},
"X-Overwrite": {overwrite},
}), }),
request.WithContentLength(int64(size)), request.WithContentLength(int64(size)),
request.WithTimeout(time.Duration(0)), request.WithTimeout(time.Duration(0)),
@ -321,7 +329,8 @@ func (handler Driver) getUploadCredential(ctx context.Context, policy serializer
// 签名上传策略 // 签名上传策略
uploadRequest, _ := http.NewRequest("POST", "/api/v3/slave/upload", nil) uploadRequest, _ := http.NewRequest("POST", "/api/v3/slave/upload", nil)
uploadRequest.Header = map[string][]string{ uploadRequest.Header = map[string][]string{
"X-Policy": {policyEncoded}, "X-Policy": {policyEncoded},
"X-Overwrite": {"false"},
} }
auth.SignRequest(handler.AuthInstance, uploadRequest, TTL) auth.SignRequest(handler.AuthInstance, uploadRequest, TTL)

@ -34,7 +34,7 @@ func TestHandler_Token(t *testing.T) {
}, },
AuthInstance: auth.HMACAuth{}, AuthInstance: auth.HMACAuth{},
} }
ctx := context.Background() ctx := context.WithValue(context.Background(), fsctx.DisableOverwrite, true)
auth.General = auth.HMACAuth{SecretKey: []byte("test")} auth.General = auth.HMACAuth{SecretKey: []byte("test")}
// 成功 // 成功
@ -49,6 +49,7 @@ func TestHandler_Token(t *testing.T) {
asserts.Equal(true, policy.AutoRename) asserts.Equal(true, policy.AutoRename)
asserts.Equal("dir", policy.SavePath) asserts.Equal("dir", policy.SavePath)
asserts.Equal("file", policy.FileName) asserts.Equal("file", policy.FileName)
asserts.Equal("file", policy.FileName)
asserts.Equal([]string{"txt"}, policy.AllowedExtension) asserts.Equal([]string{"txt"}, policy.AllowedExtension)
} }

@ -29,8 +29,8 @@ const (
ShareKeyCtx ShareKeyCtx
// LimitParentCtx 限制父目录 // LimitParentCtx 限制父目录
LimitParentCtx LimitParentCtx
// IgnoreConflictCtx 忽略重名冲突 // IgnoreDirectoryConflictCtx 忽略目录重名冲突
IgnoreConflictCtx IgnoreDirectoryConflictCtx
// RetryCtx 失败重试次数 // RetryCtx 失败重试次数
RetryCtx RetryCtx
// ForceUsePublicEndpointCtx 强制使用公网 Endpoint // ForceUsePublicEndpointCtx 强制使用公网 Endpoint
@ -39,4 +39,6 @@ const (
CancelFuncCtx CancelFuncCtx
// ValidateCapacityOnceCtx 限定归还容量的操作只執行一次 // ValidateCapacityOnceCtx 限定归还容量的操作只執行一次
ValidateCapacityOnceCtx ValidateCapacityOnceCtx
// 禁止上传时同名覆盖操作
DisableOverwrite
) )

@ -403,8 +403,8 @@ func (fs *FileSystem) CreateDirectory(ctx context.Context, fullPath string) (*mo
isExist, parent := fs.IsPathExist(base) isExist, parent := fs.IsPathExist(base)
if !isExist { if !isExist {
// 递归创建父目录 // 递归创建父目录
if _, ok := ctx.Value(fsctx.IgnoreConflictCtx).(bool); !ok { if _, ok := ctx.Value(fsctx.IgnoreDirectoryConflictCtx).(bool); !ok {
ctx = context.WithValue(ctx, fsctx.IgnoreConflictCtx, true) ctx = context.WithValue(ctx, fsctx.IgnoreDirectoryConflictCtx, true)
} }
newParent, err := fs.CreateDirectory(ctx, base) newParent, err := fs.CreateDirectory(ctx, base)
if err != nil { if err != nil {
@ -427,7 +427,7 @@ func (fs *FileSystem) CreateDirectory(ctx context.Context, fullPath string) (*mo
_, err := newFolder.Create() _, err := newFolder.Create()
if err != nil { if err != nil {
if _, ok := ctx.Value(fsctx.IgnoreConflictCtx).(bool); !ok { if _, ok := ctx.Value(fsctx.IgnoreDirectoryConflictCtx).(bool); !ok {
return nil, ErrFolderExisted return nil, ErrFolderExisted
} }

@ -6,6 +6,7 @@ import (
model "github.com/cloudreve/Cloudreve/v3/models" model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem" "github.com/cloudreve/Cloudreve/v3/pkg/filesystem"
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem/fsctx"
) )
// DecompressTask 文件压缩任务 // DecompressTask 文件压缩任务
@ -81,7 +82,12 @@ func (job *DecompressTask) Do() {
} }
job.TaskModel.SetProgress(DecompressingProgress) job.TaskModel.SetProgress(DecompressingProgress)
err = fs.Decompress(context.Background(), job.TaskProps.Src, job.TaskProps.Dst)
// 禁止重名覆盖
ctx := context.Background()
ctx = context.WithValue(ctx, fsctx.DisableOverwrite, true)
err = fs.Decompress(ctx, job.TaskProps.Src, job.TaskProps.Dst)
if err != nil { if err != nil {
job.SetErrorMsg("解压缩失败", err) job.SetErrorMsg("解压缩失败", err)
return return

@ -102,7 +102,7 @@ func (job *ImportTask) Do() {
// 列取目录、对象 // 列取目录、对象
job.TaskModel.SetProgress(ListingProgress) job.TaskModel.SetProgress(ListingProgress)
coxIgnoreConflict := context.WithValue(context.Background(), fsctx.IgnoreConflictCtx, coxIgnoreConflict := context.WithValue(context.Background(), fsctx.IgnoreDirectoryConflictCtx,
true) true)
objects, err := fs.Handler.List(ctx, job.TaskProps.Src, job.TaskProps.Recursive) objects, err := fs.Handler.List(ctx, job.TaskProps.Src, job.TaskProps.Recursive)
if err != nil { if err != nil {

@ -10,6 +10,7 @@ import (
model "github.com/cloudreve/Cloudreve/v3/models" model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem" "github.com/cloudreve/Cloudreve/v3/pkg/filesystem"
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem/fsctx"
"github.com/cloudreve/Cloudreve/v3/pkg/util" "github.com/cloudreve/Cloudreve/v3/pkg/util"
) )
@ -102,7 +103,8 @@ func (job *TransferTask) Do() {
dst = path.Join(job.TaskProps.Dst, strings.TrimPrefix(src, trim)) dst = path.Join(job.TaskProps.Dst, strings.TrimPrefix(src, trim))
} }
err = fs.UploadFromPath(context.Background(), file, dst) ctx := context.WithValue(context.Background(), fsctx.DisableOverwrite, true)
err = fs.UploadFromPath(ctx, file, dst)
if err != nil { if err != nil {
job.SetErrorMsg("文件转存失败", err) job.SetErrorMsg("文件转存失败", err)
} }

@ -373,6 +373,9 @@ func (h *Handler) handlePut(w http.ResponseWriter, r *http.Request, fs *filesyst
fs.Use("AfterUploadFailed", filesystem.HookGiveBackCapacity) fs.Use("AfterUploadFailed", filesystem.HookGiveBackCapacity)
} }
// 禁止覆盖
ctx = context.WithValue(ctx, fsctx.DisableOverwrite, true)
// 执行上传 // 执行上传
err = fs.Upload(ctx, fileData) err = fs.Upload(ctx, fileData)
if err != nil { if err != nil {
@ -407,8 +410,8 @@ func (h *Handler) handleMkcol(w http.ResponseWriter, r *http.Request, fs *filesy
return http.StatusUnsupportedMediaType, nil return http.StatusUnsupportedMediaType, nil
} }
if strings.Contains(r.UserAgent(), "rclone") { if strings.Contains(r.UserAgent(), "rclone") {
if _, ok := ctx.Value(fsctx.IgnoreConflictCtx).(bool); !ok { if _, ok := ctx.Value(fsctx.IgnoreDirectoryConflictCtx).(bool); !ok {
ctx = context.WithValue(ctx, fsctx.IgnoreConflictCtx, true) ctx = context.WithValue(ctx, fsctx.IgnoreDirectoryConflictCtx, true)
} }
} }
if _, err := fs.CreateDirectory(ctx, reqPath); err != nil { if _, err := fs.CreateDirectory(ctx, reqPath); err != nil {

@ -319,6 +319,7 @@ func FileUploadStream(c *gin.Context) {
// 执行上传 // 执行上传
ctx = context.WithValue(ctx, fsctx.ValidateCapacityOnceCtx, &sync.Once{}) ctx = context.WithValue(ctx, fsctx.ValidateCapacityOnceCtx, &sync.Once{})
ctx = context.WithValue(ctx, fsctx.DisableOverwrite, true)
uploadCtx := context.WithValue(ctx, fsctx.GinCtx, c) uploadCtx := context.WithValue(ctx, fsctx.GinCtx, c)
err = fs.Upload(uploadCtx, fileData) err = fs.Upload(uploadCtx, fileData)
if err != nil { if err != nil {

@ -71,6 +71,11 @@ func SlaveUpload(c *gin.Context) {
fs.Use("AfterUpload", filesystem.SlaveAfterUpload) fs.Use("AfterUpload", filesystem.SlaveAfterUpload)
fs.Use("AfterValidateFailed", filesystem.HookDeleteTempFile) fs.Use("AfterValidateFailed", filesystem.HookDeleteTempFile)
// 是否允许覆盖
if c.Request.Header.Get("X-Overwrite") == "false" {
ctx = context.WithValue(ctx, fsctx.DisableOverwrite, true)
}
// 执行上传 // 执行上传
err = fs.Upload(ctx, fileData) err = fs.Upload(ctx, fileData)
if err != nil { if err != nil {

@ -78,6 +78,7 @@ func (service *SingleFileService) Create(c *gin.Context) serializer.Response {
// 上下文 // 上下文
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
ctx = context.WithValue(ctx, fsctx.DisableOverwrite, true)
// 给文件系统分配钩子 // 给文件系统分配钩子
fs.Use("BeforeUpload", filesystem.HookValidateFile) fs.Use("BeforeUpload", filesystem.HookValidateFile)

Loading…
Cancel
Save