misc: refactored and ported the plugin system to islazy/plugin.Plugin (huge optimization)

This commit is contained in:
evilsocket 2018-10-13 14:13:06 +02:00
parent 788c8d5994
commit dd2e8784dc
9 changed files with 374 additions and 335 deletions

8
Gopkg.lock generated
View file

@ -59,18 +59,19 @@
revision = "f58a169a71a51037728990b2d3597a14f56b525b" revision = "f58a169a71a51037728990b2d3597a14f56b525b"
[[projects]] [[projects]]
digest = "1:32d8e0c62dc075d198abf73428828689f4e9473cadb8ef270be45fb78ae8648b" digest = "1:a029ce916ee511044c6b7fc41133249a15e8d3c16219274666205efd6431e9cd"
name = "github.com/evilsocket/islazy" name = "github.com/evilsocket/islazy"
packages = [ packages = [
"fs", "fs",
"log", "log",
"plugin",
"str", "str",
"tui", "tui",
"zip", "zip",
] ]
pruneopts = "UT" pruneopts = "UT"
revision = "72e580f7bbdfb45d1332e06653248bc609bf0d50" revision = "db3058040a83dba4e35a8931a3e1287c0b802869"
version = "v1.4.0" version = "v1.6.0"
[[projects]] [[projects]]
branch = "master" branch = "master"
@ -288,6 +289,7 @@
"github.com/elazarl/goproxy", "github.com/elazarl/goproxy",
"github.com/evilsocket/islazy/fs", "github.com/evilsocket/islazy/fs",
"github.com/evilsocket/islazy/log", "github.com/evilsocket/islazy/log",
"github.com/evilsocket/islazy/plugin",
"github.com/evilsocket/islazy/str", "github.com/evilsocket/islazy/str",
"github.com/evilsocket/islazy/tui", "github.com/evilsocket/islazy/tui",
"github.com/evilsocket/islazy/zip", "github.com/evilsocket/islazy/zip",

View file

@ -23,7 +23,9 @@
# non-go = false # non-go = false
# go-tests = true # go-tests = true
# unused-packages = true # unused-packages = true
[[constraint]]
name = "github.com/evilsocket/islazy"
version = "1.6.0"
[[constraint]] [[constraint]]
branch = "master" branch = "master"

View file

