mirror of https://github.com/cloudreve/Cloudreve
fix(remote download): improve file pre-upload validation (fix #2286)
parent
224ac28ffe
commit
969e35192a
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
|
"path"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/cloudreve/Cloudreve/v4/ent"
|
"github.com/cloudreve/Cloudreve/v4/ent"
|
||||||
|
@ -15,6 +16,59 @@ import (
|
||||||
"github.com/cloudreve/Cloudreve/v4/pkg/util"
|
"github.com/cloudreve/Cloudreve/v4/pkg/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (f *DBFS) PreValidateUpload(ctx context.Context, dst *fs.URI, files ...fs.PreValidateFile) error {
|
||||||
|
// Get navigator
|
||||||
|
navigator, err := f.getNavigator(ctx, dst, NavigatorCapabilityUploadFile, NavigatorCapabilityLockFile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
dstFile, err := f.getFileByPath(ctx, navigator, dst)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get destination folder: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if dstFile.Type() != types.FileTypeFolder {
|
||||||
|
return fmt.Errorf("destination is not a folder")
|
||||||
|
}
|
||||||
|
|
||||||
|
// check permission
|
||||||
|
if err := f.EvaluatePermission(ctx, dstFile, types.FilePermissionCreate, false); err != nil {
|
||||||
|
return fmt.Errorf("failed to evaluate permission: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
total := int64(0)
|
||||||
|
for _, file := range files {
|
||||||
|
total += file.Size
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get parent folder storage policy and performs validation
|
||||||
|
policy, err := f.getPreferredPolicy(ctx, dstFile, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate upload request
|
||||||
|
for _, file := range files {
|
||||||
|
if file.OmitName {
|
||||||
|
if err := validateFileSize(file.Size, policy); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := validateNewFile(path.Base(file.Name), file.Size, policy); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate available capacity
|
||||||
|
if err := f.validateUserCapacity(ctx, total, dstFile.Owner()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (f *DBFS) PrepareUpload(ctx context.Context, req *fs.UploadRequest, opts ...fs.Option) (*fs.UploadSession, error) {
|
func (f *DBFS) PrepareUpload(ctx context.Context, req *fs.UploadRequest, opts ...fs.Option) (*fs.UploadSession, error) {
|
||||||
// Get navigator
|
// Get navigator
|
||||||
navigator, err := f.getNavigator(ctx, req.Props.Uri, NavigatorCapabilityUploadFile, NavigatorCapabilityLockFile)
|
navigator, err := f.getNavigator(ctx, req.Props.Uri, NavigatorCapabilityUploadFile, NavigatorCapabilityLockFile)
|
||||||
|
|
|
@ -103,6 +103,8 @@ type (
|
||||||
CompleteUpload(ctx context.Context, session *UploadSession) (File, error)
|
CompleteUpload(ctx context.Context, session *UploadSession) (File, error)
|
||||||
// CancelUploadSession cancels an upload session. Delete the placeholder file if no other entity is created.
|
// CancelUploadSession cancels an upload session. Delete the placeholder file if no other entity is created.
|
||||||
CancelUploadSession(ctx context.Context, path *URI, sessionID string, session *UploadSession) ([]Entity, error)
|
CancelUploadSession(ctx context.Context, path *URI, sessionID string, session *UploadSession) ([]Entity, error)
|
||||||
|
// PreValidateUpload pre-validates an upload request.
|
||||||
|
PreValidateUpload(ctx context.Context, dst *URI, files ...PreValidateFile) error
|
||||||
}
|
}
|
||||||
|
|
||||||
LockSystem interface {
|
LockSystem interface {
|
||||||
|
@ -369,6 +371,12 @@ type (
|
||||||
ParentFiles []int `json:"parent_files"`
|
ParentFiles []int `json:"parent_files"`
|
||||||
PrimaryEntityParentFiles []int `json:"primary_entity_parent_files"`
|
PrimaryEntityParentFiles []int `json:"primary_entity_parent_files"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PreValidateFile struct {
|
||||||
|
Name string
|
||||||
|
Size int64
|
||||||
|
OmitName bool // if true, file name will not be validated
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
|
@ -38,9 +38,15 @@ type (
|
||||||
OnUploadFailed(ctx context.Context, session *fs.UploadSession)
|
OnUploadFailed(ctx context.Context, session *fs.UploadSession)
|
||||||
// Similar to CompleteUpload, but does not create actual uplaod session in storage.
|
// Similar to CompleteUpload, but does not create actual uplaod session in storage.
|
||||||
PrepareUpload(ctx context.Context, req *fs.UploadRequest, opts ...fs.Option) (*fs.UploadSession, error)
|
PrepareUpload(ctx context.Context, req *fs.UploadRequest, opts ...fs.Option) (*fs.UploadSession, error)
|
||||||
|
// PreValidateUpload pre-validates an upload request.
|
||||||
|
PreValidateUpload(ctx context.Context, dst *fs.URI, files ...fs.PreValidateFile) error
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (m *manager) PreValidateUpload(ctx context.Context, dst *fs.URI, files ...fs.PreValidateFile) error {
|
||||||
|
return m.fs.PreValidateUpload(ctx, dst, files...)
|
||||||
|
}
|
||||||
|
|
||||||
func (m *manager) CreateUploadSession(ctx context.Context, req *fs.UploadRequest, opts ...fs.Option) (*fs.UploadCredential, error) {
|
func (m *manager) CreateUploadSession(ctx context.Context, req *fs.UploadRequest, opts ...fs.Option) (*fs.UploadCredential, error) {
|
||||||
o := newOption()
|
o := newOption()
|
||||||
for _, opt := range opts {
|
for _, opt := range opts {
|
||||||
|
|
|
@ -25,7 +25,6 @@ import (
|
||||||
"github.com/cloudreve/Cloudreve/v4/pkg/logging"
|
"github.com/cloudreve/Cloudreve/v4/pkg/logging"
|
||||||
"github.com/cloudreve/Cloudreve/v4/pkg/queue"
|
"github.com/cloudreve/Cloudreve/v4/pkg/queue"
|
||||||
"github.com/cloudreve/Cloudreve/v4/pkg/serializer"
|
"github.com/cloudreve/Cloudreve/v4/pkg/serializer"
|
||||||
"github.com/gofrs/uuid"
|
|
||||||
"github.com/samber/lo"
|
"github.com/samber/lo"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -537,35 +536,18 @@ func (m *RemoteDownloadTask) validateFiles(ctx context.Context, dep dependency.D
|
||||||
return fmt.Errorf("no selected file found in download task")
|
return fmt.Errorf("no selected file found in download task")
|
||||||
}
|
}
|
||||||
|
|
||||||
// find the first valid file
|
validateArgs := lo.Map(selectedFiles, func(f downloader.TaskFile, _ int) fs.PreValidateFile {
|
||||||
var placeholderFileName string
|
return fs.PreValidateFile{
|
||||||
for _, f := range selectedFiles {
|
Name: f.Name,
|
||||||
if f.Name != "" {
|
Size: f.Size,
|
||||||
placeholderFileName = f.Name
|
OmitName: f.Name == "",
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if placeholderFileName == "" {
|
|
||||||
// File name not available yet, generate one
|
|
||||||
m.l.Debug("File name not available yet, generate one to validate the destination")
|
|
||||||
placeholderFileName = uuid.Must(uuid.NewV4()).String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a placeholder file then delete it to validate the destination
|
|
||||||
session, err := fm.PrepareUpload(ctx, &fs.UploadRequest{
|
|
||||||
Props: &fs.UploadProps{
|
|
||||||
Uri: dstUri.Join(path.Base(placeholderFileName)),
|
|
||||||
Size: status.Total,
|
|
||||||
UploadSessionID: uuid.Must(uuid.NewV4()).String(),
|
|
||||||
ExpireAt: time.Now().Add(time.Second * 3600),
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
if err != nil {
|
|
||||||
return err
|
if err := fm.PreValidateUpload(ctx, dstUri, validateArgs...); err != nil {
|
||||||
|
return fmt.Errorf("failed to pre-validate files: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fm.OnUploadFailed(ctx, session)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue