Compare commits

...

4 Commits
v1.4.1 ... main

Author SHA1 Message Date
AI Engineer
a97e1f50bf refactor: use jsmod.Get for context configuration (by AI) 2026-06-10 09:45:42 +08:00
AI Engineer
ba95b78adb feat: align JS exports to PascalCase and fix encoding dependency (by AI) 2026-06-10 02:19:58 +08:00
AI Engineer
72a1916cee feat: add Append method with automatic parent directory creation 2026-06-08 08:44:56 +08:00
AI Engineer
31fc9dffc0 对齐 Tag v1.5.0 (By AI) 2026-06-03 20:10:24 +08:00
6 changed files with 56 additions and 44 deletions

View File

@ -1,5 +1,8 @@
# Changelog # Changelog
## v1.5.2 (2026-06-08)
- **JS 对齐**: 将所有注册到 `jsmod` 的导出方法名统一为 PascalCase`Read`, `Write`, `GetFileInfo`),消除 JS 环境下的调用摩擦。
## [1.3.3] - 2026-05-30 ## [1.3.3] - 2026-05-30
- **新增**: 注册到 `jsmod`将所有文件操作方法Read/Write/Archive 等注册为高危方法unsafeList确保安全沙箱隔离。 - **新增**: 注册到 `jsmod`将所有文件操作方法Read/Write/Archive 等注册为高危方法unsafeList确保安全沙箱隔离。

15
file.go
View File

