nuclei/pkg/js/compiler/compiler.go
Tarun Koyalwar 5bd9d9ee68
memory leak fixes and optimizations (#4680)
* feat http response memory optimization + reuse buffers

* update nuclei version

* feat: reuse js vm's and compile to programs

* fix failing http integration test

* remove dead code + add -jsc

* feat reuse js vms in pool with concurrency

* update comments as per review

* bug fix+ update interactsh test to look for dns interaction

* try enabling all interactsh integration tests

---------

Co-authored-by: mzack <marco.rivoli.nvh@gmail.com>
2024-01-31 01:59:49 +05:30

118 lines
3.3 KiB
Go

// Package compiler provides a compiler for the goja runtime.
package compiler
import (
"context"
"fmt"
"time"
"github.com/dop251/goja"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators"
contextutil "github.com/projectdiscovery/utils/context"
)
// Compiler provides a runtime to execute goja runtime
// based javascript scripts efficiently while also
// providing them access to custom modules defined in libs/.
type Compiler struct{}
// New creates a new compiler for the goja runtime.
func New() *Compiler {
return &Compiler{}
}
// ExecuteOptions provides options for executing a script.
type ExecuteOptions struct {
// Callback can be used to register new runtime helper functions
// ex: export etc
Callback func(runtime *goja.Runtime) error
// Cleanup is extra cleanup function to be called after execution
Cleanup func(runtime *goja.Runtime)
/// Timeout for this script execution
Timeout int
}
// ExecuteArgs is the arguments to pass to the script.
type ExecuteArgs struct {
Args map[string]interface{} //these are protocol variables
TemplateCtx map[string]interface{} // templateCtx contains template scoped variables
}
// NewExecuteArgs returns a new execute arguments.
func NewExecuteArgs() *ExecuteArgs {
return &ExecuteArgs{
Args: make(map[string]interface{}),
TemplateCtx: make(map[string]interface{}),
}
}
// ExecuteResult is the result of executing a script.
type ExecuteResult map[string]interface{}
func NewExecuteResult() ExecuteResult {
return make(map[string]interface{})
}
// GetSuccess returns whether the script was successful or not.
func (e ExecuteResult) GetSuccess() bool {
val, ok := e["success"].(bool)
if !ok {
return false
}
return val
}
// Execute executes a script with the default options.
func (c *Compiler) Execute(code string, args *ExecuteArgs) (ExecuteResult, error) {
p, err := goja.Compile("", code, false)
if err != nil {
return nil, err
}
return c.ExecuteWithOptions(p, args, &ExecuteOptions{})
}
// ExecuteWithOptions executes a script with the provided options.
func (c *Compiler) ExecuteWithOptions(program *goja.Program, args *ExecuteArgs, opts *ExecuteOptions) (ExecuteResult, error) {
if opts == nil {
opts = &ExecuteOptions{}
}
if args == nil {
args = NewExecuteArgs()
}
// handle nil maps
if args.TemplateCtx == nil {
args.TemplateCtx = make(map[string]interface{})
}
if args.Args == nil {
args.Args = make(map[string]interface{})
}
// merge all args into templatectx
args.TemplateCtx = generators.MergeMaps(args.TemplateCtx, args.Args)
if opts.Timeout <= 0 || opts.Timeout > 180 {
// some js scripts can take longer time so allow configuring timeout
// from template but keep it within sane limits (180s)
opts.Timeout = JsProtocolTimeout
}
// execute with context and timeout
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(opts.Timeout)*time.Second)
defer cancel()
// execute the script
results, err := contextutil.ExecFuncWithTwoReturns(ctx, func() (val goja.Value, err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("panic: %v", r)
}
}()
return executeProgram(program, args, opts)
})
if err != nil {
return nil, err
}
return ExecuteResult{"response": results.Export(), "success": results.ToBoolean()}, nil
}