//go:generate dstdocgen -path "" -structure Template -output templates_doc.go -package templates
package templates

import (
	"bytes"
	"io"
	"path/filepath"
	"strconv"
	"strings"

	"github.com/projectdiscovery/nuclei/v3/pkg/model"
	"github.com/projectdiscovery/nuclei/v3/pkg/protocols"
	"github.com/projectdiscovery/nuclei/v3/pkg/protocols/code"
	"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/variables"
	"github.com/projectdiscovery/nuclei/v3/pkg/protocols/dns"
	"github.com/projectdiscovery/nuclei/v3/pkg/protocols/file"
	"github.com/projectdiscovery/nuclei/v3/pkg/protocols/headless"
	"github.com/projectdiscovery/nuclei/v3/pkg/protocols/http"
	"github.com/projectdiscovery/nuclei/v3/pkg/protocols/javascript"
	"github.com/projectdiscovery/nuclei/v3/pkg/protocols/network"
	"github.com/projectdiscovery/nuclei/v3/pkg/protocols/ssl"
	"github.com/projectdiscovery/nuclei/v3/pkg/protocols/websocket"
	"github.com/projectdiscovery/nuclei/v3/pkg/protocols/whois"
	"github.com/projectdiscovery/nuclei/v3/pkg/templates/types"
	"github.com/projectdiscovery/nuclei/v3/pkg/utils"
	"github.com/projectdiscovery/nuclei/v3/pkg/utils/json"
	"github.com/projectdiscovery/nuclei/v3/pkg/utils/yaml"
	"github.com/projectdiscovery/nuclei/v3/pkg/workflows"
	"github.com/projectdiscovery/utils/errkit"
	fileutil "github.com/projectdiscovery/utils/file"
	"go.uber.org/multierr"
)

