package compiler
import (
"context"
"fmt"
"github.com/Mzack9999/goja"
"github.com/kitabisa/go-ci"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators"
"github.com/projectdiscovery/nuclei/v3/pkg/types"
contextutil "github.com/projectdiscovery/utils/context"
"github.com/projectdiscovery/utils/errkit"
stringsutil "github.com/projectdiscovery/utils/strings"
)
var (
ErrJSExecDeadline = errkit.New("js engine execution deadline exceeded").SetKind(errkit.ErrKindDeadline).Build()
)
type Compiler struct{}
func New() *Compiler {
return &Compiler{}
}
type ExecuteOptions struct {
ExecutionId string
Callback func(runtime *goja.Runtime) error
Cleanup func(runtime *goja.Runtime)
Source *string
Context context.Context
TimeoutVariants *types.Timeouts
exports map[string]interface{}
}
type ExecuteArgs struct {
Args map[string]interface{}
TemplateCtx map[string]interface{}
}
func (e *ExecuteArgs) Map() map[string]interface{} {
return generators.MergeMaps(e.TemplateCtx, e.Args)
}
func NewExecuteArgs() *ExecuteArgs {
return &ExecuteArgs{
Args: make(map[string]interface{}),
TemplateCtx: make(map[string]interface{}),
}
}
type ExecuteResult map[string]interface{}
func (e ExecuteResult) Map() map[string]interface{} {
if e == nil {
return make(map[string]interface{})
}
return e
}
func NewExecuteResult() ExecuteResult {
return make(map[string]interface{})
}
func (e ExecuteResult) GetSuccess() bool {
if e == nil {
return false
}
val, ok := e["success"].(bool)
if !ok {
return false
}
return val
}
func (c *Compiler) ExecuteWithOptions(program *goja.Program, args *ExecuteArgs, opts *ExecuteOptions) (ExecuteResult, error) {
if opts == nil {
opts = &ExecuteOptions{Context: context.Background()}
}
if args == nil {
args = NewExecuteArgs()
}
if args.TemplateCtx == nil {
args.TemplateCtx = make(map[string]interface{})
}
if args.Args == nil {
args.Args = make(map[string]interface{})
}
args.TemplateCtx = generators.MergeMaps(args.TemplateCtx, args.Args)
ctx, cancel := context.WithTimeoutCause(opts.Context, opts.TimeoutVariants.JsCompilerExecutionTimeout, ErrJSExecDeadline)
defer cancel()
results, err := contextutil.ExecFuncWithTwoReturns(ctx, func() (val goja.Value, err error) {
defer func() {
if ci.IsCI() {
return
}
if r := recover(); r != nil {
err = fmt.Errorf("panic: %v", r)
}
}()
return ExecuteProgram(program, args, opts)
})
if err != nil {
if val, ok := err.(*goja.Exception); ok {
if x := val.Unwrap(); x != nil {
err = x
}
}
e := NewExecuteResult()
e["error"] = err.Error()
return e, err
}
var res ExecuteResult
if opts.exports != nil {
res = ExecuteResult(opts.exports)
opts.exports = nil
} else {
res = NewExecuteResult()
}
res["response"] = results.Export()
res["success"] = results.ToBoolean()
return res, nil
}
func CanRunAsIIFE(script string) bool {
return stringsutil.ContainsAny(script, exportAsToken, exportToken)
}
func SourceIIFEMode(script string, strict bool) (*goja.Program, error) {
val := fmt.Sprintf(`
(function() {
%s
})()
`, script)
return goja.Compile("", val, strict)
}
func SourceAutoMode(script string, strict bool) (*goja.Program, error) {
if !CanRunAsIIFE(script) {
return goja.Compile("", script, strict)
}
val := fmt.Sprintf(`
(function() {
%s
})()
`, script)
return goja.Compile("", val, strict)
}