//go:build integration
// +build integration

package integration_test

import (
	"errors"
	"fmt"
	"net/http"
	"net/http/httptest"
	"net/http/httputil"
	"os"
	"path/filepath"
	"reflect"
	"regexp"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/julienschmidt/httprouter"
	"github.com/projectdiscovery/nuclei/v3/internal/tests/testutils"
	"github.com/projectdiscovery/nuclei/v3/pkg/utils/json"
	"github.com/projectdiscovery/nuclei/v3/pkg/utils/yaml"
	"github.com/projectdiscovery/retryablehttp-go"
	"github.com/projectdiscovery/utils/errkit"
	logutil "github.com/projectdiscovery/utils/log"
	sliceutil "github.com/projectdiscovery/utils/slice"
	stringsutil "github.com/projectdiscovery/utils/strings"
	unitutils "github.com/projectdiscovery/utils/unit"
)

var httpTestcases = []integrationCase{
	// TODO: excluded due to parsing errors with console
	// "http/raw-unsafe-request.yaml":                  &httpRawUnsafeRequest{},
	{Path: "protocols/http/get-headers.yaml", TestCase: &httpGetHeaders{}},
	{Path: "protocols/http/get-query-string.yaml", TestCase: &httpGetQueryString{}},
	{Path: "protocols/http/get-redirects.yaml", TestCase: &httpGetRedirects{}},
	{Path: "protocols/http/get-host-redirects.yaml", TestCase: &httpGetHostRedirects{}},
	{Path: "protocols/http/disable-redirects.yaml", TestCase: &httpDisableRedirects{}},
	{Path: "protocols/http/get.yaml", TestCase: &httpGet{}},
	{Path: "protocols/http/post-body.yaml", TestCase: &httpPostBody{}},
	{Path: "protocols/http/post-json-body.yaml", TestCase: &httpPostJSONBody{}},
	{Path: "protocols/http/post-multipart-body.yaml", TestCase: &httpPostMultipartBody{}},
	{Path: "protocols/http/raw-cookie-reuse.yaml", TestCase: &httpRawCookieReuse{}},
	{Path: "protocols/http/raw-dynamic-extractor.yaml", TestCase: &httpRawDynamicExtractor{}},
	{Path: "protocols/http/raw-get-query.yaml", TestCase: &httpRawGetQuery{}},
	{Path: "protocols/http/raw-get.yaml", TestCase: &httpRawGet{}},
	{Path: "protocols/http/raw-with-params.yaml", TestCase: &httpRawWithParams{}},
	{Path: "protocols/http/raw-unsafe-with-params.yaml", TestCase: &httpRawWithParams{}}, // Not a typo, functionality is same as above
	{Path: "protocols/http/raw-path-trailing-slash.yaml", TestCase: &httpRawPathTrailingSlash{}},
	{Path: "protocols/http/raw-payload.yaml", TestCase: &httpRawPayload{}},
	{Path: "protocols/http/raw-post-body.yaml", TestCase: &httpRawPostBody{}},
	{Path: "protocols/http/raw-unsafe-path.yaml", TestCase: &httpRawUnsafePath{}},
	{Path: "protocols/http/http-paths.yaml", TestCase: &httpPaths{}},
	{Path: "protocols/http/request-condition.yaml", TestCase: &httpRequestCondition{}},
	{Path: "protocols/http/request-condition-new.yaml", TestCase: &httpRequestCondition{}},
	{Path: "protocols/http/self-contained.yaml", TestCase: &httpRequestSelfContained{}},
	{Path: "protocols/http/self-contained-with-path.yaml", TestCase: &httpRequestSelfContained{}}, // Not a typo, functionality is same as above
	{Path: "protocols/http/self-contained-with-params.yaml", TestCase: &httpRequestSelfContainedWithParams{}},
	{Path: "protocols/http/self-contained-file-input.yaml", TestCase: &httpRequestSelfContainedFileInput{}},
	{Path: "protocols/http/get-case-insensitive.yaml", TestCase: &httpGetCaseInsensitive{}},
	{Path: "protocols/http/get.yaml,protocols/http/get-case-insensitive.yaml", TestCase: &httpGetCaseInsensitiveCluster{}},
	{Path: "protocols/http/get-redirects-chain-headers.yaml", TestCase: &httpGetRedirectsChainHeaders{}},
	{Path: "protocols/http/dsl-matcher-variable.yaml", TestCase: &httpDSLVariable{}},
	{Path: "protocols/http/dsl-functions.yaml", TestCase: &httpDSLFunctions{}},
	{Path: "protocols/http/race-simple.yaml", TestCase: &httpRaceSimple{}},
	{Path: "protocols/http/race-multiple.yaml", TestCase: &httpRaceMultiple{}},
	{Path: "protocols/http/race-condition-with-delay.yaml", TestCase: &httpRaceWithDelay{}},
	{Path: "protocols/http/race-with-variables.yaml", TestCase: &httpRaceWithVariables{}},
	{Path: "protocols/http/stop-at-first-match.yaml", TestCase: &httpStopAtFirstMatch{}},
	{Path: "protocols/http/stop-at-first-match-with-extractors.yaml", TestCase: &httpStopAtFirstMatchWithExtractors{}},
	{Path: "protocols/http/variables.yaml", TestCase: &httpVariables{}},
	{Path: "protocols/http/variables-threads-previous.yaml", TestCase: &httpVariablesThreadsPrevious{}},
	{Path: "protocols/http/variable-dsl-function.yaml", TestCase: &httpVariableDSLFunction{}},
	{Path: "protocols/http/get-override-sni.yaml", TestCase: &httpSniAnnotation{}},
	{Path: "protocols/http/get-sni.yaml", TestCase: &customCLISNI{}},
	{Path: "protocols/http/redirect-match-url.yaml", TestCase: &httpRedirectMatchURL{}},
	{Path: "protocols/http/get-sni-unsafe.yaml", TestCase: &customCLISNIUnsafe{}},
	{Path: "protocols/http/annotation-timeout.yaml", TestCase: &annotationTimeout{}},
	{Path: "protocols/http/custom-attack-type.yaml", TestCase: &customAttackType{}},
	{Path: "protocols/http/get-all-ips.yaml", TestCase: &scanAllIPS{}},
	{Path: "protocols/http/get-without-scheme.yaml", TestCase: &httpGetWithoutScheme{}},
	{Path: "protocols/http/cl-body-without-header.yaml", TestCase: &httpCLBodyWithoutHeader{}},
	{Path: "protocols/http/cl-body-with-header.yaml", TestCase: &httpCLBodyWithHeader{}},
	{Path: "protocols/http/cli-with-constants.yaml", TestCase: &ConstantWithCliVar{}},
	{Path: "protocols/http/constants-with-threads.yaml", TestCase: &constantsWithThreads{}},
	{Path: "protocols/http/matcher-status.yaml", TestCase: &matcherStatusTest{}},
	{Path: "protocols/http/disable-path-automerge.yaml", TestCase: &httpDisablePathAutomerge{}},
	{Path: "protocols/http/http-preprocessor.yaml", TestCase: &httpPreprocessor{}},
	{Path: "protocols/http/multi-request.yaml", TestCase: &httpMultiRequest{}},
	{Path: "protocols/http/http-matcher-extractor-dy-extractor.yaml", TestCase: &httpMatcherExtractorDynamicExtractor{}},
	{Path: "protocols/http/multi-http-var-sharing.yaml", TestCase: &httpMultiVarSharing{}},
	{Path: "protocols/http/response-data-literal-reuse.yaml", TestCase: &httpResponseDataLiteralReuse{}},
	{Path: "protocols/http/raw-path-single-slash.yaml", TestCase: &httpRawPathSingleSlash{}},
	{Path: "protocols/http/raw-unsafe-path-single-slash.yaml", TestCase: &httpRawUnsafePathSingleSlash{}},
}