// Template is a YAML input file which defines all the requests and
// other metadata for a template.
type Template struct {
	// description: |
	//   ID is the unique id for the template.
	//
	//   #### Good IDs
	//
	//   A good ID uniquely identifies what the requests in the template
	//   are doing. Let's say you have a template that identifies a git-config
	//   file on the webservers, a good name would be `git-config-exposure`. Another
	//   example name is `azure-apps-nxdomain-takeover`.
	// examples:
	//   - name: ID Example
	//     value: "\"CVE-2021-19520\""
	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]+$"`
	// description: |
	//   Info contains metadata information about the template.
	// examples:
	//   - value: exampleInfoStructure
	Info model.Info `yaml:"info" json:"info" jsonschema:"title=info for the template,description=Info contains metadata for the template,required,type=object"`
	// description: |
	//   Flow contains the execution flow for the template.
	// examples:
	//   - flow: |
	// 		for region in regions {
	//		    http(0)
	//		 }
	//		 for vpc in vpcs {
	//		    http(1)
	//		 }
	//
	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)'"`
	// description: |
	//   Requests contains the http request to make in the template.
	//   WARNING: 'requests' will be deprecated and will be removed in a future release. Please use 'http' instead.
	// examples:
	//   - value: exampleNormalHTTPRequest
	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"`
	// description: |
	//   HTTP contains the http request to make in the template.
	// examples:
	//   - value: exampleNormalHTTPRequest
	// RequestsWithHTTP is placeholder(internal) only, and should not be used instead use RequestsHTTP
	// Deprecated: Use RequestsHTTP instead.
	RequestsWithHTTP []*http.Request `yaml:"http,omitempty" json:"http,omitempty" jsonschema:"title=http requests to make,description=HTTP requests to make for the template"`
	// description: |
	//   DNS contains the dns request to make in the template
	// examples:
	//   - value: exampleNormalDNSRequest
	RequestsDNS []*dns.Request `yaml:"dns,omitempty" json:"dns,omitempty" jsonschema:"title=dns requests to make,description=DNS requests to make for the template"`
	// description: |
	//   File contains the file request to make in the template
	// examples:
	//   - value: exampleNormalFileRequest
	RequestsFile []*file.Request `yaml:"file,omitempty" json:"file,omitempty" jsonschema:"title=file requests to make,description=File requests to make for the template"`
	// description: |
	//   Network contains the network request to make in the template
	//   WARNING: 'network' will be deprecated and will be removed in a future release. Please use 'tcp' instead.
	// examples:
	//   - value: exampleNormalNetworkRequest
	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"`
	// description: |
	//   TCP contains the network request to make in the template
	// examples:
	//   - value: exampleNormalNetworkRequest
	// RequestsWithTCP is placeholder(internal) only, and should not be used instead use RequestsNetwork
	// Deprecated: Use RequestsNetwork instead.
	RequestsWithTCP []*network.Request `yaml:"tcp,omitempty" json:"tcp,omitempty" jsonschema:"title=network(tcp) requests to make,description=Network requests to make for the template"`
	// description: |
	//   Headless contains the headless request to make in the template.
	RequestsHeadless []*headless.Request `yaml:"headless,omitempty" json:"headless,omitempty" jsonschema:"title=headless requests to make,description=Headless requests to make for the template"`
	// description: |
	//   SSL contains the SSL request to make in the template.
	RequestsSSL []*ssl.Request `yaml:"ssl,omitempty" json:"ssl,omitempty" jsonschema:"title=ssl requests to make,description=SSL requests to make for the template"`
	// description: |
	//   Websocket contains the Websocket request to make in the template.
	RequestsWebsocket []*websocket.Request `yaml:"websocket,omitempty" json:"websocket,omitempty" jsonschema:"title=websocket requests to make,description=Websocket requests to make for the template"`
	// description: |
	//   WHOIS contains the WHOIS request to make in the template.
	RequestsWHOIS []*whois.Request `yaml:"whois,omitempty" json:"whois,omitempty" jsonschema:"title=whois requests to make,description=WHOIS requests to make for the template"`
	// description: |
	//   Code contains code snippets.
	RequestsCode []*code.Request `yaml:"code,omitempty" json:"code,omitempty" jsonschema:"title=code snippets to make,description=Code snippets"`
	// description: |
	//   Javascript contains the javascript request to make in the template.
	RequestsJavascript []*javascript.Request `yaml:"javascript,omitempty" json:"javascript,omitempty" jsonschema:"title=javascript requests to make,description=Javascript requests to make for the template"`

	// description: |
	//   Workflows is a yaml based workflow declaration code.
	workflows.Workflow `yaml:",inline,omitempty" jsonschema:"title=workflows to run,description=Workflows to run for the template"`
	CompiledWorkflow   *workflows.Workflow `yaml:"-" json:"-" jsonschema:"-"`

	// description: |
	//   Self Contained marks Requests for the template as self-contained
	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"`
	// description: |
	//  Stop execution once first match is found
	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"`

	// description: |
	//   Signature is the request signature method
	//   WARNING: 'signature' will be deprecated and will be removed in a future release. Prefer using 'code' protocol for writing cloud checks
	// values:
	//   - "AWS"
	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"`

	// description: |
	//   Variables contains any variables for the current request.
	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"`

	// description: |
	//   Constants contains any scalar constant for the current template
	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"`

	// TotalRequests is the total number of requests for the template.
	TotalRequests int `yaml:"-" json:"-"`
	// Executer is the actual template executor for running template requests
	Executer protocols.Executer `yaml:"-" json:"-"`

	Path string `yaml:"-" json:"-"`

	// Verified defines if the template signature is digitally verified
	Verified bool `yaml:"-" json:"-"`
	// TemplateVerifier is identifier verifier used to verify the template (default nuclei-templates have projectdiscovery/nuclei-templates)
	TemplateVerifier string `yaml:"-" json:"-"`
	// RequestsQueue contains all template requests in order (both protocol & request order)
	RequestsQueue []protocols.Request `yaml:"-" json:"-"`

	// ImportedFiles contains list of files whose contents are imported after template was compiled
	ImportedFiles []string `yaml:"-" json:"-"`
}

// HasCodeProtocol returns true if the template has a code protocol section
//
// Deprecated: Use [HasCodeRequest] instead.
func (template *Template) HasCodeProtocol() bool {
	return len(template.RequestsCode) > 0
}

// HasFileProtocol returns true if the template has a file protocol section.
//
// Deprecated: Use [HasFileRequest] instead.
func (template *Template) HasFileProtocol() bool {
	return len(template.RequestsFile) > 0
}

