Skip to content

Commit 8ef3662

Browse files
authored
Merge pull request #6364 from projectdiscovery/loading-performance-improvements-v2
feat: loading templates performance improvements
2 parents bba2c3a + 1b6ae44 commit 8ef3662

File tree

5 files changed

+56
-28
lines changed

5 files changed

+56
-28
lines changed

internal/runner/options.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ const (
3939
DefaultDumpTrafficOutputFolder = "output"
4040
)
4141

42+
var validateOptions = validator.New()
43+
4244
func ConfigureOptions() error {
4345
// with FileStringSliceOptions, FileNormalizedStringSliceOptions, FileCommaSeparatedStringSliceOptions
4446
// 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) {
138140

139141
// validateOptions validates the configuration options passed
140142
func ValidateOptions(options *types.Options) error {
141-
validate := validator.New()
142-
if err := validate.Struct(options); err != nil {
143+
if err := validateOptions.Struct(options); err != nil {
143144
if _, ok := err.(*validator.InvalidValidationError); ok {
144145
return err
145146
}

pkg/operators/matchers/validate.go

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,29 @@ import (
88

99
"github.com/antchfx/xpath"
1010
sliceutil "github.com/projectdiscovery/utils/slice"
11-
"gopkg.in/yaml.v3"
1211
)
1312

1413
var commonExpectedFields = []string{"Type", "Condition", "Name", "MatchAll", "Negative", "Internal"}
1514

1615
// Validate perform initial validation on the matcher structure
1716
func (matcher *Matcher) Validate() error {
18-
// uses yaml marshaling to convert the struct to map[string]interface to have same field names
17+
// Build a map of YAML‐tag names that are actually set (non-zero) in the matcher.
1918
matcherMap := make(map[string]interface{})
20-
marshaledMatcher, err := yaml.Marshal(matcher)
21-
if err != nil {
22-
return err
23-
}
24-
if err := yaml.Unmarshal(marshaledMatcher, &matcherMap); err != nil {
25-
return err
19+
val := reflect.ValueOf(*matcher)
20+
typ := reflect.TypeOf(*matcher)
21+
for i := 0; i < typ.NumField(); i++ {
22+
field := typ.Field(i)
23+
// skip internal / unexported or opt-out fields
24+
yamlTag := strings.Split(field.Tag.Get("yaml"), ",")[0]
25+
if yamlTag == "" || yamlTag == "-" {
26+
continue
27+
}
28+
if val.Field(i).IsZero() {
29+
continue
30+
}
31+
matcherMap[yamlTag] = struct{}{}
2632
}
33+
var err error
2734

2835
var expectedFields []string
2936
switch matcher.matcherType {

pkg/templates/parser.go

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -131,13 +131,13 @@ func (p *Parser) ParseTemplate(templatePath string, catalog catalog.Catalog) (an
131131
_ = reader.Close()
132132
}()
133133

134-
data, err := io.ReadAll(reader)
135-
if err != nil {
136-
return nil, err
137-
}
138-
139-
// pre-process directives only for local files
134+
// For local YAML files, check if preprocessing is needed
135+
var data []byte
140136
if fileutil.FileExists(templatePath) && config.GetTemplateFormatFromExt(templatePath) == config.YAML {
137+
data, err = io.ReadAll(reader)
138+
if err != nil {
139+
return nil, err
140+
}
141141
data, err = yamlutil.PreProcess(data)
142142
if err != nil {
143143
return nil, err
@@ -148,12 +148,28 @@ func (p *Parser) ParseTemplate(templatePath string, catalog catalog.Catalog) (an
148148

149149
switch config.GetTemplateFormatFromExt(templatePath) {
150150
case config.JSON:
151+
if data == nil {
152+
data, err = io.ReadAll(reader)
153+
if err != nil {
154+
return nil, err
155+
}
156+
}
151157
err = json.Unmarshal(data, template)
152158
case config.YAML:
153-
if p.NoStrictSyntax {
154-
err = yaml.Unmarshal(data, template)
159+
if data != nil {
160+
// Already read and preprocessed
161+
if p.NoStrictSyntax {
162+
err = yaml.Unmarshal(data, template)
163+
} else {
164+
err = yaml.UnmarshalStrict(data, template)
165+
}
155166
} else {
156-
err = yaml.UnmarshalStrict(data, template)
167+
// Stream directly from reader
168+
decoder := yaml.NewDecoder(reader)
169+
if !p.NoStrictSyntax {
170+
decoder.SetStrict(true)
171+
}
172+
err = decoder.Decode(template)
157173
}
158174
default:
159175
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
162178
return nil, err
163179
}
164180

165-
p.parsedTemplatesCache.Store(templatePath, template, data, nil)
181+
p.parsedTemplatesCache.Store(templatePath, template, nil, nil) // don't keep raw bytes to save memory
166182
return template, nil
167183
}
168184

pkg/templates/templates.go

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77
"strconv"
88
"strings"
99

10-
validate "github.com/go-playground/validator/v10"
1110
"github.com/projectdiscovery/nuclei/v3/pkg/model"
1211
"github.com/projectdiscovery/nuclei/v3/pkg/protocols"
1312
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/code"
@@ -310,10 +309,8 @@ func (template *Template) validateAllRequestIDs() {
310309
// MarshalYAML forces recursive struct validation during marshal operation
311310
func (template *Template) MarshalYAML() ([]byte, error) {
312311
out, marshalErr := yaml.Marshal(template)
313-
// Review: we are adding requestIDs for templateContext
314-
// if we are using this method then we might need to purge manually added IDS that start with `templatetype_`
315-
// this is only applicable if there are more than 1 request fields in protocol
316-
errValidate := validate.New().Struct(template)
312+
// Use shared validator to avoid rebuilding struct cache for every template marshal
313+
errValidate := tplValidator.Struct(template)
317314
return out, multierr.Append(marshalErr, errValidate)
318315
}
319316

@@ -354,7 +351,7 @@ func (template *Template) UnmarshalYAML(unmarshal func(interface{}) error) error
354351
if len(alias.RequestsWithTCP) > 0 {
355352
template.RequestsNetwork = alias.RequestsWithTCP
356353
}
357-
err = validate.New().Struct(template)
354+
err = tplValidator.Struct(template)
358355
if err != nil {
359356
return err
360357
}
@@ -525,7 +522,7 @@ func (template *Template) hasMultipleRequests() bool {
525522
func (template *Template) MarshalJSON() ([]byte, error) {
526523
type TemplateAlias Template //avoid recursion
527524
out, marshalErr := json.Marshal((*TemplateAlias)(template))
528-
errValidate := validate.New().Struct(template)
525+
errValidate := tplValidator.Struct(template)
529526
return out, multierr.Append(marshalErr, errValidate)
530527
}
531528

@@ -538,7 +535,7 @@ func (template *Template) UnmarshalJSON(data []byte) error {
538535
return err
539536
}
540537
*template = Template(*alias)
541-
err = validate.New().Struct(template)
538+
err = tplValidator.Struct(template)
542539
if err != nil {
543540
return err
544541
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package templates
2+
3+
import (
4+
validate "github.com/go-playground/validator/v10"
5+
)
6+
7+
var tplValidator = validate.New()

0 commit comments

Comments
 (0)