Files
go-garth/cloudflare.go
2025-09-05 11:59:36 -07:00

158 lines
4.7 KiB
Go

package garth
import (
"context"
"fmt"
"io"
"math/rand"
"net/http"
"time"
)
// CloudflareBypass provides methods to handle Cloudflare protection
type CloudflareBypass struct {
client *http.Client
userAgent string
maxRetries int
}
// NewCloudflareBypass creates a new CloudflareBypass instance
func NewCloudflareBypass(client *http.Client) *CloudflareBypass {
return &CloudflareBypass{
client: client,
userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
maxRetries: 3,
}
}
// MakeRequest performs a request with Cloudflare bypass techniques
func (cf *CloudflareBypass) MakeRequest(ctx context.Context, req *http.Request) (*http.Response, error) {
var resp *http.Response
var err error
for attempt := 0; attempt < cf.maxRetries; attempt++ {
// Clone the request to avoid modifying the original
clonedReq := cf.cloneRequest(req)
// Apply bypass headers
cf.applyBypassHeaders(clonedReq)
// Add random delay to simulate human behavior
if attempt > 0 {
delay := time.Duration(1000+rand.Intn(2000)) * time.Millisecond
time.Sleep(delay)
}
resp, err = cf.client.Do(clonedReq)
if err != nil {
continue
}
// Check if we got blocked by Cloudflare
if cf.isCloudflareBlocked(resp) {
resp.Body.Close()
if attempt < cf.maxRetries-1 {
// Try different user agent on retry
cf.rotateUserAgent()
continue
}
return nil, fmt.Errorf("blocked by Cloudflare after %d attempts", cf.maxRetries)
}
return resp, nil
}
return nil, err
}
// applyBypassHeaders adds headers to bypass Cloudflare
func (cf *CloudflareBypass) applyBypassHeaders(req *http.Request) {
req.Header.Set("User-Agent", cf.userAgent)
req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7")
req.Header.Set("Accept-Language", "en-US,en;q=0.9")
req.Header.Set("Accept-Encoding", "gzip, deflate, br")
req.Header.Set("Cache-Control", "max-age=0")
req.Header.Set("Sec-Ch-Ua", `"Not_A Brand";v="8", "Chromium";v="120", "Google Chrome";v="120"`)
req.Header.Set("Sec-Ch-Ua-Mobile", "?0")
req.Header.Set("Sec-Ch-Ua-Platform", `"Windows"`)
req.Header.Set("Sec-Fetch-Dest", "document")
req.Header.Set("Sec-Fetch-Mode", "navigate")
req.Header.Set("Sec-Fetch-Site", "none")
req.Header.Set("Sec-Fetch-User", "?1")
req.Header.Set("Upgrade-Insecure-Requests", "1")
req.Header.Set("DNT", "1")
// Add some randomized headers
if rand.Float32() < 0.7 {
req.Header.Set("Pragma", "no-cache")
}
if rand.Float32() < 0.5 {
req.Header.Set("Connection", "keep-alive")
}
}
// isCloudflareBlocked checks if the response indicates Cloudflare blocking
func (cf *CloudflareBypass) isCloudflareBlocked(resp *http.Response) bool {
if resp.StatusCode == 403 {
// Check for Cloudflare-specific headers or content
if resp.Header.Get("Server") == "cloudflare" {
return true
}
if resp.Header.Get("CF-Ray") != "" {
return true
}
// Check response body for Cloudflare indicators
if resp.ContentLength > 0 && resp.ContentLength < 50000 {
body, err := io.ReadAll(resp.Body)
if err == nil {
bodyStr := string(body)
if contains(bodyStr, "cloudflare") || contains(bodyStr, "Attention Required") {
return true
}
}
}
}
return false
}
// rotateUserAgent changes the user agent for retry attempts
func (cf *CloudflareBypass) rotateUserAgent() {
userAgents := []string{
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/121.0",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Safari/605.1.15",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0",
}
cf.userAgent = userAgents[rand.Intn(len(userAgents))]
}
// cloneRequest creates a copy of the HTTP request
func (cf *CloudflareBypass) cloneRequest(req *http.Request) *http.Request {
cloned := req.Clone(req.Context())
if cloned.Header == nil {
cloned.Header = make(http.Header)
}
return cloned
}
// contains is a case-insensitive string contains check
func contains(s, substr string) bool {
return len(s) >= len(substr) &&
(s == substr ||
len(s) > len(substr) &&
(s[:len(substr)] == substr ||
s[len(s)-len(substr):] == substr ||
containsAt(s, substr)))
}
func containsAt(s, substr string) bool {
for i := 0; i <= len(s)-len(substr); i++ {
if s[i:i+len(substr)] == substr {
return true
}
}
return false
}