// Type returns the type of the template
func (template *Template) Type() types.ProtocolType {
	switch {
	case template.HasDNSRequest():
		return types.DNSProtocol
	case template.HasFileRequest():
		return types.FileProtocol
	case template.HasHTTPRequest():
		return types.HTTPProtocol
	case template.HasHeadlessRequest():
		return types.HeadlessProtocol
	case template.HasNetworkRequest():
		return types.NetworkProtocol
	case template.HasSSLRequest():
		return types.SSLProtocol
	case template.HasWebsocketRequest():
		return types.WebsocketProtocol
	case template.HasWHOISRequest():
		return types.WHOISProtocol
	case template.HasCodeRequest():
		return types.CodeProtocol
	case template.HasJavascriptRequest():
		return types.JavascriptProtocol
	case template.HasWorkflows():
		return types.WorkflowProtocol
	default:
		return types.InvalidProtocol
	}
}

// IsFuzzing returns true if the template is a fuzzing template
//
// Deprecated: Use [IsFuzzableRequest] instead.
func (template *Template) IsFuzzing() bool {
	if len(template.RequestsHTTP) == 0 && len(template.RequestsHeadless) == 0 {
		// fuzzing is only supported for http and headless protocols
		return false
	}
	if len(template.RequestsHTTP) > 0 {
		for _, request := range template.RequestsHTTP {
			if len(request.Fuzzing) > 0 {
				return true
			}
		}
	}
	if len(template.RequestsHeadless) > 0 {
		for _, request := range template.RequestsHeadless {
			if len(request.Fuzzing) > 0 {
				return true
			}
		}
	}
	return false
}

// UsesRequestSignature returns true if the template uses a request signature like AWS
func (template *Template) UsesRequestSignature() bool {
	return template.Signature.Value.String() != ""
}

// validateAllRequestIDs check if that protocol already has given id if not
// then is is manually set to proto_index
func (template *Template) validateAllRequestIDs() {
	// this is required in multiprotocol and flow where we save response variables
	// and all other data in template context if template as two requests in a protocol
	// then it is overwritten to avoid this we use proto_index as request ID
	if template.HasCodeRequest(1) {
		for i, req := range template.RequestsCode {
			if req.ID == "" {
				req.ID = req.Type().String() + "_" + strconv.Itoa(i+1)
			}
		}
	}

	if template.HasDNSRequest(1) {
		for i, req := range template.RequestsDNS {
			if req.ID == "" {
				req.ID = req.Type().String() + "_" + strconv.Itoa(i+1)
			}
		}
	}

	if template.HasFileRequest(1) {
		for i, req := range template.RequestsFile {
			if req.ID == "" {
				req.ID = req.Type().String() + "_" + strconv.Itoa(i+1)
			}
		}
	}

	if template.HasHTTPRequest(1) {
		for i, req := range template.RequestsHTTP {
			if req.ID == "" {
				req.ID = req.Type().String() + "_" + strconv.Itoa(i+1)
			}
		}
	}

	if template.HasHeadlessRequest(1) {
		for i, req := range template.RequestsHeadless {
			if req.ID == "" {
				req.ID = req.Type().String() + "_" + strconv.Itoa(i+1)
			}
		}
	}

	if template.HasNetworkRequest(1) {
		for i, req := range template.RequestsNetwork {
			if req.ID == "" {
				req.ID = req.Type().String() + "_" + strconv.Itoa(i+1)
			}
		}
	}

	if template.HasSSLRequest(1) {
		for i, req := range template.RequestsSSL {
			if req.ID == "" {
				req.ID = req.Type().String() + "_" + strconv.Itoa(i+1)
			}
		}
	}

	if template.HasWebsocketRequest(1) {
		for i, req := range template.RequestsWebsocket {
			if req.ID == "" {
				req.ID = req.Type().String() + "_" + strconv.Itoa(i+1)
			}
		}
	}

	if template.HasWHOISRequest(1) {
		for i, req := range template.RequestsWHOIS {
			if req.ID == "" {
				req.ID = req.Type().String() + "_" + strconv.Itoa(i+1)
			}
		}
	}

	if template.HasJavascriptRequest(1) {
		for i, req := range template.RequestsJavascript {
			if req.ID == "" {
				req.ID = req.Type().String() + "_" + strconv.Itoa(i+1)
			}
		}
	}
}

// MarshalYAML forces recursive struct validation during marshal operation
func (template *Template) MarshalYAML() ([]byte, error) {
	out, marshalErr := yaml.Marshal(template)
	// Use shared validator to avoid rebuilding struct cache for every template marshal
	errValidate := tplValidator.Struct(template)
	return out, multierr.Append(marshalErr, errValidate)
}

