2026-04-06 00:20:51 -05:00

233 lines
5.4 KiB
Go

package axscript
import (
"AdaptixServer/core/utils/fsystem"
"fmt"
"path/filepath"
"strings"
"sync"
"github.com/dop251/goja"
)
type ScriptEngine struct {
mu sync.Mutex
runtime *goja.Runtime
name string
manager *ScriptManager
functions map[string]goja.Callable
scriptPath string
scriptDir string
allowedRoots []string
importedFiles []string
}
func NewScriptEngine(name string, manager *ScriptManager) *ScriptEngine {
rt := goja.New()
rt.SetFieldNameMapper(goja.UncapFieldNameMapper())
engine := &ScriptEngine{
runtime: rt,
name: name,
manager: manager,
functions: make(map[string]goja.Callable),
}
consoleObj := rt.NewObject()
consoleObj.Set("log", func(call goja.FunctionCall) goja.Value { return goja.Undefined() })
consoleObj.Set("warn", func(call goja.FunctionCall) goja.Value { return goja.Undefined() })
consoleObj.Set("error", func(call goja.FunctionCall) goja.Value { return goja.Undefined() })
consoleObj.Set("info", func(call goja.FunctionCall) goja.Value { return goja.Undefined() })
consoleObj.Set("debug", func(call goja.FunctionCall) goja.Value { return goja.Undefined() })
rt.Set("console", consoleObj)
return engine
}
func NewScriptEngineFromPath(scriptPath string, manager *ScriptManager) (*ScriptEngine, error) {
abs, err := filepath.Abs(scriptPath)
if err != nil {
return nil, fmt.Errorf("invalid script path: %w", err)
}
resolved, err := fsystem.ResolveRealPath(abs)
if err != nil {
return nil, fmt.Errorf("cannot resolve script path: %w", err)
}
dir := filepath.Dir(resolved)
engine := NewScriptEngine(resolved, manager)
engine.scriptPath = resolved
engine.scriptDir = dir + "/"
engine.allowedRoots = []string{dir}
if manager != nil {
for _, root := range manager.globalAllowedRoots {
engine.allowedRoots = append(engine.allowedRoots, root)
}
}
return engine, nil
}
func (e *ScriptEngine) Execute(script string) error {
e.mu.Lock()
defer e.mu.Unlock()
_, err := e.runtime.RunString(script)
if err != nil {
return fmt.Errorf("script execution error in '%s': %w", e.name, err)
}
return nil
}
//////////
// /---
func (e *ScriptEngine) ScriptDir() string {
return e.scriptDir
}
// /---
func (e *ScriptEngine) ScriptPath() string {
return e.scriptPath
}
func (e *ScriptEngine) ValidatePath(path string) (string, error) {
abs, err := filepath.Abs(path)
if err != nil {
return "", fmt.Errorf("invalid path: %w", err)
}
real, err := fsystem.ResolveRealPath(abs)
if err != nil {
return "", fmt.Errorf("cannot resolve path: %w", err)
}
for _, root := range e.allowedRoots {
if strings.HasPrefix(real, root+"/") || real == root {
return real, nil
}
}
return "", fmt.Errorf("access denied: %s is outside allowed directories", path)
}
func (e *ScriptEngine) AddImportedFile(path string) {
for _, p := range e.importedFiles {
if p == path {
return
}
}
e.importedFiles = append(e.importedFiles, path)
}
func (e *ScriptEngine) GetImportedFiles() []string {
result := make([]string, len(e.importedFiles))
copy(result, e.importedFiles)
return result
}
// /---
func (e *ScriptEngine) Name() string {
return e.name
}
// /---
func (e *ScriptEngine) Runtime() *goja.Runtime {
return e.runtime
}
// /---
func (e *ScriptEngine) CallFunction(name string, args ...goja.Value) (goja.Value, error) {
e.mu.Lock()
defer e.mu.Unlock()
fn, ok := goja.AssertFunction(e.runtime.Get(name))
if !ok {
return nil, fmt.Errorf("function '%s' not found or not callable in engine '%s'", name, e.name)
}
result, err := fn(goja.Undefined(), args...)
if err != nil {
return nil, fmt.Errorf("error calling '%s' in engine '%s': %w", name, e.name, err)
}
return result, nil
}
func (e *ScriptEngine) CallCallable(fn goja.Callable, args ...goja.Value) (goja.Value, error) {
e.mu.Lock()
defer e.mu.Unlock()
result, err := fn(goja.Undefined(), args...)
if err != nil {
return nil, fmt.Errorf("error calling callable in engine '%s': %w", e.name, err)
}
return result, nil
}
// /---
func (e *ScriptEngine) StoreFunctionRef(id string, fn goja.Callable) {
e.mu.Lock()
defer e.mu.Unlock()
e.functions[id] = fn
}
// /---
func (e *ScriptEngine) GetFunctionRef(id string) (goja.Callable, bool) {
e.mu.Lock()
defer e.mu.Unlock()
fn, ok := e.functions[id]
return fn, ok
}
// /---
func (e *ScriptEngine) RemoveFunctionRef(id string) {
e.mu.Lock()
defer e.mu.Unlock()
delete(e.functions, id)
}
func (e *ScriptEngine) ToValue(v interface{}) goja.Value {
return e.runtime.ToValue(v)
}
// /---
func (e *ScriptEngine) SetGlobal(name string, value interface{}) {
e.runtime.Set(name, value)
}
func (e *ScriptEngine) GetMetadataName() string {
metaVal := e.runtime.Get("metadata")
if metaVal == nil || goja.IsUndefined(metaVal) || goja.IsNull(metaVal) {
return ""
}
metaObj := metaVal.ToObject(e.runtime)
if metaObj == nil {
return ""
}
nameVal := metaObj.Get("name")
if nameVal == nil || goja.IsUndefined(nameVal) || goja.IsNull(nameVal) {
return ""
}
return nameVal.String()
}
func (e *ScriptEngine) GetMetadataNoSave() bool {
metaVal := e.runtime.Get("metadata")
if metaVal == nil || goja.IsUndefined(metaVal) || goja.IsNull(metaVal) {
return false
}
metaObj := metaVal.ToObject(e.runtime)
if metaObj == nil {
return false
}
nosaveVal := metaObj.Get("nosave")
if nosaveVal == nil || goja.IsUndefined(nosaveVal) || goja.IsNull(nosaveVal) {
return false
}
return nosaveVal.ToBoolean()
}