mirror of
https://github.com/phishingclub/phishingclub.git
synced 2026-07-30 15:38:49 +02:00
251 lines
7.1 KiB
Go
251 lines
7.1 KiB
Go
package surf
|
|
|
|
import (
|
|
_http "net/http"
|
|
"net/url"
|
|
|
|
"github.com/enetx/http"
|
|
)
|
|
|
|
// Std returns a standard net/http.Client that wraps the configured surf client.
|
|
// This is useful for integrating with third-party libraries that expect a standard net/http.Client
|
|
// while preserving most surf features.
|
|
//
|
|
// Supported features:
|
|
// - JA3/TLS fingerprinting
|
|
// - HTTP/2 settings
|
|
// - Cookies and sessions
|
|
// - Request/Response middleware
|
|
// - Headers (User-Agent, custom headers)
|
|
// - Proxy configuration
|
|
// - Timeout settings
|
|
// - Redirect policies
|
|
// - Impersonate browser headers
|
|
//
|
|
// Known limitations:
|
|
// - Retry logic is NOT supported (implemented in Request.Do(), not in transport)
|
|
// - Response body caching is NOT supported
|
|
// - Remote address tracking is NOT supported
|
|
// - Request timing information is NOT available
|
|
//
|
|
// For applications requiring retry logic, consider implementing it at the application level
|
|
// or use surf.Client directly for those specific requests.
|
|
//
|
|
// Example usage:
|
|
//
|
|
// surfClient := surf.NewClient().
|
|
// Builder().
|
|
// JA3().Chrome().
|
|
// Session().
|
|
// Build()
|
|
//
|
|
// // For libraries expecting net/http.Client
|
|
// stdClient := surfClient.Std()
|
|
//
|
|
// botClient := &BaseBotClient{
|
|
// Client: *stdClient,
|
|
// }
|
|
func (c *Client) Std() *_http.Client {
|
|
var jar _http.CookieJar
|
|
if c.cli.Jar != nil {
|
|
jar = &CookieJarAdapter{jar: c.cli.Jar}
|
|
}
|
|
|
|
return &_http.Client{
|
|
Transport: &TransportAdapter{
|
|
transport: c.transport,
|
|
client: c,
|
|
},
|
|
Jar: jar,
|
|
Timeout: c.cli.Timeout,
|
|
CheckRedirect: redirect(c.cli.CheckRedirect),
|
|
}
|
|
}
|
|
|
|
// TransportAdapter adapts surf.Client to net/http.RoundTripper
|
|
// It uses the full surf pipeline including middleware
|
|
type TransportAdapter struct {
|
|
transport http.RoundTripper
|
|
client *Client
|
|
}
|
|
|
|
func (s *TransportAdapter) CloseIdleConnections() {
|
|
if closer, ok := s.transport.(interface{ CloseIdleConnections() }); ok {
|
|
closer.CloseIdleConnections()
|
|
}
|
|
}
|
|
|
|
// RoundTrip implements net/http.RoundTripper interface using surf's full pipeline
|
|
func (s *TransportAdapter) RoundTrip(req *_http.Request) (*_http.Response, error) {
|
|
sreq := &Request{
|
|
request: request(req),
|
|
cli: s.client,
|
|
}
|
|
|
|
if err := s.client.applyReqMW(sreq); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
_resp, err := s.client.cli.Transport.RoundTrip(sreq.request)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
resp := &Response{
|
|
response: _resp,
|
|
Client: s.client,
|
|
StatusCode: StatusCode(_resp.StatusCode),
|
|
Headers: Headers(_resp.Header),
|
|
request: sreq,
|
|
}
|
|
|
|
if err := s.client.applyRespMW(resp); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return response(resp.response, req), nil
|
|
}
|
|
|
|
// request converts net/http.Request to github.com/enetx/http.Request.
|
|
// It preserves all fields including headers, body, and context while
|
|
// adapting to the enetx/http package types.
|
|
func request(_req *_http.Request) *http.Request {
|
|
req := &http.Request{
|
|
Method: _req.Method,
|
|
URL: _req.URL,
|
|
Proto: _req.Proto,
|
|
ProtoMajor: _req.ProtoMajor,
|
|
ProtoMinor: _req.ProtoMinor,
|
|
Header: http.Header(_req.Header),
|
|
Body: _req.Body,
|
|
ContentLength: _req.ContentLength,
|
|
TransferEncoding: _req.TransferEncoding,
|
|
Close: _req.Close,
|
|
Host: _req.Host,
|
|
Form: _req.Form,
|
|
PostForm: _req.PostForm,
|
|
MultipartForm: _req.MultipartForm,
|
|
Trailer: http.Header(_req.Trailer),
|
|
RemoteAddr: _req.RemoteAddr,
|
|
RequestURI: _req.RequestURI,
|
|
TLS: _req.TLS,
|
|
Response: nil,
|
|
GetBody: _req.GetBody,
|
|
Pattern: _req.Pattern,
|
|
Cancel: _req.Cancel, // deprecated but kept for compatibility
|
|
}
|
|
|
|
return req.WithContext(_req.Context())
|
|
}
|
|
|
|
// response converts github.com/enetx/http.Response to net/http.Response.
|
|
// It preserves all response fields including status, headers, and body
|
|
// while adapting back to standard net/http types.
|
|
func response(resp *http.Response, _req *_http.Request) *_http.Response {
|
|
return &_http.Response{
|
|
Status: resp.Status,
|
|
StatusCode: resp.StatusCode,
|
|
Proto: resp.Proto,
|
|
ProtoMajor: resp.ProtoMajor,
|
|
ProtoMinor: resp.ProtoMinor,
|
|
Header: _http.Header(resp.Header),
|
|
Body: resp.Body,
|
|
ContentLength: resp.ContentLength,
|
|
TransferEncoding: resp.TransferEncoding,
|
|
Close: resp.Close,
|
|
Uncompressed: resp.Uncompressed,
|
|
Trailer: _http.Header(resp.Trailer),
|
|
Request: _req,
|
|
TLS: resp.TLS,
|
|
}
|
|
}
|
|
|
|
// CookieJarAdapter adapts github.com/enetx/http.CookieJar to net/http.CookieJar.
|
|
// It provides bidirectional cookie conversion between the two HTTP packages,
|
|
// ensuring cookies set through either interface work correctly.
|
|
type CookieJarAdapter struct{ jar http.CookieJar }
|
|
|
|
// SetCookies implements http.CookieJar interface.
|
|
// It converts standard net/http cookies to enetx/http format and
|
|
// delegates to the underlying surf cookie jar.
|
|
func (c *CookieJarAdapter) SetCookies(u *url.URL, _cookies []*_http.Cookie) {
|
|
if len(_cookies) == 0 {
|
|
c.jar.SetCookies(u, nil)
|
|
return
|
|
}
|
|
|
|
cookies := make([]*http.Cookie, 0, len(_cookies))
|
|
for _, ck := range _cookies {
|
|
cookies = append(cookies, &http.Cookie{
|
|
Name: ck.Name,
|
|
Value: ck.Value,
|
|
Quoted: ck.Quoted,
|
|
Path: ck.Path,
|
|
Domain: ck.Domain,
|
|
Expires: ck.Expires,
|
|
RawExpires: ck.RawExpires,
|
|
MaxAge: ck.MaxAge,
|
|
Secure: ck.Secure,
|
|
HttpOnly: ck.HttpOnly,
|
|
SameSite: http.SameSite(ck.SameSite),
|
|
Partitioned: ck.Partitioned,
|
|
Raw: ck.Raw,
|
|
Unparsed: ck.Unparsed,
|
|
})
|
|
}
|
|
|
|
c.jar.SetCookies(u, cookies)
|
|
}
|
|
|
|
// Cookies implements http.CookieJar interface.
|
|
// It retrieves cookies from the underlying surf cookie jar and
|
|
// converts them to standard net/http cookie format.
|
|
func (c *CookieJarAdapter) Cookies(u *url.URL) []*_http.Cookie {
|
|
cookies := c.jar.Cookies(u)
|
|
if len(cookies) == 0 {
|
|
return nil
|
|
}
|
|
|
|
_cookies := make([]*_http.Cookie, 0, len(cookies))
|
|
|
|
for _, ck := range cookies {
|
|
_cookies = append(_cookies, &_http.Cookie{
|
|
Name: ck.Name,
|
|
Value: ck.Value,
|
|
Quoted: ck.Quoted,
|
|
Path: ck.Path,
|
|
Domain: ck.Domain,
|
|
Expires: ck.Expires,
|
|
RawExpires: ck.RawExpires,
|
|
MaxAge: ck.MaxAge,
|
|
Secure: ck.Secure,
|
|
HttpOnly: ck.HttpOnly,
|
|
SameSite: _http.SameSite(ck.SameSite),
|
|
Partitioned: ck.Partitioned,
|
|
Raw: ck.Raw,
|
|
Unparsed: ck.Unparsed,
|
|
})
|
|
}
|
|
|
|
return _cookies
|
|
}
|
|
|
|
// redirect adapts surf's redirect policy function to work with standard net/http.
|
|
// It converts net/http requests to enetx/http format, calls the surf redirect policy,
|
|
// and returns the result. This ensures custom redirect policies work correctly
|
|
// through the standard http.Client interface.
|
|
func redirect(fn func(*http.Request, []*http.Request) error) func(*_http.Request, []*_http.Request) error {
|
|
if fn == nil {
|
|
return nil
|
|
}
|
|
|
|
return func(req *_http.Request, via []*_http.Request) error {
|
|
_req := request(req)
|
|
_via := make([]*http.Request, len(via))
|
|
for i := range via {
|
|
_via[i] = request(via[i])
|
|
}
|
|
return fn(_req, _via)
|
|
}
|
|
}
|