type httpMultiVarSharing struct{}

func (h *httpMultiVarSharing) Execute(filePath string) error {
	results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "https://scanme.sh", debug)
	if err != nil {
		return err
	}
	return expectResultsCount(results, 1)
}

type httpResponseDataLiteralReuse struct{}

func (h *httpResponseDataLiteralReuse) Execute(filePath string) error {
	router := httprouter.New()
	router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		_, _ = fmt.Fprint(w, `{{md5("Hello")}}`)
	})
	router.GET("/echo", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		if r.URL.Query().Get("x") != `{{md5("Hello")}}` {
			w.WriteHeader(http.StatusBadRequest)
			return
		}
		w.WriteHeader(http.StatusOK)
	})
	ts := httptest.NewServer(router)
	defer ts.Close()

	results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
	if err != nil {
		return err
	}

	return expectResultsCount(results, 1)
}

type httpMatcherExtractorDynamicExtractor struct{}

func (h *httpMatcherExtractorDynamicExtractor) Execute(filePath string) error {
	router := httprouter.New()
	router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		html := `<!DOCTYPE html>
<html lang="en">
<body>
    <a href="/domains">Domains</a>
</body>
</html>`
		_, _ = fmt.Fprint(w, html)
	})
	router.GET("/domains", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		html := `<!DOCTYPE html>
		<html lang="en">
		<head>
			<title>Dynamic Extractor Test</title>
		</head>
		<body>
			<!-- The content of the title tag matches the regex pattern for both the extractor and matcher for 'title' -->
		</body>
		</html>
		`
		_, _ = fmt.Fprint(w, html)
	})
	ts := httptest.NewServer(router)
	defer ts.Close()

	results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
	if err != nil {
		return err
	}

	return expectResultsCount(results, 1)
}

type httpInteractshRequest struct{}

// Execute executes a test case and returns an error if occurred
func (h *httpInteractshRequest) Execute(filePath string) error {
	router := httprouter.New()
	router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		value := r.Header.Get("url")
		if value != "" {
			if resp, _ := retryablehttp.DefaultClient().Get(value); resp != nil {
				_ = resp.Body.Close()
			}
		}
	})
	ts := httptest.NewServer(router)
	defer ts.Close()

	results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
	if err != nil {
		return err
	}

	return expectResultsCount(results, 1, 2)
}

type httpInteractshWithPayloadsRequest struct{}

// Execute executes a test case and returns an error if occurred
func (h *httpInteractshWithPayloadsRequest) Execute(filePath string) error {
	router := httprouter.New()
	router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		value := r.Header.Get("url")
		if value != "" {
			if resp, _ := retryablehttp.DefaultClient().Get(value); resp != nil {
				_ = resp.Body.Close()
			}
		}
	})
	ts := httptest.NewServer(router)
	defer ts.Close()

	results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
	if err != nil {
		return err
	}

	return expectResultsCount(results, 1, 3)
}

type httpDefaultMatcherCondition struct{}

// Execute executes a test case and returns an error if occurred
func (d *httpDefaultMatcherCondition) Execute(filePath string) error {
	// to simulate matcher-condition `or`
	// - template should be run twice and vulnerable server should send response that fits for that specific run
	router := httprouter.New()
	var routerErr error
	// Server endpoint where only interactsh matcher is successful and status code is not 200
	router.GET("/interactsh/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		value := r.URL.Query().Get("url")
		if value != "" {
			if _, err := retryablehttp.DefaultClient().Get("https://" + value); err != nil {
				routerErr = err
			}
		}
		w.WriteHeader(http.StatusNotFound)
	})
	// Server endpoint where url is not probed but sends a 200 status code
	router.GET("/status/", func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
		w.WriteHeader(http.StatusOK)
	})
	ts := httptest.NewServer(router)
	defer ts.Close()

	results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL+"/status", debug)
	if err != nil {
		return err
	}
	if err := expectResultsCount(results, 1); err != nil {
		return err
	}

	results, err = testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL+"/interactsh", debug)
	if err != nil {
		return err
	}
	if routerErr != nil {
		return errkit.Wrap(routerErr, "failed to send http request to interactsh server")
	}
	if err := expectResultsCount(results, 1); err != nil {
		return err
	}
	return nil
}

type httpInteractshStopAtFirstMatchRequest struct{}

// Execute executes a test case and returns an error if occurred
func (h *httpInteractshStopAtFirstMatchRequest) Execute(filePath string) error {
	router := httprouter.New()
	router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		value := r.Header.Get("url")
		if value != "" {
			if resp, _ := retryablehttp.DefaultClient().Get(value); resp != nil {
				_ = resp.Body.Close()
			}
		}
	})
	ts := httptest.NewServer(router)
	defer ts.Close()

	results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
	if err != nil {
		return err
	}
	// polling is asynchronous, so the interactions may be retrieved after the first request
	return expectResultsCount(results, 1)
}

type httpGetHeaders struct{}

// Execute executes a test case and returns an error if occurred
func (h *httpGetHeaders) Execute(filePath string) error {
	router := httprouter.New()
	router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		if strings.EqualFold(r.Header.Get("test"), "nuclei") {
			_, _ = fmt.Fprintf(w, "This is test headers matcher text")
		}
	})
	ts := httptest.NewServer(router)
	defer ts.Close()

	results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
	if err != nil {
		return err
	}

	return expectResultsCount(results, 1)
}

type httpGetQueryString struct{}

// Execute executes a test case and returns an error if occurred
func (h *httpGetQueryString) Execute(filePath string) error {
	router := httprouter.New()
	router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		if strings.EqualFold(r.URL.Query().Get("test"), "nuclei") {
			_, _ = fmt.Fprintf(w, "This is test querystring matcher text")
		}
	})
	ts := httptest.NewServer(router)
	defer ts.Close()

	results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
	if err != nil {
		return err
	}

	return expectResultsCount(results, 1)
}

