feat: 文件上传权限优化 (#4441)

Refs https://github.com/1Panel-dev/1Panel/issues/4362
This commit is contained in:
zhengkunwang 2024-04-09 16:36:09 +08:00 committed by GitHub
parent 0751326b46
commit 1e22769c8b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 82 additions and 27 deletions

View file

@ -11,7 +11,7 @@ import (
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings" "strings"
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper" "github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
"github.com/1Panel-dev/1Panel/backend/app/dto" "github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/app/dto/request" "github.com/1Panel-dev/1Panel/backend/app/dto/request"
@ -301,9 +301,9 @@ func (b *BaseApi) UploadFiles(c *gin.Context) {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return return
} }
files := form.File["file"] uploadFiles := form.File["file"]
paths := form.Value["path"] paths := form.Value["path"]
overwrite := true overwrite := true
if ow, ok := form.Value["overwrite"]; ok { if ow, ok := form.Value["overwrite"]; ok {
if len(ow) != 0 { if len(ow) != 0 {
@ -311,22 +311,47 @@ func (b *BaseApi) UploadFiles(c *gin.Context) {
overwrite = parseBool overwrite = parseBool
} }
} }
if len(paths) == 0 || !strings.Contains(paths[0], "/") { if len(paths) == 0 || !strings.Contains(paths[0], "/") {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, errors.New("error paths in request")) helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, errors.New("error paths in request"))
return return
} }
dir := path.Dir(paths[0]) dir := path.Dir(paths[0])
if _, err := os.Stat(dir); err != nil && os.IsNotExist(err) {
if err = os.MkdirAll(dir, os.ModePerm); err != nil { info, err := os.Stat(dir)
if err != nil && os.IsNotExist(err) {
mode, err := files.GetParentMode(dir)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
if err = os.MkdirAll(dir, mode); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, fmt.Errorf("mkdir %s failed, err: %v", dir, err)) helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, fmt.Errorf("mkdir %s failed, err: %v", dir, err))
return return
} }
} }
info, err = os.Stat(dir)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
mode := info.Mode()
fileOp := files.NewFileOp()
success := 0 success := 0
failures := make(buserr.MultiErr) failures := make(buserr.MultiErr)
for _, file := range files { for _, file := range uploadFiles {
dstFilename := path.Join(paths[0], file.Filename) dstFilename := path.Join(paths[0], file.Filename)
dstDir := path.Dir(dstFilename)
if !fileOp.Stat(dstDir) {
if err = fileOp.CreateDir(dstDir, mode); err != nil {
e := fmt.Errorf("create dir [%s] failed, err: %v", path.Dir(dstFilename), err)
failures[file.Filename] = e
global.LOG.Error(e)
continue
}
}
tmpFilename := dstFilename + ".tmp" tmpFilename := dstFilename + ".tmp"
if err := c.SaveUploadedFile(file, tmpFilename); err != nil { if err := c.SaveUploadedFile(file, tmpFilename); err != nil {
_ = os.Remove(tmpFilename) _ = os.Remove(tmpFilename)
@ -335,11 +360,11 @@ func (b *BaseApi) UploadFiles(c *gin.Context) {
global.LOG.Error(e) global.LOG.Error(e)
continue continue
} }
stat, statErr := os.Stat(dstFilename) dstInfo, statErr := os.Stat(dstFilename)
if overwrite { if overwrite {
_ = os.Remove(dstFilename) _ = os.Remove(dstFilename)
} }
err = os.Rename(tmpFilename, dstFilename) err = os.Rename(tmpFilename, dstFilename)
if err != nil { if err != nil {
_ = os.Remove(tmpFilename) _ = os.Remove(tmpFilename)
@ -349,7 +374,9 @@ func (b *BaseApi) UploadFiles(c *gin.Context) {
continue continue
} }
if statErr == nil { if statErr == nil {
_ = os.Chmod(dstFilename, stat.Mode()) _ = os.Chmod(dstFilename, dstInfo.Mode())
} else {
_ = os.Chmod(dstFilename, mode)
} }
success++ success++
} }
@ -499,29 +526,29 @@ func (b *BaseApi) DownloadChunkFiles(c *gin.Context) {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrFileDownloadDir, err) helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrFileDownloadDir, err)
return return
} }
c.Writer.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s", req.Name)) c.Writer.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s", req.Name))
c.Writer.Header().Set("Content-Type", "application/octet-stream") c.Writer.Header().Set("Content-Type", "application/octet-stream")
c.Writer.Header().Set("Content-Length", strconv.FormatInt(info.Size(), 10)) c.Writer.Header().Set("Content-Length", strconv.FormatInt(info.Size(), 10))
c.Writer.Header().Set("Accept-Ranges", "bytes") c.Writer.Header().Set("Accept-Ranges", "bytes")
if c.Request.Header.Get("Range") != "" { if c.Request.Header.Get("Range") != "" {
rangeHeader := c.Request.Header.Get("Range") rangeHeader := c.Request.Header.Get("Range")
rangeArr := strings.Split(rangeHeader, "=")[1] rangeArr := strings.Split(rangeHeader, "=")[1]
rangeParts := strings.Split(rangeArr, "-") rangeParts := strings.Split(rangeArr, "-")
startPos, _ := strconv.ParseInt(rangeParts[0], 10, 64) startPos, _ := strconv.ParseInt(rangeParts[0], 10, 64)
var endPos int64 var endPos int64
if rangeParts[1] == "" { if rangeParts[1] == "" {
endPos = info.Size() - 1 endPos = info.Size() - 1
} else { } else {
endPos, _ = strconv.ParseInt(rangeParts[1], 10, 64) endPos, _ = strconv.ParseInt(rangeParts[1], 10, 64)
} }
c.Writer.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", startPos, endPos, info.Size())) c.Writer.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", startPos, endPos, info.Size()))
c.Writer.WriteHeader(http.StatusPartialContent) c.Writer.WriteHeader(http.StatusPartialContent)
buffer := make([]byte, 1024*1024) buffer := make([]byte, 1024*1024)
file, err := os.Open(filePath) file, err := os.Open(filePath)
if err != nil { if err != nil {
@ -529,7 +556,7 @@ func (b *BaseApi) DownloadChunkFiles(c *gin.Context) {
return return
} }
defer file.Close() defer file.Close()
_, _ = file.Seek(startPos, 0) _, _ = file.Seek(startPos, 0)
reader := io.LimitReader(file, endPos-startPos+1) reader := io.LimitReader(file, endPos-startPos+1)
_, err = io.CopyBuffer(c.Writer, reader, buffer) _, err = io.CopyBuffer(c.Writer, reader, buffer)
@ -567,17 +594,21 @@ func (b *BaseApi) Size(c *gin.Context) {
func mergeChunks(fileName string, fileDir string, dstDir string, chunkCount int) error { func mergeChunks(fileName string, fileDir string, dstDir string, chunkCount int) error {
op := files.NewFileOp() op := files.NewFileOp()
dstDir = strings.TrimSpace(dstDir) dstDir = strings.TrimSpace(dstDir)
mode, _ := files.GetParentMode(dstDir)
if mode == 0 {
mode = os.ModePerm
}
if _, err := os.Stat(dstDir); err != nil && os.IsNotExist(err) { if _, err := os.Stat(dstDir); err != nil && os.IsNotExist(err) {
if err = op.CreateDir(dstDir, os.ModePerm); err != nil { if err = op.CreateDir(dstDir, mode); err != nil {
return err return err
} }
} }
targetFile, err := os.Create(filepath.Join(dstDir, fileName))
targetFile, err := os.OpenFile(filepath.Join(dstDir, fileName), os.O_RDWR|os.O_CREATE, mode)
if err != nil { if err != nil {
return err return err
} }
defer targetFile.Close()
for i := 0; i < chunkCount; i++ { for i := 0; i < chunkCount; i++ {
chunkPath := filepath.Join(fileDir, fmt.Sprintf("%s.%d", fileName, i)) chunkPath := filepath.Join(fileDir, fmt.Sprintf("%s.%d", fileName, i))
chunkData, err := os.ReadFile(chunkPath) chunkData, err := os.ReadFile(chunkPath)
@ -589,7 +620,7 @@ func mergeChunks(fileName string, fileDir string, dstDir string, chunkCount int)
return err return err
} }
} }
return files.NewFileOp().DeleteDir(fileDir) return files.NewFileOp().DeleteDir(fileDir)
} }
@ -639,7 +670,7 @@ func (b *BaseApi) UploadChunkFiles(c *gin.Context) {
_ = os.MkdirAll(fileDir, 0755) _ = os.MkdirAll(fileDir, 0755)
} }
filePath := filepath.Join(fileDir, filename) filePath := filepath.Join(fileDir, filename)
defer func() { defer func() {
if err != nil { if err != nil {
_ = os.Remove(fileDir) _ = os.Remove(fileDir)
@ -649,27 +680,27 @@ func (b *BaseApi) UploadChunkFiles(c *gin.Context) {
emptyFile *os.File emptyFile *os.File
chunkData []byte chunkData []byte
) )
emptyFile, err = os.Create(filePath) emptyFile, err = os.Create(filePath)
if err != nil { if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return return
} }
defer emptyFile.Close() defer emptyFile.Close()
chunkData, err = io.ReadAll(uploadFile) chunkData, err = io.ReadAll(uploadFile)
if err != nil { if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, buserr.WithMap(constant.ErrFileUpload, map[string]interface{}{"name": filename, "detail": err.Error()}, err)) helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, buserr.WithMap(constant.ErrFileUpload, map[string]interface{}{"name": filename, "detail": err.Error()}, err))
return return
} }
chunkPath := filepath.Join(fileDir, fmt.Sprintf("%s.%d", filename, chunkIndex)) chunkPath := filepath.Join(fileDir, fmt.Sprintf("%s.%d", filename, chunkIndex))
err = os.WriteFile(chunkPath, chunkData, 0644) err = os.WriteFile(chunkPath, chunkData, 0644)
if err != nil { if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, buserr.WithMap(constant.ErrFileUpload, map[string]interface{}{"name": filename, "detail": err.Error()}, err)) helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, buserr.WithMap(constant.ErrFileUpload, map[string]interface{}{"name": filename, "detail": err.Error()}, err))
return return
} }
if chunkIndex+1 == chunkCount { if chunkIndex+1 == chunkCount {
err = mergeChunks(filename, fileDir, c.PostForm("path"), chunkCount) err = mergeChunks(filename, fileDir, c.PostForm("path"), chunkCount)
if err != nil { if err != nil {

View file

@ -2,6 +2,7 @@ package files
import ( import (
"bufio" "bufio"
"fmt"
"github.com/spf13/afero" "github.com/spf13/afero"
"io" "io"
"net/http" "net/http"
@ -117,3 +118,26 @@ func ReadFileByLine(filename string, page, pageSize int) ([]string, bool, error)
return lines, isEndOfFile, nil return lines, isEndOfFile, nil
} }
func GetParentMode(path string) (os.FileMode, error) {
absPath, err := filepath.Abs(path)
if err != nil {
return 0, err
}
for {
fileInfo, err := os.Stat(absPath)
if err == nil {
return fileInfo.Mode(), nil
}
if !os.IsNotExist(err) {
return 0, err
}
parentDir := filepath.Dir(absPath)
if parentDir == absPath {
return 0, fmt.Errorf("no existing directory found in the path: %s", path)
}
absPath = parentDir
}
}