11package utils
22
33import (
4+ "hash/fnv"
5+ "math"
46 "math/rand"
57 "strconv"
68 "time"
@@ -10,20 +12,57 @@ import (
1012 "github.com/klauspost/cpuid/v2"
1113)
1214
15+ func GetRandomizer () * rand.Rand {
16+ // Seed the PRNG with the hash of CPU info, increasing the overall probable space.
17+ fnvHash := fnv .New64 ()
18+ fnvHash .Write ([]byte (strconv .Itoa (cpuid .CPU .Family ) + strconv .Itoa (cpuid .CPU .Model ) + strconv .Itoa (cpuid .CPU .PhysicalCores ) + strconv .Itoa (cpuid .CPU .LogicalCores ) + strconv .Itoa (cpuid .CPU .CacheLine ) + strconv .Itoa (cpuid .CPU .ThreadsPerCore )))
19+ return rand .New (rand .NewSource (int64 (fnvHash .Sum64 ())))
20+ }
21+ var globalRng * rand.Rand = GetRandomizer ()
22+
23+ // The Chrome version generator will suffer from deviation of a normal distribution.
1324func ChromeVersion () int {
14- // Use only CPU info as seed for PRNG
15- seed := int64 (cpuid .CPU .Family + cpuid .CPU .Model + cpuid .CPU .PhysicalCores + cpuid .CPU .LogicalCores + cpuid .CPU .CacheLine )
16- rng := rand .New (rand .NewSource (seed ))
17- // Start from Chrome 144 released on 2026.1.13
18- releaseDate := time .Date (2026 , 1 , 13 , 0 , 0 , 0 , 0 , time .UTC )
19- version := 144
20- now := time .Now ()
21- // Each version has random 25-45 day interval
22- for releaseDate .Before (now ) {
23- releaseDate = releaseDate .AddDate (0 , 0 , rng .Intn (21 )+ 25 )
24- version ++
25+ // Start from Chrome 144, released on 2026.1.13.
26+ var startVersion int = 144
27+ var timeStart int64 = time .Date (2026 , 1 , 13 , 0 , 0 , 0 , 0 , time .UTC ).Unix () / 86400
28+ var timeCurrent int64 = time .Now ().Unix () / 86400
29+ var timeDiff int = int ((timeCurrent - timeStart - 35 )) - int (math .Floor (math .Pow (globalRng .Float64 (), 2 ) * 105 ))
30+ return startVersion + (timeDiff / 35 ) // It's 31.15 currently.
31+ }
32+
33+ var safariMinorMap [25 ]int = [25 ]int {0 , 0 , 0 , 1 , 1 ,
34+ 1 , 2 , 2 , 2 , 2 , 3 , 3 , 3 , 4 , 4 ,
35+ 4 , 5 , 5 , 5 , 5 , 5 , 6 , 6 , 6 , 6 }
36+
37+ // The following version generators use deterministic generators, but with the distribution scaled by a curve.
38+ func CurlVersion () string {
39+ // curl 8.0.0 was released on 20/03/2023.
40+ var timeCurrent int64 = time .Now ().Unix () / 86400
41+ var timeStart int64 = time .Date (2023 , 3 , 20 , 0 , 0 , 0 , 0 , time .UTC ).Unix () / 86400
42+ var timeDiff int = int ((timeCurrent - timeStart - 60 )) - int (math .Floor (math .Pow (globalRng .Float64 (), 2 ) * 165 ))
43+ var minorValue int = int (timeDiff / 57 ) // The release cadence is actually 56.67 days.
44+ return "8." + strconv .Itoa (minorValue ) + ".0"
45+ }
46+ func FirefoxVersion () int {
47+ // Firefox 128 ESR was released on 09/07/2023.
48+ var timeCurrent int64 = time .Now ().Unix () / 86400
49+ var timeStart int64 = time .Date (2024 , 7 , 29 , 0 , 0 , 0 , 0 , time .UTC ).Unix () / 86400
50+ var timeDiff = timeCurrent - timeStart - 25 - int64 (math .Floor (math .Pow (globalRng .Float64 (), 2 ) * 50 ))
51+ return int (timeDiff / 30 ) + 128
52+ }
53+ func SafariVersion () string {
54+ var anchoredTime time.Time = time .Now ()
55+ var releaseYear int = anchoredTime .Year ()
56+ var splitPoint time.Time = time .Date (releaseYear , 9 , 23 , 0 , 0 , 0 , 0 , time .UTC )
57+ var delayedDays = int (math .Floor (math .Pow (globalRng .Float64 (), 3 ) * 75 ))
58+ splitPoint = splitPoint .AddDate (0 , 0 , delayedDays )
59+ if (anchoredTime .Compare (splitPoint ) < 0 ) {
60+ releaseYear --
61+ splitPoint = time .Date (releaseYear , 9 , 23 , 0 , 0 , 0 , 0 , time .UTC )
62+ splitPoint = splitPoint .AddDate (0 , 0 , delayedDays )
2563 }
26- return version - 1
64+ var minorVersion = safariMinorMap [(anchoredTime .Unix () - splitPoint .Unix ()) / 1296000 ]
65+ return strconv .Itoa (releaseYear - 1999 ) + "." + strconv .Itoa (minorVersion )
2766}
2867
2968// The full Chromium brand GREASE implementation
@@ -49,7 +88,7 @@ func getGreasedChOrder(brandLength int, seed int) []int {
4988 default :
5089 return clientHintShuffle4 [seed % len (clientHintShuffle4 )][:]
5190 }
52- return []int {}
91+ // return []int{}
5392}
5493func getUngreasedChUa (majorVersion int , forkName string ) []string {
5594 // Set the capacity to 4, the maximum allowed brand size, so Go will never allocate memory twice
@@ -74,11 +113,12 @@ func getGreasedChUa(majorVersion int, forkName string) string {
74113 return strings .Join (shuffledCh , ", " )
75114}
76115
77- // It's better to pin on Firefox ESR releases, and there could be a Firefox ESR version generator later.
78- // However, if the Firefox fingerprint in uTLS doesn't have its update cadence match that of Firefox ESR, then it's better to update the Firefox version manually instead every time a new major ESR release is available.
79- var FirefoxUA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:140.0) Gecko/20100101 Firefox/140.0"
80-
81116// The code below provides a coherent default browser user agent string based on a CPU-seeded PRNG.
117+ var CurlUA = "curl/" + CurlVersion ()
118+ var AnchoredFirefoxVersion = strconv .Itoa (FirefoxVersion ())
119+ var FirefoxUA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:" + AnchoredFirefoxVersion + ".0) Gecko/20100101 Firefox/" + AnchoredFirefoxVersion + ".0"
120+ var SafariUA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/" + SafariVersion () + " Safari/605.1.15"
121+ // Chromium browsers.
82122var AnchoredChromeVersion = ChromeVersion ()
83123var ChromeUA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/" + strconv .Itoa (AnchoredChromeVersion ) + ".0.0.0 Safari/537.36"
84124var ChromeUACH = getGreasedChUa (AnchoredChromeVersion , "chrome" )
@@ -106,10 +146,16 @@ func applyMasqueradedHeaders(header http.Header, browser string, variant string)
106146 header .Set ("User-Agent" , FirefoxUA )
107147 header ["DNT" ] = []string {"1" }
108148 header .Set ("Accept-Language" , "en-US,en;q=0.5" )
149+ case "safari" :
150+ header .Set ("User-Agent" , SafariUA )
151+ header .Set ("Accept-Language" , "en-US,en;q=0.9" )
109152 case "golang" :
110153 // Expose the default net/http header.
111154 header .Del ("User-Agent" )
112155 return
156+ case "curl" :
157+ header .Set ("User-Agent" , CurlUA )
158+ return
113159 }
114160 // Context-specific.
115161 switch variant {
@@ -125,18 +171,28 @@ func applyMasqueradedHeaders(header http.Header, browser string, variant string)
125171 switch browser {
126172 case "chrome" , "edge" :
127173 header .Set ("Accept" , "text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7" )
128- case "firefox" :
174+ case "firefox" , "safari" :
129175 header .Set ("Accept" , "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" )
130176 }
131177 }
132178 header .Set ("Sec-Fetch-Site" , "none" )
133179 header .Set ("Sec-Fetch-Mode" , "navigate" )
134- header .Set ("Sec-Fetch-User" , "?1" )
180+ switch browser {
181+ case "safari" :
182+ default :
183+ header .Set ("Sec-Fetch-User" , "?1" )
184+ }
135185 header .Set ("Sec-Fetch-Dest" , "document" )
136186 header .Set ("Priority" , "u=0, i" )
137187 case "ws" :
138188 header .Set ("Sec-Fetch-Mode" , "websocket" )
139- header .Set ("Sec-Fetch-Dest" , "empty" )
189+ switch browser {
190+ case "safari" :
191+ // Safari is NOT web-compliant here!
192+ header .Set ("Sec-Fetch-Dest" , "websocket" )
193+ default :
194+ header .Set ("Sec-Fetch-Dest" , "empty" )
195+ }
140196 header .Set ("Sec-Fetch-Site" , "same-origin" )
141197 if header .Get ("Cache-Control" ) == "" {
142198 header .Set ("Cache-Control" , "no-cache" )
@@ -157,6 +213,8 @@ func applyMasqueradedHeaders(header http.Header, browser string, variant string)
157213 header .Set ("Priority" , "u=1, i" )
158214 case "firefox" :
159215 header .Set ("Priority" , "u=4" )
216+ case "safari" :
217+ header .Set ("Priority" , "u=3, i" )
160218 }
161219 }
162220 if header .Get ("Cache-Control" ) == "" {
@@ -182,8 +240,12 @@ func TryDefaultHeadersWith(header http.Header, variant string) {
182240 applyMasqueradedHeaders (header , "chrome" , variant )
183241 case "firefox" :
184242 applyMasqueradedHeaders (header , "firefox" , variant )
243+ case "safari" :
244+ applyMasqueradedHeaders (header , "safari" , variant )
185245 case "edge" :
186246 applyMasqueradedHeaders (header , "edge" , variant )
247+ case "curl" :
248+ applyMasqueradedHeaders (header , "curl" , variant )
187249 case "golang" :
188250 applyMasqueradedHeaders (header , "golang" , variant )
189251 }
0 commit comments