github.com/iDigitalFlame/xmt@v0.5.4/cmd/script/sotto/otto.go_ (about) 1 // Copyright (C) 2020 - 2023 iDigitalFlame 2 // 3 // This program is free software: you can redistribute it and/or modify 4 // it under the terms of the GNU General Public License as published by 5 // the Free Software Foundation, either version 3 of the License, or 6 // any later version. 7 // 8 // This program is distributed in the hope that it will be useful, 9 // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 // GNU General Public License for more details. 12 // 13 // You should have received a copy of the GNU General Public License 14 // along with this program. If not, see <https://www.gnu.org/licenses/>. 15 // 16 17 // Package sotto is a mapping for the Otto (github.com/robertkrimen/otto) 18 // JavaScript engine. 19 package sotto 20 21 import ( 22 "context" 23 "sync" 24 "time" 25 26 "github.com/iDigitalFlame/xmt/c2/task" 27 "github.com/iDigitalFlame/xmt/cmd" 28 "github.com/iDigitalFlame/xmt/util" 29 "github.com/robertkrimen/otto" 30 ) 31 32 // Otto is a mapping for the Otto (github.com/robertkrimen/otto) JavaScript engine. 33 // This can be used to run JavaScript directly and can be registered by the 34 // 'task.RegisterScript' function to include the engine in the XMT task runtime. 35 const Otto ottoEngine = 0xE0 36 37 var ( 38 ottoPool = sync.Pool{ 39 New: func() any { 40 return newOtto() 41 }, 42 } 43 ottoError = new(otto.Error) 44 ottoEmpty otto.Value 45 ) 46 47 type ottoEngine uint8 48 type ottoScript struct { 49 *otto.Otto 50 c util.Builder 51 } 52 53 // Register is a simple shortcut for 'task.RegisterEngine(uint8(Otto), Otto)'. 54 func Register() error { 55 return task.RegisterEngine(uint8(Otto), Otto) 56 } 57 func newOtto() *ottoScript { 58 i := &ottoScript{Otto: otto.New()} 59 i.Interrupt = make(chan func(), 1) 60 if c, err := i.Get("console"); err == nil { 61 c.Object().Set("log", i.log) 62 } 63 i.Set("print", i.log) 64 i.Set("exec", exec) 65 i.Set("sleep", sleep) 66 return i 67 } 68 69 // Invoke will use the Otto (github.com/robertkrimen/otto) JavaScript engine to 70 // run active JavaScript. This can be used to run code not built in at compile time. 71 // 72 // The only argument is the script that is to be run. The results are the output 73 // of the console (all console.log together) and any errors that may occur or syntax 74 // errors. 75 // 76 // This will capture the output of all the console writes and adds a 'print' statement 77 // as a shortcut to be used. 78 // 79 // Another additional function 'exec' can be used to run commands natively. This 80 // function can take a vardict of strings to be the command line arguments. 81 func Invoke(s string) (string, error) { 82 return InvokeEx(context.Background(), nil, s) 83 } 84 func exec(v otto.FunctionCall) otto.Value { 85 var p cmd.Process 86 if len(v.ArgumentList) == 1 { 87 s, err := v.Argument(0).ToString() 88 if err != nil { 89 i, _ := v.Otto.ToValue(err.Error()) 90 return i 91 } 92 p.Args = cmd.Split(s) 93 } else { 94 for i := range v.ArgumentList { 95 s, err := v.Argument(i).ToString() 96 if err != nil { 97 i, _ := v.Otto.ToValue(err.Error()) 98 return i 99 } 100 p.Args = append(p.Args, s) 101 } 102 } 103 b, err := p.CombinedOutput() 104 if err != nil { 105 i, _ := v.Otto.ToValue(err.Error()) 106 return i 107 } 108 if len(b) > 0 && b[len(b)-1] == 10 { 109 b = b[:len(b)-1] 110 } 111 i, _ := v.Otto.ToValue(string(b)) 112 return i 113 } 114 func sleep(v otto.FunctionCall) otto.Value { 115 if len(v.ArgumentList) == 0 { 116 return ottoEmpty 117 } 118 n, err := v.Argument(0).ToFloat() 119 if err != nil { 120 return ottoEmpty 121 } 122 time.Sleep(time.Duration(n * float64(time.Second))) 123 return ottoEmpty 124 } 125 func (o *ottoScript) run(c chan<- error, s string) { 126 _, err := o.Run(s) 127 if err != nil && len(err.Error()) == 0 { 128 return 129 } 130 c <- err 131 } 132 func (o *ottoScript) log(v otto.FunctionCall) otto.Value { 133 for i := range v.ArgumentList { 134 if i > 0 { 135 o.c.WriteByte(' ') 136 } 137 o.c.WriteString(v.Argument(i).String()) 138 } 139 o.c.WriteByte('\n') 140 return ottoEmpty 141 } 142 143 // InvokeContext will use the Otto (github.com/robertkrimen/otto) JavaScript engine 144 // to run active JavaScript. This can be used to run code not built in at compile 145 // time. 146 // 147 // A context is required to timeout the script execution and script that is to be 148 // run. The results are the output of the console (all console.log together) and 149 // any errors that may occur or syntax errors. 150 // 151 // This will capture the output of all the console writes and adds a 'print' statement 152 // as a shortcut to be used. 153 // 154 // Another additional function 'exec' can be used to run commands natively. This 155 // function can take a vardict of strings to be the command line arguments. 156 func InvokeContext(x context.Context, s string) (string, error) { 157 return InvokeEx(x, nil, s) 158 } 159 160 // InvokeEx will use the Otto (github.com/robertkrimen/otto) JavaScript engine 161 // to run active JavaScript. This can be used to run code not built in at compile 162 // time. 163 // 164 // A context is required to timeout the script execution and script that is to be 165 // run. The results are the output of the console (all console.log together) and 166 // any errors that may occur or syntax errors. 167 // 168 // This will capture the output of all the console writes and adds a 'print' statement 169 // as a shortcut to be used. 170 // 171 // Another additional function 'exec' can be used to run commands natively. This 172 // function can take a vardict of strings to be the command line arguments. 173 // 174 // This Ex function allows to specify a map that contains any starting variables 175 // to be supplied at runtime. 176 func InvokeEx(x context.Context, m map[string]any, s string) (string, error) { 177 var ( 178 c = make(chan error, 1) 179 h = ottoPool.Get().(*ottoScript) 180 err error 181 ) 182 for k, v := range m { 183 h.Set(k, v) 184 } 185 go h.run(c, s) 186 select { 187 case <-x.Done(): 188 h.Interrupt <- func() { 189 panic(ottoError) 190 } 191 case err = <-c: 192 } 193 close(c) 194 o := h.c.String() 195 h.c.Reset() 196 for k := range m { 197 h.Set(k, nil) 198 } 199 if ottoPool.Put(h); len(o) > 1 && o[len(o)-1] == '\n' { 200 return o[:len(o)-1], err 201 } 202 return o, err 203 } 204 func (ottoEngine) Invoke(x context.Context, m map[string]any, s string) (string, error) { 205 return InvokeEx(x, m, s) 206 }