mirror of
https://github.com/phishingclub/phishingclub.git
synced 2026-06-08 23:43:59 +02:00
07c8adaf76
Signed-off-by: Ronni Skansing <rskansing@gmail.com>
376 lines
14 KiB
Go
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) }
|