// UnmarshalYAML forces recursive struct validation after unmarshal operation
func (template *Template) UnmarshalYAML(unmarshal func(interface{}) error) error {
	type Alias Template
	alias := &Alias{}
	err := unmarshal(alias)
	if err != nil {
		return err
	}
	*template = Template(*alias)

	if !ReTemplateID.MatchString(template.ID) {
		return errkit.New("template id must match expression %v", ReTemplateID, "tag", "invalid_template")
	}

	info := template.Info
	if utils.IsBlank(info.Name) {
		return errkit.New("no template name field provided", "tag", "invalid_template")
	}

	if info.Authors.IsEmpty() {
		return errkit.New("no template author field provided", "tag", "invalid_template")
	}

	if template.HasHTTPRequest() || template.HasNetworkRequest() {
		_ = deprecatedProtocolNameTemplates.Set(template.ID, true)
	}

	if HasRequest(alias.RequestsHTTP) && HasRequest(alias.RequestsWithHTTP) {
		return errkit.New("use http or requests, both are not supported", "tag", "invalid_template")
	}

	if HasRequest(alias.RequestsNetwork) && HasRequest(alias.RequestsWithTCP) {
		return errkit.New("use tcp or network, both are not supported", "tag", "invalid_template")
	}

	if HasRequest(alias.RequestsWithHTTP) {
		template.RequestsHTTP = alias.RequestsWithHTTP
	}

	if HasRequest(alias.RequestsWithTCP) {
		template.RequestsNetwork = alias.RequestsWithTCP
	}

	err = tplValidator.Struct(template)
	if err != nil {
		return err
	}

	// check if the template contains more than 1 protocol request
	// if so  preserve the order of the protocols and requests
	if template.hasMultipleRequests() {
		var tempmap yaml.MapSlice

		err = unmarshal(&tempmap)
		if err != nil {
			return errkit.Wrapf(err, "failed to unmarshal multi protocol template %s", template.ID)
		}

		arr := []string{}
		for _, v := range tempmap {
			key, ok := v.Key.(string)
			if !ok {
				continue
			}

			arr = append(arr, key)
		}

		// add protocols to the protocol stack (the idea is to preserve the order of the protocols)
		template.addRequestsToQueue(arr...)
	}

	return nil
}

// ImportFileRefs checks if sensitive fields like `flow` , `source` in code protocol are referencing files
// instead of actual javascript / engine code if so it loads the file contents and replaces the reference
func (template *Template) ImportFileRefs(options *protocols.ExecutorOptions) error {
	var errs []error

	loadFile := func(source string) (string, bool) {
		// load file respecting sandbox
		data, err := options.Options.LoadHelperFile(source, options.TemplatePath, options.Catalog)
		if err == nil {
			defer func() {
				_ = data.Close()
			}()

			bin, err := io.ReadAll(data)
			if err == nil {
				return string(bin), true
			} else {
				errs = append(errs, err)
			}
		} else {
			errs = append(errs, err)
		}

		return "", false
	}

	// for code protocol requests
	for _, request := range template.RequestsCode {
		// simple test to check if source is a file or a snippet
		if !strings.ContainsRune(request.Source, '\n') && fileutil.FileExists(request.Source) {
			if val, ok := loadFile(request.Source); ok {
				template.ImportedFiles = append(template.ImportedFiles, request.Source)
				request.Source = val
			}
		}
	}

	// for javascript protocol code references
	for _, request := range template.RequestsJavascript {
		// simple test to check if source is a file or a snippet
		if !strings.ContainsRune(request.Code, '\n') && fileutil.FileExists(request.Code) {
			if val, ok := loadFile(request.Code); ok {
				template.ImportedFiles = append(template.ImportedFiles, request.Code)
				request.Code = val
			}
		}
	}

	// flow code references
	if template.IsFlowTemplate() {
		if filepath.Ext(template.Flow) == ".js" && fileutil.FileExists(template.Flow) {
			if val, ok := loadFile(template.Flow); ok {
				template.ImportedFiles = append(template.ImportedFiles, template.Flow)
				template.Flow = val
			}
		}

		options.Flow = template.Flow
	}

	// for multiprotocol requests
	// mutually exclusive with flow
	if HasRequest(template.RequestsQueue) && template.Flow == "" {
		// this is most likely a multiprotocol template
		for _, req := range template.RequestsQueue {
			if req.Type() == types.CodeProtocol {
				request := req.(*code.Request)
				// simple test to check if source is a file or a snippet
				if !strings.ContainsRune(request.Source, '\n') && fileutil.FileExists(request.Source) {
					if val, ok := loadFile(request.Source); ok {
						template.ImportedFiles = append(template.ImportedFiles, request.Source)
						request.Source = val
					}
				}
			}
		}

		// for javascript protocol code references
		for _, req := range template.RequestsQueue {
			if req.Type() == types.JavascriptProtocol {
				request := req.(*javascript.Request)
				// simple test to check if source is a file or a snippet
				if !strings.ContainsRune(request.Code, '\n') && fileutil.FileExists(request.Code) {
					if val, ok := loadFile(request.Code); ok {
						template.ImportedFiles = append(template.ImportedFiles, request.Code)
						request.Code = val
					}
				}
			}
		}
	}

	return multierr.Combine(errs...)
}

