mirror of
https://github.com/projectdiscovery/nuclei.git
synced 2025-12-17 23:47:26 +00:00
190 lines
5.1 KiB
Go
190 lines
5.1 KiB
Go
// Package compiler provides a compiler for the goja runtime.
|
|
package compiler
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/dop251/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 is the error returned when alloted time for script execution exceeds
|
|
ErrJSExecDeadline = errkit.New("js engine execution deadline exceeded").SetKind(errkit.ErrKindDeadline).Build()
|
|
)
|
|
|
|
// 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)
|
|
|
|
// Source is original source of the script
|
|
Source *string
|
|
|
|
Context context.Context
|
|
|
|
TimeoutVariants *types.Timeouts
|
|
|
|
// Manually exported objects
|
|
exports map[string]interface{}
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// Map returns a merged map of the TemplateCtx and Args fields.
|
|
func (e *ExecuteArgs) Map() map[string]interface{} {
|
|
return generators.MergeMaps(e.TemplateCtx, e.Args)
|
|
}
|
|
|
|
// 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{}
|
|
|
|
// Map returns the map representation of the ExecuteResult
|
|
func (e ExecuteResult) Map() map[string]interface{} {
|
|
if e == nil {
|
|
return make(map[string]interface{})
|
|
}
|
|
return e
|
|
}
|
|
|
|
// NewExecuteResult returns a new execute result instance
|
|
func NewExecuteResult() ExecuteResult {
|
|
return make(map[string]interface{})
|
|
}
|
|
|
|
// GetSuccess returns whether the script was successful or not.
|
|
func (e ExecuteResult) GetSuccess() bool {
|
|
if e == nil {
|
|
return false
|
|
}
|
|
val, ok := e["success"].(bool)
|
|
if !ok {
|
|
return false
|
|
}
|
|
return val
|
|
}
|
|
|
|
// 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{Context: context.Background()}
|
|
}
|
|
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)
|
|
|
|
// execute with context and timeout
|
|
|
|
ctx, cancel := context.WithTimeoutCause(opts.Context, opts.TimeoutVariants.JsCompilerExecutionTimeout, ErrJSExecDeadline)
|
|
defer cancel()
|
|
// execute the script
|
|
results, err := contextutil.ExecFuncWithTwoReturns(ctx, func() (val goja.Value, err error) {
|
|
// TODO(dwisiswant0): remove this once we get the RCA.
|
|
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
|
|
}
|
|
|
|
// if the script uses export/ExportAS tokens then we can run it in IIFE mode
|
|
// but if not we can't run it
|
|
func CanRunAsIIFE(script string) bool {
|
|
return stringsutil.ContainsAny(script, exportAsToken, exportToken)
|
|
}
|
|
|
|
// SourceIIFEMode is a mode where the script is wrapped in a function and compiled.
|
|
// This is used when the script is not exported or exported as a function.
|
|
func SourceIIFEMode(script string, strict bool) (*goja.Program, error) {
|
|
val := fmt.Sprintf(`
|
|
(function() {
|
|
%s
|
|
})()
|
|
`, script)
|
|
return goja.Compile("", val, strict)
|
|
}
|
|
|
|
// SourceAutoMode is a mode where the script is wrapped in a function and compiled.
|
|
// This is used when the script is exported or exported as a function.
|
|
func SourceAutoMode(script string, strict bool) (*goja.Program, error) {
|
|
if !CanRunAsIIFE(script) {
|
|
// this will not be run in a pooled runtime
|
|
return goja.Compile("", script, strict)
|
|
}
|
|
val := fmt.Sprintf(`
|
|
(function() {
|
|
%s
|
|
})()
|
|
`, script)
|
|
return goja.Compile("", val, strict)
|
|
}
|