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