package compiler
import (
"bytes"
"context"
"fmt"
"reflect"
"sync"
"github.com/Mzack9999/goja"
"github.com/Mzack9999/goja_nodejs/console"
"github.com/Mzack9999/goja_nodejs/require"
"github.com/kitabisa/go-ci"
"github.com/projectdiscovery/gologger"
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libbytes"
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libfs"
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libikev2"
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libkerberos"
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libldap"
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libmssql"
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libmysql"
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libnet"
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/liboracle"
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libpop3"
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libpostgres"
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/librdp"
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libredis"
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/librsync"
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libsmb"
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libsmtp"
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libssh"
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libstructs"
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libtelnet"
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libvnc"
"github.com/projectdiscovery/nuclei/v3/pkg/js/global"
"github.com/projectdiscovery/nuclei/v3/pkg/js/gojs"
"github.com/projectdiscovery/nuclei/v3/pkg/js/libs/goconsole"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
"github.com/projectdiscovery/nuclei/v3/pkg/utils/json"
stringsutil "github.com/projectdiscovery/utils/strings"
syncutil "github.com/projectdiscovery/utils/sync"
)
const (
exportToken = "Export"
exportAsToken = "ExportAs"
)
var (
r *require.Registry
lazyRegistryInit = sync.OnceFunc(func() {
r = new(require.Registry)
require.RegisterNativeModule(console.ModuleName, console.RequireWithPrinter(goconsole.NewGoConsolePrinter()))
})
pooljsc *syncutil.AdaptiveWaitGroup
lazySgInit = sync.OnceFunc(func() {
pooljsc, _ = syncutil.New(syncutil.WithSize(PoolingJsVmConcurrency))
})
sgResizeCheck = func(ctx context.Context) {
if pooljsc.Size != PoolingJsVmConcurrency {
if err := pooljsc.Resize(ctx, PoolingJsVmConcurrency); err != nil {
gologger.Warning().Msgf("Could not resize workpool: %s\n", err)
}
}
}
)
var gojapool = &sync.Pool{
New: func() interface{} {
return createNewRuntime()
},
}
func executeWithRuntime(runtime *goja.Runtime, p *goja.Program, args *ExecuteArgs, opts *ExecuteOptions) (result goja.Value, err error) {
defer func() {
_ = runtime.GlobalObject().Delete("template")
for k := range args.Args {
_ = runtime.GlobalObject().Delete(k)
}
if opts != nil && opts.Cleanup != nil {
opts.Cleanup(runtime)
}
runtime.RemoveContextValue("executionId")
}()
defer func() {
if ci.IsCI() {
return
}
if r := recover(); r != nil {
err = fmt.Errorf("panic: %s", r)
}
}()
_ = runtime.Set("template", args.TemplateCtx)
for k, v := range args.Args {
_ = runtime.Set(k, v)
}
if opts != nil && opts.Callback != nil {
if err := opts.Callback(runtime); err != nil {
return nil, err
}
}
runtime.SetContextValue("executionId", opts.ExecutionId)
return runtime.RunProgram(p)
}
func ExecuteProgram(p *goja.Program, args *ExecuteArgs, opts *ExecuteOptions) (result goja.Value, err error) {
if opts.Source == nil {
return executeWithoutPooling(p, args, opts)
}
if !stringsutil.ContainsAny(*opts.Source, exportAsToken, exportToken) {
return executeWithoutPooling(p, args, opts)
}
return executeWithPoolingProgram(p, args, opts)
}
func executeWithPoolingProgram(p *goja.Program, args *ExecuteArgs, opts *ExecuteOptions) (result goja.Value, err error) {
lazySgInit()
sgResizeCheck(opts.Context)
pooljsc.Add()
defer pooljsc.Done()
runtime := gojapool.Get().(*goja.Runtime)
defer gojapool.Put(runtime)
var buff bytes.Buffer
opts.exports = make(map[string]interface{})
defer func() {
_ = runtime.GlobalObject().Delete(exportAsToken)
_ = runtime.GlobalObject().Delete(exportToken)
}()
_ = gojs.RegisterFuncWithSignature(runtime, gojs.FuncOpts{
Name: "Export",
Signatures: []string{"Export(value any)"},
Description: "Converts a given value to a string and is appended to output of script",
FuncDecl: func(call goja.FunctionCall, runtime *goja.Runtime) goja.Value {
if len(call.Arguments) == 0 {
return goja.Null()
}
for _, arg := range call.Arguments {
if out := stringify(arg, runtime); out != "" {
buff.WriteString(out)
}
}
return goja.Null()
},
})
_ = gojs.RegisterFuncWithSignature(runtime, gojs.FuncOpts{
Name: "ExportAs",
Signatures: []string{"ExportAs(key string,value any)"},
Description: "Exports given value with specified key and makes it available in DSL and response",
FuncDecl: func(call goja.FunctionCall, runtime *goja.Runtime) goja.Value {
if len(call.Arguments) != 2 {
panic(runtime.ToValue("ExportAs expects 2 arguments"))
}
key := call.Argument(0).String()
value := call.Argument(1)
opts.exports[key] = stringify(value, runtime)
return goja.Null()
},
})
val, err := executeWithRuntime(runtime, p, args, opts)
if err != nil {
return nil, err
}
if val.Export() != nil {
buff.WriteString(stringify(val, runtime))
}
return runtime.ToValue(buff.String()), nil
}
func InternalGetGeneratorRuntime() *goja.Runtime {
runtime := gojapool.Get().(*goja.Runtime)
return runtime
}
func getRegistry() *require.Registry {
lazyRegistryInit()
return r
}
func createNewRuntime() *goja.Runtime {
runtime := protocolstate.NewJSRuntime()
_ = getRegistry().Enable(runtime)
_ = runtime.Set("console", require.Require(runtime, console.ModuleName))
if err := global.RegisterNativeScripts(runtime); err != nil {
gologger.Error().Msgf("Could not register scripts: %s\n", err)
}
return runtime
}
func stringify(gojaValue goja.Value, runtime *goja.Runtime) string {
value := gojaValue.Export()
if value == nil {
return ""
}
kind := reflect.TypeOf(value).Kind()
if kind == reflect.Struct || kind == reflect.Ptr && reflect.ValueOf(value).Elem().Kind() == reflect.Struct {
jsonStringify, ok := goja.AssertFunction(runtime.Get("to_json"))
if ok {
result, err := jsonStringify(goja.Undefined(), gojaValue)
if err == nil {
return result.String()
}
}
val := value
if kind == reflect.Ptr {
val = reflect.ValueOf(value).Elem().Interface()
}
bin, err := json.Marshal(val)
if err == nil {
return string(bin)
}
}
return fmt.Sprintf("%+v", value)
}