cloudscraper_rs/modules/tls/
mod.rs1use rand::Rng;
7use rand::seq::SliceRandom;
8use std::collections::HashMap;
9
10use super::spoofing::BrowserType;
11
12use crate::challenges::solvers::TlsProfileManager;
13
14#[derive(Debug, Clone)]
15pub struct BrowserProfile {
16 pub browser: BrowserType,
17 pub ja3: String,
18 pub cipher_suites: Vec<String>,
19 pub alpn_protocols: Vec<String>,
20 pub tls_extensions: Vec<u16>,
21}
22
23#[derive(Debug, Clone)]
24pub struct TLSConfig {
25 pub rotate_ja3: bool,
26 pub rotate_ciphers: bool,
27 pub preferred_browser: BrowserType,
28 pub rotation_interval: usize,
29}
30
31impl Default for TLSConfig {
32 fn default() -> Self {
33 Self {
34 rotate_ja3: true,
35 rotate_ciphers: true,
36 preferred_browser: BrowserType::Chrome,
37 rotation_interval: 5,
38 }
39 }
40}
41
42#[derive(Debug)]
43struct DomainTLSState {
44 profile_index: usize,
45 requests_since_rotation: usize,
46}
47
48impl DomainTLSState {
49 fn new(index: usize) -> Self {
50 Self {
51 profile_index: index,
52 requests_since_rotation: 0,
53 }
54 }
55}
56
57#[derive(Debug)]
59pub struct DefaultTLSManager {
60 config: TLSConfig,
61 profiles: Vec<BrowserProfile>,
62 per_domain: HashMap<String, DomainTLSState>,
63 rng: rand::rngs::ThreadRng,
64}
65
66impl DefaultTLSManager {
67 pub fn new(config: TLSConfig) -> Self {
68 let mut manager = Self {
69 profiles: build_default_profiles(),
70 rng: rand::thread_rng(),
71 per_domain: HashMap::new(),
72 config,
73 };
74 manager.promote_preferred_profile();
76 manager
77 }
78
79 fn promote_preferred_profile(&mut self) {
80 if let Some(pos) = self
81 .profiles
82 .iter()
83 .position(|p| p.browser == self.config.preferred_browser)
84 {
85 self.profiles.swap(0, pos);
86 }
87 }
88
89 fn domain_state_mut(&mut self, domain: &str) -> &mut DomainTLSState {
90 let idx = self.rng.gen_range(0..self.profiles.len());
91 self.per_domain
92 .entry(domain.to_string())
93 .or_insert_with(|| DomainTLSState::new(idx))
94 }
95
96 pub fn current_profile(&mut self, domain: &str) -> BrowserProfile {
97 let should_rotate = {
98 let state = self.domain_state_mut(domain);
99 state.requests_since_rotation += 1;
100 state.requests_since_rotation >= self.config.rotation_interval
101 };
102
103 if should_rotate {
104 self.rotate_profile(domain);
105 }
106
107 let index = self.domain_state_mut(domain).profile_index;
108 self.profiles[index].clone()
109 }
110
111 pub fn rotate_profile(&mut self, domain: &str) {
112 let profiles_len = self.profiles.len();
113 let current_index = {
114 let state = self.domain_state_mut(domain);
115 state.requests_since_rotation = 0;
116 state.profile_index
117 };
118
119 if profiles_len <= 1 {
120 return;
121 }
122
123 let mut candidates: Vec<usize> = (0..profiles_len).collect();
124 candidates.retain(|idx| *idx != current_index);
125 if let Some(next_index) = candidates.choose(&mut self.rng).copied() {
126 let state = self.domain_state_mut(domain);
127 state.profile_index = next_index;
128 }
129 }
130
131 pub fn add_custom_profile(&mut self, profile: BrowserProfile) {
132 self.profiles.push(profile);
133 }
134}
135
136impl Default for DefaultTLSManager {
137 fn default() -> Self {
138 Self::new(TLSConfig::default())
139 }
140}
141
142impl TlsProfileManager for DefaultTLSManager {
143 fn rotate_profile(&mut self, domain: &str) {
144 DefaultTLSManager::rotate_profile(self, domain);
145 }
146}
147
148fn build_default_profiles() -> Vec<BrowserProfile> {
149 vec![
150 BrowserProfile {
151 browser: BrowserType::Chrome,
152 ja3: "771,4866-4865-4867-49196-49195-52393,0-11-10-35-13-45-16-43,29-23-24,0".into(),
153 cipher_suites: vec![
154 "TLS_AES_128_GCM_SHA256".into(),
155 "TLS_AES_256_GCM_SHA384".into(),
156 "TLS_CHACHA20_POLY1305_SHA256".into(),
157 ],
158 alpn_protocols: vec!["h2".into(), "http/1.1".into()],
159 tls_extensions: vec![0, 11, 10, 35, 13, 45, 16, 43],
160 },
161 BrowserProfile {
162 browser: BrowserType::Firefox,
163 ja3: "771,4866-4865-4867-49196-49200,0-11-10-35-13-27,23-24,0".into(),
164 cipher_suites: vec![
165 "TLS_AES_128_GCM_SHA256".into(),
166 "TLS_AES_256_GCM_SHA384".into(),
167 "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256".into(),
168 ],
169 alpn_protocols: vec!["h2".into(), "http/1.1".into()],
170 tls_extensions: vec![0, 11, 10, 35, 13, 27],
171 },
172 BrowserProfile {
173 browser: BrowserType::Safari,
174 ja3: "771,4865-4866-4867-49195-49196,0-11-10-35-13-16,29-23-24,0".into(),
175 cipher_suites: vec![
176 "TLS_AES_128_GCM_SHA256".into(),
177 "TLS_CHACHA20_POLY1305_SHA256".into(),
178 "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256".into(),
179 ],
180 alpn_protocols: vec!["h2".into(), "http/1.1".into()],
181 tls_extensions: vec![0, 11, 10, 35, 13, 16],
182 },
183 BrowserProfile {
184 browser: BrowserType::MobileChrome,
185 ja3: "771,4866-4865-4867-49196,0-11-10-35-13-45,29-23-24,0".into(),
186 cipher_suites: vec![
187 "TLS_AES_128_GCM_SHA256".into(),
188 "TLS_CHACHA20_POLY1305_SHA256".into(),
189 ],
190 alpn_protocols: vec!["h2".into(), "http/1.1".into()],
191 tls_extensions: vec![0, 11, 10, 35, 13, 45],
192 },
193 BrowserProfile {
194 browser: BrowserType::MobileSafari,
195 ja3: "771,4865-4866-4867-49195,0-11-10-35-16,29-23-24,0".into(),
196 cipher_suites: vec![
197 "TLS_AES_128_GCM_SHA256".into(),
198 "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256".into(),
199 ],
200 alpn_protocols: vec!["h2".into(), "http/1.1".into()],
201 tls_extensions: vec![0, 11, 10, 35, 16],
202 },
203 ]
204}
205
206#[cfg(test)]
207mod tests {
208 use super::*;
209
210 #[test]
211 fn rotates_profiles() {
212 let mut manager = DefaultTLSManager::default();
213 let profile1 = manager.current_profile("example.com");
214 manager.rotate_profile("example.com");
215 let profile2 = manager.current_profile("example.com");
216 assert!(profile1.ja3 != profile2.ja3 || profile1.browser != profile2.browser);
217 }
218}