type httpGetRedirects struct{}

// Execute executes a test case and returns an error if occurred
func (h *httpGetRedirects) Execute(filePath string) error {
	router := httprouter.New()
	router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		http.Redirect(w, r, "/redirected", http.StatusFound)
	})
	router.GET("/redirected", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		_, _ = fmt.Fprintf(w, "This is test redirects matcher text")
	})
	ts := httptest.NewServer(router)
	defer ts.Close()

	results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
	if err != nil {
		return err
	}

	return expectResultsCount(results, 1)
}

type httpGetHostRedirects struct{}

// Execute executes a test case and returns an error if occurred
func (h *httpGetHostRedirects) Execute(filePath string) error {
	router := httprouter.New()
	router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		http.Redirect(w, r, "/redirected1", http.StatusFound)
	})
	router.GET("/redirected1", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		http.Redirect(w, r, "redirected2", http.StatusFound)
	})
	router.GET("/redirected2", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		http.Redirect(w, r, "/redirected3", http.StatusFound)
	})
	router.GET("/redirected3", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		http.Redirect(w, r, "https://scanme.sh", http.StatusTemporaryRedirect)
	})
	ts := httptest.NewServer(router)
	defer ts.Close()

	results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
	if err != nil {
		return err
	}

	return expectResultsCount(results, 1)
}

type httpDisableRedirects struct{}

// Execute executes a test case and returns an error if occurred
func (h *httpDisableRedirects) Execute(filePath string) error {
	router := httprouter.New()
	router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		http.Redirect(w, r, "/redirected", http.StatusMovedPermanently)
	})
	router.GET("/redirected", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		_, _ = fmt.Fprintf(w, "This is test redirects matcher text")
	})
	ts := httptest.NewServer(router)
	defer ts.Close()

	results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug, "-dr")
	if err != nil {
		return err
	}

	return expectResultsCount(results, 0)
}

type httpGet struct{}

// Execute executes a test case and returns an error if occurred
func (h *httpGet) Execute(filePath string) error {
	router := httprouter.New()
	router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		_, _ = fmt.Fprintf(w, "This is test matcher text")
	})
	ts := httptest.NewServer(router)
	defer ts.Close()

	results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
	if err != nil {
		return err
	}

	return expectResultsCount(results, 1)
}

type httpDSLVariable struct{}

// Execute executes a test case and returns an error if occurred
func (h *httpDSLVariable) Execute(filePath string) error {
	router := httprouter.New()
	router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		_, _ = fmt.Fprintf(w, "This is test matcher text")
	})
	ts := httptest.NewServer(router)
	defer ts.Close()

	results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
	if err != nil {
		return err
	}

	return expectResultsCount(results, 5)
}

type httpDSLFunctions struct{}

func (h *httpDSLFunctions) Execute(filePath string) error {
	router := httprouter.New()
	router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		request, err := httputil.DumpRequest(r, true)
		if err != nil {
			_, _ = fmt.Fprint(w, err.Error())
		} else {
			_, _ = fmt.Fprint(w, string(request))
		}
	})
	ts := httptest.NewServer(router)
	defer ts.Close()

	results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug, "-nc")
	if err != nil {
		return err
	}

	if err := expectResultsCount(results, 1); err != nil {
		return err
	}

	// get result part
	resultPart, err := stringsutil.After(results[0], ts.URL)
	if err != nil {
		return err
	}

	// remove additional characters till the first valid result and ignore last ] which doesn't alter the total count
	resultPart = stringsutil.TrimPrefixAny(resultPart, "/", " ", "[")

	extracted := strings.Split(resultPart, ",")
	numberOfDslFunctions := 88
	if len(extracted) != numberOfDslFunctions {
		return errors.New("incorrect number of results")
	}

	for _, header := range extracted {
		header = strings.Trim(header, `"`)
		parts := strings.Split(header, ": ")
		index, err := strconv.Atoi(parts[0])
		if err != nil {
			return err
		}
		if index < 0 || index > numberOfDslFunctions {
			return fmt.Errorf("incorrect header index found: %d", index)
		}
		if strings.TrimSpace(parts[1]) == "" {
			return fmt.Errorf("the DSL expression with index %d was not evaluated correctly", index)
		}
	}

	return nil
}

type httpPostBody struct{}

// Execute executes a test case and returns an error if occurred
func (h *httpPostBody) Execute(filePath string) error {
	router := httprouter.New()
	var routerErr error

	router.POST("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		if err := r.ParseForm(); err != nil {
			routerErr = err
			return
		}
		if strings.EqualFold(r.Form.Get("username"), "test") && strings.EqualFold(r.Form.Get("password"), "nuclei") {
			_, _ = fmt.Fprintf(w, "This is test post-body matcher text")
		}
	})
	ts := httptest.NewServer(router)
	defer ts.Close()

	results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
	if err != nil {
		return err
	}
	if routerErr != nil {
		return routerErr
	}

	return expectResultsCount(results, 1)
}

type httpPostJSONBody struct{}

// Execute executes a test case and returns an error if occurred
func (h *httpPostJSONBody) Execute(filePath string) error {
	router := httprouter.New()
	var routerErr error

	router.POST("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		type doc struct {
			Username string `json:"username"`
			Password string `json:"password"`
		}
		obj := &doc{}
		if err := json.NewDecoder(r.Body).Decode(obj); err != nil {
			routerErr = err
			return
		}
		if strings.EqualFold(obj.Username, "test") && strings.EqualFold(obj.Password, "nuclei") {
			_, _ = fmt.Fprintf(w, "This is test post-json-body matcher text")
		}
	})
	ts := httptest.NewServer(router)
	defer ts.Close()

	results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
	if err != nil {
		return err
	}
	if routerErr != nil {
		return routerErr
	}

	return expectResultsCount(results, 1)
}

type httpPostMultipartBody struct{}

// Execute executes a test case and returns an error if occurred
func (h *httpPostMultipartBody) Execute(filePath string) error {
	router := httprouter.New()
	var routerErr error

	router.POST("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		if err := r.ParseMultipartForm(unitutils.Mega); err != nil {
			routerErr = err
			return
		}
		password, ok := r.MultipartForm.Value["password"]
		if !ok || len(password) != 1 {
			routerErr = errors.New("no password in request")
			return
		}
		file := r.MultipartForm.File["username"]
		if len(file) != 1 {
			routerErr = errors.New("no file in request")
			return
		}
		if strings.EqualFold(password[0], "nuclei") && strings.EqualFold(file[0].Filename, "username") {
			_, _ = fmt.Fprintf(w, "This is test post-multipart matcher text")
		}
	})
	ts := httptest.NewServer(router)
	defer ts.Close()

	results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
	if err != nil {
		return err
	}
	if routerErr != nil {
		return routerErr
	}

	return expectResultsCount(results, 1)
}

