Initial sanitized code sync
This commit is contained in:
110
pkg/stripe/browser_fp.go
Normal file
110
pkg/stripe/browser_fp.go
Normal file
@@ -0,0 +1,110 @@
|
||||
package stripe
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"math/big"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// BrowserFingerprint holds real browser fingerprint data harvested from a real browser.
|
||||
type BrowserFingerprint struct {
|
||||
CookieSupport bool `json:"cookie_support"`
|
||||
DoNotTrack bool `json:"do_not_track"`
|
||||
Language string `json:"language"`
|
||||
Platform string `json:"platform"`
|
||||
ScreenSize string `json:"screen_size"`
|
||||
UserAgent string `json:"user_agent"`
|
||||
Plugins string `json:"plugins"`
|
||||
WebGLVendor string `json:"webgl_vendor"`
|
||||
WebGLRenderer string `json:"webgl_renderer"`
|
||||
FontsBits string `json:"fonts_bits"`
|
||||
CanvasHash string `json:"canvas_hash"`
|
||||
OaiDID string `json:"oai_did"`
|
||||
CfClearance string `json:"cf_clearance"`
|
||||
CsrfToken string `json:"csrf_token"`
|
||||
Region string `json:"region"`
|
||||
StripeFpID string `json:"stripe_fingerprint_id"`
|
||||
}
|
||||
|
||||
// BrowserFingerprintPool manages a pool of pre-harvested browser fingerprints.
|
||||
type BrowserFingerprintPool struct {
|
||||
mu sync.Mutex
|
||||
fingerprints []*BrowserFingerprint
|
||||
index int
|
||||
}
|
||||
|
||||
// NewBrowserFingerprintPool loads all fingerprint JSON files from a directory.
|
||||
// If filterLang is not empty, only loads fingerprints matching that language prefix (e.g. "ko").
|
||||
func NewBrowserFingerprintPool(dir string, filterLang ...string) (*BrowserFingerprintPool, error) {
|
||||
files, err := filepath.Glob(filepath.Join(dir, "*.json"))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("glob fingerprint dir: %w", err)
|
||||
}
|
||||
|
||||
if len(files) == 0 {
|
||||
return nil, fmt.Errorf("no fingerprint files found in %s", dir)
|
||||
}
|
||||
|
||||
langPrefix := ""
|
||||
if len(filterLang) > 0 && filterLang[0] != "" {
|
||||
langPrefix = strings.ToLower(filterLang[0])
|
||||
}
|
||||
|
||||
var fps []*BrowserFingerprint
|
||||
for _, f := range files {
|
||||
data, err := os.ReadFile(f)
|
||||
if err != nil {
|
||||
log.Printf("[fingerprint] warning: skip %s: %v", f, err)
|
||||
continue
|
||||
}
|
||||
var fp BrowserFingerprint
|
||||
if err := json.Unmarshal(data, &fp); err != nil {
|
||||
log.Printf("[fingerprint] warning: skip %s: %v", f, err)
|
||||
continue
|
||||
}
|
||||
if fp.CanvasHash == "" || fp.UserAgent == "" {
|
||||
log.Printf("[fingerprint] warning: skip %s: missing canvas_hash or user_agent", f)
|
||||
continue
|
||||
}
|
||||
// Filter by language if specified
|
||||
if langPrefix != "" && !strings.HasPrefix(strings.ToLower(fp.Language), langPrefix) {
|
||||
log.Printf("[fingerprint] skip %s: language %q doesn't match %q", filepath.Base(f), fp.Language, langPrefix)
|
||||
continue
|
||||
}
|
||||
fps = append(fps, &fp)
|
||||
}
|
||||
|
||||
if len(fps) == 0 {
|
||||
return nil, fmt.Errorf("no valid fingerprints loaded from %s", dir)
|
||||
}
|
||||
|
||||
// Shuffle
|
||||
for i := len(fps) - 1; i > 0; i-- {
|
||||
n, _ := rand.Int(rand.Reader, big.NewInt(int64(i+1)))
|
||||
j := int(n.Int64())
|
||||
fps[i], fps[j] = fps[j], fps[i]
|
||||
}
|
||||
|
||||
log.Printf("[fingerprint] loaded %d browser fingerprints from %s", len(fps), dir)
|
||||
return &BrowserFingerprintPool{fingerprints: fps}, nil
|
||||
}
|
||||
|
||||
// Get returns the next fingerprint in round-robin order.
|
||||
func (p *BrowserFingerprintPool) Get() *BrowserFingerprint {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
fp := p.fingerprints[p.index%len(p.fingerprints)]
|
||||
p.index++
|
||||
return fp
|
||||
}
|
||||
|
||||
// Count returns the number of fingerprints in the pool.
|
||||
func (p *BrowserFingerprintPool) Count() int {
|
||||
return len(p.fingerprints)
|
||||
}
|
||||
Reference in New Issue
Block a user