mirror of
https://github.com/phishingclub/phishingclub.git
synced 2026-07-09 21:58:42 +02:00
07c8adaf76
Signed-off-by: Ronni Skansing <rskansing@gmail.com>
364 lines
11 KiB
Go
364 lines
11 KiB
Go
package surf
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"fmt"
|
|
"maps"
|
|
"math/rand"
|
|
"net"
|
|
"net/url"
|
|
"time"
|
|
|
|
"github.com/enetx/g"
|
|
"github.com/enetx/http"
|
|
"github.com/enetx/http/cookiejar"
|
|
"github.com/enetx/http2"
|
|
"golang.org/x/net/publicsuffix"
|
|
)
|
|
|
|
// defaultDialerMW initializes the default network dialer for the surf client.
|
|
// Sets up timeout and keep-alive configuration for TCP connections.
|
|
func defaultDialerMW(client *Client) error {
|
|
client.dialer = &net.Dialer{Timeout: _dialerTimeout, KeepAlive: _TCPKeepAlive}
|
|
return nil
|
|
}
|
|
|
|
// defaultTLSConfigMW initializes the default TLS configuration for the surf client.
|
|
// Configures TLS settings with insecure skip verify enabled by default for flexibility.
|
|
func defaultTLSConfigMW(client *Client) error {
|
|
client.tlsConfig = &tls.Config{InsecureSkipVerify: true}
|
|
return nil
|
|
}
|
|
|
|
// defaultTransportMW initializes the default HTTP transport for the surf client.
|
|
// Configures connection pooling, timeouts, and enables HTTP/2 support by default.
|
|
func defaultTransportMW(client *Client) error {
|
|
transport := http.DefaultTransport.(*http.Transport).Clone()
|
|
transport.DialContext = client.dialer.DialContext
|
|
transport.TLSClientConfig = client.tlsConfig
|
|
transport.MaxIdleConns = _maxIdleConns
|
|
transport.MaxConnsPerHost = _maxConnsPerHost
|
|
transport.MaxIdleConnsPerHost = _maxIdleConnsPerHost
|
|
transport.IdleConnTimeout = _idleConnTimeout
|
|
transport.ForceAttemptHTTP2 = true
|
|
|
|
client.transport = transport
|
|
|
|
return nil
|
|
}
|
|
|
|
// defaultClientMW initializes the default HTTP client for the surf client.
|
|
// Sets up the HTTP client with the configured transport and timeout settings.
|
|
func defaultClientMW(client *Client) error {
|
|
client.cli = &http.Client{Transport: client.transport, Timeout: _clientTimeout}
|
|
return nil
|
|
}
|
|
|
|
// boundaryMW sets a custom boundary function for multipart form data.
|
|
// The boundary function is called to generate unique boundaries for multipart requests.
|
|
func boundaryMW(client *Client, boundary func() g.String) error {
|
|
client.boundary = boundary
|
|
return nil
|
|
}
|
|
|
|
// forseHTTP1MW configures the client to use HTTP/1.1 forcefully.
|
|
// Disables HTTP/2 and forces the client to use only HTTP/1.1 protocol.
|
|
func forseHTTP1MW(client *Client) error {
|
|
transport := client.GetTransport().(*http.Transport)
|
|
transport.Protocols = new(http.Protocols)
|
|
transport.Protocols.SetHTTP1(true)
|
|
return nil
|
|
}
|
|
|
|
// sessionMW configures the client's cookie jar for session management.
|
|
// It initializes a new cookie jar and sets up the TLS configuration
|
|
// to manage client sessions efficiently.
|
|
func sessionMW(client *Client) error {
|
|
client.GetClient().Jar, _ = cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})
|
|
client.GetTLSConfig().ClientSessionCache = tls.NewLRUClientSessionCache(0)
|
|
return nil
|
|
}
|
|
|
|
// disableKeepAliveMW disables the keep-alive setting for the client's transport.
|
|
func disableKeepAliveMW(client *Client) error {
|
|
client.GetTransport().(*http.Transport).DisableKeepAlives = true
|
|
return nil
|
|
}
|
|
|
|
// disableCompressionMW disables compression for the client's transport.
|
|
func disableCompressionMW(client *Client) error {
|
|
client.GetTransport().(*http.Transport).DisableCompression = true
|
|
return nil
|
|
}
|
|
|
|
// interfaceAddrMW configures the client's local network interface address for outbound connections.
|
|
// This allows binding the client to a specific network interface or IP address for dialing.
|
|
// Useful for systems with multiple network interfaces or for controlling which IP address to use.
|
|
func interfaceAddrMW(client *Client, address g.String) error {
|
|
if address != "" {
|
|
ip, err := net.ResolveTCPAddr("tcp", address.Std()+":0")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
client.GetDialer().LocalAddr = ip
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// timeoutMW configures the client's overall request timeout.
|
|
// This sets the maximum duration for entire HTTP requests including connection,
|
|
// request transmission, and response reading.
|
|
func timeoutMW(client *Client, timeout time.Duration) error {
|
|
client.GetClient().Timeout = timeout
|
|
return nil
|
|
}
|
|
|
|
// redirectPolicyMW configures the client's HTTP redirect handling behavior.
|
|
// Sets up redirect policies including maximum redirect count, host-only redirects,
|
|
// header forwarding on redirects, and custom redirect functions.
|
|
func redirectPolicyMW(client *Client) error {
|
|
builder := client.builder
|
|
maxRedirects := _maxRedirects
|
|
|
|
if builder != nil {
|
|
// Use custom redirect function if provided
|
|
if builder.checkRedirect != nil {
|
|
client.GetClient().CheckRedirect = builder.checkRedirect
|
|
return nil
|
|
}
|
|
|
|
// Override default max redirects if specified
|
|
if builder.maxRedirects != 0 {
|
|
maxRedirects = builder.maxRedirects
|
|
}
|
|
}
|
|
|
|
// Set up default redirect policy with configured behavior
|
|
client.GetClient().CheckRedirect = func(req *http.Request, via []*http.Request) error {
|
|
// Stop redirecting if maximum redirect count is exceeded
|
|
if len(via) >= maxRedirects {
|
|
return http.ErrUseLastResponse
|
|
}
|
|
|
|
if builder != nil {
|
|
// Only follow redirects within the same host if configured
|
|
if builder.followOnlyHostRedirects {
|
|
newHost := req.URL.Host
|
|
oldHost := via[0].Host
|
|
|
|
if oldHost == "" {
|
|
oldHost = via[0].URL.Host
|
|
}
|
|
|
|
if newHost != oldHost {
|
|
return http.ErrUseLastResponse
|
|
}
|
|
}
|
|
|
|
// Forward headers from original request to redirect if configured
|
|
if builder.forwardHeadersOnRedirect {
|
|
maps.Copy(req.Header, via[0].Header)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// dnsMW configures a custom DNS server for the client.
|
|
// Sets up the client to use the specified DNS server address for hostname resolution
|
|
// instead of the system's default DNS configuration.
|
|
func dnsMW(client *Client, dns g.String) error {
|
|
if dns.Empty() {
|
|
return nil
|
|
}
|
|
|
|
client.GetDialer().Resolver = &net.Resolver{
|
|
PreferGo: true,
|
|
Dial: func(ctx context.Context, _, _ string) (net.Conn, error) {
|
|
var dialer net.Dialer
|
|
return dialer.DialContext(ctx, "udp", dns.Std())
|
|
},
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// dnsTLSMW configures DNS over TLS (DoT) for the client.
|
|
// Replaces the default DNS resolver with a secure DNS-over-TLS resolver
|
|
// to encrypt DNS queries and protect against DNS manipulation.
|
|
func dnsTLSMW(client *Client, resolver *net.Resolver) error {
|
|
client.GetDialer().Resolver = resolver
|
|
return nil
|
|
}
|
|
|
|
// unixSocketMW configures the client to connect via Unix domain sockets.
|
|
// Replaces the standard TCP connection with Unix socket communication,
|
|
// useful for connecting to local services that expose Unix socket interfaces.
|
|
func unixSocketMW(client *Client, address g.String) error {
|
|
if address.Empty() {
|
|
return nil
|
|
}
|
|
|
|
client.GetTransport().(*http.Transport).DialContext = func(ctx context.Context, _, _ string) (net.Conn, error) {
|
|
return (&net.Dialer{}).DialContext(ctx, "unix", address.Std())
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// proxyMW configures HTTP proxy settings for the client transport.
|
|
// Supports both static proxy configurations (string URLs) and dynamic proxy providers
|
|
// (functions that return proxy URLs for rotation). Handles various proxy types including
|
|
// HTTP, HTTPS, and SOCKS proxies. Skips configuration for JA3 and HTTP/3 transports
|
|
// which handle proxies differently.
|
|
func proxyMW(client *Client, proxys any) error {
|
|
// Skip proxy configuration for JA3 transport (handled separately)
|
|
if client.builder.ja {
|
|
return nil
|
|
}
|
|
|
|
// Skip if HTTP/3 transport is being used (handled separately)
|
|
if _, ok := client.GetTransport().(*uquicTransport); ok {
|
|
return nil
|
|
}
|
|
|
|
transport, ok := client.GetTransport().(*http.Transport)
|
|
if !ok {
|
|
return fmt.Errorf("transport is not *http.Transport")
|
|
}
|
|
|
|
// Clear proxy if nil provided
|
|
if proxys == nil {
|
|
transport.Proxy = nil
|
|
return nil
|
|
}
|
|
|
|
// Helper function to set static proxy
|
|
setProxy := func(proxy string) {
|
|
if proxy == "" {
|
|
return
|
|
}
|
|
if proxyURL, err := url.Parse(proxy); err == nil {
|
|
transport.Proxy = http.ProxyURL(proxyURL)
|
|
} else {
|
|
transport.Proxy = func(*http.Request) (*url.URL, error) {
|
|
return nil, fmt.Errorf("invalid proxy URL %q: %w", proxy, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Handle static proxy configurations
|
|
switch v := proxys.(type) {
|
|
case string:
|
|
setProxy(v)
|
|
return nil
|
|
case g.String:
|
|
setProxy(v.Std())
|
|
return nil
|
|
}
|
|
|
|
// Handle dynamic proxy configurations - evaluate proxy per request
|
|
transport.Proxy = func(*http.Request) (*url.URL, error) {
|
|
var proxy string
|
|
|
|
switch v := proxys.(type) {
|
|
case func() g.String:
|
|
proxy = v().Std()
|
|
case []string:
|
|
if len(v) > 0 {
|
|
proxy = v[rand.Intn(len(v))]
|
|
}
|
|
case g.Slice[string]:
|
|
proxy = v.Random()
|
|
case g.Slice[g.String]:
|
|
proxy = v.Random().Std()
|
|
default:
|
|
return nil, fmt.Errorf("unsupported proxy type: %T", proxys)
|
|
}
|
|
|
|
if proxy == "" {
|
|
return nil, nil
|
|
}
|
|
|
|
return url.Parse(proxy)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// h2cMW configures HTTP/2 Cleartext (H2C) support for the client.
|
|
// H2C allows HTTP/2 communication over plain text connections without TLS.
|
|
// This is useful for internal communication or development scenarios where TLS is not required.
|
|
// Skips configuration if HTTP/3 transport is being used as they are incompatible.
|
|
func h2cMW(client *Client) error {
|
|
// H2C is incompatible with HTTP/3 transport - skip if HTTP/3 is being used
|
|
if _, ok := client.transport.(*uquicTransport); ok {
|
|
return nil
|
|
}
|
|
|
|
t2 := new(http2.Transport)
|
|
|
|
// Configure H2C specific settings
|
|
t2.AllowHTTP = true
|
|
t2.DisableCompression = client.GetTransport().(*http.Transport).DisableCompression
|
|
t2.IdleConnTimeout = client.transport.(*http.Transport).IdleConnTimeout
|
|
|
|
// Override TLS dial to use plain text connections
|
|
t2.DialTLSContext = func(_ context.Context, network, addr string, _ *tls.Config) (net.Conn, error) {
|
|
return net.Dial(network, addr)
|
|
}
|
|
|
|
// Apply HTTP/2 settings if configured
|
|
if client.builder.http2settings != nil {
|
|
h := client.builder.http2settings
|
|
|
|
// Helper function to append non-zero settings
|
|
appendSetting := func(id http2.SettingID, val uint32) {
|
|
if val != 0 || (id == http2.SettingEnablePush && h.usePush) {
|
|
t2.Settings = append(t2.Settings, http2.Setting{ID: id, Val: val})
|
|
}
|
|
}
|
|
|
|
// Apply all configured HTTP/2 settings
|
|
settings := [...]struct {
|
|
id http2.SettingID
|
|
val uint32
|
|
}{
|
|
{http2.SettingHeaderTableSize, h.headerTableSize},
|
|
{http2.SettingEnablePush, h.enablePush},
|
|
{http2.SettingMaxConcurrentStreams, h.maxConcurrentStreams},
|
|
{http2.SettingInitialWindowSize, h.initialWindowSize},
|
|
{http2.SettingMaxFrameSize, h.maxFrameSize},
|
|
{http2.SettingMaxHeaderListSize, h.maxHeaderListSize},
|
|
}
|
|
|
|
for _, s := range settings {
|
|
appendSetting(s.id, s.val)
|
|
}
|
|
|
|
// Apply flow control settings if configured
|
|
if h.connectionFlow != 0 {
|
|
t2.ConnectionFlow = h.connectionFlow
|
|
}
|
|
|
|
// Apply priority settings if configured
|
|
if !h.priorityParam.IsZero() {
|
|
t2.PriorityParam = h.priorityParam
|
|
}
|
|
|
|
if h.priorityFrames != nil {
|
|
t2.PriorityFrames = h.priorityFrames
|
|
}
|
|
}
|
|
|
|
client.cli.Transport = t2
|
|
|
|
return nil
|
|
}
|