type httpRawDynamicExtractor struct{}

// Execute executes a test case and returns an error if occurred
func (h *httpRawDynamicExtractor) Execute(filePath string) error {
	router := httprouter.New()
	var routerErr error

	router.POST("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		if err := r.ParseForm(); err != nil {
			routerErr = err
			return
		}
		if strings.EqualFold(r.Form.Get("testing"), "parameter") {
			_, _ = fmt.Fprintf(w, "Token: 'nuclei'")
		}
	})
	router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		if strings.EqualFold(r.URL.Query().Get("username"), "nuclei") {
			_, _ = fmt.Fprintf(w, "Test is test-dynamic-extractor-raw matcher text")
		}
	})
	ts := httptest.NewServer(router)
	defer ts.Close()

	results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
	if err != nil {
		return err
	}
	if routerErr != nil {
		return routerErr
	}

	return expectResultsCount(results, 1)
}

type httpRawGetQuery struct{}

// Execute executes a test case and returns an error if occurred
func (h *httpRawGetQuery) Execute(filePath string) error {
	router := httprouter.New()
	router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		if strings.EqualFold(r.URL.Query().Get("test"), "nuclei") {
			_, _ = fmt.Fprintf(w, "Test is test raw-get-query-matcher text")
		}
	})
	ts := httptest.NewServer(router)
	defer ts.Close()

	results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
	if err != nil {
		return err
	}

	return expectResultsCount(results, 1)
}

type httpRawGet struct{}

// Execute executes a test case and returns an error if occurred
func (h *httpRawGet) Execute(filePath string) error {
	router := httprouter.New()
	router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		_, _ = fmt.Fprintf(w, "Test is test raw-get-matcher text")
	})
	ts := httptest.NewServer(router)
	defer ts.Close()

	results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
	if err != nil {
		return err
	}

	return expectResultsCount(results, 1)
}

type httpRawWithParams struct{}

// Execute executes a test case and returns an error if occurred
func (h *httpRawWithParams) Execute(filePath string) error {
	router := httprouter.New()
	var errx error
	router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		params := r.URL.Query()
		// we intentionally use params["test"] instead of params.Get("test") to test the case where
		// there are multiple parameters with the same name
		if !reflect.DeepEqual(params["key1"], []string{"value1"}) {
			errx = errkit.Append(errx, errkit.New("key1 not found in params", "expected", []string{"value1"}, "got", params["key1"]))
		}
		if !reflect.DeepEqual(params["key2"], []string{"value2"}) {
			errx = errkit.Append(errx, errkit.New("key2 not found in params", "expected", []string{"value2"}, "got", params["key2"]))
		}
		_, _ = fmt.Fprintf(w, "Test is test raw-params-matcher text")
	})
	ts := httptest.NewServer(router)
	defer ts.Close()

	results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL+"/?key1=value1", debug)
	if err != nil {
		return err
	}
	if errx != nil {
		return err
	}
	return expectResultsCount(results, 1)
}

type httpRawPathTrailingSlash struct{}

func (h *httpRawPathTrailingSlash) Execute(filepath string) error {
	router := httprouter.New()
	var routerErr error

	router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		if r.RequestURI != "/test/..;/..;/" {
			routerErr = fmt.Errorf("expected path /test/..;/..;/ but got %v", r.RequestURI)
			return
		}
	})
	ts := httptest.NewServer(router)
	defer ts.Close()

	_, err := testutils.RunNucleiTemplateAndGetResults(filepath, ts.URL, debug)
	if err != nil {
		return err
	}
	if routerErr != nil {
		return routerErr
	}
	return nil
}

type httpRawPayload struct{}

// Execute executes a test case and returns an error if occurred
func (h *httpRawPayload) Execute(filePath string) error {
	router := httprouter.New()
	var routerErr error

	router.POST("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		if err := r.ParseForm(); err != nil {
			routerErr = err
			return
		}
		if !strings.EqualFold(r.Header.Get("another_header"), "bnVjbGVp") && !strings.EqualFold(r.Header.Get("another_header"), "Z3Vlc3Q=") {
			return
		}
		if strings.EqualFold(r.Form.Get("username"), "test") && (strings.EqualFold(r.Form.Get("password"), "nuclei") || strings.EqualFold(r.Form.Get("password"), "guest")) {
			_, _ = fmt.Fprintf(w, "Test is raw-payload matcher text")
		}
	})
	ts := httptest.NewServer(router)
	defer ts.Close()

	results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
	if err != nil {
		return err
	}
	if routerErr != nil {
		return routerErr
	}

	return expectResultsCount(results, 2)
}

type httpRawPostBody struct{}

// Execute executes a test case and returns an error if occurred
func (h *httpRawPostBody) Execute(filePath string) error {
	router := httprouter.New()
	var routerErr error

	router.POST("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		if err := r.ParseForm(); err != nil {
			routerErr = err
			return
		}
		if strings.EqualFold(r.Form.Get("username"), "test") && strings.EqualFold(r.Form.Get("password"), "nuclei") {
			_, _ = fmt.Fprintf(w, "Test is test raw-post-body-matcher text")
		}
	})
	ts := httptest.NewServer(router)
	defer ts.Close()

	results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
	if err != nil {
		return err
	}
	if routerErr != nil {
		return routerErr
	}

	return expectResultsCount(results, 1)
}

type httpRawUnsafePath struct{}

func (h *httpRawUnsafePath) Execute(filepath string) error {
	// testing unsafe paths using router feedback is not possible cause they are `unsafe urls`
	// hence it is done by parsing and matching paths from nuclei output with `-debug-req` flag
	// read template files
	bin, err := os.ReadFile(fixturePath(filepath))
	if err != nil {
		return err
	}

	// Instead of storing expected `paths` in code it is stored in
	// `reference` section of template
	type template struct {
		Info struct {
			Reference []string `yaml:"reference"`
		}
	}
	var tpl template
	if err = yaml.Unmarshal(bin, &tpl); err != nil {
		return err
	}
	// expected relative paths
	expected := []string{}
	expected = append(expected, tpl.Info.Reference...)
	if len(expected) == 0 {
		return fmt.Errorf("something went wrong with %v template", filepath)
	}

	results, err := testutils.RunNucleiBinaryAndGetCombinedOutput(debug, []string{"-t", filepath, "-u", "scanme.sh", "-debug-req"})
	if err != nil {
		return err
	}

	actual := []string{}
	for _, v := range strings.Split(results, "\n") {
		if strings.Contains(v, "GET") {
			parts := strings.Fields(v)
			if len(parts) == 3 {
				actual = append(actual, parts[1])
			}
		}
	}

	if !reflect.DeepEqual(expected, actual) {
		return fmt.Errorf("%8v: %v\n%-8v: %v", "expected", expected, "actual", actual)
	}
	return nil
}

