/*
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
:                                                                               :
:   █▀ █ █▀▀   ·   Blazing-fast pentesting suite                                :
:   ▄█ █ █▀    ·   BSD 3-Clause License                                         :
:                                                                               :
:   (c) 2022-2026 vmfunc, xyzeva,                                               :
:                 lunchcat alumni & contributors                                :
:                                                                               :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
*/

/*

   BSD 3-Clause License
   (c) 2022-2026 vmfunc, xyzeva & contributors

*/

package detectors

import (
	"net/http"

	fw "github.com/vmfunc/sif/internal/scan/frameworks"
)

// The detectors here identify the WAF in front of a target. Each keys on a
// header, Set-Cookie name, or block-page string the WAF itself sets, so a page
// that merely names the vendor in prose does not trip it.
func init() {
	fw.Register(&cloudflareWAFDetector{})
	fw.Register(&sucuriDetector{})
	fw.Register(&incapsulaDetector{})
	fw.Register(&bigipASMDetector{})
	fw.Register(&modSecurityDetector{})
}

// cloudflareWAFDetector detects an active Cloudflare WAF or bot challenge via the
// cf-mitigated header and __cf_chl_ tokens, which appear only on an interception
// response (plain proxying is covered by the cf-ray CDN marker).
type cloudflareWAFDetector struct{}

func (d *cloudflareWAFDetector) Name() string { return "Cloudflare WAF" }

func (d *cloudflareWAFDetector) Signatures() []fw.Signature {
	return []fw.Signature{
		{Pattern: "cf-mitigated", Weight: 0.6, HeaderOnly: true},
		{Pattern: "__cf_chl_", Weight: 0.5},
		{Pattern: "Attention Required! | Cloudflare", Weight: 0.4},
	}
}

func (d *cloudflareWAFDetector) Detect(body string, headers http.Header) (float32, string) {
	base := fw.NewBaseDetector(d.Name(), d.Signatures())
	return sigmoidConfidence(base.MatchSignatures(body, headers)), ""
}

// sucuriDetector detects the Sucuri (GoDaddy) cloud WAF via its x-sucuri-id and
// x-sucuri-cache headers, backed by the block-page string.
type sucuriDetector struct{}

func (d *sucuriDetector) Name() string { return "Sucuri WAF" }

func (d *sucuriDetector) Signatures() []fw.Signature {
	return []fw.Signature{
		{Pattern: "x-sucuri-id", Weight: 0.6, HeaderOnly: true},
		{Pattern: "x-sucuri-cache", Weight: 0.4, HeaderOnly: true},
		{Pattern: "Sucuri WebSite Firewall", Weight: 0.5},
	}
}

func (d *sucuriDetector) Detect(body string, headers http.Header) (float32, string) {
	base := fw.NewBaseDetector(d.Name(), d.Signatures())
	return sigmoidConfidence(base.MatchSignatures(body, headers)), ""
}

// incapsulaDetector detects the Imperva Incapsula cloud WAF via its x-iinfo
// header, incap_ses_ Set-Cookie name, and block-page incident string. Each is
// Incapsula-exclusive and clears the threshold on its own.
type incapsulaDetector struct{}

func (d *incapsulaDetector) Name() string { return "Imperva Incapsula" }

func (d *incapsulaDetector) Signatures() []fw.Signature {
	return []fw.Signature{
		{Pattern: "x-iinfo", Weight: 0.6, HeaderOnly: true},
		{Pattern: "incap_ses_", Weight: 0.5, HeaderOnly: true},
		{Pattern: "Incapsula incident ID", Weight: 0.5},
	}
}

func (d *incapsulaDetector) Detect(body string, headers http.Header) (float32, string) {
	base := fw.NewBaseDetector(d.Name(), d.Signatures())
	return sigmoidConfidence(base.MatchSignatures(body, headers)), ""
}

// bigipASMDetector detects the F5 BIG-IP Application Security Manager via its
// block-page rejection sentence and support-id line. Block-page only, so it never
// fires on a passing response.
type bigipASMDetector struct{}

func (d *bigipASMDetector) Name() string { return "F5 BIG-IP ASM" }

func (d *bigipASMDetector) Signatures() []fw.Signature {
	return []fw.Signature{
		{Pattern: "The requested URL was rejected. Please consult with your administrator.", Weight: 0.6},
		{Pattern: "Your support ID is:", Weight: 0.3},
	}
}

func (d *bigipASMDetector) Detect(body string, headers http.Header) (float32, string) {
	base := fw.NewBaseDetector(d.Name(), d.Signatures())
	return sigmoidConfidence(base.MatchSignatures(body, headers)), ""
}

// modSecurityDetector detects ModSecurity via its generated block page (primary)
// and the Mod_Security/NOYB server-token values (supporting, since suppressible).
type modSecurityDetector struct{}

func (d *modSecurityDetector) Name() string { return "ModSecurity" }

func (d *modSecurityDetector) Signatures() []fw.Signature {
	return []fw.Signature{
		{Pattern: "This error was generated by Mod_Security", Weight: 0.6},
		{Pattern: "Mod_Security", Weight: 0.3, HeaderOnly: true},
		{Pattern: "NOYB", Weight: 0.2, HeaderOnly: true},
	}
}

func (d *modSecurityDetector) Detect(body string, headers http.Header) (float32, string) {
	base := fw.NewBaseDetector(d.Name(), d.Signatures())
	return sigmoidConfidence(base.MatchSignatures(body, headers)), ""
}
