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