type httpPaths struct{}

func (h *httpPaths) Execute(filepath string) error {
	// covers testcases similar to httpRawUnsafePath but when `unsafe:false`
	bin, err := os.ReadFile(fixturePath(filepath))
	if err != nil {
		return err
	}

	// Instead of storing expected `paths` in code it is stored in
	// `reference` section of template
	type template struct {
		Info struct {
			Reference []string `yaml:"reference"`
		}
	}
	var tpl template
	if err = yaml.Unmarshal(bin, &tpl); err != nil {
		return err
	}
	// expected relative paths
	expected := []string{}
	expected = append(expected, tpl.Info.Reference...)
	if len(expected) == 0 {
		return fmt.Errorf("something went wrong with %v template", filepath)
	}

	results, err := testutils.RunNucleiBinaryAndGetCombinedOutput(debug, []string{"-t", filepath, "-u", "scanme.sh", "-debug-req"})
	if err != nil {
		return err
	}

	actual := []string{}
	for _, v := range strings.Split(results, "\n") {
		if strings.Contains(v, "GET") {
			parts := strings.Fields(v)
			if len(parts) == 3 {
				actual = append(actual, parts[1])
			}
		}
	}

	if len(expected) > len(actual) {
		actualValuesIndex := max(len(actual)-1, 0)
		return fmt.Errorf("missing values : %v", expected[actualValuesIndex:])
	} else if len(expected) < len(actual) {
		return fmt.Errorf("unexpected values : %v", actual[len(expected)-1:])
	} else {
		if !reflect.DeepEqual(expected, actual) {
			return fmt.Errorf("expected: %v\n\nactual: %v", expected, actual)
		}
	}
	return nil
}

type httpRawCookieReuse struct{}

// Execute executes a test case and returns an error if occurred
func (h *httpRawCookieReuse) Execute(filePath string) error {
	router := httprouter.New()
	var routerErr error

	router.POST("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		if err := r.ParseForm(); err != nil {
			routerErr = err
			return
		}
		if strings.EqualFold(r.Form.Get("testing"), "parameter") {
			http.SetCookie(w, &http.Cookie{Name: "nuclei", Value: "test"})
		}
	})
	router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		if err := r.ParseForm(); err != nil {
			routerErr = err
			return
		}
		cookie, err := r.Cookie("nuclei")
		if err != nil {
			routerErr = err
			return
		}

		if strings.EqualFold(cookie.Value, "test") {
			_, _ = fmt.Fprintf(w, "Test is test-cookie-reuse matcher text")
		}
	})
	ts := httptest.NewServer(router)
	defer ts.Close()

	results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
	if err != nil {
		return err
	}
	if routerErr != nil {
		return routerErr
	}

	return expectResultsCount(results, 1)
}

// TODO: excluded due to parsing errors with console
// type httpRawUnsafeRequest struct{
// Execute executes a test case and returns an error if occurred
// func (h *httpRawUnsafeRequest) Execute(filePath string) error {
// 	var routerErr error
//
// 	ts := testutils.NewTCPServer(nil, defaultStaticPort, func(conn net.Conn) {
// 		defer conn.Close()
// 		_, _ = conn.Write([]byte("protocols/http/1.1 200 OK\r\nContent-Length: 36\r\nContent-Type: text/plain; charset=utf-8\r\n\r\nThis is test raw-unsafe-matcher test"))
// 	})
// 	defer ts.Close()
//
// 	results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "http://"+ts.URL, debug)
// 	if err != nil {
// 		return err
// 	}
// 	if routerErr != nil {
// 		return routerErr
// 	}
//
// 	return expectResultsCount(results, 1)
// }

type httpRequestCondition struct{}

// Execute executes a test case and returns an error if occurred
func (h *httpRequestCondition) Execute(filePath string) error {
	router := httprouter.New()

	router.GET("/200", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		w.WriteHeader(http.StatusOK)
	})
	router.GET("/400", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		w.WriteHeader(http.StatusBadRequest)
	})
	ts := httptest.NewServer(router)
	defer ts.Close()

	results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
	if err != nil {
		return err
	}

	return expectResultsCount(results, 1)
}

type httpRequestSelfContained struct{}

// Execute executes a test case and returns an error if occurred
func (h *httpRequestSelfContained) Execute(filePath string) error {
	router := httprouter.New()
	router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		_, _ = w.Write([]byte("This is self-contained response"))
	})
	server := httptest.NewServer(router)
	defer server.Close()

	templatePath, err := tempFixturePath(filePath, map[string]string{
		"http://127.0.0.1:5431": server.URL,
		"127.0.0.1:5431":        strings.TrimPrefix(server.URL, "http://"),
	})
	if err != nil {
		return err
	}
	results, err := testutils.RunNucleiTemplateAndGetResults(templatePath, "", debug, "-esc")
	if err != nil {
		return err
	}

	return expectResultsCount(results, 1)
}

// testcase to check duplicated values in params
type httpRequestSelfContainedWithParams struct{}

// Execute executes a test case and returns an error if occurred
func (h *httpRequestSelfContainedWithParams) Execute(filePath string) error {
	router := httprouter.New()
	var errx error
	router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		params := r.URL.Query()
		// we intentionally use params["test"] instead of params.Get("test") to test the case where
		// there are multiple parameters with the same name
		if !reflect.DeepEqual(params["something"], []string{"here"}) {
			errx = errkit.Append(errx, errkit.New("something not found in params", "expected", []string{"here"}, "got", params["something"]))
		}
		if !reflect.DeepEqual(params["key"], []string{"value"}) {
			errx = errkit.Append(errx, errkit.New("key not found in params", "expected", []string{"value"}, "got", params["key"]))
		}
		_, _ = w.Write([]byte("This is self-contained response"))
	})
	server := httptest.NewServer(router)
	defer server.Close()

	templatePath, err := tempFixturePath(filePath, map[string]string{
		"http://127.0.0.1:5431": server.URL,
		"127.0.0.1:5431":        strings.TrimPrefix(server.URL, "http://"),
	})
	if err != nil {
		return err
	}
	results, err := testutils.RunNucleiTemplateAndGetResults(templatePath, "", debug, "-esc")
	if err != nil {
		return err
	}
	if errx != nil {
		return errx
	}

	return expectResultsCount(results, 1)
}

type httpRequestSelfContainedFileInput struct{}

