Files
phishingclub/backend/vendor/github.com/enetx/surf/builder.go
T
Ronni Skansing 07c8adaf76 update vendor deps
Signed-off-by: Ronni Skansing <rskansing@gmail.com>
2025-11-06 23:31:08 +01:00

376 lines
14 KiB
Go

package surf
import (
"context"
"fmt"
"time"
"github.com/enetx/g"
"github.com/enetx/http"
)
// browser represents the browser type being impersonated for fingerprinting.
type browser int
const (
unknownBrowser browser = iota // No specific browser fingerprinting
chromeBrowser // Chrome browser fingerprinting
firefoxBrowser // Firefox browser fingerprinting
)
// Builder provides a fluent interface for configuring HTTP clients with various advanced features
// including proxy settings, TLS fingerprinting, HTTP/2 and HTTP/3 support, retry logic,
// redirect handling, and browser impersonation capabilities.
type Builder struct {
cli *Client // The client being configured
proxy any // Proxy configuration (static string/slice or dynamic function)
checkRedirect func(*http.Request, []*http.Request) error // Custom redirect policy function
http2settings *HTTP2Settings // HTTP/2 specific settings
http3settings *HTTP3Settings // HTTP/3 specific settings
retryCodes g.Slice[int] // HTTP status codes that trigger retries
cliMWs *middleware[*Client] // Priority-ordered client middlewares
retryWait time.Duration // Wait duration between retry attempts
retryMax int // Maximum number of retry attempts
maxRedirects int // Maximum number of redirects to follow
forceHTTP1 bool // Force HTTP/1.1 protocol usage
cacheBody bool // Enable response body caching
followOnlyHostRedirects bool // Only follow redirects within same host
forwardHeadersOnRedirect bool // Preserve headers during redirects
ja bool // Enable JA3 TLS fingerprinting
singleton bool // Use singleton pattern for connection reuse
browser browser // Browser type for fingerprinting
http3 bool // Enable HTTP/3 with automatic browser detection
}
// Build sets the provided settings for the client and returns the updated client.
// It configures various settings like HTTP2, sessions, keep-alive, dial TLS, resolver,
// interface address, timeout, and redirect policy.
func (b *Builder) Build() *Client {
// Apply HTTP/3 settings lazily if enabled
if b.http3 {
http3s := b.HTTP3Settings()
switch b.browser {
case chromeBrowser:
http3s.Chrome().Set()
case firefoxBrowser:
http3s.Firefox().Set()
default:
// Default to Chrome if no browser was detected
http3s.Chrome().Set()
}
}
// apply each middleware to the Client
b.cliMWs.run(b.cli)
return b.cli
}
// With registers middleware into the client builder with optional priority.
//
// It accepts one of the following middleware function types:
// - func(*surf.Client) error — client middleware, modifies or initializes the client
// - func(*surf.Request) error — request middleware, intercepts or transforms outgoing requests
// - func(*surf.Response) error — response middleware, intercepts or transforms incoming responses
//
// Parameters:
// - middleware: A function matching one of the supported middleware types.
// - priority (optional): Integer priority level. Lower values run earlier. Defaults to 0.
//
// Middleware with the same priority are executed in order of insertion (FIFO).
// If the middleware type is not recognized, With panics with an informative error.
//
// Example:
//
// // Adding client middleware to modify client settings.
// .With(func(client *surf.Client) error {
// // Custom logic to modify the client settings.
// return nil
// })
//
// // Adding request middleware to intercept outgoing requests.
// .With(func(req *surf.Request) error {
// // Custom logic to modify outgoing requests.
// return nil
// })
//
// // Adding response middleware to intercept incoming responses.
// .With(func(resp *surf.Response) error {
// // Custom logic to handle incoming responses.
// return nil
// })
//
// Note: Ensure that middleware functions adhere to the specified function signatures to work correctly with the With method.
func (b *Builder) With(middleware any, priority ...int) *Builder {
p := g.Int(g.Slice[int](priority).Get(0).UnwrapOrDefault())
switch v := middleware.(type) {
case func(*Client) error:
b.addCliMW(v, p)
case func(*Request) error:
b.addReqMW(v, p)
case func(*Response) error:
b.addRespMW(v, p)
default:
panic(fmt.Sprintf("invalid middleware type: %T", v))
}
return b
}
// addCliMW adds a client middleware to the ClientBuilder.
func (b *Builder) addCliMW(m func(*Client) error, priority g.Int) *Builder {
b.cliMWs.add(priority, m)
return b
}
// addReqMW adds a request middleware to the ClientBuilder.
func (b *Builder) addReqMW(m func(*Request) error, priority g.Int) *Builder {
b.cli.reqMWs.add(priority, m)
return b
}
// addRespMW adds a response middleware to the ClientBuilder.
func (b *Builder) addRespMW(m func(*Response) error, priority g.Int) *Builder {
b.cli.respMWs.add(priority, m)
return b
}
func (b *Builder) Boundary(boundary func() g.String) *Builder {
return b.addCliMW(func(client *Client) error { return boundaryMW(client, boundary) }, 999)
}
// Singleton configures the client to use a singleton instance, ensuring there's only one client instance.
// This is needed specifically for JA or Impersonate functionalities.
//
// cli := surf.NewClient().
// Builder().
// Singleton(). // for reuse client
// Impersonate().
// FireFox().
// Build()
//
// defer cli.CloseIdleConnections()
func (b *Builder) Singleton() *Builder {
b.singleton = true
return b
}
// H2C configures the client to handle HTTP/2 Cleartext (h2c).
func (b *Builder) H2C() *Builder { return b.addCliMW(h2cMW, 999) }
// HTTP2Settings configures settings related to HTTP/2 and returns an http2s struct.
func (b *Builder) HTTP2Settings() *HTTP2Settings {
h2 := &HTTP2Settings{builder: b}
b.http2settings = h2
return h2
}
// HTTP3Settings configures settings related to HTTP/3 and returns an http3s struct.
func (b *Builder) HTTP3Settings() *HTTP3Settings {
h3 := &HTTP3Settings{builder: b}
b.http3settings = h3
b.http3 = false
return h3
}
// HTTP3 enables HTTP/3 with automatic browser detection.
// Settings are applied lazily in Build() based on the impersonated browser.
// Usage: surf.NewClient().Builder().Impersonate().Chrome().HTTP3().Build()
func (b *Builder) HTTP3() *Builder {
b.http3 = true
return b
}
// Impersonate configures something related to impersonation and returns an impersonate struct.
func (b *Builder) Impersonate() *Impersonate { return &Impersonate{builder: b} }
// JA configures the client to use a specific TLS fingerprint.
func (b *Builder) JA() *JA {
b.ja = true
return &JA{builder: b}
}
// UnixSocket sets the path for a Unix domain socket.
// This allows the HTTP client to connect to the server using a Unix domain
// socket instead of a traditional TCP/IP connection.
func (b *Builder) UnixSocket(address g.String) *Builder {
return b.addCliMW(func(client *Client) error { return unixSocketMW(client, address) }, 0)
}
// DNS sets the custom DNS resolver address.
func (b *Builder) DNS(dns g.String) *Builder {
return b.addCliMW(func(client *Client) error { return dnsMW(client, dns) }, 0)
}
// DNSOverTLS configures the client to use DNS over TLS.
func (b *Builder) DNSOverTLS() *DNSOverTLS { return &DNSOverTLS{builder: b} }
// Timeout sets the timeout duration for the client.
func (b *Builder) Timeout(timeout time.Duration) *Builder {
return b.addCliMW(func(client *Client) error { return timeoutMW(client, timeout) }, 0)
}
// InterfaceAddr sets the network interface address for the client.
func (b *Builder) InterfaceAddr(address g.String) *Builder {
return b.addCliMW(func(client *Client) error { return interfaceAddrMW(client, address) }, 0)
}
// Proxy sets the proxy settings for the client.
// Supports both static proxy configurations and dynamic proxy provider functions.
//
// Static proxy examples:
//
// .Proxy("socks5://127.0.0.1:9050")
// .Proxy([]string{"socks5://proxy1", "http://proxy2"})
//
// Dynamic proxy example:
//
// .Proxy(func() g.String {
// // Your proxy rotation logic here
// return "socks5://127.0.0.1:9050"
// })
func (b *Builder) Proxy(proxy any) *Builder {
b.proxy = proxy
return b.addCliMW(func(client *Client) error { return proxyMW(client, proxy) }, 0)
}
// BasicAuth sets the basic authentication credentials for the client.
func (b *Builder) BasicAuth(authentication g.String) *Builder {
return b.addReqMW(func(req *Request) error { return basicAuthMW(req, authentication) }, 900)
}
// BearerAuth sets the bearer token for the client.
func (b *Builder) BearerAuth(authentication g.String) *Builder {
return b.addReqMW(func(req *Request) error { return bearerAuthMW(req, authentication) }, 901)
}
// UserAgent sets the user agent for the client.
func (b *Builder) UserAgent(userAgent any) *Builder {
return b.addReqMW(func(req *Request) error { return userAgentMW(req, userAgent) }, 0)
}
// SetHeaders sets headers for the request, replacing existing ones with the same name.
func (b *Builder) SetHeaders(headers ...any) *Builder {
return b.addReqMW(func(r *Request) error {
r.SetHeaders(headers...)
return nil
}, 0)
}
// AddHeaders adds headers to the request, appending to any existing headers with the same name.
func (b *Builder) AddHeaders(headers ...any) *Builder {
return b.addReqMW(func(r *Request) error {
r.AddHeaders(headers...)
return nil
}, 0)
}
// AddCookies adds cookies to the request.
func (b *Builder) AddCookies(cookies ...*http.Cookie) *Builder {
return b.addReqMW(func(r *Request) error {
r.AddCookies(cookies...)
return nil
}, 0)
}
// WithContext associates the provided context with the request.
func (b *Builder) WithContext(ctx context.Context) *Builder {
return b.addReqMW(func(r *Request) error {
r.WithContext(ctx)
return nil
}, 0)
}
// ContentType sets the content type for the client.
func (b *Builder) ContentType(contentType g.String) *Builder {
return b.addReqMW(func(req *Request) error { return contentTypeMW(req, contentType) }, 0)
}
// CacheBody configures whether the client should cache the body of the response.
func (b *Builder) CacheBody() *Builder {
b.cacheBody = true
return b
}
// GetRemoteAddress configures whether the client should get the remote address.
func (b *Builder) GetRemoteAddress() *Builder { return b.addReqMW(remoteAddrMW, 0) }
// DisableKeepAlive disable keep-alive connections.
func (b *Builder) DisableKeepAlive() *Builder { return b.addCliMW(disableKeepAliveMW, 0) }
// DisableCompression disables compression for the HTTP client.
func (b *Builder) DisableCompression() *Builder { return b.addCliMW(disableCompressionMW, 0) }
// Retry configures the retry behavior of the client.
//
// Parameters:
//
// retryMax: Maximum number of retries to be attempted.
// retryWait: Duration to wait between retries.
// codes: Optional list of HTTP status codes that trigger retries.
// If no codes are provided, default codes will be used
// (500, 429, 503 - Internal Server Error, Too Many Requests, Service Unavailable).
func (b *Builder) Retry(retryMax int, retryWait time.Duration, codes ...int) *Builder {
b.retryMax = retryMax
b.retryWait = retryWait
if len(codes) == 0 {
b.retryCodes = g.SliceOf(
http.StatusInternalServerError,
http.StatusTooManyRequests,
http.StatusServiceUnavailable,
)
} else {
b.retryCodes = g.SliceOf(codes...)
}
return b
}
// ForceHTTP1MW configures the client to use HTTP/1.1 forcefully.
func (b *Builder) ForceHTTP1() *Builder {
b.forceHTTP1 = true
return b.addCliMW(forseHTTP1MW, 0)
}
// Session configures whether the client should maintain a session.
func (b *Builder) Session() *Builder { return b.addCliMW(sessionMW, 0) }
// MaxRedirects sets the maximum number of redirects the client should follow.
func (b *Builder) MaxRedirects(maxRedirects int) *Builder {
b.maxRedirects = maxRedirects
return b.addCliMW(redirectPolicyMW, 0)
}
// NotFollowRedirects disables following redirects for the client.
func (b *Builder) NotFollowRedirects() *Builder {
return b.RedirectPolicy(func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse })
}
// FollowOnlyHostRedirects configures whether the client should only follow redirects within the
// same host.
func (b *Builder) FollowOnlyHostRedirects() *Builder {
b.followOnlyHostRedirects = true
return b.addCliMW(redirectPolicyMW, 0)
}
// ForwardHeadersOnRedirect adds a middleware to the ClientBuilder object that ensures HTTP headers are
// forwarded during a redirect.
func (b *Builder) ForwardHeadersOnRedirect() *Builder {
b.forwardHeadersOnRedirect = true
return b.addCliMW(redirectPolicyMW, 0)
}
// RedirectPolicy sets a custom redirect policy for the client.
func (b *Builder) RedirectPolicy(fn func(*http.Request, []*http.Request) error) *Builder {
b.checkRedirect = fn
return b.addCliMW(redirectPolicyMW, 0)
}
// String generate a string representation of the ClientBuilder instance.
func (b Builder) String() string { return fmt.Sprintf("%#v", b) }