// GetFileImports returns a list of files that are imported by the template
func (template *Template) GetFileImports() []string {
	return template.ImportedFiles
}

// addRequestsToQueue adds protocol requests to the queue and preserves order of the protocols and requests
func (template *Template) addRequestsToQueue(keys ...string) {
	for _, key := range keys {
		switch key {
		case types.DNSProtocol.String():
			template.RequestsQueue = append(template.RequestsQueue, template.convertRequestToProtocolsRequest(template.RequestsDNS)...)
		case types.FileProtocol.String():
			template.RequestsQueue = append(template.RequestsQueue, template.convertRequestToProtocolsRequest(template.RequestsFile)...)
		case types.HTTPProtocol.String():
			template.RequestsQueue = append(template.RequestsQueue, template.convertRequestToProtocolsRequest(template.RequestsHTTP)...)
		case types.HeadlessProtocol.String():
			template.RequestsQueue = append(template.RequestsQueue, template.convertRequestToProtocolsRequest(template.RequestsHeadless)...)
		case types.NetworkProtocol.String():
			template.RequestsQueue = append(template.RequestsQueue, template.convertRequestToProtocolsRequest(template.RequestsNetwork)...)
		case types.SSLProtocol.String():
			template.RequestsQueue = append(template.RequestsQueue, template.convertRequestToProtocolsRequest(template.RequestsSSL)...)
		case types.WebsocketProtocol.String():
			template.RequestsQueue = append(template.RequestsQueue, template.convertRequestToProtocolsRequest(template.RequestsWebsocket)...)
		case types.WHOISProtocol.String():
			template.RequestsQueue = append(template.RequestsQueue, template.convertRequestToProtocolsRequest(template.RequestsWHOIS)...)
		case types.CodeProtocol.String():
			template.RequestsQueue = append(template.RequestsQueue, template.convertRequestToProtocolsRequest(template.RequestsCode)...)
		case types.JavascriptProtocol.String():
			template.RequestsQueue = append(template.RequestsQueue, template.convertRequestToProtocolsRequest(template.RequestsJavascript)...)
			// for deprecated protocols
		case "requests":
			template.RequestsQueue = append(template.RequestsQueue, template.convertRequestToProtocolsRequest(template.RequestsHTTP)...)
		case "network":
			template.RequestsQueue = append(template.RequestsQueue, template.convertRequestToProtocolsRequest(template.RequestsNetwork)...)
		}
	}
}

// hasMultipleRequests checks if the template has multiple requests
// if so it preserves the order of the request during compile and execution
func (template *Template) hasMultipleRequests() bool {
	counter := len(template.RequestsDNS) + len(template.RequestsFile) +
		len(template.RequestsHTTP) + len(template.RequestsHeadless) +
		len(template.RequestsNetwork) + len(template.RequestsSSL) +
		len(template.RequestsWebsocket) + len(template.RequestsWHOIS) +
		len(template.RequestsCode) + len(template.RequestsJavascript)
	return counter > 1
}

// MarshalJSON forces recursive struct validation during marshal operation
func (template *Template) MarshalJSON() ([]byte, error) {
	type TemplateAlias Template //avoid recursion
	out, marshalErr := json.Marshal((*TemplateAlias)(template))
	errValidate := tplValidator.Struct(template)
	return out, multierr.Append(marshalErr, errValidate)
}

// templateAlias is an internal alias of [Template] that intentionally
// drops Template's custom JSON/YAML methods so the JSON decoder reflects
// over the underlying fields. Used by both the lax and strict JSON paths.
type templateAlias Template