func (h *httpRequestSelfContainedFileInput) Execute(filePath string) error {
	router := httprouter.New()
	gotReqToEndpoints := []string{}
	router.GET("/one", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		gotReqToEndpoints = append(gotReqToEndpoints, "/one")
		_, _ = w.Write([]byte("This is self-contained response"))
	})
	router.GET("/two", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		gotReqToEndpoints = append(gotReqToEndpoints, "/two")
		_, _ = w.Write([]byte("This is self-contained response"))
	})
	server := httptest.NewServer(router)
	defer server.Close()

	// create temp file
	FileLoc, err := os.CreateTemp("", "self-contained-payload-*.txt")
	if err != nil {
		return errkit.Wrap(err, "failed to create temp file")
	}
	if _, err := FileLoc.Write([]byte("one\ntwo\n")); err != nil {
		return errkit.Wrap(err, "failed to write payload to temp file")
	}
	defer func() {
		_ = FileLoc.Close()
	}()

	templatePath, err := tempFixturePath(filePath, map[string]string{
		"http://127.0.0.1:5431": server.URL,
		"127.0.0.1:5431":        strings.TrimPrefix(server.URL, "http://"),
	})
	if err != nil {
		return err
	}
	results, err := testutils.RunNucleiTemplateAndGetResults(templatePath, "", debug, "-V", "test="+FileLoc.Name(), "-esc")
	if err != nil {
		return err
	}

	if err := expectResultsCount(results, 4); err != nil {
		return err
	}

	if !sliceutil.ElementsMatch(gotReqToEndpoints, []string{"/one", "/two", "/one", "/two"}) {
		return errkit.New("expected requests to be sent to `/one` and `/two` endpoints but were sent to `%v`", gotReqToEndpoints, "filePath", filePath)
	}
	return nil
}

func tempFixturePath(relativePath string, replacements map[string]string) (string, error) {
	data, err := os.ReadFile(fixturePath(relativePath))
	if err != nil {
		return "", fmt.Errorf("failed to read fixture %s: %w", relativePath, err)
	}
	content := string(data)
	for oldValue, newValue := range replacements {
		content = strings.ReplaceAll(content, oldValue, newValue)
	}
	tempDir, err := os.MkdirTemp(suite.tempDir, "http-self-contained-*")
	if err != nil {
		return "", fmt.Errorf("failed to create temp fixture dir: %w", err)
	}
	tempPath := filepath.Join(tempDir, filepath.Base(relativePath))
	if err := os.WriteFile(tempPath, []byte(content), 0600); err != nil {
		return "", fmt.Errorf("failed to write temp fixture %s: %w", relativePath, err)
	}
	return tempPath, nil
}

type httpGetCaseInsensitive struct{}

// Execute executes a test case and returns an error if occurred
func (h *httpGetCaseInsensitive) Execute(filePath string) error {
	router := httprouter.New()
	router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		_, _ = fmt.Fprintf(w, "THIS IS TEST MATCHER TEXT")
	})
	ts := httptest.NewServer(router)
	defer ts.Close()

	results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
	if err != nil {
		return err
	}

	return expectResultsCount(results, 1)
}

type httpGetCaseInsensitiveCluster struct{}

// Execute executes a test case and returns an error if occurred
func (h *httpGetCaseInsensitiveCluster) Execute(filesPath string) error {
	router := httprouter.New()
	router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		_, _ = fmt.Fprintf(w, "This is test matcher text")
	})
	ts := httptest.NewServer(router)
	defer ts.Close()

	files := strings.Split(filesPath, ",")

	results, err := testutils.RunNucleiTemplateAndGetResults(files[0], ts.URL, debug, "-t", files[1])
	if err != nil {
		return err
	}

	return expectResultsCount(results, 2)
}

type httpGetRedirectsChainHeaders struct{}

// Execute executes a test case and returns an error if occurred
func (h *httpGetRedirectsChainHeaders) Execute(filePath string) error {
	router := httprouter.New()
	router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		http.Redirect(w, r, "/redirected", http.StatusFound)
	})
	router.GET("/redirected", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		w.Header().Set("Secret", "TestRedirectHeaderMatch")
		http.Redirect(w, r, "/final", http.StatusFound)
	})
	router.GET("/final", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		_, _ = w.Write([]byte("ok"))
	})
	ts := httptest.NewServer(router)
	defer ts.Close()

	results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
	if err != nil {
		return err
	}

	return expectResultsCount(results, 1)
}

type httpRaceSimple struct{}

// Execute executes a test case and returns an error if occurred
func (h *httpRaceSimple) Execute(filePath string) error {
	router := httprouter.New()
	router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		w.WriteHeader(http.StatusOK)
	})
	ts := httptest.NewServer(router)
	defer ts.Close()

	results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
	if err != nil {
		return err
	}
	return expectResultsCount(results, 10)
}

type httpRaceMultiple struct{}

// Execute executes a test case and returns an error if occurred
func (h *httpRaceMultiple) Execute(filePath string) error {
	router := httprouter.New()
	router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		w.WriteHeader(http.StatusOK)
	})
	ts := httptest.NewServer(router)
	defer ts.Close()

	results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
	if err != nil {
		return err
	}
	return expectResultsCount(results, 5)
}

type httpRaceWithDelay struct{}

// Execute executes a test case and returns an error if occurred
func (h *httpRaceWithDelay) Execute(filePath string) error {
	var requestTimes []time.Time
	var mu sync.Mutex

	router := httprouter.New()
	router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		mu.Lock()
		requestTimes = append(requestTimes, time.Now())
		mu.Unlock()
		time.Sleep(2 * time.Second)
		w.WriteHeader(http.StatusOK)
	})
	ts := httptest.NewServer(router)
	defer ts.Close()

	results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
	if err != nil {
		return err
	}
	if err := expectResultsCount(results, 3); err != nil {
		return err
	}

	mu.Lock()
	defer mu.Unlock()
	if len(requestTimes) != 3 {
		return fmt.Errorf("expected 3 requests, got %d", len(requestTimes))
	}

	// Check concurrency of first two requests (should be very close)
	if diff := requestTimes[1].Sub(requestTimes[0]); diff > 500*time.Millisecond {
		return fmt.Errorf("expected first 2 requests to be concurrent, diff: %v", diff)
	}

	// Check delay of third request (should be after ~2s)
	if diff := requestTimes[2].Sub(requestTimes[0]); diff < 1500*time.Millisecond {
		return fmt.Errorf("expected 3rd request to be delayed, diff: %v", diff)
	}

	return nil
}

type httpRaceWithVariables struct{}

// Execute tests that variables and constants are properly resolved in race mode.
func (h *httpRaceWithVariables) Execute(filePath string) error {
	router := httprouter.New()
	router.GET("/race", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		// Echo back the API key header so we can match on it
		_, _ = fmt.Fprint(w, r.Header.Get("X-API-Key"))
	})
	ts := httptest.NewServer(router)
	defer ts.Close()

	results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
	if err != nil {
		return err
	}

	return expectResultsCount(results, 3)
}

type httpStopAtFirstMatch struct{}

// Execute executes a test case and returns an error if occurred
func (h *httpStopAtFirstMatch) Execute(filePath string) error {
	router := httprouter.New()
	router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		_, _ = fmt.Fprintf(w, "This is test")
	})
	ts := httptest.NewServer(router)
	defer ts.Close()

	results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
	if err != nil {
		return err
	}

	return expectResultsCount(results, 1)
}

