Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
projectdiscovery
GitHub Repository: projectdiscovery/nuclei
Path: blob/dev/pkg/templates/parser.go
2070 views
1
package templates
2
3
import (
4
"fmt"
5
"io"
6
"strings"
7
"sync"
8
9
"github.com/projectdiscovery/nuclei/v3/pkg/catalog"
10
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
11
"github.com/projectdiscovery/nuclei/v3/pkg/utils"
12
"github.com/projectdiscovery/nuclei/v3/pkg/utils/json"
13
"github.com/projectdiscovery/nuclei/v3/pkg/utils/stats"
14
yamlutil "github.com/projectdiscovery/nuclei/v3/pkg/utils/yaml"
15
"github.com/projectdiscovery/utils/errkit"
16
fileutil "github.com/projectdiscovery/utils/file"
17
18
"gopkg.in/yaml.v2"
19
)
20
21
type Parser struct {
22
ShouldValidate bool
23
NoStrictSyntax bool
24
// this cache can be copied safely between ephemeral instances
25
parsedTemplatesCache *Cache
26
// this cache might potentially contain references to heap objects
27
// it's recommended to always empty it at the end of execution
28
compiledTemplatesCache *Cache
29
sync.Mutex
30
}
31
32
func NewParser() *Parser {
33
p := &Parser{
34
parsedTemplatesCache: NewCache(),
35
compiledTemplatesCache: NewCache(),
36
}
37
38
return p
39
}
40
41
func NewParserWithParsedCache(cache *Cache) *Parser {
42
return &Parser{
43
parsedTemplatesCache: cache,
44
compiledTemplatesCache: NewCache(),
45
}
46
}
47
48
// Cache returns the parsed templates cache
49
func (p *Parser) Cache() *Cache {
50
return p.parsedTemplatesCache
51
}
52
53
// CompiledCache returns the compiled templates cache
54
func (p *Parser) CompiledCache() *Cache {
55
return p.compiledTemplatesCache
56
}
57
58
func (p *Parser) ParsedCount() int {
59
p.Lock()
60
defer p.Unlock()
61
return len(p.parsedTemplatesCache.items.Map)
62
}
63
64
func (p *Parser) CompiledCount() int {
65
p.Lock()
66
defer p.Unlock()
67
return len(p.compiledTemplatesCache.items.Map)
68
}
69
70
func checkOpenFileError(err error) bool {
71
if err != nil && strings.Contains(err.Error(), "too many open files") {
72
panic(err)
73
}
74
return false
75
}
76
77
// LoadTemplate returns true if the template is valid and matches the filtering criteria.
78
func (p *Parser) LoadTemplate(templatePath string, t any, extraTags []string, catalog catalog.Catalog) (bool, error) {
79
tagFilter, ok := t.(*TagFilter)
80
if !ok {
81
panic("not a *TagFilter")
82
}
83
t, templateParseError := p.ParseTemplate(templatePath, catalog)
84
if templateParseError != nil {
85
checkOpenFileError(templateParseError)
86
return false, errkit.Newf("Could not load template %s: %s", templatePath, templateParseError)
87
}
88
template, ok := t.(*Template)
89
if !ok {
90
panic("not a template")
91
}
92
93
if len(template.Workflows) > 0 {
94
return false, nil
95
}
96
97
validationError := validateTemplateMandatoryFields(template)
98
if validationError != nil {
99
stats.Increment(SyntaxErrorStats)
100
return false, errkit.Newf("Could not load template %s: %s", templatePath, validationError)
101
}
102
103
ret, err := isTemplateInfoMetadataMatch(tagFilter, template, extraTags)
104
if err != nil {
105
checkOpenFileError(err)
106
return ret, errkit.Newf("Could not load template %s: %s", templatePath, err)
107
}
108
// if template loaded then check the template for optional fields to add warnings
109
if ret {
110
validationWarning := validateTemplateOptionalFields(template)
111
if validationWarning != nil {
112
stats.Increment(SyntaxWarningStats)
113
checkOpenFileError(validationWarning)
114
return ret, errkit.Newf("Could not load template %s: %s", templatePath, validationWarning)
115
}
116
}
117
return ret, nil
118
}
119
120
// ParseTemplate parses a template and returns a *templates.Template structure
121
func (p *Parser) ParseTemplate(templatePath string, catalog catalog.Catalog) (any, error) {
122
value, _, err := p.parsedTemplatesCache.Has(templatePath)
123
if value != nil {
124
return value, err
125
}
126
127
reader, err := utils.ReaderFromPathOrURL(templatePath, catalog)
128
if err != nil {
129
return nil, err
130
}
131
defer func() {
132
_ = reader.Close()
133
}()
134
135
// For local YAML files, check if preprocessing is needed
136
var data []byte
137
if fileutil.FileExists(templatePath) && config.GetTemplateFormatFromExt(templatePath) == config.YAML {
138
data, err = io.ReadAll(reader)
139
if err != nil {
140
return nil, err
141
}
142
data, err = yamlutil.PreProcess(data)
143
if err != nil {
144
return nil, err
145
}
146
}
147
148
template := &Template{}
149
150
switch config.GetTemplateFormatFromExt(templatePath) {
151
case config.JSON:
152
if data == nil {
153
data, err = io.ReadAll(reader)
154
if err != nil {
155
return nil, err
156
}
157
}
158
err = json.Unmarshal(data, template)
159
case config.YAML:
160
if data != nil {
161
// Already read and preprocessed
162
if p.NoStrictSyntax {
163
err = yaml.Unmarshal(data, template)
164
} else {
165
err = yaml.UnmarshalStrict(data, template)
166
}
167
} else {
168
// Stream directly from reader
169
decoder := yaml.NewDecoder(reader)
170
if !p.NoStrictSyntax {
171
decoder.SetStrict(true)
172
}
173
err = decoder.Decode(template)
174
}
175
default:
176
err = fmt.Errorf("failed to identify template format expected JSON or YAML but got %v", templatePath)
177
}
178
if err != nil {
179
return nil, err
180
}
181
182
p.parsedTemplatesCache.Store(templatePath, template, nil, nil) // don't keep raw bytes to save memory
183
return template, nil
184
}
185
186
// LoadWorkflow returns true if the workflow is valid and matches the filtering criteria.
187
func (p *Parser) LoadWorkflow(templatePath string, catalog catalog.Catalog) (bool, error) {
188
t, templateParseError := p.ParseTemplate(templatePath, catalog)
189
if templateParseError != nil {
190
return false, templateParseError
191
}
192
193
template, ok := t.(*Template)
194
if !ok {
195
panic("not a template")
196
}
197
198
if len(template.Workflows) > 0 {
199
if validationError := validateTemplateMandatoryFields(template); validationError != nil {
200
stats.Increment(SyntaxErrorStats)
201
return false, validationError
202
}
203
return true, nil
204
}
205
206
return false, nil
207
}
208
209