@ -1,129 +1,64 @@
package modules package modules
import ( import (
"io/ioutil"
"net/http" "net/http"
"github.com/bettercap/bettercap/log" "github.com/bettercap/bettercap/log"
"github.com/bettercap/bettercap/session" "github.com/bettercap/bettercap/session"
"github.com/robertkrimen/otto" "github.com/robertkrimen/otto"
"github.com/evilsocket/islazy/plugin"
) )
type HttpProxyScript struct { type HttpProxyScript struct {
*ProxyScript *plugin.Plugin
onRequestScript *otto.Script
onResponseScript *otto.Script
onCommandScript *otto.Script
}
func LoadHttpProxyScriptSource(path, source string, sess *session.Session) (err error, s *HttpProxyScript) { doOnRequest bool
err, ps := LoadProxyScriptSource(path, source, sess) doOnResponse bool
if err != nil { doOnCommand bool
return
}
s = &HttpProxyScript{
ProxyScript: ps,
onRequestScript: nil,
onResponseScript: nil,
onCommandScript: nil,
}
if s.hasCallback("onRequest") {
s.onRequestScript, err = s.VM.Compile("", "onRequest(req, res)")
if err != nil {
log.Error("Error while compiling onRequest callback: %s", "\nTraceback:\n "+err.(*otto.Error).String())
return
}
}
if s.hasCallback("onResponse") {
s.onResponseScript, err = s.VM.Compile("", "onResponse(req, res)")
if err != nil {
log.Error("Error while compiling onResponse callback: %s", "\nTraceback:\n "+err.(*otto.Error).String())
return
}
}
if s.hasCallback("onCommand") {
s.onCommandScript, err = s.VM.Compile("", "onCommand(cmd)")
if err != nil {
log.Error("Error while compiling onCommand callback: %s", "\nTraceback:\n "+err.(*otto.Error).String())
return
}
}
return
} }
func LoadHttpProxyScript(path string, sess *session.Session) (err error, s *HttpProxyScript) { func LoadHttpProxyScript(path string, sess *session.Session) (err error, s *HttpProxyScript) {
log.Info("loading proxy script %s ...", path) log.Info("loading proxy script %s ...", path)
raw, err := ioutil.ReadFile(path) plug, err := plugin.Load(path)
if err != nil { if err != nil {
return return
} }
return LoadHttpProxyScriptSource(path, string(raw), sess) // define session pointer
} if err = plug.Set("env", sess.Env.Data); err != nil {
log.Error("Error while defining environment: %+v", err)
func (s *HttpProxyScript) doRequestDefines(req *http.Request) (err error, jsreq *JSRequest, jsres *JSResponse) {
jsreq = NewJSRequest(req)
if err = s.VM.Set("req", jsreq); err != nil {
log.Error("Error while defining request: %s", "\nTraceback:\n "+err.(*otto.Error).String())
return return
} }
jsres = NewJSResponse(nil) // run onLoad if defined
if err = s.VM.Set("res", jsres); err != nil { if plug.HasFunc("onLoad") {
log.Error("Error while defining response: %s", "\nTraceback:\n "+err.(*otto.Error).String()) if _, err = plug.Call("onLoad"); err != nil {
log.Error("Error while executing onLoad callback: %s", "\nTraceback:\n "+err.(*otto.Error).String())
return return
} }
return
} }
func (s *HttpProxyScript) doResponseDefines(res *http.Response) (err error, jsreq *JSRequest, jsres *JSResponse) { s = &HttpProxyScript{
jsreq = NewJSRequest(res.Request) Plugin: plug,
if err = s.VM.Set("req", jsreq); err != nil { doOnRequest: plug.HasFunc("onRequest"),
log.Error("Error while defining request: %s", "\nTraceback:\n "+err.(*otto.Error).String()) doOnResponse: plug.HasFunc("onResponse"),
return doOnCommand: plug.HasFunc("onCommand"),
}
jsres = NewJSResponse(res)
if err = s.VM.Set("res", jsres); err != nil {
log.Error("Error while defining response: %s", "\nTraceback:\n "+err.(*otto.Error).String())
return
}
return
}
func (s *HttpProxyScript) doCommandDefines(cmd string) (err error) {
if err = s.VM.Set("cmd", cmd); err != nil {
log.Error("Error while defining cmd: %s", "\nTraceback:\n "+err.(*otto.Error).String())
} }
return return
} }
func (s *HttpProxyScript) OnRequest(original *http.Request) (jsreq *JSRequest, jsres *JSResponse) { func (s *HttpProxyScript) OnRequest(original *http.Request) (jsreq *JSRequest, jsres *JSResponse) {
var err error if s.doOnRequest {
jsreq := NewJSRequest(original)
jsres := NewJSResponse(nil)
if s.onRequestScript != nil { if _, err := s.Call("onRequest", jsreq, jsres); err != nil {
s.Lock() log.Error("%s", err)
defer s.Unlock()
if err, jsreq, jsres = s.doRequestDefines(original); err != nil {
log.Error("Error while running bootstrap definitions: %s", "\nTraceback:\n "+err.(*otto.Error).String())
return nil, nil return nil, nil
} } else if jsreq.WasModified() {
if _, err = s.VM.Run(s.onRequestScript); err != nil {
log.Error("Error while executing onRequest callback: %s", "\nTraceback:\n "+err.(*otto.Error).String())
return nil, nil
}
if jsreq.WasModified() {
jsreq.UpdateHash() jsreq.UpdateHash()
return jsreq, nil return jsreq, nil
} else if jsres.WasModified() { } else if jsres.WasModified() {
@ -136,23 +71,14 @@ func (s *HttpProxyScript) OnRequest(original *http.Request) (jsreq *JSRequest, j
} }
func (s *HttpProxyScript) OnResponse(res *http.Response) (jsreq *JSRequest, jsres *JSResponse) { func (s *HttpProxyScript) OnResponse(res *http.Response) (jsreq *JSRequest, jsres *JSResponse) {
var err error if s.doOnResponse {
jsreq := NewJSRequest(res.Request)
jsres := NewJSResponse(res)
if s.onResponseScript != nil { if _, err := s.Call("onResponse", jsreq, jsres); err != nil {
s.Lock() log.Error("%s", err)
defer s.Unlock()
if err, jsreq, jsres = s.doResponseDefines(res); err != nil {
log.Error("Error while running bootstrap definitions: %s", "\nTraceback:\n "+err.(*otto.Error).String())
return nil, nil return nil, nil
} } else if jsres.WasModified() {
if _, err = s.VM.Run(s.onResponseScript); err != nil {
log.Error("Error while executing onRequest callback: %s", "\nTraceback:\n "+err.(*otto.Error).String())
return nil, nil
}
if jsres.WasModified() {
jsres.UpdateHash() jsres.UpdateHash()
return nil, jsres return nil, jsres
} }
@ -162,19 +88,11 @@ func (s *HttpProxyScript) OnResponse(res *http.Response) (jsreq *JSRequest, jsre
} }
func (s *HttpProxyScript) OnCommand(cmd string) bool { func (s *HttpProxyScript) OnCommand(cmd string) bool {
if s.onCommandScript != nil { if s.doOnCommand {
s.Lock() if ret, err := s.Call("onCommand", cmd); err != nil {
defer s.Unlock() log.Error("Error while executing onCommand callback: %+v", err)
if err := s.doCommandDefines(cmd); err != nil {
log.Error("Error while running bootstrap onCommand definitions: %s", "\nTraceback:\n "+err.(*otto.Error).String())
return false return false
} } else if v, ok := ret.(bool); ok {
if ret, err := s.VM.Run(s.onCommandScript); err != nil {
log.Error("Error while executing onCommand callback: %s", "\nTraceback:\n "+err.(*otto.Error).String())
return false
} else if v, err := ret.ToBoolean(); err == nil {
return v return v
} }
} }

View file

@ -1,37 +0,0 @@
package modules
import (
"net/http"
"testing"
"github.com/bettercap/bettercap/log"
"github.com/bettercap/bettercap/session"
)
func getScript(src string) *HttpProxyScript {
sess := session.Session{}
sess.Env, _ = session.NewEnvironment("")
err, script := LoadHttpProxyScriptSource("", src, &sess)
if err != nil {
log.Fatal("%s", err)
}
return script
}
func getRequest() *http.Request {
req, err := http.NewRequest("GET", "http://www.google.com/", nil)
if err != nil {
log.Fatal("%s", err)
}
return req
}
func BenchmarkOnRequest(b *testing.B) {
script := getScript("function onRequest(req,res){}")
req := getRequest()
for n := 0; n < b.N; n++ {
script.OnRequest(req)
}
}

View file

@ -3,11 +3,12 @@ package modules
import ( import (
"encoding/base64" "encoding/base64"
"io/ioutil" "io/ioutil"
"sync"
"github.com/bettercap/bettercap/log" "github.com/bettercap/bettercap/log"
"github.com/bettercap/bettercap/session" "github.com/bettercap/bettercap/session"
"github.com/evilsocket/islazy/plugin"
"github.com/robertkrimen/otto" "github.com/robertkrimen/otto"
) )
@ -18,71 +19,9 @@ func errOtto(format string, args ...interface{}) otto.Value {
return nullOtto return nullOtto
} }
type ProxyScript struct { func init() {
sync.Mutex
Path string
Source string
VM *otto.Otto
sess *session.Session
cbCacheLock *sync.Mutex
cbCache map[string]bool
}
func LoadProxyScriptSource(path, source string, sess *session.Session) (err error, s *ProxyScript) {
s = &ProxyScript{
Path: path,
Source: source,
VM: otto.New(),
sess: sess,
cbCacheLock: &sync.Mutex{},
cbCache: make(map[string]bool),
}
// this will define callbacks and global objects
_, err = s.VM.Run(s.Source)
if err != nil {
return
}
// define session pointer
err = s.VM.Set("env", sess.Env.Data)
if err != nil {
log.Error("Error while defining environment: %s", "\nTraceback:\n "+err.(*otto.Error).String())
return
}
err = s.defineBuiltins()
if err != nil {
log.Error("Error while defining builtin functions: %s", "\nTraceback:\n "+err.(*otto.Error).String())
return
}
// run onLoad if defined
if s.hasCallback("onLoad") {
_, err = s.VM.Run("onLoad()")
if err != nil {
log.Error("Error while executing onLoad callback: %s", "\nTraceback:\n "+err.(*otto.Error).String())
return
}
}
return
}
func LoadProxyScript(path string, sess *session.Session) (err error, s *ProxyScript) {
raw, err := ioutil.ReadFile(path)
if err != nil {
return
}
return LoadProxyScriptSource(path, string(raw), sess)
}
func (s *ProxyScript) defineBuiltins() error {
// used to read a file ... doh // used to read a file ... doh
s.VM.Set("readFile", func(call otto.FunctionCall) otto.Value { plugin.Defines["readFile"] = func(call otto.FunctionCall) otto.Value {
argv := call.ArgumentList argv := call.ArgumentList
argc := len(argv) argc := len(argv)
if argc != 1 { if argc != 1 {
@ -95,14 +34,14 @@ func (s *ProxyScript) defineBuiltins() error {
return errOtto("Could not read %s: %s", filename, err) return errOtto("Could not read %s: %s", filename, err)
} }
v, err := s.VM.ToValue(string(raw)) v, err := otto.ToValue(string(raw))
if err != nil { if err != nil {
return errOtto("Could not convert to string: %s", err) return errOtto("Could not convert to string: %s", err)
} }
return v return v
}) }
s.VM.Set("writeFile", func(call otto.FunctionCall) otto.Value { plugin.Defines["writeFile"] = func(call otto.FunctionCall) otto.Value {
argv := call.ArgumentList argv := call.ArgumentList
argc := len(argv) argc := len(argv)
if argc != 2 { if argc != 2 {
@ -118,89 +57,89 @@ func (s *ProxyScript) defineBuiltins() error {
} }
return otto.NullValue() return otto.NullValue()
}) }
// log something // log something
s.VM.Set("log", func(call otto.FunctionCall) otto.Value { plugin.Defines["log"] = func(call otto.FunctionCall) otto.Value {
for _, v := range call.ArgumentList { for _, v := range call.ArgumentList {
log.Info("%s", v.String()) log.Info("%s", v.String())
} }
return otto.Value{} return otto.Value{}
}) }
// log debug // log debug
s.VM.Set("log_debug", func(call otto.FunctionCall) otto.Value { plugin.Defines["log_debug"] = func(call otto.FunctionCall) otto.Value {
for _, v := range call.ArgumentList { for _, v := range call.ArgumentList {
log.Debug("%s", v.String()) log.Debug("%s", v.String())
} }
return otto.Value{} return otto.Value{}
}) }
// log info // log info
s.VM.Set("log_info", func(call otto.FunctionCall) otto.Value { plugin.Defines["log_info"] = func(call otto.FunctionCall) otto.Value {
for _, v := range call.ArgumentList { for _, v := range call.ArgumentList {
log.Info("%s", v.String()) log.Info("%s", v.String())
} }
return otto.Value{} return otto.Value{}
}) }
// log warning // log warning
s.VM.Set("log_warn", func(call otto.FunctionCall) otto.Value { plugin.Defines["log_warn"] = func(call otto.FunctionCall) otto.Value {
for _, v := range call.ArgumentList { for _, v := range call.ArgumentList {
log.Warning("%s", v.String()) log.Warning("%s", v.String())
} }
return otto.Value{} return otto.Value{}
}) }
// log error // log error
s.VM.Set("log_error", func(call otto.FunctionCall) otto.Value { plugin.Defines["log_error"] = func(call otto.FunctionCall) otto.Value {
for _, v := range call.ArgumentList { for _, v := range call.ArgumentList {
log.Error("%s", v.String()) log.Error("%s", v.String())
} }
return otto.Value{} return otto.Value{}
}) }
// log fatal // log fatal
s.VM.Set("log_fatal", func(call otto.FunctionCall) otto.Value { plugin.Defines["log_fatal"] = func(call otto.FunctionCall) otto.Value {
for _, v := range call.ArgumentList { for _, v := range call.ArgumentList {
log.Fatal("%s", v.String()) log.Fatal("%s", v.String())
} }
return otto.Value{} return otto.Value{}
}) }
// javascript btoa function // javascript btoa function
s.VM.Set("btoa", func(call otto.FunctionCall) otto.Value { plugin.Defines["btoa"] = func(call otto.FunctionCall) otto.Value {
varValue := base64.StdEncoding.EncodeToString([]byte(call.Argument(0).String())) varValue := base64.StdEncoding.EncodeToString([]byte(call.Argument(0).String()))
v, err := s.VM.ToValue(varValue) v, err := otto.ToValue(varValue)
if err != nil { if err != nil {
return errOtto("Could not convert to string: %s", varValue) return errOtto("Could not convert to string: %s", varValue)
} }
return v return v
}) }
// javascript atob function // javascript atob function
s.VM.Set("atob", func(call otto.FunctionCall) otto.Value { plugin.Defines["atob"] = func(call otto.FunctionCall) otto.Value {
varValue, err := base64.StdEncoding.DecodeString(call.Argument(0).String()) varValue, err := base64.StdEncoding.DecodeString(call.Argument(0).String())
if err != nil { if err != nil {
return errOtto("Could not decode string: %s", call.Argument(0).String()) return errOtto("Could not decode string: %s", call.Argument(0).String())
} }
v, err := s.VM.ToValue(string(varValue)) v, err := otto.ToValue(string(varValue))
if err != nil { if err != nil {
return errOtto("Could not convert to string: %s", varValue) return errOtto("Could not convert to string: %s", varValue)
} }
return v return v
}) }
// read or write environment variable // read or write environment variable
s.VM.Set("env", func(call otto.FunctionCall) otto.Value { plugin.Defines["env"] = func(call otto.FunctionCall) otto.Value {
argv := call.ArgumentList argv := call.ArgumentList
argc := len(argv) argc := len(argv)
if argc == 1 { if argc == 1 {
// get // get
varName := call.Argument(0).String() varName := call.Argument(0).String()
if found, varValue := s.sess.Env.Get(varName); found { if found, varValue := session.I.Env.Get(varName); found {
v, err := s.VM.ToValue(varValue) v, err := otto.ToValue(varValue)
if err != nil { if err != nil {
return errOtto("Could not convert to string: %s", varValue) return errOtto("Could not convert to string: %s", varValue)
} }
@ -211,33 +150,11 @@ func (s *ProxyScript) defineBuiltins() error {
// set // set
varName := call.Argument(0).String() varName := call.Argument(0).String()
varValue := call.Argument(1).String() varValue := call.Argument(1).String()
s.sess.Env.Set(varName, varValue) session.I.Env.Set(varName, varValue)
} else { } else {
return errOtto("env: expected 1 or 2 arguments, %d given instead.", argc) return errOtto("env: expected 1 or 2 arguments, %d given instead.", argc)
} }
return nullOtto return nullOtto
})
return nil
} }
func (s *ProxyScript) hasCallback(name string) bool {
s.cbCacheLock.Lock()
defer s.cbCacheLock.Unlock()
// check the cache
has, found := s.cbCache[name]
if !found {
// check the VM
cb, err := s.VM.Get(name)
if err == nil && cb.IsFunction() {
has = true
} else {
has = false
}
s.cbCache[name] = has
}
return has
} }

View file

@ -1,7 +1,6 @@
package modules package modules
import ( import (
"io/ioutil"
"net" "net"
"strings" "strings"
@ -9,97 +8,62 @@ import (
"github.com/bettercap/bettercap/session" "github.com/bettercap/bettercap/session"
"github.com/robertkrimen/otto" "github.com/robertkrimen/otto"
"github.com/evilsocket/islazy/plugin"
) )
type TcpProxyScript struct { type TcpProxyScript struct {
*ProxyScript *plugin.Plugin
onDataScript *otto.Script doOnData bool
}
func LoadTcpProxyScriptSource(path, source string, sess *session.Session) (err error, s *TcpProxyScript) {
err, ps := LoadProxyScriptSource(path, source, sess)
if err != nil {
return
}
s = &TcpProxyScript{
ProxyScript: ps,
onDataScript: nil,
}
if s.hasCallback("onData") {
s.onDataScript, err = s.VM.Compile("", "onData(from, to, data)")
if err != nil {
log.Error("Error while compiling onData callback: %s", err)
return
}
}
return
} }
func LoadTcpProxyScript(path string, sess *session.Session) (err error, s *TcpProxyScript) { func LoadTcpProxyScript(path string, sess *session.Session) (err error, s *TcpProxyScript) {
log.Info("loading TCP proxy script %s ...", path) log.Info("loading tcp proxy script %s ...", path)
raw, err := ioutil.ReadFile(path) plug, err := plugin.Load(path)
if err != nil { if err != nil {
return return
} }
return LoadTcpProxyScriptSource(path, string(raw), sess) // define session pointer
if err = plug.Set("env", sess.Env.Data); err != nil {
log.Error("Error while defining environment: %+v", err)
return
} }
func (s *TcpProxyScript) doDefines(from, to net.Addr, data []byte) (err error) { // run onLoad if defined
addrFrom := strings.Split(from.String(), ":")[0] if plug.HasFunc("onLoad") {
addrTo := strings.Split(to.String(), ":")[0] if _, err = plug.Call("onLoad"); err != nil {
log.Error("Error while executing onLoad callback: %s", "\nTraceback:\n "+err.(*otto.Error).String())
return
}
}
if err = s.VM.Set("from", addrFrom); err != nil { s = &TcpProxyScript{
log.Error("Error while defining from: %s", err) Plugin: plug,
return doOnData: plug.HasFunc("onData"),
} else if err = s.VM.Set("to", addrTo); err != nil {
log.Error("Error while defining to: %s", err)
return
} else if err = s.VM.Set("data", data); err != nil {
log.Error("Error while defining data: %s", err)
return
} }
return return
} }
func (s *TcpProxyScript) OnData(from, to net.Addr, data []byte) []byte { func (s *TcpProxyScript) OnData(from, to net.Addr, data []byte) []byte {
if s.onDataScript != nil { if s.doOnData {
s.Lock() s.Lock()
defer s.Unlock() defer s.Unlock()
err := s.doDefines(from, to, data) addrFrom := strings.Split(from.String(), ":")[0]
if err != nil { addrTo := strings.Split(to.String(), ":")[0]
log.Error("Error while running bootstrap definitions: %s", err)
return nil
}
ret, err := s.VM.Run(s.onDataScript) if ret, err := s.Call("onData", addrFrom, addrTo, data); err != nil {
if err != nil {
log.Error("Error while executing onData callback: %s", err) log.Error("Error while executing onData callback: %s", err)
return nil return nil
} } else if ret != nil {
array, ok := ret.([]byte)
// do we have any return value to override the buffer with?
if !ret.IsNull() && !ret.IsUndefined() {
exported, err := ret.Export()
if err != nil {
log.Error("Error while exporting results: %s", err)
return nil
}
array, ok := exported.([]byte)
if !ok { if !ok {
log.Error("Error while casting exported value to array of byte: value = %s", exported) log.Error("Error while casting exported value to array of byte: value = %+v", ret)
return nil
} }
return array return array
} }
} }
return nil return nil
} }

36
vendor/github.com/evilsocket/islazy/plugin/compile.go generated vendored Normal file
View file

@ -0,0 +1,36 @@
package plugin
import (
// "unicode"
"github.com/robertkrimen/otto"
)
func (p *Plugin) compile() (err error) {
// create a new vm
p.vm = otto.New()
// track objects already defined by Otto
predefined := map[string]bool{}
for name := range p.vm.Context().Symbols {
predefined[name] = true
}
// run the code once in order to define all the functions
// and validate the syntax, then get the callbacks
if _, err = p.vm.Run(p.Code); err != nil {
return
}
// every uppercase object is considered exported
for name, sym := range p.vm.Context().Symbols {
// ignore predefined objects
if _, found := predefined[name]; !found {
// ignore lowercase global objects
// if unicode.IsUpper(rune(name[0])) {
if sym.IsFunction() {
p.callbacks[name] = sym
} else {
p.objects[name] = sym
}
// }
}
}
return nil
}

4
vendor/github.com/evilsocket/islazy/plugin/doc.go generated vendored Normal file
View file

@ -0,0 +1,4 @@
// Package plugin contains objects and functions to load and
// use javascript plugins in order to extend the functionalities
// of your projects.
package plugin

233
vendor/github.com/evilsocket/islazy/plugin/plugin.go generated vendored Normal file
View file

@ -0,0 +1,233 @@
package plugin
import (
"fmt"
"io/ioutil"
"path/filepath"
"strings"
"sync"
"github.com/robertkrimen/otto"
)
// Defines is a map containing the predefined objects
// and functions for each vm of each plugin.
var Defines = map[string]interface{}{}
// Plugin is an object representing a javascript
// file exporting functions and variables that
// your project can use to extend its functionalities.
type Plugin struct {
sync.Mutex
// The basename of the plugin.
Name string
// The actual javascript code.
Code string
// The full path of the plugin.
Path string
vm *otto.Otto
callbacks map[string]otto.Value
objects map[string]otto.Value
}
// Parse parsesand compiles a plugin given its source code.
func Parse(code string) (*Plugin, error) {
plugin := &Plugin{
Code: code,
callbacks: make(map[string]otto.Value),
objects: make(map[string]otto.Value),
}
if err := plugin.compile(); err != nil {
return nil, err
}
for name, val := range Defines {
if err := plugin.vm.Set(name, val); err != nil {
return nil, err
}
}
return plugin, nil
}
// Load loads and compiles a plugin given its path.
func Load(path string) (plug *Plugin, err error) {
if raw, err := ioutil.ReadFile(path); err != nil {
return nil, err
} else if plug, err = Parse(string(raw)); err != nil {
return nil, err
} else {
plug.Path = path
plug.Name = strings.Replace(filepath.Base(path), ".js", "", -1)
}
return plug, nil
}
// Clone returns a new instance identical to the plugin.
func (p *Plugin) Clone() (clone *Plugin) {
var err error
if p.Path == "" {
clone, err = Parse(p.Code)
} else {
clone, err = Load(p.Path)
}
if err != nil {
panic(err) // this should never happen
}
return clone
}
// HasFunc returns true if the function with `name`
// has been declared in the plugin code.
func (p *Plugin) HasFunc(name string) bool {
_, found := p.callbacks[name]
return found
}
// Set sets a variable into the VM of this plugin instance.
func (p *Plugin) Set(name string, v interface{}) error {
p.Lock()
defer p.Unlock()
return p.vm.Set(name, v)
}
// Call executes one of the declared callbacks of the plugin by its name.
func (p *Plugin) Call(name string, args ...interface{}) (interface{}, error) {
p.Lock()
defer p.Unlock()
if cb, found := p.callbacks[name]; !found {
return nil, fmt.Errorf("%s does not name a function", name)
} else if ret, err := cb.Call(otto.NullValue(), args...); err != nil {
return nil, err
} else if !ret.IsUndefined() {
exported, err := ret.Export()
if err != nil {
return nil, err
}
return exported, nil
}
return nil, nil
}
// Methods returns a list of methods exported from the javascript
func (p *Plugin) Methods() []string {
methods := []string{}
for key, _ := range p.callbacks {
methods = append(methods, key)
}
return methods
}
// Objects returns a list of object exported by the javascript
func (p *Plugin) Objects() []string {
objs := []string{}
for key, _ := range p.callbacks {
objs = append(objs, key)
}
return objs
}
// GetTypeObject returns the type of the object by its name
func (p *Plugin) GetTypeObject(name string) string {
if obj, found := p.objects[name]; !found {
return ""
} else if obj.IsPrimitive() {
if obj.IsBoolean() {
return "BooleanPrimitive"
} else if obj.IsNumber() {
return "NumberPrimitive"
} else if obj.IsString() {
return "StringPrimitive"
}
} else if obj.IsObject() {
switch obj.Class() {
case "Array":
return "ArrayObject"
case "String":
return "StringObject"
case "Boolean":
return "BooleanObject"
case "Number":
return "NumberObject"
case "Date":
return "DateObject"
case "RegExp":
return "RegExpObject"
case "Error":
return "ErrorObject"
}
}
return ""
}
// IsStringPrimitive returns true if the object with a
// given name is a javascript primitive string
func (p *Plugin) IsStringPrimitive(name string) bool {
return p.GetTypeObject(name) == "StringPrimitive"
}
// IsBooleanPrimitive returns true if the object with a
// given name is a javascript primitive boolean, false otherwise
func (p *Plugin) IsBooleanPrimitive(name string) bool {
return p.GetTypeObject(name) == "BooleanPrimitive"
}
// IsNumberPrimitive returns true if the object with a
// given name is a javascript primitive number, false otherwise
func (p *Plugin) IsNumberPrimitive(name string) bool {
return p.GetTypeObject(name) == "NumberPrimitive"
}
// IsArrayObject returns true if the object with a
// given name is a javascript array object, false otherwise
func (p *Plugin) IsArrayObject(name string) bool {
return p.GetTypeObject(name) == "ArrayObject"
}
// IsStringObject returns true if the object with a
// given name is a javascript string object, false otherwise
func (p *Plugin) IsStringObject(name string) bool {
return p.GetTypeObject(name) == "StringObject"
}
// IsBooleanObject returns true if the object with a
// given name is a javascript boolean object, false otherwise
func (p *Plugin) IsBooleanObject(name string) bool {
return p.GetTypeObject(name) == "BooleanObject"
}
// IsNumberObject returns true if the object with a
// given name is a javascript Number object, false otherwise
func (p *Plugin) IsNumberObject(name string) bool {
return p.GetTypeObject(name) == "NumberObject"
}
// IsDateObject returns true if the object with a
// given name is a javascript Date object, false otherwise
func (p *Plugin) IsDateObject(name string) bool {
return p.GetTypeObject(name) == "DateObject"
}
// IsRegExpObject returns true if the object with a
// given name is a javascript RegExp object, false otherwise
func (p *Plugin) IsRegExpObject(name string) bool {
return p.GetTypeObject(name) == "RegExpObject"
}
// IsErrorObject returns true if the object with a
// given name is a javascript error object, false otherwise
func (p *Plugin) IsErrorObject(name string) bool {
return p.GetTypeObject(name) == "ErrorObject"
}
// GetObject returns an interface containing the value of the object by its name
func (p *Plugin) GetObject(name string) (interface{}, error) {
if obj, found := p.objects[name]; !found {
return nil, fmt.Errorf("%s does not name an object", name)
} else {
return obj.Export()
}
}