package handles import ( "io" "net/url" stdpath "path" "strconv" "time" "github.com/OpenListTeam/OpenList/v4/internal/conf" "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/setting" "github.com/OpenListTeam/OpenList/v4/internal/stream" "github.com/OpenListTeam/OpenList/v4/internal/task" "github.com/OpenListTeam/OpenList/v4/pkg/utils" "github.com/OpenListTeam/OpenList/v4/server/common" "github.com/gin-gonic/gin" ) func getLastModified(c *gin.Context) time.Time { now := time.Now() lastModifiedStr := c.GetHeader("Last-Modified") lastModifiedMillisecond, err := strconv.ParseInt(lastModifiedStr, 10, 64) if err != nil { return now } lastModified := time.UnixMilli(lastModifiedMillisecond) return lastModified } // shouldIgnoreSystemFile checks if the filename should be ignored based on settings func shouldIgnoreSystemFile(filename string) bool { if setting.GetBool(conf.IgnoreSystemFiles) { return utils.IsSystemFile(filename) } return false } func FsStream(c *gin.Context) { defer func() { if n, _ := io.ReadFull(c.Request.Body, []byte{0}); n == 1 { _, _ = utils.CopyWithBuffer(io.Discard, c.Request.Body) } _ = c.Request.Body.Close() }() path := c.GetHeader("File-Path") path, err := url.PathUnescape(path) if err != nil { common.ErrorResp(c, err, 400) return } asTask := c.GetHeader("As-Task") == "true" overwrite := c.GetHeader("Overwrite") != "false" user := c.Request.Context().Value(conf.UserKey).(*model.User) path, err = user.JoinPath(path) if err != nil { common.ErrorResp(c, err, 403) return } if !overwrite { if res, _ := fs.Get(c.Request.Context(), path, &fs.GetArgs{NoLog: true}); res != nil { common.ErrorStrResp(c, "file exists", 403) return } } dir, name := stdpath.Split(path) // Check if system file should be ignored if shouldIgnoreSystemFile(name) { common.ErrorStrResp(c, errs.IgnoredSystemFile.Error(), 403) return } // 如果请求头 Content-Length 和 X-File-Size 都没有,则 size=-1,表示未知大小的流式上传 size := c.Request.ContentLength if size < 0 { sizeStr := c.GetHeader("X-File-Size") if sizeStr != "" { size, err = strconv.ParseInt(sizeStr, 10, 64) if err != nil { common.ErrorResp(c, err, 400) return } } } h := make(map[*utils.HashType]string) if md5 := c.GetHeader("X-File-Md5"); md5 != "" { h[utils.MD5] = md5 } if sha1 := c.GetHeader("X-File-Sha1"); sha1 != "" { h[utils.SHA1] = sha1 } if sha256 := c.GetHeader("X-File-Sha256"); sha256 != "" { h[utils.SHA256] = sha256 } mimetype := c.GetHeader("Content-Type") if len(mimetype) == 0 { mimetype = utils.GetMimeType(name) } s := &stream.FileStream{ Obj: &model.Object{ Name: name, Size: size, Modified: getLastModified(c), HashInfo: utils.NewHashInfoByMap(h), }, Reader: c.Request.Body, Mimetype: mimetype, WebPutAsTask: asTask, } var t task.TaskExtensionInfo if asTask { t, err = fs.PutAsTask(c.Request.Context(), dir, s) } else { err = fs.PutDirectly(c.Request.Context(), dir, s, true) } if err != nil { common.ErrorResp(c, err, 500) return } if t == nil { common.SuccessResp(c) return } common.SuccessResp(c, gin.H{ "task": getTaskInfo(t), }) } func FsForm(c *gin.Context) { defer func() { if n, _ := io.ReadFull(c.Request.Body, []byte{0}); n == 1 { _, _ = utils.CopyWithBuffer(io.Discard, c.Request.Body) } _ = c.Request.Body.Close() }() path := c.GetHeader("File-Path") path, err := url.PathUnescape(path) if err != nil { common.ErrorResp(c, err, 400) return } asTask := c.GetHeader("As-Task") == "true" overwrite := c.GetHeader("Overwrite") != "false" user := c.Request.Context().Value(conf.UserKey).(*model.User) path, err = user.JoinPath(path) if err != nil { common.ErrorResp(c, err, 403) return } if !overwrite { if res, _ := fs.Get(c.Request.Context(), path, &fs.GetArgs{NoLog: true}); res != nil { common.ErrorStrResp(c, "file exists", 403) return } } storage, err := fs.GetStorage(path, &fs.GetStoragesArgs{}) if err != nil { common.ErrorResp(c, err, 400) return } if storage.Config().NoUpload { common.ErrorStrResp(c, "Current storage doesn't support upload", 405) return } file, err := c.FormFile("file") if err != nil { common.ErrorResp(c, err, 500) return } f, err := file.Open() if err != nil { common.ErrorResp(c, err, 500) return } defer f.Close() dir, name := stdpath.Split(path) // Check if system file should be ignored if shouldIgnoreSystemFile(name) { common.ErrorStrResp(c, errs.IgnoredSystemFile.Error(), 403) return } h := make(map[*utils.HashType]string) if md5 := c.GetHeader("X-File-Md5"); md5 != "" { h[utils.MD5] = md5 } if sha1 := c.GetHeader("X-File-Sha1"); sha1 != "" { h[utils.SHA1] = sha1 } if sha256 := c.GetHeader("X-File-Sha256"); sha256 != "" { h[utils.SHA256] = sha256 } mimetype := file.Header.Get("Content-Type") if len(mimetype) == 0 { mimetype = utils.GetMimeType(name) } s := &stream.FileStream{ Obj: &model.Object{ Name: name, Size: file.Size, Modified: getLastModified(c), HashInfo: utils.NewHashInfoByMap(h), }, Reader: f, Mimetype: mimetype, WebPutAsTask: asTask, } var t task.TaskExtensionInfo if asTask { s.Reader = struct { io.Reader }{f} t, err = fs.PutAsTask(c.Request.Context(), dir, s) } else { err = fs.PutDirectly(c.Request.Context(), dir, s, true) } if err != nil { common.ErrorResp(c, err, 500) return } if t == nil { common.SuccessResp(c) return } common.SuccessResp(c, gin.H{ "task": getTaskInfo(t), }) }