Compare commits
No commits in common. "main" and "v1.5.3" have entirely different histories.
142
js_export.go
142
js_export.go
@ -12,106 +12,48 @@ import (
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
jsmod.Register("db", map[string]any{
|
jsmod.Register("db", map[string]any{
|
||||||
// 入口:支持别名获取,不传则默认 "default"
|
"get": func(ctx context.Context, name string) (*jsDB, error) {
|
||||||
"Get": func(ctx context.Context, name *string) (*jsDB, error) {
|
isReadOnly := false
|
||||||
target := "default"
|
|
||||||
if name != nil {
|
|
||||||
target = *name
|
|
||||||
}
|
|
||||||
return getJSDB(ctx, target)
|
|
||||||
},
|
|
||||||
|
|
||||||
// 默认快捷调用 (面向 "default" 实例)
|
// 安全模式下的动态判定
|
||||||
"Query": func(ctx context.Context, query string, args ...any) (*QueryResult, error) {
|
if jsmod.IsSafeMode(ctx) {
|
||||||
jd, err := getJSDB(ctx, "default")
|
if strings.HasPrefix(name, "sqlite://") {
|
||||||
if err != nil {
|
// 1. SQLite 路径校验
|
||||||
return nil, err
|
path := strings.TrimPrefix(name, "sqlite://")
|
||||||
}
|
if idx := strings.Index(path, "?"); idx != -1 {
|
||||||
return jd.Query(query, args...), nil
|
path = path[:idx]
|
||||||
},
|
}
|
||||||
"Exec": func(ctx context.Context, query string, args ...any) (*ExecResult, error) {
|
// 如果路径不在沙箱内,VerifyPathForSafeMode 会直接返回 error,阻止连接
|
||||||
jd, err := getJSDB(ctx, "default")
|
_, err := file.VerifyPathForSafeMode(ctx, path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return jd.Exec(query, args...), nil
|
isReadOnly = false
|
||||||
},
|
} else {
|
||||||
"Insert": func(ctx context.Context, table string, data any) (*ExecResult, error) {
|
// 2. 远程数据库前缀校验
|
||||||
jd, err := getJSDB(ctx, "default")
|
var allowedDSNs []string
|
||||||
if err != nil {
|
cast.Convert(&allowedDSNs, ctx.Value("AllowedDSNs"))
|
||||||
return nil, err
|
matched := false
|
||||||
}
|
for _, prefix := range allowedDSNs {
|
||||||
return jd.Insert(table, data), nil
|
if strings.HasPrefix(name, prefix) {
|
||||||
},
|
matched = true
|
||||||
"Update": func(ctx context.Context, table string, data any, conditions string, args ...any) (*ExecResult, error) {
|
break
|
||||||
jd, err := getJSDB(ctx, "default")
|
}
|
||||||
if err != nil {
|
}
|
||||||
return nil, err
|
if !matched {
|
||||||
}
|
// 未命中白名单,降级为只读模式
|
||||||
return jd.Update(table, data, conditions, args...), nil
|
isReadOnly = true
|
||||||
},
|
}
|
||||||
"Delete": func(ctx context.Context, table string, conditions string, args ...any) (*ExecResult, error) {
|
|
||||||
jd, err := getJSDB(ctx, "default")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return jd.Delete(table, conditions, args...), nil
|
|
||||||
},
|
|
||||||
"Replace": func(ctx context.Context, table string, data any) (*ExecResult, error) {
|
|
||||||
jd, err := getJSDB(ctx, "default")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return jd.Replace(table, data), nil
|
|
||||||
},
|
|
||||||
"Begin": func(ctx context.Context) (*jsTx, error) {
|
|
||||||
jd, err := getJSDB(ctx, "default")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return jd.Begin()
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func getJSDB(ctx context.Context, name string) (*jsDB, error) {
|
|
||||||
isReadOnly := false
|
|
||||||
|
|
||||||
// 安全模式下的动态判定
|
|
||||||
if jsmod.IsSafeMode(ctx) {
|
|
||||||
if strings.HasPrefix(name, "sqlite://") {
|
|
||||||
// 1. SQLite 路径校验
|
|
||||||
path := strings.TrimPrefix(name, "sqlite://")
|
|
||||||
if idx := strings.Index(path, "?"); idx != -1 {
|
|
||||||
path = path[:idx]
|
|
||||||
}
|
|
||||||
_, err := file.VerifyPathForSafeMode(ctx, path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
isReadOnly = false
|
|
||||||
} else {
|
|
||||||
// 2. 远程数据库前缀校验
|
|
||||||
var allowedDSNs []string
|
|
||||||
cast.Convert(&allowedDSNs, jsmod.Get(ctx, "AllowedDSNs"))
|
|
||||||
matched := false
|
|
||||||
for _, prefix := range allowedDSNs {
|
|
||||||
if strings.HasPrefix(name, prefix) {
|
|
||||||
matched = true
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !matched {
|
|
||||||
isReadOnly = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
d := GetDB(name, nil)
|
d := GetDB(name, nil)
|
||||||
if d.Error != nil {
|
if d.Error != nil {
|
||||||
return nil, d.Error
|
return nil, d.Error
|
||||||
}
|
}
|
||||||
return &jsDB{db: d, ctx: ctx, isReadOnly: isReadOnly}, nil
|
return &jsDB{db: d, ctx: ctx, isReadOnly: isReadOnly}, nil
|
||||||
|
},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
type jsDB struct {
|
type jsDB struct {
|
||||||
@ -179,10 +121,6 @@ func (jd *jsDB) Begin() (*jsTx, error) {
|
|||||||
return &jsTx{tx: tx, ctx: jd.ctx, isReadOnly: jd.isReadOnly}, nil
|
return &jsTx{tx: tx, ctx: jd.ctx, isReadOnly: jd.isReadOnly}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Metadata
|
|
||||||
func (jd *jsDB) InKeys(numArgs int) string { return jd.db.InKeys(numArgs) }
|
|
||||||
func (jd *jsDB) Quote(text string) string { return jd.db.Quote(text) }
|
|
||||||
|
|
||||||
// jsTx wraps *Tx for JS environment
|
// jsTx wraps *Tx for JS environment
|
||||||
type jsTx struct {
|
type jsTx struct {
|
||||||
tx *Tx
|
tx *Tx
|
||||||
@ -244,3 +182,7 @@ func (jt *jsTx) Commit() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (jt *jsTx) Rollback() error { return jt.tx.Rollback() }
|
func (jt *jsTx) Rollback() error { return jt.tx.Rollback() }
|
||||||
|
|
||||||
|
// Metadata
|
||||||
|
func (jd *jsDB) InKeys(numArgs int) string { return jd.db.InKeys(numArgs) }
|
||||||
|
func (jd *jsDB) Quote(text string) string { return jd.db.Quote(text) }
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user