nuclei/pkg/protocols/http/request_annotations.go
Ice3man 0b82e8b7aa
feat: added support for context cancellation to engine (#5096)
* feat: added support for context cancellation to engine

* misc

* feat: added contexts everywhere

* misc

* misc

* use granular http timeouts and increase http timeout to 30s using multiplier

* track response header timeout in mhe

* update responseHeaderTimeout to 5sec

* skip failing windows test

---------

Co-authored-by: Tarun Koyalwar <tarun@projectdiscovery.io>
2024-04-25 15:37:56 +05:30

148 lines
4.1 KiB
Go

package http
import (
"context"
"crypto/tls"
"net"
"regexp"
"strings"
"time"
"github.com/projectdiscovery/fastdialer/fastdialer"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/http/httpclientpool"
"github.com/projectdiscovery/retryablehttp-go"
iputil "github.com/projectdiscovery/utils/ip"
stringsutil "github.com/projectdiscovery/utils/strings"
)
var (
// @Host:target overrides the input target with the annotated one (similar to self-contained requests)
reHostAnnotation = regexp.MustCompile(`(?m)^@Host:\s*(.+)\s*$`)
// @tls-sni:target overrides the input target with the annotated one
// special values:
// request.host: takes the value from the host header
// target: overrides with the specific value
reSniAnnotation = regexp.MustCompile(`(?m)^@tls-sni:\s*(.+)\s*$`)
// @timeout:duration overrides the input timeout with a custom duration
reTimeoutAnnotation = regexp.MustCompile(`(?m)^@timeout:\s*(.+)\s*$`)
// @once sets the request to be executed only once for a specific URL
reOnceAnnotation = regexp.MustCompile(`(?m)^@once\s*$`)
)
type flowMark int
const (
Once flowMark = iota
)
// parseFlowAnnotations and override requests flow
func parseFlowAnnotations(rawRequest string) (flowMark, bool) {
var fm flowMark
// parse request for known override annotations
var hasFlowOverride bool
// @once
if reOnceAnnotation.MatchString(rawRequest) {
fm = Once
hasFlowOverride = true
}
return fm, hasFlowOverride
}
type annotationOverrides struct {
request *retryablehttp.Request
cancelFunc context.CancelFunc
interactshURLs []string
}
// parseAnnotations and override requests settings
func (r *Request) parseAnnotations(rawRequest string, request *retryablehttp.Request) (overrides annotationOverrides, modified bool) {
// parse request for known override annotations
// @Host:target
if hosts := reHostAnnotation.FindStringSubmatch(rawRequest); len(hosts) > 0 {
value := strings.TrimSpace(hosts[1])
// handle scheme
switch {
case stringsutil.HasPrefixI(value, "http://"):
request.URL.Scheme = "http"
case stringsutil.HasPrefixI(value, "https://"):
request.URL.Scheme = "https"
}
value = stringsutil.TrimPrefixAny(value, "http://", "https://")
if isHostPort(value) {
request.URL.Host = value
} else {
hostPort := value
port := request.URL.Port()
if port != "" {
hostPort = net.JoinHostPort(hostPort, port)
}
request.URL.Host = hostPort
}
modified = true
}
// @tls-sni:target
if hosts := reSniAnnotation.FindStringSubmatch(rawRequest); len(hosts) > 0 {
value := strings.TrimSpace(hosts[1])
value = stringsutil.TrimPrefixAny(value, "http://", "https://")
var literal bool
switch value {
case "request.host":
value = request.Host
case "interactsh-url":
if interactshURL, err := r.options.Interactsh.NewURLWithData("interactsh-url"); err == nil {
value = interactshURL
}
overrides.interactshURLs = append(overrides.interactshURLs, value)
default:
literal = true
}
ctx := context.WithValue(request.Context(), fastdialer.SniName, value)
request = request.Clone(ctx)
if literal {
request.TLS = &tls.ConnectionState{ServerName: value}
}
modified = true
}
// @timeout:duration
if r.connConfiguration.NoTimeout {
modified = true
var ctx context.Context
if duration := reTimeoutAnnotation.FindStringSubmatch(rawRequest); len(duration) > 0 {
value := strings.TrimSpace(duration[1])
if parsed, err := time.ParseDuration(value); err == nil {
//nolint:govet // cancelled automatically by withTimeout
ctx, overrides.cancelFunc = context.WithTimeout(context.Background(), parsed)
request = request.Clone(ctx)
}
} else {
//nolint:govet // cancelled automatically by withTimeout
ctx, overrides.cancelFunc = context.WithTimeout(context.Background(), httpclientpool.GetHttpTimeout(r.options.Options))
request = request.Clone(ctx)
}
}
overrides.request = request
return
}
func isHostPort(value string) bool {
_, port, err := net.SplitHostPort(value)
if err != nil {
return false
}
if !iputil.IsPort(port) {
return false
}
return true
}