127 lines
3.3 KiB
Go
127 lines
3.3 KiB
Go
package util
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"errors"
|
|
"net/http"
|
|
"time"
|
|
)
|
|
|
|
// Full credit to https://github.com/DaRealFreak/cloudflare-bp-go
|
|
|
|
// RetryConfig configures the retry behavior
|
|
type RetryConfig struct {
|
|
MaxRetries int
|
|
RetryDelay time.Duration
|
|
TimeoutOnly bool // Only retry on timeout errors
|
|
}
|
|
|
|
// cloudFlareRoundTripper is a custom round tripper add the validated request headers.
|
|
type cloudFlareRoundTripper struct {
|
|
inner http.RoundTripper
|
|
options Options
|
|
retry *RetryConfig
|
|
}
|
|
|
|
// Options the option to set custom headers
|
|
type Options struct {
|
|
AddMissingHeaders bool
|
|
Headers map[string]string
|
|
}
|
|
|
|
// AddCloudFlareByPass returns a round tripper adding the required headers for the CloudFlare checks
|
|
// and updates the TLS configuration of the passed inner transport.
|
|
func AddCloudFlareByPass(inner http.RoundTripper, options ...Options) http.RoundTripper {
|
|
if trans, ok := inner.(*http.Transport); ok {
|
|
trans.TLSClientConfig = getCloudFlareTLSConfiguration()
|
|
}
|
|
|
|
roundTripper := &cloudFlareRoundTripper{
|
|
inner: inner,
|
|
retry: &RetryConfig{
|
|
MaxRetries: 3,
|
|
RetryDelay: 2 * time.Second,
|
|
TimeoutOnly: true,
|
|
},
|
|
}
|
|
|
|
if options != nil && len(options) > 0 {
|
|
roundTripper.options = options[0]
|
|
} else {
|
|
roundTripper.options = GetDefaultOptions()
|
|
}
|
|
|
|
return roundTripper
|
|
}
|
|
|
|
// RoundTrip adds the required request headers to pass CloudFlare checks.
|
|
func (ug *cloudFlareRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) {
|
|
var lastErr error
|
|
attempts := 0
|
|
|
|
for attempts <= ug.retry.MaxRetries {
|
|
// Add headers for this attempt
|
|
if ug.options.AddMissingHeaders {
|
|
for header, value := range ug.options.Headers {
|
|
if _, ok := r.Header[header]; !ok {
|
|
if header == "User-Agent" {
|
|
// Generate new random user agent for each attempt
|
|
r.Header.Set(header, GetRandomUserAgent())
|
|
} else {
|
|
r.Header.Set(header, value)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Make the request
|
|
var resp *http.Response
|
|
var err error
|
|
|
|
// in case we don't have an inner transport layer from the round tripper
|
|
if ug.inner == nil {
|
|
resp, err = (&http.Transport{
|
|
TLSClientConfig: getCloudFlareTLSConfiguration(),
|
|
ForceAttemptHTTP2: false,
|
|
}).RoundTrip(r)
|
|
} else {
|
|
resp, err = ug.inner.RoundTrip(r)
|
|
}
|
|
|
|
// If successful or not a timeout error, return immediately
|
|
if err == nil || (ug.retry.TimeoutOnly && !errors.Is(err, http.ErrHandlerTimeout)) {
|
|
return resp, err
|
|
}
|
|
|
|
lastErr = err
|
|
attempts++
|
|
|
|
// If we have more retries, wait before next attempt
|
|
if attempts <= ug.retry.MaxRetries {
|
|
time.Sleep(ug.retry.RetryDelay)
|
|
}
|
|
}
|
|
|
|
return nil, lastErr
|
|
}
|
|
|
|
// getCloudFlareTLSConfiguration returns an accepted client TLS configuration to not get detected by CloudFlare directly
|
|
// in case the configuration needs to be updated later on: https://wiki.mozilla.org/Security/Server_Side_TLS .
|
|
func getCloudFlareTLSConfiguration() *tls.Config {
|
|
return &tls.Config{
|
|
CurvePreferences: []tls.CurveID{tls.CurveP256, tls.CurveP384, tls.CurveP521, tls.X25519},
|
|
}
|
|
}
|
|
|
|
// GetDefaultOptions returns the options set by default
|
|
func GetDefaultOptions() Options {
|
|
return Options{
|
|
AddMissingHeaders: true,
|
|
Headers: map[string]string{
|
|
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
|
|
"Accept-Language": "en-US,en;q=0.5",
|
|
"User-Agent": GetRandomUserAgent(),
|
|
},
|
|
}
|
|
}
|