type httpStopAtFirstMatchWithExtractors struct{}

// Execute executes a test case and returns an error if occurred
func (h *httpStopAtFirstMatchWithExtractors) Execute(filePath string) error {
	router := httprouter.New()
	router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		_, _ = fmt.Fprintf(w, "This is test")
	})
	ts := httptest.NewServer(router)
	defer ts.Close()

	results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
	if err != nil {
		return err
	}

	return expectResultsCount(results, 2)
}

type httpVariables struct{}

// Execute executes a test case and returns an error if occurred
func (h *httpVariables) Execute(filePath string) error {
	router := httprouter.New()
	router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		_, _ = fmt.Fprintf(w, "%s\n%s\n%s", r.Header.Get("Test"), r.Header.Get("Another"), r.Header.Get("Email"))
	})
	ts := httptest.NewServer(router)
	defer ts.Close()

	results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
	if err != nil {
		return err
	}
	if err := expectResultsCount(results, 1); err != nil {
		return err
	}

	// variable override that does not have any match
	// to make sure the variable override is working
	results, err = testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug, "-var", "a1=failed")
	if err != nil {
		return err
	}

	return expectResultsCount(results, 0)
}

type httpVariablesThreadsPrevious struct{}

// Execute tests that variables can reference data extracted from previous requests
// when using threads mode (parallel execution).
func (h *httpVariablesThreadsPrevious) Execute(filePath string) error {
	router := httprouter.New()
	router.GET("/login", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		_, _ = fmt.Fprint(w, "token=secret123")
	})
	router.GET("/api", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		// Echo back the Authorization header so we can match on it
		_, _ = fmt.Fprint(w, r.Header.Get("Authorization"))
	})
	ts := httptest.NewServer(router)
	defer ts.Close()

	results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
	if err != nil {
		return err
	}

	return expectResultsCount(results, 1)
}

type httpVariableDSLFunction struct{}

// Execute executes a test case and returns an error if occurred
func (h *httpVariableDSLFunction) Execute(filePath string) error {
	results, err := testutils.RunNucleiBinaryAndGetCombinedOutput(debug, []string{"-t", filePath, "-u", "https://scanme.sh", "-debug-req"})
	if err != nil {
		return err
	}

	actual := []string{}
	for _, v := range strings.Split(results, "\n") {
		if strings.Contains(v, "GET") {
			parts := strings.Fields(v)
			if len(parts) == 3 {
				actual = append(actual, parts[1])
			}
		}
	}
	if len(actual) == 2 && actual[0] == actual[1] {
		return nil
	}

	return fmt.Errorf("expected 2 requests with same URL, got %v", actual)
}

type customCLISNI struct{}

// Execute executes a test case and returns an error if occurred
func (h *customCLISNI) Execute(filePath string) error {
	router := httprouter.New()
	router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		if r.TLS.ServerName == "test" {
			_, _ = w.Write([]byte("test-ok"))
		} else {
			_, _ = w.Write([]byte("test-ko"))
		}
	})
	ts := httptest.NewTLSServer(router)
	defer ts.Close()

	results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug, "-sni", "test")
	if err != nil {
		return err
	}
	return expectResultsCount(results, 1)
}

type httpSniAnnotation struct{}

// Execute executes a test case and returns an error if occurred
func (h *httpSniAnnotation) Execute(filePath string) error {
	router := httprouter.New()
	router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		if r.TLS.ServerName == "test" {
			_, _ = w.Write([]byte("test-ok"))
		} else {
			_, _ = w.Write([]byte("test-ko"))
		}
	})
	ts := httptest.NewTLSServer(router)
	defer ts.Close()

	results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
	if err != nil {
		return err
	}
	return expectResultsCount(results, 1)
}

type httpRedirectMatchURL struct{}

// Execute executes a test case and returns an error if occurred
func (h *httpRedirectMatchURL) Execute(filePath string) error {
	router := httprouter.New()
	router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		http.Redirect(w, r, "/redirected", http.StatusFound)
		_, _ = w.Write([]byte("This is test redirects matcher text"))
	})
	router.GET("/redirected", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		_, _ = fmt.Fprintf(w, "This is test redirects matcher text")
	})
	ts := httptest.NewServer(router)
	defer ts.Close()

	results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug, "-no-meta")
	if err != nil {
		return err
	}

	if err := expectResultsCount(results, 1); err != nil {
		return err
	}
	if results[0] != fmt.Sprintf("%s/redirected", ts.URL) {
		return fmt.Errorf("mismatched url found: %s", results[0])
	}
	return nil
}

type customCLISNIUnsafe struct{}

// Execute executes a test case and returns an error if occurred
func (h *customCLISNIUnsafe) Execute(filePath string) error {
	router := httprouter.New()
	router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		if r.TLS.ServerName == "test" {
			_, _ = w.Write([]byte("test-ok"))
		} else {
			_, _ = w.Write([]byte("test-ko"))
		}
	})
	ts := httptest.NewTLSServer(router)
	defer ts.Close()

	results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug, "-sni", "test")
	if err != nil {
		return err
	}
	return expectResultsCount(results, 1)
}

type annotationTimeout struct{}

// Execute executes a test case and returns an error if occurred
func (h *annotationTimeout) Execute(filePath string) error {
	router := httprouter.New()
	router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		time.Sleep(4 * time.Second)
		_, _ = fmt.Fprintf(w, "This is test matcher text")
	})
	ts := httptest.NewTLSServer(router)
	defer ts.Close()

	results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug, "-timeout", "1")
	if err != nil {
		return err
	}
	return expectResultsCount(results, 1)
}

type customAttackType struct{}

// Execute executes a test case and returns an error if occurred
func (h *customAttackType) Execute(filePath string) error {
	router := httprouter.New()
	got := []string{}
	router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		got = append(got, r.URL.RawQuery)
		_, _ = fmt.Fprintf(w, "This is test custom payload")
	})
	ts := httptest.NewTLSServer(router)
	defer ts.Close()

	_, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug, "-attack-type", "clusterbomb")
	if err != nil {
		return err
	}
	return expectResultsCount(got, 4)
}

// Disabled as GH doesn't support ipv6
type scanAllIPS struct{}

// Execute executes a test case and returns an error if occurred
func (h *scanAllIPS) Execute(filePath string) error {
	got, err := testutils.RunNucleiTemplateAndGetResults(filePath, "https://scanme.sh", debug, "-scan-all-ips", "-iv", "4")
	if err != nil {
		return err
	}
	// limiting test to ipv4 (GH doesn't support ipv6)
	return expectResultsCount(got, 1)
}

// ensure that ip|host are handled without http|https scheme
type httpGetWithoutScheme struct{}

