Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
projectdiscovery
GitHub Repository: projectdiscovery/nuclei
Path: blob/dev/pkg/templates/templates.go
2070 views
1
//go:generate dstdocgen -path "" -structure Template -output templates_doc.go -package templates
2
package templates
3
4
import (
5
"io"
6
"path/filepath"
7
"strconv"
8
"strings"
9
10
"github.com/projectdiscovery/nuclei/v3/pkg/model"
11
"github.com/projectdiscovery/nuclei/v3/pkg/protocols"
12
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/code"
13
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/variables"
14
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/dns"
15
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/file"
16
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/headless"
17
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/http"
18
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/javascript"
19
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/network"
20
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/ssl"
21
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/websocket"
22
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/whois"
23
"github.com/projectdiscovery/nuclei/v3/pkg/templates/types"
24
"github.com/projectdiscovery/nuclei/v3/pkg/utils"
25
"github.com/projectdiscovery/nuclei/v3/pkg/utils/json"
26
"github.com/projectdiscovery/nuclei/v3/pkg/workflows"
27
"github.com/projectdiscovery/utils/errkit"
28
fileutil "github.com/projectdiscovery/utils/file"
29
"go.uber.org/multierr"
30
"gopkg.in/yaml.v2"
31
)
32
33
// Template is a YAML input file which defines all the requests and
34
// other metadata for a template.
35
type Template struct {
36
// description: |
37
// ID is the unique id for the template.
38
//
39
// #### Good IDs
40
//
41
// A good ID uniquely identifies what the requests in the template
42
// are doing. Let's say you have a template that identifies a git-config
43
// file on the webservers, a good name would be `git-config-exposure`. Another
44
// example name is `azure-apps-nxdomain-takeover`.
45
// examples:
46
// - name: ID Example
47
// value: "\"CVE-2021-19520\""
48
ID string `yaml:"id" json:"id" jsonschema:"title=id of the template,description=The Unique ID for the template,required,example=cve-2021-19520,pattern=^([a-zA-Z0-9]+[-_])*[a-zA-Z0-9]+$"`
49
// description: |
50
// Info contains metadata information about the template.
51
// examples:
52
// - value: exampleInfoStructure
53
Info model.Info `yaml:"info" json:"info" jsonschema:"title=info for the template,description=Info contains metadata for the template,required,type=object"`
54
// description: |
55
// Flow contains the execution flow for the template.
56
// examples:
57
// - flow: |
58
// for region in regions {
59
// http(0)
60
// }
61
// for vpc in vpcs {
62
// http(1)
63
// }
64
//
65
Flow string `yaml:"flow,omitempty" json:"flow,omitempty" jsonschema:"title=template execution flow in js,description=Flow contains js code which defines how the template should be executed,type=string,example='flow: http(0) && http(1)'"`
66
// description: |
67
// Requests contains the http request to make in the template.
68
// WARNING: 'requests' will be deprecated and will be removed in a future release. Please use 'http' instead.
69
// examples:
70
// - value: exampleNormalHTTPRequest
71
RequestsHTTP []*http.Request `yaml:"requests,omitempty" json:"requests,omitempty" jsonschema:"title=http requests to make,description=HTTP requests to make for the template,deprecated=true"`
72
// description: |
73
// HTTP contains the http request to make in the template.
74
// examples:
75
// - value: exampleNormalHTTPRequest
76
// RequestsWithHTTP is placeholder(internal) only, and should not be used instead use RequestsHTTP
77
// Deprecated: Use RequestsHTTP instead.
78
RequestsWithHTTP []*http.Request `yaml:"http,omitempty" json:"http,omitempty" jsonschema:"title=http requests to make,description=HTTP requests to make for the template"`
79
// description: |
80
// DNS contains the dns request to make in the template
81
// examples:
82
// - value: exampleNormalDNSRequest
83
RequestsDNS []*dns.Request `yaml:"dns,omitempty" json:"dns,omitempty" jsonschema:"title=dns requests to make,description=DNS requests to make for the template"`
84
// description: |
85
// File contains the file request to make in the template
86
// examples:
87
// - value: exampleNormalFileRequest
88
RequestsFile []*file.Request `yaml:"file,omitempty" json:"file,omitempty" jsonschema:"title=file requests to make,description=File requests to make for the template"`
89
// description: |
90
// Network contains the network request to make in the template
91
// WARNING: 'network' will be deprecated and will be removed in a future release. Please use 'tcp' instead.
92
// examples:
93
// - value: exampleNormalNetworkRequest
94
RequestsNetwork []*network.Request `yaml:"network,omitempty" json:"network,omitempty" jsonschema:"title=network requests to make,description=Network requests to make for the template,deprecated=true"`
95
// description: |
96
// TCP contains the network request to make in the template
97
// examples:
98
// - value: exampleNormalNetworkRequest
99
// RequestsWithTCP is placeholder(internal) only, and should not be used instead use RequestsNetwork
100
// Deprecated: Use RequestsNetwork instead.
101
RequestsWithTCP []*network.Request `yaml:"tcp,omitempty" json:"tcp,omitempty" jsonschema:"title=network(tcp) requests to make,description=Network requests to make for the template"`
102
// description: |
103
// Headless contains the headless request to make in the template.
104
RequestsHeadless []*headless.Request `yaml:"headless,omitempty" json:"headless,omitempty" jsonschema:"title=headless requests to make,description=Headless requests to make for the template"`
105
// description: |
106
// SSL contains the SSL request to make in the template.
107
RequestsSSL []*ssl.Request `yaml:"ssl,omitempty" json:"ssl,omitempty" jsonschema:"title=ssl requests to make,description=SSL requests to make for the template"`
108
// description: |
109
// Websocket contains the Websocket request to make in the template.
110
RequestsWebsocket []*websocket.Request `yaml:"websocket,omitempty" json:"websocket,omitempty" jsonschema:"title=websocket requests to make,description=Websocket requests to make for the template"`
111
// description: |
112
// WHOIS contains the WHOIS request to make in the template.
113
RequestsWHOIS []*whois.Request `yaml:"whois,omitempty" json:"whois,omitempty" jsonschema:"title=whois requests to make,description=WHOIS requests to make for the template"`
114
// description: |
115
// Code contains code snippets.
116
RequestsCode []*code.Request `yaml:"code,omitempty" json:"code,omitempty" jsonschema:"title=code snippets to make,description=Code snippets"`
117
// description: |
118
// Javascript contains the javascript request to make in the template.
119
RequestsJavascript []*javascript.Request `yaml:"javascript,omitempty" json:"javascript,omitempty" jsonschema:"title=javascript requests to make,description=Javascript requests to make for the template"`
120
121
// description: |
122
// Workflows is a yaml based workflow declaration code.
123
workflows.Workflow `yaml:",inline,omitempty" jsonschema:"title=workflows to run,description=Workflows to run for the template"`
124
CompiledWorkflow *workflows.Workflow `yaml:"-" json:"-" jsonschema:"-"`
125
126
// description: |
127
// Self Contained marks Requests for the template as self-contained
128
SelfContained bool `yaml:"self-contained,omitempty" json:"self-contained,omitempty" jsonschema:"title=mark requests as self-contained,description=Mark Requests for the template as self-contained"`
129
// description: |
130
// Stop execution once first match is found
131
StopAtFirstMatch bool `yaml:"stop-at-first-match,omitempty" json:"stop-at-first-match,omitempty" jsonschema:"title=stop at first match,description=Stop at first match for the template"`
132
133
// description: |
134
// Signature is the request signature method
135
// WARNING: 'signature' will be deprecated and will be removed in a future release. Prefer using 'code' protocol for writing cloud checks
136
// values:
137
// - "AWS"
138
Signature http.SignatureTypeHolder `yaml:"signature,omitempty" json:"signature,omitempty" jsonschema:"title=signature is the http request signature method,description=Signature is the HTTP Request signature Method,enum=AWS,deprecated=true"`
139
140
// description: |
141
// Variables contains any variables for the current request.
142
Variables variables.Variable `yaml:"variables,omitempty" json:"variables,omitempty" jsonschema:"title=variables for the http request,description=Variables contains any variables for the current request,type=object"`
143
144
// description: |
145
// Constants contains any scalar constant for the current template
146
Constants map[string]interface{} `yaml:"constants,omitempty" json:"constants,omitempty" jsonschema:"title=constant for the template,description=constants contains any constant for the template,type=object"`
147
148
// TotalRequests is the total number of requests for the template.
149
TotalRequests int `yaml:"-" json:"-"`
150
// Executer is the actual template executor for running template requests
151
Executer protocols.Executer `yaml:"-" json:"-"`
152
153
Path string `yaml:"-" json:"-"`
154
155
// Verified defines if the template signature is digitally verified
156
Verified bool `yaml:"-" json:"-"`
157
// TemplateVerifier is identifier verifier used to verify the template (default nuclei-templates have projectdiscovery/nuclei-templates)
158
TemplateVerifier string `yaml:"-" json:"-"`
159
// RequestsQueue contains all template requests in order (both protocol & request order)
160
RequestsQueue []protocols.Request `yaml:"-" json:"-"`
161
162
// ImportedFiles contains list of files whose contents are imported after template was compiled
163
ImportedFiles []string `yaml:"-" json:"-"`
164
}
165
166
// Type returns the type of the template
167
func (template *Template) Type() types.ProtocolType {
168
switch {
169
case len(template.RequestsDNS) > 0:
170
return types.DNSProtocol
171
case len(template.RequestsFile) > 0:
172
return types.FileProtocol
173
case len(template.RequestsHTTP) > 0:
174
return types.HTTPProtocol
175
case len(template.RequestsHeadless) > 0:
176
return types.HeadlessProtocol
177
case len(template.RequestsNetwork) > 0:
178
return types.NetworkProtocol
179
case len(template.RequestsSSL) > 0:
180
return types.SSLProtocol
181
case len(template.RequestsWebsocket) > 0:
182
return types.WebsocketProtocol
183
case len(template.RequestsWHOIS) > 0:
184
return types.WHOISProtocol
185
case len(template.RequestsCode) > 0:
186
return types.CodeProtocol
187
case len(template.RequestsJavascript) > 0:
188
return types.JavascriptProtocol
189
case len(template.Workflows) > 0:
190
return types.WorkflowProtocol
191
default:
192
return types.InvalidProtocol
193
}
194
}
195
196
// IsFuzzing returns true if the template is a fuzzing template
197
func (template *Template) IsFuzzing() bool {
198
if len(template.RequestsHTTP) == 0 && len(template.RequestsHeadless) == 0 {
199
// fuzzing is only supported for http and headless protocols
200
return false
201
}
202
if len(template.RequestsHTTP) > 0 {
203
for _, request := range template.RequestsHTTP {
204
if len(request.Fuzzing) > 0 {
205
return true
206
}
207
}
208
}
209
if len(template.RequestsHeadless) > 0 {
210
for _, request := range template.RequestsHeadless {
211
if len(request.Fuzzing) > 0 {
212
return true
213
}
214
}
215
}
216
return false
217
}
218
219
// UsesRequestSignature returns true if the template uses a request signature like AWS
220
func (template *Template) UsesRequestSignature() bool {
221
return template.Signature.Value.String() != ""
222
}
223
224
// HasCodeProtocol returns true if the template has a code protocol section
225
func (template *Template) HasCodeProtocol() bool {
226
return len(template.RequestsCode) > 0
227
}
228
229
// validateAllRequestIDs check if that protocol already has given id if not
230
// then is is manually set to proto_index
231
func (template *Template) validateAllRequestIDs() {
232
// this is required in multiprotocol and flow where we save response variables
233
// and all other data in template context if template as two requests in a protocol
234
// then it is overwritten to avoid this we use proto_index as request ID
235
if len(template.RequestsCode) > 1 {
236
for i, req := range template.RequestsCode {
237
if req.ID == "" {
238
req.ID = req.Type().String() + "_" + strconv.Itoa(i+1)
239
}
240
}
241
}
242
243
if len(template.RequestsDNS) > 1 {
244
for i, req := range template.RequestsDNS {
245
if req.ID == "" {
246
req.ID = req.Type().String() + "_" + strconv.Itoa(i+1)
247
}
248
}
249
}
250
if len(template.RequestsFile) > 1 {
251
for i, req := range template.RequestsFile {
252
if req.ID == "" {
253
req.ID = req.Type().String() + "_" + strconv.Itoa(i+1)
254
}
255
}
256
}
257
if len(template.RequestsHTTP) > 1 {
258
for i, req := range template.RequestsHTTP {
259
if req.ID == "" {
260
req.ID = req.Type().String() + "_" + strconv.Itoa(i+1)
261
}
262
}
263
}
264
if len(template.RequestsHeadless) > 1 {
265
for i, req := range template.RequestsHeadless {
266
if req.ID == "" {
267
req.ID = req.Type().String() + "_" + strconv.Itoa(i+1)
268
}
269
}
270
271
}
272
if len(template.RequestsNetwork) > 1 {
273
for i, req := range template.RequestsNetwork {
274
if req.ID == "" {
275
req.ID = req.Type().String() + "_" + strconv.Itoa(i+1)
276
}
277
}
278
}
279
if len(template.RequestsSSL) > 1 {
280
for i, req := range template.RequestsSSL {
281
if req.ID == "" {
282
req.ID = req.Type().String() + "_" + strconv.Itoa(i+1)
283
}
284
}
285
}
286
if len(template.RequestsWebsocket) > 1 {
287
for i, req := range template.RequestsWebsocket {
288
if req.ID == "" {
289
req.ID = req.Type().String() + "_" + strconv.Itoa(i+1)
290
}
291
}
292
}
293
if len(template.RequestsWHOIS) > 1 {
294
for i, req := range template.RequestsWHOIS {
295
if req.ID == "" {
296
req.ID = req.Type().String() + "_" + strconv.Itoa(i+1)
297
}
298
}
299
}
300
if len(template.RequestsJavascript) > 1 {
301
for i, req := range template.RequestsJavascript {
302
if req.ID == "" {
303
req.ID = req.Type().String() + "_" + strconv.Itoa(i+1)
304
}
305
}
306
}
307
}
308
309
// MarshalYAML forces recursive struct validation during marshal operation
310
func (template *Template) MarshalYAML() ([]byte, error) {
311
out, marshalErr := yaml.Marshal(template)
312
// Use shared validator to avoid rebuilding struct cache for every template marshal
313
errValidate := tplValidator.Struct(template)
314
return out, multierr.Append(marshalErr, errValidate)
315
}
316
317
// UnmarshalYAML forces recursive struct validation after unmarshal operation
318
func (template *Template) UnmarshalYAML(unmarshal func(interface{}) error) error {
319
type Alias Template
320
alias := &Alias{}
321
err := unmarshal(alias)
322
if err != nil {
323
return err
324
}
325
*template = Template(*alias)
326
327
if !ReTemplateID.MatchString(template.ID) {
328
return errkit.New("template id must match expression %v", ReTemplateID, "tag", "invalid_template")
329
}
330
info := template.Info
331
if utils.IsBlank(info.Name) {
332
return errkit.New("no template name field provided", "tag", "invalid_template")
333
}
334
if info.Authors.IsEmpty() {
335
return errkit.New("no template author field provided", "tag", "invalid_template")
336
}
337
338
if len(template.RequestsHTTP) > 0 || len(template.RequestsNetwork) > 0 {
339
_ = deprecatedProtocolNameTemplates.Set(template.ID, true)
340
}
341
342
if len(alias.RequestsHTTP) > 0 && len(alias.RequestsWithHTTP) > 0 {
343
return errkit.New("use http or requests, both are not supported", "tag", "invalid_template")
344
}
345
if len(alias.RequestsNetwork) > 0 && len(alias.RequestsWithTCP) > 0 {
346
return errkit.New("use tcp or network, both are not supported", "tag", "invalid_template")
347
}
348
if len(alias.RequestsWithHTTP) > 0 {
349
template.RequestsHTTP = alias.RequestsWithHTTP
350
}
351
if len(alias.RequestsWithTCP) > 0 {
352
template.RequestsNetwork = alias.RequestsWithTCP
353
}
354
err = tplValidator.Struct(template)
355
if err != nil {
356
return err
357
}
358
// check if the template contains more than 1 protocol request
359
// if so preserve the order of the protocols and requests
360
if template.hasMultipleRequests() {
361
var tempmap yaml.MapSlice
362
err = unmarshal(&tempmap)
363
if err != nil {
364
return errkit.Wrapf(err, "failed to unmarshal multi protocol template %s", template.ID)
365
}
366
arr := []string{}
367
for _, v := range tempmap {
368
key, ok := v.Key.(string)
369
if !ok {
370
continue
371
}
372
arr = append(arr, key)
373
}
374
// add protocols to the protocol stack (the idea is to preserve the order of the protocols)
375
template.addRequestsToQueue(arr...)
376
}
377
return nil
378
}
379
380
// ImportFileRefs checks if sensitive fields like `flow` , `source` in code protocol are referencing files
381
// instead of actual javascript / engine code if so it loads the file contents and replaces the reference
382
func (template *Template) ImportFileRefs(options *protocols.ExecutorOptions) error {
383
var errs []error
384
385
loadFile := func(source string) (string, bool) {
386
// load file respecting sandbox
387
data, err := options.Options.LoadHelperFile(source, options.TemplatePath, options.Catalog)
388
if err == nil {
389
defer func() {
390
_ = data.Close()
391
}()
392
bin, err := io.ReadAll(data)
393
if err == nil {
394
return string(bin), true
395
} else {
396
errs = append(errs, err)
397
}
398
} else {
399
errs = append(errs, err)
400
}
401
return "", false
402
}
403
404
// for code protocol requests
405
for _, request := range template.RequestsCode {
406
// simple test to check if source is a file or a snippet
407
if len(strings.Split(request.Source, "\n")) == 1 && fileutil.FileExists(request.Source) {
408
if val, ok := loadFile(request.Source); ok {
409
template.ImportedFiles = append(template.ImportedFiles, request.Source)
410
request.Source = val
411
}
412
}
413
}
414
415
// for javascript protocol code references
416
for _, request := range template.RequestsJavascript {
417
// simple test to check if source is a file or a snippet
418
if len(strings.Split(request.Code, "\n")) == 1 && fileutil.FileExists(request.Code) {
419
if val, ok := loadFile(request.Code); ok {
420
template.ImportedFiles = append(template.ImportedFiles, request.Code)
421
request.Code = val
422
}
423
}
424
}
425
426
// flow code references
427
if template.Flow != "" {
428
if len(template.Flow) > 0 && filepath.Ext(template.Flow) == ".js" && fileutil.FileExists(template.Flow) {
429
if val, ok := loadFile(template.Flow); ok {
430
template.ImportedFiles = append(template.ImportedFiles, template.Flow)
431
template.Flow = val
432
}
433
}
434
options.Flow = template.Flow
435
}
436
437
// for multiprotocol requests
438
// mutually exclusive with flow
439
if len(template.RequestsQueue) > 0 && template.Flow == "" {
440
// this is most likely a multiprotocol template
441
for _, req := range template.RequestsQueue {
442
if req.Type() == types.CodeProtocol {
443
request := req.(*code.Request)
444
// simple test to check if source is a file or a snippet
445
if len(strings.Split(request.Source, "\n")) == 1 && fileutil.FileExists(request.Source) {
446
if val, ok := loadFile(request.Source); ok {
447
template.ImportedFiles = append(template.ImportedFiles, request.Source)
448
request.Source = val
449
}
450
}
451
}
452
}
453
454
// for javascript protocol code references
455
for _, req := range template.RequestsQueue {
456
if req.Type() == types.JavascriptProtocol {
457
request := req.(*javascript.Request)
458
// simple test to check if source is a file or a snippet
459
if len(strings.Split(request.Code, "\n")) == 1 && fileutil.FileExists(request.Code) {
460
if val, ok := loadFile(request.Code); ok {
461
template.ImportedFiles = append(template.ImportedFiles, request.Code)
462
request.Code = val
463
}
464
}
465
}
466
}
467
}
468
469
return multierr.Combine(errs...)
470
}
471
472
// GetFileImports returns a list of files that are imported by the template
473
func (template *Template) GetFileImports() []string {
474
return template.ImportedFiles
475
}
476
477
// addRequestsToQueue adds protocol requests to the queue and preserves order of the protocols and requests
478
func (template *Template) addRequestsToQueue(keys ...string) {
479
for _, key := range keys {
480
switch key {
481
case types.DNSProtocol.String():
482
template.RequestsQueue = append(template.RequestsQueue, template.convertRequestToProtocolsRequest(template.RequestsDNS)...)
483
case types.FileProtocol.String():
484
template.RequestsQueue = append(template.RequestsQueue, template.convertRequestToProtocolsRequest(template.RequestsFile)...)
485
case types.HTTPProtocol.String():
486
template.RequestsQueue = append(template.RequestsQueue, template.convertRequestToProtocolsRequest(template.RequestsHTTP)...)
487
case types.HeadlessProtocol.String():
488
template.RequestsQueue = append(template.RequestsQueue, template.convertRequestToProtocolsRequest(template.RequestsHeadless)...)
489
case types.NetworkProtocol.String():
490
template.RequestsQueue = append(template.RequestsQueue, template.convertRequestToProtocolsRequest(template.RequestsNetwork)...)
491
case types.SSLProtocol.String():
492
template.RequestsQueue = append(template.RequestsQueue, template.convertRequestToProtocolsRequest(template.RequestsSSL)...)
493
case types.WebsocketProtocol.String():
494
template.RequestsQueue = append(template.RequestsQueue, template.convertRequestToProtocolsRequest(template.RequestsWebsocket)...)
495
case types.WHOISProtocol.String():
496
template.RequestsQueue = append(template.RequestsQueue, template.convertRequestToProtocolsRequest(template.RequestsWHOIS)...)
497
case types.CodeProtocol.String():
498
template.RequestsQueue = append(template.RequestsQueue, template.convertRequestToProtocolsRequest(template.RequestsCode)...)
499
case types.JavascriptProtocol.String():
500
template.RequestsQueue = append(template.RequestsQueue, template.convertRequestToProtocolsRequest(template.RequestsJavascript)...)
501
// for deprecated protocols
502
case "requests":
503
template.RequestsQueue = append(template.RequestsQueue, template.convertRequestToProtocolsRequest(template.RequestsHTTP)...)
504
case "network":
505
template.RequestsQueue = append(template.RequestsQueue, template.convertRequestToProtocolsRequest(template.RequestsNetwork)...)
506
}
507
}
508
}
509
510
// hasMultipleRequests checks if the template has multiple requests
511
// if so it preserves the order of the request during compile and execution
512
func (template *Template) hasMultipleRequests() bool {
513
counter := len(template.RequestsDNS) + len(template.RequestsFile) +
514
len(template.RequestsHTTP) + len(template.RequestsHeadless) +
515
len(template.RequestsNetwork) + len(template.RequestsSSL) +
516
len(template.RequestsWebsocket) + len(template.RequestsWHOIS) +
517
len(template.RequestsCode) + len(template.RequestsJavascript)
518
return counter > 1
519
}
520
521
// MarshalJSON forces recursive struct validation during marshal operation
522
func (template *Template) MarshalJSON() ([]byte, error) {
523
type TemplateAlias Template //avoid recursion
524
out, marshalErr := json.Marshal((*TemplateAlias)(template))
525
errValidate := tplValidator.Struct(template)
526
return out, multierr.Append(marshalErr, errValidate)
527
}
528
529
// UnmarshalJSON forces recursive struct validation after unmarshal operation
530
func (template *Template) UnmarshalJSON(data []byte) error {
531
type Alias Template
532
alias := &Alias{}
533
err := json.Unmarshal(data, alias)
534
if err != nil {
535
return err
536
}
537
*template = Template(*alias)
538
err = tplValidator.Struct(template)
539
if err != nil {
540
return err
541
}
542
// check if the template contains more than 1 protocol request
543
// if so preserve the order of the protocols and requests
544
if template.hasMultipleRequests() {
545
var tempMap map[string]interface{}
546
err = json.Unmarshal(data, &tempMap)
547
if err != nil {
548
return errkit.Wrapf(err, "failed to unmarshal multi protocol template %s", template.ID)
549
}
550
arr := []string{}
551
for k := range tempMap {
552
arr = append(arr, k)
553
}
554
template.addRequestsToQueue(arr...)
555
}
556
return nil
557
}
558
559
// HasFileProtocol returns true if the template has a file protocol section
560
func (template *Template) HasFileProtocol() bool {
561
return len(template.RequestsFile) > 0
562
}
563
564