feat: implement VerifyPathForSafeMode v1.4.1
This commit is contained in:
parent
18e85b1ff1
commit
daf1fa2289
72
file.go
72
file.go
@ -2,6 +2,7 @@ package file
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
@ -9,6 +10,9 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"apigo.cc/go/cast"
|
||||||
|
"apigo.cc/go/jsmod"
|
||||||
)
|
)
|
||||||
|
|
||||||
type FileInfo struct {
|
type FileInfo struct {
|
||||||
@ -268,3 +272,71 @@ func RunCommand(command string, args ...string) ([]string, error) {
|
|||||||
}
|
}
|
||||||
return lines, nil
|
return lines, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RealPath 返回客观的物理绝对路径,处理软连接和相对路径
|
||||||
|
func RealPath(path string) string {
|
||||||
|
abs, err := filepath.Abs(path)
|
||||||
|
if err != nil {
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
// 尝试解析软连接
|
||||||
|
real, err := filepath.EvalSymlinks(abs)
|
||||||
|
if err == nil {
|
||||||
|
return real
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果文件不存在,EvalSymlinks 会失败,此时返回绝对路径即可(后续写入操作会处理)
|
||||||
|
return abs
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyPathForSafeMode 在安全模式下校验路径是否在 AllowedDirs 白名单内
|
||||||
|
func VerifyPathForSafeMode(ctx context.Context, path string) (string, error) {
|
||||||
|
// 如果不是安全模式,直接返回 RealPath
|
||||||
|
if ctx == nil || !jsmod.IsSafeMode(ctx) {
|
||||||
|
return RealPath(path), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. 获取白名单 (约定从 ctx 获取 "AllowedDirs")
|
||||||
|
var allowedDirs []string
|
||||||
|
cast.Convert(&allowedDirs, ctx.Value("AllowedDirs"))
|
||||||
|
if len(allowedDirs) == 0 {
|
||||||
|
return "", fmt.Errorf("file: access denied, AllowedDirs not found in context")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 获取物理路径
|
||||||
|
real := RealPath(path)
|
||||||
|
|
||||||
|
// 3. 校验逻辑:判断当前物理路径是否以任何一个白名单目录为前缀
|
||||||
|
isAllowed := false
|
||||||
|
for _, dir := range allowedDirs {
|
||||||
|
if strings.HasPrefix(real, dir) {
|
||||||
|
isAllowed = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 写操作保护:如果路径不存在,追溯其存在的父目录权限
|
||||||
|
if !isAllowed {
|
||||||
|
temp := real
|
||||||
|
for temp != "" && temp != string(os.PathSeparator) {
|
||||||
|
temp = filepath.Dir(temp)
|
||||||
|
if _, err := os.Stat(temp); err == nil {
|
||||||
|
// 找到了第一个存在的祖先目录,检查它是否受控
|
||||||
|
for _, dir := range allowedDirs {
|
||||||
|
if strings.HasPrefix(temp, dir) {
|
||||||
|
isAllowed = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isAllowed {
|
||||||
|
return "", fmt.Errorf("file: permission denied for path %s", path)
|
||||||
|
}
|
||||||
|
|
||||||
|
return real, nil
|
||||||
|
}
|
||||||
|
|||||||
191
js_export.go
191
js_export.go
@ -1,38 +1,165 @@
|
|||||||
package file
|
package file
|
||||||
|
|
||||||
import "apigo.cc/go/jsmod"
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"apigo.cc/go/jsmod"
|
||||||
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
jsmod.Register("file", map[string]any{
|
jsmod.Register("file", map[string]any{
|
||||||
"exists": Exists,
|
// 读操作 (映射到私有包装器)
|
||||||
"read": Read,
|
"exists": func(ctx context.Context, path string) bool {
|
||||||
"readBytes": ReadBytes,
|
p, err := VerifyPathForSafeMode(ctx, path)
|
||||||
"readLines": ReadLines,
|
if err != nil {
|
||||||
"readDir": ReadDir,
|
return false
|
||||||
"getFileInfo": GetFileInfo,
|
}
|
||||||
"getAbsFilename": GetAbsFilename,
|
return Exists(p)
|
||||||
"write": Write,
|
},
|
||||||
"writeBytes": WriteBytes,
|
"read": func(ctx context.Context, path string) (string, error) {
|
||||||
"remove": Remove,
|
p, err := VerifyPathForSafeMode(ctx, path)
|
||||||
"mkdir": Mkdir,
|
if err != nil {
|
||||||
"copy": Copy,
|
return "", err
|
||||||
"move": Move,
|
}
|
||||||
"replace": Replace,
|
return Read(p)
|
||||||
"unmarshalFile": UnmarshalFile,
|
},
|
||||||
"marshalFile": MarshalFile,
|
"readBytes": func(ctx context.Context, path string) ([]byte, error) {
|
||||||
"marshalFilePretty": MarshalFilePretty,
|
p, err := VerifyPathForSafeMode(ctx, path)
|
||||||
"patchFile": PatchFile,
|
if err != nil {
|
||||||
"archive": Archive,
|
return nil, err
|
||||||
"extract": Extract,
|
}
|
||||||
"compress": Compress,
|
return ReadBytes(p)
|
||||||
"decompress": Decompress,
|
},
|
||||||
"readFileFromMemory": ReadFileFromMemory,
|
"readLines": func(ctx context.Context, path string) ([]string, error) {
|
||||||
"readDirFromMemory": ReadDirFromMemory,
|
p, err := VerifyPathForSafeMode(ctx, path)
|
||||||
},
|
if err != nil {
|
||||||
"exists", "read", "readBytes", "readLines", "readDir", "getFileInfo", "getAbsFilename",
|
return nil, err
|
||||||
"write", "writeBytes", "remove", "mkdir", "copy", "move", "replace",
|
}
|
||||||
"unmarshalFile", "marshalFile", "marshalFilePretty", "patchFile",
|
return ReadLines(p)
|
||||||
"archive", "extract", "compress", "decompress",
|
},
|
||||||
"readFileFromMemory", "readDirFromMemory",
|
"readDir": func(ctx context.Context, path string) ([]FileInfo, error) {
|
||||||
)
|
p, err := VerifyPathForSafeMode(ctx, path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return ReadDir(p)
|
||||||
|
},
|
||||||
|
"getFileInfo": func(ctx context.Context, path string) *FileInfo {
|
||||||
|
p, err := VerifyPathForSafeMode(ctx, path)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return GetFileInfo(p)
|
||||||
|
},
|
||||||
|
|
||||||
|
// 写操作
|
||||||
|
"write": func(ctx context.Context, path string, content string) error {
|
||||||
|
p, err := VerifyPathForSafeMode(ctx, path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return Write(p, content)
|
||||||
|
},
|
||||||
|
"writeBytes": func(ctx context.Context, path string, content []byte) error {
|
||||||
|
p, err := VerifyPathForSafeMode(ctx, path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return WriteBytes(p, content)
|
||||||
|
},
|
||||||
|
"remove": func(ctx context.Context, path string) error {
|
||||||
|
p, err := VerifyPathForSafeMode(ctx, path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return Remove(p)
|
||||||
|
},
|
||||||
|
"mkdir": func(ctx context.Context, path string) error {
|
||||||
|
p, err := VerifyPathForSafeMode(ctx, path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return Mkdir(p)
|
||||||
|
},
|
||||||
|
"copy": func(ctx context.Context, from, to string) error {
|
||||||
|
pFrom, err := VerifyPathForSafeMode(ctx, from)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
pTo, err := VerifyPathForSafeMode(ctx, to)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return Copy(pFrom, pTo)
|
||||||
|
},
|
||||||
|
"move": func(ctx context.Context, from, to string) error {
|
||||||
|
pFrom, err := VerifyPathForSafeMode(ctx, from)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
pTo, err := VerifyPathForSafeMode(ctx, to)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return Move(pFrom, pTo)
|
||||||
|
},
|
||||||
|
"replace": func(ctx context.Context, path, old, new string) error {
|
||||||
|
p, err := VerifyPathForSafeMode(ctx, path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return Replace(p, old, new)
|
||||||
|
},
|
||||||
|
|
||||||
|
// 序列化
|
||||||
|
"unmarshalFile": func(ctx context.Context, path string, to any) error {
|
||||||
|
p, err := VerifyPathForSafeMode(ctx, path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return UnmarshalFile(p, to)
|
||||||
|
},
|
||||||
|
"marshalFile": func(ctx context.Context, path string, data any) error {
|
||||||
|
p, err := VerifyPathForSafeMode(ctx, path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return MarshalFile(p, data)
|
||||||
|
},
|
||||||
|
"marshalFilePretty": func(ctx context.Context, path string, data any) error {
|
||||||
|
p, err := VerifyPathForSafeMode(ctx, path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return MarshalFilePretty(p, data)
|
||||||
|
},
|
||||||
|
|
||||||
|
// 归档
|
||||||
|
"archive": func(ctx context.Context, src, dest string) error {
|
||||||
|
pSrc, err := VerifyPathForSafeMode(ctx, src)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
pDest, err := VerifyPathForSafeMode(ctx, dest)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return Archive(pSrc, pDest)
|
||||||
|
},
|
||||||
|
"extract": func(ctx context.Context, src, dest string, strip bool) error {
|
||||||
|
pSrc, err := VerifyPathForSafeMode(ctx, src)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
pDest, err := VerifyPathForSafeMode(ctx, dest)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return Extract(pSrc, pDest, strip)
|
||||||
|
},
|
||||||
|
|
||||||
|
// 压缩工具 (无路径,不校验)
|
||||||
|
"compress": Compress,
|
||||||
|
"decompress": Decompress,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user