// UnmarshalJSON forces recursive struct validation after unmarshal operation.
// This is the permissive path used by external callers of [json.Unmarshal];
// strict template loading goes through [Template.unmarshalJSONStrict] which
// is what the parser uses by default — see [Parser.ParseTemplate].
func (template *Template) UnmarshalJSON(data []byte) error {
	alias := &templateAlias{}
	if err := json.Unmarshal(data, alias); err != nil {
		return err
	}
	*template = Template(*alias)
	return template.finalizeFromJSON(data)
}

// unmarshalJSONStrict is the strict equivalent of [Template.UnmarshalJSON]:
// it rejects unknown fields and any trailing data after the JSON document,
// matching the [yaml.UnmarshalStrict] semantics used for YAML templates.
//
// The encoding/json contract cannot propagate [DisallowUnknownFields] through
// a custom [Template.UnmarshalJSON], so the strict path decodes into the
// internal alias directly. Per-call (rather than a process-wide toggle) so
// concurrent parsers with different strictness settings (cf. #6322) cannot
// interfere with each other.
func (template *Template) unmarshalJSONStrict(data []byte) error {
	alias := &templateAlias{}
	dec := json.NewDecoder(bytes.NewReader(data))
	dec.DisallowUnknownFields()
	if err := dec.Decode(alias); err != nil {
		return err
	}
	// Reject trailing data after the first JSON value. json.Decoder by
	// design only consumes one top-level value, so without this check a
	// template like `{"id":"a",...}{"id":"hijack",...}` would silently
	// load the first document. A second Decode into a throwaway must
	// return io.EOF (i.e. only whitespace remained).
	var rest interface{}
	if err := dec.Decode(&rest); err != io.EOF {
		if err == nil {
			return errkit.Newf("unexpected trailing data after JSON template")
		}
		return err
	}
	*template = Template(*alias)
	return template.finalizeFromJSON(data)
}

// finalizeFromJSON runs the post-decode struct validation and multi-protocol
// bookkeeping shared by the lax and strict JSON paths.
func (template *Template) finalizeFromJSON(data []byte) error {
	if err := tplValidator.Struct(template); err != nil {
		return err
	}
	// check if the template contains more than 1 protocol request
	// if so  preserve the order of the protocols and requests
	if template.hasMultipleRequests() {
		var tempMap map[string]interface{}
		if err := json.Unmarshal(data, &tempMap); err != nil {
			return errkit.Wrapf(err, "failed to unmarshal multi protocol template %s", template.ID)
		}
		arr := []string{}
		for k := range tempMap {
			arr = append(arr, k)
		}
		template.addRequestsToQueue(arr...)
	}
	return nil
}

// Requirements holds the required options for a template to be enabled.
//
// Deprecated: use [Template.RequiredCapabilities] instead.
type Requirements struct {
	Headless      bool
	Code          bool
	DAST          bool
	SelfContained bool
	File          bool
}

// Requirements returns what options must be enabled for the template to run.
//
// Deprecated: use [Template.RequiredCapabilities] instead.
func (template *Template) Requirements() Requirements {
	return Requirements{
		Headless:      template.HasHeadlessRequest(),
		Code:          template.HasCodeRequest(),
		DAST:          template.IsFuzzableRequest(),
		SelfContained: template.SelfContained,
		File:          template.HasFileRequest(),
	}
}

// Capabilities represents the enabled options/capabilities.
//
// Deprecated: use [CapabilitySet] and [CapabilitiesFromOptions] instead.
type Capabilities struct {
	Headless      bool
	Code          bool
	DAST          bool
	SelfContained bool
	File          bool
}

// IsEnabledFor checks if all template requirements are satisfied by the given
// capabilities.
//
// Deprecated: use [Template.MissingCapabilities] instead.
func (template *Template) IsEnabledFor(caps Capabilities) bool {
	return len(template.MissingCapabilities(caps.toCapabilitySet())) == 0
}

func (caps Capabilities) toCapabilitySet() CapabilitySet {
	return CapabilitySet{
		CapabilityHeadless:      caps.Headless,
		CapabilityCode:          caps.Code,
		CapabilityDAST:          caps.DAST,
		CapabilitySelfContained: caps.SelfContained,
		CapabilityFile:          caps.File,
	}
}
