package session import ( "encoding/json" "fmt" "io/ioutil" "os" "sync" "github.com/bettercap/bettercap/v2/js" "github.com/evilsocket/islazy/fs" "github.com/evilsocket/islazy/log" "github.com/robertkrimen/otto" ) // see https://github.com/robertkrimen/otto/issues/213 var jsRuntime = otto.New() var jsListeners = sync.Map{} func jsRunFunc(call otto.FunctionCall) otto.Value { argv := call.ArgumentList argc := len(argv) if argc != 1 { return js.ReportError("run accepts one string argument") } else if argv[0].IsString() == false { return js.ReportError("run accepts one string argument") } for _, cmd := range ParseCommands(argv[0].String()) { if err := I.Run(cmd); err != nil { return js.ReportError("error running '%s': %v", cmd, err) } } return js.NullValue } func jsOnEventFunc(call otto.FunctionCall) otto.Value { argv := call.ArgumentList argc := len(argv) cb := otto.NullValue() filterExpr := "" // just one argument, a function to receive all events if argc == 1 { if argv[0].IsFunction() == false { return js.ReportError("the single argument must be a function") } cb = argv[0] } else { if argc != 2 { return js.ReportError("expected two arguments (event_name, callback), got %d", argc) } else if argv[0].IsString() == false { return js.ReportError("first argument must be a string") } else if argv[1].IsFunction() == false { return js.ReportError("second argument must be a function") } filterExpr = argv[0].String() cb = argv[1] } listenerKey := fmt.Sprintf("%s:%s", filterExpr, cb.String()) if _, found := jsListeners.Load(listenerKey); found { return js.ReportError("listener already exists") } // add to listeners closeChan := make(chan bool) jsListeners.Store(listenerKey, closeChan) // start a go routine for this event listener go func(expr string, cb otto.Value, closeChan chan bool) { listener := I.Events.Listen() defer I.Events.Unlisten(listener) defer close(closeChan) for { select { case event := <-listener: if expr == "" || event.Tag == expr { // some objects don't do well with js, so convert them to a generic map // before passing them to the callback var opaque interface{} if raw, err := json.Marshal(event); err != nil { I.Events.Log(log.ERROR, "error serializing event %s: %v", event.Tag, err) } else if err = json.Unmarshal(raw, &opaque); err != nil { I.Events.Log(log.ERROR, "error serializing event %s: %v", event.Tag, err) } // lock vm if ready and available locked := false if I.script != nil { I.script.Lock() locked = true } if _, err := cb.Call(otto.NullValue(), opaque); err != nil { I.Events.Log(log.ERROR, "error dispatching event %s: %v", event.Tag, err) } // unlock vm if ready and available if locked { I.script.Unlock() } } case <-closeChan: return } } }(filterExpr, cb, closeChan) return js.NullValue } func jsRemoveEventListenerFunc(call otto.FunctionCall) otto.Value { argv := call.ArgumentList argc := len(argv) cb := otto.NullValue() filterExpr := "" // just one argument, a function to receive all events if argc == 1 { if argv[0].IsFunction() == false { return js.ReportError("the single argument must be a function") } cb = argv[0] } else { if argc != 2 { return js.ReportError("expected two arguments (event_name, callback), got %d", argc) } else if argv[0].IsString() == false { return js.ReportError("first argument must be a string") } else if argv[1].IsFunction() == false { return js.ReportError("second argument must be a function") } filterExpr = argv[0].String() cb = argv[1] } listenerKey := fmt.Sprintf("%s:%s", filterExpr, cb.String()) if closer, found := jsListeners.Load(listenerKey); found { closer.(chan bool) <- true jsListeners.Delete(listenerKey) } else { return js.ReportError("listener not found") } return js.NullValue } func jsSaveToFileFunc(call otto.FunctionCall) otto.Value { argv := call.ArgumentList argc := len(argv) if argc != 2 { return js.ReportError("saveToFile accepts two string arguments") } else if argv[0].IsString() == false { return js.ReportError("saveToFile accepts two string arguments") } else if argv[1].IsString() == false { return js.ReportError("saveToFile accepts two string arguments") } fileName := argv[0].String() data := argv[1].String() if err := ioutil.WriteFile(fileName, []byte(data), os.ModePerm); err != nil { return js.ReportError("error writing to '%s': %v", fileName, err) } return js.NullValue } func jsSaveJSONFunc(call otto.FunctionCall) otto.Value { argv := call.ArgumentList argc := len(argv) if argc != 2 { return js.ReportError("saveJSON accepts one object and one string arguments") } else if argv[0].IsObject() == false { return js.ReportError("saveJSON accepts one object and one string arguments") } else if argv[1].IsString() == false { return js.ReportError("saveJSON accepts one object and one string arguments") } obj := argv[0] if fileName, err := fs.Expand(argv[1].String()); err != nil { return js.ReportError("can't expand '%s': %v", fileName, err) } else if exp, err := obj.Export(); err != nil { return js.ReportError("error exporting object: %v", err) } else if raw, err := json.Marshal(exp); err != nil { return js.ReportError("error serializing object: %v", err) } else if err = ioutil.WriteFile(fileName, raw, os.ModePerm); err != nil { return js.ReportError("error writing to '%s': %v", fileName, err) } return js.NullValue } func jsLoadJSONFunc(call otto.FunctionCall) otto.Value { argv := call.ArgumentList argc := len(argv) if argc != 1 { return js.ReportError("loadJSON accepts one string argument") } else if argv[0].IsString() == false { return js.ReportError("loadJSON accepts one string argument") } var obj interface{} if fileName, err := fs.Expand(argv[0].String()); err != nil { return js.ReportError("can't expand '%s': %v", fileName, err) } else if rawData, err := ioutil.ReadFile(fileName); err != nil { return js.ReportError("can't read '%s': %v", fileName, err) } else if err = json.Unmarshal(rawData, &obj); err != nil { return js.ReportError("can't parse '%s': %v", fileName, err) } else if v, err := jsRuntime.ToValue(obj); err != nil { return js.ReportError("could not convert '%s' to javascript object: %s", fileName, err) } else { return v } } func jsFileExistsFunc(call otto.FunctionCall) otto.Value { argv := call.ArgumentList argc := len(argv) if argc != 1 { return js.ReportError("fileExists accepts one string argument") } else if argv[0].IsString() == false { return js.ReportError("fileExists accepts one string argument") } else if fileName, err := fs.Expand(argv[0].String()); err != nil { return js.ReportError("can't expand '%s': %v", fileName, err) } else if fs.Exists(fileName) { return otto.TrueValue() } return otto.FalseValue() }