// Execute executes a test case and returns an error if occurred
func (h *httpGetWithoutScheme) Execute(filePath string) error {
	got, err := testutils.RunNucleiTemplateAndGetResults(filePath, "scanme.sh", debug)
	if err != nil {
		return err
	}
	return expectResultsCount(got, 1)
}

// content-length in case the response has no header but has a body
type httpCLBodyWithoutHeader struct{}

// Execute executes a test case and returns an error if occurred
func (h *httpCLBodyWithoutHeader) Execute(filePath string) error {
	logutil.DisableDefaultLogger()
	defer logutil.EnableDefaultLogger()

	router := httprouter.New()
	router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		w.Header()["Content-Length"] = []string{"-1"}
		_, _ = fmt.Fprintf(w, "this is a test")
	})
	ts := httptest.NewTLSServer(router)
	defer ts.Close()

	got, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
	if err != nil {
		return err
	}
	return expectResultsCount(got, 1)
}

// content-length in case the response has content-length header and a body
type httpCLBodyWithHeader struct{}

// Execute executes a test case and returns an error if occurred
func (h *httpCLBodyWithHeader) Execute(filePath string) error {
	router := httprouter.New()
	router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		w.Header()["Content-Length"] = []string{"50000"}
		_, _ = fmt.Fprintf(w, "this is a test")
	})
	ts := httptest.NewTLSServer(router)
	defer ts.Close()

	got, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
	if err != nil {
		return err
	}
	return expectResultsCount(got, 1)
}

// constant shouldn't be overwritten by cli var with same name
type ConstantWithCliVar struct{}

// Execute executes a test case and returns an error if occurred
func (h *ConstantWithCliVar) Execute(filePath string) error {
	router := httprouter.New()
	router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		_, _ = fmt.Fprint(w, r.URL.Query().Get("p"))
	})
	ts := httptest.NewTLSServer(router)
	defer ts.Close()

	got, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug, "-V", "test=fromcli")
	if err != nil {
		return err
	}
	return expectResultsCount(got, 1)
}

type constantsWithThreads struct{}

// Execute tests that constants are properly resolved when using threads mode.
func (h *constantsWithThreads) Execute(filePath string) error {
	router := httprouter.New()
	router.GET("/api/:version", func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
		// Echo back the API key header and version so we can match on them
		_, _ = fmt.Fprintf(w, "%s %s", r.Header.Get("X-API-Key"), p.ByName("version"))
	})
	ts := httptest.NewServer(router)
	defer ts.Close()

	results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
	if err != nil {
		return err
	}

	return expectResultsCount(results, 1)
}

type matcherStatusTest struct{}

// Execute executes a test case and returns an error if occurred
func (h *matcherStatusTest) Execute(filePath string) error {
	router := httprouter.New()
	router.GET("/200", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		w.WriteHeader(http.StatusOK)
	})
	ts := httptest.NewServer(router)
	defer ts.Close()

	results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug, "-ms")
	if err != nil {
		return err
	}
	return expectResultsCount(results, 1)
}

// disable path automerge in raw request
type httpDisablePathAutomerge struct{}

// Execute executes a test case and returns an error if occurred
func (h *httpDisablePathAutomerge) Execute(filePath string) error {
	router := httprouter.New()
	router.GET("/api/v1/test", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		_, _ = fmt.Fprint(w, r.URL.Query().Get("id"))
	})
	router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		_, _ = fmt.Fprint(w, "empty path in raw request")
	})

	ts := httptest.NewServer(router)
	defer ts.Close()
	got, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL+"/api/v1/user", debug)
	if err != nil {
		return err
	}
	return expectResultsCount(got, 2)
}

type httpInteractshRequestsWithMCAnd struct{}

func (h *httpInteractshRequestsWithMCAnd) Execute(filePath string) error {
	got, err := testutils.RunNucleiTemplateAndGetResults(filePath, "honey.scanme.sh", debug)
	if err != nil {
		return err
	}
	return expectResultsCount(got, 1)
}

// integration test to check if preprocessor i.e {{randstr}}
// is working correctly
type httpPreprocessor struct{}

// Execute executes a test case and returns an error if occurred
func (h *httpPreprocessor) Execute(filePath string) error {
	router := httprouter.New()
	re := regexp.MustCompile(`[A-Za-z0-9]{25,}`)
	router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		value := r.URL.RequestURI()
		if re.MatchString(value) {
			w.WriteHeader(http.StatusOK)
			_, _ = fmt.Fprint(w, "ok")
		} else {
			w.WriteHeader(http.StatusBadRequest)
			_, _ = fmt.Fprint(w, "not ok")
		}
	})
	ts := httptest.NewServer(router)
	defer ts.Close()

	results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
	if err != nil {
		return err
	}

	return expectResultsCount(results, 1)
}

type httpMultiRequest struct{}

// Execute executes a test case and returns an error if occurred
func (h *httpMultiRequest) Execute(filePath string) error {
	router := httprouter.New()
	router.GET("/ping", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		w.WriteHeader(http.StatusOK)
		_, _ = fmt.Fprint(w, "ping")
	})
	router.GET("/pong", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		w.WriteHeader(http.StatusOK)
		_, _ = fmt.Fprint(w, "pong")
	})
	ts := httptest.NewServer(router)
	defer ts.Close()

	results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
	if err != nil {
		return err
	}

	return expectResultsCount(results, 1)
}

type httpRawPathSingleSlash struct{}

func (h *httpRawPathSingleSlash) Execute(filepath string) error {
	expectedPath := "/index.php"
	results, err := testutils.RunNucleiBinaryAndGetCombinedOutput(debug, []string{"-t", filepath, "-u", "scanme.sh/index.php", "-debug-req"})
	if err != nil {
		return err
	}

	var actual string
	for _, v := range strings.Split(results, "\n") {
		if strings.Contains(v, "GET") {
			parts := strings.Fields(v)
			if len(parts) == 3 {
				actual = parts[1]
			}
		}
	}

	if actual != expectedPath {
		return fmt.Errorf("expected: %v\n\nactual: %v", expectedPath, actual)
	}
	return nil
}

type httpRawUnsafePathSingleSlash struct{}

func (h *httpRawUnsafePathSingleSlash) Execute(filepath string) error {
	expectedPath := "/index.php"
	results, err := testutils.RunNucleiBinaryAndGetCombinedOutput(debug, []string{"-t", filepath, "-u", "scanme.sh/index.php", "-debug-req"})
	if err != nil {
		return err
	}

	var actual string
	for _, v := range strings.Split(results, "\n") {
		if strings.Contains(v, "GET") {
			parts := strings.Fields(v)
			if len(parts) == 3 {
				actual = parts[1]
			}
		}
	}

	if actual != expectedPath {
		return fmt.Errorf("expected: %v\n\nactual: %v", expectedPath, actual)
	}
	return nil
}
