111 lines
3.2 KiB
Go
111 lines
3.2 KiB
Go
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)
|
|
}
|