Feat: disable overwrite for non-updating put request, only works under local,slave,OneDrive,OSS policy. (#764)
This commit is contained in:
parent
c949d47161
commit
5e226efea1
16 changed files with 67 additions and 15 deletions
2
assets
2
assets
|
@ -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…
Add table
Reference in a new issue