mirror of
https://github.com/projectdiscovery/nuclei.git
synced 2025-12-17 17:56:56 +00:00
feat: loading templates performance improvements
This commit is contained in:
parent
078284936c
commit
53b167064a
@ -39,6 +39,8 @@ const (
|
|||||||
DefaultDumpTrafficOutputFolder = "output"
|
DefaultDumpTrafficOutputFolder = "output"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var validateOptions = validator.New()
|
||||||
|
|
||||||
func ConfigureOptions() error {
|
func ConfigureOptions() error {
|
||||||
// with FileStringSliceOptions, FileNormalizedStringSliceOptions, FileCommaSeparatedStringSliceOptions
|
// with FileStringSliceOptions, FileNormalizedStringSliceOptions, FileCommaSeparatedStringSliceOptions
|
||||||
// if file has the extension `.yaml` or `.json` we consider those as strings and not files to be read
|
// if file has the extension `.yaml` or `.json` we consider those as strings and not files to be read
|
||||||
@ -138,8 +140,7 @@ func ParseOptions(options *types.Options) {
|
|||||||
|
|
||||||
// validateOptions validates the configuration options passed
|
// validateOptions validates the configuration options passed
|
||||||
func ValidateOptions(options *types.Options) error {
|
func ValidateOptions(options *types.Options) error {
|
||||||
validate := validator.New()
|
if err := validateOptions.Struct(options); err != nil {
|
||||||
if err := validate.Struct(options); err != nil {
|
|
||||||
if _, ok := err.(*validator.InvalidValidationError); ok {
|
if _, ok := err.(*validator.InvalidValidationError); ok {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,22 +8,29 @@ import (
|
|||||||
|
|
||||||
"github.com/antchfx/xpath"
|
"github.com/antchfx/xpath"
|
||||||
sliceutil "github.com/projectdiscovery/utils/slice"
|
sliceutil "github.com/projectdiscovery/utils/slice"
|
||||||
"gopkg.in/yaml.v3"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var commonExpectedFields = []string{"Type", "Condition", "Name", "MatchAll", "Negative", "Internal"}
|
var commonExpectedFields = []string{"Type", "Condition", "Name", "MatchAll", "Negative", "Internal"}
|
||||||
|
|
||||||
// Validate perform initial validation on the matcher structure
|
// Validate perform initial validation on the matcher structure
|
||||||
func (matcher *Matcher) Validate() error {
|
func (matcher *Matcher) Validate() error {
|
||||||
// uses yaml marshaling to convert the struct to map[string]interface to have same field names
|
// Build a map of YAML‐tag names that are actually set (non-zero) in the matcher.
|
||||||
matcherMap := make(map[string]interface{})
|
matcherMap := make(map[string]interface{})
|
||||||
marshaledMatcher, err := yaml.Marshal(matcher)
|
val := reflect.ValueOf(*matcher)
|
||||||
if err != nil {
|
typ := reflect.TypeOf(*matcher)
|
||||||
return err
|
for i := 0; i < typ.NumField(); i++ {
|
||||||
}
|
field := typ.Field(i)
|
||||||
if err := yaml.Unmarshal(marshaledMatcher, &matcherMap); err != nil {
|
// skip internal / unexported or opt-out fields
|
||||||
return err
|
yamlTag := strings.Split(field.Tag.Get("yaml"), ",")[0]
|
||||||
|
if yamlTag == "" || yamlTag == "-" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if val.Field(i).IsZero() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
matcherMap[yamlTag] = struct{}{}
|
||||||
}
|
}
|
||||||
|
var err error
|
||||||
|
|
||||||
var expectedFields []string
|
var expectedFields []string
|
||||||
switch matcher.matcherType {
|
switch matcher.matcherType {
|
||||||
|
|||||||
@ -131,13 +131,13 @@ func (p *Parser) ParseTemplate(templatePath string, catalog catalog.Catalog) (an
|
|||||||
_ = reader.Close()
|
_ = reader.Close()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
data, err := io.ReadAll(reader)
|
// For local YAML files, check if preprocessing is needed
|
||||||
if err != nil {
|
var data []byte
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// pre-process directives only for local files
|
|
||||||
if fileutil.FileExists(templatePath) && config.GetTemplateFormatFromExt(templatePath) == config.YAML {
|
if fileutil.FileExists(templatePath) && config.GetTemplateFormatFromExt(templatePath) == config.YAML {
|
||||||
|
data, err = io.ReadAll(reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
data, err = yamlutil.PreProcess(data)
|
data, err = yamlutil.PreProcess(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -148,12 +148,28 @@ func (p *Parser) ParseTemplate(templatePath string, catalog catalog.Catalog) (an
|
|||||||
|
|
||||||
switch config.GetTemplateFormatFromExt(templatePath) {
|
switch config.GetTemplateFormatFromExt(templatePath) {
|
||||||
case config.JSON:
|
case config.JSON:
|
||||||
|
if data == nil {
|
||||||
|
data, err = io.ReadAll(reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
err = json.Unmarshal(data, template)
|
err = json.Unmarshal(data, template)
|
||||||
case config.YAML:
|
case config.YAML:
|
||||||
if p.NoStrictSyntax {
|
if data != nil {
|
||||||
err = yaml.Unmarshal(data, template)
|
// Already read and preprocessed
|
||||||
|
if p.NoStrictSyntax {
|
||||||
|
err = yaml.Unmarshal(data, template)
|
||||||
|
} else {
|
||||||
|
err = yaml.UnmarshalStrict(data, template)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
err = yaml.UnmarshalStrict(data, template)
|
// Stream directly from reader
|
||||||
|
decoder := yaml.NewDecoder(reader)
|
||||||
|
if !p.NoStrictSyntax {
|
||||||
|
decoder.SetStrict(true)
|
||||||
|
}
|
||||||
|
err = decoder.Decode(template)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
err = fmt.Errorf("failed to identify template format expected JSON or YAML but got %v", templatePath)
|
err = fmt.Errorf("failed to identify template format expected JSON or YAML but got %v", templatePath)
|
||||||
@ -162,7 +178,7 @@ func (p *Parser) ParseTemplate(templatePath string, catalog catalog.Catalog) (an
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
p.parsedTemplatesCache.Store(templatePath, template, data, nil)
|
p.parsedTemplatesCache.Store(templatePath, template, nil, nil) // don't keep raw bytes to save memory
|
||||||
return template, nil
|
return template, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -7,7 +7,6 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
validate "github.com/go-playground/validator/v10"
|
|
||||||
"github.com/projectdiscovery/nuclei/v3/pkg/model"
|
"github.com/projectdiscovery/nuclei/v3/pkg/model"
|
||||||
"github.com/projectdiscovery/nuclei/v3/pkg/protocols"
|
"github.com/projectdiscovery/nuclei/v3/pkg/protocols"
|
||||||
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/code"
|
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/code"
|
||||||
@ -310,10 +309,8 @@ func (template *Template) validateAllRequestIDs() {
|
|||||||
// MarshalYAML forces recursive struct validation during marshal operation
|
// MarshalYAML forces recursive struct validation during marshal operation
|
||||||
func (template *Template) MarshalYAML() ([]byte, error) {
|
func (template *Template) MarshalYAML() ([]byte, error) {
|
||||||
out, marshalErr := yaml.Marshal(template)
|
out, marshalErr := yaml.Marshal(template)
|
||||||
// Review: we are adding requestIDs for templateContext
|
// Use shared validator to avoid rebuilding struct cache for every template marshal
|
||||||
// if we are using this method then we might need to purge manually added IDS that start with `templatetype_`
|
errValidate := tplValidator.Struct(template)
|
||||||
// this is only applicable if there are more than 1 request fields in protocol
|
|
||||||
errValidate := validate.New().Struct(template)
|
|
||||||
return out, multierr.Append(marshalErr, errValidate)
|
return out, multierr.Append(marshalErr, errValidate)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -354,7 +351,7 @@ func (template *Template) UnmarshalYAML(unmarshal func(interface{}) error) error
|
|||||||
if len(alias.RequestsWithTCP) > 0 {
|
if len(alias.RequestsWithTCP) > 0 {
|
||||||
template.RequestsNetwork = alias.RequestsWithTCP
|
template.RequestsNetwork = alias.RequestsWithTCP
|
||||||
}
|
}
|
||||||
err = validate.New().Struct(template)
|
err = tplValidator.Struct(template)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -525,7 +522,7 @@ func (template *Template) hasMultipleRequests() bool {
|
|||||||
func (template *Template) MarshalJSON() ([]byte, error) {
|
func (template *Template) MarshalJSON() ([]byte, error) {
|
||||||
type TemplateAlias Template //avoid recursion
|
type TemplateAlias Template //avoid recursion
|
||||||
out, marshalErr := json.Marshal((*TemplateAlias)(template))
|
out, marshalErr := json.Marshal((*TemplateAlias)(template))
|
||||||
errValidate := validate.New().Struct(template)
|
errValidate := tplValidator.Struct(template)
|
||||||
return out, multierr.Append(marshalErr, errValidate)
|
return out, multierr.Append(marshalErr, errValidate)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -538,7 +535,7 @@ func (template *Template) UnmarshalJSON(data []byte) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
*template = Template(*alias)
|
*template = Template(*alias)
|
||||||
err = validate.New().Struct(template)
|
err = tplValidator.Struct(template)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
7
pkg/templates/validator_singleton.go
Normal file
7
pkg/templates/validator_singleton.go
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package templates
|
||||||
|
|
||||||
|
import (
|
||||||
|
validate "github.com/go-playground/validator/v10"
|
||||||
|
)
|
||||||
|
|
||||||
|
var tplValidator = validate.New()
|
||||||
Loading…
x
Reference in New Issue
Block a user