diff --git a/backend/app/api/v1/file.go b/backend/app/api/v1/file.go index f2c3a70dd444..4702bba861b6 100644 --- a/backend/app/api/v1/file.go +++ b/backend/app/api/v1/file.go @@ -11,7 +11,7 @@ import ( "path/filepath" "strconv" "strings" - + "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/request" @@ -301,9 +301,9 @@ func (b *BaseApi) UploadFiles(c *gin.Context) { helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) return } - files := form.File["file"] + uploadFiles := form.File["file"] paths := form.Value["path"] - + overwrite := true if ow, ok := form.Value["overwrite"]; ok { if len(ow) != 0 { @@ -311,22 +311,47 @@ func (b *BaseApi) UploadFiles(c *gin.Context) { overwrite = parseBool } } - + if len(paths) == 0 || !strings.Contains(paths[0], "/") { helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, errors.New("error paths in request")) return } 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)) 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 failures := make(buserr.MultiErr) - for _, file := range files { + for _, file := range uploadFiles { 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" if err := c.SaveUploadedFile(file, tmpFilename); err != nil { _ = os.Remove(tmpFilename) @@ -335,11 +360,11 @@ func (b *BaseApi) UploadFiles(c *gin.Context) { global.LOG.Error(e) continue } - stat, statErr := os.Stat(dstFilename) + dstInfo, statErr := os.Stat(dstFilename) if overwrite { _ = os.Remove(dstFilename) } - + err = os.Rename(tmpFilename, dstFilename) if err != nil { _ = os.Remove(tmpFilename) @@ -349,7 +374,9 @@ func (b *BaseApi) UploadFiles(c *gin.Context) { continue } if statErr == nil { - _ = os.Chmod(dstFilename, stat.Mode()) + _ = os.Chmod(dstFilename, dstInfo.Mode()) + } else { + _ = os.Chmod(dstFilename, mode) } success++ } @@ -499,29 +526,29 @@ func (b *BaseApi) DownloadChunkFiles(c *gin.Context) { helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrFileDownloadDir, err) return } - + 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-Length", strconv.FormatInt(info.Size(), 10)) c.Writer.Header().Set("Accept-Ranges", "bytes") - + if c.Request.Header.Get("Range") != "" { rangeHeader := c.Request.Header.Get("Range") rangeArr := strings.Split(rangeHeader, "=")[1] rangeParts := strings.Split(rangeArr, "-") - + startPos, _ := strconv.ParseInt(rangeParts[0], 10, 64) - + var endPos int64 if rangeParts[1] == "" { endPos = info.Size() - 1 } else { 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.WriteHeader(http.StatusPartialContent) - + buffer := make([]byte, 1024*1024) file, err := os.Open(filePath) if err != nil { @@ -529,7 +556,7 @@ func (b *BaseApi) DownloadChunkFiles(c *gin.Context) { return } defer file.Close() - + _, _ = file.Seek(startPos, 0) reader := io.LimitReader(file, endPos-startPos+1) _, 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 { op := files.NewFileOp() 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 = op.CreateDir(dstDir, os.ModePerm); err != nil { + if err = op.CreateDir(dstDir, mode); err != nil { 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 { return err } - defer targetFile.Close() - + for i := 0; i < chunkCount; i++ { chunkPath := filepath.Join(fileDir, fmt.Sprintf("%s.%d", fileName, i)) chunkData, err := os.ReadFile(chunkPath) @@ -589,7 +620,7 @@ func mergeChunks(fileName string, fileDir string, dstDir string, chunkCount int) return err } } - + return files.NewFileOp().DeleteDir(fileDir) } @@ -639,7 +670,7 @@ func (b *BaseApi) UploadChunkFiles(c *gin.Context) { _ = os.MkdirAll(fileDir, 0755) } filePath := filepath.Join(fileDir, filename) - + defer func() { if err != nil { _ = os.Remove(fileDir) @@ -649,27 +680,27 @@ func (b *BaseApi) UploadChunkFiles(c *gin.Context) { emptyFile *os.File chunkData []byte ) - + emptyFile, err = os.Create(filePath) if err != nil { helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) return } defer emptyFile.Close() - + chunkData, err = io.ReadAll(uploadFile) if err != nil { helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, buserr.WithMap(constant.ErrFileUpload, map[string]interface{}{"name": filename, "detail": err.Error()}, err)) return } - + chunkPath := filepath.Join(fileDir, fmt.Sprintf("%s.%d", filename, chunkIndex)) err = os.WriteFile(chunkPath, chunkData, 0644) if err != nil { helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, buserr.WithMap(constant.ErrFileUpload, map[string]interface{}{"name": filename, "detail": err.Error()}, err)) return } - + if chunkIndex+1 == chunkCount { err = mergeChunks(filename, fileDir, c.PostForm("path"), chunkCount) if err != nil { diff --git a/backend/utils/files/utils.go b/backend/utils/files/utils.go index b77e2650ea57..ae2b6c4c2309 100644 --- a/backend/utils/files/utils.go +++ b/backend/utils/files/utils.go @@ -2,6 +2,7 @@ package files import ( "bufio" + "fmt" "github.com/spf13/afero" "io" "net/http" @@ -117,3 +118,26 @@ func ReadFileByLine(filename string, page, pageSize int) ([]string, bool, error) 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 + } +}