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>
This commit is contained in:
ShenLin
2026-09-01 18:30:22 +08:00
committed by jyxjjj
parent f244adf60e
commit bba3516693
3 changed files with 50 additions and 2 deletions
+14
View File
@@ -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 会移除 // ,手动加回去
+35 -1
View File
@@ -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) {
+1 -1
View File
@@ -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) {