From bba35166938431569f0b89922916d4e87c9cf3ff Mon Sep 17 00:00:00 2001 From: ShenLin <773933146@qq.com> Date: Tue, 1 Sep 2026 18:30:22 +0800 Subject: [PATCH] fix(upload): authorize direct upload destinations - Resolve and authorize the canonical destination from the request payload - Reject upload capabilities that cross virtual storage mount boundaries - Remove the unrelated File-Path middleware authorization check Co-authored-by: Codex <267193182+codex@users.noreply.github.com> Signed-off-by: jyxjjj <16695261+jyxjjj@users.noreply.github.com> --- internal/op/path.go | 14 +++++++++++++ server/handles/direct_upload.go | 36 ++++++++++++++++++++++++++++++++- server/router.go | 2 +- 3 files changed, 50 insertions(+), 2 deletions(-) diff --git a/internal/op/path.go b/internal/op/path.go index 294261e89..9157e2f22 100644 --- a/internal/op/path.go +++ b/internal/op/path.go @@ -30,6 +30,20 @@ func GetStorageAndActualPath(rawPath string) (storage driver.Driver, actualPath return } +// GetStorageVirtualMountPath returns the deterministic virtual mount path +// without advancing the balanced-storage counter. +func GetStorageVirtualMountPath(rawPath string) (string, error) { + rawPath = utils.FixAndCleanPath(rawPath) + storages := getStoragesByPath(rawPath) + if len(storages) == 0 { + if rawPath == "/" { + return "", errs.NewErr(errs.StorageNotFound, "please add a storage first") + } + return "", errs.NewErr(errs.StorageNotFound, "rawPath: %s", rawPath) + } + return utils.FixAndCleanPath(utils.GetActualMountPath(storages[0].GetStorage().MountPath)), nil +} + // urlTreeSplitLineFormPath 分割path中分割真实路径和UrlTree定义字符串 func urlTreeSplitLineFormPath(path string) (pp string, file string) { // url.PathUnescape 会移除 // ,手动加回去 diff --git a/server/handles/direct_upload.go b/server/handles/direct_upload.go index d77c3044c..b016f028c 100644 --- a/server/handles/direct_upload.go +++ b/server/handles/direct_upload.go @@ -8,8 +8,10 @@ import ( "github.com/OpenListTeam/OpenList/v4/internal/errs" "github.com/OpenListTeam/OpenList/v4/internal/fs" "github.com/OpenListTeam/OpenList/v4/internal/model" + "github.com/OpenListTeam/OpenList/v4/internal/op" "github.com/OpenListTeam/OpenList/v4/server/common" "github.com/gin-gonic/gin" + "github.com/pkg/errors" ) type FsGetDirectUploadInfoReq struct { @@ -44,8 +46,40 @@ func FsGetDirectUploadInfo(c *gin.Context) { common.ErrorResp(c, err, 403) return } - overwrite := c.GetHeader("Overwrite") != "false" + // Resolve the destination once so permission checks and the issued upload + // capability cannot target different paths. dstPath := stdpath.Join(path, req.FileName) + path = stdpath.Dir(dstPath) + req.FileName = stdpath.Base(dstPath) + parentMeta, err := op.GetNearestMeta(path) + if err != nil && !errors.Is(errors.Cause(err), errs.MetaNotFound) { + common.ErrorResp(c, err, 500, true) + return + } + if !user.CanWriteContent() && !common.CanWriteContentBypassUserPerms(parentMeta, path) { + common.ErrorResp(c, errs.PermissionDenied, 403) + return + } + if !common.CanWrite(user, parentMeta, path) { + common.ErrorResp(c, errs.PermissionDenied, 403) + return + } + // A single-segment file name can still name a nested mount point. + parentMountPath, err := op.GetStorageVirtualMountPath(path) + if err != nil { + common.ErrorResp(c, err, 500) + return + } + targetMountPath, err := op.GetStorageVirtualMountPath(dstPath) + if err != nil { + common.ErrorResp(c, err, 500) + return + } + if parentMountPath != targetMountPath { + common.ErrorResp(c, errs.PermissionDenied, 403) + return + } + overwrite := c.GetHeader("Overwrite") != "false" if !overwrite { res, err := fs.Get(c.Request.Context(), dstPath, &fs.GetArgs{NoLog: true}) if err != nil && !errs.IsObjectNotFound(err) { diff --git a/server/router.go b/server/router.go index 737bfd9f5..dc0e77efb 100644 --- a/server/router.go +++ b/server/router.go @@ -233,7 +233,7 @@ func _fs(g *gin.RouterGroup) { g.POST("/torrent/rapid_upload", handles.TorrentRapidUpload) g.POST("/torrent/generate", handles.GenerateTorrentForPath) // Direct upload (client-side upload to storage) - g.POST("/get_direct_upload_info", middlewares.FsUp, handles.FsGetDirectUploadInfo) + g.POST("/get_direct_upload_info", handles.FsGetDirectUploadInfo) } func _task(g *gin.RouterGroup) {