diff --git a/API.md b/API.md index 5269b8ee8..3d0fc0b12 100644 --- a/API.md +++ b/API.md @@ -1032,6 +1032,7 @@ View API token in Settings - About, request header: `Authorization: T * `code`: non-zero for exceptions * `-1`: Parameter parsing error + * `403`: Permission denied (file is not in the workspace) * `404`: Not Found (file doesn't exist) * `405`: Method Not Allowed (it's a directory) * `500`: Server Error (stat file failed / read file failed) diff --git a/API_zh_CN.md b/API_zh_CN.md index 83e4745bf..a66d9d8dc 100644 --- a/API_zh_CN.md +++ b/API_zh_CN.md @@ -1025,6 +1025,7 @@ * `code`: 非零的异常值 * `-1`: 参数解析错误 + * `403`: 无访问权限 (文件不在工作空间下) * `404`: 未找到 (文件不存在) * `405`: 方法不被允许 (这是一个目录) * `500`: 服务器错误 (文件查询失败 / 文件读取失败) diff --git a/kernel/api/archive.go b/kernel/api/archive.go index 835262b3f..a886474e8 100644 --- a/kernel/api/archive.go +++ b/kernel/api/archive.go @@ -34,20 +34,34 @@ func zip(c *gin.Context) { return } - path := arg["path"].(string) - zipPath := arg["zipPath"].(string) - zipFile, err := gulu.Zip.Create(zipPath) + entryPath := arg["path"].(string) + entryAbsPath, err := util.GetAbsPathInWorkspace(entryPath) if nil != err { ret.Code = -1 ret.Msg = err.Error() return } - base := filepath.Base(path) - if gulu.File.IsDir(path) { - err = zipFile.AddDirectory(base, path) + zipFilePath := arg["zipPath"].(string) + zipAbsFilePath, err := util.GetAbsPathInWorkspace(zipFilePath) + if nil != err { + ret.Code = -1 + ret.Msg = err.Error() + return + } + + zipFile, err := gulu.Zip.Create(zipAbsFilePath) + if nil != err { + ret.Code = -1 + ret.Msg = err.Error() + return + } + + base := filepath.Base(entryAbsPath) + if gulu.File.IsDir(entryAbsPath) { + err = zipFile.AddDirectory(base, entryAbsPath) } else { - err = zipFile.AddEntry(base, path) + err = zipFile.AddEntry(base, entryAbsPath) } if nil != err { ret.Code = -1 @@ -71,9 +85,23 @@ func unzip(c *gin.Context) { return } - zipPath := arg["zipPath"].(string) - path := arg["path"].(string) - if err := gulu.Zip.Unzip(zipPath, path); nil != err { + zipFilePath := arg["zipPath"].(string) + zipAbsFilePath, err := util.GetAbsPathInWorkspace(zipFilePath) + if nil != err { + ret.Code = -1 + ret.Msg = err.Error() + return + } + + entryPath := arg["path"].(string) + entryAbsPath, err := util.GetAbsPathInWorkspace(entryPath) + if nil != err { + ret.Code = -1 + ret.Msg = err.Error() + return + } + + if err := gulu.Zip.Unzip(zipAbsFilePath, entryAbsPath); nil != err { ret.Code = -1 ret.Msg = err.Error() return diff --git a/kernel/api/file.go b/kernel/api/file.go index fb8db6f64..d638d08ce 100644 --- a/kernel/api/file.go +++ b/kernel/api/file.go @@ -90,38 +90,45 @@ func getFile(c *gin.Context) { } filePath := arg["path"].(string) - filePath = filepath.Join(util.WorkspaceDir, filePath) - info, err := os.Stat(filePath) + fileAbsPath, err := util.GetAbsPathInWorkspace(filePath) + if nil != err { + ret.Code = http.StatusForbidden + ret.Msg = err.Error() + c.JSON(http.StatusAccepted, ret) + return + } + info, err := os.Stat(fileAbsPath) if os.IsNotExist(err) { - ret.Code = 404 + ret.Code = http.StatusNotFound + ret.Msg = err.Error() c.JSON(http.StatusAccepted, ret) return } if nil != err { - logging.LogErrorf("stat [%s] failed: %s", filePath, err) - ret.Code = 500 + logging.LogErrorf("stat [%s] failed: %s", fileAbsPath, err) + ret.Code = http.StatusInternalServerError ret.Msg = err.Error() c.JSON(http.StatusAccepted, ret) return } if info.IsDir() { - logging.LogErrorf("file [%s] is a directory", filePath) - ret.Code = 405 + logging.LogErrorf("file [%s] is a directory", fileAbsPath) + ret.Code = http.StatusMethodNotAllowed ret.Msg = "file is a directory" c.JSON(http.StatusAccepted, ret) return } - data, err := filelock.ReadFile(filePath) + data, err := filelock.ReadFile(fileAbsPath) if nil != err { - logging.LogErrorf("read file [%s] failed: %s", filePath, err) - ret.Code = 500 + logging.LogErrorf("read file [%s] failed: %s", fileAbsPath, err) + ret.Code = http.StatusInternalServerError ret.Msg = err.Error() c.JSON(http.StatusAccepted, ret) return } - contentType := mime.TypeByExtension(filepath.Ext(filePath)) + contentType := mime.TypeByExtension(filepath.Ext(fileAbsPath)) if "" == contentType { if m := mimetype.Detect(data); nil != m { contentType = m.String() @@ -144,40 +151,46 @@ func readDir(c *gin.Context) { } dirPath := arg["path"].(string) - dirPath = filepath.Join(util.WorkspaceDir, dirPath) - info, err := os.Stat(dirPath) + dirAbsPath, err := util.GetAbsPathInWorkspace(dirPath) + if nil != err { + ret.Code = http.StatusForbidden + ret.Msg = err.Error() + return + } + info, err := os.Stat(dirAbsPath) if os.IsNotExist(err) { - ret.Code = 404 + ret.Code = http.StatusNotFound + ret.Msg = err.Error() return } if nil != err { - logging.LogErrorf("stat [%s] failed: %s", dirPath, err) - ret.Code = 500 + logging.LogErrorf("stat [%s] failed: %s", dirAbsPath, err) + ret.Code = http.StatusInternalServerError ret.Msg = err.Error() return } if !info.IsDir() { - logging.LogErrorf("file [%s] is not a directory", dirPath) - ret.Code = 405 + logging.LogErrorf("file [%s] is not a directory", dirAbsPath) + ret.Code = http.StatusMethodNotAllowed ret.Msg = "file is not a directory" return } - entries, err := os.ReadDir(dirPath) + entries, err := os.ReadDir(dirAbsPath) if nil != err { - logging.LogErrorf("read dir [%s] failed: %s", dirPath, err) - ret.Code = 500 + logging.LogErrorf("read dir [%s] failed: %s", dirAbsPath, err) + ret.Code = http.StatusInternalServerError ret.Msg = err.Error() return } files := []map[string]interface{}{} for _, entry := range entries { - path := filepath.Join(dirPath, entry.Name()) + path := filepath.Join(dirAbsPath, entry.Name()) info, err = os.Stat(path) if nil != err { logging.LogErrorf("stat [%s] failed: %s", path, err) - ret.Code = 500 + ret.Code = http.StatusInternalServerError ret.Msg = err.Error() return } @@ -202,25 +215,36 @@ func renameFile(c *gin.Context) { return } - filePath := arg["path"].(string) - filePath = filepath.Join(util.WorkspaceDir, filePath) - if !filelock.IsExist(filePath) { - ret.Code = 404 + srcPath := arg["path"].(string) + srcAbsPath, err := util.GetAbsPathInWorkspace(srcPath) + if nil != err { + ret.Code = http.StatusForbidden + ret.Msg = err.Error() + return + } + if !filelock.IsExist(srcAbsPath) { + ret.Code = http.StatusNotFound ret.Msg = "the [path] file or directory does not exist" return } - newPath := arg["newPath"].(string) - newPath = filepath.Join(util.WorkspaceDir, newPath) - if filelock.IsExist(newPath) { - ret.Code = 409 + destPath := arg["newPath"].(string) + destAbsPath, err := util.GetAbsPathInWorkspace(destPath) + if nil != err { + ret.Code = http.StatusForbidden + ret.Msg = err.Error() + c.JSON(http.StatusAccepted, ret) + return + } + if filelock.IsExist(destAbsPath) { + ret.Code = http.StatusConflict ret.Msg = "the [newPath] file or directory already exists" return } - if err := filelock.Rename(filePath, newPath); nil != err { - logging.LogErrorf("rename file [%s] to [%s] failed: %s", filePath, newPath, err) - ret.Code = 500 + if err := filelock.Rename(srcAbsPath, destAbsPath); nil != err { + logging.LogErrorf("rename file [%s] to [%s] failed: %s", srcAbsPath, destAbsPath, err) + ret.Code = http.StatusInternalServerError ret.Msg = err.Error() return } @@ -237,22 +261,27 @@ func removeFile(c *gin.Context) { } filePath := arg["path"].(string) - filePath = filepath.Join(util.WorkspaceDir, filePath) - _, err := os.Stat(filePath) + fileAbsPath, err := util.GetAbsPathInWorkspace(filePath) + if nil != err { + ret.Code = http.StatusForbidden + ret.Msg = err.Error() + return + } + _, err = os.Stat(fileAbsPath) if os.IsNotExist(err) { - ret.Code = 404 + ret.Code = http.StatusNotFound return } if nil != err { - logging.LogErrorf("stat [%s] failed: %s", filePath, err) - ret.Code = 500 + logging.LogErrorf("stat [%s] failed: %s", fileAbsPath, err) + ret.Code = http.StatusInternalServerError ret.Msg = err.Error() return } - if err = filelock.Remove(filePath); nil != err { - logging.LogErrorf("remove [%s] failed: %s", filePath, err) - ret.Code = 500 + if err = filelock.Remove(fileAbsPath); nil != err { + logging.LogErrorf("remove [%s] failed: %s", fileAbsPath, err) + ret.Code = http.StatusInternalServerError ret.Msg = err.Error() return } @@ -262,30 +291,36 @@ func putFile(c *gin.Context) { ret := gulu.Ret.NewResult() defer c.JSON(http.StatusOK, ret) + var err error filePath := c.PostForm("path") - filePath = filepath.Join(util.WorkspaceDir, filePath) + fileAbsPath, err := util.GetAbsPathInWorkspace(filePath) + if nil != err { + ret.Code = http.StatusForbidden + ret.Msg = err.Error() + return + } + isDirStr := c.PostForm("isDir") isDir, _ := strconv.ParseBool(isDirStr) - var err error if isDir { - err = os.MkdirAll(filePath, 0755) + err = os.MkdirAll(fileAbsPath, 0755) if nil != err { - logging.LogErrorf("make a dir [%s] failed: %s", filePath, err) + logging.LogErrorf("make dir [%s] failed: %s", fileAbsPath, err) } } else { fileHeader, _ := c.FormFile("file") if nil == fileHeader { - logging.LogErrorf("form file is nil [path=%s]", filePath) - ret.Code = 400 + logging.LogErrorf("form file is nil [path=%s]", fileAbsPath) + ret.Code = http.StatusBadRequest ret.Msg = "form file is nil" return } for { - dir := filepath.Dir(filePath) + dir := filepath.Dir(fileAbsPath) if err = os.MkdirAll(dir, 0755); nil != err { - logging.LogErrorf("put a file [%s] make dir [%s] failed: %s", filePath, dir, err) + logging.LogErrorf("put file [%s] make dir [%s] failed: %s", fileAbsPath, dir, err) break } @@ -303,9 +338,9 @@ func putFile(c *gin.Context) { break } - err = filelock.WriteFile(filePath, data) + err = filelock.WriteFile(fileAbsPath, data) if nil != err { - logging.LogErrorf("put a file [%s] failed: %s", filePath, err) + logging.LogErrorf("write file [%s] failed: %s", fileAbsPath, err) break } break @@ -323,15 +358,15 @@ func putFile(c *gin.Context) { modTimeInt, parseErr := strconv.ParseInt(modTimeStr, 10, 64) if nil != parseErr { logging.LogErrorf("parse mod time [%s] failed: %s", modTimeStr, parseErr) - ret.Code = 500 + ret.Code = http.StatusInternalServerError ret.Msg = parseErr.Error() return } modTime = millisecond2Time(modTimeInt) } - if err = os.Chtimes(filePath, modTime, modTime); nil != err { + if err = os.Chtimes(fileAbsPath, modTime, modTime); nil != err { logging.LogErrorf("change time failed: %s", err) - ret.Code = 500 + ret.Code = http.StatusInternalServerError ret.Msg = err.Error() return } diff --git a/kernel/util/path.go b/kernel/util/path.go index 8ea46c66b..8c653a31f 100644 --- a/kernel/util/path.go +++ b/kernel/util/path.go @@ -265,3 +265,12 @@ func IsDisplayableAsset(p string) bool { } return false } + +func GetAbsPathInWorkspace(relPath string) (string, error) { + absPath := filepath.Join(WorkspaceDir, relPath) + if IsSubPath(WorkspaceDir, absPath) { + return absPath, nil + } else { + return "", os.ErrPermission + } +}