mirror of
https://github.com/rizinorg/rizin-notebook.git
synced 2026-11-07 12:18:07 -10:00
178 lines
4.1 KiB
Go
178 lines
4.1 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/dop251/goja"
|
|
"golang.org/x/sync/semaphore"
|
|
)
|
|
|
|
// scriptTimeout is the maximum execution time for a single script.
|
|
const scriptTimeout = 5 * time.Minute
|
|
|
|
type JavaScript struct {
|
|
semaphore *semaphore.Weighted
|
|
runtime *goja.Runtime
|
|
rizin *Rizin
|
|
output strings.Builder
|
|
}
|
|
|
|
func rizinCmd(command string, rz *Rizin) (string, error) {
|
|
if _, err := rz.exec("e scr.color=0"); err != nil {
|
|
return "", err
|
|
}
|
|
result, err := rz.exec(command)
|
|
// Restore color regardless of command success.
|
|
rz.exec("e scr.color=3")
|
|
return result, err
|
|
}
|
|
|
|
func convertValue(ivalue interface{}) string {
|
|
switch value := ivalue.(type) {
|
|
case []interface{}:
|
|
bytes, _ := json.MarshalIndent(value, "", "\t")
|
|
return string(bytes)
|
|
case map[string]interface{}:
|
|
bytes, _ := json.MarshalIndent(value, "", "\t")
|
|
return string(bytes)
|
|
default:
|
|
return fmt.Sprintf("%v", value)
|
|
}
|
|
}
|
|
|
|
func NewJavaScript() *JavaScript {
|
|
runtime := goja.New()
|
|
if runtime == nil {
|
|
fmt.Println("error: cannot create JavaScript runtime")
|
|
return nil
|
|
}
|
|
|
|
sem := semaphore.NewWeighted(1)
|
|
js := &JavaScript{semaphore: sem, runtime: runtime, rizin: nil}
|
|
|
|
// Register the rizin API object.
|
|
rizinAPI := map[string]interface{}{}
|
|
rizinAPI["cmd"] = func(args ...interface{}) goja.Value {
|
|
if js.rizin == nil {
|
|
panic(js.runtime.ToValue("Rizin pipe is closed."))
|
|
}
|
|
if len(args) < 1 {
|
|
panic(js.runtime.ToValue("No string was passed."))
|
|
}
|
|
cmdStr, ok := args[0].(string)
|
|
if !ok {
|
|
panic(js.runtime.ToValue("input is not a string."))
|
|
}
|
|
result, err := rizinCmd(cmdStr, js.rizin)
|
|
if err != nil {
|
|
panic(js.runtime.ToValue(err.Error()))
|
|
}
|
|
return js.runtime.ToValue(result)
|
|
}
|
|
|
|
rizinAPI["cmdj"] = func(args ...interface{}) goja.Value {
|
|
if js.rizin == nil {
|
|
panic(js.runtime.ToValue("Rizin pipe is closed."))
|
|
}
|
|
if len(args) < 1 {
|
|
panic(js.runtime.ToValue("No string was passed."))
|
|
}
|
|
cmdStr, ok := args[0].(string)
|
|
if !ok {
|
|
panic(js.runtime.ToValue("input is not a string."))
|
|
}
|
|
result, err := rizinCmd(cmdStr, js.rizin)
|
|
if err != nil {
|
|
panic(js.runtime.ToValue(err.Error()))
|
|
}
|
|
var data interface{}
|
|
if jsonErr := json.Unmarshal([]byte(result), &data); jsonErr != nil {
|
|
panic(js.runtime.ToValue(jsonErr.Error()))
|
|
}
|
|
return js.runtime.ToValue(data)
|
|
}
|
|
|
|
// Register the console API object.
|
|
consoleAPI := map[string]interface{}{}
|
|
|
|
// writeArgs writes all arguments to js.output separated by spaces.
|
|
writeArgs := func(prefix string, args []interface{}) {
|
|
if len(prefix) > 0 {
|
|
js.output.WriteString(prefix)
|
|
js.output.WriteString(" ")
|
|
}
|
|
for i, value := range args {
|
|
js.output.WriteString(convertValue(value))
|
|
if i+1 < len(args) {
|
|
js.output.WriteString(" ")
|
|
}
|
|
}
|
|
js.output.WriteString("\n")
|
|
}
|
|
|
|
consoleAPI["log"] = func(args ...interface{}) {
|
|
writeArgs("", args)
|
|
}
|
|
|
|
consoleAPI["warn"] = func(args ...interface{}) {
|
|
writeArgs("[WARN]", args)
|
|
}
|
|
|
|
consoleAPI["error"] = func(args ...interface{}) {
|
|
writeArgs("[ERROR]", args)
|
|
}
|
|
|
|
consoleAPI["info"] = func(args ...interface{}) {
|
|
writeArgs("[INFO]", args)
|
|
}
|
|
|
|
consoleAPI["clear"] = func() {
|
|
js.output.Reset()
|
|
}
|
|
|
|
consoleAPI["table"] = func(args ...interface{}) {
|
|
if len(args) < 1 {
|
|
return
|
|
}
|
|
bytes, err := json.MarshalIndent(args[0], "", " ")
|
|
if err != nil {
|
|
js.output.WriteString(fmt.Sprintf("[TABLE] %v\n", args[0]))
|
|
return
|
|
}
|
|
js.output.WriteString(string(bytes))
|
|
js.output.WriteString("\n")
|
|
}
|
|
|
|
runtime.Set("rizin", rizinAPI)
|
|
runtime.Set("console", consoleAPI)
|
|
return js
|
|
}
|
|
|
|
func (js *JavaScript) exec(script string, rz *Rizin) (string, error) {
|
|
if !js.semaphore.TryAcquire(1) {
|
|
return "", errors.New("a script is already running")
|
|
}
|
|
defer js.semaphore.Release(1)
|
|
|
|
js.rizin = rz
|
|
js.output.Reset()
|
|
|
|
timer := time.AfterFunc(scriptTimeout, func() {
|
|
js.runtime.Interrupt("The script execution has timed out.")
|
|
})
|
|
defer timer.Stop()
|
|
|
|
_, err := js.runtime.RunScript("script.js", script)
|
|
result := js.output.String()
|
|
|
|
// Clean up state for next execution.
|
|
js.rizin = nil
|
|
js.output.Reset()
|
|
|
|
return result, err
|
|
}
|