@ -123,6 +123,17 @@ func Write(filename string, content string) error {
return WriteBytes(filename, []byte(content)) return WriteBytes(filename, []byte(content))
} }
func Append(filename string, content []byte) error {
EnsureParentDir(filename)
f, err := os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return err
}
defer f.Close()
_, err = f.Write(content)
return err
}
func Copy(from, to string) error { func Copy(from, to string) error {
fromStat, err := os.Stat(from) fromStat, err := os.Stat(from)
if err != nil { if err != nil {
@ -297,9 +308,9 @@ func VerifyPathForSafeMode(ctx context.Context, path string) (string, error) {
return RealPath(path), nil return RealPath(path), nil
} }
// 1. 获取白名单 (约定从 ctx 获取 "AllowedDirs") // 1. 获取白名单
var allowedDirs []string var allowedDirs []string
cast.Convert(&allowedDirs, ctx.Value("AllowedDirs")) cast.Convert(&allowedDirs, jsmod.Get(ctx, "AllowedDirs"))
if len(allowedDirs) == 0 { if len(allowedDirs) == 0 {
return "", fmt.Errorf("file: access denied, AllowedDirs not found in context") return "", fmt.Errorf("file: access denied, AllowedDirs not found in context")
} }

14
go.mod
View File

@ -3,18 +3,18 @@ module apigo.cc/go/file
go 1.25.0 go 1.25.0
require ( require (
apigo.cc/go/cast v1.3.3 apigo.cc/go/cast v1.5.0
apigo.cc/go/encoding v1.3.1 apigo.cc/go/encoding v1.5.0
apigo.cc/go/jsmod v1.0.0 apigo.cc/go/jsmod v1.5.0
apigo.cc/go/safe v1.3.1 apigo.cc/go/safe v1.5.0
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
) )
require ( require (
apigo.cc/go/rand v1.3.1 // indirect apigo.cc/go/rand v1.5.0 // indirect
github.com/kr/pretty v0.3.0 // indirect github.com/kr/pretty v0.3.0 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect
golang.org/x/crypto v0.51.0 // indirect golang.org/x/crypto v0.52.0 // indirect
golang.org/x/sys v0.44.0 // indirect golang.org/x/sys v0.45.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
) )

26
go.sum
View File

@ -1,13 +1,13 @@
apigo.cc/go/cast v1.3.3 h1:aln5eDR5DZVWVzZ/y5SJh1gQNgWv2sT82I25NaO9g34= apigo.cc/go/cast v1.5.0 h1:UBGJtFQ8eJPMQXs37cUgqd7YQo1zI9opuSDBDmn2/pE=
apigo.cc/go/cast v1.3.3/go.mod h1:lGlwImiOvHxG7buyMWhFzcdvQzmSaoKbmr7bcDfUpHk= apigo.cc/go/cast v1.5.0/go.mod h1:z2GW5p5WCZGEqVVIJUdhl232vRbLf2Qu4EDlEakX/D8=
apigo.cc/go/encoding v1.3.1 h1:y8O58KYAyulkThg1O2ji2BqjnFoSvk42sit9I3z+K7Y= apigo.cc/go/encoding v1.5.0 h1:EJNdRVDOMoI2DAvZwQNQTbYuqB/6zsEzvg7lS5pQI+I=
apigo.cc/go/encoding v1.3.1/go.mod h1:xAJk5b83VZ31mXMTnyp0dfMoBKfT/AHDn0u+cQfojgY= apigo.cc/go/encoding v1.5.0/go.mod h1:8++NfZj3hWig0qh2g7GQRw/4LpSvCYMWUZ+8J+x58cA=
apigo.cc/go/jsmod v1.0.0 h1:lVQMq0tCno4kbHlQ3j5wzsm+v24J+bznIoHxpton0pE= apigo.cc/go/jsmod v1.5.0 h1:JgQtJNiJWy1NOP9AzE8NX5VXJkpO/x3GqLsCCSny5Ec=
apigo.cc/go/jsmod v1.0.0/go.mod h1:bmyeZtOAP/j5am+YRnaiM89smysK24K7ebk0koFtsSw= apigo.cc/go/jsmod v1.5.0/go.mod h1:bmyeZtOAP/j5am+YRnaiM89smysK24K7ebk0koFtsSw=
apigo.cc/go/rand v1.3.1 h1:7FvsI6PtQ5XrWER0dTiLVo0p7GIxRidT/TBKhVy93j8= apigo.cc/go/rand v1.5.0 h1:1o8hh8fhdBuk1/h02IvugvamuT3dkWbVJrqEJVQKB2E=
apigo.cc/go/rand v1.3.1/go.mod h1:mZ/4Soa3bk+XvDaqPWJuUe1bfEi4eThBj1XmEAuYxsk= apigo.cc/go/rand v1.5.0/go.mod h1:Lh98S2dm9UY0X+M+kNQQEKyXHG5pcCKSFPyXN0QCGdk=
apigo.cc/go/safe v1.3.1 h1:irTCqPAC97gGsX/Lw5AzLelDt1xXLEZIAaVhLELWe9Q= apigo.cc/go/safe v1.5.0 h1:W1NblmcU8cex1f9Y5z8mNLUJOzZTE1s6fszb3FbhGnk=
apigo.cc/go/safe v1.3.1/go.mod h1:XdOpBhN2vkImalaykYXXmEpczqWa1y3ah6/Q72cdRqE= apigo.cc/go/safe v1.5.0/go.mod h1:OfQ5d6COePSGEuPvMeOk6KagX2sezw7nvKh7exj9SeM=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
@ -20,10 +20,8 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI= golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988=
golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8= golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ=
golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=

View File

@ -9,42 +9,42 @@ import (
func init() { func init() {
jsmod.Register("file", map[string]any{ jsmod.Register("file", map[string]any{
// 读操作 (映射到私有包装器) // 读操作 (映射到私有包装器)
"exists": func(ctx context.Context, path string) bool { "Exists": func(ctx context.Context, path string) bool {
p, err := VerifyPathForSafeMode(ctx, path) p, err := VerifyPathForSafeMode(ctx, path)
if err != nil { if err != nil {
return false return false
} }
return Exists(p) return Exists(p)
}, },
"read": func(ctx context.Context, path string) (string, error) { "Read": func(ctx context.Context, path string) (string, error) {
p, err := VerifyPathForSafeMode(ctx, path) p, err := VerifyPathForSafeMode(ctx, path)
if err != nil { if err != nil {
return "", err return "", err
} }
return Read(p) return Read(p)
}, },
"readBytes": func(ctx context.Context, path string) ([]byte, error) { "ReadBytes": func(ctx context.Context, path string) ([]byte, error) {
p, err := VerifyPathForSafeMode(ctx, path) p, err := VerifyPathForSafeMode(ctx, path)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return ReadBytes(p) return ReadBytes(p)
}, },
"readLines": func(ctx context.Context, path string) ([]string, error) { "ReadLines": func(ctx context.Context, path string) ([]string, error) {
p, err := VerifyPathForSafeMode(ctx, path) p, err := VerifyPathForSafeMode(ctx, path)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return ReadLines(p) return ReadLines(p)
}, },
"readDir": func(ctx context.Context, path string) ([]FileInfo, error) { "ReadDir": func(ctx context.Context, path string) ([]FileInfo, error) {
p, err := VerifyPathForSafeMode(ctx, path) p, err := VerifyPathForSafeMode(ctx, path)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return ReadDir(p) return ReadDir(p)
}, },
"getFileInfo": func(ctx context.Context, path string) *FileInfo { "GetFileInfo": func(ctx context.Context, path string) *FileInfo {
p, err := VerifyPathForSafeMode(ctx, path) p, err := VerifyPathForSafeMode(ctx, path)
if err != nil { if err != nil {
return nil return nil
@ -53,35 +53,35 @@ func init() {
}, },
// 写操作 // 写操作
"write": func(ctx context.Context, path string, content string) error { "Write": func(ctx context.Context, path string, content string) error {
p, err := VerifyPathForSafeMode(ctx, path) p, err := VerifyPathForSafeMode(ctx, path)
if err != nil { if err != nil {
return err return err
} }
return Write(p, content) return Write(p, content)
}, },
"writeBytes": func(ctx context.Context, path string, content []byte) error { "WriteBytes": func(ctx context.Context, path string, content []byte) error {
p, err := VerifyPathForSafeMode(ctx, path) p, err := VerifyPathForSafeMode(ctx, path)
if err != nil { if err != nil {
return err return err
} }
return WriteBytes(p, content) return WriteBytes(p, content)
}, },
"remove": func(ctx context.Context, path string) error { "Remove": func(ctx context.Context, path string) error {
p, err := VerifyPathForSafeMode(ctx, path) p, err := VerifyPathForSafeMode(ctx, path)
if err != nil { if err != nil {
return err return err
} }
return Remove(p) return Remove(p)
}, },
"mkdir": func(ctx context.Context, path string) error { "Mkdir": func(ctx context.Context, path string) error {
p, err := VerifyPathForSafeMode(ctx, path) p, err := VerifyPathForSafeMode(ctx, path)
if err != nil { if err != nil {
return err return err
} }
return Mkdir(p) return Mkdir(p)
}, },
"copy": func(ctx context.Context, from, to string) error { "Copy": func(ctx context.Context, from, to string) error {
pFrom, err := VerifyPathForSafeMode(ctx, from) pFrom, err := VerifyPathForSafeMode(ctx, from)
if err != nil { if err != nil {
return err return err
@ -92,7 +92,7 @@ func init() {
} }
return Copy(pFrom, pTo) return Copy(pFrom, pTo)
}, },
"move": func(ctx context.Context, from, to string) error { "Move": func(ctx context.Context, from, to string) error {
pFrom, err := VerifyPathForSafeMode(ctx, from) pFrom, err := VerifyPathForSafeMode(ctx, from)
if err != nil { if err != nil {
return err return err
@ -103,7 +103,7 @@ func init() {
} }
return Move(pFrom, pTo) return Move(pFrom, pTo)
}, },
"replace": func(ctx context.Context, path, old, new string) error { "Replace": func(ctx context.Context, path, old, new string) error {
p, err := VerifyPathForSafeMode(ctx, path) p, err := VerifyPathForSafeMode(ctx, path)
if err != nil { if err != nil {
return err return err
@ -112,21 +112,21 @@ func init() {
}, },
// 序列化 // 序列化
"unmarshalFile": func(ctx context.Context, path string, to any) error { "UnmarshalFile": func(ctx context.Context, path string, to any) error {
p, err := VerifyPathForSafeMode(ctx, path) p, err := VerifyPathForSafeMode(ctx, path)
if err != nil { if err != nil {
return err return err
} }
return UnmarshalFile(p, to) return UnmarshalFile(p, to)
}, },
"marshalFile": func(ctx context.Context, path string, data any) error { "MarshalFile": func(ctx context.Context, path string, data any) error {
p, err := VerifyPathForSafeMode(ctx, path) p, err := VerifyPathForSafeMode(ctx, path)
if err != nil { if err != nil {
return err return err
} }
return MarshalFile(p, data) return MarshalFile(p, data)
}, },
"marshalFilePretty": func(ctx context.Context, path string, data any) error { "MarshalFilePretty": func(ctx context.Context, path string, data any) error {
p, err := VerifyPathForSafeMode(ctx, path) p, err := VerifyPathForSafeMode(ctx, path)
if err != nil { if err != nil {
return err return err
@ -135,7 +135,7 @@ func init() {
}, },
// 归档 // 归档
"archive": func(ctx context.Context, src, dest string) error { "Archive": func(ctx context.Context, src, dest string) error {
pSrc, err := VerifyPathForSafeMode(ctx, src) pSrc, err := VerifyPathForSafeMode(ctx, src)
if err != nil { if err != nil {
return err return err
@ -146,7 +146,7 @@ func init() {
} }
return Archive(pSrc, pDest) return Archive(pSrc, pDest)
}, },
"extract": func(ctx context.Context, src, dest string, strip bool) error { "Extract": func(ctx context.Context, src, dest string, strip bool) error {
pSrc, err := VerifyPathForSafeMode(ctx, src) pSrc, err := VerifyPathForSafeMode(ctx, src)
if err != nil { if err != nil {
return err return err
@ -159,7 +159,7 @@ func init() {
}, },
// 压缩工具 (无路径,不校验) // 压缩工具 (无路径,不校验)
"compress": Compress, "Compress": Compress,
"decompress": Decompress, "Decompress": Decompress,
}) })
} }

View File

@ -186,7 +186,7 @@ func LoadFileToB64(filename string) *MemFileB64 {
ModTime: info.ModTime(), ModTime: info.ModTime(),
IsDir: false, IsDir: false,
Size: info.Size(), Size: info.Size(),
DataB64: encoding.Base64(data), DataB64: []byte(encoding.Base64(data)),
Compressed: compressed, Compressed: compressed,
} }
} }