browser.go 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. package utils
  2. import (
  3. "math/rand"
  4. "strconv"
  5. "time"
  6. "net/http"
  7. "strings"
  8. "github.com/klauspost/cpuid/v2"
  9. )
  10. func ChromeVersion() int {
  11. // Use only CPU info as seed for PRNG
  12. seed := int64(cpuid.CPU.Family + cpuid.CPU.Model + cpuid.CPU.PhysicalCores + cpuid.CPU.LogicalCores + cpuid.CPU.CacheLine)
  13. rng := rand.New(rand.NewSource(seed))
  14. // Start from Chrome 144 released on 2026.1.13
  15. releaseDate := time.Date(2026, 1, 13, 0, 0, 0, 0, time.UTC)
  16. version := 144
  17. now := time.Now()
  18. // Each version has random 25-45 day interval
  19. for releaseDate.Before(now) {
  20. releaseDate = releaseDate.AddDate(0, 0, rng.Intn(21)+25)
  21. version++
  22. }
  23. return version - 1
  24. }
  25. // The full Chromium brand GREASE implementation
  26. var clientHintGreaseNA = []string{" ", "(", ":", "-", ".", "/", ")", ";", "=", "?", "_"}
  27. var clientHintVersionNA = []string{"8", "99", "24"}
  28. var clientHintShuffle3 = [][3]int{{0, 1, 2}, {0, 2, 1}, {1, 0, 2}, {1, 2, 0}, {2, 0, 1}, {2, 1, 0}}
  29. var clientHintShuffle4 = [][4]int{
  30. {0, 1, 2, 3}, {0, 1, 3, 2}, {0, 2, 1, 3}, {0, 2, 3, 1}, {0, 3, 1, 2}, {0, 3, 2, 1},
  31. {1, 0, 2, 3}, {1, 0, 3, 2}, {1, 2, 0, 3}, {1, 2, 3, 0}, {1, 3, 0, 2}, {1, 3, 2, 0},
  32. {2, 0, 1, 3}, {2, 0, 3, 1}, {2, 1, 0, 3}, {2, 1, 3, 0}, {2, 3, 0, 1}, {2, 3, 1, 0},
  33. {3, 0, 1, 2}, {3, 0, 2, 1}, {3, 1, 0, 2}, {3, 1, 2, 0}, {3, 2, 0, 1}, {3, 2, 1, 0}}
  34. func getGreasedChInvalidBrand(seed int) string {
  35. return "\"Not" + clientHintGreaseNA[seed % len(clientHintGreaseNA)] + "A" + clientHintGreaseNA[(seed + 1) % len(clientHintGreaseNA)] + "Brand\";v=\"" + clientHintVersionNA[seed % len(clientHintVersionNA)] + "\"";
  36. }
  37. func getGreasedChOrder(brandLength int, seed int) []int {
  38. switch brandLength {
  39. case 1:
  40. return []int{0}
  41. case 2:
  42. return []int{seed % brandLength, (seed + 1) % brandLength}
  43. case 3:
  44. return clientHintShuffle3[seed % len(clientHintShuffle3)][:]
  45. default:
  46. return clientHintShuffle4[seed % len(clientHintShuffle4)][:]
  47. }
  48. return []int{}
  49. }
  50. func getUngreasedChUa(majorVersion int, forkName string) []string {
  51. // Set the capacity to 4, the maximum allowed brand size, so Go will never allocate memory twice
  52. baseChUa := make([]string, 0, 4)
  53. baseChUa = append(baseChUa, getGreasedChInvalidBrand(majorVersion),
  54. "\"Chromium\";v=\"" + strconv.Itoa(majorVersion) + "\"")
  55. switch forkName {
  56. case "chrome":
  57. baseChUa = append(baseChUa, "\"Google Chrome\";v=\"" + strconv.Itoa(majorVersion) + "\"")
  58. case "edge":
  59. baseChUa = append(baseChUa, "\"Microsoft Edge\";v=\"" + strconv.Itoa(majorVersion) + "\"")
  60. }
  61. return baseChUa
  62. }
  63. func getGreasedChUa(majorVersion int, forkName string) string {
  64. ungreasedCh := getUngreasedChUa(majorVersion, forkName)
  65. shuffleMap := getGreasedChOrder(len(ungreasedCh), majorVersion)
  66. shuffledCh := make([]string, len(ungreasedCh))
  67. for i, e := range shuffleMap {
  68. shuffledCh[e] = ungreasedCh[i]
  69. }
  70. return strings.Join(shuffledCh, ", ")
  71. }
  72. // It's better to pin on Firefox ESR releases, and there could be a Firefox ESR version generator later.
  73. // 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.
  74. var FirefoxUA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:140.0) Gecko/20100101 Firefox/140.0"
  75. // The code below provides a coherent default browser user agent string based on a CPU-seeded PRNG.
  76. var AnchoredChromeVersion = ChromeVersion()
  77. var 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"
  78. var ChromeUACH = getGreasedChUa(AnchoredChromeVersion, "chrome")
  79. var MSEdgeUA = ChromeUA + "Edg/" + strconv.Itoa(AnchoredChromeVersion) + ".0.0.0"
  80. var MSEdgeUACH = getGreasedChUa(AnchoredChromeVersion, "edge")
  81. func applyMasqueradedHeaders(header http.Header, browser string, variant string) {
  82. // Browser-specific.
  83. switch browser {
  84. case "chrome":
  85. header["Sec-CH-UA"] = []string{ChromeUACH}
  86. header["Sec-CH-UA-Mobile"] = []string{"?0"}
  87. header["Sec-CH-UA-Platform"] = []string{"\"Windows\""}
  88. header["DNT"] = []string{"1"}
  89. header.Set("User-Agent", ChromeUA)
  90. header.Set("Accept-Language", "en-US,en;q=0.9")
  91. case "edge":
  92. header["Sec-CH-UA"] = []string{MSEdgeUACH}
  93. header["Sec-CH-UA-Mobile"] = []string{"?0"}
  94. header["Sec-CH-UA-Platform"] = []string{"\"Windows\""}
  95. header["DNT"] = []string{"1"}
  96. header.Set("User-Agent", MSEdgeUA)
  97. header.Set("Accept-Language", "en-US,en;q=0.9")
  98. case "firefox":
  99. header.Set("User-Agent", FirefoxUA)
  100. header["DNT"] = []string{"1"}
  101. header.Set("Accept-Language", "en-US,en;q=0.5")
  102. case "golang":
  103. // Expose the default net/http header.
  104. header.Del("User-Agent")
  105. return
  106. }
  107. // Context-specific.
  108. switch variant {
  109. case "nav":
  110. if header.Get("Cache-Control") == "" {
  111. switch browser {
  112. case "chrome", "edge":
  113. header.Set("Cache-Control", "max-age=0")
  114. }
  115. }
  116. header.Set("Upgrade-Insecure-Requests", "1")
  117. if header.Get("Accept") == "" {
  118. switch browser {
  119. case "chrome", "edge":
  120. 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")
  121. case "firefox":
  122. header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
  123. }
  124. }
  125. header.Set("Sec-Fetch-Site", "none")
  126. header.Set("Sec-Fetch-Mode", "navigate")
  127. header.Set("Sec-Fetch-User", "?1")
  128. header.Set("Sec-Fetch-Dest", "document")
  129. header.Set("Priority", "u=0, i")
  130. case "ws":
  131. header.Set("Sec-Fetch-Mode", "websocket")
  132. header.Set("Sec-Fetch-Dest", "empty")
  133. header.Set("Sec-Fetch-Site", "same-origin")
  134. if header.Get("Cache-Control") == "" {
  135. header.Set("Cache-Control", "no-cache")
  136. }
  137. if header.Get("Pragma") == "" {
  138. header.Set("Pragma", "no-cache")
  139. }
  140. if header.Get("Accept") == "" {
  141. header.Set("Accept", "*/*")
  142. }
  143. case "fetch":
  144. header.Set("Sec-Fetch-Mode", "cors")
  145. header.Set("Sec-Fetch-Dest", "empty")
  146. header.Set("Sec-Fetch-Site", "same-origin")
  147. if header.Get("Priority") == "" {
  148. switch browser {
  149. case "chrome", "edge":
  150. header.Set("Priority", "u=1, i")
  151. case "firefox":
  152. header.Set("Priority", "u=4")
  153. }
  154. }
  155. if header.Get("Cache-Control") == "" {
  156. header.Set("Cache-Control", "no-cache")
  157. }
  158. if header.Get("Pragma") == "" {
  159. header.Set("Pragma", "no-cache")
  160. }
  161. if header.Get("Accept") == "" {
  162. header.Set("Accept", "*/*")
  163. }
  164. }
  165. }
  166. func TryDefaultHeadersWith(header http.Header, variant string) {
  167. // The global UA special value handler for transports. Used to be called HandleTransportUASettings.
  168. // Just a FYI to whoever needing to fix this piece of code after some spontaneous event, I tried to make the two methods separate to let the code be cleaner and more organized.
  169. if len(header.Values("User-Agent")) < 1 {
  170. applyMasqueradedHeaders(header, "chrome", variant)
  171. } else {
  172. switch header.Get("User-Agent") {
  173. case "chrome":
  174. applyMasqueradedHeaders(header, "chrome", variant)
  175. case "firefox":
  176. applyMasqueradedHeaders(header, "firefox", variant)
  177. case "edge":
  178. applyMasqueradedHeaders(header, "edge", variant)
  179. case "golang":
  180. applyMasqueradedHeaders(header, "golang", variant)
  181. }
  182. }
  183. }